SBSUV4ZJX7FZS7JUBQBSVENWODXRKTXSNLG57OGPLUXNN5SVEQJAC packages:- channel
importers:content:dependencies:uuid: 8.3.0ws: 6.2.1specifiers:uuid: ^8.0.0ws: ^6.0.0lockfileVersion: 5.1packages:/async-limiter/1.0.1:dev: falseresolution:integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==/uuid/8.3.0:dev: falsehasBin: trueresolution:integrity: sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==/ws/6.2.1:dependencies:async-limiter: 1.0.1dev: falseresolution:integrity: sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
import crypto from "crypto"class Session {constructor() {this.cleaner = setInterval(this.clean.bind(this), 1000 * 60 * 60)this.key = '2390fbad9157e1f8de9ecb5a494feeb988dfe2ba31c39ca1ba91ba2fa9d30d20'}clean() {const timeouts = datastore.read('/session/connections/+/tokens/+')_.forEach(timeouts, (timeout, path) => {if (+ new Date < timeout) { return }const pointer = Pointer.create(path)pointer.replace('/timeout', '')datastore.destroy(pointer)})}fetchSession(cookie, ws, channels) {console.log(`fetchSession\n\ntoken:\n${cookie}`)const response = {o: 'a',c: 5,v: false}// {// tokens: ,// topics: []// }const [user, token, mac] = cookie.split(':')const verify = crypto.createHmac('sha256',user + ':' + token,this.key).digest('hex')const session = datastore.read(`/session/connections/${user}`)if (!session) returnlet topics = []if (crypto.timingSafeEqual(Buffer.from(verify, 'utf8'), Buffer.from(mac, 'utf8')) &&session && session.tokens) {_.find(session.tokens, (timestamp, stored_token) => {if (crypto.timingSafeEqual(Buffer.from(stored_token, 'utf8'), Buffer.from(token, 'utf8')) &×tamp > + new Date) {response.v = { u: user, s: session.topics || [] }return true}})}_.forEach((session.topics || []), topic => {console.log(topic)const pointer = Pointer.create(topic)const channel = channels[pointer.trunk_steps[0]]channel.resume(pointer, ws)})ws.send(JSON.stringify(response))}requestSession(user, ws, users) {console.log("FFFFFFFFFFFFFFFFF")if (!users.isAuthorized(Pointer.create(`state/users/${user}/#`), ws)) {return}const token = crypto.randomBytes(256).toString('hex')const cookie = user + ':' + tokenconsole.log(`postSession for ${user} \n\ntoken:\n${token}`)const mac = crypto.createHmac('sha256',user + ':' + token,this.key).digest('hex')console.log('L')console.log(mac)datastore.write(`/session/connections/${user}/tokens/${token}`, + new Date + 60 * 60 * 24 * 7 * 1000)const response = {o: 'a',c: 6,v: cookie + ':' + mac}ws.send(JSON.stringify(response))}addSubscription(user, pointer) {console.log(`#addSubscription ${user}, ${pointer.topic}`)const topics = new Set(datastore.read(`/session/connections/${user}/topics`) || [])topics.add(pointer.topic)datastore.write(`/session/connections/${user}/topics`, Array.from(topics))}toString() {return 'Channel-Connections'}}const session = new Session()export { session }
import WebSocket from 'ws'import { v4 as uuid } from 'uuid'import './models/item'import './models/event'import './models/user'import './models/admin'import './models/stash'import './models/spotify'import { events } from './channels/event.js'import { users } from './channels/user.js'import { items } from './channels/item.js'import { admin } from './channels/admin.js'import { session } from './session.js'let main = () => {/* Models */const channels = {events: events,users: users,items: items,admin: admin}console.log(channels)/* IPC Sockets */const port = 25706const wss = new WebSocket.Server({ port })console.log(`Listenning on ${port}`)wss.on('connection', function connection(ws, request) {const ip = request.socket.remoteAddressws.uuid = uuid()ws.toString = () => ws.uuidconsole.log(`Connection from ${ip}`)datastore.push('/session/connections', ip)const node_id = datastore.read('/session/node_id')const hello = {}hello.o = 'w'hello.v = node_idhello.p = '/session/node_id'ws.send(JSON.stringify(hello))const publish = (topic, pointer, value) => {const message = {}message.o = 'p'message.p = pointer.pathmessage.v = value//console.log(`sending: ${JSON.stringify(message)}`)ws.send(JSON.stringify(message))}ws.on('message', (message) => {console.log(message)message = JSON.parse(message)if (!message.o) {return}let pointer, channelswitch (message.o) {case 'a':console.dir(message)switch (message.c) {case 0:// createAccountpointer = Pointer.create(message.t)channel = channels[pointer.trunk_steps[0]]channel.createAuthorization(pointer, message.v, ws)breakcase 1:// startSessionpointer = Pointer.create(message.t)channel = channels[pointer.trunk_steps[0]]channel.authorize(pointer, message.v, ws)breakcase 4:// verifySessionpointer = Pointer.create(message.t)channel = channels[pointer.trunk_steps[0]]if (channel.prove(pointer, message.v, ws)) {session.addSubscription(message.v.u, pointer)}breakcase 5:// post sessionsession.fetchSession(message.v, ws, channels)breakcase 6:// fetch sessionsession.requestSession(message.v, ws, users)default:break}breakcase 'm':pointer = Pointer.create(message.p)channel = channels[pointer.trunk_steps[0]]channel.merge(pointer, message.v, ws)breakcase 'w':pointer = Pointer.create(message.p)channel = channels[pointer.trunk_steps[0]]channel.write(pointer, message.v, ws)breakcase 'd':pointer = Pointer.create(message.p)channel = channels[pointer.trunk_steps[0]]channel.delete(pointer, ws)breakcase 's':pointer = Pointer.create(message.p)channel = channels[pointer.trunk_steps[0]]channel.subscribe(pointer, ws, publish)if (!message.i) breakcase 'r':pointer = Pointer.create(message.p)channel = channels[pointer.trunk_steps[0]]_.forEach(channel.read(pointer, ws), (value, path) => {const response = { o: 'r' }response.p = pathresponse.v = valuews.send(JSON.stringify(response))})break}})ws.on('close', () => {datastore.pull('/session/connections', ip)_.forEach(channels, channel => {channel.unsubscribe(ws)})console.log('disconnected')})})const cleanup = () => {console.log('\rShutting down server') // eslint-disable-line no-consoleprocess.removeListener('SIGINT', cleanup)process.removeListener('SIGTERM', cleanup)wss.close(() => {datastore.destroy('')process.exit()})}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)}// TODO: Figure out a better way to prevent jsdoctest from executing this.if (!process.env.TEST) {main()}
class User {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/state/users/+/#', this, this.onStateQueued.bind(this))}toString() {return 'Model-Users'}onStateQueued(topic, pointer, value) {console.log(`${this.toString()} #onStateQueued: ${pointer.path}, ${JSON.stringify(value)}`)if (!value) { return }if (pointer.leaf == 'vote') {const event_id = datastore.read(`/state${pointer.trunk_path}/event_id`)console.log('event_id', pointer.branch_path, `/state${pointer.trunk_path}/event_id`, event_id)if (!event_id) { return }datastore.set(`/q/state/events/${event_id}${pointer.branch_path}`, value)datastore.set(pointer, null, { silent: true })console.log(`Transfer vote to /q/state/${pointer.branch_steps.slice(2,4).join('/')}${pointer.trunk_path}/${pointer.branch_steps.slice(-3).join('/')}`)datastore.set(`/q/state/${pointer.branch_steps.slice(2,4).join('/')}${pointer.trunk_path}/${pointer.branch_steps.slice(-3).join('/')}`, value)datastore.set(pointer, null, { silent: true })} else if (pointer.leaf == 'pin') {this.accept(pointer, value)pointer.leaf = 'srp'datastore.set(pointer.path, 'authenticated')} else {this.accept(pointer, value)}}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()console.log(dequeued_pointer.path)datastore.set(dequeued_pointer, value, { force })}destroy() {}}const users = new User()const cleanup = () => {users.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { users }
import fs from 'fs'class Stash {constructor() {console.log(`${this.toString()} #constructor`)setInterval(this.stash.bind(this), 5000)}toString() {return 'Model-Stash'}stash() {console.log(`${this.toString()} #stash`)const tree = {}if (datastore.has('/state')) Object.assign(tree, { state: datastore.read('/state') })if (datastore.has('/setup')) Object.assign(tree, { setup: datastore.read('/setup') })fs.writeFile('./.stash.json',JSON.stringify(tree, null, 2),(error) => {if (!error) returnconsole.log("Error Writing to Stash")console.log(error)})}destroy() {clearInterval(this.interval)}}const stash = new Stash()const cleanup = () => {stash.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { stash }
import https from 'https'class Driver {constructor() {this.chain = datastore.chain()this.chain.link('start', 'action/events/+/start', this.initializePlaylist.bind(this))this.debouncePushPlaylistDetails = _.debounce(this.pushPlaylistDetails, 1000)this.chain.link('name', 'action/events/+/name', this.debouncePushPlaylistDetails.bind(this))this.chain.link('description', 'action/events/+/description', this.debouncePushPlaylistDetails.bind(this))this.chain.link('queue_playlist', 'q/state/events/+/playlist', this.onPlaylistItemsQueued.bind(this))}toString() {return 'Driver-Spotify-Database'}token() {return datastore.read('/setup/admin/token')}initializePlaylist(value, pointer) {console.log(`#initialize_playlist ${pointer.path}`)const eventId = pointer.branch_path.split('/').slice(-1)[0]const name = eventIdconst admin_id = datastore.read("/setup/admin/id")const body = {name,public: true,collaborative: false}const request = https.request({method: 'POST',hostname: 'api.spotify.com',path: `/v1/users/${admin_id}/playlists`,headers: {'Authorization': `Bearer ${this.token()}`,'Content-Type': 'application/json'}},response => {console.log(response.statusCode)if (![200, 201].includes(response.statusCode)) { return }let body = ''response.on('data', chunk => {console.log(body)body += chunk.toString()})response.on('end', () => {this.decodeInitializePlaylist(JSON.parse(body))})})request.on('error', e => {console.error(`problem with request ${e.message}`)})request.write(JSON.stringify(body))request.end()}decodeInitializePlaylist(response) {console.log(`#decodeInitializePlaylist`)console.log(response)const control_id = response['id']const djin_id = response['name']const externalUrl = response['external_urls']['spotify']const branch_path = `/events/${djin_id}`datastore.write(`/setup${branch_path}/spotify_id`, control_id)this.updateInitializedPlaylist(branch_path)}updateInitializedPlaylist(branch_path) {const name = datastore.read(`/setup${branch_path}/name`)const description = datastore.read(`/setup${branch_path}/description`)const options = {name,description: description + `\nCreated with Djinlist (www.djinlist.ca).`}this.changePlaylistDetails(branch_path, options, branch_path => {datastore.write(`/setup${branch_path}/initialized`, true)})}onPushPlaylistDetails(topic, pointer, value) {datastore.write(`/setup${pointer.branch_path}/details_synced`, false)this.debouncePushPlaylistDetails(value, pointer)}pushPlaylistDetails(value, pointer) {const name = datastore.read(`/setup${pointer.branch_path}/name`)const description = datastore.read(`/setup${pointer.branch_path}/description`)const options = {}if (name) { Object.assign(options, { name }) }if (description) { Object.assign(options, { description }) }this.changePlaylistDetails(pointer.branch_path, options, branch_path => {datastore.write(`/setup${branch_path}/details_synced`, true)})}changePlaylistDetails(branch_path, options, callback) {const spotify_id = datastore.read(`/setup${branch_path}/spotify_id`)console.log(`#changePlaylistDetails ${branch_path}, ${spotify_id}`)console.log('options:', options)const request = https.request({method: 'PUT',hostname: 'api.spotify.com',path: `/v1/playlists/${spotify_id}`,headers: {'Authorization': `Bearer ${this.token()}`,'Content-Type': 'application/json'}},response => {response.on('end', () => callback(branch_path))})request.on('error', e => {console.error(`problem with request ${e.message}`)})request.write(JSON.stringify(options))request.end()}onTokenExpiry(topic, pointer, value) {const time = value - (+ new Date())console.log('#onTokenExpiry #{time}')if (this.refreshTokenInterval) {clearInterval(this.refreshTokenInterval)}if (time > 0) {this.refreshTokenInterval = setInterval(this.refreshToken.bind(this), time * 1000)} else {this.refreshToken()}}refreshToken() {const refreshToken = datastore.read(`/setup/admin/refresh_id`)console.log(`#refreshToken ${refresh_id}`)if (!refreshToken) { return }const body = {grant_type: 'refresh_token',refresh_token: refreshToken}request = https.request({method: 'POST',hostname: 'accounts.spotify.com',path: '/api/token',headers: {'Authorization': `Basic ZmUwMDk5M2ZmOTNlNDgyNzgwNGFmMTZlMWRlMzEyZGU6ODQ1NzQzNzhkMDg2NDQwZGI2MDczNmRiN2MxNzc1Mzg=`,'Content-Type': 'application/json'}},response => {if (![200, 201].includes(response.statusCode)) { return }let body = ''response.on('data', chunk => {body += chunk.toString()})response.on('end', () => {this.decodeTokenRefresh(JSON.parse(body))})})request.on('error', e => {console.log(`problem with request ${e.message}`)})request.write(JSON.stringify(body))request.end()}decodeRefreshToken(message) {console.log(`#decodeRefreshToken ${JSON.stringify(message)}`)const new_expiry = (+ new Date()) + message.expires_indatastore.write('/setup/admin/token', message.access_token)datastore.write('/setup/admin/expiry', new_expiry)}onPlaylistItemsQueued(value, pointer) {if (!value) returnconsole.log(`#onPlaylistItemsQueued ${value.length}`)const spotify_id = datastore.read(`/setup${pointer.branch_path}/spotify_id`)// const length = datastore.read(`/setup${pointer.branch_path}/spotify_id`)const request = https.request({method: 'PUT',hostname: 'api.spotify.com',path: `/v1/playlists/${spotify_id}/tracks`,headers: {'Authorization': `Bearer ${this.token()}`,'Content-Type': 'application/json'}})request.on('error', e => {console.log(`problem with request ${e.message}`)})const tracks = _.reduce(value || [], (acc, track) => {return _.concat(acc, [track[0]])}, [])request.write(JSON.stringify({uris: tracks}))request.end()}destroy() {}}const spotify = new Driverconst cleanup = () => {spotify.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { spotify }
class Item {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/state/items/+/#', this, this.onStateQueued.bind(this))}toString() {return 'Model-Items'}onStateQueued(topic, pointer, value) {console.log(`${this.toString()} #onStateQueued: ${pointer.path}, ${JSON.stringify(value)}`)this.accept(pointer, value)}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer, value, { force })}destroy() {}}const items = new Item()const cleanup = () => {items.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { items }
class Event {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/state/events/+/#', this, this.onStateQueued.bind(this))datastore.subscribe('q/setup/events/+/#', this, this.onSetupQueued.bind(this))this.ticker = setInterval(this.publishPlaylists.bind(this), 60 * 1000)}toString() {return 'Model-Events'}onStateQueued(topic, pointer, value) {console.log(`${this.toString()} #onStateQueued: ${pointer.path}, ${JSON.stringify(value)}`)switch (pointer.branch_path.length) {case 4:if (pointer.branch_steps[2] == 'users') {this.onUserQueued(topic, pointer, value)}breakdefault:this.accept(pointer, value)break}}onSetupQueued(topic, pointer, value) {console.log(`${this.toString()} #onSetupQueued: ${pointer.path}, ${JSON.stringify(value)}`)this.accept(pointer, value)}onUserQueued(topic, pointer, value) {switch (pointer.leaf) {case 'vote':this.accept(pointer, value)}}publishPlaylist(name, pointer) {const issueAt = datastore.read(pointer.path + '/issue_at')const currentTime = + new Date()console.log(`#publishPlaylist ${name} ${issueAt && (issueAt < currentTime)}`)if (!issueAt // &&// currentTime.getDay() == 5 ||// currentTime.getHours() == 20 ||// currentTime.getMinutes() == 0) {this.issuePlaylist(name, pointer)} else if (issueAt < currentTime) {this.issuePlaylist(name, pointer)// Determine the next time we need to update the playlistconst intervalDays = datastore.read(pointer.path + '/interval')const intervalSeconds = intervalDays * 24 * 60 * 60datastore.write(pointer.path + '/issue_at', issueAt + intervalSeconds)}}publishPlaylists() {const playlists = datastore.read('setup/events/+/name')_.forEach(playlists, (playlist, path) => {const pointer = Pointer.create(path)this.publishPlaylist(playlist, pointer.slice(0, -1))})}tally(pointer) {const items = datastore.read(`/state${pointer.trunk_path}/users/+/items/+/vote`)const length = datastore.read(`/state${pointer.trunk_path}/users/+/items/+/vote`)const tally = {}_.forEach(items, (value, path) => {const id = path.split('/').slice(-2, -1)[0]if (tally[id]) {tally[id] += 1} else {tally[id] = 1}})const sorted_list = _.reverse(_.sortBy(_.entries(tally), entry => entry[1]))const current_list = datastore.read(`/state${pointer.trunk_path}/playlist`)let match = trueif (current_list) {for (var idx in current_list) {if (current_list[idx] != sorted_list[idx]) { match = false }}} else {match = false}if (match) returndatastore.write(`/q/state${pointer.trunk_path}/playlist`, sorted_list)}issuePlaylist(name, pointer) {console.log(`#issuePlaylist ${name} at ${pointer.path}`)const exists = datastore.read(`/setup${pointer.trunk_path}/spotify_id`)if (exists) {this.tally(pointer)} else {datastore.write(`/action${pointer.trunk_path}/start`, +new Date())}}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer, value, { force })}destroy() {clearInterval(this.ticker)}}const events = new Event()const cleanup = () => {events.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { events }
class Admin {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/setup/admin/#', this, this.onSetupQueued.bind(this))}toString() {return 'Model-Admin'}onSetupQueued(topic, pointer, value) {console.log(`${this.toString()} #onSetupQueued: ${pointer.path}, ${JSON.stringify(value)}`)this.accept(pointer, value)}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer, value, { force })}destroy() {}}const admin = new Admin()const cleanup = () => {admin.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { admin }
import _ from 'lodash'if (!global._) global._ = _import { Datastore, Pointer} from '@djinlist/datastore'if (!global.datastore) global.datastore = new Datastore()if (!global.Pointer) global.Pointer = Pointerimport fs from 'fs'const loadConfig = async () => {// configuration information goes heredatastore.set('/session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')let filePathfs.access('.stash.json', fs.constants.F_OK, (err) => {filePath = `.${err ? 'preconfig' : 'stash'}.json`})const onFileFound = (file, data) => {if (!data) { return false }const _root = JSON.parse(data)if (Object.keys(_root).length === 0) { return false }console.log(`index.js Parsing root found at ${file}`)datastore.set('/state', _root.state)datastore.set('/setup', _root.setup)main()return true}['.stash.json', '.preconfig.json'].find(filePath => {try {return onFileFound(filePath, fs.readFileSync(filePath))} catch (e) {console.log(`Error reading config at ${filePath}`)console.log(e)return false}})}const main = () => {import('./server')}loadConfig()
import { Base } from './base.js'class User extends Base {constructor() {super()datastore.subscribe('state/users/+/#', this, this.publish.bind(this))this.blacklist('state/users/+/auth/#')this.blacklist('state/users/+/auth/*')}toString() {return 'Channel-Users'}}const users = new User()export { users }
import { Base } from './base.js'class Item extends Base {constructor() {super()this.whitelist('state/items/+/#')}toString() {return 'Channel-Items'}}const items = new Item()export { items }
import { Base } from './base.js'class Event extends Base {constructor() {super()this.whitelist('setup/events/+/name')this.whitelist('setup/events/+/items/#')this.whitelist('setup/events/+/private')this.blacklist('state/events/+/pin')datastore.subscribe('state/events/+/pin', this, this.syndicate.bind(this))datastore.subscribe('state/events/+/#', this, this.publish.bind(this))this.initialize('/state/events', '/pin', this.syndicate.bind(this))}toString() {return 'Channel-Events'}}const events = new Event()export { events }
import { TopicTree, coppice } from '@djinlist/datastore'import { authorization } from '../authorization.js'class Base {constructor() {Object.defineProperties(this, {_permissions: {value: new TopicTree},_subscribers: {value: new TopicTree}})}toString() {return 'Channel-Base'}whitelist(topic) {console.log(`${this.toString()} #whitelist ${topic}`)this.list(topic, 'whitelist')}blacklist(topic) {console.log(`${this.toString()} #blacklist ${topic}`)this.list(topic, 'blacklist')}dewhitelist(topic) {console.log(`${this.toString()} #dewhitelist ${topic}`)this.delist(topic, 'whitelist')}deblacklist(topic) {console.log(`${this.toString()} #deblacklist ${topic}`)this.delist(topic, 'blacklist')}list(topic, type) {const permissions = this._permissions.getWithDefault(topic, [])permissions._topic = topicpermissions._value.push(type)}initialize(prefix, postfix, callback) {console.log(`${this.toString()} #initialize ${prefix}/+/${postfix}`)const keys = datastore.keys(prefix)const callback_topic = `${prefix}/+${postfix}`_.forEach(keys, key => {const pointer = Pointer.create(`${prefix}/${key}${postfix}`)const value = datastore.read(pointer)callback(callback_topic, pointer, value)})}delist(topic, type) {const permissions = this._permissions.get(topic)if (permissions == null || Object.keys(permissions).length === 0) { return }permissions.delete(type)}syndicate(topic, pointer, value) {const syndicated_path = pointer.steps.slice(0, -1).concat('#').join('/')const private_path = '/' + pointer.steps.slice(0, -1).concat('private').join('/')console.log(`${this.toString()} #syndicate ${syndicated_path} ${value}`)if (!value) {this.whitelist(syndicated_path)datastore.destroy(private_path)} else {this.dewhitelist(syndicated_path)datastore.write(private_path, true)}}publish(topic, pointer, value) {console.log(`${this.toString()} #publish ${topic} @ ${pointer.path}`)const permissions = this._permissions.entries(pointer.topic)let whitelisted = falsefor (var idx in permissions) {const _permitted = permissions[idx][1]if (_permitted.includes('blacklist')) { return }if (_permitted.includes('whitelist')) {whitelisted = true}}const permitted = _.concat(...Object.values(_.fromPairs(permissions)))_.remove(permitted,subscriber => ['whitelist', 'blacklist'].includes(subscriber))const subscribers = this._subscribers.entries(pointer.path.slice(1))if (subscribers.length == 0) { return }let parseif (whitelisted) {parse = ([_topic, subscribed]) => {console.log(_topic, subscribed)_.forEach(subscribed, callbacks => {callbacks.forEach(callback => callback(_topic, pointer, value))})}} else {parse = ([_topic, subscribed]) => {_.forEach(subscribed, (callbacks, subscriber) => {if (permitted.includes(subscriber)) {callbacks.forEach(callback => callback(_topic, pointer, value))}})}}_.forEach(subscribers, parse)}createAuthorization(pointer, packet, subscriber) {const response = authorization.createAuthorization(pointer, packet)subscriber.send(JSON.stringify(response))}authorize(pointer, packet, subscriber) {const response = authorization.authorize(pointer, packet)subscriber.send(JSON.stringify(response))}prove(pointer, packet, subscriber) {const response = authorization.prove(pointer, packet)if (response.v.p === false) {console.log(`#prove FAIL ${pointer.topic} ${subscriber}`)return false}pointer.root = '+'console.log('test', pointer.topic)const permissions = this._permissions.getWithDefault(pointer.topic, [])permissions._value.push(subscriber.toString())console.log(`${this.toString()} #prove SUCCEED ${pointer.topic} ${subscriber}`)subscriber.send(JSON.stringify(response))return true}resume(pointer, subscriber) {console.log(`${this.toString()} #resume ${pointer.topic} ${subscriber}`)const permissions = this._permissions.getWithDefault(pointer.topic, [])permissions._value.push(subscriber.toString())}isAuthorized(pointer, subscriber) {const entries = this._permissions.entries(pointer.dequeue().topic)if (!entries) {console.log(`${this.toString()} #isAuthorized FAIL ${pointer.path}`)return false}let authorized = falsefor (var idx in entries) {const callbacks = entries[idx][1]if (callbacks.includes('blacklist')) {console.log(`${this.toString()} #isAuthorized BLACKLIST ${entries[idx][0]} ${pointer.path}`)return false}if (callbacks.includes('whitelist')) {console.log(`${this.toString()} #isAuthorized WHITELIST ${entries[idx][0]}`)authorized = true} else if (callbacks.includes(subscriber.toString())) {console.log(`${this.toString()} #isAuthorized APPROVED ${entries[idx][0]}`)authorized = true}}console.log(`${this.toString()} #isAuthorized SUCCEED ${pointer.path}`)return authorized}read(pointer, subscriber) {if (!this.isAuthorized(pointer, subscriber)) { return }console.log(`#read ${pointer.path}`)console.dir(datastore.read(pointer.path))const authorizeCoppice = (path, subscriber) => {return _.reduce(datastore.read(pointer.path), (result, value, path) => {if (this.isAuthorized(Pointer.create(path), subscriber)) {result[path] = value}return result}, {})}const data_coppice = {}if (pointer.is_wildcard) {Object.assign(data_coppice,authorizeCoppice(pointer.path, subscriber))} else {const found = datastore.read(pointer.path)if (_.isPlainObject(found)) {coppice(found, pointer.path, data_coppice)} else {data_coppice[path] = found}}return data_coppice}write(pointer, value, subscriber) {if (!this.isAuthorized(pointer, subscriber)) { return }console.log(`${this.toString()} #write`, pointer.path, value)return datastore.write(pointer.path, value)}merge(pointer, value, subscriber) {if (!this.isAuthorized(pointer, subscriber)) { return }console.log(`${this.toString()} #merge`, pointer.path, value)return datastore.merge(pointer.path, value)}delete(pointer, subscriber) {if (!this.isAuthorized(pointer, subscriber)) { return }return datastore.delete(pointer.path)}subscribe(pointer, subscriber, callback) {const subscribers = this._subscribers.getWithDefault(pointer.topic, {})._valueif (_.isArray(subscribers[subscriber])) {subscribers[subscriber].push(callback)} else {subscribers[subscriber] = [callback]}}unsubscribe(subscriber) {console.log(`#unsubscribe ${subscriber}`)const removeSubscriber = ({ _value }) => {if (!_value) returndelete _value[subscriber]}this._subscribers.apply(removeSubscriber)const removePermission = ({ _value }) => {if (!_value) return_.remove(_value, subscriber.toString())}this._permissions.apply(removePermission)}}export { Base }
import { Base } from './base.js'class Admin extends Base {constructor() {super()this.blacklist('state/admin/auth/#')this.blacklist('state/admin/auth/*')}toString() {return 'Channel-Admin'}}const admin = new Admin()export { admin }
import srp from "secure-remote-password/server"import crypto from "crypto"class serverAuthorization {createAuthorization(pointer, packet) {// 0const salt = packet.sconst verifier = packet.vconst salt_pointer = pointer.replace('/#', '/auth/salt')const verifier_pointer = pointer.replace('/#', '/auth/verifier')const response = {o: 'a',c: 0,t: pointer.topic,v: true}response.v = !datastore.has(salt_pointer)if (response.v) {datastore.write(salt_pointer, salt)datastore.write(verifier_pointer, verifier)}return response // reject}authorize(pointer, packet) {// 1const salt_pointer = pointer.replace('/#', '/auth/salt')const verifier_pointer = pointer.replace('/#', '/auth/verifier')const client_public_key_pointer = pointer.replace('/#', '/auth/client_public_key')const server_secret_key_pointer = pointer.replace('/#', '/auth/server_secret_key')datastore.write(client_public_key_pointer, packet.k)const response = {o: 'a',c: 2,t: pointer.topic,v: {u: packet.u}}let salt = datastore.read(salt_pointer)let verifier = datastore.read(verifier_pointer)let ephemeralif (salt && verifier) {console.log(`#authorize() ${packet.u} found`)ephemeral = srp.generateEphemeral(verifier)datastore.write(server_secret_key_pointer, ephemeral.secret)response.v.k = ephemeral.publicresponse.v.s = salt} else {console.log(`#authorize() ${packet.u} not found`)salt = crypto.randomBytes(32).toString('hex')ephemeral = crypto.randomBytes(256).toString('hex')response.v.k = ephemeralresponse.v.s = salt}return response}prove(pointer, packet) {// 3const salt_pointer = pointer.replace('/#', '/auth/salt')const verifier_pointer = pointer.replace('/#', '/auth/verifier')const client_public_key_pointer = pointer.replace('/#', '/auth/client_public_key')const server_secret_key_pointer = pointer.replace('/#', '/auth/server_secret_key')const session_key_pointer = pointer.replace('/#', '/auth/session_key')const server_secret_key = datastore.read(server_secret_key_pointer)const client_public_key = datastore.read(client_public_key_pointer)const salt = datastore.read(salt_pointer)const username = packet.uconst verifier = datastore.read(verifier_pointer)const proof = packet.pvar session, responsetry {session = srp.deriveSession(server_secret_key,client_public_key,salt,username,verifier,proof)datastore.write(session_key_pointer, session.key)response = {o: 'a',c: 4,t: pointer.topic,v: {u: packet.u,p: session.proof}}return response} catch (error) {response = {o: 'a',c: 4,t: pointer.topic,v: {u: packet.u,p: false}}return response}}}const authorization = new serverAuthorization()export { authorization }
{"name": "@djinlist/content","version": "1.0.0","type": "module","license": "UNLICENSED","private": true,"main": "src/index.js","dependencies": {"ws": "^6.0.0","uuid": "^8.0.0"},"exports": {".": "./src/index.js"}}
{"state": {"events": {"75c6cfd0-139a-4a33-8826-9c284645f1ae": {"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"items": {"spotify:track:7nDYw1nNAW4dAqgmW2W3tq": {"vote": 1599514588577},"spotify:track:6JqYhSdTE4WbQrMXxPH5cD": {"vote": 1599514590773},"spotify:track:3QpkbrYXtlU3LRJu3sTK6V": {"vote": 1599514593054},"spotify:track:1yTTMcUhL7rtz08Dsgb7Qb": {"vote": 1599514595941},"spotify:track:6S1IgeHxxOT9qVWnmsdGxe": {"vote": 1599514599534},"spotify:track:3VXvKTOQoY0kWvpjU67uq2": {"vote": 1599514649778},"spotify:track:2P0FH5jSRu8cctdYfTXtje": {"vote": 1599514650996},"spotify:track:6As34Fmjj7dtReKB51NOVc": {"vote": 1600015121994},"spotify:track:5t9KYe0Fhd5cW6UYT4qP8f": {"vote": 1600015123841},"spotify:track:17jEoYoOfRD6dvNCMmC9n4": {"vote": 1600015125507},"spotify:track:1tkg4EHVoqnhR6iFEXb60y": {"vote": 1602363580571},"spotify:track:5u1n1kITHCxxp8twBcZxWy": {"vote": 1602363581598},"spotify:track:5KCbr5ndeby4y4ggthdiAb": {"vote": 1602363582535},"spotify:track:0sNOPYInjylsM8ZnQozPjt": {"vote": 1608595195694},"spotify:track:4U1c58fpDgbjkb6sVQg26L": {"vote": 1608595196665},"spotify:track:5ryZK3msA04LNcnMaMtm6p": {"vote": 1608595197424},"spotify:track:00deiAYxr1qQx4km9ftnPK": {"vote": 1608595199560},"spotify:track:7lQ8MOhq6IN2w8EYcFNSUk": {"vote": 1608595299092}}},"f9666a8a-4df1-5d14-9b66-8d08d8d3df58": {"items": {"spotify:track:5t9KYe0Fhd5cW6UYT4qP8f": {"vote": 1600015220987},"spotify:track:6As34Fmjj7dtReKB51NOVc": {"vote": 1600015223023},"spotify:track:1zXpHPdBAUxnOCQqFMFLk3": {"vote": 1600015224884},"spotify:track:2kXeAEpGBN874ZKJPV24fr": {"vote": 1600016939990},"spotify:track:03ITeFvMvTRpTC92WsQWw5": {"vote": 1600016941050},"spotify:track:3eCwKRKjGT0EIJe3FKOjIo": {"vote": 1600016942825},"spotify:track:3HVRywtkhSjhpmkaeaYTgh": {"vote": 1600028103232},"spotify:track:0YedjUOqafibhe8htcD6Gz": {"vote": 1600028946840},"spotify:track:4G3DWijMhNkWZwLcxnDI0H": {"vote": 1600028954864},"spotify:track:54WIS7qug0Gnt65eD9gg8g": {"vote": 1600035577353},"spotify:track:4kK14radw0XfwxJDPt9tnP": {"vote": 1600036253132}}}},"playlist": [["spotify:track:5t9KYe0Fhd5cW6UYT4qP8f",2],["spotify:track:6As34Fmjj7dtReKB51NOVc",2],["spotify:track:4kK14radw0XfwxJDPt9tnP",1],["spotify:track:54WIS7qug0Gnt65eD9gg8g",1],["spotify:track:4G3DWijMhNkWZwLcxnDI0H",1],["spotify:track:0YedjUOqafibhe8htcD6Gz",1],["spotify:track:3HVRywtkhSjhpmkaeaYTgh",1],["spotify:track:3eCwKRKjGT0EIJe3FKOjIo",1],["spotify:track:03ITeFvMvTRpTC92WsQWw5",1],["spotify:track:2kXeAEpGBN874ZKJPV24fr",1],["spotify:track:1zXpHPdBAUxnOCQqFMFLk3",1],["spotify:track:7lQ8MOhq6IN2w8EYcFNSUk",1],["spotify:track:00deiAYxr1qQx4km9ftnPK",1],["spotify:track:5ryZK3msA04LNcnMaMtm6p",1],["spotify:track:4U1c58fpDgbjkb6sVQg26L",1],["spotify:track:0sNOPYInjylsM8ZnQozPjt",1],["spotify:track:5KCbr5ndeby4y4ggthdiAb",1],["spotify:track:5u1n1kITHCxxp8twBcZxWy",1],["spotify:track:1tkg4EHVoqnhR6iFEXb60y",1],["spotify:track:17jEoYoOfRD6dvNCMmC9n4",1],["spotify:track:2P0FH5jSRu8cctdYfTXtje",1],["spotify:track:3VXvKTOQoY0kWvpjU67uq2",1],["spotify:track:6S1IgeHxxOT9qVWnmsdGxe",1],["spotify:track:1yTTMcUhL7rtz08Dsgb7Qb",1],["spotify:track:3QpkbrYXtlU3LRJu3sTK6V",1],["spotify:track:6JqYhSdTE4WbQrMXxPH5cD",1],["spotify:track:7nDYw1nNAW4dAqgmW2W3tq",1]]},"62854dc2-7d97-45d3-be03-f0bac69119f8": {"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"items": {"spotify:track:45bE4HXI0AwGZXfZtMp8JR": {"vote": 1600015005150},"spotify:track:4wosxLl0mAqhneDzya2MfY": {"vote": 1600015008029},"spotify:track:2J4P46vCFm1rPkNkp9pZWX": {"vote": 1600015009080},"spotify:track:5nLNuK7OoJt36gY9gWgnbo": {"vote": 1607307765906},"spotify:track:5vk6nP3fXbz9FoFmsu5coD": {"vote": 1607307767167},"spotify:track:7ytR5pFWmSjzHJIeQkgog4": {"vote": 1608594919265},"spotify:track:4CNzuSQoL5jgCxzYmuMvcz": {"vote": 1612729873750},"spotify:track:4VSyH8AkIt3kaR5xIPFVVi": {"vote": 1612730821011},"spotify:track:1XXimziG1uhM0eDNCZCrUl": {"vote": 1613256471284},"spotify:track:7lPN2DXiMsVn7XUKtOW1CS": {"vote": 1612733220849},"spotify:track:463CkQjx2Zk1yXoBuierM9": {"vote": 1612733222218},"spotify:track:5QO79kh1waicV47BqGRL3g": {"vote": 1613256469721},"spotify:track:3YJJjQPAbDT7mGpX3WtQ9A": {"vote": 1612733333737},"spotify:track:6Im9k8u9iIzKMrmV7BWtlF": {"vote": 1612733577976},"spotify:track:1FkIrCoa9Lkd8rgZ4VhNP9": {"vote": 1612757417811},"spotify:track:4WPaFfZYr290KKtbc0rEO7": {"vote": 1612757423526},"spotify:track:0izUjTuDrUy2FgQOSRALSU": {"vote": 1612757442106},"spotify:track:4fWK7zJp17fuhDfQ9YnAei": {"vote": 1612757443252},"spotify:track:3CeCwYWvdfXbZLXFhBrbnf": {"vote": 1613258089402},"spotify:track:5Kskr9LcNYa0tpt5f0ZEJx": {"vote": 1613258320712},"spotify:track:7Ei7kZxjEw9d76cEDxoxua": {"vote": 1613259636644},"spotify:track:2NeyJbL3ROKCjRkAjs77ya": {"vote": 1613259033247},"spotify:track:7L6G0wpIUiPXuvoo7qhb06": {"vote": 1613259037259},"spotify:track:5srKMwXoeyrRnyTnNbpgIW": {"vote": 1613259038382},"spotify:track:4Iedi94TIaB2GGb1nMB68v": {"vote": 1613259638535},"spotify:track:26UxwWl9xCb83OynXELJcL": {"vote": 1613259640344},"spotify:track:2oI1Avedp7KK4Wytv2Dx0O": {"vote": 1615669778570},"spotify:track:3hbi5zXAgQt0Z9V5JSOnCe": {"vote": 1625408125835},"USRW29600011": {"vote": 1625614042071},"USRW30900002": {"vote": 1625521507643},"USA2P2125949": {"vote": 1625710090088},"CAUM72100222": {"vote": 1625710098134},"QZES82074435": {"vote": 1625741218535},"USUM72021500": {"vote": 1625741219686},"GBAHS2100318": {"vote": 1626494269063},"FRX202125956": {"vote": 1626743462791}},"events": {"62854dc2-7d97-45d3-be03-f0bac69119f8": {"items": {"spotify:track:2NeyJbL3ROKCjRkAjs77ya": {"vote": 1613259033247},"spotify:track:1FkIrCoa9Lkd8rgZ4VhNP9": {"vote": 1612757417811},"spotify:track:4WPaFfZYr290KKtbc0rEO7": {"vote": 1612757423526},"spotify:track:0izUjTuDrUy2FgQOSRALSU": {"vote": 1612757442106},"spotify:track:4fWK7zJp17fuhDfQ9YnAei": {"vote": 1612757443252},"spotify:track:5QO79kh1waicV47BqGRL3g": {"vote": 1613256469721},"spotify:track:1XXimziG1uhM0eDNCZCrUl": {"vote": 1613256471284},"spotify:track:3CeCwYWvdfXbZLXFhBrbnf": {"vote": 1613258089402},"spotify:track:5Kskr9LcNYa0tpt5f0ZEJx": {"vote": 1613258320712},"spotify:track:7Ei7kZxjEw9d76cEDxoxua": {"vote": 1613259636644},"spotify:track:7L6G0wpIUiPXuvoo7qhb06": {"vote": 1613259037259},"spotify:track:5srKMwXoeyrRnyTnNbpgIW": {"vote": 1613259038382},"spotify:track:4Iedi94TIaB2GGb1nMB68v": {"vote": 1613259638535},"spotify:track:26UxwWl9xCb83OynXELJcL": {"vote": 1613259640344},"spotify:track:2oI1Avedp7KK4Wytv2Dx0O": {"vote": 1615669778570},"spotify:track:3hbi5zXAgQt0Z9V5JSOnCe": {"vote": 1625408125835},"USRW29600011": {"vote": 1625614042071},"USRW30900002": {"vote": 1625521507643},"USA2P2125949": {"vote": 1625710090088},"CAUM72100222": {"vote": 1625710098134},"QZES82074435": {"vote": 1625741218535},"USUM72021500": {"vote": 1625741219686},"GBAHS2100318": {"vote": 1626494269063},"FRX202125956": {"vote": 1626743462791}}}}}},"playlist": [["FRX202125956",1],["GBAHS2100318",1],["USUM72021500",1],["QZES82074435",1],["CAUM72100222",1],["USA2P2125949",1],["USRW30900002",1],["USRW29600011",1],["spotify:track:3hbi5zXAgQt0Z9V5JSOnCe",1],["spotify:track:2oI1Avedp7KK4Wytv2Dx0O",1],["spotify:track:26UxwWl9xCb83OynXELJcL",1],["spotify:track:4Iedi94TIaB2GGb1nMB68v",1],["spotify:track:5srKMwXoeyrRnyTnNbpgIW",1],["spotify:track:7L6G0wpIUiPXuvoo7qhb06",1],["spotify:track:2NeyJbL3ROKCjRkAjs77ya",1],["spotify:track:7Ei7kZxjEw9d76cEDxoxua",1],["spotify:track:5Kskr9LcNYa0tpt5f0ZEJx",1],["spotify:track:3CeCwYWvdfXbZLXFhBrbnf",1],["spotify:track:4fWK7zJp17fuhDfQ9YnAei",1],["spotify:track:0izUjTuDrUy2FgQOSRALSU",1],["spotify:track:4WPaFfZYr290KKtbc0rEO7",1],["spotify:track:1FkIrCoa9Lkd8rgZ4VhNP9",1],["spotify:track:6Im9k8u9iIzKMrmV7BWtlF",1],["spotify:track:3YJJjQPAbDT7mGpX3WtQ9A",1],["spotify:track:5QO79kh1waicV47BqGRL3g",1],["spotify:track:463CkQjx2Zk1yXoBuierM9",1],["spotify:track:7lPN2DXiMsVn7XUKtOW1CS",1],["spotify:track:1XXimziG1uhM0eDNCZCrUl",1],["spotify:track:4VSyH8AkIt3kaR5xIPFVVi",1],["spotify:track:4CNzuSQoL5jgCxzYmuMvcz",1],["spotify:track:7ytR5pFWmSjzHJIeQkgog4",1],["spotify:track:5vk6nP3fXbz9FoFmsu5coD",1],["spotify:track:5nLNuK7OoJt36gY9gWgnbo",1],["spotify:track:2J4P46vCFm1rPkNkp9pZWX",1],["spotify:track:4wosxLl0mAqhneDzya2MfY",1],["spotify:track:45bE4HXI0AwGZXfZtMp8JR",1]],"items": {"spotify:track:1FkIrCoa9Lkd8rgZ4VhNP9": {"name": "My People","artists": ["Erykah Badu"],"image_url": "https://i.scdn.co/image/ab67616d00001e0213066c8c5df466c8a3e57ca4"},"spotify:track:08zt4rqVjqahvXaWAuEBbP": {"name": "Cold Feet","artists": ["Loud Luxury"],"image_url": "https://i.scdn.co/image/ab67616d00001e0294805d9c6cd1f558d0b0a8ef"},"spotify:track:2plLJpUcYPFrl1sW2pMG63": {"name": "Lights Up","artists": ["Harry Styles"],"image_url": "https://i.scdn.co/image/ab67616d00001e02766ba00b287429d0b13e1e5f"},"spotify:track:0gmgCD6OoJMcoK5af0exA2": {"name": "The Lake (with Wrabel)","artists": ["Galantis","Wrabel"],"image_url": "https://i.scdn.co/image/ab67616d00001e02816cee08cb1ed2c881416a24"},"spotify:track:5QO79kh1waicV47BqGRL3g": {"name": "Save Your Tears","artists": ["The Weeknd"],"image_url": "https://i.scdn.co/image/ab67616d00001e028863bc11d2aa12b54f5aeb36"},"spotify:track:4pBhTGnL5N5KqsyqU58jee": {"name": "I'm not Pretty","artists": ["JESSIA"],"image_url": "https://i.scdn.co/image/ab67616d00001e02fb006ce1d7c3f263c521f01e"},"spotify:track:4wcOBczfEVjEgsF4aKhKbL": {"name": "When You're Home","artists": ["Tyler Shaw"],"image_url": "https://i.scdn.co/image/ab67616d00001e02b16d438b180bdec4ef771c19"},"spotify:track:6Im9k8u9iIzKMrmV7BWtlF": {"name": "34+35","artists": ["Ariana Grande"],"image_url": "https://i.scdn.co/image/ab67616d00001e025ef878a782c987d38d82b605"},"spotify:track:54bFM56PmE4YLRnqpW6Tha": {"name": "Therefore I Am","artists": ["Billie Eilish"],"image_url": "https://i.scdn.co/image/ab67616d00001e02fec5ef9f3133aff71c525acc"},"spotify:track:3USxtqRwSYz57Ewm6wWRMp": {"name": "Heat Waves","artists": ["Glass Animals"],"image_url": "https://i.scdn.co/image/ab67616d00001e02712701c5e263efc8726b1464"},"spotify:track:6IE47jpPeatF2Iay7GZtEc": {"name": "Feel It All Around","artists": ["Washed Out"],"image_url": "https://i.scdn.co/image/ab67616d00001e020f2e0c616a2eb4e00d4c51c3"},"spotify:track:2EH1ZVZx2wPGtQb5V2hNih": {"name": "Good To Sea","artists": ["Pinback"],"image_url": "https://i.scdn.co/image/ab67616d00001e02a3708b5baa5ec5f353324f81"},"spotify:track:24HPkbkXJsIFC4eyg63zgQ": {"name": "Wraith Pinned to the Mist and Other Games","artists": ["of Montreal"],"image_url": "https://i.scdn.co/image/ab67616d00001e0238a6cc0b38036949ed001f9a"},"spotify:track:3qUqucPIPqlSnzq5MacjxQ": {"name": "Goût cerise","artists": ["Ragers"],"image_url": "https://i.scdn.co/image/ab67616d00001e02be8727b798d326c3c164b359"},"spotify:track:6HyUeilH0GYtSWRYBtRm3l": {"name": "À qui j'dois ressembler ?","artists": ["Matthieu Lévesque"],"image_url": "https://i.scdn.co/image/ab67616d00001e0242ce05b61d23754a0204a4d2"},"spotify:track:4ycyOBm9iFoiNVkafhb1WW": {"name": "Nouveaux parrains","artists": ["Sofiane","Soolking"],"image_url": "https://i.scdn.co/image/ab67616d00001e028b6fb0dffb290ef4f27e56e7"},"spotify:track:6iUlUzSGZzKtlCvQ3wCVZD": {"name": "Breakdown (feat. Krayzie Bone & Wish Bone)","artists": ["Mariah Carey","Krayzie Bone","Wishbone"],"image_url": "https://i.scdn.co/image/ab67616d00001e0298ed501c6a838b2244ebaf75"},"spotify:track:58r4JuwHhXLAkttkaUZfLw": {"name": "Got to Be Real","artists": ["Cheryl Lynn"],"image_url": "https://i.scdn.co/image/ab67616d00001e02c0e98d7c48b548f1a3833368"},"spotify:track:4CNzuSQoL5jgCxzYmuMvcz": {"name": "Like It (with 6LACK)","artists": ["Summer Walker","6LACK"],"image_url": "https://i.scdn.co/image/ab67616d00001e02b5ed9187ac7f8aa281a547e3"},"spotify:track:7vxLj7MREliG5i5vSnqSVr": {"name": "Body","artists": ["Summer Walker"],"image_url": "https://i.scdn.co/image/ab67616d00001e02b5ed9187ac7f8aa281a547e3"},"spotify:track:4VSyH8AkIt3kaR5xIPFVVi": {"name": "Where My Girls At","artists": ["702"],"image_url": "https://i.scdn.co/image/ab67616d00001e020100a4e7e46b63b46e03b158"},"spotify:track:2NeyJbL3ROKCjRkAjs77ya": {"name": "Ashes","artists": ["Stellar"],"image_url": "https://i.scdn.co/image/ab67616d00001e023286f23a94f357cdbbb4d718"},"spotify:track:4WPaFfZYr290KKtbc0rEO7": {"name": "Life's Gone Down Low","artists": ["Lijadu Sisters"],"image_url": "https://i.scdn.co/image/ab67616d00001e02236de7aa0dab13d4c9b02eba"},"spotify:track:0izUjTuDrUy2FgQOSRALSU": {"name": "Lockdown","artists": ["Koffee"],"image_url": "https://i.scdn.co/image/ab67616d00001e02cdbcabc170ce557ae4919753"},"spotify:track:4fWK7zJp17fuhDfQ9YnAei": {"name": "Gettaway (feat. Space & Nicole)","artists": ["Missy Elliott","Nicole Wray","Space"],"image_url": "https://i.scdn.co/image/ab67616d00001e02f27571e59cac2e7a4624c9c4"},"spotify:track:1XXimziG1uhM0eDNCZCrUl": {"name": "Up","artists": ["Cardi B"],"image_url": "https://i.scdn.co/image/ab67616d00001e02d619b8baab0619516bb53804"},"spotify:track:3CeCwYWvdfXbZLXFhBrbnf": {"name": "Love Story (Taylor’s Version)","artists": ["Taylor Swift"],"image_url": "https://i.scdn.co/image/ab67616d00001e02877ea8fa223c26f19aaef92d"},"spotify:track:5Kskr9LcNYa0tpt5f0ZEJx": {"name": "Calling My Phone","artists": ["Lil Tjay","6LACK"],"image_url": "https://i.scdn.co/image/ab67616d00001e021b36f91abf80aedb7c88f460"},"spotify:track:7Ei7kZxjEw9d76cEDxoxua": {"name": "What It Feels Like","artists": ["Nipsey Hussle","JAY-Z"],"image_url": "https://i.scdn.co/image/ab67616d00001e02b02a3380a69bd2418a1f68f1"},"spotify:track:7L6G0wpIUiPXuvoo7qhb06": {"name": "oops!","artists": ["Yung Gravy"],"image_url": "https://i.scdn.co/image/ab67616d00001e02c5e844c860c9717785b7aaa2"},"spotify:track:5srKMwXoeyrRnyTnNbpgIW": {"name": "People I Don't Like","artists": ["UPSAHL"],"image_url": "https://i.scdn.co/image/ab67616d00001e02de3975b105bc6d216359ffe6"},"spotify:track:4Iedi94TIaB2GGb1nMB68v": {"name": "On Me","artists": ["Lil Baby"],"image_url": "https://i.scdn.co/image/ab67616d00001e028de3ce24866dcc8ffddbebac"},"spotify:track:26UxwWl9xCb83OynXELJcL": {"name": "Masterpiece","artists": ["DaBaby"],"image_url": "https://i.scdn.co/image/ab67616d00001e027b41da110df7023757e8f8fa"},"spotify:track:2oI1Avedp7KK4Wytv2Dx0O": {"name": "Love is a losing game","artists": ["THEHONESTGUY","Malaika Khadijaa"],"image_url": "https://i.scdn.co/image/ab67616d00001e025f27cac740de78fddbab7bfe"},"spotify:track:3hbi5zXAgQt0Z9V5JSOnCe": {"name": "Everybody Wants To Party","artists": ["Dubdogz","JØRD"],"image_url": "https://i.scdn.co/image/ab67616d00001e0286271a532aa6196b056f369f"}}},"fe71a1ee-6e64-4d4f-8a03-7b091d93c823": {"playlist": []},"null": {"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"items": {"spotify:track:0k7wmahjkn389wAZdz19Cv": {"vote": 1607306677700}}}}},"": {"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"spotify:track:1FkIrCoa9Lkd8rgZ4VhNP9": {"vote": 1612735281874},"spotify:track:08zt4rqVjqahvXaWAuEBbP": {"vote": 1612750262848},"spotify:track:2plLJpUcYPFrl1sW2pMG63": {"vote": 1612750264080},"spotify:track:0gmgCD6OoJMcoK5af0exA2": {"vote": 1612750266245}}}},"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"spotify:track:5QO79kh1waicV47BqGRL3g": {"vote": 1612751939386},"spotify:track:4pBhTGnL5N5KqsyqU58jee": {"vote": 1612751940395},"spotify:track:4wcOBczfEVjEgsF4aKhKbL": {"vote": 1612751941208},"spotify:track:6Im9k8u9iIzKMrmV7BWtlF": {"vote": 1612752104792},"spotify:track:54bFM56PmE4YLRnqpW6Tha": {"vote": 1612752106934},"spotify:track:3USxtqRwSYz57Ewm6wWRMp": {"vote": 1612752110688},"spotify:track:6IE47jpPeatF2Iay7GZtEc": {"vote": 1612752137501},"spotify:track:2EH1ZVZx2wPGtQb5V2hNih": {"vote": 1612752146666},"spotify:track:24HPkbkXJsIFC4eyg63zgQ": {"vote": 1612752149757},"spotify:track:3qUqucPIPqlSnzq5MacjxQ": {"vote": 1612752423687},"spotify:track:6HyUeilH0GYtSWRYBtRm3l": {"vote": 1612752429315},"spotify:track:4ycyOBm9iFoiNVkafhb1WW": {"vote": 1612752433894},"spotify:track:6iUlUzSGZzKtlCvQ3wCVZD": {"vote": 1612753413416},"spotify:track:58r4JuwHhXLAkttkaUZfLw": {"vote": 1612753415873},"spotify:track:4CNzuSQoL5jgCxzYmuMvcz": {"vote": 1612753423847},"items": {"spotify:track:2NeyJbL3ROKCjRkAjs77ya": {"vote": 1612756744096}}}}},"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"auth": {"salt": "27e869c626c79ebc8b0d4e59887f3bb66a8031a5e01a0e5faedbdcaa4a7748ab","verifier": "ab2acfc79f5bb1cad3f16276bed6089c626c6e8bb870486a687d5c41b1f0e9534e096e1657c4baee38b805a6ab8c2c38ec38d29048eb8f1f1a28f2a7668f0c2f8983ea6514a00f26f2bc42530946d11f9f304882d0bd4ce240272fe5fc46ada47c22917d630cb2c65f08f2a76438c9d08bfa458d5e1b67ebf43a829027365d1a2d8794b08132796c32d9d9492cc42ca5147eff60304a5b38496b0326f92dfbde167b722834657b45db7a2f51398128fb32753f6deba8a5f474bc58d6d34a1c5a5f75e84860cfba95ad4742017053b464e924bfd10aeceb0169b5c88ddb0bddaee59b198ecdae658a3211eeaf5590c2315459cc7ab78a2114d6e0a162845b386e","client_public_key": "8b119bd7b590235ed80827763e385af341e31e69743cab56500ea1dbe59a2703c38111c3a21751e018de0bf38237a313bc6ad52ac1ce816605e3c11b931822a1c0726715171576450bcd34c3b7a262cc0755fce9afd918e8e60f6e7b3ec8c55a12cd399a5940638113a00735971fbd4a19ed9b0821a6f625d9ac7a3c2d9590e89c4c2692ded40c8b442c477404b54a4aaae2ba0f226d800a1e4a6b5469ac50f5e48e466b82319728bc37839a88ad96c302f4eae80eb7d834cbf0e9b1554cec4b6c9a0749cb843257287f834785ce39d1ef06f71ecd2c19a5143e34f4ccf7d7ece7be83f8129ade15e678c02c9c7bbfafd69c68b26c4787de006c7fccb3f49dd5","server_secret_key": "357ea32f5d17bb517b882d150955d810eccf1dcc0cc3c9a99262897ea15e83ae","session_key": "34b1ae3709db4bcb9948ab8769b130a8a1c5954dc8d551432b0c93316bf13e2d"},"email": "thomas.p.cowan@gmail.com","name": "tpcowan","created_at": 1599514138874,"services": {"spotify": {"expiry": 1627916615,"token": "BQBW0F54C5aapo5-1Lq3B3nF3MUSM7j19a7CRrfKnjeg9xdN-y3Ix3zOP3ggRdarU5WtWZYyOYAAM-owFrEdXcT-cXkHYH_WI3p7Dtm4JRPnVGGLsZeVeA15N7bYzFRvPxFBooHoGehc2ILFlEYUyHUJ60odagrpOGyUsW3oCeZBO_7-3FrvEzgtDxkIrg"}},"drivers": {"spotify": {"token": "BQBW0F54C5aapo5-1Lq3B3nF3MUSM7j19a7CRrfKnjeg9xdN-y3Ix3zOP3ggRdarU5WtWZYyOYAAM-owFrEdXcT-cXkHYH_WI3p7Dtm4JRPnVGGLsZeVeA15N7bYzFRvPxFBooHoGehc2ILFlEYUyHUJ60odagrpOGyUsW3oCeZBO_7-3FrvEzgtDxkIrg"}},"event_id": "62854dc2-7d97-45d3-be03-f0bac69119f8","items": {"1tkg4EHVoqnhR6iFEXb60y": {"artists": ["Pop Smoke"],"image_url": "https://i.scdn.co/image/ab67616d00001e0277ada0863603903f57b34369"},"5u1n1kITHCxxp8twBcZxWy": {"artists": ["Justin Bieber","Chance the Rapper"],"image_url": "https://i.scdn.co/image/ab67616d00001e02572c68f79b356c21202e248c"},"5KCbr5ndeby4y4ggthdiAb": {"artists": ["Shawn Mendes"],"image_url": "https://i.scdn.co/image/ab67616d00001e023d9621bb2904dc57a60a6b36"},"spotify:track:0k7wmahjkn389wAZdz19Cv": {"artists": ["Future","Lil Uzi Vert"],"image_url": "https://i.scdn.co/image/ab67616d00001e0257928e0363878a71692e6a1f"},"spotify:track:5nLNuK7OoJt36gY9gWgnbo": {"artists": ["YSN Fab"],"image_url": "https://i.scdn.co/image/ab67616d00001e02dea56579b16b977c620ac267"},"spotify:track:5vk6nP3fXbz9FoFmsu5coD": {"artists": ["Tizzy Stackz"],"image_url": "https://i.scdn.co/image/ab67616d00001e020b20ee77457a3f4df0dcc63e"},"spotify:track:7ytR5pFWmSjzHJIeQkgog4": {"artists": ["DaBaby","Roddy Ricch"],"image_url": "https://i.scdn.co/image/ab67616d00001e0220e08c8cc23f404d723b5647"},"spotify:track:0sNOPYInjylsM8ZnQozPjt": {"artists": ["Ramin Djawadi"],"image_url": "https://i.scdn.co/image/ab67616d00001e02239a1395e4d595efc28af924"},"spotify:track:4U1c58fpDgbjkb6sVQg26L": {"artists": ["Ramin Djawadi"],"image_url": "https://i.scdn.co/image/ab67616d00001e02239a1395e4d595efc28af924"},"spotify:track:5ryZK3msA04LNcnMaMtm6p": {"artists": ["Ramin Djawadi"],"image_url": "https://i.scdn.co/image/ab67616d00001e02239a1395e4d595efc28af924"},"spotify:track:00deiAYxr1qQx4km9ftnPK": {"artists": ["Ramin Djawadi"],"image_url": "https://i.scdn.co/image/ab67616d00001e0262be9b16adb2e32aed9bda26"},"spotify:track:7lQ8MOhq6IN2w8EYcFNSUk": {"artists": ["Eminem"],"image_url": "https://i.scdn.co/image/ab67616d00001e026ca5c90113b30c3c43ffb8f4"},"spotify:track:4CNzuSQoL5jgCxzYmuMvcz": {"artists": ["Summer Walker","6LACK"],"image_url": "https://i.scdn.co/image/ab67616d00001e02b5ed9187ac7f8aa281a547e3"},"spotify:track:4VSyH8AkIt3kaR5xIPFVVi": {"artists": ["702"],"image_url": "https://i.scdn.co/image/ab67616d00001e020100a4e7e46b63b46e03b158"},"spotify:track:1XXimziG1uhM0eDNCZCrUl": {"artists": ["Cardi B"],"image_url": "https://i.scdn.co/image/ab67616d00001e02d619b8baab0619516bb53804"},"spotify:track:7lPN2DXiMsVn7XUKtOW1CS": {"artists": ["Olivia Rodrigo"],"image_url": "https://i.scdn.co/image/ab67616d00001e0259779689e1d9c15ca2f76b84"},"spotify:track:463CkQjx2Zk1yXoBuierM9": {"artists": ["Dua Lipa","DaBaby"],"image_url": "https://i.scdn.co/image/ab67616d00001e0249caa4fc6f962057ba65576a"},"spotify:track:5QO79kh1waicV47BqGRL3g": {"artists": ["The Weeknd"],"image_url": "https://i.scdn.co/image/ab67616d00001e028863bc11d2aa12b54f5aeb36"},"spotify:track:3YJJjQPAbDT7mGpX3WtQ9A": {"artists": ["SZA"],"image_url": "https://i.scdn.co/image/ab67616d00001e023097b1375ab17ae5bf302a0a"},"spotify:track:6Im9k8u9iIzKMrmV7BWtlF": {"artists": ["Ariana Grande"],"image_url": "https://i.scdn.co/image/ab67616d00001e025ef878a782c987d38d82b605"}}},"f9666a8a-4df1-5d14-9b66-8d08d8d3df58": {"auth": {"salt": "311ac5713fe41aa6ddb172325967c2ec73f93c5d52e4db1603ce5e209eb55a65","verifier": "39722ca76312f45013194c033279fdcac4470bcf8b001d64ad8d75896b372567b787775ea2d49b886e52cbde406f7977c1ac6201143801c00ddf2804262ab7dbe6b78f2d110d5addb5715ce485070f1736bae20e302ce809368b19b5bcb16e4ac785744a71824edc69847729d3d0e68559b640fa842ff1cf77520c83a2f67f84d72e1de89240c82c145c2b88452cbd5f7403998c554a20296fa017e0419698f289c8eb5a79dc9c8bb9184201c7d27a795658fba76a1114ccb2d0f3e71538af86d60ab2dbcf8b1ce0321ff84d6799352e9a0b11d26c2c4152a1c9d38d98988e6d87318f223f33f5c5e970a796186981338569fd97713b7f93825f30cb42311e2f","client_public_key": "365c94c8676042a17ceac7ff56d03be4c8565e086ccbe43eae362d5c2fe4eb9d22cc5d1ee460b96b30c91bb40d50cb6cf3b93dd6319bc2a9dc6a2a403da9d6f44a77f0478f33f6d09fbb8ccb43576889d437f07f189dc407781a6a65ca44738bffe20e51198a92b990143c4f4a3e5811d60c8202a87720176b564b66ed5ae5b58d833709a6c299517387da112f11363f9b429baf593bca9891adec15fa27685ad0040a8dc788c15e07fa1905360827e4f5ad59fbdeede252334d1b3213d5b74297412622f31065d9c9907eadede277e72d3ed4aa6fd5167947d56ac211bed8d6a1913119a7b876d8247b60966e4fb3c842f0a586757866e84c52e89cafa79fb4","server_secret_key": "53a123ed0d4fde5bb266ecf1e1d065ef0fa97c38d78069cfe16fb883f3ec12b3","session_key": "a5ad681112cf8a6c1e057b7f3697110dfbec110921861d0b9566a0238064ce76"},"email": "telcowan@gmail.com","name": "tec","created_at": 1600015188562,"drivers": {"spotify": {"token": "BQBYmk6K2LAFwuhn0zdk_6TgjMlc3N1Zz9-KZBHxvMI695au6QlMpSmPQvxqPcjeznJXSJbJiWT6gOmEy84XyZ0S9UCYNR6MK14Nth4D5tbHeLHushL2NJltos_Si2N_VDDKDV82m1vgj83_KvdaJhPF8_6jJOGk"}},"services": {"spotify": {"expiry": 1600825145,"token": "BQBYmk6K2LAFwuhn0zdk_6TgjMlc3N1Zz9-KZBHxvMI695au6QlMpSmPQvxqPcjeznJXSJbJiWT6gOmEy84XyZ0S9UCYNR6MK14Nth4D5tbHeLHushL2NJltos_Si2N_VDDKDV82m1vgj83_KvdaJhPF8_6jJOGk"}},"event_id": "75c6cfd0-139a-4a33-8826-9c284645f1ae","items": {"54WIS7qug0Gnt65eD9gg8g": {"artists": ["Morgan Delt"]},"4kK14radw0XfwxJDPt9tnP": {"artists": ["Lewis Del Mar"],"image_url": "https://i.scdn.co/image/ab67616d00001e0211555ed45c4377e101d7979d"}}},"59c40275-d764-5f53-907d-c9ccc7b097d7": {"auth": {"salt": "1f3f6715b48937796e62ea7725e02ec3be2edf681298a5d345b61bc75486739d","verifier": "5f2c9aaab598abfd6bc47ffc772061aa67c735d5ddc27d192f6a05b98c994ef151f27b67e9d059c46e05200eebed9cc905ec12a7f38fa30ab7778bebe0796572ef7f7d14eda52a938e65fc577c6e686e98f5f7ca4f2cc9b49747493bc266c6b0e62c90349dc3311bd310e0523d27fb404f3df4ad6fc7d50d2d48d5b9f23eb49456a8cef6642acbef2bf0c914f1d2f2801ae7de6d24d89b9698410dd2cfc91f592befcef89513ef8755ff11473ac8cdcb5ce3d99f970621403fd44f5b1aa177f9f33171de5f57b0a6eb16e24a96302acf7bf6443803b77efb35d8e956885c00836c142eb2fd4f4a3006311f513dc0554530b1691464b0a3c85304c8b96ec26cec","client_public_key": "a5f078c9d7c9e334a9d1c86328af91753e963f2f2633e04f8c6734e4ede048bdd4a4736c3f7309b0c7322fd1a9c23a8610ab34337b35cc773142cef80b0f7dbe16c745355981c9db7f578c263c64bf42c31eb9f34a9df9029790549bb0d0a4baa8e4005a47815ae29b97b902e887ed5d7019a3715ec456059b3465c8bdae10f7d14760e304ee61f94951b13e3589c1078f8f1853257ccc2b4b85da198462316e95cb8cb5d82df321de6955c0e406ae1964c403fd6bded40cbd1af7958a56af52ba0d609d2428788c3e75a85280f552890c608830c55ee58abcac686e0fccbacd46ceb4e227e5600c3091236ceddb5ee58ba16a3784c2b0e8b9c22ee209cf6bbf","server_secret_key": "edfeee05c987ab609e43f5b5ab6e8affd3a34249db192fdf9978cc664c591b99","session_key": "ce6589a816fb281814bca9fe6ae1d6e3d48c865d02673fe48982f6324a130bfc"},"created_at": 1607305069408}},"items": {"spotify:track:7nDYw1nNAW4dAqgmW2W3tq": {"name": "Almost (Sweet Music)"},"spotify:track:6JqYhSdTE4WbQrMXxPH5cD": {"name": "Honeypie"},"spotify:track:3QpkbrYXtlU3LRJu3sTK6V": {"name": "Joy"},"spotify:track:1yTTMcUhL7rtz08Dsgb7Qb": {"name": "The Bones - with Hozier"},"spotify:track:6S1IgeHxxOT9qVWnmsdGxe": {"name": "Treehouse (feat. Shotty Horroh)"},"spotify:track:3VXvKTOQoY0kWvpjU67uq2": {"name": "Daydreaming"},"spotify:track:2P0FH5jSRu8cctdYfTXtje": {"name": "Space and Time"},"spotify:track:45bE4HXI0AwGZXfZtMp8JR": {"name": "you broke me first"},"spotify:track:4wosxLl0mAqhneDzya2MfY": {"name": "Head & Heart (feat. MNEK)"},"spotify:track:2J4P46vCFm1rPkNkp9pZWX": {"name": "Ice Cream (with Selena Gomez)"},"spotify:track:6As34Fmjj7dtReKB51NOVc": {"name": "Super Natural"},"spotify:track:5t9KYe0Fhd5cW6UYT4qP8f": {"name": "Good Vibrations - Remastered"},"spotify:track:17jEoYoOfRD6dvNCMmC9n4": {"name": "City Club"},"spotify:track:1zXpHPdBAUxnOCQqFMFLk3": {"name": "Saltwater"},"spotify:track:2kXeAEpGBN874ZKJPV24fr": {"name": "Acaríñame"},"spotify:track:03ITeFvMvTRpTC92WsQWw5": {"name": "La Cumbia de los Monjes"},"spotify:track:3eCwKRKjGT0EIJe3FKOjIo": {"name": "La Sirenita"},"spotify:track:3HVRywtkhSjhpmkaeaYTgh": {"name": "Life Itself"},"spotify:track:0YedjUOqafibhe8htcD6Gz": {"name": "Bounce (feat. N.O.R.E.) - Radio Version"},"spotify:track:4G3DWijMhNkWZwLcxnDI0H": {"name": "Freeze Me"},"spotify:track:54WIS7qug0Gnt65eD9gg8g": {"name": "Some Sunsick Day"},"spotify:track:4kK14radw0XfwxJDPt9tnP": {"name": "Painting (Masterpiece)"},"spotify:track:1tkg4EHVoqnhR6iFEXb60y": {"name": "What You Know Bout Love"},"spotify:track:5u1n1kITHCxxp8twBcZxWy": {"name": "Holy (feat. Chance The Rapper)"},"spotify:track:5KCbr5ndeby4y4ggthdiAb": {"name": "Wonder"},"spotify:track:0k7wmahjkn389wAZdz19Cv": {"name": "Drankin N Smokin"},"spotify:track:5nLNuK7OoJt36gY9gWgnbo": {"name": "Get Rich or Die Tryin"},"spotify:track:5vk6nP3fXbz9FoFmsu5coD": {"name": "Boujee"},"spotify:track:7ytR5pFWmSjzHJIeQkgog4": {"name": "ROCKSTAR (feat. Roddy Ricch)"},"spotify:track:0sNOPYInjylsM8ZnQozPjt": {"name": "Winter Is Coming - From The \"Game Of Thrones\" Soundtrack"},"spotify:track:4U1c58fpDgbjkb6sVQg26L": {"name": "Jon's Honor - From The \"Game Of Thrones\" Soundtrack"},"spotify:track:5ryZK3msA04LNcnMaMtm6p": {"name": "The Night's Watch - From The \"Game Of Thrones\" Soundtrack"},"spotify:track:00deiAYxr1qQx4km9ftnPK": {"name": "White Walkers"},"spotify:track:7lQ8MOhq6IN2w8EYcFNSUk": {"name": "Without Me"},"spotify:track:4CNzuSQoL5jgCxzYmuMvcz": {"name": "Like It (with 6LACK)"},"spotify:track:4VSyH8AkIt3kaR5xIPFVVi": {"name": "Where My Girls At"},"spotify:track:2h9TDNEXRhcDIV3fsoEVq9": {"name": "What Other People Say"},"spotify:track:1A8990rtwHQ417l3yADe5t": {"name": "GNF (OKOKOK)"},"spotify:track:0tQmgwFKw9069z1BXniOiA": {"name": "Provide (feat. Chris Brown & Mark Morrison)"},"spotify:track:2u8NmvhYX6wiviyxJTOhEi": {"name": "Making A Fire"},"spotify:track:2Y0wPrPQBrGhoLn14xRYCG": {"name": "Come & Go (with Marshmello)"},"spotify:track:5SWnsxjhdcEDc7LJjq9UHk": {"name": "Runnin"},"spotify:track:3SYO8wU4bEgIYt7AeGRIwG": {"name": "Nightwhisper"},"spotify:track:73X9X7kDgsm4YeHpc8prf6": {"name": "Apricots"},"spotify:track:1XXimziG1uhM0eDNCZCrUl": {"name": "Up"},"spotify:track:7lPN2DXiMsVn7XUKtOW1CS": {"name": "drivers license"},"spotify:track:463CkQjx2Zk1yXoBuierM9": {"name": "Levitating (feat. DaBaby)"},"spotify:track:5QO79kh1waicV47BqGRL3g": {"name": "Save Your Tears"},"spotify:track:3YJJjQPAbDT7mGpX3WtQ9A": {"name": "Good Days"},"spotify:track:6Im9k8u9iIzKMrmV7BWtlF": {"name": "34+35"},"USRW29600011": {"name": "Everlong","spotfiy_id": "spotify:track:5UWwZ5lm5PKu6eKsHAGxOk","isrc": "USRW29600011"},"USRW30900002": {"name": "Everlong - Acoustic Version","spotfiy_id": "spotify:track:3QmesrvdbPjwf7i40nht1D","isrc": "USRW30900002"},"USA2P2125949": {"name": "All Eyes On Me - Song Only","spotfiy_id": "spotify:track:47emsK4Cj4dMqctYq18U03","isrc": "USA2P2125949"},"CAUM72100222": {"name": "Therapy","spotfiy_id": "spotify:track:3rsJVGczbI4PRb9YdyoZms","isrc": "CAUM72100222"},"QZES82074435": {"name": "Stunnin' (feat. Harm Franklin)","spotfiy_id": "spotify:track:2D0dj3hVkRQJCp63cxCPEx","isrc": "QZES82074435"},"USUM72021500": {"name": "Therefore I Am","spotfiy_id": "spotify:track:54bFM56PmE4YLRnqpW6Tha","isrc": "USUM72021500"},"GBAHS2100318": {"name": "Bad Habits","spotfiy_id": "spotify:track:6PQ88X9TkUIAUIZJHW2upE","isrc": "GBAHS2100318"},"FRX202125956": {"name": "Tonight","spotfiy_id": "spotify:track:3YoiHH3Myq1fo19pNCTmkW","isrc": "FRX202125956"}},"admin": {"auth": {"salt": "f0c7b75bc552729b75de85c221238275a665b2608204dc2def1bec3657c22946","verifier": "1a058c680c16bb0f09e2a69d92ca79ee4bdd24f28645fe5e5fed5b1ca8edd8d46760b5b469653923d1876695a4246fd603dd6045c81f10ed69d8b515ceb6e1784a4e0041ee7e9f716cc374519d258fbc08c14f9b82db7e2187a3693fa15e32f9bbb4c3a718ee7e8a30e9fec8bcf57ad592922206b84ca0af2480b4e98016600496a8873278745f2ec53306ea9e7dddfa3277faac8aa00f6091337fdaf66cb2e61fc45cca130b43dedeb3db2f3f3d9c1a8cdcdc17ec3c0892c621478afb22f6060e3e0195ea8527a07c6e0bc01b7304baad7a22a3e6162eec6c5adc8f78a1a4704430d7bba6d4f7c0a90960d98703a82d4e8069487cb35fe295fdb6b1b4e4eb51","client_public_key": "725fce92074dd0b06b3dea7858ddc2f12cd9a40f3091019c3ab743222783986d20924babef5904ae5acc976998b8186e846187feb1f98026060966619fba3c414546fdcca95b357eae4446b942fdfb79ad8c540f3e715a6a1162db8bd17edffc9596563f0757b70475b77017310861dfe1b504893fc50fb15e334a366f3a919eebb2c9996f27779f470a70b07f0a0f6db553de0f2d1f49fc778b44e897f13c96eac82b3004f428b8dbb966d01c36c85c50bcc262965f54af4e622d5ec8bbd9b4d2e4bc645d6b9784fa156ed081566f29cdcaad190bdca02d7026cb0d6b6c764ed5881d4a65ac9a5b0ae896beb9c1fb5b89811d90b596206a65ee9fded3889764","server_secret_key": "d5c862d34de2c18b3c162be538cfb59e5957224452b6c9733a37ff27f580480f","session_key": "bc444d93d7efa3010bdbe36e3762af63850147fd2f13df7c31ca3696348b591c"}}},"setup": {"admin": {"token": "BQAICrqhcCVqGJ5C73OVEuvJ-AHiyU9Fsa44yB6aBu1WC1q7Ig0-_QeGLkk7ZCCE_gX6_zU_ncwnL7Ot8JxeQFPBdsspZ3ZlK4hMyEmmOYHgnANFf48BOeuADuFjRogM-dXqpyTMWyQGVW6ShftOllU1ljsmWvBKPkbLuj4Afx4Fs3So15BOC4KQJsdyOsH_","code": "AQBkIHzrU8B3Kd5qmmcXdea-iyF60OaPk0zK6oS40zv4q2-RD2H9SQr1KHiinx8fx_IxO2VNgF1WOZp_ahnq6pPoQ42MgL7i8-ou1jxgMJyzD7Gc64cPc17EK64FWHW8__ugxPTN-Cl_BMp00zBzc80M3PxVb67oPCW3KALp1rNollD2Z2JPQhlikaHr3Zzj8otGbhVSvZfTbUY4eA0Sh6lrwWj8UtXwGlSBszBufNlgjh6--g3PtkrR5g","refresh_token": "AQDxl71M2uyxcKw-1j-oxKYpcnQvsKcRLra8i43aYJTqPgZkh5P4QgOiRZwIuiXKuUG3ebEomKtudJ_NDBw5_NXnwZk1seXqXSOIa5reZyZ3LTW5RG30WEA9KOOgi4DVQ8k","id": "12166793664","expiry": 1607203962},"events": {"75c6cfd0-139a-4a33-8826-9c284645f1ae": {"name": "Calgary Weekly List","playlist": [["spotify:track:5t9KYe0Fhd5cW6UYT4qP8f",2],["spotify:track:6As34Fmjj7dtReKB51NOVc",2],["spotify:track:4kK14radw0XfwxJDPt9tnP",1],["spotify:track:54WIS7qug0Gnt65eD9gg8g",1],["spotify:track:4G3DWijMhNkWZwLcxnDI0H",1],["spotify:track:0YedjUOqafibhe8htcD6Gz",1],["spotify:track:3HVRywtkhSjhpmkaeaYTgh",1],["spotify:track:3eCwKRKjGT0EIJe3FKOjIo",1],["spotify:track:03ITeFvMvTRpTC92WsQWw5",1],["spotify:track:2kXeAEpGBN874ZKJPV24fr",1],["spotify:track:1zXpHPdBAUxnOCQqFMFLk3",1],["spotify:track:5KCbr5ndeby4y4ggthdiAb",1],["spotify:track:5u1n1kITHCxxp8twBcZxWy",1],["spotify:track:1tkg4EHVoqnhR6iFEXb60y",1],["spotify:track:17jEoYoOfRD6dvNCMmC9n4",1],["spotify:track:2P0FH5jSRu8cctdYfTXtje",1],["spotify:track:3VXvKTOQoY0kWvpjU67uq2",1],["spotify:track:6S1IgeHxxOT9qVWnmsdGxe",1],["spotify:track:1yTTMcUhL7rtz08Dsgb7Qb",1],["spotify:track:3QpkbrYXtlU3LRJu3sTK6V",1],["spotify:track:6JqYhSdTE4WbQrMXxPH5cD",1],["spotify:track:7nDYw1nNAW4dAqgmW2W3tq",1]],"spotify_id": "1UxyXZ4YuKdbPHNkFYXpyZ"},"62854dc2-7d97-45d3-be03-f0bac69119f8": {"name": "Toronto Weekly List","playlist": [["spotify:track:2J4P46vCFm1rPkNkp9pZWX",1],["spotify:track:4wosxLl0mAqhneDzya2MfY",1],["spotify:track:45bE4HXI0AwGZXfZtMp8JR",1]],"spotify_id": "3Do3cXEkmdHao31jX0B1oo"},"fe71a1ee-6e64-4d4f-8a03-7b091d93c823": {"name": "Belgium Weekly List","playlist": [],"spotify_id": "6vXyy0FUbzWgrPuNB9xY88"}}}}
{"state": {"events": {"75c6cfd0-139a-4a33-8826-9c284645f1ae": {"name": "Calgary Weekly List"},"62854dc2-7d97-45d3-be03-f0bac69119f8": {"name": "Toronto Weekly List"},"fe71a1ee-6e64-4d4f-8a03-7b091d93c823": {"name": "Belgium Weekly List"}}}}
#!/usr/bin/env bash. $BIN_DIR/_lib.shnode --experimental-modules --experimental-json-modules --es-module-specifier-resolution=node src/index.js
.git*.lognode_modulesapp/nginx.conf
#!/usr/bin/env bash. $BIN_DIR/_lib.shrsync --progress -Pavuz --exclude-from="$WORKING_BIN_DIR/rsync-deploy.ignore" -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" "${MONO_DIR}/." "tpcowan@processor.djinmusic.ca:/home/tpcowan/djinmusic"rsync --progress -Pavuz -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" $WORKING_BIN_DIR/deploy-remote.sh "tpcowan@processor.djinmusic.ca:/home/tpcowan/deploy-remote.sh"ssh -F $HOME/.ssh/id_rsa_corda_digital_ocean tpcowan@processor.djinmusic.ca "sh /home/tpcowan/deploy-remote.sh"
#!/usr/bin/env bashif [[ "$(uname -s)" == "Darwin" ]]; thenecho "Don't run this on your local computer!"exit 1fiecho "[remote] Updating processor"cd djinmusicpnpm install -rcd ..echo "[remote] Installed"
#!/bin/zshautoenv_source_parentautostash WORKING_DIR=$(dirname ${0})autostash WORKING_BIN_DIR="${WORKING_DIR}/.bin"autostash alias start="${WORKING_BIN_DIR}/start.sh"
import crypto from "crypto"class Session {constructor() {this.cleaner = setInterval(this.clean.bind(this), 1000 * 60 * 60)this.key = '2390fbad9157e1f8de9ecb5a494feeb988dfe2ba31c39ca1ba91ba2fa9d30d20'}clean() {const timeouts = datastore.read('/session/connections/+/tokens/+')_.forEach(timeouts, (timeout, path) => {if (+ new Date < timeout) { return }const pointer = new Pointer(path.replace('/timeout', ''))datastore.destroy(pointer.path)})}fetchSession(cookie, ws, channels) {console.log(`fetchSession\n\ntoken:\n${cookie}`)const response = {o: 'a',c: 5,v: false}// {// tokens: ,// topics: []// }const [user, token, mac] = cookie.split(':')const verify = crypto.createHmac('sha256',user + ':' + token,this.key).digest('hex')const session = datastore.read(`/session/connections/${user}`)if (!session) returnif (crypto.timingSafeEqual(Buffer.from(verify, 'utf8'), Buffer.from(mac, 'utf8')) &&session && session.tokens) {_.find(session.tokens, (timestamp, stored_token) => {if (crypto.timingSafeEqual(Buffer.from(stored_token, 'utf8'), Buffer.from(token, 'utf8')) &×tamp > + new Date) {response.v = { u: user, s: session.topics || [] }return true}})}_.forEach((session.topics || []), pattern => {console.log(pattern)const topic = new Topic(pattern)const channel = channels[topic.trunk[0]]channel.resume(topic, ws)})ws.send(JSON.stringify(response))}requestSession(user, ws, users) {if (!users.isAuthorized(new Topic(`state/users/${user}/#`), ws)) {return}const token = crypto.randomBytes(256).toString('hex')const cookie = user + ':' + tokenconsole.log(`postSession for ${user} \n\ntoken:\n${token}`)const mac = crypto.createHmac('sha256',user + ':' + token,this.key).digest('hex')console.log('L')console.log(mac)datastore.write(`/session/connections/${user}/tokens/${token}`, + new Date + 60 * 60 * 24 * 7 * 1000)const response = {o: 'a',c: 6,v: cookie + ':' + mac}ws.send(JSON.stringify(response))}addSubscription(user, topic) {console.log(`#addSubscription ${user}, ${topic.pattern}`)const topics = new Set(datastore.read(`/session/connections/${user}/topics`) || [])topics.add(topic.pattern)datastore.write(`/session/connections/${user}/topics`, Array.from(topics))}toString() {return 'Channel-Connections'}}const session = new Session()export { session }
import WebSocket from 'ws'import { v4 as uuid } from 'uuid'import './models/item'import './models/event'import './models/user'import './models/admin'import './models/stash'import './models/spotify'import { events } from './channels/event.js'import { users } from './channels/user.js'import { items } from './channels/item.js'import { admin } from './channels/admin.js'import { session } from './session.js'let main = () => {/* Models */const channels = {events: events,users: users,items: items,admin: admin}console.log(channels)/* IPC Sockets */const port = 25706const wss = new WebSocket.Server({ port })console.log(`Listenning on ${port}`)wss.on('connection', function connection(ws, request) {const ip = request.socket.remoteAddressws.uuid = uuid()ws.toString = () => ws.uuidconsole.log(`Connection from ${ip}`)datastore.push('/session/connections', ip)const node_id = datastore.read('/session/node_id')const hello = {}hello.o = 'w'hello.v = node_idhello.p = '/session/node_id'ws.send(JSON.stringify(hello))const publish = (topic, pointer) => {const message = {}const { tree, path } = pointerconst { value } = treemessage.o = 'p'message.t = topicmessage.p = pathmessage.v = valueconsole.log(`sending: ${JSON.stringify(message)}`)ws.send(JSON.stringify(message))}ws.on('message', (message) => {console.log(message)message = JSON.parse(message)if (!message.o) {return}let pointer, topic, channelswitch (message.o) {case 'a': {switch (message.c) {case 0: {// createAccounttopic = new Topic(message.t)channel = channels[topic.trunk[0]]channel.createAuthorization(topic, message.v, ws)} breakcase 1: {// startSessiontopic = new Topic(message.t)channel = channels[topic.trunk[0]]channel.authorize(topic, message.v, ws)} breakcase 4: {// verifySessiontopic = new Topic(message.t)channel = channels[topic.trunk[0]]if (channel.prove(topic, message.v, ws)) {session.addSubscription(message.v.u, topic)}} breakcase 5: {// post sessionsession.fetchSession(message.v, ws, channels)} breakcase 6: {// fetch sessionsession.requestSession(message.v, ws, users)} breakdefault:break}} breakcase 'm': {pointer = new Pointer(message.p)channel = channels[pointer.trunk[0]]channel.merge(pointer, message.v, ws)} breakcase 'w': {pointer = new Pointer(message.p)channel = channels[pointer.trunk[0]]channel.write(pointer, message.v, ws)} breakcase 'd': {pointer = new Pointer(message.p)channel = channels[pointer.trunk[0]]channel.delete(pointer, ws)} breakcase 's': {topic = new Topic(message.t)console.log({topic})channel = channels[topic.trunk[0]]channel.subscribe(topic, ws, publish)if (!message.i) { break }console.log('IMMEDIATE')message.p = `/${message.t}`}case 'r': {pointer = new Pointer(message.p)channel = channels[pointer.trunk[0]]_.forEach(channel.read(pointer, ws), (value, path) => {const response = { o: 'r' }response.p = pathresponse.v = valuews.send(JSON.stringify(response))})} breakcase 'u': {topic = new Topic(message.t)channel = channels[topic.trunk[0]]channel.unsubscribe(topic, ws, publish)if (!message.i) break}}})ws.on('close', () => {datastore.pull('/session/connections', ip)_.forEach(channels, channel => {channel.unsubscribe(ws)})console.log('disconnected')})})const cleanup = () => {console.log('\rShutting down server') // eslint-disable-line no-consoleprocess.removeListener('SIGINT', cleanup)process.removeListener('SIGTERM', cleanup)wss.close(() => {datastore.destroy('')process.exit()})}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)}// TODO: Figure out a better way to prevent jsdoctest from executing this.if (!process.env.TEST) {main()}
import { v4 as uuid } from 'uuid'class User {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/state/users/+/#', this.onStateQueued.bind(this))datastore.subscribe('q/action/users/+/events/new', this.onEventCreate.bind(this))datastore.subscribe('q/setup/users/+/events/+/+', this.onEventSetup.bind(this))}toString() {return 'Model-Users'}onStateQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onStateQueued: ${pointer.path}, ${JSON.stringify(value)}`)if (!value || event.type !== '=') { return }if (pointer.leaf == 'pin') {// FIXME: @thomascowan what purpose does this serve? 09/20/2021this.accept(pointer, value)pointer.leaf = 'srp'datastore.set(pointer.path, 'authenticated')} else if (pointer.steps[4] === 'events') {switch (pointer.leaf) {case 'vote': {// NOTE: if the leaf is 'vote', then transfer the vote to the eventconst event_id = datastore.read(`/state${pointer.trunk_path}/event_id`)console.log('event_id', pointer.branch_path, `/state${pointer.trunk_path}/event_id`, event_id)if (!event_id) { return }datastore.set(pointer, null, { silent: true })console.log(`Transfer vote to /q/state/${pointer.branch.slice(2,4).join('/')}${pointer.trunk_path}/${pointer.branch.slice(-2).join('/')}`)datastore.set(`/q/state/${pointer.branch.slice(2,4).join('/')}${pointer.trunk_path}/${pointer.branch.slice(-2).join('/')}`, value)datastore.set(pointer, null, { silent: true })} breakcase 'pin': {datastore.read(`/setup/${pointer.branch.slice(2,4).join('/')}/owner`)} break}} else {this.accept(pointer, value)}}// What are the basic actions that can be taken on an event? Assuming there are three levels// of permissions that are consistent across all event types.// (These event types can be elaborated and parse out by reading a 'type' leaf later)// 1. Owner// 2. Admin// - add admin (action) v// - change name (setup) v// - splice// - insert// - blacklist (setup) v// - whitelist (setup) v// - ban user (setup) v// - unban user (setup) v// - change date (setup)// - description (setup) v// 3. Participant// - vote//// 'q/setup/users/+/events/+/+'onEventSetup(topic, event) {if (event.type !== '=') returnconst { pointer } = eventswitch(pointer.leaf) {// leafs that don't require validationcase 'name':case 'description':case 'private': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {this.accept(event.pointer.sliceBranch(2), event.pointer.value)}} break// leafs that require some validationcase 'pin': {this.accept(pointer, pointer.value)} breakcase 'time': {this.accept(pointer, pointer.value)} breakcase 'type': {if (!['recurring', 'one_time'].includes(pointer.value)) returnthis.accept(pointer, pointer.value)} break// special casescase 'blacklist': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {datastore.push(`/setup/events/${pointer.setps[5]}/blacklist`, pointer.value)}} breakcase 'whitelist': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {datastore.pull(`/setup/events/${pointer.setps[5]}/blacklist`, pointer.value)}} breakcase 'ban': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {const admins = datastore.read(`/setup/events/${pointer.steps[5]}/admin`)if (admins.includes(event.pointer.value)) {if (this.permissionFor('user', pointer.steps[3], pointer.steps[5])) {datastore.pull(`/setup/events/${pointer.setps[5]}/admin`, pointer.value)datastore.push(`/setup/events/${pointer.setps[5]}/banned`, pointer.value)}} else {datastore.pull(`/setup/events/${pointer.setps[5]}/banned`, pointer.value)}}} breakcase 'unban': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {datastore.pull(`/setup/events/${pointer.setps[5]}/banned`, pointer.value)}} break}}permissionFor(level, user, event) {if (level === 'owner') {return datastore.read(`/setup/events/${event}/owner`) === user} else if (level === 'admin') {(datastore.read(`/setup/events/${event}/admin`) || []).includes(user)} else {(datastore.read(`/setup/events/${event}/users`) || []).includes(user)}}onEventCreate(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueif (!value) return// NOTE: check the account type and the number of owned events// TODO: @thomascowan add support for accoun typesconst account_type = datastore.read(`/setup${pointer.trunk_path}/type`) || 'free'const owned = datastore.read(`/setup${pointer.trunk_path}/events/owned`) || []// NOTE: check that the user meets the criteria required to create a new eventswitch (account_type) {case 'business': {// NOTE: Placeholder do nothing for now} breakcase 'basic': {if (owned.length >= 5) return} breakcase 'free': {if (owned.length >= 3) return}}// NOTE: create an event with the user_idconst user_id = pointer.steps[3]const new_event_id = uuid()datastore.write(`/setup/events/${new_event_id}/private`, true)datastore.write(`/setup/events/${new_event_id}/owner`, user_id)datastore.write(`/setup/events/${new_event_id}/admin`, [user_id])datastore.write(`/setup/events/${new_event_id}/users`, [user_id])}onEventPin(topic, event) {if (event.type !== '=') {return}const { pointer } = eventconst value = pointer?.tree?.valueif (!this.validatePin(value)) {return}const owners = datastore.read(`/state/${pointer.branch.slice(2,4).join('/')}/owners`)const user_id = pointer.steps[3]if (!owners.includes(user_id)) {return}datastore.write(`/state${pointer.branch.slice(2,4).join('/')}/pin`, value)}validatePin(pin) {/\d{4}/.test(pin) // FIXME: @thomascowan Improve available logic for pins}accept(pointer, value, { force = false } = {}) {datastore.set(pointer.path, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer.path, value, { force })}destroy() {}}const users = new User()const cleanup = () => {users.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { users }
import fs from 'fs'class Stash {constructor() {console.log(`${this.toString()} #constructor`)setInterval(this.stash.bind(this), 5000)}toString() {return 'Model-Stash'}stash() {console.log(`${this.toString()} #stash`)const tree = {}const state = datastore.read('/state')const setup = datastore.read('/setup')if (state) Object.assign(tree, { state })if (setup) Object.assign(tree, { setup })fs.writeFile('./.stash.json',JSON.stringify(tree, null, 2),(error) => {if (!error) returnconsole.log("Error Writing to Stash")console.log(error)})}destroy() {clearInterval(this.interval)}}const stash = new Stash()const cleanup = () => {stash.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { stash }
import https from 'https'class Driver {constructor() {datastore.subscribe('action/events/+/start', this.initializePlaylist.bind(this))this.debouncePushPlaylistDetails = _.debounce(this.pushPlaylistDetails, 1000)datastore.subscribe('action/events/+/name', this.debouncePushPlaylistDetails.bind(this))datastore.subscribe('action/events/+/description', this.debouncePushPlaylistDetails.bind(this))datastore.subscribe('q/state/events/+/playlist', this.onPlaylistItemsQueued.bind(this))}toString() {return 'Driver-Spotify-Database'}token() {return datastore.read('/setup/admin/token')}initializePlaylist(topic, event) {const { pointer } = eventconsole.log(`#initialize_playlist ${pointer.path}`)const eventId = pointer.branch.slice(-1)[0]const name = eventIdconst admin_id = datastore.read("/setup/admin/id")const body = {name,public: true,collaborative: false}const request = https.request({method: 'POST',hostname: 'api.spotify.com',path: `/v1/users/${admin_id}/playlists`,headers: {'Authorization': `Bearer ${this.token()}`,'Content-Type': 'application/json'}},response => {console.log(response.statusCode)if (![200, 201].includes(response.statusCode)) { return }let body = ''response.on('data', chunk => {console.log(body)body += chunk.toString()})response.on('end', () => {this.decodeInitializePlaylist(JSON.parse(body))})})request.on('error', e => {console.error(`problem with request ${e.message}`)})request.write(JSON.stringify(body))request.end()}decodeInitializePlaylist(response) {console.log(`#decodeInitializePlaylist`)console.log(response)const control_id = response['id']const djin_id = response['name']const externalUrl = response['external_urls']['spotify']const branch_path = `/events/${djin_id}`datastore.write(`/setup${branch_path}/spotify_id`, control_id)this.updateInitializedPlaylist(branch_path)}updateInitializedPlaylist(branch_path) {const name = datastore.read(`/setup${branch_path}/name`)const description = datastore.read(`/setup${branch_path}/description`)const options = {name,description: description + `\nCreated with Djinlist (www.djinlist.ca).`}this.changePlaylistDetails(branch_path, options, branch_path => {datastore.write(`/setup${branch_path}/initialized`, true)})}onPushPlaylistDetails(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valuedatastore.write(`/setup${pointer.branch_path}/details_synced`, false)this.debouncePushPlaylistDetails(value, pointer)}pushPlaylistDetails(value, pointer) {if (event.type !== '=') returnconst name = datastore.read(`/setup${pointer.branch_path}/name`)const description = datastore.read(`/setup${pointer.branch_path}/description`)const options = {}if (name) { Object.assign(options, { name }) }if (description) { Object.assign(options, { description }) }this.changePlaylistDetails(pointer.branch_path, options, branch_path => {datastore.write(`/setup${branch_path}/details_synced`, true)})}changePlaylistDetails(branch_path, options, callback) {const spotify_id = datastore.read(`/setup${branch_path}/spotify_id`)console.log(`#changePlaylistDetails ${branch_path}, ${spotify_id}`)console.log('options:', options)const request = https.request({method: 'PUT',hostname: 'api.spotify.com',path: `/v1/playlists/${spotify_id}`,headers: {'Authorization': `Bearer ${this.token()}`,'Content-Type': 'application/json'}},response => {response.on('end', () => callback(branch_path))})request.on('error', e => {console.error(`problem with request ${e.message}`)})request.write(JSON.stringify(options))request.end()}onTokenExpiry(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconst time = value - (+ new Date())console.log('#onTokenExpiry #{time}')if (this.refreshTokenInterval) {clearInterval(this.refreshTokenInterval)}if (time > 0) {this.refreshTokenInterval = setInterval(this.refreshToken.bind(this), time * 1000)} else {this.refreshToken()}}refreshToken() {const refreshToken = datastore.read(`/setup/admin/refresh_id`)console.log(`#refreshToken ${refresh_id}`)if (!refreshToken) { return }const body = {grant_type: 'refresh_token',refresh_token: refreshToken}request = https.request({method: 'POST',hostname: 'accounts.spotify.com',path: '/api/token',headers: {'Authorization': `Basic ZmUwMDk5M2ZmOTNlNDgyNzgwNGFmMTZlMWRlMzEyZGU6ODQ1NzQzNzhkMDg2NDQwZGI2MDczNmRiN2MxNzc1Mzg=`,'Content-Type': 'application/json'}},response => {if (![200, 201].includes(response.statusCode)) { return }let body = ''response.on('data', chunk => {body += chunk.toString()})response.on('end', () => {this.decodeTokenRefresh(JSON.parse(body))})})request.on('error', e => {console.log(`problem with request ${e.message}`)})request.write(JSON.stringify(body))request.end()}decodeRefreshToken(message) {console.log(`#decodeRefreshToken ${JSON.stringify(message)}`)const new_expiry = (+ new Date()) + message.expires_indatastore.write('/setup/admin/token', message.access_token)datastore.write('/setup/admin/expiry', new_expiry)}onPlaylistItemsQueued(value, pointer) {if (!value) returnconsole.log(`#onPlaylistItemsQueued ${value.length}`)const spotify_id = datastore.read(`/setup${pointer.branch_path}/spotify_id`)// const length = datastore.read(`/setup${pointer.branch_path}/spotify_id`)const request = https.request({method: 'PUT',hostname: 'api.spotify.com',path: `/v1/playlists/${spotify_id}/tracks`,headers: {'Authorization': `Bearer ${this.token()}`,'Content-Type': 'application/json'}})request.on('error', e => {console.log(`problem with request ${e.message}`)})const tracks = _.reduce(value || [], (acc, track) => {return _.concat(acc, [track[0]])}, [])request.write(JSON.stringify({uris: tracks}))request.end()}destroy() {}}const spotify = new Driverconst cleanup = () => {spotify.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { spotify }
class Item {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/state/items/+/#', this.onStateQueued.bind(this))}toString() {return 'Model-Items'}onStateQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onStateQueued: ${pointer.path}, ${JSON.stringify(value)}`)this.accept(pointer, value)}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer.path, value, { force })}destroy() {}}const items = new Item()const cleanup = () => {items.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { items }
class Event {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/state/events/+/#', this.onStateQueued.bind(this))datastore.subscribe('q/setup/events/+/#', this.onSetupQueued.bind(this))this.ticker = setInterval(this.publishPlaylists.bind(this), 60 * 1000)}toString() {return 'Model-Events'}onStateQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onStateQueued: ${pointer.path}, ${JSON.stringify(value)}`)switch (pointer.branch_path.length) {case 4:if (pointer.branch_steps[2] == 'users') {this.onUserQueued(topic, pointer, value)}breakdefault:this.accept(pointer, value)break}}onSetupQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onSetupQueued: ${pointer.path}, ${JSON.stringify(value)}`)this.accept(pointer, value)}onUserQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueswitch (pointer.leaf) {case 'vote':this.accept(pointer, value)}}publishPlaylist(name, pointer) {const issueAt = datastore.read(pointer.path + '/issue_at')const currentTime = + new Date()console.log(`#publishPlaylist ${name} ${issueAt && (issueAt < currentTime)}`)if (!issueAt // &&// currentTime.getDay() == 5 ||// currentTime.getHours() == 20 ||// currentTime.getMinutes() == 0) {this.issuePlaylist(name, pointer)} else if (issueAt < currentTime) {this.issuePlaylist(name, pointer)// Determine the next time we need to update the playlistconst intervalDays = datastore.read(pointer.path + '/interval')const intervalSeconds = intervalDays * 24 * 60 * 60datastore.write(pointer.path + '/issue_at', issueAt + intervalSeconds)}}publishPlaylists() {const playlists = datastore.read('/setup/events/+/name')console.log(`#publishPlaylists`, playlists)_.forEach(playlists, (playlist, path) => {const pointer = new Pointer(path)this.publishPlaylist(playlist, pointer.slice(0, -1))})}tally(pointer) {const items = datastore.read(`/state${pointer.trunk_path}/users/+/items/+/vote`)const length = datastore.read(`/state${pointer.trunk_path}/users/+/items/+/vote`)const tally = {}_.forEach(items, (value, path) => {const id = path.split('/').slice(-2, -1)[0]if (tally[id]) {tally[id] += 1} else {tally[id] = 1}})const sorted_list = _.reverse(_.sortBy(_.entries(tally), entry => entry[1]))const current_list = datastore.read(`/state${pointer.trunk_path}/playlist`)let match = trueif (current_list) {for (var idx in current_list) {if (current_list[idx] != sorted_list[idx]) { match = false }}} else {match = false}if (match) returndatastore.write(`/q/state${pointer.trunk_path}/playlist`, sorted_list)}issuePlaylist(name, pointer) {console.log(`#issuePlaylist ${name} at ${pointer.path}`)const exists = datastore.read(`/setup${pointer.trunk_path}/spotify_id`)if (exists) {this.tally(pointer)} else {datastore.write(`/action${pointer.trunk_path}/start`, +new Date())}}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer.path, value, { force })}destroy() {console.log(`${this.toString()} #destroy`)clearInterval(this.ticker)}}const events = new Event()const cleanup = () => {events.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { events }
class Admin {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/setup/admin/#', this.onSetupQueued.bind(this))}toString() {return 'Model-Admin'}onSetupQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onSetupQueued: ${pointer.path}, ${JSON.stringify(value)}`)this.accept(pointer, value)}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer.path, value, { force })}destroy() {}}const admin = new Admin()const cleanup = () => {admin.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { admin }
import _ from 'lodash'if (!global._) global._ = _import { Datastore, Pointer, Topic } from '@controlenvy/datastore'if (!global.datastore) global.datastore = new Datastore()if (!global.Pointer) global.Pointer = Pointerif (!global.Topic) global.Topic = Topicimport fs from 'fs'const loadConfig = async () => {// configuration information goes heredatastore.set('/session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')let filePathfs.access('.stash.json', fs.constants.F_OK, (err) => {filePath = `.${err ? 'preconfig' : 'stash'}.json`})const onFileFound = (file, data) => {if (!data) { return false }const _root = JSON.parse(data)if (Object.keys(_root).length === 0) { return false }console.log(`index.js Parsing root found at ${file}`)datastore.merge('/state', _root.state)datastore.merge('/setup', _root.setup)main()return true}['.stash.json', '.preconfig.json'].find(filePath => {try {return onFileFound(filePath, fs.readFileSync(filePath))} catch (e) {console.log(`Error reading config at ${filePath}`)console.log(e)return false}})}const main = () => {import('./server')}loadConfig()
import { Base } from './base.js'class User extends Base {constructor() {super()datastore.subscribe('state/users/+/#', this.publish.bind(this))this.blacklist(new Topic('state/users/+/auth/#'))this.blacklist(new Topic('state/users/+/auth/*'))}toString() {return 'Channel-Users'}}const users = new User()export { users }
class TopicTree {constructor() {Object.defineProperties(this, {_root: {value: this.createTreeNode()}})}createTreeNode() {const node = Object.create(null)Object.defineProperties(node, {_value: {writable: true}})return node}// expensive, call rarelyall(func = null, output = [], node = this._root) {if (node._topic && node._value && (!func || func(node))) {output.push([node._topic, node._value])}_.forEach(node, (child, key) => {if (!['_value', '_topic'].includes(key)) {this.all(func, output, child)}})return output}apply(func, node = this._root) {func(node)return _.forEach(node, (child, key) => {if (!['_value', '_topic'].includes(key)) {this.apply(func, child)}})}get(topic) {const steps = topic.split('/')let left = this._rootfor (const step of steps) {left = left[step]if (left == null) {left = this.createTreeNode()break}}return left}getWithDefault(topic, value) {const steps = topic.split('/')let node = this._rootfor (const step of steps) {if (node[step] == null) {node[step] = this.createTreeNode()}node = node[step]}if (node._value == null) {node._topic = topicnode._value = value}return node}add(topic, value) {const node = this.getWithDefault(topic)node._topic = topicnode._value = value}values(topic) {const steps = topic.split('/')return this._values(this._root, steps, 0, []).reverse()}_values(node, steps, pivot, values) {if (steps.length == pivot) {if (node._value != null) {values.push(node._value)}return values}const step = steps[pivot]if (node['#'] != null) {values.push(node['#']._value)}if (node['+'] != null) {values = this._values(node['+'], steps, pivot + 1, values)}if (node[step] != null) {values = this._values(node[step], steps, pivot + 1, values)}return values}entries(topic) {const steps = topic.split('/')return this._entries(this._root, steps, 0, []).reverse()}_entries(node, steps, pivot, entries) {if (steps.length == pivot) {if (node._value != null) {entries.push([node._topic, node._value])}if (node['*'] != null) {entries.push([node['*']._topic, node['*']._value])}return entries}const step = steps[pivot]if (node['#'] != null) {entries.push([node['#']._topic, node['#']._value])}if (node['+'] != null) {entries = this._entries(node['+'], steps, pivot + 1, entries)}if (node[step] != null) {entries = this._entries(node[step], steps, pivot + 1, entries)}return entries}}export { TopicTree }
import { Base } from './base.js'class Item extends Base {constructor() {super()this.whitelist(new Topic('state/items/+/#'))}toString() {return 'Channel-Items'}}const items = new Item()export { items }
import { Base } from './base.js'class Event extends Base {constructor() {super()this.whitelist(new Topic('setup/events/+/name'))this.whitelist(new Topic('setup/events/+/items/#'))this.whitelist(new Topic('setup/events/+/private'))this.blacklist(new Topic('state/events/+/pin'))datastore.subscribe('setup/events/+/pin', this.syndicate.bind(this))datastore.subscribe('setup/events/+/#', this.publish.bind(this))datastore.subscribe('state/events/+/#', this.publish.bind(this))this.initialize('/setup/events', '/pin', this.syndicate.bind(this))}toString() {return 'Channel-Events'}}const events = new Event()export { events }
import { coppice } from '@controlenvy/datastore'import { TopicTree } from './topic_tree.js'import { authorization } from '../authorization.js'class Base {constructor() {Object.defineProperties(this, {_permissions: {value: new TopicTree},_subscribers: {value: new TopicTree}})}toString() {return 'Channel-Base'}whitelist(topic) {console.log(`${this.toString()} #whitelist ${topic.toString()}`)this.list(topic, 'whitelist')}blacklist(topic) {console.log(`${this.toString()} #blacklist ${topic.toString()}`)this.list(topic, 'blacklist')}dewhitelist(topic) {console.log(`${this.toString()} #dewhitelist ${topic.toString()}`)this.delist(topic, 'whitelist')}deblacklist(topic) {console.log(`${this.toString()} #deblacklist ${topic.toString()}`)this.delist(topic, 'blacklist')}list(topic, type) {const permissions = this._permissions.getWithDefault(topic.pattern, [])permissions._topic = topicpermissions._value.push(type)}initialize(prefix, postfix, callback) {console.log(`${this.toString()} #initialize ${prefix}/+${postfix}`)const keys = datastore.get(prefix)?.keys() || []const callback_topic = `${prefix}/+${postfix}`_.forEach(keys, key => {const pointer = new Pointer(`${prefix}/${key}${postfix}`)const value = datastore.read(pointer)callback(callback_topic, pointer, value)})}delist(topic, type) {const permissions = this._permissions.get(topic.pattern)if (permissions == null || Object.keys(permissions).length === 0) { return }permissions.delete(type)}syndicate(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconst syndicated_path = pointer.steps.slice(0, -1).concat('#').join('/')const private_path = '/' + pointer.steps.slice(0, -1).concat('private').join('/')console.log(`${this.toString()} #syndicate ${syndicated_path} ${value}`)if (!value) {this.whitelist(syndicated_path)datastore.delete(private_path)} else {this.dewhitelist(syndicated_path)datastore.write(private_path, true)}}publish(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconsole.log(`${this.toString()} #publish ${topic} @ ${pointer.path}`)const permissions = this._permissions.entries(pointer.path.slice(1))let whitelisted = falsefor (var idx in permissions) {const _permitted = permissions[idx][1]if (_permitted.includes('blacklist')) { return }if (_permitted.includes('whitelist')) {whitelisted = true}}const permitted = _.concat(...Object.values(_.fromPairs(permissions)))console.log({permitted})_.remove(permitted,subscriber => ['whitelist', 'blacklist'].includes(subscriber))const subscribers = this._subscribers.entries(pointer.path.slice(1))if (subscribers.length == 0) { return }let parseif (whitelisted) {parse = ([_topic, subscribed]) => {_.forEach(subscribed, callbacks => {callbacks.forEach(callback => callback(_topic, pointer))})}} else {parse = ([_topic, subscribed]) => {_.forEach(subscribed, (callbacks, subscriber) => {if (permitted.includes(subscriber)) {callbacks.forEach(callback => callback(_topic, pointer))}})}}_.forEach(subscribers, parse)}createAuthorization(pointer, packet, subscriber) {const response = authorization.createAuthorization(pointer, packet)subscriber.send(JSON.stringify(response))}authorize(topic, packet, subscriber) {const response = authorization.authorize(topic, packet)subscriber.send(JSON.stringify(response))}prove(topic, packet, subscriber) {const response = authorization.prove(topic, packet)if (response.v.p === false) {console.log(`#prove FAIL ${topic.pattern} ${subscriber}`)return false}topic = topic.changeRoot('+')const permissions = this._permissions.getWithDefault(topic.pattern, [])permissions._value.push(subscriber.toString())console.log(`${this.toString()} #prove SUCCEED ${topic.pattern} ${subscriber}`)subscriber.send(JSON.stringify(response))return true}resume(topic, subscriber) {console.log(`${this.toString()} #topic ${topic.pattern} ${subscriber}`)const permissions = this._permissions.getWithDefault(topic.pattern, [])permissions._value.push(subscriber.toString())}isAuthorized(topic, subscriber) {const entries = this._permissions.entries(topic.dequeue().pattern)console.log({p: this._permissions._root.state, kk: topic.dequeue().pattern, entries})if (!entries) {console.log(`${this.toString()} #isAuthorized FAIL ${topic.pattern}`)return false}let authorized = falsefor (var idx in entries) {const callbacks = entries[idx][1]if (callbacks.includes('blacklist')) {console.log(`${this.toString()} #isAuthorized BLACKLIST ${entries[idx][0]} ${topic.pattern}`)return false}if (callbacks.includes('whitelist')) {console.log(`${this.toString()} #isAuthorized WHITELIST ${entries[idx][0]}`)authorized = true} else if (callbacks.includes(subscriber.toString())) {console.log(`${this.toString()} #isAuthorized APPROVED ${entries[idx][0]}`)authorized = true}}console.log(`${this.toString()} #isAuthorized ${authorized ? 'SUCCEED' : 'FAIL'} ${topic.pattern}`)return authorized}read(pointer, subscriber) {const data_coppice = {}if (pointer.isWildcard()) {console.log(`${this.toString()} #search ${pointer.path}`)return _.reduce(datastore.search(pointer.path), (result, value, path) => {if (this.isAuthorized(new Topic(path.slice(1)), subscriber)) {result[path] = value}return result}, {})} else {console.log(`${this.toString()} #read ${pointer.path}`)if (!this.isAuthorized(new Topic(pointer.steps), subscriber)) { return }const found = datastore.read(pointer.path)if (_.isPlainObject(found)) {coppice(found, pointer.path, data_coppice)} else {data_coppice[pointer.path] = found}}return data_coppice}write(pointer, value, subscriber) {if (!this.isAuthorized(new Topic(pointer.steps), subscriber)) { return }console.log(`${this.toString()} #write`, pointer.path, value)return datastore.write(pointer.path, value)}merge(pointer, value, subscriber) {if (!this.isAuthorized(new Topic(pointer.steps), subscriber)) { return }console.log(`${this.toString()} #merge`, pointer.path, value)return datastore.merge(pointer.path, value)}delete(pointer, subscriber) {if (!this.isAuthorized(new Topic(pointer.steps), subscriber)) { return }return datastore.delete(pointer.path)}subscribe(topic, subscriber, callback) {const subscribers = this._subscribers.getWithDefault(topic.pattern, {})._valueif (_.isArray(subscribers[subscriber])) {subscribers[subscriber].push(callback)} else {subscribers[subscriber] = [callback]}}unsubscribe(subscriber) {console.log(`#unsubscribe ${subscriber}`)const removeSubscriber = ({ _value }) => {if (!_value) returndelete _value[subscriber]}this._subscribers.apply(removeSubscriber)const removePermission = ({ _value }) => {if (!_value) return_.remove(_value, subscriber.toString())}this._permissions.apply(removePermission)}}export { Base }
import { Base } from './base.js'class Admin extends Base {constructor() {super()this.blacklist(new Topic('state/admin/auth/#'))this.blacklist(new Topic('state/admin/auth/*'))}toString() {return 'Channel-Admin'}}const admin = new Admin()export { admin }
import srp from "secure-remote-password/server"import crypto from "crypto"class serverAuthorization {createAuthorization(topic, packet) {// 0const salt = packet.sconst verifier = packet.vconst salt_topic = topic.replace('/#', '/auth/salt')const verifier_topic = topic.replace('/#', '/auth/verifier')const response = {o: 'a',c: 0,t: topic.pattern,v: true}console.log({salt_topic})response.v = !datastore.read('/' + salt_topic.pattern)if (response.v) {datastore.write('/' + salt_topic.pattern, salt)datastore.write('/' + verifier_topic.pattern, verifier)}return response // reject}authorize(topic, packet) {// 1const salt_topic = topic.replace('/#', '/auth/salt')const verifier_topic = topic.replace('/#', '/auth/verifier')const client_public_key_topic = topic.replace('/#', '/auth/client_public_key')const server_secret_key_topic = topic.replace('/#', '/auth/server_secret_key')console.log({topic, salt_topic, verifier_topic, client_public_key_topic, server_secret_key_topic})datastore.write('/' + client_public_key_topic.pattern, packet.k)const response = {o: 'a',c: 2,t: topic.pattern,v: {u: packet.u}}let salt = datastore.read('/' + salt_topic.pattern)let verifier = datastore.read('/' + verifier_topic.pattern)let ephemeralif (salt && verifier) {console.log(`#authorize() ${packet.u} found`)ephemeral = srp.generateEphemeral(verifier)datastore.write('/' + server_secret_key_topic.pattern, ephemeral.secret)response.v.k = ephemeral.publicresponse.v.s = salt} else {console.log(`#authorize() ${packet.u} not found`)salt = crypto.randomBytes(32).toString('hex')ephemeral = crypto.randomBytes(256).toString('hex')response.v.k = ephemeralresponse.v.s = salt}return response}prove(topic, packet) {// 3const salt_topic = topic.replace('/#', '/auth/salt')const verifier_topic = topic.replace('/#', '/auth/verifier')const client_public_key_topic = topic.replace('/#', '/auth/client_public_key')const server_secret_key_topic = topic.replace('/#', '/auth/server_secret_key')const session_key_topic = topic.replace('/#', '/auth/session_key')const server_secret_key = datastore.read('/' + server_secret_key_topic.pattern)const client_public_key = datastore.read('/' + client_public_key_topic.pattern)const salt = datastore.read('/' + salt_topic.pattern)const username = packet.uconst verifier = datastore.read('/' + verifier_topic.pattern)const proof = packet.pvar session, responsetry {session = srp.deriveSession(server_secret_key,client_public_key,salt,username,verifier,proof)datastore.write('/' + session_key_topic.pattern, session.key)response = {o: 'a',c: 4,t: topic.pattern,v: {u: packet.u,p: session.proof}}return response} catch (error) {response = {o: 'a',c: 4,t: topic.pattern,v: {u: packet.u,p: false}}return response}}}const authorization = new serverAuthorization()export { authorization }
{"name": "@djinlist/content","version": "1.0.0","type": "module","license": "UNLICENSED","private": true,"main": "src/index.js","dependencies": {"ws": "^6.0.0","uuid": "^8.0.0"},"exports": {".": "./src/index.js"}}
{"state": {"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"auth": {"salt": "e92d1a36e8056d3efd60d9e27f18f813a087d1b4327ecaad076b1ca198b9cc37","verifier": "94de3d2f9006dc28450425e7bd6bb07c92bebd1722139c9906b4e71ca325bfff5890bcb9319dc703f5e51cf7b6fe0b49660a6b7a6240eafbf2d30c60e8c8ec862b5607580e9690fcf1cae12f903a22c03da5c6a4b86889c1832a7f15354872b6ee5077b1aa956607b0455eaf28e700a2455345fa0d76777a4190efa5e6983043c14bb70aec211a266111ae80b06e68be62d2e7882c7cdf44369a19e9dc0dcd9ffbb116bd63382b47c61e03d2a4a616e3c83c85815b76cd3b8d9c0032bee03a1737f7491b83e08cff566bd2be9811e0c34edc5cb93ebb16d0b314e0bac21e2daedaa9e79a5dc61e02e096f4b9a8c24cc5ca1b2df21b09f5182c0e0f1aa0a4069b","client_public_key": "7aaa63ff31225a38343089e9e2e04e90aa3a3c5eb11f17ff66af9999079c98c16b9eb64d319023c686702e0c990b92d9f69656b28fe78c04a7a0d1e3c08589a5778f10c75a49c28a9045a3aa7d9f47e8807d75b76ee1bf717528f4ebbb5f1904b8438bddfd96a81a9beb7f2f811a732302a787918461154ac6a94dfdead46f0fcb072249b19549031c1b2c087cab122161312c3fa359355f5d0b6b4d63de3a6f5eecdb386bec7c55efc97107c35ad4318171c3a1ea73b6251449834a6605fc8567d58d8f3f8fca7678b1a78b5b8ff1077f43a815ad612089928a355aa94d89b305531354725dc940ad4e6c6133363164085884fec070f74b800f9f620d0dc539","server_secret_key": "7397ff2fcea13bc79b0f1025db63814eb1a71a83da423a20048cee764cedbc29","session_key": "b49f0604bb3a7f59c693c06b1ad63dc75c0272c07ccfd386de329de80deaf01e"},"email": "thomas.p.cowan@gmail.com","name": "tpcowan","created_at": 1629466616852,"services": {"spotify": {"expiry": 1633222298,"token": "BQB-QnozbTKvP0Ki7tdQdsSg_1RdFkXHtiDQV1xVdxWE2bOZXlzAmHZ3FLTqZyJWAA8Jh65269T4chOdgEWLxj_lnScyofxR_lp_M229cyVrx3ZC3maJkXwQIhLy8wvZUbYAgB4MzAlUx5qpLto19mBHiGJA2Qe_kPAGPwHqWhE-1VTDagpih5w-_aCLtw","client": {"expiry": 1633323505,"token": "BQChLRiYH97h389FuIhZ3teQGSTI1N1OLYTHd20Bk56w_Fg7PN3eTCoW5MKvBNGYBBEGToo5Qor1byQsblBITsljwBwzo-0dDS6XtkrCLWLizIvol6U3UVHnC_FfN2h9dWOrmQlokbmHrn2f2obVs2GbXLhPCf6FQS3idpHVW02aCO4hcCex_Q87FCJZ4g"}}},"event_id": "fe71a1ee-6e64-4d4f-8a03-7b091d93c823","drivers": {"spotify": {"token": "BQB-QnozbTKvP0Ki7tdQdsSg_1RdFkXHtiDQV1xVdxWE2bOZXlzAmHZ3FLTqZyJWAA8Jh65269T4chOdgEWLxj_lnScyofxR_lp_M229cyVrx3ZC3maJkXwQIhLy8wvZUbYAgB4MzAlUx5qpLto19mBHiGJA2Qe_kPAGPwHqWhE-1VTDagpih5w-_aCLtw","client": {"token": "BQB-QnozbTKvP0Ki7tdQdsSg_1RdFkXHtiDQV1xVdxWE2bOZXlzAmHZ3FLTqZyJWAA8Jh65269T4chOdgEWLxj_lnScyofxR_lp_M229cyVrx3ZC3maJkXwQIhLy8wvZUbYAgB4MzAlUx5qpLto19mBHiGJA2Qe_kPAGPwHqWhE-1VTDagpih5w-_aCLtw"}}}},"59c40275-d764-5f53-907d-c9ccc7b097d7": {"auth": {"client_public_key": "77c45a87ee811e78ac8bfa91dc1e309af7ca653d446afc3747e08037201f629b80edbb9453aae7c3f21ad3c5e2d55e400c191c1e682854af0010a7446911c31dfc8febbd1d91ad7bdba6530047d6c0103c7ffad0740772feae5c74902804f6028b0a5cff3e9a92c672598d780e6e439966a4c57649920abfc58acf0aff21dee13a095b3936eada69ee3b5bfea35482249b953174c5d792c4f12c3a877f6eb25ad0f01f8648b06de9e2466b734b1fe637640e1eb367a845949808b16c1c3fd4ffedd4fd6e44a493c5f7280c01658884370e0792b29b546b80e8ecf317d563cee3051fb51813fc91ce86a212420235170f51b53edfb494902cb37e97844a08e5ae"}}},"items": {"USRW29600011": {"name": "Everlong","spotfiy_id": "spotify:track:5UWwZ5lm5PKu6eKsHAGxOk","isrc": "USRW29600011"}},"events": {"fe71a1ee-6e64-4d4f-8a03-7b091d93c823": {"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"items": {"USRW29600011": 1632019111472}}}}}},"setup": {"events": {"75c6cfd0-139a-4a33-8826-9c284645f1ae": {"name": "Calgary Weekly List"},"62854dc2-7d97-45d3-be03-f0bac69119f8": {"name": "Toronto Weekly List"},"fe71a1ee-6e64-4d4f-8a03-7b091d93c823": {"name": "Belgium Weekly List"}}}}
{"setup": {"events": {"75c6cfd0-139a-4a33-8826-9c284645f1ae": {"name": "Calgary Weekly List"},"62854dc2-7d97-45d3-be03-f0bac69119f8": {"name": "Toronto Weekly List"},"fe71a1ee-6e64-4d4f-8a03-7b091d93c823": {"name": "Belgium Weekly List"}}}}
#!/usr/bin/env bash. $BIN_DIR/_lib.shnode --experimental-modules --experimental-json-modules --es-module-specifier-resolution=node src/index.js
.git*.lognode_modulesapp/nginx.conf
#!/usr/bin/env bash. $BIN_DIR/_lib.shrsync --progress -Pavuz --exclude-from="$WORKING_BIN_DIR/rsync-deploy.ignore" -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" "${MONO_DIR}/." "tpcowan@processor.djinmusic.ca:/home/tpcowan/djinmusic"rsync --progress -Pavuz -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" $WORKING_BIN_DIR/deploy-remote.sh "tpcowan@processor.djinmusic.ca:/home/tpcowan/deploy-remote.sh"ssh -F $HOME/.ssh/id_rsa_corda_digital_ocean tpcowan@processor.djinmusic.ca "sh /home/tpcowan/deploy-remote.sh"
#!/usr/bin/env bashif [[ "$(uname -s)" == "Darwin" ]]; thenecho "Don't run this on your local computer!"exit 1fiecho "[remote] Updating processor"cd djinmusicpnpm install -rcd ..echo "[remote] Installed"
#!/bin/zshautoenv_source_parentautostash WORKING_DIR=$(dirname ${0})autostash WORKING_BIN_DIR="${WORKING_DIR}/.bin"autostash alias start="${WORKING_BIN_DIR}/start.sh"
http {server {listen 80 default_server;listen [::]:80 default_server;server_name djinmusic.ca www.djinmusic.ca;return 301 https://$host$request_uri;}server {listen 443 default_server;listen [::]:443 default_server;server_name djinmusic.ca www.djinmusic.ca;location / {https://localhost:3000}}}
proxy_redirect off;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;client_max_body_size 10m;client_body_buffer_size 128k;proxy_connect_timeout 90;proxy_send_timeout 90;proxy_read_timeout 90;proxy_buffers 32 4k;
packages:- 'lib/**'- 'services/channel'- 'app'# - 'app/admin'
lockfileVersion: 5.3importers:.:specifiers:'@controlenvy/datastore': 4.0.0babel-eslint: ^10.0.3benchmark: ^2.1.4chai: ^4.2.0concurrently: ^5.0.0eslint: ^6.0.1eslint-config-prettier: ^6.0.0eslint-plugin-html: ^6.0.0eslint-plugin-optimize-regex: ^1.1.6eslint-plugin-prettier: ^3.1.0eslint-plugin-svelte3: ^2.7.3globby: ^11.0.0husky: ^3.0.0ipware: ^2.0.0jsdoctest: ^1.7.1lint-staged: ^9.2.0lodash: ^4.17.15lodash-es: ^4.17.15luxon: ^1.21.3mocha: ^6.1.4moment: ^2.24.0node-watch: ^0.6.3polka: ^0.5.2prettier: ^1.19.1secure-remote-password: ^0.3.1sinon: ^7.3.2sinon-chai: ^3.3.0sirv: ^0.4.2stylelint: ^11.1.1stylelint-config-prettier: ^5.1.0stylelint-config-rational-order: ^0.1.2stylelint-config-standard: ^18.3.0stylelint-order: ^3.1.1stylelint-prettier: ^1.1.0stylelint-scss: ^3.13.0svelte: ^3.0.0svelte-preprocess: ^3.3.0svelte-preprocess-sass: ^0.2.0dependencies:'@controlenvy/datastore': link:lib/datastorelodash: 4.17.21lodash-es: 4.17.21secure-remote-password: 0.3.1optionalDependencies:concurrently: 5.3.0ipware: 2.0.0luxon: 1.28.0polka: 0.5.2sirv: 0.4.6devDependencies:babel-eslint: 10.1.0_eslint@6.8.0benchmark: 2.1.4chai: 4.3.4eslint: 6.8.0eslint-config-prettier: 6.15.0_eslint@6.8.0eslint-plugin-html: 6.1.2eslint-plugin-optimize-regex: 1.2.1eslint-plugin-prettier: 3.4.0_b77cd85fda941e232840dc83bf6b7690eslint-plugin-svelte3: 2.7.3_eslint@6.8.0+svelte@3.42.1globby: 11.0.4husky: 3.1.0jsdoctest: 1.7.1lint-staged: 9.5.0mocha: 6.2.3moment: 2.29.1node-watch: 0.6.4prettier: 1.19.1sinon: 7.5.0sinon-chai: 3.7.0_chai@4.3.4+sinon@7.5.0stylelint: 11.1.1stylelint-config-prettier: 5.3.0_stylelint@11.1.1stylelint-config-rational-order: 0.1.2stylelint-config-standard: 18.3.0_stylelint@11.1.1stylelint-order: 3.1.1_stylelint@11.1.1stylelint-prettier: 1.2.0_prettier@1.19.1+stylelint@11.1.1stylelint-scss: 3.20.1_stylelint@11.1.1svelte: 3.42.1svelte-preprocess: 3.9.12_svelte@3.42.1svelte-preprocess-sass: 0.2.0app/djiny:specifiers:'@fortawesome/free-brands-svg-icons': ^5.12.0'@fortawesome/free-solid-svg-icons': ^5.12.0'@sveltejs/adapter-static': next'@sveltejs/kit': next'@taylorzane/sveltejs-adapter-node': ^1.0.0-next.35eslint: ^7.22.0eslint-config-prettier: ^8.1.0eslint-plugin-svelte3: ^3.2.0lodash: ^4.17.21node-sass: ^6.0.1prettier: ~2.2.1prettier-plugin-svelte: ^2.2.0query-string: 4.3.2sanitize.css: ^12.0.1sass: ^1.35.2secure-remote-password: ^0.3.1svelte: ^3.34.0svelte-preprocess: ^4.7.3uuid: ^8.0.0dependencies:'@fortawesome/free-brands-svg-icons': 5.15.4'@fortawesome/free-solid-svg-icons': 5.15.4sanitize.css: 12.0.1sass: 1.37.5devDependencies:'@sveltejs/adapter-static': 1.0.0-next.16'@sveltejs/kit': 1.0.0-next.146_svelte@3.42.1'@taylorzane/sveltejs-adapter-node': 1.0.0-next.35eslint: 7.32.0eslint-config-prettier: 8.3.0_eslint@7.32.0eslint-plugin-svelte3: 3.2.0_eslint@7.32.0+svelte@3.42.1lodash: 4.17.21node-sass: 6.0.1prettier: 2.2.1prettier-plugin-svelte: 2.3.1_prettier@2.2.1+svelte@3.42.1query-string: 4.3.2secure-remote-password: 0.3.1svelte: 3.42.1svelte-preprocess: 4.7.4_6197623e5ed34153d1bcd9290e2954d7uuid: 8.3.2lib/datastore:specifiers:chai: ^4.3.0lodash: ^4.17.15mocha: ^8.2.1sinon: ^9.2.4sinon-chai: ^3.5.0dependencies:lodash: 4.17.21devDependencies:chai: 4.3.4mocha: 8.4.0sinon: 9.2.4sinon-chai: 3.7.0_chai@4.3.4+sinon@9.2.4lib/datastore_client:specifiers:'@djinlist/datastore': 0.0.0lodash: ^4.17.15dependencies:'@djinlist/datastore': link:../datastore_oldlodash: 4.17.21lib/datastore_old:specifiers:lodash: ^4.17.15dependencies:lodash: 4.17.21services/channel:specifiers:uuid: ^8.0.0ws: ^6.0.0dependencies:uuid: 8.3.2ws: 6.2.2packages:/@arr/every/1.0.1:resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==}engines: {node: '>=4'}dev: falseoptional: true/@babel/code-frame/7.12.11:resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==}dependencies:'@babel/highlight': 7.14.5dev: true/@babel/code-frame/7.14.5:resolution: {integrity: sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==}engines: {node: '>=6.9.0'}dependencies:'@babel/highlight': 7.14.5dev: true/@babel/compat-data/7.15.0:resolution: {integrity: sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==}engines: {node: '>=6.9.0'}dev: true/@babel/core/7.15.0:resolution: {integrity: sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw==}engines: {node: '>=6.9.0'}dependencies:'@babel/code-frame': 7.14.5'@babel/generator': 7.15.0'@babel/helper-compilation-targets': 7.15.0_@babel+core@7.15.0'@babel/helper-module-transforms': 7.15.0'@babel/helpers': 7.14.8'@babel/parser': 7.15.2'@babel/template': 7.14.5'@babel/traverse': 7.15.0'@babel/types': 7.15.0convert-source-map: 1.8.0debug: 4.3.2gensync: 1.0.0-beta.2json5: 2.2.0semver: 6.3.0source-map: 0.5.7transitivePeerDependencies:- supports-colordev: true/@babel/generator/7.15.0:resolution: {integrity: sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==}engines: {node: '>=6.9.0'}dependencies:'@babel/types': 7.15.0jsesc: 2.5.2source-map: 0.5.7dev: true/@babel/helper-compilation-targets/7.15.0_@babel+core@7.15.0:resolution: {integrity: sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A==}engines: {node: '>=6.9.0'}peerDependencies:'@babel/core': ^7.0.0dependencies:'@babel/compat-data': 7.15.0'@babel/core': 7.15.0'@babel/helper-validator-option': 7.14.5browserslist: 4.16.7semver: 6.3.0dev: true/@babel/helper-function-name/7.14.5:resolution: {integrity: sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==}engines: {node: '>=6.9.0'}dependencies:'@babel/helper-get-function-arity': 7.14.5'@babel/template': 7.14.5'@babel/types': 7.15.0dev: true/@babel/helper-get-function-arity/7.14.5:resolution: {integrity: sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==}engines: {node: '>=6.9.0'}dependencies:'@babel/types': 7.15.0dev: true/@babel/helper-hoist-variables/7.14.5:resolution: {integrity: sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==}engines: {node: '>=6.9.0'}dependencies:'@babel/types': 7.15.0dev: true/@babel/helper-member-expression-to-functions/7.15.0:resolution: {integrity: sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==}engines: {node: '>=6.9.0'}dependencies:'@babel/types': 7.15.0dev: true/@babel/helper-module-imports/7.14.5:resolution: {integrity: sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==}engines: {node: '>=6.9.0'}dependencies:'@babel/types': 7.15.0dev: true/@babel/helper-module-transforms/7.15.0:resolution: {integrity: sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==}engines: {node: '>=6.9.0'}dependencies:'@babel/helper-module-imports': 7.14.5'@babel/helper-replace-supers': 7.15.0'@babel/helper-simple-access': 7.14.8'@babel/helper-split-export-declaration': 7.14.5'@babel/helper-validator-identifier': 7.14.9'@babel/template': 7.14.5'@babel/traverse': 7.15.0'@babel/types': 7.15.0transitivePeerDependencies:- supports-colordev: true/@babel/helper-optimise-call-expression/7.14.5:resolution: {integrity: sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==}engines: {node: '>=6.9.0'}dependencies:'@babel/types': 7.15.0dev: true/@babel/helper-replace-supers/7.15.0:resolution: {integrity: sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==}engines: {node: '>=6.9.0'}dependencies:'@babel/helper-member-expression-to-functions': 7.15.0'@babel/helper-optimise-call-expression': 7.14.5'@babel/traverse': 7.15.0'@babel/types': 7.15.0transitivePeerDependencies:- supports-colordev: true/@babel/helper-simple-access/7.14.8:resolution: {integrity: sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==}engines: {node: '>=6.9.0'}dependencies:'@babel/types': 7.15.0dev: true/@babel/helper-split-export-declaration/7.14.5:resolution: {integrity: sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==}engines: {node: '>=6.9.0'}dependencies:'@babel/types': 7.15.0dev: true/@babel/helper-validator-identifier/7.14.9:resolution: {integrity: sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==}engines: {node: '>=6.9.0'}dev: true/@babel/helper-validator-option/7.14.5:resolution: {integrity: sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==}engines: {node: '>=6.9.0'}dev: true/@babel/helpers/7.14.8:resolution: {integrity: sha512-ZRDmI56pnV+p1dH6d+UN6GINGz7Krps3+270qqI9UJ4wxYThfAIcI5i7j5vXC4FJ3Wap+S9qcebxeYiqn87DZw==}engines: {node: '>=6.9.0'}dependencies:'@babel/template': 7.14.5'@babel/traverse': 7.15.0'@babel/types': 7.15.0transitivePeerDependencies:- supports-colordev: true/@babel/highlight/7.14.5:resolution: {integrity: sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==}engines: {node: '>=6.9.0'}dependencies:'@babel/helper-validator-identifier': 7.14.9chalk: 2.4.2js-tokens: 4.0.0dev: true/@babel/parser/7.15.2:resolution: {integrity: sha512-bMJXql1Ss8lFnvr11TZDH4ArtwlAS5NG9qBmdiFW2UHHm6MVoR+GDc5XE2b9K938cyjc9O6/+vjjcffLDtfuDg==}engines: {node: '>=6.0.0'}hasBin: truedev: true/@babel/template/7.14.5:resolution: {integrity: sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==}engines: {node: '>=6.9.0'}dependencies:'@babel/code-frame': 7.14.5'@babel/parser': 7.15.2'@babel/types': 7.15.0dev: true/@babel/traverse/7.15.0:resolution: {integrity: sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==}engines: {node: '>=6.9.0'}dependencies:'@babel/code-frame': 7.14.5'@babel/generator': 7.15.0'@babel/helper-function-name': 7.14.5'@babel/helper-hoist-variables': 7.14.5'@babel/helper-split-export-declaration': 7.14.5'@babel/parser': 7.15.2'@babel/types': 7.15.0debug: 4.3.2globals: 11.12.0transitivePeerDependencies:- supports-colordev: true/@babel/types/7.15.0:resolution: {integrity: sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==}engines: {node: '>=6.9.0'}dependencies:'@babel/helper-validator-identifier': 7.14.9to-fast-properties: 2.0.0dev: true/@eslint/eslintrc/0.4.3:resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==}engines: {node: ^10.12.0 || >=12.0.0}dependencies:ajv: 6.12.6debug: 4.3.2espree: 7.3.1globals: 13.10.0ignore: 4.0.6import-fresh: 3.3.0js-yaml: 3.14.1minimatch: 3.0.4strip-json-comments: 3.1.1transitivePeerDependencies:- supports-colordev: true/@fortawesome/fontawesome-common-types/0.2.36:resolution: {integrity: sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==, tarball: '@fortawesome/fontawesome-common-types/-/0.2.36/fontawesome-common-types-0.2.36.tgz'}engines: {node: '>=6'}requiresBuild: truedev: false/@fortawesome/free-brands-svg-icons/5.15.4:resolution: {integrity: sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==, tarball: '@fortawesome/free-brands-svg-icons/-/5.15.4/free-brands-svg-icons-5.15.4.tgz'}engines: {node: '>=6'}requiresBuild: truedependencies:'@fortawesome/fontawesome-common-types': 0.2.36dev: false/@fortawesome/free-solid-svg-icons/5.15.4:resolution: {integrity: sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==, tarball: '@fortawesome/free-solid-svg-icons/-/5.15.4/free-solid-svg-icons-5.15.4.tgz'}engines: {node: '>=6'}requiresBuild: truedependencies:'@fortawesome/fontawesome-common-types': 0.2.36dev: false/@humanwhocodes/config-array/0.5.0:resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==}engines: {node: '>=10.10.0'}dependencies:'@humanwhocodes/object-schema': 1.2.0debug: 4.3.2minimatch: 3.0.4transitivePeerDependencies:- supports-colordev: true/@humanwhocodes/object-schema/1.2.0:resolution: {integrity: sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==}dev: true/@mrmlnc/readdir-enhanced/2.2.1:resolution: {integrity: sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==}engines: {node: '>=4'}dependencies:call-me-maybe: 1.0.1glob-to-regexp: 0.3.0dev: true/@nodelib/fs.scandir/2.1.5:resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}engines: {node: '>= 8'}dependencies:'@nodelib/fs.stat': 2.0.5run-parallel: 1.2.0dev: true/@nodelib/fs.stat/1.1.3:resolution: {integrity: sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==}engines: {node: '>= 6'}dev: true/@nodelib/fs.stat/2.0.5:resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}engines: {node: '>= 8'}dev: true/@nodelib/fs.walk/1.2.8:resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}engines: {node: '>= 8'}dependencies:'@nodelib/fs.scandir': 2.1.5fastq: 1.11.1dev: true/@polka/url/0.5.0:resolution: {integrity: sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==}dev: falseoptional: true/@rollup/pluginutils/4.1.1:resolution: {integrity: sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==}engines: {node: '>= 8.0.0'}dependencies:estree-walker: 2.0.2picomatch: 2.3.0dev: true/@samverschueren/stream-to-observable/0.3.1_rxjs@6.6.7:resolution: {integrity: sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==}engines: {node: '>=6'}peerDependencies:rxjs: '*'zen-observable: '*'peerDependenciesMeta:rxjs:optional: truezen-observable:optional: truedependencies:any-observable: 0.3.0rxjs: 6.6.7dev: true/@sinonjs/commons/1.8.3:resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==}dependencies:type-detect: 4.0.8dev: true/@sinonjs/fake-timers/6.0.1:resolution: {integrity: sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==}dependencies:'@sinonjs/commons': 1.8.3dev: true/@sinonjs/formatio/3.2.2:resolution: {integrity: sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==}dependencies:'@sinonjs/commons': 1.8.3'@sinonjs/samsam': 3.3.3dev: true/@sinonjs/samsam/3.3.3:resolution: {integrity: sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==}dependencies:'@sinonjs/commons': 1.8.3array-from: 2.1.1lodash: 4.17.21dev: true/@sinonjs/samsam/5.3.1:resolution: {integrity: sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==}dependencies:'@sinonjs/commons': 1.8.3lodash.get: 4.4.2type-detect: 4.0.8dev: true/@sinonjs/text-encoding/0.7.1:resolution: {integrity: sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==}dev: true/@sveltejs/adapter-static/1.0.0-next.16:resolution: {integrity: sha512-xGFcg+GHF0BL1fyWx2vCzlYj4S4R+Od9cF00soo1TVp/scGOi1G9grSYYW4x5H+iDn1sscoJ65OGBGWIcOgrXg==}dev: true/@sveltejs/kit/1.0.0-next.146_svelte@3.42.1:resolution: {integrity: sha512-MSatcaCRfjl88Prd5mW4pNOJ3Gsr525+Vjr24MoKtyTt6PZQmTfQsDVwyP93exn/6w2xl9uMCW6cFpDVBu7jSg==}engines: {node: ^12.20 || >=14.13}hasBin: truepeerDependencies:svelte: ^3.34.0dependencies:'@sveltejs/vite-plugin-svelte': 1.0.0-next.15_svelte@3.42.1+vite@2.4.4cheap-watch: 1.0.3sade: 1.7.4svelte: 3.42.1vite: 2.4.4transitivePeerDependencies:- diff-match-patch- supports-colordev: true/@sveltejs/vite-plugin-svelte/1.0.0-next.15_svelte@3.42.1+vite@2.4.4:resolution: {integrity: sha512-8yGX7PxaqtvWw+GHiO2DV7lZ4M7DwIrFq+PgZGZ9X09PuoSeaWszm76GWQXJMKHoPPhdA9084662en9qbv4aRw==}engines: {node: ^12.20 || ^14.13.1 || >= 16}peerDependencies:diff-match-patch: ^1.0.5svelte: ^3.34.0vite: ^2.3.7peerDependenciesMeta:diff-match-patch:optional: truedependencies:'@rollup/pluginutils': 4.1.1debug: 4.3.2kleur: 4.1.4magic-string: 0.25.7require-relative: 0.8.7svelte: 3.42.1svelte-hmr: 0.14.7_svelte@3.42.1vite: 2.4.4transitivePeerDependencies:- supports-colordev: true/@taylorzane/sveltejs-adapter-node/1.0.0-next.35:resolution: {integrity: sha512-5DVAmeCgcKtU+DZ36HoglNHjPZbx52/cP3V9s/RcwAzEa9VKh+MVwxW/fYm8M7x3bD4YUhaAy5ECdKjCPxUwcw==}dependencies:esbuild: 0.12.19tiny-glob: 0.2.9dev: true/@types/glob/7.1.4:resolution: {integrity: sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==}dependencies:'@types/minimatch': 3.0.5'@types/node': 16.4.13dev: true/@types/minimatch/3.0.5:resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==}dev: true/@types/minimist/1.2.2:resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}dev: true/@types/node/16.4.13:resolution: {integrity: sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==}dev: true/@types/normalize-package-data/2.4.1:resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}dev: true/@types/pug/2.0.5:resolution: {integrity: sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA==}dev: true/@types/sass/1.16.1:resolution: {integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ==}dependencies:'@types/node': 16.4.13dev: true/@types/unist/2.0.6:resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}dev: true/@types/vfile-message/2.0.0:resolution: {integrity: sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==}deprecated: This is a stub types definition. vfile-message provides its own type definitions, so you do not need this installed.dependencies:vfile-message: 3.0.1dev: true/@types/vfile/3.0.2:resolution: {integrity: sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==}dependencies:'@types/node': 16.4.13'@types/unist': 2.0.6'@types/vfile-message': 2.0.0dev: true/@ungap/promise-all-settled/1.1.2:resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==}dev: true/abbrev/1.1.1:resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}dev: true/acorn-jsx/5.3.2_acorn@7.4.1:resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}peerDependencies:acorn: ^6.0.0 || ^7.0.0 || ^8.0.0dependencies:acorn: 7.4.1dev: true/acorn/7.4.1:resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}engines: {node: '>=0.4.0'}hasBin: truedev: true/aggregate-error/3.1.0:resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}engines: {node: '>=8'}dependencies:clean-stack: 2.2.0indent-string: 4.0.0dev: true/ajv/6.12.6:resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}dependencies:fast-deep-equal: 3.1.3fast-json-stable-stringify: 2.1.0json-schema-traverse: 0.4.1uri-js: 4.4.1dev: true/ajv/8.6.2:resolution: {integrity: sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==}dependencies:fast-deep-equal: 3.1.3json-schema-traverse: 1.0.0require-from-string: 2.0.2uri-js: 4.4.1dev: true/amdefine/1.0.1:resolution: {integrity: sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=}engines: {node: '>=0.4.2'}dev: true/ansi-colors/3.2.3:resolution: {integrity: sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==}engines: {node: '>=6'}dev: true/ansi-colors/4.1.1:resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}engines: {node: '>=6'}dev: true/ansi-escapes/3.2.0:resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==}engines: {node: '>=4'}dev: true/ansi-escapes/4.3.2:resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}engines: {node: '>=8'}dependencies:type-fest: 0.21.3dev: true/ansi-regex/2.1.1:resolution: {integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=}engines: {node: '>=0.10.0'}dev: true/ansi-regex/3.0.0:resolution: {integrity: sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=}engines: {node: '>=4'}dev: true/ansi-regex/4.1.0:resolution: {integrity: sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==}engines: {node: '>=6'}/ansi-regex/5.0.0:resolution: {integrity: sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==}engines: {node: '>=8'}dev: true/ansi-styles/2.2.1:resolution: {integrity: sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=}engines: {node: '>=0.10.0'}dev: true/ansi-styles/3.2.1:resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}engines: {node: '>=4'}dependencies:color-convert: 1.9.3/ansi-styles/4.3.0:resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}engines: {node: '>=8'}dependencies:color-convert: 2.0.1dev: true/any-observable/0.3.0:resolution: {integrity: sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==}engines: {node: '>=6'}dev: true/anymatch/3.1.2:resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}engines: {node: '>= 8'}dependencies:normalize-path: 3.0.0picomatch: 2.3.0/aproba/1.2.0:resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==}dev: true/are-we-there-yet/1.1.5:resolution: {integrity: sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==}dependencies:delegates: 1.0.0readable-stream: 2.3.7dev: true/argparse/1.0.10:resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}dependencies:sprintf-js: 1.0.3dev: true/argparse/2.0.1:resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}dev: true/arr-diff/4.0.0:resolution: {integrity: sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=}engines: {node: '>=0.10.0'}dev: true/arr-flatten/1.1.0:resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==}engines: {node: '>=0.10.0'}dev: true/arr-union/3.1.0:resolution: {integrity: sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=}engines: {node: '>=0.10.0'}dev: true/array-buffer-to-hex/1.0.0:resolution: {integrity: sha512-arycdkxgK1cj6s03GDb96tlCxOl1n3kg9M2OHseUc6Pqyqp+lgfceFPmG507eI5V+oxOSEnlOw/dFc7LXBXF4Q==}/array-find-index/1.0.2:resolution: {integrity: sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=}engines: {node: '>=0.10.0'}dev: true/array-from/2.1.1:resolution: {integrity: sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=}dev: true/array-union/1.0.2:resolution: {integrity: sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=}engines: {node: '>=0.10.0'}dependencies:array-uniq: 1.0.3dev: true/array-union/2.1.0:resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}engines: {node: '>=8'}dev: true/array-uniq/1.0.3:resolution: {integrity: sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=}engines: {node: '>=0.10.0'}dev: true/array-unique/0.3.2:resolution: {integrity: sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=}engines: {node: '>=0.10.0'}dev: true/arrify/1.0.1:resolution: {integrity: sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=}engines: {node: '>=0.10.0'}dev: true/asn1/0.2.4:resolution: {integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==}dependencies:safer-buffer: 2.1.2dev: true/assert-plus/1.0.0:resolution: {integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=}engines: {node: '>=0.8'}dev: true/assertion-error/1.1.0:resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}dev: true/assign-symbols/1.0.0:resolution: {integrity: sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=}engines: {node: '>=0.10.0'}dev: true/astral-regex/1.0.0:resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==}engines: {node: '>=4'}dev: true/astral-regex/2.0.0:resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}engines: {node: '>=8'}dev: true/async-foreach/0.1.3:resolution: {integrity: sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=}dev: true/async-limiter/1.0.1:resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==}dev: false/asynckit/0.4.0:resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=}dev: true/atob/2.1.2:resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}engines: {node: '>= 4.5.0'}hasBin: truedev: true/autoprefixer/9.8.6:resolution: {integrity: sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==}hasBin: truedependencies:browserslist: 4.16.7caniuse-lite: 1.0.30001249colorette: 1.2.2normalize-range: 0.1.2num2fraction: 1.2.2postcss: 7.0.36postcss-value-parser: 4.1.0dev: true/aws-sign2/0.7.0:resolution: {integrity: sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=}dev: true/aws4/1.11.0:resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}dev: true/babel-eslint/10.1.0_eslint@6.8.0:resolution: {integrity: sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==}engines: {node: '>=6'}deprecated: babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.peerDependencies:eslint: '>= 4.12.1'dependencies:'@babel/code-frame': 7.14.5'@babel/parser': 7.15.2'@babel/traverse': 7.15.0'@babel/types': 7.15.0eslint: 6.8.0eslint-visitor-keys: 1.3.0resolve: 1.20.0transitivePeerDependencies:- supports-colordev: true/bail/1.0.5:resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==}dev: true/balanced-match/1.0.2:resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}dev: true/base/0.11.2:resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}engines: {node: '>=0.10.0'}dependencies:cache-base: 1.0.1class-utils: 0.3.6component-emitter: 1.3.0define-property: 1.0.0isobject: 3.0.1mixin-deep: 1.3.2pascalcase: 0.1.1dev: true/bcrypt-pbkdf/1.0.2:resolution: {integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=}dependencies:tweetnacl: 0.14.5dev: true/benchmark/2.1.4:resolution: {integrity: sha1-CfPeMckWQl1JjMLuVloOvzwqVik=}dependencies:lodash: 4.17.21platform: 1.3.6dev: true/binary-extensions/2.2.0:resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}engines: {node: '>=8'}/brace-expansion/1.1.11:resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}dependencies:balanced-match: 1.0.2concat-map: 0.0.1dev: true/braces/2.3.2:resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}engines: {node: '>=0.10.0'}dependencies:arr-flatten: 1.1.0array-unique: 0.3.2extend-shallow: 2.0.1fill-range: 4.0.0isobject: 3.0.1repeat-element: 1.1.4snapdragon: 0.8.2snapdragon-node: 2.1.1split-string: 3.1.0to-regex: 3.0.2dev: true/braces/3.0.2:resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}engines: {node: '>=8'}dependencies:fill-range: 7.0.1/browser-stdout/1.3.1:resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}dev: true/browserslist/4.16.7:resolution: {integrity: sha512-7I4qVwqZltJ7j37wObBe3SoTz+nS8APaNcrBOlgoirb6/HbEU2XxW/LpUDTCngM6iauwFqmRTuOMfyKnFGY5JA==}engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}hasBin: truedependencies:caniuse-lite: 1.0.30001249colorette: 1.2.2electron-to-chromium: 1.3.800escalade: 3.1.1node-releases: 1.1.73dev: true/cache-base/1.0.1:resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==}engines: {node: '>=0.10.0'}dependencies:collection-visit: 1.0.0component-emitter: 1.3.0get-value: 2.0.6has-value: 1.0.0isobject: 3.0.1set-value: 2.0.1to-object-path: 0.3.0union-value: 1.0.1unset-value: 1.0.0dev: true/call-bind/1.0.2:resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}dependencies:function-bind: 1.1.1get-intrinsic: 1.1.1dev: true/call-me-maybe/1.0.1:resolution: {integrity: sha1-JtII6onje1y95gJQoV8DHBak1ms=}dev: true/caller-callsite/2.0.0:resolution: {integrity: sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=}engines: {node: '>=4'}dependencies:callsites: 2.0.0dev: true/caller-path/2.0.0:resolution: {integrity: sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=}engines: {node: '>=4'}dependencies:caller-callsite: 2.0.0dev: true/callsites/2.0.0:resolution: {integrity: sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=}engines: {node: '>=4'}dev: true/callsites/3.1.0:resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}engines: {node: '>=6'}dev: true/camelcase-keys/4.2.0:resolution: {integrity: sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=}engines: {node: '>=4'}dependencies:camelcase: 4.1.0map-obj: 2.0.0quick-lru: 1.1.0dev: true/camelcase-keys/6.2.2:resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}engines: {node: '>=8'}dependencies:camelcase: 5.3.1map-obj: 4.2.1quick-lru: 4.0.1dev: true/camelcase/4.1.0:resolution: {integrity: sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=}engines: {node: '>=4'}dev: true/camelcase/5.3.1:resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}engines: {node: '>=6'}/camelcase/6.2.0:resolution: {integrity: sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==}engines: {node: '>=10'}dev: true/caniuse-lite/1.0.30001249:resolution: {integrity: sha512-vcX4U8lwVXPdqzPWi6cAJ3FnQaqXbBqy/GZseKNQzRj37J7qZdGcBtxq/QLFNLLlfsoXLUdHw8Iwenri86Tagw==}dev: true/caseless/0.12.0:resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=}dev: true/ccount/1.1.0:resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==}dev: true/chai/4.3.4:resolution: {integrity: sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==}engines: {node: '>=4'}dependencies:assertion-error: 1.1.0check-error: 1.0.2deep-eql: 3.0.1get-func-name: 2.0.0pathval: 1.1.1type-detect: 4.0.8dev: true/chalk/1.1.3:resolution: {integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=}engines: {node: '>=0.10.0'}dependencies:ansi-styles: 2.2.1escape-string-regexp: 1.0.5has-ansi: 2.0.0strip-ansi: 3.0.1supports-color: 2.0.0dev: true/chalk/2.4.2:resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}engines: {node: '>=4'}dependencies:ansi-styles: 3.2.1escape-string-regexp: 1.0.5supports-color: 5.5.0/chalk/4.1.2:resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}engines: {node: '>=10'}dependencies:ansi-styles: 4.3.0supports-color: 7.2.0dev: true/character-entities-html4/1.1.4:resolution: {integrity: sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==}dev: true/character-entities-legacy/1.1.4:resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==}dev: true/character-entities/1.2.4:resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==}dev: true/character-reference-invalid/1.1.4:resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}dev: true/chardet/0.7.0:resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}dev: true/cheap-watch/1.0.3:resolution: {integrity: sha512-xC5CruMhLzjPwJ5ecUxGu1uGmwJQykUhqd2QrCrYbwvsFYdRyviu6jG9+pccwDXJR/OpmOTOJ9yLFunVgQu9wg==}engines: {node: '>=8'}dev: true/check-error/1.0.2:resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=}dev: true/chokidar/3.5.1:resolution: {integrity: sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==}engines: {node: '>= 8.10.0'}dependencies:anymatch: 3.1.2braces: 3.0.2glob-parent: 5.1.2is-binary-path: 2.1.0is-glob: 4.0.1normalize-path: 3.0.0readdirp: 3.5.0optionalDependencies:fsevents: 2.3.2dev: true/chokidar/3.5.2:resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==}engines: {node: '>= 8.10.0'}dependencies:anymatch: 3.1.2braces: 3.0.2glob-parent: 5.1.2is-binary-path: 2.1.0is-glob: 4.0.1normalize-path: 3.0.0readdirp: 3.6.0optionalDependencies:fsevents: 2.3.2dev: false/chownr/2.0.0:resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}engines: {node: '>=10'}dev: true/ci-info/2.0.0:resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}dev: true/class-utils/0.3.6:resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}engines: {node: '>=0.10.0'}dependencies:arr-union: 3.1.0define-property: 0.2.5isobject: 3.0.1static-extend: 0.1.2dev: true/clean-stack/2.2.0:resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}engines: {node: '>=6'}dev: true/cli-cursor/2.1.0:resolution: {integrity: sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=}engines: {node: '>=4'}dependencies:restore-cursor: 2.0.0dev: true/cli-cursor/3.1.0:resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}engines: {node: '>=8'}dependencies:restore-cursor: 3.1.0dev: true/cli-truncate/0.2.1:resolution: {integrity: sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=}engines: {node: '>=0.10.0'}dependencies:slice-ansi: 0.0.4string-width: 1.0.2dev: true/cli-width/3.0.0:resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}engines: {node: '>= 10'}dev: true/cliui/5.0.0:resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==}dependencies:string-width: 3.1.0strip-ansi: 5.2.0wrap-ansi: 5.1.0/cliui/7.0.4:resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}dependencies:string-width: 4.2.2strip-ansi: 6.0.0wrap-ansi: 7.0.0dev: true/clone-regexp/1.0.1:resolution: {integrity: sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==}engines: {node: '>=0.10.0'}dependencies:is-regexp: 1.0.0is-supported-regexp-flag: 1.0.1dev: true/clone-regexp/2.2.0:resolution: {integrity: sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==}engines: {node: '>=6'}dependencies:is-regexp: 2.1.0dev: true/code-point-at/1.1.0:resolution: {integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=}engines: {node: '>=0.10.0'}dev: true/collapse-white-space/1.0.6:resolution: {integrity: sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==}dev: true/collection-visit/1.0.0:resolution: {integrity: sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=}engines: {node: '>=0.10.0'}dependencies:map-visit: 1.0.0object-visit: 1.0.1dev: true/color-convert/1.9.3:resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}dependencies:color-name: 1.1.3/color-convert/2.0.1:resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}engines: {node: '>=7.0.0'}dependencies:color-name: 1.1.4dev: true/color-name/1.1.3:resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=}/color-name/1.1.4:resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}dev: true/colorette/1.2.2:resolution: {integrity: sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==}dev: true/combined-stream/1.0.8:resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}engines: {node: '>= 0.8'}dependencies:delayed-stream: 1.0.0dev: true/commander/2.20.3:resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}dev: true/commander/2.9.0:resolution: {integrity: sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=}engines: {node: '>= 0.6.x'}dependencies:graceful-readlink: 1.0.1dev: true/component-emitter/1.3.0:resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}dev: true/concat-map/0.0.1:resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}dev: true/concurrently/5.3.0:resolution: {integrity: sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==}engines: {node: '>=6.0.0'}hasBin: truedependencies:chalk: 2.4.2date-fns: 2.23.0lodash: 4.17.21read-pkg: 4.0.1rxjs: 6.6.7spawn-command: 0.0.2-1supports-color: 6.1.0tree-kill: 1.2.2yargs: 13.3.2dev: falseoptional: true/config-chain/1.1.13:resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}dependencies:ini: 1.3.8proto-list: 1.2.4dev: true/console-control-strings/1.1.0:resolution: {integrity: sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=}dev: true/convert-source-map/1.8.0:resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}dependencies:safe-buffer: 5.1.2dev: true/copy-descriptor/0.1.1:resolution: {integrity: sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=}engines: {node: '>=0.10.0'}dev: true/core-util-is/1.0.2:resolution: {integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=}dev: true/cosmiconfig/5.2.1:resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==}engines: {node: '>=4'}dependencies:import-fresh: 2.0.0is-directory: 0.3.1js-yaml: 3.14.1parse-json: 4.0.0dev: true/cross-spawn/6.0.5:resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}engines: {node: '>=4.8'}dependencies:nice-try: 1.0.5path-key: 2.0.1semver: 5.7.1shebang-command: 1.2.0which: 1.3.1dev: true/cross-spawn/7.0.3:resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}engines: {node: '>= 8'}dependencies:path-key: 3.1.1shebang-command: 2.0.0which: 2.0.2dev: true/crypto-digest-sync/1.0.0:resolution: {integrity: sha512-UQBOB5z+HF4iA8shKQ3PPwhCmdFAihwcytD1Qh4uiz78x04cZZmKtZ1F1VyAjkrA8uEZqXt2tMXfj3dJHtcbng==}/crypto-random-hex/1.0.0:resolution: {integrity: sha512-1DuZQ03El13TRgfrqbbjW40Gvi4OKInny/Wxqj23/JMXe214C/3Tlz92bKXWDW3NZT5RjXUGdYW4qiIOUPf+cA==}dependencies:array-buffer-to-hex: 1.0.0/cssesc/3.0.0:resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}engines: {node: '>=4'}hasBin: truedev: true/currently-unhandled/0.4.1:resolution: {integrity: sha1-mI3zP+qxke95mmE2nddsF635V+o=}engines: {node: '>=0.10.0'}dependencies:array-find-index: 1.0.2dev: true/dashdash/1.14.1:resolution: {integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=}engines: {node: '>=0.10'}dependencies:assert-plus: 1.0.0dev: true/date-fns/1.30.1:resolution: {integrity: sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==}dev: true/date-fns/2.23.0:resolution: {integrity: sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==}engines: {node: '>=0.11'}dev: falseoptional: true/debug/2.6.9:resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}dependencies:ms: 2.0.0dev: true/debug/3.2.6:resolution: {integrity: sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==}deprecated: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)dependencies:ms: 2.1.1dev: true/debug/4.3.1_supports-color@8.1.1:resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==}engines: {node: '>=6.0'}peerDependencies:supports-color: '*'peerDependenciesMeta:supports-color:optional: truedependencies:ms: 2.1.2supports-color: 8.1.1dev: true/debug/4.3.2:resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}engines: {node: '>=6.0'}peerDependencies:supports-color: '*'peerDependenciesMeta:supports-color:optional: truedependencies:ms: 2.1.2dev: true/decamelize-keys/1.1.0:resolution: {integrity: sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=}engines: {node: '>=0.10.0'}dependencies:decamelize: 1.2.0map-obj: 1.0.1dev: true/decamelize/1.2.0:resolution: {integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=}engines: {node: '>=0.10.0'}/decamelize/4.0.0:resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}engines: {node: '>=10'}dev: true/decode-uri-component/0.2.0:resolution: {integrity: sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=}engines: {node: '>=0.10'}dev: true/dedent/0.7.0:resolution: {integrity: sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=}dev: true/deep-eql/3.0.1:resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==}engines: {node: '>=0.12'}dependencies:type-detect: 4.0.8dev: true/deep-is/0.1.3:resolution: {integrity: sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=}dev: true/define-properties/1.1.3:resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==}engines: {node: '>= 0.4'}dependencies:object-keys: 1.1.1dev: true/define-property/0.2.5:resolution: {integrity: sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=}engines: {node: '>=0.10.0'}dependencies:is-descriptor: 0.1.6dev: true/define-property/1.0.0:resolution: {integrity: sha1-dp66rz9KY6rTr56NMEybvnm/sOY=}engines: {node: '>=0.10.0'}dependencies:is-descriptor: 1.0.2dev: true/define-property/2.0.2:resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==}engines: {node: '>=0.10.0'}dependencies:is-descriptor: 1.0.2isobject: 3.0.1dev: true/del/5.1.0:resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==}engines: {node: '>=8'}dependencies:globby: 10.0.2graceful-fs: 4.2.8is-glob: 4.0.1is-path-cwd: 2.2.0is-path-inside: 3.0.3p-map: 3.0.0rimraf: 3.0.2slash: 3.0.0dev: true/delayed-stream/1.0.0:resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=}engines: {node: '>=0.4.0'}dev: true/delegates/1.0.0:resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=}dev: true/detect-indent/6.1.0:resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}engines: {node: '>=8'}dev: true/diff/3.5.0:resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==}engines: {node: '>=0.3.1'}dev: true/diff/4.0.2:resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}engines: {node: '>=0.3.1'}dev: true/diff/5.0.0:resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==}engines: {node: '>=0.3.1'}dev: true/dir-glob/2.2.2:resolution: {integrity: sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==}engines: {node: '>=4'}dependencies:path-type: 3.0.0dev: true/dir-glob/3.0.1:resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}engines: {node: '>=8'}dependencies:path-type: 4.0.0dev: true/doctrine/3.0.0:resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}engines: {node: '>=6.0.0'}dependencies:esutils: 2.0.3dev: true/dom-serializer/0.2.2:resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==}dependencies:domelementtype: 2.2.0entities: 2.2.0dev: true/dom-serializer/1.3.2:resolution: {integrity: sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==}dependencies:domelementtype: 2.2.0domhandler: 4.2.0entities: 2.2.0dev: true/domelementtype/1.3.1:resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==}dev: true/domelementtype/2.2.0:resolution: {integrity: sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==}dev: true/domhandler/2.4.2:resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==}dependencies:domelementtype: 1.3.1dev: true/domhandler/4.2.0:resolution: {integrity: sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==}engines: {node: '>= 4'}dependencies:domelementtype: 2.2.0dev: true/domutils/1.7.0:resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}dependencies:dom-serializer: 0.2.2domelementtype: 1.3.1dev: true/domutils/2.7.0:resolution: {integrity: sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==}dependencies:dom-serializer: 1.3.2domelementtype: 2.2.0domhandler: 4.2.0dev: true/dot-prop/5.3.0:resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}engines: {node: '>=8'}dependencies:is-obj: 2.0.0dev: true/dox/0.9.0:resolution: {integrity: sha1-vpewhcufSgt+gINdVH53uGh9Cgw=}hasBin: truedependencies:commander: 2.9.0jsdoctypeparser: 1.2.0markdown-it: 7.0.1dev: true/ecc-jsbn/0.1.2:resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=}dependencies:jsbn: 0.1.1safer-buffer: 2.1.2dev: true/editorconfig/0.15.3:resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==}hasBin: truedependencies:commander: 2.20.3lru-cache: 4.1.5semver: 5.7.1sigmund: 1.0.1dev: true/electron-to-chromium/1.3.800:resolution: {integrity: sha512-qagikPjZJSDWP85uWoxs32oK/xk/y3MhDZELfKRCWI7pBc0ZFlmjnXb+3+aNMaiqboeDJJa0v7CJd5cO1HKwEQ==}dev: true/elegant-spinner/1.0.1:resolution: {integrity: sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=}engines: {node: '>=0.10.0'}dev: true/emoji-regex/7.0.3:resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==}/emoji-regex/8.0.0:resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}dev: true/encode-utf8/1.0.3:resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}/end-of-stream/1.4.4:resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}dependencies:once: 1.4.0dev: true/enquirer/2.3.6:resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==}engines: {node: '>=8.6'}dependencies:ansi-colors: 4.1.1dev: true/entities/1.1.2:resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}dev: true/entities/2.2.0:resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}dev: true/env-paths/2.2.1:resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}engines: {node: '>=6'}dev: true/error-ex/1.3.2:resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}dependencies:is-arrayish: 0.2.1/es-abstract/1.18.5:resolution: {integrity: sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==}engines: {node: '>= 0.4'}dependencies:call-bind: 1.0.2es-to-primitive: 1.2.1function-bind: 1.1.1get-intrinsic: 1.1.1has: 1.0.3has-symbols: 1.0.2internal-slot: 1.0.3is-callable: 1.2.4is-negative-zero: 2.0.1is-regex: 1.1.4is-string: 1.0.7object-inspect: 1.11.0object-keys: 1.1.1object.assign: 4.1.2string.prototype.trimend: 1.0.4string.prototype.trimstart: 1.0.4unbox-primitive: 1.0.1dev: true/es-to-primitive/1.2.1:resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}engines: {node: '>= 0.4'}dependencies:is-callable: 1.2.4is-date-object: 1.0.5is-symbol: 1.0.4dev: true/esbuild/0.12.19:resolution: {integrity: sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w==}hasBin: truerequiresBuild: truedev: true/escalade/3.1.1:resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}engines: {node: '>=6'}dev: true/escape-string-regexp/1.0.5:resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}engines: {node: '>=0.8.0'}/escape-string-regexp/4.0.0:resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}engines: {node: '>=10'}dev: true/eslint-config-prettier/6.15.0_eslint@6.8.0:resolution: {integrity: sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==}hasBin: truepeerDependencies:eslint: '>=3.14.1'dependencies:eslint: 6.8.0get-stdin: 6.0.0dev: true/eslint-config-prettier/8.3.0_eslint@7.32.0:resolution: {integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==}hasBin: truepeerDependencies:eslint: '>=7.0.0'dependencies:eslint: 7.32.0dev: true/eslint-plugin-html/6.1.2:resolution: {integrity: sha512-bhBIRyZFqI4EoF12lGDHAmgfff8eLXx6R52/K3ESQhsxzCzIE6hdebS7Py651f7U3RBotqroUnC3L29bR7qJWQ==}dependencies:htmlparser2: 6.1.0dev: true/eslint-plugin-optimize-regex/1.2.1:resolution: {integrity: sha512-fUaU7Tj1G/KSTDTABJw4Wp427Rl7RPl9ViYTu1Jrv36fJw4DFhd4elPdXiuYtdPsNsvzn9GcVlKEssGIVjw0UQ==}engines: {node: '>=10'}dependencies:regexp-tree: 0.1.23dev: true/eslint-plugin-prettier/3.4.0_b77cd85fda941e232840dc83bf6b7690:resolution: {integrity: sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==}engines: {node: '>=6.0.0'}peerDependencies:eslint: '>=5.0.0'eslint-config-prettier: '*'prettier: '>=1.13.0'peerDependenciesMeta:eslint-config-prettier:optional: truedependencies:eslint: 6.8.0eslint-config-prettier: 6.15.0_eslint@6.8.0prettier: 1.19.1prettier-linter-helpers: 1.0.0dev: true/eslint-plugin-svelte3/2.7.3_eslint@6.8.0+svelte@3.42.1:resolution: {integrity: sha512-p6HhxyICX9x/x+8WSy6AVk2bmv9ayoznoTSyCvK47th/k/07ksuJixMwbGX9qxJVAmPBaYMjEIMSEZtJHPIN7w==}peerDependencies:eslint: '>=6.0.0'svelte: ^3.2.0dependencies:eslint: 6.8.0svelte: 3.42.1dev: true/eslint-plugin-svelte3/3.2.0_eslint@7.32.0+svelte@3.42.1:resolution: {integrity: sha512-qdWB1QN21dEozsJFdR8XlEhMnsS6aKHjsXWuNmchYwxoet5I6QdCr1Xcq62++IzRBMCNCeH4waXqSOAdqrZzgA==}engines: {node: '>=10'}peerDependencies:eslint: '>=6.0.0'svelte: ^3.2.0dependencies:eslint: 7.32.0svelte: 3.42.1dev: true/eslint-scope/5.1.1:resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}engines: {node: '>=8.0.0'}dependencies:esrecurse: 4.3.0estraverse: 4.3.0dev: true/eslint-utils/1.4.3:resolution: {integrity: sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==}engines: {node: '>=6'}dependencies:eslint-visitor-keys: 1.3.0dev: true/eslint-utils/2.1.0:resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==}engines: {node: '>=6'}dependencies:eslint-visitor-keys: 1.3.0dev: true/eslint-visitor-keys/1.3.0:resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==}engines: {node: '>=4'}dev: true/eslint-visitor-keys/2.1.0:resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}engines: {node: '>=10'}dev: true/eslint/6.8.0:resolution: {integrity: sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==}engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1}hasBin: truedependencies:'@babel/code-frame': 7.14.5ajv: 6.12.6chalk: 2.4.2cross-spawn: 6.0.5debug: 4.3.2doctrine: 3.0.0eslint-scope: 5.1.1eslint-utils: 1.4.3eslint-visitor-keys: 1.3.0espree: 6.2.1esquery: 1.4.0esutils: 2.0.3file-entry-cache: 5.0.1functional-red-black-tree: 1.0.1glob-parent: 5.1.2globals: 12.4.0ignore: 4.0.6import-fresh: 3.3.0imurmurhash: 0.1.4inquirer: 7.3.3is-glob: 4.0.1js-yaml: 3.14.1json-stable-stringify-without-jsonify: 1.0.1levn: 0.3.0lodash: 4.17.21minimatch: 3.0.4mkdirp: 0.5.5natural-compare: 1.4.0optionator: 0.8.3progress: 2.0.3regexpp: 2.0.1semver: 6.3.0strip-ansi: 5.2.0strip-json-comments: 3.1.1table: 5.4.6text-table: 0.2.0v8-compile-cache: 2.3.0transitivePeerDependencies:- supports-colordev: true/eslint/7.32.0:resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==}engines: {node: ^10.12.0 || >=12.0.0}hasBin: truedependencies:'@babel/code-frame': 7.12.11'@eslint/eslintrc': 0.4.3'@humanwhocodes/config-array': 0.5.0ajv: 6.12.6chalk: 4.1.2cross-spawn: 7.0.3debug: 4.3.2doctrine: 3.0.0enquirer: 2.3.6escape-string-regexp: 4.0.0eslint-scope: 5.1.1eslint-utils: 2.1.0eslint-visitor-keys: 2.1.0espree: 7.3.1esquery: 1.4.0esutils: 2.0.3fast-deep-equal: 3.1.3file-entry-cache: 6.0.1functional-red-black-tree: 1.0.1glob-parent: 5.1.2globals: 13.10.0ignore: 4.0.6import-fresh: 3.3.0imurmurhash: 0.1.4is-glob: 4.0.1js-yaml: 3.14.1json-stable-stringify-without-jsonify: 1.0.1levn: 0.4.1lodash.merge: 4.6.2minimatch: 3.0.4natural-compare: 1.4.0optionator: 0.9.1progress: 2.0.3regexpp: 3.2.0semver: 7.3.5strip-ansi: 6.0.0strip-json-comments: 3.1.1table: 6.7.1text-table: 0.2.0v8-compile-cache: 2.3.0transitivePeerDependencies:- supports-colordev: true/espree/6.2.1:resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==}engines: {node: '>=6.0.0'}dependencies:acorn: 7.4.1acorn-jsx: 5.3.2_acorn@7.4.1eslint-visitor-keys: 1.3.0dev: true/espree/7.3.1:resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==}engines: {node: ^10.12.0 || >=12.0.0}dependencies:acorn: 7.4.1acorn-jsx: 5.3.2_acorn@7.4.1eslint-visitor-keys: 1.3.0dev: true/esprima/4.0.1:resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}engines: {node: '>=4'}hasBin: truedev: true/esquery/1.4.0:resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==}engines: {node: '>=0.10'}dependencies:estraverse: 5.2.0dev: true/esrecurse/4.3.0:resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}engines: {node: '>=4.0'}dependencies:estraverse: 5.2.0dev: true/estraverse/4.3.0:resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}engines: {node: '>=4.0'}dev: true/estraverse/5.2.0:resolution: {integrity: sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==}engines: {node: '>=4.0'}dev: true/estree-walker/2.0.2:resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}dev: true/esutils/2.0.3:resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}engines: {node: '>=0.10.0'}dev: true/execa/1.0.0:resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==}engines: {node: '>=6'}dependencies:cross-spawn: 6.0.5get-stream: 4.1.0is-stream: 1.1.0npm-run-path: 2.0.2p-finally: 1.0.0signal-exit: 3.0.3strip-eof: 1.0.0dev: true/execa/2.1.0:resolution: {integrity: sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==}engines: {node: ^8.12.0 || >=9.7.0}dependencies:cross-spawn: 7.0.3get-stream: 5.2.0is-stream: 2.0.1merge-stream: 2.0.0npm-run-path: 3.1.0onetime: 5.1.2p-finally: 2.0.1signal-exit: 3.0.3strip-final-newline: 2.0.0dev: true/execall/1.0.0:resolution: {integrity: sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M=}engines: {node: '>=0.10.0'}dependencies:clone-regexp: 1.0.1dev: true/execall/2.0.0:resolution: {integrity: sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==}engines: {node: '>=8'}dependencies:clone-regexp: 2.2.0dev: true/expand-brackets/2.1.4:resolution: {integrity: sha1-t3c14xXOMPa27/D4OwQVGiJEliI=}engines: {node: '>=0.10.0'}dependencies:debug: 2.6.9define-property: 0.2.5extend-shallow: 2.0.1posix-character-classes: 0.1.1regex-not: 1.0.2snapdragon: 0.8.2to-regex: 3.0.2dev: true/extend-shallow/2.0.1:resolution: {integrity: sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=}engines: {node: '>=0.10.0'}dependencies:is-extendable: 0.1.1dev: true/extend-shallow/3.0.2:resolution: {integrity: sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=}engines: {node: '>=0.10.0'}dependencies:assign-symbols: 1.0.0is-extendable: 1.0.1dev: true/extend/3.0.2:resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}dev: true/external-editor/3.1.0:resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}engines: {node: '>=4'}dependencies:chardet: 0.7.0iconv-lite: 0.4.24tmp: 0.0.33dev: true/extglob/2.0.4:resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==}engines: {node: '>=0.10.0'}dependencies:array-unique: 0.3.2define-property: 1.0.0expand-brackets: 2.1.4extend-shallow: 2.0.1fragment-cache: 0.2.1regex-not: 1.0.2snapdragon: 0.8.2to-regex: 3.0.2dev: true/extsprintf/1.3.0:resolution: {integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=}engines: {'0': node >=0.6.0}dev: true/fast-deep-equal/3.1.3:resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}dev: true/fast-diff/1.2.0:resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==}dev: true/fast-glob/2.2.7:resolution: {integrity: sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==}engines: {node: '>=4.0.0'}dependencies:'@mrmlnc/readdir-enhanced': 2.2.1'@nodelib/fs.stat': 1.1.3glob-parent: 3.1.0is-glob: 4.0.1merge2: 1.4.1micromatch: 3.1.10dev: true/fast-glob/3.2.7:resolution: {integrity: sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==}engines: {node: '>=8'}dependencies:'@nodelib/fs.stat': 2.0.5'@nodelib/fs.walk': 1.2.8glob-parent: 5.1.2merge2: 1.4.1micromatch: 4.0.4dev: true/fast-json-stable-stringify/2.1.0:resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}dev: true/fast-levenshtein/2.0.6:resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=}dev: true/fastq/1.11.1:resolution: {integrity: sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==}dependencies:reusify: 1.0.4dev: true/figures/1.7.0:resolution: {integrity: sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=}engines: {node: '>=0.10.0'}dependencies:escape-string-regexp: 1.0.5object-assign: 4.1.1dev: true/figures/2.0.0:resolution: {integrity: sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=}engines: {node: '>=4'}dependencies:escape-string-regexp: 1.0.5dev: true/figures/3.2.0:resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}engines: {node: '>=8'}dependencies:escape-string-regexp: 1.0.5dev: true/file-entry-cache/4.0.0:resolution: {integrity: sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==}engines: {node: '>=4'}dependencies:flat-cache: 2.0.1dev: true/file-entry-cache/5.0.1:resolution: {integrity: sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==}engines: {node: '>=4'}dependencies:flat-cache: 2.0.1dev: true/file-entry-cache/6.0.1:resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}engines: {node: ^10.12.0 || >=12.0.0}dependencies:flat-cache: 3.0.4dev: true/fill-range/4.0.0:resolution: {integrity: sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=}engines: {node: '>=0.10.0'}dependencies:extend-shallow: 2.0.1is-number: 3.0.0repeat-string: 1.6.1to-regex-range: 2.1.1dev: true/fill-range/7.0.1:resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}engines: {node: '>=8'}dependencies:to-regex-range: 5.0.1/find-up/2.1.0:resolution: {integrity: sha1-RdG35QbHF93UgndaK3eSCjwMV6c=}engines: {node: '>=4'}dependencies:locate-path: 2.0.0dev: true/find-up/3.0.0:resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}engines: {node: '>=6'}dependencies:locate-path: 3.0.0/find-up/4.1.0:resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}engines: {node: '>=8'}dependencies:locate-path: 5.0.0path-exists: 4.0.0dev: true/find-up/5.0.0:resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}engines: {node: '>=10'}dependencies:locate-path: 6.0.0path-exists: 4.0.0dev: true/flat-cache/2.0.1:resolution: {integrity: sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==}engines: {node: '>=4'}dependencies:flatted: 2.0.2rimraf: 2.6.3write: 1.0.3dev: true/flat-cache/3.0.4:resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}engines: {node: ^10.12.0 || >=12.0.0}dependencies:flatted: 3.2.2rimraf: 3.0.2dev: true/flat/4.1.1:resolution: {integrity: sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==}hasBin: truedependencies:is-buffer: 2.0.5dev: true/flat/5.0.2:resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}hasBin: truedev: true/flatted/2.0.2:resolution: {integrity: sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==}dev: true/flatted/3.2.2:resolution: {integrity: sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==}dev: true/for-in/1.0.2:resolution: {integrity: sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=}engines: {node: '>=0.10.0'}dev: true/forever-agent/0.6.1:resolution: {integrity: sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=}dev: true/form-data/2.3.3:resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}engines: {node: '>= 0.12'}dependencies:asynckit: 0.4.0combined-stream: 1.0.8mime-types: 2.1.32dev: true/fragment-cache/0.2.1:resolution: {integrity: sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=}engines: {node: '>=0.10.0'}dependencies:map-cache: 0.2.2dev: true/fs-minipass/2.1.0:resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}engines: {node: '>= 8'}dependencies:minipass: 3.1.3dev: true/fs.realpath/1.0.0:resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}dev: true/fsevents/2.3.2:resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}os: [darwin]optional: true/function-bind/1.1.1:resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}/functional-red-black-tree/1.0.1:resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=}dev: true/gauge/2.7.4:resolution: {integrity: sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=}dependencies:aproba: 1.2.0console-control-strings: 1.1.0has-unicode: 2.0.1object-assign: 4.1.1signal-exit: 3.0.3string-width: 1.0.2strip-ansi: 3.0.1wide-align: 1.1.3dev: true/gaze/1.1.3:resolution: {integrity: sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==}engines: {node: '>= 4.0.0'}dependencies:globule: 1.3.2dev: true/gensync/1.0.0-beta.2:resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}engines: {node: '>=6.9.0'}dev: true/get-caller-file/2.0.5:resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}engines: {node: 6.* || 8.* || >= 10.*}/get-func-name/2.0.0:resolution: {integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=}dev: true/get-intrinsic/1.1.1:resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==}dependencies:function-bind: 1.1.1has: 1.0.3has-symbols: 1.0.2dev: true/get-own-enumerable-property-symbols/3.0.2:resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}dev: true/get-stdin/4.0.1:resolution: {integrity: sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=}engines: {node: '>=0.10.0'}dev: true/get-stdin/6.0.0:resolution: {integrity: sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==}engines: {node: '>=4'}dev: true/get-stdin/7.0.0:resolution: {integrity: sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==}engines: {node: '>=8'}dev: true/get-stream/4.1.0:resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==}engines: {node: '>=6'}dependencies:pump: 3.0.0dev: true/get-stream/5.2.0:resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}engines: {node: '>=8'}dependencies:pump: 3.0.0dev: true/get-value/2.0.6:resolution: {integrity: sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=}engines: {node: '>=0.10.0'}dev: true/getpass/0.1.7:resolution: {integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=}dependencies:assert-plus: 1.0.0dev: true/glob-parent/3.1.0:resolution: {integrity: sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=}dependencies:is-glob: 3.1.0path-dirname: 1.0.2dev: true/glob-parent/5.1.2:resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}engines: {node: '>= 6'}dependencies:is-glob: 4.0.1/glob-to-regexp/0.3.0:resolution: {integrity: sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=}dev: true/glob/7.1.3:resolution: {integrity: sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==}dependencies:fs.realpath: 1.0.0inflight: 1.0.6inherits: 2.0.4minimatch: 3.0.4once: 1.4.0path-is-absolute: 1.0.1dev: true/glob/7.1.6:resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}dependencies:fs.realpath: 1.0.0inflight: 1.0.6inherits: 2.0.4minimatch: 3.0.4once: 1.4.0path-is-absolute: 1.0.1dev: true/glob/7.1.7:resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==}dependencies:fs.realpath: 1.0.0inflight: 1.0.6inherits: 2.0.4minimatch: 3.0.4once: 1.4.0path-is-absolute: 1.0.1dev: true/global-modules/2.0.0:resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==}engines: {node: '>=6'}dependencies:global-prefix: 3.0.0dev: true/global-prefix/3.0.0:resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==}engines: {node: '>=6'}dependencies:ini: 1.3.8kind-of: 6.0.3which: 1.3.1dev: true/globals/11.12.0:resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}engines: {node: '>=4'}dev: true/globals/12.4.0:resolution: {integrity: sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==}engines: {node: '>=8'}dependencies:type-fest: 0.8.1dev: true/globals/13.10.0:resolution: {integrity: sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==}engines: {node: '>=8'}dependencies:type-fest: 0.20.2dev: true/globalyzer/0.1.0:resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}dev: true/globby/10.0.2:resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==}engines: {node: '>=8'}dependencies:'@types/glob': 7.1.4array-union: 2.1.0dir-glob: 3.0.1fast-glob: 3.2.7glob: 7.1.7ignore: 5.1.8merge2: 1.4.1slash: 3.0.0dev: true/globby/11.0.4:resolution: {integrity: sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==}engines: {node: '>=10'}dependencies:array-union: 2.1.0dir-glob: 3.0.1fast-glob: 3.2.7ignore: 5.1.8merge2: 1.4.1slash: 3.0.0dev: true/globby/9.2.0:resolution: {integrity: sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==}engines: {node: '>=6'}dependencies:'@types/glob': 7.1.4array-union: 1.0.2dir-glob: 2.2.2fast-glob: 2.2.7glob: 7.1.7ignore: 4.0.6pify: 4.0.1slash: 2.0.0dev: true/globjoin/0.1.4:resolution: {integrity: sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=}dev: true/globrex/0.1.2:resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}dev: true/globule/1.3.2:resolution: {integrity: sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==}engines: {node: '>= 0.10'}dependencies:glob: 7.1.7lodash: 4.17.21minimatch: 3.0.4dev: true/gonzales-pe/4.3.0:resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==}engines: {node: '>=0.6.0'}hasBin: truedependencies:minimist: 1.2.5dev: true/graceful-fs/4.2.8:resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==}dev: true/graceful-readlink/1.0.1:resolution: {integrity: sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=}dev: true/growl/1.10.5:resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==}engines: {node: '>=4.x'}dev: true/har-schema/2.0.0:resolution: {integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=}engines: {node: '>=4'}dev: true/har-validator/5.1.5:resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==}engines: {node: '>=6'}deprecated: this library is no longer supporteddependencies:ajv: 6.12.6har-schema: 2.0.0dev: true/hard-rejection/2.1.0:resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}engines: {node: '>=6'}dev: true/has-ansi/2.0.0:resolution: {integrity: sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=}engines: {node: '>=0.10.0'}dependencies:ansi-regex: 2.1.1dev: true/has-bigints/1.0.1:resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==}dev: true/has-flag/3.0.0:resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=}engines: {node: '>=4'}/has-flag/4.0.0:resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}engines: {node: '>=8'}dev: true/has-symbols/1.0.2:resolution: {integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==}engines: {node: '>= 0.4'}dev: true/has-tostringtag/1.0.0:resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}engines: {node: '>= 0.4'}dependencies:has-symbols: 1.0.2dev: true/has-unicode/2.0.1:resolution: {integrity: sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=}dev: true/has-value/0.3.1:resolution: {integrity: sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=}engines: {node: '>=0.10.0'}dependencies:get-value: 2.0.6has-values: 0.1.4isobject: 2.1.0dev: true/has-value/1.0.0:resolution: {integrity: sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=}engines: {node: '>=0.10.0'}dependencies:get-value: 2.0.6has-values: 1.0.0isobject: 3.0.1dev: true/has-values/0.1.4:resolution: {integrity: sha1-bWHeldkd/Km5oCCJrThL/49it3E=}engines: {node: '>=0.10.0'}dev: true/has-values/1.0.0:resolution: {integrity: sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=}engines: {node: '>=0.10.0'}dependencies:is-number: 3.0.0kind-of: 4.0.0dev: true/has/1.0.3:resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}engines: {node: '>= 0.4.0'}dependencies:function-bind: 1.1.1/he/1.2.0:resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}hasBin: truedev: true/hex-to-array-buffer/1.1.0:resolution: {integrity: sha512-vvl3IM8FfT1uOnHtEqyjkDK9Luqz6MQrH82qIvVnjyXxRhkeaEZyRRPiBgf2yym3nweRVEfayxt/1SoTXZYd4Q==}/hosted-git-info/2.8.9:resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}/hosted-git-info/4.0.2:resolution: {integrity: sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==}engines: {node: '>=10'}dependencies:lru-cache: 6.0.0dev: true/html-tags/2.0.0:resolution: {integrity: sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=}engines: {node: '>=4'}dev: true/html-tags/3.1.0:resolution: {integrity: sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==}engines: {node: '>=8'}dev: true/htmlparser2/3.10.1:resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}dependencies:domelementtype: 1.3.1domhandler: 2.4.2domutils: 1.7.0entities: 1.1.2inherits: 2.0.4readable-stream: 3.6.0dev: true/htmlparser2/6.1.0:resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}dependencies:domelementtype: 2.2.0domhandler: 4.2.0domutils: 2.7.0entities: 2.2.0dev: true/http-signature/1.2.0:resolution: {integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=}engines: {node: '>=0.8', npm: '>=1.3.7'}dependencies:assert-plus: 1.0.0jsprim: 1.4.1sshpk: 1.16.1dev: true/husky/3.1.0:resolution: {integrity: sha512-FJkPoHHB+6s4a+jwPqBudBDvYZsoQW5/HBuMSehC8qDiCe50kpcxeqFoDSlow+9I6wg47YxBoT3WxaURlrDIIQ==}engines: {node: '>=8.6.0'}hasBin: truerequiresBuild: truedependencies:chalk: 2.4.2ci-info: 2.0.0cosmiconfig: 5.2.1execa: 1.0.0get-stdin: 7.0.0opencollective-postinstall: 2.0.3pkg-dir: 4.2.0please-upgrade-node: 3.2.0read-pkg: 5.2.0run-node: 1.0.0slash: 3.0.0dev: true/iconv-lite/0.4.24:resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}engines: {node: '>=0.10.0'}dependencies:safer-buffer: 2.1.2dev: true/ignore/4.0.6:resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}engines: {node: '>= 4'}dev: true/ignore/5.1.8:resolution: {integrity: sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==}engines: {node: '>= 4'}dev: true/import-fresh/2.0.0:resolution: {integrity: sha1-2BNVwVYS04bGH53dOSLUMEgipUY=}engines: {node: '>=4'}dependencies:caller-path: 2.0.0resolve-from: 3.0.0dev: true/import-fresh/3.3.0:resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}engines: {node: '>=6'}dependencies:parent-module: 1.0.1resolve-from: 4.0.0dev: true/import-lazy/3.1.0:resolution: {integrity: sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==}engines: {node: '>=6'}dev: true/import-lazy/4.0.0:resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}engines: {node: '>=8'}dev: true/imurmurhash/0.1.4:resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=}engines: {node: '>=0.8.19'}dev: true/indent-string/3.2.0:resolution: {integrity: sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=}engines: {node: '>=4'}dev: true/indent-string/4.0.0:resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}engines: {node: '>=8'}dev: true/indexes-of/1.0.1:resolution: {integrity: sha1-8w9xbI4r00bHtn0985FVZqfAVgc=}dev: true/inflight/1.0.6:resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}dependencies:once: 1.4.0wrappy: 1.0.2dev: true/inherits/2.0.4:resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}dev: true/ini/1.3.8:resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}dev: true/inquirer/7.3.3:resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==}engines: {node: '>=8.0.0'}dependencies:ansi-escapes: 4.3.2chalk: 4.1.2cli-cursor: 3.1.0cli-width: 3.0.0external-editor: 3.1.0figures: 3.2.0lodash: 4.17.21mute-stream: 0.0.8run-async: 2.4.1rxjs: 6.6.7string-width: 4.2.2strip-ansi: 6.0.0through: 2.3.8dev: true/internal-slot/1.0.3:resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}engines: {node: '>= 0.4'}dependencies:get-intrinsic: 1.1.1has: 1.0.3side-channel: 1.0.4dev: true/ipware/2.0.0:resolution: {integrity: sha512-xOqLoYyWIBcO6ANVZK7GgnuH9V7wbqDf6/MrRi4I+Ae1A11m6jcaNRtMSezypkupRUjiSSLq3F/DTvAM1cZfQw==}dev: falseoptional: true/is-accessor-descriptor/0.1.6:resolution: {integrity: sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=}engines: {node: '>=0.10.0'}dependencies:kind-of: 3.2.2dev: true/is-accessor-descriptor/1.0.0:resolution: {integrity: sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==}engines: {node: '>=0.10.0'}dependencies:kind-of: 6.0.3dev: true/is-alphabetical/1.0.4:resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}dev: true/is-alphanumeric/1.0.0:resolution: {integrity: sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=}engines: {node: '>=0.10.0'}dev: true/is-alphanumerical/1.0.4:resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==}dependencies:is-alphabetical: 1.0.4is-decimal: 1.0.4dev: true/is-arrayish/0.2.1:resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=}/is-bigint/1.0.3:resolution: {integrity: sha512-ZU538ajmYJmzysE5yU4Y7uIrPQ2j704u+hXFiIPQExpqzzUbpe5jCPdTfmz7jXRxZdvjY3KZ3ZNenoXQovX+Dg==}dev: true/is-binary-path/2.1.0:resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}engines: {node: '>=8'}dependencies:binary-extensions: 2.2.0/is-boolean-object/1.1.2:resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}engines: {node: '>= 0.4'}dependencies:call-bind: 1.0.2has-tostringtag: 1.0.0dev: true/is-buffer/1.1.6:resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}dev: true/is-buffer/2.0.5:resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}engines: {node: '>=4'}dev: true/is-callable/1.2.4:resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==}engines: {node: '>= 0.4'}dev: true/is-core-module/2.5.0:resolution: {integrity: sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==}dependencies:has: 1.0.3/is-data-descriptor/0.1.4:resolution: {integrity: sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=}engines: {node: '>=0.10.0'}dependencies:kind-of: 3.2.2dev: true/is-data-descriptor/1.0.0:resolution: {integrity: sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==}engines: {node: '>=0.10.0'}dependencies:kind-of: 6.0.3dev: true/is-date-object/1.0.5:resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}engines: {node: '>= 0.4'}dependencies:has-tostringtag: 1.0.0dev: true/is-decimal/1.0.4:resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==}dev: true/is-descriptor/0.1.6:resolution: {integrity: sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==}engines: {node: '>=0.10.0'}dependencies:is-accessor-descriptor: 0.1.6is-data-descriptor: 0.1.4kind-of: 5.1.0dev: true/is-descriptor/1.0.2:resolution: {integrity: sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==}engines: {node: '>=0.10.0'}dependencies:is-accessor-descriptor: 1.0.0is-data-descriptor: 1.0.0kind-of: 6.0.3dev: true/is-directory/0.3.1:resolution: {integrity: sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=}engines: {node: '>=0.10.0'}dev: true/is-extendable/0.1.1:resolution: {integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=}engines: {node: '>=0.10.0'}dev: true/is-extendable/1.0.1:resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==}engines: {node: '>=0.10.0'}dependencies:is-plain-object: 2.0.4dev: true/is-extglob/2.1.1:resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=}engines: {node: '>=0.10.0'}/is-fullwidth-code-point/1.0.0:resolution: {integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=}engines: {node: '>=0.10.0'}dependencies:number-is-nan: 1.0.1dev: true/is-fullwidth-code-point/2.0.0:resolution: {integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=}engines: {node: '>=4'}/is-fullwidth-code-point/3.0.0:resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}engines: {node: '>=8'}dev: true/is-glob/3.1.0:resolution: {integrity: sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=}engines: {node: '>=0.10.0'}dependencies:is-extglob: 2.1.1dev: true/is-glob/4.0.1:resolution: {integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==}engines: {node: '>=0.10.0'}dependencies:is-extglob: 2.1.1/is-hexadecimal/1.0.4:resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==}dev: true/is-negative-zero/2.0.1:resolution: {integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==}engines: {node: '>= 0.4'}dev: true/is-number-object/1.0.6:resolution: {integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==}engines: {node: '>= 0.4'}dependencies:has-tostringtag: 1.0.0dev: true/is-number/3.0.0:resolution: {integrity: sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=}engines: {node: '>=0.10.0'}dependencies:kind-of: 3.2.2dev: true/is-number/7.0.0:resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}engines: {node: '>=0.12.0'}/is-obj/1.0.1:resolution: {integrity: sha1-PkcprB9f3gJc19g6iW2rn09n2w8=}engines: {node: '>=0.10.0'}dev: true/is-obj/2.0.0:resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}engines: {node: '>=8'}dev: true/is-observable/1.1.0:resolution: {integrity: sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==}engines: {node: '>=4'}dependencies:symbol-observable: 1.2.0dev: true/is-path-cwd/2.2.0:resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}engines: {node: '>=6'}dev: true/is-path-inside/3.0.3:resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}engines: {node: '>=8'}dev: true/is-plain-obj/1.1.0:resolution: {integrity: sha1-caUMhCnfync8kqOQpKA7OfzVHT4=}engines: {node: '>=0.10.0'}dev: true/is-plain-obj/2.1.0:resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}engines: {node: '>=8'}dev: true/is-plain-object/2.0.4:resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}engines: {node: '>=0.10.0'}dependencies:isobject: 3.0.1dev: true/is-promise/2.2.2:resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}dev: true/is-regex/1.1.4:resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}engines: {node: '>= 0.4'}dependencies:call-bind: 1.0.2has-tostringtag: 1.0.0dev: true/is-regexp/1.0.0:resolution: {integrity: sha1-/S2INUXEa6xaYz57mgnof6LLUGk=}engines: {node: '>=0.10.0'}dev: true/is-regexp/2.1.0:resolution: {integrity: sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==}engines: {node: '>=6'}dev: true/is-stream/1.1.0:resolution: {integrity: sha1-EtSj3U5o4Lec6428hBc66A2RykQ=}engines: {node: '>=0.10.0'}dev: true/is-stream/2.0.1:resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}engines: {node: '>=8'}dev: true/is-string/1.0.7:resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}engines: {node: '>= 0.4'}dependencies:has-tostringtag: 1.0.0dev: true/is-supported-regexp-flag/1.0.1:resolution: {integrity: sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==}engines: {node: '>=0.10.0'}dev: true/is-symbol/1.0.4:resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}engines: {node: '>= 0.4'}dependencies:has-symbols: 1.0.2dev: true/is-typedarray/1.0.0:resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=}dev: true/is-whitespace-character/1.0.4:resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==}dev: true/is-windows/1.0.2:resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}engines: {node: '>=0.10.0'}dev: true/is-word-character/1.0.4:resolution: {integrity: sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==}dev: true/isarray/0.0.1:resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=}dev: true/isarray/1.0.0:resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=}dev: true/isexe/2.0.0:resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=}dev: true/isobject/2.1.0:resolution: {integrity: sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=}engines: {node: '>=0.10.0'}dependencies:isarray: 1.0.0dev: true/isobject/3.0.1:resolution: {integrity: sha1-TkMekrEalzFjaqH5yNHMvP2reN8=}engines: {node: '>=0.10.0'}dev: true/isstream/0.1.2:resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=}dev: true/js-base64/2.6.4:resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}dev: true/js-beautify/1.14.0:resolution: {integrity: sha512-yuck9KirNSCAwyNJbqW+BxJqJ0NLJ4PwBUzQQACl5O3qHMBXVkXb/rD0ilh/Lat/tn88zSZ+CAHOlk0DsY7GuQ==}engines: {node: '>=10'}hasBin: truedependencies:config-chain: 1.1.13editorconfig: 0.15.3glob: 7.1.7nopt: 5.0.0dev: true/js-tokens/4.0.0:resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}dev: true/js-yaml/3.13.1:resolution: {integrity: sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==}hasBin: truedependencies:argparse: 1.0.10esprima: 4.0.1dev: true/js-yaml/3.14.1:resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}hasBin: truedependencies:argparse: 1.0.10esprima: 4.0.1dev: true/js-yaml/4.0.0:resolution: {integrity: sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==}hasBin: truedependencies:argparse: 2.0.1dev: true/jsbn/0.1.1:resolution: {integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM=}dev: true/jsbn/1.1.0:resolution: {integrity: sha1-sBMHyym2GKHtJux56RH4A8TaAEA=}/jsdoctest/1.7.1:resolution: {integrity: sha512-mSSYyKXNDerEEwhV7NiNRVU+KCNlkACrYP6T1odwoB9N47m28e8eU8O0SoG5iy8JcydcLUf0cd3u8aKUyDv8LA==}hasBin: truedependencies:commander: 2.20.3dox: 0.9.0js-beautify: 1.14.0lodash: 4.17.21should: 11.2.1uglify-js: 3.14.1dev: true/jsdoctypeparser/1.2.0:resolution: {integrity: sha1-597cFToRhJ/8UUEUSuhqfvDCU5I=}dependencies:lodash: 3.10.1dev: true/jsesc/2.5.2:resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}engines: {node: '>=4'}hasBin: truedev: true/json-parse-better-errors/1.0.2:resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}/json-parse-even-better-errors/2.3.1:resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}dev: true/json-schema-traverse/0.4.1:resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}dev: true/json-schema-traverse/1.0.0:resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}dev: true/json-schema/0.2.3:resolution: {integrity: sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=}dev: true/json-stable-stringify-without-jsonify/1.0.1:resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=}dev: true/json-stringify-safe/5.0.1:resolution: {integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=}dev: true/json5/2.2.0:resolution: {integrity: sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==}engines: {node: '>=6'}hasBin: truedependencies:minimist: 1.2.5dev: true/jsprim/1.4.1:resolution: {integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=}engines: {'0': node >=0.6.0}dependencies:assert-plus: 1.0.0extsprintf: 1.3.0json-schema: 0.2.3verror: 1.10.0dev: true/just-extend/4.2.1:resolution: {integrity: sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==}dev: true/kind-of/3.2.2:resolution: {integrity: sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=}engines: {node: '>=0.10.0'}dependencies:is-buffer: 1.1.6dev: true/kind-of/4.0.0:resolution: {integrity: sha1-IIE989cSkosgc3hpGkUGb65y3Vc=}engines: {node: '>=0.10.0'}dependencies:is-buffer: 1.1.6dev: true/kind-of/5.1.0:resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==}engines: {node: '>=0.10.0'}dev: true/kind-of/6.0.3:resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}engines: {node: '>=0.10.0'}dev: true/kleur/4.1.4:resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==}engines: {node: '>=6'}dev: true/known-css-properties/0.11.0:resolution: {integrity: sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==}dev: true/known-css-properties/0.16.0:resolution: {integrity: sha512-0g5vDDPvNnQk7WM/aE92dTDxXJoOE0biiIcUb3qkn/F6h/ZQZPlZIbE2XSXH2vFPfphkgCxuR2vH6HHnobEOaQ==}dev: true/leven/2.1.0:resolution: {integrity: sha1-wuep93IJTe6dNCAq6KzORoeHVYA=}engines: {node: '>=0.10.0'}dev: true/leven/3.1.0:resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}engines: {node: '>=6'}dev: true/levn/0.3.0:resolution: {integrity: sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=}engines: {node: '>= 0.8.0'}dependencies:prelude-ls: 1.1.2type-check: 0.3.2dev: true/levn/0.4.1:resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}engines: {node: '>= 0.8.0'}dependencies:prelude-ls: 1.2.1type-check: 0.4.0dev: true/lines-and-columns/1.1.6:resolution: {integrity: sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=}dev: true/linkify-it/2.2.0:resolution: {integrity: sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==}dependencies:uc.micro: 1.0.6dev: true/lint-staged/9.5.0:resolution: {integrity: sha512-nawMob9cb/G1J98nb8v3VC/E8rcX1rryUYXVZ69aT9kde6YWX+uvNOEHY5yf2gcWcTJGiD0kqXmCnS3oD75GIA==}hasBin: truedependencies:chalk: 2.4.2commander: 2.20.3cosmiconfig: 5.2.1debug: 4.3.2dedent: 0.7.0del: 5.1.0execa: 2.1.0listr: 0.14.3log-symbols: 3.0.0micromatch: 4.0.4normalize-path: 3.0.0please-upgrade-node: 3.2.0string-argv: 0.3.1stringify-object: 3.3.0transitivePeerDependencies:- supports-color- zen-observabledev: true/listr-silent-renderer/1.1.1:resolution: {integrity: sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=}engines: {node: '>=4'}dev: true/listr-update-renderer/0.5.0_listr@0.14.3:resolution: {integrity: sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==}engines: {node: '>=6'}peerDependencies:listr: ^0.14.2dependencies:chalk: 1.1.3cli-truncate: 0.2.1elegant-spinner: 1.0.1figures: 1.7.0indent-string: 3.2.0listr: 0.14.3log-symbols: 1.0.2log-update: 2.3.0strip-ansi: 3.0.1dev: true/listr-verbose-renderer/0.5.0:resolution: {integrity: sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==}engines: {node: '>=4'}dependencies:chalk: 2.4.2cli-cursor: 2.1.0date-fns: 1.30.1figures: 2.0.0dev: true/listr/0.14.3:resolution: {integrity: sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==}engines: {node: '>=6'}dependencies:'@samverschueren/stream-to-observable': 0.3.1_rxjs@6.6.7is-observable: 1.1.0is-promise: 2.2.2is-stream: 1.1.0listr-silent-renderer: 1.1.1listr-update-renderer: 0.5.0_listr@0.14.3listr-verbose-renderer: 0.5.0p-map: 2.1.0rxjs: 6.6.7transitivePeerDependencies:- zen-observabledev: true/load-json-file/4.0.0:resolution: {integrity: sha1-L19Fq5HjMhYjT9U62rZo607AmTs=}engines: {node: '>=4'}dependencies:graceful-fs: 4.2.8parse-json: 4.0.0pify: 3.0.0strip-bom: 3.0.0dev: true/locate-path/2.0.0:resolution: {integrity: sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=}engines: {node: '>=4'}dependencies:p-locate: 2.0.0path-exists: 3.0.0dev: true/locate-path/3.0.0:resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}engines: {node: '>=6'}dependencies:p-locate: 3.0.0path-exists: 3.0.0/locate-path/5.0.0:resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}engines: {node: '>=8'}dependencies:p-locate: 4.1.0dev: true/locate-path/6.0.0:resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}engines: {node: '>=10'}dependencies:p-locate: 5.0.0dev: true/lodash-es/4.17.21:resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}dev: false/lodash.clonedeep/4.5.0:resolution: {integrity: sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=}dev: true/lodash.get/4.4.2:resolution: {integrity: sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=}dev: true/lodash.merge/4.6.2:resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}dev: true/lodash.truncate/4.4.2:resolution: {integrity: sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=}dev: true/lodash/3.10.1:resolution: {integrity: sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=}dev: true/lodash/4.17.21:resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}/log-symbols/1.0.2:resolution: {integrity: sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=}engines: {node: '>=0.10.0'}dependencies:chalk: 1.1.3dev: true/log-symbols/2.2.0:resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==}engines: {node: '>=4'}dependencies:chalk: 2.4.2dev: true/log-symbols/3.0.0:resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==}engines: {node: '>=8'}dependencies:chalk: 2.4.2dev: true/log-symbols/4.0.0:resolution: {integrity: sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==}engines: {node: '>=10'}dependencies:chalk: 4.1.2dev: true/log-update/2.3.0:resolution: {integrity: sha1-iDKP19HOeTiykoN0bwsbwSayRwg=}engines: {node: '>=4'}dependencies:ansi-escapes: 3.2.0cli-cursor: 2.1.0wrap-ansi: 3.0.1dev: true/lolex/4.2.0:resolution: {integrity: sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==}dev: true/lolex/5.1.2:resolution: {integrity: sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==}dependencies:'@sinonjs/commons': 1.8.3dev: true/longest-streak/2.0.4:resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==}dev: true/loud-rejection/1.6.0:resolution: {integrity: sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=}engines: {node: '>=0.10.0'}dependencies:currently-unhandled: 0.4.1signal-exit: 3.0.3dev: true/lru-cache/4.1.5:resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}dependencies:pseudomap: 1.0.2yallist: 2.1.2dev: true/lru-cache/6.0.0:resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}engines: {node: '>=10'}dependencies:yallist: 4.0.0dev: true/luxon/1.28.0:resolution: {integrity: sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==}dev: falseoptional: true/magic-string/0.25.7:resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}dependencies:sourcemap-codec: 1.4.8dev: true/map-cache/0.2.2:resolution: {integrity: sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=}engines: {node: '>=0.10.0'}dev: true/map-obj/1.0.1:resolution: {integrity: sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=}engines: {node: '>=0.10.0'}dev: true/map-obj/2.0.0:resolution: {integrity: sha1-plzSkIepJZi4eRJXpSPgISIqwfk=}engines: {node: '>=4'}dev: true/map-obj/4.2.1:resolution: {integrity: sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==}engines: {node: '>=8'}dev: true/map-visit/1.0.0:resolution: {integrity: sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=}engines: {node: '>=0.10.0'}dependencies:object-visit: 1.0.1dev: true/markdown-escapes/1.0.4:resolution: {integrity: sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==}dev: true/markdown-it/7.0.1:resolution: {integrity: sha1-8S2LiKk+ZCVDSN/Rg71wv2BWekI=}hasBin: truedependencies:argparse: 1.0.10entities: 1.1.2linkify-it: 2.2.0mdurl: 1.0.1uc.micro: 1.0.6dev: true/markdown-table/1.1.3:resolution: {integrity: sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==}dev: true/matchit/1.1.0:resolution: {integrity: sha512-+nGYoOlfHmxe5BW5tE0EMJppXEwdSf8uBA1GTZC7Q77kbT35+VKLYJMzVNWCHSsga1ps1tPYFtFyvxvKzWVmMA==}engines: {node: '>=6'}dependencies:'@arr/every': 1.0.1dev: falseoptional: true/mathml-tag-names/2.1.3:resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}dev: true/mdast-util-compact/1.0.4:resolution: {integrity: sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==}dependencies:unist-util-visit: 1.4.1dev: true/mdurl/1.0.1:resolution: {integrity: sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=}dev: true/meow/5.0.0:resolution: {integrity: sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==}engines: {node: '>=6'}dependencies:camelcase-keys: 4.2.0decamelize-keys: 1.1.0loud-rejection: 1.6.0minimist-options: 3.0.2normalize-package-data: 2.5.0read-pkg-up: 3.0.0redent: 2.0.0trim-newlines: 2.0.0yargs-parser: 10.1.0dev: true/meow/9.0.0:resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}engines: {node: '>=10'}dependencies:'@types/minimist': 1.2.2camelcase-keys: 6.2.2decamelize: 1.2.0decamelize-keys: 1.1.0hard-rejection: 2.1.0minimist-options: 4.1.0normalize-package-data: 3.0.2read-pkg-up: 7.0.1redent: 3.0.0trim-newlines: 3.0.1type-fest: 0.18.1yargs-parser: 20.2.9dev: true/merge-stream/2.0.0:resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}dev: true/merge2/1.4.1:resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}engines: {node: '>= 8'}dev: true/micromatch/3.1.10:resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==}engines: {node: '>=0.10.0'}dependencies:arr-diff: 4.0.0array-unique: 0.3.2braces: 2.3.2define-property: 2.0.2extend-shallow: 3.0.2extglob: 2.0.4fragment-cache: 0.2.1kind-of: 6.0.3nanomatch: 1.2.13object.pick: 1.3.0regex-not: 1.0.2snapdragon: 0.8.2to-regex: 3.0.2dev: true/micromatch/4.0.4:resolution: {integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==}engines: {node: '>=8.6'}dependencies:braces: 3.0.2picomatch: 2.3.0dev: true/mime-db/1.49.0:resolution: {integrity: sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==}engines: {node: '>= 0.6'}dev: true/mime-types/2.1.32:resolution: {integrity: sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==}engines: {node: '>= 0.6'}dependencies:mime-db: 1.49.0dev: true/mime/2.5.2:resolution: {integrity: sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==}engines: {node: '>=4.0.0'}hasBin: truedev: falseoptional: true/mimic-fn/1.2.0:resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==}engines: {node: '>=4'}dev: true/mimic-fn/2.1.0:resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}engines: {node: '>=6'}dev: true/min-indent/1.0.1:resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}engines: {node: '>=4'}dev: true/minimatch/3.0.4:resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}dependencies:brace-expansion: 1.1.11dev: true/minimist-options/3.0.2:resolution: {integrity: sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==}engines: {node: '>= 4'}dependencies:arrify: 1.0.1is-plain-obj: 1.1.0dev: true/minimist-options/4.1.0:resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}engines: {node: '>= 6'}dependencies:arrify: 1.0.1is-plain-obj: 1.1.0kind-of: 6.0.3dev: true/minimist/1.2.5:resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==}dev: true/minipass/3.1.3:resolution: {integrity: sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==}engines: {node: '>=8'}dependencies:yallist: 4.0.0dev: true/minizlib/2.1.2:resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}engines: {node: '>= 8'}dependencies:minipass: 3.1.3yallist: 4.0.0dev: true/mixin-deep/1.3.2:resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}engines: {node: '>=0.10.0'}dependencies:for-in: 1.0.2is-extendable: 1.0.1dev: true/mkdirp/0.5.4:resolution: {integrity: sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==}deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)hasBin: truedependencies:minimist: 1.2.5dev: true/mkdirp/0.5.5:resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==}hasBin: truedependencies:minimist: 1.2.5dev: true/mkdirp/1.0.4:resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}engines: {node: '>=10'}hasBin: truedev: true/mocha/6.2.3:resolution: {integrity: sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==}engines: {node: '>= 6.0.0'}hasBin: truedependencies:ansi-colors: 3.2.3browser-stdout: 1.3.1debug: 3.2.6diff: 3.5.0escape-string-regexp: 1.0.5find-up: 3.0.0glob: 7.1.3growl: 1.10.5he: 1.2.0js-yaml: 3.13.1log-symbols: 2.2.0minimatch: 3.0.4mkdirp: 0.5.4ms: 2.1.1node-environment-flags: 1.0.5object.assign: 4.1.0strip-json-comments: 2.0.1supports-color: 6.0.0which: 1.3.1wide-align: 1.1.3yargs: 13.3.2yargs-parser: 13.1.2yargs-unparser: 1.6.0dev: true/mocha/8.4.0:resolution: {integrity: sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==}engines: {node: '>= 10.12.0'}hasBin: truedependencies:'@ungap/promise-all-settled': 1.1.2ansi-colors: 4.1.1browser-stdout: 1.3.1chokidar: 3.5.1debug: 4.3.1_supports-color@8.1.1diff: 5.0.0escape-string-regexp: 4.0.0find-up: 5.0.0glob: 7.1.6growl: 1.10.5he: 1.2.0js-yaml: 4.0.0log-symbols: 4.0.0minimatch: 3.0.4ms: 2.1.3nanoid: 3.1.20serialize-javascript: 5.0.1strip-json-comments: 3.1.1supports-color: 8.1.1which: 2.0.2wide-align: 1.1.3workerpool: 6.1.0yargs: 16.2.0yargs-parser: 20.2.4yargs-unparser: 2.0.0dev: true/moment/2.29.1:resolution: {integrity: sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==}dev: true/mri/1.1.6:resolution: {integrity: sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==}engines: {node: '>=4'}dev: true/ms/2.0.0:resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=}dev: true/ms/2.1.1:resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==}dev: true/ms/2.1.2:resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}dev: true/ms/2.1.3:resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}dev: true/mute-stream/0.0.8:resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}dev: true/nan/2.15.0:resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==}dev: true/nanoid/3.1.20:resolution: {integrity: sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==}engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}hasBin: truedev: true/nanoid/3.1.23:resolution: {integrity: sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==}engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}hasBin: truedev: true/nanomatch/1.2.13:resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}engines: {node: '>=0.10.0'}dependencies:arr-diff: 4.0.0array-unique: 0.3.2define-property: 2.0.2extend-shallow: 3.0.2fragment-cache: 0.2.1is-windows: 1.0.2kind-of: 6.0.3object.pick: 1.3.0regex-not: 1.0.2snapdragon: 0.8.2to-regex: 3.0.2dev: true/natural-compare/1.4.0:resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=}dev: true/nice-try/1.0.5:resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}dev: true/nise/1.5.3:resolution: {integrity: sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==}dependencies:'@sinonjs/formatio': 3.2.2'@sinonjs/text-encoding': 0.7.1just-extend: 4.2.1lolex: 5.1.2path-to-regexp: 1.8.0dev: true/nise/4.1.0:resolution: {integrity: sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==}dependencies:'@sinonjs/commons': 1.8.3'@sinonjs/fake-timers': 6.0.1'@sinonjs/text-encoding': 0.7.1just-extend: 4.2.1path-to-regexp: 1.8.0dev: true/node-environment-flags/1.0.5:resolution: {integrity: sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==}dependencies:object.getownpropertydescriptors: 2.1.2semver: 5.7.1dev: true/node-gyp/7.1.2:resolution: {integrity: sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==}engines: {node: '>= 10.12.0'}hasBin: truedependencies:env-paths: 2.2.1glob: 7.1.7graceful-fs: 4.2.8nopt: 5.0.0npmlog: 4.1.2request: 2.88.2rimraf: 3.0.2semver: 7.3.5tar: 6.1.7which: 2.0.2dev: true/node-releases/1.1.73:resolution: {integrity: sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==}dev: true/node-sass/6.0.1:resolution: {integrity: sha512-f+Rbqt92Ful9gX0cGtdYwjTrWAaGURgaK5rZCWOgCNyGWusFYHhbqCCBoFBeat+HKETOU02AyTxNhJV0YZf2jQ==}engines: {node: '>=12'}hasBin: truerequiresBuild: truedependencies:async-foreach: 0.1.3chalk: 1.1.3cross-spawn: 7.0.3gaze: 1.1.3get-stdin: 4.0.1glob: 7.1.7lodash: 4.17.21meow: 9.0.0nan: 2.15.0node-gyp: 7.1.2npmlog: 4.1.2request: 2.88.2sass-graph: 2.2.5stdout-stream: 1.4.1true-case-path: 1.0.3dev: true/node-watch/0.6.4:resolution: {integrity: sha512-cI6CHzivIFESe8djiK3Wh90CtWQBxLwMem8x8S+2GSvCvFgoMuOKVlfJtQ/2v3Afg3wOnHl/+tXotEs8z5vOrg==}engines: {node: '>=6'}dev: true/nopt/5.0.0:resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}engines: {node: '>=6'}hasBin: truedependencies:abbrev: 1.1.1dev: true/normalize-package-data/2.5.0:resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}dependencies:hosted-git-info: 2.8.9resolve: 1.20.0semver: 5.7.1validate-npm-package-license: 3.0.4/normalize-package-data/3.0.2:resolution: {integrity: sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==}engines: {node: '>=10'}dependencies:hosted-git-info: 4.0.2resolve: 1.20.0semver: 7.3.5validate-npm-package-license: 3.0.4dev: true/normalize-path/3.0.0:resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}engines: {node: '>=0.10.0'}/normalize-range/0.1.2:resolution: {integrity: sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=}engines: {node: '>=0.10.0'}dev: true/normalize-selector/0.2.0:resolution: {integrity: sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=}dev: true/npm-run-path/2.0.2:resolution: {integrity: sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=}engines: {node: '>=4'}dependencies:path-key: 2.0.1dev: true/npm-run-path/3.1.0:resolution: {integrity: sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==}engines: {node: '>=8'}dependencies:path-key: 3.1.1dev: true/npmlog/4.1.2:resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==}dependencies:are-we-there-yet: 1.1.5console-control-strings: 1.1.0gauge: 2.7.4set-blocking: 2.0.0dev: true/num2fraction/1.2.2:resolution: {integrity: sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=}dev: true/number-is-nan/1.0.1:resolution: {integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=}engines: {node: '>=0.10.0'}dev: true/oauth-sign/0.9.0:resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}dev: true/object-assign/4.1.1:resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}engines: {node: '>=0.10.0'}dev: true/object-copy/0.1.0:resolution: {integrity: sha1-fn2Fi3gb18mRpBupde04EnVOmYw=}engines: {node: '>=0.10.0'}dependencies:copy-descriptor: 0.1.1define-property: 0.2.5kind-of: 3.2.2dev: true/object-inspect/1.11.0:resolution: {integrity: sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==}dev: true/object-keys/1.1.1:resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}engines: {node: '>= 0.4'}dev: true/object-visit/1.0.1:resolution: {integrity: sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=}engines: {node: '>=0.10.0'}dependencies:isobject: 3.0.1dev: true/object.assign/4.1.0:resolution: {integrity: sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==}engines: {node: '>= 0.4'}dependencies:define-properties: 1.1.3function-bind: 1.1.1has-symbols: 1.0.2object-keys: 1.1.1dev: true/object.assign/4.1.2:resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==}engines: {node: '>= 0.4'}dependencies:call-bind: 1.0.2define-properties: 1.1.3has-symbols: 1.0.2object-keys: 1.1.1dev: true/object.getownpropertydescriptors/2.1.2:resolution: {integrity: sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==}engines: {node: '>= 0.8'}dependencies:call-bind: 1.0.2define-properties: 1.1.3es-abstract: 1.18.5dev: true/object.pick/1.3.0:resolution: {integrity: sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=}engines: {node: '>=0.10.0'}dependencies:isobject: 3.0.1dev: true/once/1.4.0:resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}dependencies:wrappy: 1.0.2dev: true/onetime/2.0.1:resolution: {integrity: sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=}engines: {node: '>=4'}dependencies:mimic-fn: 1.2.0dev: true/onetime/5.1.2:resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}engines: {node: '>=6'}dependencies:mimic-fn: 2.1.0dev: true/opencollective-postinstall/2.0.3:resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==}hasBin: truedev: true/optionator/0.8.3:resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}engines: {node: '>= 0.8.0'}dependencies:deep-is: 0.1.3fast-levenshtein: 2.0.6levn: 0.3.0prelude-ls: 1.1.2type-check: 0.3.2word-wrap: 1.2.3dev: true/optionator/0.9.1:resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}engines: {node: '>= 0.8.0'}dependencies:deep-is: 0.1.3fast-levenshtein: 2.0.6levn: 0.4.1prelude-ls: 1.2.1type-check: 0.4.0word-wrap: 1.2.3dev: true/os-tmpdir/1.0.2:resolution: {integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=}engines: {node: '>=0.10.0'}dev: true/p-finally/1.0.0:resolution: {integrity: sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=}engines: {node: '>=4'}dev: true/p-finally/2.0.1:resolution: {integrity: sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==}engines: {node: '>=8'}dev: true/p-limit/1.3.0:resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==}engines: {node: '>=4'}dependencies:p-try: 1.0.0dev: true/p-limit/2.3.0:resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}engines: {node: '>=6'}dependencies:p-try: 2.2.0/p-limit/3.1.0:resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}engines: {node: '>=10'}dependencies:yocto-queue: 0.1.0dev: true/p-locate/2.0.0:resolution: {integrity: sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=}engines: {node: '>=4'}dependencies:p-limit: 1.3.0dev: true/p-locate/3.0.0:resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}engines: {node: '>=6'}dependencies:p-limit: 2.3.0/p-locate/4.1.0:resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}engines: {node: '>=8'}dependencies:p-limit: 2.3.0dev: true/p-locate/5.0.0:resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}engines: {node: '>=10'}dependencies:p-limit: 3.1.0dev: true/p-map/2.1.0:resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==}engines: {node: '>=6'}dev: true/p-map/3.0.0:resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==}engines: {node: '>=8'}dependencies:aggregate-error: 3.1.0dev: true/p-try/1.0.0:resolution: {integrity: sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=}engines: {node: '>=4'}dev: true/p-try/2.2.0:resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}engines: {node: '>=6'}/pad-start/1.0.2:resolution: {integrity: sha1-I+W6s+lkRrYoFs/28VCXXwQNGxQ=}/parent-module/1.0.1:resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}engines: {node: '>=6'}dependencies:callsites: 3.1.0dev: true/parse-entities/1.2.2:resolution: {integrity: sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==}dependencies:character-entities: 1.2.4character-entities-legacy: 1.1.4character-reference-invalid: 1.1.4is-alphanumerical: 1.0.4is-decimal: 1.0.4is-hexadecimal: 1.0.4dev: true/parse-json/4.0.0:resolution: {integrity: sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=}engines: {node: '>=4'}dependencies:error-ex: 1.3.2json-parse-better-errors: 1.0.2/parse-json/5.2.0:resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}engines: {node: '>=8'}dependencies:'@babel/code-frame': 7.14.5error-ex: 1.3.2json-parse-even-better-errors: 2.3.1lines-and-columns: 1.1.6dev: true/pascalcase/0.1.1:resolution: {integrity: sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=}engines: {node: '>=0.10.0'}dev: true/path-dirname/1.0.2:resolution: {integrity: sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=}dev: true/path-exists/3.0.0:resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=}engines: {node: '>=4'}/path-exists/4.0.0:resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}engines: {node: '>=8'}dev: true/path-is-absolute/1.0.1:resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}engines: {node: '>=0.10.0'}dev: true/path-key/2.0.1:resolution: {integrity: sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=}engines: {node: '>=4'}dev: true/path-key/3.1.1:resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}engines: {node: '>=8'}dev: true/path-parse/1.0.7:resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}/path-to-regexp/1.8.0:resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==}dependencies:isarray: 0.0.1dev: true/path-type/3.0.0:resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}engines: {node: '>=4'}dependencies:pify: 3.0.0dev: true/path-type/4.0.0:resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}engines: {node: '>=8'}dev: true/pathval/1.1.1:resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}dev: true/performance-now/2.1.0:resolution: {integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=}dev: true/picomatch/2.3.0:resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}engines: {node: '>=8.6'}/pify/3.0.0:resolution: {integrity: sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=}engines: {node: '>=4'}/pify/4.0.1:resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}engines: {node: '>=6'}dev: true/pkg-dir/4.2.0:resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}engines: {node: '>=8'}dependencies:find-up: 4.1.0dev: true/platform/1.3.6:resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}dev: true/please-upgrade-node/3.2.0:resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==}dependencies:semver-compare: 1.0.0dev: true/polka/0.5.2:resolution: {integrity: sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==}dependencies:'@polka/url': 0.5.0trouter: 2.0.1dev: falseoptional: true/posix-character-classes/0.1.1:resolution: {integrity: sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=}engines: {node: '>=0.10.0'}dev: true/postcss-html/0.36.0_2b33a41d320e3e2012e5b3b0fadc703b:resolution: {integrity: sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==}peerDependencies:postcss: '>=5.0.0'postcss-syntax: '>=0.36.0'dependencies:htmlparser2: 3.10.1postcss: 7.0.36postcss-syntax: 0.36.2_postcss@7.0.36dev: true/postcss-jsx/0.36.4_2b33a41d320e3e2012e5b3b0fadc703b:resolution: {integrity: sha512-jwO/7qWUvYuWYnpOb0+4bIIgJt7003pgU3P6nETBLaOyBXuTD55ho21xnals5nBrlpTIFodyd3/jBi6UO3dHvA==}peerDependencies:postcss: '>=5.0.0'postcss-syntax: '>=0.36.0'dependencies:'@babel/core': 7.15.0postcss: 7.0.36postcss-syntax: 0.36.2_postcss@7.0.36transitivePeerDependencies:- supports-colordev: true/postcss-less/3.1.4:resolution: {integrity: sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==}engines: {node: '>=6.14.4'}dependencies:postcss: 7.0.36dev: true/postcss-markdown/0.36.0_2b33a41d320e3e2012e5b3b0fadc703b:resolution: {integrity: sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==}peerDependencies:postcss: '>=5.0.0'postcss-syntax: '>=0.36.0'dependencies:postcss: 7.0.36postcss-syntax: 0.36.2_postcss@7.0.36remark: 10.0.1unist-util-find-all-after: 1.0.5dev: true/postcss-media-query-parser/0.2.3:resolution: {integrity: sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=}dev: true/postcss-reporter/6.0.1:resolution: {integrity: sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==}engines: {node: '>=6'}dependencies:chalk: 2.4.2lodash: 4.17.21log-symbols: 2.2.0postcss: 7.0.36dev: true/postcss-resolve-nested-selector/0.1.1:resolution: {integrity: sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=}dev: true/postcss-safe-parser/4.0.2:resolution: {integrity: sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==}engines: {node: '>=6.0.0'}dependencies:postcss: 7.0.36dev: true/postcss-sass/0.3.5:resolution: {integrity: sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A==}dependencies:gonzales-pe: 4.3.0postcss: 7.0.36dev: true/postcss-sass/0.4.4:resolution: {integrity: sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==}dependencies:gonzales-pe: 4.3.0postcss: 7.0.36dev: true/postcss-scss/2.1.1:resolution: {integrity: sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==}engines: {node: '>=6.0.0'}dependencies:postcss: 7.0.36dev: true/postcss-selector-parser/3.1.2:resolution: {integrity: sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==}engines: {node: '>=8'}dependencies:dot-prop: 5.3.0indexes-of: 1.0.1uniq: 1.0.1dev: true/postcss-selector-parser/6.0.6:resolution: {integrity: sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==}engines: {node: '>=4'}dependencies:cssesc: 3.0.0util-deprecate: 1.0.2dev: true/postcss-sorting/4.1.0:resolution: {integrity: sha512-r4T2oQd1giURJdHQ/RMb72dKZCuLOdWx2B/XhXN1Y1ZdnwXsKH896Qz6vD4tFy9xSjpKNYhlZoJmWyhH/7JUQw==}engines: {node: '>=6.14.3'}dependencies:lodash: 4.17.21postcss: 7.0.36dev: true/postcss-sorting/5.0.1:resolution: {integrity: sha512-Y9fUFkIhfrm6i0Ta3n+89j56EFqaNRdUKqXyRp6kvTcSXnmgEjaVowCXH+JBe9+YKWqd4nc28r2sgwnzJalccA==}engines: {node: '>=8.7.0'}dependencies:lodash: 4.17.21postcss: 7.0.36dev: true/postcss-syntax/0.36.2_postcss@7.0.36:resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==}peerDependencies:postcss: '>=5.0.0'dependencies:postcss: 7.0.36dev: true/postcss-value-parser/3.3.1:resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==}dev: true/postcss-value-parser/4.1.0:resolution: {integrity: sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==}dev: true/postcss/7.0.36:resolution: {integrity: sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==}engines: {node: '>=6.0.0'}dependencies:chalk: 2.4.2source-map: 0.6.1supports-color: 6.1.0dev: true/postcss/8.3.6:resolution: {integrity: sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==}engines: {node: ^10 || ^12 || >=14}dependencies:colorette: 1.2.2nanoid: 3.1.23source-map-js: 0.6.2dev: true/prelude-ls/1.1.2:resolution: {integrity: sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=}engines: {node: '>= 0.8.0'}dev: true/prelude-ls/1.2.1:resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}engines: {node: '>= 0.8.0'}dev: true/prettier-linter-helpers/1.0.0:resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}engines: {node: '>=6.0.0'}dependencies:fast-diff: 1.2.0dev: true/prettier-plugin-svelte/2.3.1_prettier@2.2.1+svelte@3.42.1:resolution: {integrity: sha512-F1/r6OYoBq8Zgurhs1MN25tdrhPw0JW5JjioPRqpxbYdmrZ3gY/DzHGs0B6zwd4DLyRsfGB2gqhxUCbHt/D1fw==}peerDependencies:prettier: ^1.16.4 || ^2.0.0svelte: ^3.2.0dependencies:prettier: 2.2.1svelte: 3.42.1dev: true/prettier/1.19.1:resolution: {integrity: sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==}engines: {node: '>=4'}hasBin: truedev: true/prettier/2.2.1:resolution: {integrity: sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==}engines: {node: '>=10.13.0'}hasBin: truedev: true/process-nextick-args/2.0.1:resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}dev: true/progress/2.0.3:resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}engines: {node: '>=0.4.0'}dev: true/proto-list/1.2.4:resolution: {integrity: sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=}dev: true/pseudomap/1.0.2:resolution: {integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM=}dev: true/psl/1.8.0:resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==}dev: true/pump/3.0.0:resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}dependencies:end-of-stream: 1.4.4once: 1.4.0dev: true/punycode/2.1.1:resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}engines: {node: '>=6'}dev: true/qs/6.5.2:resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==}engines: {node: '>=0.6'}dev: true/query-string/4.3.2:resolution: {integrity: sha1-7A/XZfWKUAMaOWjCQxOG+JR6XN0=}engines: {node: '>=0.10.0'}dependencies:object-assign: 4.1.1strict-uri-encode: 1.1.0dev: true/queue-microtask/1.2.3:resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}dev: true/quick-lru/1.1.0:resolution: {integrity: sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=}engines: {node: '>=4'}dev: true/quick-lru/4.0.1:resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==}engines: {node: '>=8'}dev: true/randombytes/2.1.0:resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}dependencies:safe-buffer: 5.2.1dev: true/read-pkg-up/3.0.0:resolution: {integrity: sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=}engines: {node: '>=4'}dependencies:find-up: 2.1.0read-pkg: 3.0.0dev: true/read-pkg-up/7.0.1:resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}engines: {node: '>=8'}dependencies:find-up: 4.1.0read-pkg: 5.2.0type-fest: 0.8.1dev: true/read-pkg/3.0.0:resolution: {integrity: sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=}engines: {node: '>=4'}dependencies:load-json-file: 4.0.0normalize-package-data: 2.5.0path-type: 3.0.0dev: true/read-pkg/4.0.1:resolution: {integrity: sha1-ljYlN48+HE1IyFhytabsfV0JMjc=}engines: {node: '>=6'}dependencies:normalize-package-data: 2.5.0parse-json: 4.0.0pify: 3.0.0dev: falseoptional: true/read-pkg/5.2.0:resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}engines: {node: '>=8'}dependencies:'@types/normalize-package-data': 2.4.1normalize-package-data: 2.5.0parse-json: 5.2.0type-fest: 0.6.0dev: true/readable-stream/2.3.7:resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}dependencies:core-util-is: 1.0.2inherits: 2.0.4isarray: 1.0.0process-nextick-args: 2.0.1safe-buffer: 5.1.2string_decoder: 1.1.1util-deprecate: 1.0.2dev: true/readable-stream/3.6.0:resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}engines: {node: '>= 6'}dependencies:inherits: 2.0.4string_decoder: 1.3.0util-deprecate: 1.0.2dev: true/readdirp/3.5.0:resolution: {integrity: sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==}engines: {node: '>=8.10.0'}dependencies:picomatch: 2.3.0dev: true/readdirp/3.6.0:resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}engines: {node: '>=8.10.0'}dependencies:picomatch: 2.3.0dev: false/redent/2.0.0:resolution: {integrity: sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=}engines: {node: '>=4'}dependencies:indent-string: 3.2.0strip-indent: 2.0.0dev: true/redent/3.0.0:resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}engines: {node: '>=8'}dependencies:indent-string: 4.0.0strip-indent: 3.0.0dev: true/regex-not/1.0.2:resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}engines: {node: '>=0.10.0'}dependencies:extend-shallow: 3.0.2safe-regex: 1.1.0dev: true/regexp-tree/0.1.23:resolution: {integrity: sha512-+7HWfb4Bvu8Rs2eQTUIpX9I/PlQkYOuTNbRpKLJlQpSgwSkzFYh+pUj0gtvglnOZLKB6YgnIgRuJ2/IlpL48qw==}hasBin: truedev: true/regexpp/2.0.1:resolution: {integrity: sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==}engines: {node: '>=6.5.0'}dev: true/regexpp/3.2.0:resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}engines: {node: '>=8'}dev: true/remark-parse/6.0.3:resolution: {integrity: sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==}dependencies:collapse-white-space: 1.0.6is-alphabetical: 1.0.4is-decimal: 1.0.4is-whitespace-character: 1.0.4is-word-character: 1.0.4markdown-escapes: 1.0.4parse-entities: 1.2.2repeat-string: 1.6.1state-toggle: 1.0.3trim: 0.0.1trim-trailing-lines: 1.1.4unherit: 1.1.3unist-util-remove-position: 1.1.4vfile-location: 2.0.6xtend: 4.0.2dev: true/remark-stringify/6.0.4:resolution: {integrity: sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==}dependencies:ccount: 1.1.0is-alphanumeric: 1.0.0is-decimal: 1.0.4is-whitespace-character: 1.0.4longest-streak: 2.0.4markdown-escapes: 1.0.4markdown-table: 1.1.3mdast-util-compact: 1.0.4parse-entities: 1.2.2repeat-string: 1.6.1state-toggle: 1.0.3stringify-entities: 1.3.2unherit: 1.1.3xtend: 4.0.2dev: true/remark/10.0.1:resolution: {integrity: sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==}dependencies:remark-parse: 6.0.3remark-stringify: 6.0.4unified: 7.1.0dev: true/repeat-element/1.1.4:resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==}engines: {node: '>=0.10.0'}dev: true/repeat-string/1.6.1:resolution: {integrity: sha1-jcrkcOHIirwtYA//Sndihtp15jc=}engines: {node: '>=0.10'}dev: true/replace-ext/1.0.0:resolution: {integrity: sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=}engines: {node: '>= 0.10'}dev: true/request/2.88.2:resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}engines: {node: '>= 6'}deprecated: request has been deprecated, see https://github.com/request/request/issues/3142dependencies:aws-sign2: 0.7.0aws4: 1.11.0caseless: 0.12.0combined-stream: 1.0.8extend: 3.0.2forever-agent: 0.6.1form-data: 2.3.3har-validator: 5.1.5http-signature: 1.2.0is-typedarray: 1.0.0isstream: 0.1.2json-stringify-safe: 5.0.1mime-types: 2.1.32oauth-sign: 0.9.0performance-now: 2.1.0qs: 6.5.2safe-buffer: 5.2.1tough-cookie: 2.5.0tunnel-agent: 0.6.0uuid: 3.4.0dev: true/require-directory/2.1.1:resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=}engines: {node: '>=0.10.0'}/require-from-string/2.0.2:resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}engines: {node: '>=0.10.0'}dev: true/require-main-filename/2.0.0:resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}/require-relative/0.8.7:resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=}dev: true/resolve-from/3.0.0:resolution: {integrity: sha1-six699nWiBvItuZTM17rywoYh0g=}engines: {node: '>=4'}dev: true/resolve-from/4.0.0:resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}engines: {node: '>=4'}dev: true/resolve-from/5.0.0:resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}engines: {node: '>=8'}dev: true/resolve-url/0.2.1:resolution: {integrity: sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=}deprecated: https://github.com/lydell/resolve-url#deprecateddev: true/resolve/1.20.0:resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}dependencies:is-core-module: 2.5.0path-parse: 1.0.7/restore-cursor/2.0.0:resolution: {integrity: sha1-n37ih/gv0ybU/RYpI9YhKe7g368=}engines: {node: '>=4'}dependencies:onetime: 2.0.1signal-exit: 3.0.3dev: true/restore-cursor/3.1.0:resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}engines: {node: '>=8'}dependencies:onetime: 5.1.2signal-exit: 3.0.3dev: true/ret/0.1.15:resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}engines: {node: '>=0.12'}dev: true/reusify/1.0.4:resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}engines: {iojs: '>=1.0.0', node: '>=0.10.0'}dev: true/rimraf/2.6.3:resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==}hasBin: truedependencies:glob: 7.1.7dev: true/rimraf/3.0.2:resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}hasBin: truedependencies:glob: 7.1.7dev: true/rollup/2.56.1:resolution: {integrity: sha512-KkrsNjeiTfGJMUFBi/PNfj3fnt70akqdoNXOjlzwo98uA1qrlkmgt6SGaK5OwhyDYCVnJb6jb2Xa2wbI47P4Nw==}engines: {node: '>=10.0.0'}hasBin: trueoptionalDependencies:fsevents: 2.3.2dev: true/run-async/2.4.1:resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}engines: {node: '>=0.12.0'}dev: true/run-node/1.0.0:resolution: {integrity: sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==}engines: {node: '>=4'}hasBin: truedev: true/run-parallel/1.2.0:resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}dependencies:queue-microtask: 1.2.3dev: true/rxjs/6.6.7:resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==}engines: {npm: '>=2.0.0'}dependencies:tslib: 1.14.1/sade/1.7.4:resolution: {integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==}engines: {node: '>= 6'}dependencies:mri: 1.1.6dev: true/safe-buffer/5.1.2:resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}dev: true/safe-buffer/5.2.1:resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}dev: true/safe-regex/1.1.0:resolution: {integrity: sha1-QKNmnzsHfR6UPURinhV91IAjvy4=}dependencies:ret: 0.1.15dev: true/safer-buffer/2.1.2:resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}dev: true/sanitize.css/12.0.1:resolution: {integrity: sha512-QbusSBnWHaRBZeTxsJyknwI0q+q6m1NtLBmB76JfW/rdVN7Ws6Zz70w65+430/ouVcdNVT3qwrDgrM6PaYyRtw==}dev: false/sass-graph/2.2.5:resolution: {integrity: sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==}hasBin: truedependencies:glob: 7.1.7lodash: 4.17.21scss-tokenizer: 0.2.3yargs: 13.3.2dev: true/sass/1.37.5:resolution: {integrity: sha512-Cx3ewxz9QB/ErnVIiWg2cH0kiYZ0FPvheDTVC6BsiEGBTZKKZJ1Gq5Kq6jy3PKtL6+EJ8NIoaBW/RSd2R6cZOA==}engines: {node: '>=8.9.0'}hasBin: truedependencies:chokidar: 3.5.2dev: false/scss-tokenizer/0.2.3:resolution: {integrity: sha1-jrBtualyMzOCTT9VMGQRSYR85dE=}dependencies:js-base64: 2.6.4source-map: 0.4.4dev: true/secure-remote-password/0.3.1:resolution: {integrity: sha512-iEp/qLRfb9XYhfKFrPFfdeD7KVreCjhDKSTRP1G1nRIO0Sw1hjnVHD58ymOhiy9Zf5quHbDIbG9cTupji7qwnA==}dependencies:array-buffer-to-hex: 1.0.0crypto-digest-sync: 1.0.0crypto-random-hex: 1.0.0encode-utf8: 1.0.3hex-to-array-buffer: 1.1.0jsbn: 1.1.0pad-start: 1.0.2/semver-compare/1.0.0:resolution: {integrity: sha1-De4hahyUGrN+nvsXiPavxf9VN/w=}dev: true/semver/5.7.1:resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}hasBin: true/semver/6.3.0:resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}hasBin: truedev: true/semver/7.3.5:resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}engines: {node: '>=10'}hasBin: truedependencies:lru-cache: 6.0.0dev: true/serialize-javascript/5.0.1:resolution: {integrity: sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==}dependencies:randombytes: 2.1.0dev: true/set-blocking/2.0.0:resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=}/set-value/2.0.1:resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}engines: {node: '>=0.10.0'}dependencies:extend-shallow: 2.0.1is-extendable: 0.1.1is-plain-object: 2.0.4split-string: 3.1.0dev: true/shebang-command/1.2.0:resolution: {integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=}engines: {node: '>=0.10.0'}dependencies:shebang-regex: 1.0.0dev: true/shebang-command/2.0.0:resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}engines: {node: '>=8'}dependencies:shebang-regex: 3.0.0dev: true/shebang-regex/1.0.0:resolution: {integrity: sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=}engines: {node: '>=0.10.0'}dev: true/shebang-regex/3.0.0:resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}engines: {node: '>=8'}dev: true/should-equal/1.0.1:resolution: {integrity: sha1-C26VFvJgGp+wuy3MNpr6HH4gCvc=}dependencies:should-type: 1.4.0dev: true/should-format/3.0.3:resolution: {integrity: sha1-m/yPdPo5IFxT04w01xcwPidxJPE=}dependencies:should-type: 1.4.0should-type-adaptors: 1.1.0dev: true/should-type-adaptors/1.1.0:resolution: {integrity: sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==}dependencies:should-type: 1.4.0should-util: 1.0.1dev: true/should-type/1.4.0:resolution: {integrity: sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=}dev: true/should-util/1.0.1:resolution: {integrity: sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==}dev: true/should/11.2.1:resolution: {integrity: sha1-kPVRRVUtAc/CAGZuToGKHJZw7aI=}dependencies:should-equal: 1.0.1should-format: 3.0.3should-type: 1.4.0should-type-adaptors: 1.1.0should-util: 1.0.1dev: true/side-channel/1.0.4:resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}dependencies:call-bind: 1.0.2get-intrinsic: 1.1.1object-inspect: 1.11.0dev: true/sigmund/1.0.1:resolution: {integrity: sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=}dev: true/signal-exit/3.0.3:resolution: {integrity: sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==}dev: true/sinon-chai/3.7.0_chai@4.3.4+sinon@7.5.0:resolution: {integrity: sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==}peerDependencies:chai: ^4.0.0sinon: '>=4.0.0'dependencies:chai: 4.3.4sinon: 7.5.0dev: true/sinon-chai/3.7.0_chai@4.3.4+sinon@9.2.4:resolution: {integrity: sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==}peerDependencies:chai: ^4.0.0sinon: '>=4.0.0'dependencies:chai: 4.3.4sinon: 9.2.4dev: true/sinon/7.5.0:resolution: {integrity: sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==}dependencies:'@sinonjs/commons': 1.8.3'@sinonjs/formatio': 3.2.2'@sinonjs/samsam': 3.3.3diff: 3.5.0lolex: 4.2.0nise: 1.5.3supports-color: 5.5.0dev: true/sinon/9.2.4:resolution: {integrity: sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==}dependencies:'@sinonjs/commons': 1.8.3'@sinonjs/fake-timers': 6.0.1'@sinonjs/samsam': 5.3.1diff: 4.0.2nise: 4.1.0supports-color: 7.2.0dev: true/sirv/0.4.6:resolution: {integrity: sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ==}engines: {node: '>= 6'}dependencies:'@polka/url': 0.5.0mime: 2.5.2dev: falseoptional: true/slash/2.0.0:resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==}engines: {node: '>=6'}dev: true/slash/3.0.0:resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}engines: {node: '>=8'}dev: true/slice-ansi/0.0.4:resolution: {integrity: sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=}engines: {node: '>=0.10.0'}dev: true/slice-ansi/2.1.0:resolution: {integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==}engines: {node: '>=6'}dependencies:ansi-styles: 3.2.1astral-regex: 1.0.0is-fullwidth-code-point: 2.0.0dev: true/slice-ansi/4.0.0:resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}engines: {node: '>=10'}dependencies:ansi-styles: 4.3.0astral-regex: 2.0.0is-fullwidth-code-point: 3.0.0dev: true/snapdragon-node/2.1.1:resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==}engines: {node: '>=0.10.0'}dependencies:define-property: 1.0.0isobject: 3.0.1snapdragon-util: 3.0.1dev: true/snapdragon-util/3.0.1:resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==}engines: {node: '>=0.10.0'}dependencies:kind-of: 3.2.2dev: true/snapdragon/0.8.2:resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}engines: {node: '>=0.10.0'}dependencies:base: 0.11.2debug: 2.6.9define-property: 0.2.5extend-shallow: 2.0.1map-cache: 0.2.2source-map: 0.5.7source-map-resolve: 0.5.3use: 3.1.1dev: true/source-map-js/0.6.2:resolution: {integrity: sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==}engines: {node: '>=0.10.0'}dev: true/source-map-resolve/0.5.3:resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==}dependencies:atob: 2.1.2decode-uri-component: 0.2.0resolve-url: 0.2.1source-map-url: 0.4.1urix: 0.1.0dev: true/source-map-url/0.4.1:resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==}dev: true/source-map/0.4.4:resolution: {integrity: sha1-66T12pwNyZneaAMti092FzZSA2s=}engines: {node: '>=0.8.0'}dependencies:amdefine: 1.0.1dev: true/source-map/0.5.7:resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=}engines: {node: '>=0.10.0'}dev: true/source-map/0.6.1:resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}engines: {node: '>=0.10.0'}dev: true/sourcemap-codec/1.4.8:resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}dev: true/spawn-command/0.0.2-1:resolution: {integrity: sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=}dev: falseoptional: true/spdx-correct/3.1.1:resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}dependencies:spdx-expression-parse: 3.0.1spdx-license-ids: 3.0.10/spdx-exceptions/2.3.0:resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==}/spdx-expression-parse/3.0.1:resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}dependencies:spdx-exceptions: 2.3.0spdx-license-ids: 3.0.10/spdx-license-ids/3.0.10:resolution: {integrity: sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==}/specificity/0.4.1:resolution: {integrity: sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==}hasBin: truedev: true/split-string/3.1.0:resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==}engines: {node: '>=0.10.0'}dependencies:extend-shallow: 3.0.2dev: true/sprintf-js/1.0.3:resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=}dev: true/sshpk/1.16.1:resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==}engines: {node: '>=0.10.0'}hasBin: truedependencies:asn1: 0.2.4assert-plus: 1.0.0bcrypt-pbkdf: 1.0.2dashdash: 1.14.1ecc-jsbn: 0.1.2getpass: 0.1.7jsbn: 0.1.1safer-buffer: 2.1.2tweetnacl: 0.14.5dev: true/state-toggle/1.0.3:resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==}dev: true/static-extend/0.1.2:resolution: {integrity: sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=}engines: {node: '>=0.10.0'}dependencies:define-property: 0.2.5object-copy: 0.1.0dev: true/stdout-stream/1.4.1:resolution: {integrity: sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==}dependencies:readable-stream: 2.3.7dev: true/strict-uri-encode/1.1.0:resolution: {integrity: sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=}engines: {node: '>=0.10.0'}dev: true/string-argv/0.3.1:resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==}engines: {node: '>=0.6.19'}dev: true/string-width/1.0.2:resolution: {integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=}engines: {node: '>=0.10.0'}dependencies:code-point-at: 1.1.0is-fullwidth-code-point: 1.0.0strip-ansi: 3.0.1dev: true/string-width/2.1.1:resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}engines: {node: '>=4'}dependencies:is-fullwidth-code-point: 2.0.0strip-ansi: 4.0.0dev: true/string-width/3.1.0:resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}engines: {node: '>=6'}dependencies:emoji-regex: 7.0.3is-fullwidth-code-point: 2.0.0strip-ansi: 5.2.0/string-width/4.2.2:resolution: {integrity: sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==}engines: {node: '>=8'}dependencies:emoji-regex: 8.0.0is-fullwidth-code-point: 3.0.0strip-ansi: 6.0.0dev: true/string.prototype.trimend/1.0.4:resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==}dependencies:call-bind: 1.0.2define-properties: 1.1.3dev: true/string.prototype.trimstart/1.0.4:resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==}dependencies:call-bind: 1.0.2define-properties: 1.1.3dev: true/string_decoder/1.1.1:resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}dependencies:safe-buffer: 5.1.2dev: true/string_decoder/1.3.0:resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}dependencies:safe-buffer: 5.2.1dev: true/stringify-entities/1.3.2:resolution: {integrity: sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==}dependencies:character-entities-html4: 1.1.4character-entities-legacy: 1.1.4is-alphanumerical: 1.0.4is-hexadecimal: 1.0.4dev: true/stringify-object/3.3.0:resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}engines: {node: '>=4'}dependencies:get-own-enumerable-property-symbols: 3.0.2is-obj: 1.0.1is-regexp: 1.0.0dev: true/strip-ansi/3.0.1:resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=}engines: {node: '>=0.10.0'}dependencies:ansi-regex: 2.1.1dev: true/strip-ansi/4.0.0:resolution: {integrity: sha1-qEeQIusaw2iocTibY1JixQXuNo8=}engines: {node: '>=4'}dependencies:ansi-regex: 3.0.0dev: true/strip-ansi/5.2.0:resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==}engines: {node: '>=6'}dependencies:ansi-regex: 4.1.0/strip-ansi/6.0.0:resolution: {integrity: sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==}engines: {node: '>=8'}dependencies:ansi-regex: 5.0.0dev: true/strip-bom/3.0.0:resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=}engines: {node: '>=4'}dev: true/strip-eof/1.0.0:resolution: {integrity: sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=}engines: {node: '>=0.10.0'}dev: true/strip-final-newline/2.0.0:resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}engines: {node: '>=6'}dev: true/strip-indent/2.0.0:resolution: {integrity: sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=}engines: {node: '>=4'}dev: true/strip-indent/3.0.0:resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}engines: {node: '>=8'}dependencies:min-indent: 1.0.1dev: true/strip-json-comments/2.0.1:resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=}engines: {node: '>=0.10.0'}dev: true/strip-json-comments/3.1.1:resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}engines: {node: '>=8'}dev: true/style-search/0.1.0:resolution: {integrity: sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=}dev: true/stylelint-config-prettier/5.3.0_stylelint@11.1.1:resolution: {integrity: sha512-To1lmaEYTmmGsVFSnVmU0cgXBrX4m31fm5sPgFCAH7Ep4ctdJhmTPe1aUnsZ9p2wY+SPhngqaFU5r1njaUyiaA==}engines: {node: '>= 6', npm: '>= 3'}hasBin: truepeerDependencies:stylelint: ^9.1.1 || ^10.0.0 || ^11.0.0dependencies:stylelint: 11.1.1dev: true/stylelint-config-rational-order/0.1.2:resolution: {integrity: sha512-Qo7ZQaihCwTqijfZg4sbdQQHtugOX/B1/fYh018EiDZHW+lkqH9uHOnsDwDPGZrYJuB6CoyI7MZh2ecw2dOkew==}dependencies:stylelint: 9.10.1stylelint-order: 2.2.1_stylelint@9.10.1transitivePeerDependencies:- supports-colordev: true/stylelint-config-recommended/2.2.0_stylelint@11.1.1:resolution: {integrity: sha512-bZ+d4RiNEfmoR74KZtCKmsABdBJr4iXRiCso+6LtMJPw5rd/KnxUWTxht7TbafrTJK1YRjNgnN0iVZaJfc3xJA==}peerDependencies:stylelint: ^8.3.0 || ^9.0.0 || ^10.0.0dependencies:stylelint: 11.1.1dev: true/stylelint-config-standard/18.3.0_stylelint@11.1.1:resolution: {integrity: sha512-Tdc/TFeddjjy64LvjPau9SsfVRexmTFqUhnMBrzz07J4p2dVQtmpncRF/o8yZn8ugA3Ut43E6o1GtjX80TFytw==}peerDependencies:stylelint: ^8.3.0 || ^9.0.0 || ^10.0.0dependencies:stylelint: 11.1.1stylelint-config-recommended: 2.2.0_stylelint@11.1.1dev: true/stylelint-order/2.2.1_stylelint@9.10.1:resolution: {integrity: sha512-019KBV9j8qp1MfBjJuotse6MgaZqGVtXMc91GU9MsS9Feb+jYUvUU3Z8XiClqPdqJZQ0ryXQJGg3U3PcEjXwfg==}engines: {node: '>=6'}peerDependencies:stylelint: ^9.10.1 || ^10.0.0dependencies:lodash: 4.17.21postcss: 7.0.36postcss-sorting: 4.1.0stylelint: 9.10.1dev: true/stylelint-order/3.1.1_stylelint@11.1.1:resolution: {integrity: sha512-4gP/r8j/6JGZ/LL41b2sYtQqfwZl4VSqTp7WeIwI67v/OXNQ08dnn64BGXNwAUSgb2+YIvIOxQaMzqMyQMzoyQ==}engines: {node: '>=8.7.0'}peerDependencies:stylelint: '>=10.0.1'dependencies:lodash: 4.17.21postcss: 7.0.36postcss-sorting: 5.0.1stylelint: 11.1.1dev: true/stylelint-prettier/1.2.0_prettier@1.19.1+stylelint@11.1.1:resolution: {integrity: sha512-/MYz6W2CNgKHblPzPtk7cybu8H5dGG3c2GevL64RButERj1uJg4SdBIIat1hMfDOmN6QQpldc6tCc//ZAWh9WQ==}engines: {node: '>=6'}peerDependencies:prettier: '>= 0.11.0'stylelint: '>= 9.2.1'dependencies:prettier: 1.19.1prettier-linter-helpers: 1.0.0stylelint: 11.1.1dev: true/stylelint-scss/3.20.1_stylelint@11.1.1:resolution: {integrity: sha512-OTd55O1TTAC5nGKkVmUDLpz53LlK39R3MImv1CfuvsK7/qugktqiZAeQLuuC4UBhzxCnsc7fp9u/gfRZwFAIkA==}engines: {node: '>=8'}peerDependencies:stylelint: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0dependencies:lodash: 4.17.21postcss-media-query-parser: 0.2.3postcss-resolve-nested-selector: 0.1.1postcss-selector-parser: 6.0.6postcss-value-parser: 4.1.0stylelint: 11.1.1dev: true/stylelint/11.1.1:resolution: {integrity: sha512-Vx6TAJsxG6qksiFvxQTKriQhp1CqUWdpTDITEkAjTR+l+8Af7qNlvrUDXfpuFJgXh/ayF8xdMSKE+SstcsPmMA==}engines: {node: '>=8.7.0'}hasBin: truedependencies:autoprefixer: 9.8.6balanced-match: 1.0.2chalk: 2.4.2cosmiconfig: 5.2.1debug: 4.3.2execall: 2.0.0file-entry-cache: 5.0.1get-stdin: 7.0.0global-modules: 2.0.0globby: 9.2.0globjoin: 0.1.4html-tags: 3.1.0ignore: 5.1.8import-lazy: 4.0.0imurmurhash: 0.1.4known-css-properties: 0.16.0leven: 3.1.0lodash: 4.17.21log-symbols: 3.0.0mathml-tag-names: 2.1.3meow: 5.0.0micromatch: 4.0.4normalize-selector: 0.2.0postcss: 7.0.36postcss-html: 0.36.0_2b33a41d320e3e2012e5b3b0fadc703bpostcss-jsx: 0.36.4_2b33a41d320e3e2012e5b3b0fadc703bpostcss-less: 3.1.4postcss-markdown: 0.36.0_2b33a41d320e3e2012e5b3b0fadc703bpostcss-media-query-parser: 0.2.3postcss-reporter: 6.0.1postcss-resolve-nested-selector: 0.1.1postcss-safe-parser: 4.0.2postcss-sass: 0.4.4postcss-scss: 2.1.1postcss-selector-parser: 3.1.2postcss-syntax: 0.36.2_postcss@7.0.36postcss-value-parser: 4.1.0resolve-from: 5.0.0signal-exit: 3.0.3slash: 3.0.0specificity: 0.4.1string-width: 4.2.2strip-ansi: 5.2.0style-search: 0.1.0sugarss: 2.0.0svg-tags: 1.0.0table: 5.4.6v8-compile-cache: 2.3.0transitivePeerDependencies:- supports-colordev: true/stylelint/9.10.1:resolution: {integrity: sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ==}engines: {node: '>=6'}hasBin: truedependencies:autoprefixer: 9.8.6balanced-match: 1.0.2chalk: 2.4.2cosmiconfig: 5.2.1debug: 4.3.2execall: 1.0.0file-entry-cache: 4.0.0get-stdin: 6.0.0global-modules: 2.0.0globby: 9.2.0globjoin: 0.1.4html-tags: 2.0.0ignore: 5.1.8import-lazy: 3.1.0imurmurhash: 0.1.4known-css-properties: 0.11.0leven: 2.1.0lodash: 4.17.21log-symbols: 2.2.0mathml-tag-names: 2.1.3meow: 5.0.0micromatch: 3.1.10normalize-selector: 0.2.0pify: 4.0.1postcss: 7.0.36postcss-html: 0.36.0_2b33a41d320e3e2012e5b3b0fadc703bpostcss-jsx: 0.36.4_2b33a41d320e3e2012e5b3b0fadc703bpostcss-less: 3.1.4postcss-markdown: 0.36.0_2b33a41d320e3e2012e5b3b0fadc703bpostcss-media-query-parser: 0.2.3postcss-reporter: 6.0.1postcss-resolve-nested-selector: 0.1.1postcss-safe-parser: 4.0.2postcss-sass: 0.3.5postcss-scss: 2.1.1postcss-selector-parser: 3.1.2postcss-syntax: 0.36.2_postcss@7.0.36postcss-value-parser: 3.3.1resolve-from: 4.0.0signal-exit: 3.0.3slash: 2.0.0specificity: 0.4.1string-width: 3.1.0style-search: 0.1.0sugarss: 2.0.0svg-tags: 1.0.0table: 5.4.6transitivePeerDependencies:- supports-colordev: true/sugarss/2.0.0:resolution: {integrity: sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==}dependencies:postcss: 7.0.36dev: true/supports-color/2.0.0:resolution: {integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=}engines: {node: '>=0.8.0'}dev: true/supports-color/5.5.0:resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}engines: {node: '>=4'}dependencies:has-flag: 3.0.0/supports-color/6.0.0:resolution: {integrity: sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==}engines: {node: '>=6'}dependencies:has-flag: 3.0.0dev: true/supports-color/6.1.0:resolution: {integrity: sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==}engines: {node: '>=6'}dependencies:has-flag: 3.0.0/supports-color/7.2.0:resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}engines: {node: '>=8'}dependencies:has-flag: 4.0.0dev: true/supports-color/8.1.1:resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}engines: {node: '>=10'}dependencies:has-flag: 4.0.0dev: true/svelte-hmr/0.14.7_svelte@3.42.1:resolution: {integrity: sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==}peerDependencies:svelte: '>=3.19.0'dependencies:svelte: 3.42.1dev: true/svelte-preprocess-filter/1.0.0:resolution: {integrity: sha512-92innv59nyEx24xbfcSurB5ocwC8qFdDtGli/JVMHzJsxyvV2yjQKIcbUqU9VIV5mKUWO2PoY93nncS2yF4ULQ==}dev: true/svelte-preprocess-sass/0.2.0:resolution: {integrity: sha512-xcjwihO9hhd5W9hCSFKv1iBc8XhMif50IPP9Qu2G8IaxVaOoBUWeZu21Qu26tOw2gtv44/3p00eLrxGzLWyCLg==}dependencies:svelte-preprocess-filter: 1.0.0dev: true/svelte-preprocess/3.9.12_svelte@3.42.1:resolution: {integrity: sha512-OX8a7drmlYcX/bLKbtRTvcc0lYu5Ub78D4B/GVxac2zeyrj1e5vEJU6BsxFbc/8kFDqI6BgsCLZAqsFDr/KrDQ==}engines: {node: '>= 7.6.0'}requiresBuild: truepeerDependencies:'@babel/core': ^7.10.2coffeescript: ^2.5.1less: ^3.11.3node-sass: '*'postcss: ^7.0.32postcss-load-config: ^2.1.0pug: ^3.0.0sass: ^1.26.8stylus: ^0.54.7svelte: ^3.23.0typescript: ^3.9.5peerDependenciesMeta:'@babel/core':optional: truecoffeescript:optional: trueless:optional: truenode-sass:optional: truepostcss:optional: truepostcss-load-config:optional: truepug:optional: truesass:optional: truestylus:optional: truesvelte:optional: truetypescript:optional: truedependencies:'@types/pug': 2.0.5'@types/sass': 1.16.1detect-indent: 6.1.0strip-indent: 3.0.0svelte: 3.42.1dev: true/svelte-preprocess/4.7.4_6197623e5ed34153d1bcd9290e2954d7:resolution: {integrity: sha512-mDAmaltQl6e5zU2VEtoWEf7eLTfuOTGr9zt+BpA3AGHo8MIhKiNSPE9OLTCTOMgj0vj/uL9QBbaNmpG4G1CgIA==}engines: {node: '>= 9.11.2'}requiresBuild: truepeerDependencies:'@babel/core': ^7.10.2coffeescript: ^2.5.1less: ^3.11.3node-sass: '*'postcss: ^7 || ^8postcss-load-config: ^2.1.0 || ^3.0.0pug: ^3.0.0sass: ^1.26.8stylus: ^0.54.7sugarss: ^2.0.0svelte: ^3.23.0typescript: ^3.9.5 || ^4.0.0peerDependenciesMeta:'@babel/core':optional: truecoffeescript:optional: trueless:optional: truenode-sass:optional: truepostcss:optional: truepostcss-load-config:optional: truepug:optional: truesass:optional: truestylus:optional: truesugarss:optional: truetypescript:optional: truedependencies:'@types/pug': 2.0.5'@types/sass': 1.16.1detect-indent: 6.1.0node-sass: 6.0.1sass: 1.37.5strip-indent: 3.0.0svelte: 3.42.1dev: true/svelte/3.42.1:resolution: {integrity: sha512-XtExLd2JAU3T7M2g/DkO3UNj/3n1WdTXrfL63OZ5nZq7nAqd9wQw+lR4Pv/wkVbrWbAIPfLDX47UjFdmnY+YtQ==}engines: {node: '>= 8'}dev: true/svg-tags/1.0.0:resolution: {integrity: sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=}dev: true/symbol-observable/1.2.0:resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==}engines: {node: '>=0.10.0'}dev: true/table/5.4.6:resolution: {integrity: sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==}engines: {node: '>=6.0.0'}dependencies:ajv: 6.12.6lodash: 4.17.21slice-ansi: 2.1.0string-width: 3.1.0dev: true/table/6.7.1:resolution: {integrity: sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==}engines: {node: '>=10.0.0'}dependencies:ajv: 8.6.2lodash.clonedeep: 4.5.0lodash.truncate: 4.4.2slice-ansi: 4.0.0string-width: 4.2.2strip-ansi: 6.0.0dev: true/tar/6.1.7:resolution: {integrity: sha512-PBoRkOJU0X3lejJ8GaRCsobjXTgFofRDSPdSUhRSdlwJfifRlQBwGXitDItdGFu0/h0XDMCkig0RN1iT7DBxhA==}engines: {node: '>= 10'}dependencies:chownr: 2.0.0fs-minipass: 2.1.0minipass: 3.1.3minizlib: 2.1.2mkdirp: 1.0.4yallist: 4.0.0dev: true/text-table/0.2.0:resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=}dev: true/through/2.3.8:resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=}dev: true/tiny-glob/0.2.9:resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}dependencies:globalyzer: 0.1.0globrex: 0.1.2dev: true/tmp/0.0.33:resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}engines: {node: '>=0.6.0'}dependencies:os-tmpdir: 1.0.2dev: true/to-fast-properties/2.0.0:resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=}engines: {node: '>=4'}dev: true/to-object-path/0.3.0:resolution: {integrity: sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=}engines: {node: '>=0.10.0'}dependencies:kind-of: 3.2.2dev: true/to-regex-range/2.1.1:resolution: {integrity: sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=}engines: {node: '>=0.10.0'}dependencies:is-number: 3.0.0repeat-string: 1.6.1dev: true/to-regex-range/5.0.1:resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}engines: {node: '>=8.0'}dependencies:is-number: 7.0.0/to-regex/3.0.2:resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==}engines: {node: '>=0.10.0'}dependencies:define-property: 2.0.2extend-shallow: 3.0.2regex-not: 1.0.2safe-regex: 1.1.0dev: true/tough-cookie/2.5.0:resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}engines: {node: '>=0.8'}dependencies:psl: 1.8.0punycode: 2.1.1dev: true/tree-kill/1.2.2:resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}hasBin: truedev: falseoptional: true/trim-newlines/2.0.0:resolution: {integrity: sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=}engines: {node: '>=4'}dev: true/trim-newlines/3.0.1:resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}engines: {node: '>=8'}dev: true/trim-trailing-lines/1.1.4:resolution: {integrity: sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==}dev: true/trim/0.0.1:resolution: {integrity: sha1-WFhUf2spB1fulczMZm+1AITEYN0=}dev: true/trough/1.0.5:resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==}dev: true/trouter/2.0.1:resolution: {integrity: sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==}engines: {node: '>=6'}dependencies:matchit: 1.1.0dev: falseoptional: true/true-case-path/1.0.3:resolution: {integrity: sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==}dependencies:glob: 7.1.7dev: true/tslib/1.14.1:resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}/tunnel-agent/0.6.0:resolution: {integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=}dependencies:safe-buffer: 5.2.1dev: true/tweetnacl/0.14.5:resolution: {integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=}dev: true/type-check/0.3.2:resolution: {integrity: sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=}engines: {node: '>= 0.8.0'}dependencies:prelude-ls: 1.1.2dev: true/type-check/0.4.0:resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}engines: {node: '>= 0.8.0'}dependencies:prelude-ls: 1.2.1dev: true/type-detect/4.0.8:resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}engines: {node: '>=4'}dev: true/type-fest/0.18.1:resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==}engines: {node: '>=10'}dev: true/type-fest/0.20.2:resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}engines: {node: '>=10'}dev: true/type-fest/0.21.3:resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}engines: {node: '>=10'}dev: true/type-fest/0.6.0:resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==}engines: {node: '>=8'}dev: true/type-fest/0.8.1:resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}engines: {node: '>=8'}dev: true/uc.micro/1.0.6:resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}dev: true/uglify-js/3.14.1:resolution: {integrity: sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g==}engines: {node: '>=0.8.0'}hasBin: truedev: true/unbox-primitive/1.0.1:resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==}dependencies:function-bind: 1.1.1has-bigints: 1.0.1has-symbols: 1.0.2which-boxed-primitive: 1.0.2dev: true/unherit/1.1.3:resolution: {integrity: sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==}dependencies:inherits: 2.0.4xtend: 4.0.2dev: true/unified/7.1.0:resolution: {integrity: sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==}dependencies:'@types/unist': 2.0.6'@types/vfile': 3.0.2bail: 1.0.5extend: 3.0.2is-plain-obj: 1.1.0trough: 1.0.5vfile: 3.0.1x-is-string: 0.1.0dev: true/union-value/1.0.1:resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==}engines: {node: '>=0.10.0'}dependencies:arr-union: 3.1.0get-value: 2.0.6is-extendable: 0.1.1set-value: 2.0.1dev: true/uniq/1.0.1:resolution: {integrity: sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=}dev: true/unist-util-find-all-after/1.0.5:resolution: {integrity: sha512-lWgIc3rrTMTlK1Y0hEuL+k+ApzFk78h+lsaa2gHf63Gp5Ww+mt11huDniuaoq1H+XMK2lIIjjPkncxXcDp3QDw==}dependencies:unist-util-is: 3.0.0dev: true/unist-util-is/3.0.0:resolution: {integrity: sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==}dev: true/unist-util-remove-position/1.1.4:resolution: {integrity: sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==}dependencies:unist-util-visit: 1.4.1dev: true/unist-util-stringify-position/1.1.2:resolution: {integrity: sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==}dev: true/unist-util-stringify-position/3.0.0:resolution: {integrity: sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA==}dependencies:'@types/unist': 2.0.6dev: true/unist-util-visit-parents/2.1.2:resolution: {integrity: sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==}dependencies:unist-util-is: 3.0.0dev: true/unist-util-visit/1.4.1:resolution: {integrity: sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==}dependencies:unist-util-visit-parents: 2.1.2dev: true/unset-value/1.0.0:resolution: {integrity: sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=}engines: {node: '>=0.10.0'}dependencies:has-value: 0.3.1isobject: 3.0.1dev: true/uri-js/4.4.1:resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}dependencies:punycode: 2.1.1dev: true/urix/0.1.0:resolution: {integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=}deprecated: Please see https://github.com/lydell/urix#deprecateddev: true/use/3.1.1:resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}engines: {node: '>=0.10.0'}dev: true/util-deprecate/1.0.2:resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}dev: true/uuid/3.4.0:resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.hasBin: truedev: true/uuid/8.3.2:resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}hasBin: true/v8-compile-cache/2.3.0:resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}dev: true/validate-npm-package-license/3.0.4:resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}dependencies:spdx-correct: 3.1.1spdx-expression-parse: 3.0.1/verror/1.10.0:resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=}engines: {'0': node >=0.6.0}dependencies:assert-plus: 1.0.0core-util-is: 1.0.2extsprintf: 1.3.0dev: true/vfile-location/2.0.6:resolution: {integrity: sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==}dev: true/vfile-message/1.1.1:resolution: {integrity: sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==}dependencies:unist-util-stringify-position: 1.1.2dev: true/vfile-message/3.0.1:resolution: {integrity: sha512-gYmSHcZZUEtYpTmaWaFJwsuUD70/rTY4v09COp8TGtOkix6gGxb/a8iTQByIY9ciTk9GwAwIXd/J9OPfM4Bvaw==}dependencies:'@types/unist': 2.0.6unist-util-stringify-position: 3.0.0dev: true/vfile/3.0.1:resolution: {integrity: sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==}dependencies:is-buffer: 2.0.5replace-ext: 1.0.0unist-util-stringify-position: 1.1.2vfile-message: 1.1.1dev: true/vite/2.4.4:resolution: {integrity: sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw==}engines: {node: '>=12.0.0'}hasBin: truedependencies:esbuild: 0.12.19postcss: 8.3.6resolve: 1.20.0rollup: 2.56.1optionalDependencies:fsevents: 2.3.2dev: true/which-boxed-primitive/1.0.2:resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}dependencies:is-bigint: 1.0.3is-boolean-object: 1.1.2is-number-object: 1.0.6is-string: 1.0.7is-symbol: 1.0.4dev: true/which-module/2.0.0:resolution: {integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=}/which/1.3.1:resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}hasBin: truedependencies:isexe: 2.0.0dev: true/which/2.0.2:resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}engines: {node: '>= 8'}hasBin: truedependencies:isexe: 2.0.0dev: true/wide-align/1.1.3:resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==}dependencies:string-width: 2.1.1dev: true/word-wrap/1.2.3:resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}engines: {node: '>=0.10.0'}dev: true/workerpool/6.1.0:resolution: {integrity: sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==}dev: true/wrap-ansi/3.0.1:resolution: {integrity: sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=}engines: {node: '>=4'}dependencies:string-width: 2.1.1strip-ansi: 4.0.0dev: true/wrap-ansi/5.1.0:resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==}engines: {node: '>=6'}dependencies:ansi-styles: 3.2.1string-width: 3.1.0strip-ansi: 5.2.0/wrap-ansi/7.0.0:resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}engines: {node: '>=10'}dependencies:ansi-styles: 4.3.0string-width: 4.2.2strip-ansi: 6.0.0dev: true/wrappy/1.0.2:resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}dev: true/write/1.0.3:resolution: {integrity: sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==}engines: {node: '>=4'}dependencies:mkdirp: 0.5.5dev: true/ws/6.2.2:resolution: {integrity: sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==}dependencies:async-limiter: 1.0.1dev: false/x-is-string/0.1.0:resolution: {integrity: sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=}dev: true/xtend/4.0.2:resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}engines: {node: '>=0.4'}dev: true/y18n/4.0.3:resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}/y18n/5.0.8:resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}engines: {node: '>=10'}dev: true/yallist/2.1.2:resolution: {integrity: sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=}dev: true/yallist/4.0.0:resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}dev: true/yargs-parser/10.1.0:resolution: {integrity: sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==}dependencies:camelcase: 4.1.0dev: true/yargs-parser/13.1.2:resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==}dependencies:camelcase: 5.3.1decamelize: 1.2.0/yargs-parser/20.2.4:resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}engines: {node: '>=10'}dev: true/yargs-parser/20.2.9:resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}engines: {node: '>=10'}dev: true/yargs-unparser/1.6.0:resolution: {integrity: sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==}engines: {node: '>=6'}dependencies:flat: 4.1.1lodash: 4.17.21yargs: 13.3.2dev: true/yargs-unparser/2.0.0:resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}engines: {node: '>=10'}dependencies:camelcase: 6.2.0decamelize: 4.0.0flat: 5.0.2is-plain-obj: 2.1.0dev: true/yargs/13.3.2:resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==}dependencies:cliui: 5.0.0find-up: 3.0.0get-caller-file: 2.0.5require-directory: 2.1.1require-main-filename: 2.0.0set-blocking: 2.0.0string-width: 3.1.0which-module: 2.0.0y18n: 4.0.3yargs-parser: 13.1.2/yargs/16.2.0:resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}engines: {node: '>=10'}dependencies:cliui: 7.0.4escalade: 3.1.1get-caller-file: 2.0.5require-directory: 2.1.1string-width: 4.2.2y18n: 5.0.8yargs-parser: 20.2.4dev: true/yocto-queue/0.1.0:resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}engines: {node: '>=10'}dev: true
{"name": "@djinlist/v0","version": "0.0.1","license": "UNLICENSED","private": true,"husky": {"hooks": {"prepare-commit-msg": ".bin/prepare-commit-msg.sh","pre-commit": "lint-staged"}},"lint-staged": {"*": [".bin/pre-commit.sh"],"*.js": [".bin/format.sh js","eslint","git add"],"*.{html,svelte}": [".bin/format.sh html","eslint","git add"]},"dependencies": {"@controlenvy/datastore": "4.0.0","lodash": "^4.17.15","lodash-es": "^4.17.15","secure-remote-password": "^0.3.1"},"devDependencies": {"globby": "^11.0.0","husky": "^3.0.0","moment": "^2.24.0","node-watch": "^0.6.3","benchmark": "^2.1.4","jsdoctest": "^1.7.1","chai": "^4.2.0","mocha": "^6.1.4","sinon": "^7.3.2","sinon-chai": "^3.3.0","babel-eslint": "^10.0.3","lint-staged": "^9.2.0","eslint": "^6.0.1","eslint-config-prettier": "^6.0.0","eslint-plugin-html": "^6.0.0","eslint-plugin-optimize-regex": "^1.1.6","eslint-plugin-prettier": "^3.1.0","eslint-plugin-svelte3": "^2.7.3","prettier": "^1.19.1","stylelint": "^11.1.1","stylelint-config-prettier": "^5.1.0","stylelint-config-rational-order": "^0.1.2","stylelint-config-standard": "^18.3.0","stylelint-order": "^3.1.1","stylelint-prettier": "^1.1.0","stylelint-scss": "^3.13.0"},"optionalDependencies": {"concurrently": "^5.0.0","ipware": "^2.0.0","luxon": "^1.21.3","polka": "^0.5.2","sirv": "^0.4.2"}}
/*** Test Dependencies*/import './helper'import chai, { expect } from 'chai'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { TopicTree } from 'topic_tree.js'describe('TopicTree', () => {describe('#values', () => {it('matches "/setup/rooms/1/name"', () => {const tree = new TopicTree()tree.add('setup', 3000)tree.add('setup/rooms', 3300)tree.add('setup/rooms/1', 3330)tree.add('setup/rooms/1/name', 3333)tree.add('#', 1000)tree.add('setup/rooms/#', 3310)tree.add('setup/rooms/+/name', 3323)tree.add('setup/rooms/+', 3320)tree.add('setup/rooms/+/power', -3323)tree.add('setup/+/+/name', 3223)const values = tree.values('setup/rooms/1/name')expect(values).to.eql([3333, 3323, 3310, 3223, 1000])})})describe('#entries', () => {it('matches "/setup/rooms/1/name"', () => {const tree = new TopicTree()tree.add('setup', 3000)tree.add('setup/rooms', 3300)tree.add('setup/rooms/1', 3330)tree.add('setup/rooms/1/name', 3333)tree.add('#', 1000)tree.add('setup/rooms/#', 3310)tree.add('setup/rooms/+/name', 3323)tree.add('setup/rooms/+', 3320)tree.add('setup/rooms/+/power', -3323)tree.add('setup/+/+/name', 3223)const entries = tree.entries('setup/rooms/1/name')expect(entries).to.eql([['setup/rooms/1/name', 3333],['setup/rooms/+/name', 3323],['setup/rooms/#', 3310],['setup/+/+/name', 3223],['#', 1000]])})})})
/*** Test Dependencies*/import './helper'import chai, { expect } from 'chai'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from 'pointer.js'describe('Pointer', () => {describe('new Pointer', () => {it('""', () => {const pointer = new Pointer('')expect(pointer._path).to.equal('')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('')expect(pointer.steps).to.eql([])expect(pointer.topic).to.equal('#')expect(pointer.length).to.equal(0)expect(pointer == '').to.be.true})it('"/a"', () => {const pointer = new Pointer('/a')expect(pointer._path).to.equal('/a')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('"/a/b"', () => {const pointer = new Pointer('/a/b')expect(pointer._path).to.equal('/a/b')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})it('[]', () => {const pointer = new Pointer([])expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql([])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('')expect(pointer.steps).to.eql([])expect(pointer.topic).to.equal('#')expect(pointer.length).to.equal(0)expect(pointer == '').to.be.true})it('["a"]', () => {const pointer = new Pointer(['a'])expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql(['a'])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('["a", "b"]', () => {const pointer = new Pointer(['a', 'b'])expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql(['a', 'b'])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})it('"#"', () => {const pointer = new Pointer('#')expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('#')expect(pointer.path).to.equal('')expect(pointer.steps).to.eql([])expect(pointer.topic).to.equal('#')expect(pointer.length).to.equal(0)expect(pointer == '').to.be.true})it('"a"', () => {const pointer = new Pointer('a')expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('a')expect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('"a/b"', () => {const pointer = new Pointer('a/b')expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('a/b')expect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})})describe('pointer.path =', () => {it('"/a"', () => {const pointer = new Pointer('')pointer.path = '/a'expect(pointer._path).to.equal('/a')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('"/a/b"', () => {const pointer = new Pointer('')pointer.path = '/a/b'expect(pointer._path).to.equal('/a/b')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})})describe('pointer.steps =', () => {it('["a"]', () => {const pointer = new Pointer([])pointer.steps = ['a']expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql(['a'])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('["a", "b"]', () => {const pointer = new Pointer([])pointer.steps = ['a', 'b']expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql(['a', 'b'])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})})describe('pointer.topic =', () => {it('"a"', () => {const pointer = new Pointer('#')pointer.topic = 'a'expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('a')expect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('"a/b"', () => {const pointer = new Pointer('#')pointer.topic = 'a/b'expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('a/b')expect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})})})
/*** Test Dependencies*/import '../helper'import chai, { expect } from 'chai'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from 'pointer.js'describe('Pointer', () => {describe('ControlEnvy', () => {;[['/systems/local/setup/rooms/1/name', '/systems/local', null, 'setup', '/rooms/1', '/rooms/1', '/name', 'name'],['/systems/local/q/setup/rooms/1/name', '/systems/local', 'q', 'setup', '/rooms/1', '/rooms/1', '/name', 'name'],['/setup/components/1/switchers/1/outputs/1/name', null, null, 'setup', '/components/1', '/components/1/switchers/1/outputs/1', '/name', 'name']].forEach(test => {const [path, grove_path, flag, root, trunk_path, branch_path, twig_path, leaf] = testdescribe(`"${path}"`, () => {const pointer = new Pointer(path)it(`grove_path: ${grove_path}`, () => {expect(pointer.grove_path).to.equal(grove_path)})it(`flag: ${flag}`, () => {expect(pointer.flag).to.equal(flag)})it(`root: ${root}`, () => {expect(pointer.root).to.equal(root)})it(`trunk_path: ${trunk_path}`, () => {expect(pointer.trunk_path).to.equal(trunk_path)})it(`branch_path: ${branch_path}`, () => {expect(pointer.branch_path).to.equal(branch_path)})it(`twig_path: ${twig_path}`, () => {expect(pointer.twig_path).to.equal(twig_path)})it(`leaf: ${leaf}`, () => {expect(pointer.leaf).to.equal(leaf)})/*let path =flag === ''? `/${root}${branch_path}${twig_path}`: `/${flag}/${root}${branch_path}${twig_path}`it.skip(`toMessage('r') { o: 'r', c: '${trunk_path}', p: '${path}' }`, () => {expect(pointer.toMessage('r')).to.eql({o: 'r',c: trunk_path,p: path})})it.skip(`toMessage('w', '') { o: 'w', c: '${trunk_path}', p: '${path}', v: '' }`, () => {expect(pointer.toMessage('w', '')).to.eql({o: 'w',c: trunk_path,p: path,v: ''})})it.skip(`toMessage('s', undefined, { i: true }) { o: 'r', c: '${trunk_path}', p: '${path}', i: true }`, () => {expect(pointer.toMessage('s', undefined, { i: true })).to.eql({o: 's',c: trunk_path,p: path,i: true})})*/})})describe('pointer.grove_path =', () => {const pointer = new Pointer('/setup/rooms/1/name')it('"/systems/local"', () => {pointer.grove_path = '/systems/local'expect(pointer.grove_path).to.be.equal('/systems/local')expect(pointer.path).to.be.equal('/systems/local/setup/rooms/1/name')})it('"/systems/test"', () => {pointer.grove_path = '/systems/test'expect(pointer.grove_path).to.be.equal('/systems/test')expect(pointer.path).to.be.equal('/systems/test/setup/rooms/1/name')})it('undefined', () => {pointer.grove_path = undefinedexpect(pointer.grove_path).to.be.nullexpect(pointer.path).to.be.equal('/setup/rooms/1/name')})})describe('pointer.flag =', () => {const pointer = new Pointer('/setup/rooms/1/name')it('"q"', () => {pointer.flag = 'q'expect(pointer.flag).to.be.equal('q')expect(pointer.path).to.be.equal('/q/setup/rooms/1/name')})it('undefined', () => {pointer.flag = undefinedexpect(pointer.flag).to.be.nullexpect(pointer.path).to.be.equal('/setup/rooms/1/name')})})describe('pointer.trunk_path =', () => {const pointer = new Pointer('/setup/rooms/1/name')it('"/rooms/2"', () => {pointer.trunk_path = '/rooms/2'expect(pointer.trunk_path).to.be.equal('/rooms/2')expect(pointer.path).to.be.equal('/setup/rooms/2/name')})it('undefined', () => {pointer.trunk_path = undefinedexpect(pointer.trunk_path).to.be.nullexpect(pointer.path).to.be.equal('/setup/name')})})describe('pointer.branch_path =', () => {const pointer = new Pointer('/setup/rooms/1/name')it('"/components/1/displays/1"', () => {pointer.branch_path = '/components/1/displays/1'expect(pointer.branch_path).to.be.equal('/components/1/displays/1')expect(pointer.path).to.be.equal('/setup/components/1/displays/1/name')})it('undefined', () => {pointer.branch_path = undefinedexpect(pointer.branch_path).to.be.nullexpect(pointer.path).to.be.equal('/setup/name')})})describe('pointer.leaf =', () => {const pointer = new Pointer('/setup/rooms/1/name')it('"/setup/rooms/1/name"', () => {pointer.leaf = 'description'expect(pointer.leaf).to.be.equal('description')expect(pointer.path).to.be.equal('/setup/rooms/1/description')})it('undefined', () => {pointer.leaf = undefinedexpect(pointer.leaf).to.be.nullexpect(pointer.path).to.be.equal('/setup/rooms/1')})})/*;[[{o: 'r',c: '/systems/7360c8b7-46fe-4d3a-994e-ff07bed6aa93',p: '/setup/rooms/1/name'},'','','setup','/systems/7360c8b7-46fe-4d3a-994e-ff07bed6aa93','/rooms/1','/name','name'],[{o: 'r',c: '/systems/7360c8b7-46fe-4d3a-994e-ff07bed6aa93',p: '/setup/rooms/+/name'},'','','setup','/systems/7360c8b7-46fe-4d3a-994e-ff07bed6aa93','/rooms/+','/name','name'],[{o: 'w',c: '/systems/7360c8b7-46fe-4d3a-994e-ff07bed6aa93',p: '/setup/rooms/1/name',v: 'Room 1'},'','','setup','/systems/7360c8b7-46fe-4d3a-994e-ff07bed6aa93','/rooms/1','/name','name'],[{o: 's',c: '/systems/7360c8b7-46fe-4d3a-994e-ff07bed6aa93',p: '/setup/rooms/1/name'},'','','setup','/systems/7360c8b7-46fe-4d3a-994e-ff07bed6aa93','/rooms/1','/name','name']].forEach(test => {const [message, hook, flag, root, trunk_path, branch_path, twig_path, leaf] = testdescribe(JSON.stringify(message), () => {const pointer = Pointer.create(message)it(`hook: ${hook}`, () => {expect(pointer.hook).to.equal(hook)})it(`flag: ${flag}`, () => {expect(pointer.flag).to.equal(flag)})it(`root: ${root}`, () => {expect(pointer.root).to.equal(root)})it(`trunk_path: ${trunk_path}`, () => {expect(pointer.trunk_path).to.equal(trunk_path)})it(`branch_path: ${branch_path}`, () => {expect(pointer.branch_path).to.equal(branch_path)})it(`twig_path: ${twig_path}`, () => {expect(pointer.twig_path).to.equal(twig_path)})it(`leaf: ${leaf}`, () => {expect(pointer.leaf).to.equal(leaf)})let path =flag === ''? `/${root}${branch_path}${twig_path}`: `/${flag}/${root}${branch_path}${twig_path}`it(`toMessage('${message.o}') { o: '${message.o}', c: '${trunk_path}', p: '${path}', v: '${message.v}' }`, () => {expect(pointer.toMessage(message.o, message.v)).to.eql(message)})})})*/})})
global.window = {}global.requestAnimationFrame = function() {}global._ = require('lodash')const YAML = require('js-yaml')const fs = require('fs')const path = require('path')function _getCallerFile() {var originalFunc = Error.prepareStackTracevar callerfiletry {var error = new Error()var currentfileError.prepareStackTrace = function(error, stack) {return stack}currentfile = error.stack.shift().getFileName()while (error.stack.length) {callerfile = error.stack.shift().getFileName()if (currentfile !== callerfile) break}} catch (e) {// do nothing}Error.prepareStackTrace = originalFuncreturn callerfile}global.requireYAML = function(file_path) {const dirname = path.dirname(_getCallerFile())const yaml = fs.readFileSync(path.resolve(dirname, file_path))const json = YAML.safeLoad(yaml.toString())return json}
systems:local:setup:rooms:'1':name: Kitchencomponent_paths:- /components/1display_paths:- /components/1/displays/1'2':name: Breakfast Roomcomponent_paths:- /components/2display_paths:- /components/2/displays/1components:'1':name: Displaylocation_paths:- /rooms/1displays:'1':name: Kitchen Display'2':name: Displaylocation_paths:- /rooms/2displays:'1':name: Breakfast Room Display
---systems:2175edf8-5dac-4b9d-9ba5-8f830bef452a:setup:rooms:'1':component_paths:- /components/1
---systems:2175edf8-5dac-4b9d-9ba5-8f830bef452a:setup:components:'1':name: Component 1location_paths:- /rooms/1'2':name: Component 2'3':name: Component 3displays:'1':name: Display 1
/*** Test Dependencies*/import './helper'import chai, { expect } from 'chai'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Datastore } from 'datastore.js'const datastore = new Datastore()describe('Datastore', () => {describe('#set', () => {it('"/a", true', () => {datastore.set('/a', true)const result = datastore._root['a']expect(result).to.equal(true)})it('"/a/b", true', () => {datastore.set('/a/b', true)const result = datastore._root['a']['b']expect(result).to.equal(true)})it('"/a/b/c", true', () => {datastore.set('/a/b/c', true)const result = datastore._root['a']['b']['c']expect(result).to.equal(true)})})describe('#delete', () => {it('"/a"', () => {datastore.set('/a', true)datastore.delete('/a')const result = datastore._rootexpect(result).to.eql({})})it('"/a/b"', () => {datastore.set('/a/b', true)datastore.delete('/a/b')const result = datastore._rootexpect(result).to.eql({})})it('"/a/b/c"', () => {datastore.set('/a/b/c', true)datastore.delete('/a/b/c')const result = datastore._rootexpect(result).to.eql({})})it('"/a2"', () => {datastore.set('/a1', true)datastore.set('/a2', true)datastore.delete('/a2')const result = datastore._rootexpect(result).to.eql({ a1: true })})})})
/*** Test Dependencies*/import '../helper'import chai, { expect } from 'chai'import sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from 'pointer.js'import { Datastore as Base } from 'datastore/base.js'import { External } from 'datastore/external.js'import { PubSub } from 'datastore/pubsub.js'class Datastore extends PubSub(External(Base)) {}describe('Datastore', () => {describe('#pubsub', () => {describe('"/setup/rooms/1/name", "Kitchen"', () => {it('#subscribe "setup/rooms/*"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('setup/rooms/*', subscriber, callback, {immediate: false})datastore.set('/setup/rooms/1/name', 'Kitchen')const calls = [['setup/rooms/*', sinon.match(new Pointer('/setup/rooms/1').isEqual), { 1: {} }],['setup/rooms/*', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), { 1: { name: 'Kitchen' } }]]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "#"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('#', subscriber, callback, {immediate: false})datastore.set('/setup/rooms/1/name', 'Kitchen')const calls = [['#', sinon.match(new Pointer('/setup').isEqual), {}],['#', sinon.match(new Pointer('/setup/rooms').isEqual), {}],['#', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}],['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "setup/#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()const pointer = Pointer.create('/setup/rooms/1/name')datastore.subscribe('setup/#', subscriber, callback, {immediate: false})datastore.write(pointer, 'Kitchen')expect(callback).to.have.been.calledWith('setup/#', sinon.match(pointer.isEqual), 'Kitchen')})it('#subscribe "#", immediate', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.set('/setup/rooms/1/name', 'Kitchen')datastore.subscribe('#', subscriber, callback)const calls = [['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "setup/#", immediate', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.write('/setup/rooms/1/name', 'Kitchen')datastore.subscribe('setup/#', subscriber, callback)const calls = [['setup/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#unsubscribe "#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()datastore.subscribe('#', subscriber, callback)datastore.unsubscribe('#', subscriber)const map = datastore._topic_tree._root['#']._valueexpect(map).to.be.emptyexpect(() => {datastore.set('/expected/to/error', false)}).to.not.throw()})it('#subscribe "setup/+/+"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('setup/+/+', subscriber, callback, {immediate: false})datastore.write('/setup/rooms/1/name', 'Kitchen')const calls = [['setup/+/+', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}]]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "setup/+/#"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('setup/+/#', subscriber, callback, {immediate: false})datastore.write('/setup/rooms/1/name', 'Kitchen')const calls = [['setup/+/#', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}],['setup/+/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "setup/+/*"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('setup/+/*', subscriber, callback, {immediate: false})datastore.write('/setup/rooms/1/name', 'Kitchen')const calls = [['setup/+/*',sinon.match(new Pointer('/setup/rooms/1').isEqual),{'1': {}}],['setup/+/*',sinon.match(new Pointer('/setup/rooms/1/name').isEqual),{'1': {name: 'Kitchen'}}]]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})})})})
/*** Test Dependencies*/import '../helper'import chai, { expect } from 'chai'import sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from 'pointer.js'import { Datastore as Base } from 'datastore/base.js'import { External } from 'datastore/external.js'import { PubSub } from 'datastore/pubsub.js'import { Hooks } from 'datastore/hooks.js'class Datastore extends Hooks(PubSub(External(Base))) {}describe('Datastore', () => {describe('#hooks', () => {describe('"/setup/rooms/1/name", "Kitchen"', () => {it('#subscribe "when/setup/rooms/*"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/setup/rooms/*', subscriber, callback, {immediate: false})datastore.set('/setup/rooms/1/name', 'Kitchen')const calls = [['when/setup/rooms/*', sinon.match(new Pointer('/setup/rooms/1').isEqual), { 1: {} }],['when/setup/rooms/*', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), { 1: { name: 'Kitchen' } }]]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "#"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('#', subscriber, callback, { immediate: false })datastore.set('/setup/rooms/1/name', 'Kitchen')const calls = [['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen'],['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen'],['#', sinon.match(new Pointer('/setup').isEqual), {}],['#', sinon.match(new Pointer('/setup/rooms').isEqual), {}],['#', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}],['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen'],['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "when/#"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/#', subscriber, callback, {immediate: false})datastore.set('/setup/rooms/1/name', 'Kitchen')const calls = [['when/#', sinon.match(new Pointer('/setup').isEqual), {}],['when/#', sinon.match(new Pointer('/setup/rooms').isEqual), {}],['when/#', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}],['when/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "before/setup/#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()const pointer = Pointer.create('/setup/rooms/1/name')datastore.subscribe('before/setup/#', subscriber, callback, {immediate: false})datastore.write(pointer, 'Kitchen')expect(callback).to.have.been.calledWith('before/setup/#', sinon.match(pointer.isEqual), 'Kitchen')})it('#subscribe "when/setup/#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()const pointer = Pointer.create('/setup/rooms/1/name')datastore.subscribe('when/setup/#', subscriber, callback, {immediate: false})datastore.write(pointer, 'Kitchen')expect(callback).to.have.been.calledWith('when/setup/#', sinon.match(pointer.isEqual), 'Kitchen')})it('#subscribe "after/setup/#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()const pointer = Pointer.create('/setup/rooms/1/name')datastore.subscribe('after/setup/#', subscriber, callback, {immediate: false})datastore.write(pointer, 'Kitchen')expect(callback).to.have.been.calledWith('after/setup/#', sinon.match(pointer.isEqual), 'Kitchen')})it('#subscribe "when/#", immediate', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.set('/setup/rooms/1/name', 'Kitchen')datastore.subscribe('when/#', subscriber, callback)const calls = [['when/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "when/setup/#", immediate', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.write('/setup/rooms/1/name', 'Kitchen')datastore.subscribe('when/setup/#', subscriber, callback)const calls = [['when/setup/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "async/setup/#", immediate', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.write('/setup/rooms/1/name', 'Kitchen')datastore.subscribe('async/setup/#', subscriber, callback)const calls = [['async/setup/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#unsubscribe "when/#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()datastore.subscribe('when/#', subscriber, callback)datastore.unsubscribe('when/#', subscriber)const map = datastore._topic_tree._root['when']['#']._valueexpect(map).to.be.emptyexpect(() => {datastore.set('/expected/to/error', false)}).to.not.throw()})it('#subscribe "when/setup/+/+"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/setup/+/+', subscriber, callback, {immediate: false})datastore.write('/setup/rooms/1/name', 'Kitchen')const calls = [['when/setup/+/+', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}]]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "when/setup/+/#"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/setup/+/#', subscriber, callback, {immediate: false})datastore.write('/setup/rooms/1/name', 'Kitchen')const calls = [['when/setup/+/#', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}],['when/setup/+/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "when/setup/+/*"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/setup/+/*', subscriber, callback, {immediate: false})datastore.write('/setup/rooms/1/name', 'Kitchen')const calls = [['when/setup/+/*',sinon.match(new Pointer('/setup/rooms/1').isEqual),{'1': {}}],['when/setup/+/*',sinon.match(new Pointer('/setup/rooms/1/name').isEqual),{'1': {name: 'Kitchen'}}]]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})})})})
/*** Test Dependencies*/import '../helper'import chai, { expect } from 'chai'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Datastore as Base } from 'datastore/base.js'import { External } from 'datastore/external.js'class Datastore extends External(Base) {}const data = {'/setup/rooms/1/name': 'Kitchen','/setup/rooms/2/name': 'Breakfast Room'}describe('Datastore', () => {describe('#read', () => {let topicit('"/setup"', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore.read('/setup')expect(result).to.eql({rooms: {'1': {name: 'Kitchen'},'2': {name: 'Breakfast Room'}}})})it('"/setup", { coppiced: true }', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore.read('/setup', { coppiced: true })expect(result).to.eql({'/setup/rooms/1/name': 'Kitchen','/setup/rooms/2/name': 'Breakfast Room'})})it('"setup/rooms/+/name"', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore.read('setup/rooms/+/name', { coppiced: true })expect(result).to.eql({'/setup/rooms/1/name': 'Kitchen','/setup/rooms/2/name': 'Breakfast Room'})})it('"/setup/rooms/+/name"', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore.read('/setup/rooms/+/name', { coppiced: true })expect(result).to.eql({'/setup/rooms/1/name': 'Kitchen','/setup/rooms/2/name': 'Breakfast Room'})})it('"setup/rooms/+/+/+"', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore.read('setup/rooms/+/+/+', { coppiced: true })expect(result).to.eql({})})it('"setup/rooms/+/+/#"', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore.read('setup/rooms/+/+/+', { coppiced: true })expect(result).to.eql({})})topic = 'systems/local/setup/components/+/displays/+/name'it(`"${topic}"`, () => {const yaml = requireYAML('../fixtures/system.yaml')const datastore = new Datastore()datastore.write('', yaml)const result = datastore.read(topic)expect(result).to.eql({'/systems/local/setup/components/1/displays/1/name': 'Kitchen Display','/systems/local/setup/components/2/displays/1/name': 'Breakfast Room Display'})})})describe('#_search', () => {it('"setup/rooms/1/name"', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore._search(datastore._root, ['setup', 'rooms', '1', 'name'], 0, {})expect(result).to.eql({'/setup/rooms/1/name': 'Kitchen'})})it('"setup/rooms/+/name"', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore._search(datastore._root, ['setup', 'rooms', '+', 'name'], 0, {})expect(result).to.eql({'/setup/rooms/1/name': 'Kitchen','/setup/rooms/2/name': 'Breakfast Room'})})it('"setup/rooms/#"', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore._search(datastore._root, ['setup', 'rooms', '#'], 0, {})expect(result).to.eql({'/setup/rooms/1/name': 'Kitchen','/setup/rooms/2/name': 'Breakfast Room'})})})describe('#write', () => {it('"/setup/rooms", { "1": { name: "Dining Room" } }', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}datastore.write('/setup/rooms', { '1': { name: 'Dining Room' } })const result = datastore.read('/setup/rooms')expect(result).to.eql({'1': {name: 'Dining Room'}})})})describe('#merge', () => {it('"/setup/rooms", { "3": { name: "Dining Room" } }', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}datastore.merge('/setup/rooms', { '3': { name: 'Dining Room' } })const result = datastore.read('/setup/rooms')expect(result).to.eql({'1': {name: 'Kitchen'},'2': {name: 'Breakfast Room'},'3': {name: 'Dining Room'}})})})})
/*** Test Dependencies*/import '../helper'import chai, { expect } from 'chai'import sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from 'pointer.js'import { Datastore as Base } from 'datastore/base.js'import { Convenience } from 'datastore/convenience.js'class Datastore extends Convenience(Base) {}const data = {'/setup/rooms/1/area_paths': ['/areas/1', '/areas/2'],'/setup/rooms/2/area_paths': ['/areas/1'],'/setup/components/1/name': 'Component 1','/setup/components/1/displays/1/name': 'Display'}describe('Datastore', () => {describe('#each', () => {it('', () => {const datastore = new Datastore()const callback = sinon.spy()for (const path in data) {const value = data[path]datastore.set(path, value)}datastore.each('/setup/rooms/1/area_paths', callback)expect(callback).to.have.been.calledWith('/areas/1')expect(callback).to.have.been.calledWith('/areas/2')})})describe('#includes', () => {it('', () => {const datastore = new Datastore()const callback = sinon.spy()for (const path in data) {const value = data[path]datastore.set(path, value)}datastore.each('/setup/rooms/1/area_paths', callback)expect(callback).to.have.been.calledWith('/areas/1')expect(callback).to.have.been.calledWith('/areas/2')})})describe('#any', () => {it('', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore.any('/setup/rooms')expect(result).to.be.true})})describe('#keys', () => {it('', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore.keys('/setup/rooms')expect(result).to.be.eql(['1', '2'])})})describe('#branchPaths', () => {it('', () => {const datastore = new Datastore()for (const path in data) {const value = data[path]datastore.set(path, value)}const result = datastore.branchPaths('/setup/components/1')expect(result).to.be.eql(['/components/1', '/components/1/displays/1'])})})describe('#push', () => {it("'/setup/rooms/2/area_paths', '/areas/2'", () => {const datastore = new Datastore()datastore.queue = sinon.spy()for (const path in data) {const value = data[path]datastore.set(path, value)}datastore.push('/setup/rooms/2/area_paths', '/areas/2')const pointer_match = sinon.match(new Pointer('/setup/rooms/2/area_paths').isEqual)expect(datastore.queue).to.have.been.calledWith(pointer_match, ['/areas/1', '/areas/2'])})})describe('#pull', () => {it("'/setup/rooms/1/area_paths', '/areas/2'", () => {const datastore = new Datastore()datastore.queue = sinon.spy()for (const path in data) {const value = data[path]datastore.set(path, value)}datastore.pull('/setup/rooms/1/area_paths', '/areas/2')const pointer_match = sinon.match(new Pointer('/setup/rooms/1/area_paths').isEqual)expect(datastore.queue).to.have.been.calledWith(pointer_match, ['/areas/1'])})})describe('#add', () => {it("'/setup/rooms', { name: 'Dining Room' }", () => {const datastore = new Datastore()datastore.queue = sinon.spy()for (const path in data) {const value = data[path]datastore.set(path, value)}datastore.add('/setup/rooms', { name: 'Dining Room' })const pointer_match = sinon.match(new Pointer('/setup/rooms/3').isEqual)expect(datastore.queue).to.have.been.calledWith(pointer_match, { name: 'Dining Room' })})})})
/*** Test Dependencies*/import '../helper'import chai, { expect } from 'chai'import sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Linkable, Pipable, Injectable } from 'chainable.js'import { Datastore } from 'datastore.js'const datastore = new Datastore()datastore.write('', {setup: {rooms: {'1': {name: 'Kitchen',source_path: '/components/1/sources/1'}},components: {'1': {sources: {'1': {name: 'Apple TV'}}},'2': {sources: {'1': {name: 'Kaleidescape'}}}}},systems: {'1': {setup: {name: 'System 1'}},'2': {setup: {name: 'System 2'}}}})describe('datastore.chain()', () => {let chaindescribe('.prop("system_path", "systems/1")', () => {before(() => {chain = datastore.chain()})after(() => {chain.destroy()})it('creates 1 injectable', () => {chain.prop('system_path', 'systems/1')expect(chain.props).to.have.keys('system_path')expect(chain.props['system_path']).to.be.instanceof(Injectable)expect(chain.props['system_path'].value).to.eql('systems/1')})it('links with dependents', () => {chain.link('system_name', ':system_path/setup/name')expect(chain.links['system_name'].value).to.eql('System 1')})describe('.prop("system_path", "systems/2")', () => {it('updates dependents', () => {chain.prop('system_path', 'systems/2')expect(chain.props).to.have.keys('system_path')expect(chain.props['system_path']).to.be.instanceof(Injectable)expect(chain.props['system_path'].value).to.eql('systems/2')})it('updates dependents', () => {expect(chain.links['system_name'].value).to.eql('System 2')})})})describe('.link("source_path", "setup/rooms/1/source_path")', () => {before(() => {chain = datastore.chain()})after(() => {chain.destroy()})it('creates 1 chainable', () => {chain.link('source_path', 'setup/rooms/1/source_path')expect(chain.links).to.have.keys('source_path')expect(chain.links['source_path']).to.be.instanceof(Linkable)expect(chain.links['source_path'].value).to.eql('/components/1/sources/1')})describe('.link("source_name", "setup/:source_path/name")', () => {it('creates 2 chainables', () => {chain.link('source_name', 'setup/:source_path/name')expect(chain.links).to.have.keys('source_path', 'source_name')expect(chain.links['source_name']).to.be.instanceof(Linkable)expect(chain.links['source_name'].value).to.eql('Apple TV')})it('updates dependents', () => {datastore.set('/setup/rooms/1/source_path', '/components/2/sources/1')expect(chain.links['source_name'].value).to.eql('Kaleidescape')})it('relinks when called twice', () => {const callback = sinon.spy()chain.link('source_name', 'setup/:source_path/name', callback)expect(callback).not.to.have.been.calleddatastore.set('/setup/rooms/1/source_path', '/components/2/sources/2')expect(callback).to.have.been.called})})describe('.pipe("source_trunk_path", "source_path", "trunk_path")', () => {it('creates 3 chainables', () => {chain.pipe('source_trunk_path', 'source_path', value =>value.split('/').slice(0, 3).join('/'))expect(chain.pipes).to.have.keys('source_trunk_path')expect(chain.pipes['source_trunk_path']).to.be.instanceof(Pipable)expect(chain.pipes['source_trunk_path'].value).to.eql('/components/2')})describe('.pipe("source_trunk_path_length", "source_trunk_path", "trunk_path")', () => {it('creates 4 chainables', () => {chain.pipe('source_trunk_path_length', 'source_trunk_path', 'length')expect(chain.pipes).to.have.keys('source_trunk_path', 'source_trunk_path_length')expect(chain.pipes['source_trunk_path_length']).to.be.instanceof(Pipable)expect(chain.pipes['source_trunk_path_length'].value).to.eql(13)})})})})})
/*** Test Dependencies*/import '../helper'import chai, { expect } from 'chai'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Datastore } from 'datastore/base.js'const datastore = new Datastore()describe('Datastore', () => {describe('#set', () => {it('"/a", true', () => {datastore.set('/a', true)const result = datastore._root['a']expect(result).to.equal(true)})it('"/a/b", true', () => {datastore.set('/a/b', true)const result = datastore._root['a']['b']expect(result).to.equal(true)})it('"/a/b/c", true', () => {datastore.set('/a/b/c', true)const result = datastore._root['a']['b']['c']expect(result).to.equal(true)})})describe('#delete', () => {it('"/a"', () => {datastore.set('/a', true)datastore.delete('/a')const result = datastore._rootexpect(result).to.eql({})})it('"/a/b"', () => {datastore.set('/a/b', true)datastore.delete('/a/b')const result = datastore._rootexpect(result).to.eql({})})it('"/a/b/c"', () => {datastore.set('/a/b/c', true)datastore.delete('/a/b/c')const result = datastore._rootexpect(result).to.eql({})})it('"/a2"', () => {datastore.set('/a1', true)datastore.set('/a2', true)datastore.delete('/a2')const result = datastore._rootexpect(result).to.eql({ a1: true })})})})
/*** Test Dependencies*/import './helper'import chai, { expect } from 'chai'import sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { ChainableGroup, Injectable, Linkable, Pathable, Preferable, Stubbable } from 'chainable.js'import { Datastore } from 'datastore.js'const datastore = new Datastore()datastore.write('', {session: {system_path: '/systems/test',room_path: '/rooms/1'},systems: {test: {setup: {rooms: {'1': {name: 'Kitchen'}}}}},setup: {rooms: {'1': {name: 'Kitchen',source_path: '/components/1/sources/1'},'2': {name: 'Breakfast Room',source_path: '/components/3/sources/1'}},components: {'1': {name: 'Apple TV',sources: {'1': {name: 'Apple TV'}}},'2': {name: 'Kaleidescape Strato',sources: {'1': {name: 'Kaleidescape'}}},'3': {name: 'Sonos CONNECT',sources: {'1': {name: 'Sonos'}}}}}})describe('ChainableGroup', () => {let groupdescribe('.link("source_path", "setup/rooms/1/source_path")', () => {before(() => {group = new ChainableGroup(datastore)})it('creates 1 chainable', () => {group.link('source_path', 'setup/rooms/1/source_path')expect(group.links).to.have.keys('source_path')expect(group.links['source_path']).to.be.instanceof(Linkable)expect(group.links['source_path'].value).to.eql('/components/1/sources/1')})describe('.link("source_name", "setup/:source_path/name")', () => {it('creates 2 chainables', () => {group.link('source_name', 'setup/:source_path/name')expect(group.links).to.have.keys('source_path', 'source_name')expect(group.links['source_name']).to.be.instanceof(Linkable)expect(group.links['source_name'].value).to.eql('Apple TV')})it('updates dependents', () => {datastore.set('/setup/rooms/1/source_path', '/components/2/sources/1')expect(group.links['source_name'].value).to.eql('Kaleidescape')})describe('.link("source_path", "setup/rooms/2/source_path")', () => {it('updates dependents', () => {group.link('source_path', 'setup/rooms/2/source_path')expect(group.links['source_path'].dependents.size).to.eql(1)expect(group.links['source_name'].value).to.eql('Sonos')})})})})describe('.link("system_path", "/session/system_path")', () => {before(() => {group = new ChainableGroup(datastore)})it('creates 1 chainable', () => {group.link('system_path', '/session/system_path')expect(group.links).to.have.keys('system_path')expect(group.links['system_path']).to.be.instanceof(Linkable)expect(group.links['system_path'].value).to.eql('/systems/test')})describe('.link("power", "/:system_path/state/rooms/1/power")', () => {it('creates power chainable', () => {group.link('power', '/:system_path/state/rooms/1/power')expect(group.links).to.have.keys('system_path', 'power')expect(group.links['power']).to.be.instanceof(Linkable)})it('relinks power chainable', () => {group.link('power', `/:system_path/state/rooms/2/power`)expect(group.links['system_path'].dependents.size).to.eql(1)})})})describe('.path("room_setup_path", ":system_path/setup/:room_path")', () => {before(() => {group = new ChainableGroup(datastore)})it('creates 1 chainable', () => {group.link('system_path', 'session/system_path')group.link('room_path', 'session/room_path')group.path('room_setup_path', ':system_path/setup/:room_path')expect(group.paths).to.have.keys('room_setup_path')expect(group.paths['room_setup_path']).to.be.instanceof(Pathable)expect(group.paths['room_setup_path'].value).to.eql('/systems/test/setup/rooms/1')})})describe('.prefer("name", ["source_name", "component_name"], "", callback)', () => {let callbackbefore(() => {group = new ChainableGroup(datastore)callback = sinon.spy()group.link('component_name', 'setup/components/4/name').link('source_name', 'setup/components/4/sources/1/name').prefer('name', ['source_name', 'component_name'], 'Source Name', callback)})it('has a default value', () => {expect(group.prefs).to.have.keys('name')expect(group.prefs['name']).to.be.instanceof(Preferable)expect(group.prefs['name'].value).to.eql('Source Name')expect(group.prefs['name'].callbacks).to.eql([callback])})it('prefers the component name', () => {datastore.set('/setup/components/4/name', 'Xfinity X1')expect(group.prefs['name'].value).to.eql('Xfinity X1')expect(callback).to.have.been.calledWith('Xfinity X1')})it('prefers the source name', () => {datastore.set('/setup/components/4/sources/1/name', 'His Cable')expect(group.prefs['name'].value).to.eql('His Cable')expect(callback).to.have.been.calledWith('His Cable')})it('falls back to the default value', () => {datastore.set('/setup/components/4/name', null)datastore.set('/setup/components/4/sources/1/name', null)expect(group.prefs['name'].value).to.eql('Source Name')expect(callback).to.have.been.calledWith('Source Name')})})describe('.link("source_path", "setup/:room_path/source_path")', () => {before(() => {group = new ChainableGroup(datastore)})it('creates 2 chainables', () => {group.link('source_path', 'setup/:room_path/source_path')expect(group.links).to.have.keys('source_path')expect(group.links['source_path']).to.be.instanceof(Linkable)expect(group.links['source_path'].value).to.be.undefinedexpect(group.stubs).to.have.keys('room_path')expect(group.stubs['room_path']).to.be.instanceof(Stubbable)expect(group.stubs['room_path'].value).to.be.undefined})describe('.prop("room_path", "/rooms/1")', () => {it('replaces 1 chainable', () => {group.prop('room_path', '/rooms/1')expect(group.props).to.have.keys('room_path')expect(group.props['room_path']).to.be.instanceof(Injectable)expect(group.props['room_path'].value).to.eql('/rooms/1')expect(group.stubs).to.be.empty})})/*describe('.link("source_name", "setup/:source_path/name")', () => {it('creates 2 chainables', () => {group.link('source_name', 'setup/:source_path/name')expect(group.links).to.have.keys('source_path', 'source_name')expect(group.links['source_name']).to.be.instanceof(Linkable)expect(group.links['source_name'].value).to.eql('Apple TV')})it('updates dependents', () => {datastore.set('/setup/rooms/1/source_path', '/components/2/sources/1')expect(group.links['source_name'].value).to.eql('Kaleidescape')})describe('.link("source_path", "setup/rooms/2/source_path")', () => {it('updates dependents', () => {group.link('source_path', 'setup/rooms/2/source_path')expect(group.links['source_path'].dependents.size).to.eql(1)expect(group.links['source_name'].value).to.eql('Sonos')})})})*/})})
function coppice(object, prefix = '', result = {}) {for (const key in object) {const path = `${prefix}/${key}`const value = object[key]if (isTraversable(value)) {coppice(value, path, result)} else {result[path] = value}}return result}function isCoppice(value) {if (typeof value === 'object') {for (const key in value) {return key.startsWith('/')}}return false}function isBareObject(object) {return _.isPlainObject(object)}function isEmpty(object) {for (const key in object) {if (isBareObject(object)) {return false}if (Object.prototype.hasOwnProperty.call(object, key)) {return false}}return true}function isTraversable(value) {if (value == null) {return false}if (Array.isArray(value)) {return false}if (typeof value === 'object') {return true}return false}export { coppice, isCoppice, isBareObject, isEmpty, isTraversable }
class TopicTree {constructor() {Object.defineProperties(this, {_root: {value: this.createTreeNode()}})}createTreeNode() {const node = Object.create(null)Object.defineProperties(node, {_value: {writable: true}})return node}// expensive, call rarelyall(func = null, output = [], node = this._root) {if (node._topic && node._value && (!func || func(node))) {output.push([node._topic, node._value])}_.forEach(node, (child, key) => {if (!['_value', '_topic'].includes(key)) {this.all(func, output, child)}})return output}apply(func, node = this._root) {func(node)return _.forEach(node, (child, key) => {if (!['_value', '_topic'].includes(key)) {this.apply(func, child)}})}get(topic) {const steps = topic.split('/')let left = this._rootfor (const step of steps) {left = left[step]if (left == null) {left = this.createTreeNode()break}}return left}getWithDefault(topic, value) {const steps = topic.split('/')let node = this._rootfor (const step of steps) {if (node[step] == null) {node[step] = this.createTreeNode()}node = node[step]}if (node._value == null) {node._topic = topicnode._value = value}return node}add(topic, value) {const node = this.getWithDefault(topic)node._topic = topicnode._value = value}values(topic) {const steps = topic.split('/')return this._values(this._root, steps, 0, []).reverse()}_values(node, steps, pivot, values) {if (steps.length == pivot) {if (node._value != null) {values.push(node._value)}return values}const step = steps[pivot]if (node['#'] != null) {values.push(node['#']._value)}if (node['+'] != null) {values = this._values(node['+'], steps, pivot + 1, values)}if (node[step] != null) {values = this._values(node[step], steps, pivot + 1, values)}return values}entries(topic) {const steps = topic.split('/')return this._entries(this._root, steps, 0, []).reverse()}_entries(node, steps, pivot, entries) {if (steps.length == pivot) {if (node._value != null) {entries.push([node._topic, node._value])}if (node['*'] != null) {entries.push([node['*']._topic, node['*']._value])}return entries}const step = steps[pivot]if (node['#'] != null) {entries.push([node['#']._topic, node['#']._value])}if (node['+'] != null) {entries = this._entries(node['+'], steps, pivot + 1, entries)}if (node[step] != null) {entries = this._entries(node[step], steps, pivot + 1, entries)}return entries}}export { TopicTree }
import { Pointer as Base } from './pointer/base.js'import { DjinList } from './pointer/djinlist.js'class Pointer extends DjinList(Base) {static create(args) {if (args instanceof Pointer) {return args}if (Array.isArray(args) || typeof args === 'string' || args instanceof String) {return new Pointer(args)}if (typeof args === 'object' && Object.prototype.hasOwnProperty.call(args, 'c') && (Object.prototype.hasOwnProperty.call(args, 'p') || Object.prototype.hasOwnProperty.call(args, 't')) && Object.prototype.hasOwnProperty.call(args, 'o')) {return this.createFromMessage(args)}}static createWithDefaults(args, { flag = '', root = '', trunk_path = '' } = {}) {const pointer = Pointer.create(args)if ((flag != '' && pointer.flag == '') || (root != '' && pointer.root == '') || (trunk_path != '' && pointer.trunk_path == '')) {return new Pointer((pointer.trunk_path != '' ? pointer.trunk_path : trunk_path) +(pointer.flag != '' ? `/${pointer.flag}` : flag != '' ? `/${flag}` : '') +(pointer.root != '' ? `/${pointer.root}` : root != '' ? `/${root}` : '') +pointer.branch_path +pointer.twig_path)}return pointer}static createFromMessage(message) {if (message.t != null && message.p == null) {message.p = message.t}if (message.o == 's' || message.o == 'u') {if (message.p == '') {return new Pointer(`${message.c}/#`)}}if (message.p == '') {return new Pointer(message.c)} else if (message.p.startsWith('/')) {return new Pointer(`${message.c}${message.p}`)} else {return new Pointer(`${message.c}/${message.p}`)}}replace(match_string, replace_string) {return new Pointer(this.topic.replace(match_string, replace_string))}queue() {if (this.flag == 'q') return thisreturn this.replace(`/${this.root}/`, `/q/${this.root}/`)}dequeue() {if (this.flag != 'q') return thisreturn this.replace('q/', '/')}concat(...steps) {steps = _.concat(this.steps, steps)return Pointer.create(steps)}changeTrunk(trunk_path) {return this.replace(this.trunk_path, trunk_path)}changeBranch(branch_path) {return this.replace(this.branch_path, branch_path)}slicePath(start, end) {return `/${this.steps.slice(start, end).join('/')}`}sliceTrunk(start, end) {return `/${this.trunk_steps.slice(start, end).join('/')}`}sliceBranch(start, end) {return `/${this.branch_steps.slice(start, end).join('/')}`}slice(begin, end) {return new Pointer(this.steps.slice(begin, end))}}const toPointer = text => {if (text[0] != '/') return textreturn text.replace(/~/g, '~0').replace(/\//g, '~1')}const toPath = text => {if (text.slice(0, 2) != '~1') return textreturn text.replace(/~1/g, '/').replace(/~0/g, '~')}export { Pointer, toPointer, toPath }
const DjinList = superclass =>class extends superclass {get flag() {if (!this._parsed) {this.parse()}if (this._flag_start == null) {return null}return this.steps[this._flag_start]}set flag(value) {if (!this._parsed) {this.parse()}if (this._flag_start == null) {let start = 0if (this._hook_start != null) {start += 1}if (this._grove_start != null) {start += 2}this.steps.splice(start, 0, value)this._parsed = false} else {if (value != null) {this.steps.splice(this._flag_start, 1, value)} else {this.steps.splice(this._flag_start, 1)this._parsed = false}}this.steps = this.steps // eslint-disable-line}get root() {if (!this._parsed) {this.parse()}if (this._root_start == null) {return null}return this.steps[this._root_start]}set root(value) {if (!this._parsed) {this.parse()}if (typeof value === 'string') {switch (true) {case value.startsWith('/'): {value = value.slice(1)}case value.startsWith('q'): {value = value.split('/')}}}if (this._root_start == null) {let start = 0if (this._flag_start != null) {start += 1}if (typeof value === 'string') {this.steps.splice(start, 0, value)} else {this.steps.splice(start, 0, ...value)}this._parsed = false} else {if (typeof value === 'string') {this.steps.splice(this._root_start, 1, value)} else {this.steps.splice(this._root_start, 1, ...value)}this._parsed = value.length > 0}this.steps = this.steps // eslint-disable-line}get trunk_path() {if (!this._parsed) {this.parse()}if (this._trunk_start == null) {return null}return `/${this.steps.slice(this._trunk_start, this._trunk_end).join('/')}`}set trunk_path(value) {const steps = value == null ? [] : value.slice(1).split('/')this.trunk_steps = steps}get trunk_steps() {if (!this._parsed) {this.parse()}if (this._trunk_start == null) {return null}return this.steps.slice(this._trunk_start, this._trunk_end)}set trunk_steps(value) {if (!this._parsed) {this.parse()}if (this._trunk_start == null) {let start = 0if (this._flag_start != null) {start += 1}if (this._root_start != null) {start += 1}this.steps.splice(start, 0, ...value)this._parsed = false} else {this.steps.splice(this._trunk_start, 2, ...value)this._parsed = value.length > 0}this.steps = this.steps // eslint-disable-line}get branch_path() {if (!this._parsed) {this.parse()}if (this._branch_start == null) {return null}return `/${this.steps.slice(this._branch_start, this._branch_end).join('/')}`}set branch_path(value) {const steps = value == null ? [] : value.slice(1).split('/')this.branch_steps = steps}get branch_steps() {if (!this._parsed) {this.parse()}if (this._branch_start == null) {return null}return this.steps.slice(this._branch_start, this._branch_end)}set branch_steps(value) {if (!this._parsed) {this.parse()}if (this._branch_start == null) {let start = 0if (this._hook_start != null) {start += 1}if (this._grove_start != null) {start += 2}if (this._flag_start != null) {start += 1}if (this._root_start != null) {start += 1}this.steps.splice(start, 0, ...value)this._parsed = false} else {const removed = this._branch_end - this._branch_start // FIXME: Remove damned build processes. This ';' shouldn't be needed!this.steps.splice(this._branch_start, removed, ...value)this._parsed = value.length === removed}this.steps = this.steps // eslint-disable-line}get twig_path() {if (!this._parsed) {this.parse()}if (this._twig_start == null) {return null}return `/${this.steps.slice(this._twig_start, this._twig_end).join('/')}`}get twig_steps() {if (!this._parsed) {this.parse()}if (this._twig_start == null) {return null}return this.steps.slice(this._twig_start, this._twig_end)}get leaf() {if (!this._parsed) {this.parse()}if (this._twig_start == null) {return null}return this.steps[this.steps.length - 1]}set leaf(value) {if (!this._parsed) {this.parse()}if (value == null) {if (this.leaf != null) {this.steps = this.steps.slice(0, -1)this._twig_start = nullthis._twig_end = null}return}if (this.leaf == null) {this._twig_start = this.steps.lengththis._twig_end = this._twig_start + 1}this.steps[this._twig_end - 1] = valuethis.steps = this.steps // eslint-disable-line}parse() {let cursor = 0Object.defineProperties(this, {_flag_start: {writable: true,value: undefined},_root_start: {writable: true,value: undefined},_trunk_start: {writable: true,value: undefined},_trunk_end: {writable: true,value: undefined},_branch_start: {writable: true,value: undefined},_branch_end: {writable: true,value: undefined},_twig_start: {writable: true,value: undefined},_twig_end: {writable: true,value: undefined}})switch (this.steps[cursor]) {case 'q':this._flag_start = cursorcursor += 1breakcase 'session':this._root_start = cursorcursor += 1this.parseSession(cursor)return}switch (this.steps[cursor]) {case 'action':case 'state':case 'setup':case '+':this._root_start = cursorcursor += 1break}switch (this.steps[cursor]) {case 'users':case 'events':case 'items':this._trunk_start = cursorthis._branch_start = cursorthis._trunk_end = cursor + 2breakcase 'admin':this._trunk_start = cursorthis._branch_start = cursorthis._trunk_end = cursor + 1breakcase 'services':this._trunk_start = cursorthis._trunk_end = cursor + 1cursor += 1this.parseService(cursor)return}const limit = this.steps.length - 1while (cursor < limit) {const collection = this.steps[cursor]const id = this.steps[cursor + 1]if (!(/^(?:\w+|\+)$/.test(collection) && /^(?:\d+|\+|[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}|\w{22})$/i.test(id))) {break}cursor += 2this._branch_end = cursor}if (cursor < this.steps.length) {this._twig_start = cursorthis._twig_end = this.steps.length}this._parsed = true}parseSession(cursor) {switch (this.steps[cursor]) {case 'path':case 'redirect':case 'user':this.twig_start = cursorcursor += 1this.twig_end = this.steps.lengthreturncase 'services':this._trunk_start = cursorthis._trunk_end = cursor + 1cursor += 1this.parseService(cursor)return}this._parsed = true}parseService(cursor) {switch (this.steps[cursor]) {case 'spotify':case 'apple-music':this._branch_start = cursorthis._branch_end = cursor + 1cursor += 1break}const limit = this.steps.length - 1while (cursor < limit) {const collection = this.steps[cursor]const id = this.steps[cursor + 1]if (!(/^(?:\w+|\+)$/.test(collection) && /^(?:\d+|\+|[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12})$/i.test(id))) {break}cursor += 2this._branch_end = cursor}if (cursor < this.steps.length) {this._twig_start = cursorthis._twig_end = this.steps.length}this._parsed = true}}export { DjinList }
class Pointer {constructor(path_steps_topic) {this.isEqual = this.isEqual.bind(this)this.clear()if (Array.isArray(path_steps_topic)) {this.steps = path_steps_topicreturn}if (typeof path_steps_topic === 'string') {if (path_steps_topic.startsWith('/') || path_steps_topic === '') {this.path = path_steps_topic} else {this.topic = path_steps_topic}return}throw new TypeError(`Pointer path_steps_topic must be an array or string not a ${typeof path_steps_topic}`)}get path() {if (typeof this._path === 'string') {return this._path}if (Array.isArray(this._steps)) {if (this.steps.length === 0) {this._path = ''} else {this._path = `/${this._steps.join('/')}`}return this._path}if (typeof this._topic === 'string') {if (this._topic === '#') {this._path = ''} else {this._path = `/${this._topic}`}return this._path}throw new TypeError('Pointer must have path or steps or topic.')}set path(value) {if (typeof value === 'string') {this.clear()this._path = valuereturn}throw new TypeError('Pointer path must be a string.')}get steps() {if (Array.isArray(this._steps)) {return this._steps}if (typeof this._path === 'string') {if (this._path === '') {this._steps = []} else {this._steps = this._path.slice(1).split('/')}return this._steps}if (typeof this._topic === 'string') {if (this._topic === '#') {this._steps = []} else {this._steps = this._topic.split('/')}return this._steps}throw new TypeError('Pointer must have path or steps or topic.')}set steps(value) {if (Array.isArray(value)) {this.clear()if (value[0] === '#') {this._steps = []} else {this._steps = value}return}throw new TypeError('Pointer steps must be an array.')}get topic() {if (typeof this._topic === 'string') {return this._topic}if (typeof this._path === 'string') {if (this._path === '') {this._topic = '#'} else {this._topic = this._path.slice(1)}return this._topic}if (Array.isArray(this._steps)) {if (this.steps.length === 0) {this._topic = '#'} else {this._topic = this._steps.join('/')}return this._topic}throw new TypeError('Pointer must have path or steps or topic.')}set topic(value) {if (typeof value === 'string') {this.clear()this._topic = valuereturn}throw new TypeError('Pointer topic must be a string.')}get is_wildcard() {if (typeof this._is_wildcard !== 'boolean') {this._is_wildcard = this.topic.endsWith('#') || this.topic.endsWith('/*') || this.topic.indexOf('/+') > -1}return this._is_wildcard}toString() {return this.path}isEqual(other) {return this.path === other.path}get length() {return this.steps.length}clone() {return Pointer.create(this.path)}slice(index) {return Pointer.create(this.steps.slice(index))}clear() {Object.defineProperties(this, {_path: {enumerable: false,writable: true,value: undefined},_steps: {enumerable: false,writable: true,value: undefined},_topic: {enumerable: false,writable: true,value: undefined},_is_wildcard: {enumerable: false,writable: true,value: undefined}})}}export { Pointer }
export { Datastore as Base } from './datastore/base.js'export { External } from './datastore/external.js'export { Convenience } from './datastore/convenience.js'export { PubSub } from './datastore/pubsub.js'export { Chain } from './datastore/chain.js'export { Hooks } from './datastore/hooks.js'export { Datastore, DatastoreWithHooks } from './datastore.js'export { Pointer, toPointer, toPath } from './pointer.js'export { coppice, isCoppice } from './utils.js'export { TopicTree } from './topic_tree.js'
import { Datastore as Base } from './datastore/base.js'import { External } from './datastore/external.js'import { Convenience } from './datastore/convenience.js'import { PubSub } from './datastore/pubsub.js'import { Chain } from './datastore/chain.js'import { Hooks } from './datastore/hooks.js'class Datastore extends Chain(PubSub(Convenience(External(Base)))) {}class DatastoreWithHooks extends Hooks(Chain(PubSub(Convenience(External(Base))))) {}export { Datastore, DatastoreWithHooks}
import { Pointer } from '../pointer.js'import { TopicTree } from '../topic_tree.js'const PubSub = superclass =>class extends superclass {constructor() {super()Object.defineProperties(this, {_topic_tree: {value: new TopicTree()},_subscribers_map: {value: new Map()}})}publish(pointer, value) {// console.log(`Datastore #publish ` + pointer.path)const topic = pointer.topicconst subscription_maps = this._topic_tree.entries(topic)if (subscription_maps == null) {return}subscription_maps.forEach(entry => {const [topic, subscription_map] = entryif (subscription_map) {subscription_map.forEach((callback, subscriber) => {callback.call(subscriber, topic, pointer, value)})}})const steps = pointer.steps.slice()while (steps.pop()) {const path = `/${steps.join('/')}`const subtopic = `${steps.join('/')}/*`const subscription_maps = this._topic_tree.entries(subtopic)const node_value = this.get(path)subscription_maps.filter(([topic]) => topic && topic.endsWith('*')).forEach(entry => {const [topic, subscription_map] = entrysubscription_map.forEach((callback, subscriber) => {callback.call(subscriber, topic, pointer, node_value)})})}}subscribe(topic, subscriber, callback, options = { immediate: true }) {// console.log(`Datastore #subscribe ` + topic)if (typeof topic !== 'string') {throw new TypeError('topic must be a string')}if (topic.startsWith('/')) {throw new TypeError(`topic ${topic} must be an MQTT-style topic`)}// Convert JSON Pointer to MQTT topicsconst subscription_map = this._topic_tree.getWithDefault(topic, new Map())._valuesubscription_map.set(subscriber, callback)const subscriber_set = (() => {if (this._subscribers_map.has(subscriber)) {return this._subscribers_map.get(subscriber)} else {return new Set()}})()subscriber_set.add(topic)this._subscribers_map.set(subscriber, subscriber_set)if (!options.immediate) {return}if (topic.endsWith('/*')) {topic = topic.slice(0, -2)}let pointer = Pointer.create(topic)if (!pointer.is_wildcard) {const results = this.read(pointer)callback(topic, pointer, results)return}const results = this.read(pointer, { coppiced: true })console.log(results)for (const path in results) {callback(topic, Pointer.create(path), results[path])}}once(topic, callback) {const nonce = Object.create(null)const wrapper = (topic, pointer, value) => {if (typeof value === 'undefined') returncallback(topic, pointer, value)this.unsubscribe(topic, nonce)}this.subscribe(topic, nonce, wrapper)}unsubscribe(topic, subscriber) {if (topic == null) {const subscriber_set = this._subscribers_map.get(subscriber)if (subscriber_set == null) returnsubscriber_set.forEach(topic => {this.unsubscribe(topic, subscriber)})return}if (typeof topic !== 'string') {throw new TypeError('topic must be a string')}if (topic.startsWith('/')) {throw new TypeError(`topic ${topic} must be an MQTT-style topic`)}const subscription_map = this._topic_tree.get(topic)._valueif (subscription_map == null) {return}subscription_map.delete(subscriber)}}export { PubSub }
import { Pointer } from '../pointer.js'import { isEmpty, isBareObject } from '../utils.js'const Hooks = superclass =>class extends superclass {set(pointer, value, { force = false, silent = false } = {}) {pointer = Pointer.create(pointer)if (pointer == null) {return}if (value == null) {this.delete(pointer, { force, silent })return}if (pointer.length === 0) {return}if (!force && _.isEqual(value, this.get(pointer))) {return}if (!silent) {this.publish(pointer, value, 'before')this.publish(pointer, value, 'async')}const last = pointer.steps.slice(-1)[0]let left = this._rootpointer.steps.slice(0, -1).forEach((step, i) => {if (!isBareObject(left[step])) {const subpointer = Pointer.create(pointer.steps.slice(0, i + 1))const object = Object.create(null)left[step] = objectif (!silent) {this.publish(subpointer, object, 'when')}}left = left[step]})left[last] = valueif (!silent) {this.publish(pointer, value, 'when')this.publish(pointer, value, 'after')}}delete(pointer, { force = false, silent = false } = {}) {pointer = Pointer.create(pointer)if (pointer == null) {return}if (pointer.length === 0) {this.clear()return}if (!force && this.get(pointer) == null) {return}if (!silent) {this.publish(pointer, null, 'async')this.publish(pointer, null, 'before')}for (let i = pointer.length - 1; i > -1; i--) {const subpointer = pointer.slice(0, i)const right = this.get(subpointer)if (isBareObject(right)) {const key = pointer.steps[i]delete right[key]if (!isEmpty(right)) {break}}}if (!silent) {this.publish(pointer, null, 'when')this.publish(pointer, null, 'after')}}publish(pointer, value, phase) {const topic = `${phase}/${pointer.topic}`const subscription_maps = this._topic_tree.entries(topic)if (subscription_maps == null) {return}subscription_maps.forEach(entry => {const [topic, subscription_map] = entrysubscription_map.forEach((callback, subscriber) => {callback.call(subscriber, topic, pointer, value)})})const steps = pointer.steps.slice()while (steps.pop()) {const path = `/${steps.join('/')}`const subtopic = `${phase}${path}/*`const subscription_maps = this._topic_tree.entries(subtopic)const node_value = this.get(path)subscription_maps.filter(([topic]) => topic.endsWith('*')).forEach(entry => {const [topic, subscription_map] = entrysubscription_map.forEach((callback, subscriber) => {callback.call(subscriber, topic, pointer, node_value)})})}}subscribe(topic, subscriber, callback, options = { immediate: true }) {if (typeof topic !== 'string') {throw new TypeError('topic must be a string')}if (topic.startsWith('/')) {throw new TypeError(`topic ${topic} must be an MQTT-style topic`)}// Convert JSON Pointer to MQTT topicsconst subscription_map = this._topic_tree.getWithDefault(topic, new Map())._valuesubscription_map.set(subscriber, callback)const subscriber_set = (() => {if (this._subscribers_map.has(subscriber)) {return this._subscribers_map.get(subscriber)} else {return new Set()}})()subscriber_set.add(topic)this._subscribers_map.set(subscriber, subscriber_set)if (!options.immediate) {return}if (topic.endsWith('/*')) {topic = topic.slice(0, -2)}let pointer = Pointer.create(topic).slice(1)if (!pointer.is_wildcard) {const results = this.read(pointer)callback(topic, pointer, results)return}const results = this.read(pointer, { coppiced: true })for (const path in results) {callback(topic, Pointer.create(path), results[path])}}}export { Hooks }
import { Pointer } from '../pointer.js'import { coppice, isBareObject, isTraversable } from '../utils.js'const External = superclass =>class extends superclass {read(pointer, { coppiced = false } = {}) {pointer = Pointer.create(pointer)if (pointer == null) {return}if (pointer.is_wildcard && pointer.path !== '') {return this._search(this._root, pointer.steps, 0, {})}const value = this.get(pointer)if (coppiced) {return coppice(value, pointer.path)}return value}_search(node, steps, pivot, results) {if (steps.length === pivot) {return results}const step = steps[pivot]if (step === '+') {if (isTraversable(node)) {for (const key in node) {steps[pivot] = keyconst next = node[key]if (steps.length - 1 === pivot) {const path = `/${steps.join('/')}`results[path] = nextcontinue}if (isBareObject(next)) {results = this._search(next, steps, pivot + 1, results)}}}steps[pivot] = '+'return results}if (step === '#') {const path = `/${steps.slice(0, pivot).join('/')}`const subtree = this.get(path)if (isBareObject(subtree)) {results = coppice(subtree, path, results)}return results}const next = node[step]if (next === undefined) {return results}if (steps.length - 1 === pivot) {const path = `/${steps.join('/')}`results[path] = nextreturn results}return this._search(next, steps, pivot + 1, results)}write(pointer, value, { force = false } = {}) {pointer = Pointer.create(pointer)if (pointer == null) {return}if (pointer.flag === 'q' && typeof this.queue === 'function') {this.queue(pointer, value)return}if (!isTraversable(value)) {this.set(pointer, value, { force: force })return}this.delete(pointer, { force: force })const copse = coppice(value, pointer.path)for (const path in copse) {const value = copse[path]this.set(path, value, { force: force })}}merge(pointer, value, { force = false } = {}) {pointer = Pointer.create(pointer)if (pointer == null) {return}if (pointer.flag === 'q' && typeof this.queue === 'function') {this.queue(pointer, value)return}if (!isTraversable(value)) {this.set(pointer, value, { force: force })return}const copse = coppice(value, pointer.path)for (const path in copse) {const value = copse[path]this.set(path, value, { force: force })}}destroy(pointer, { force = false } = {}) {pointer = Pointer.create(pointer)if (pointer == null) {return}this.delete(pointer, { force: force })}}export { External }
import { Pointer } from '../pointer.js'import { isBareObject, isEmpty, coppice } from '../utils.js'const Convenience = superclass =>class extends superclass {has(pointer) {pointer = Pointer.create(pointer)if (pointer == null) {return}return this.get(pointer) != null}each(pointer, callback) {pointer = Pointer.create(pointer)if (pointer == null) {return}const node = this.get(pointer)if (!Array.isArray(node)) {return}node.forEach(item => {callback(item)})}includes(pointer, value) {pointer = Pointer.create(pointer)if (pointer == null) {return}const node = this.get(pointer)if (!Array.isArray(node)) {return false}return node.includes(value)}any(pointer) {pointer = Pointer.create(pointer)if (pointer == null) {return}const node = this.get(pointer)if (isBareObject(node)) {return !isEmpty(node)}return false}keys(pointer) {pointer = Pointer.create(pointer)if (pointer == null) {return []}const node = this.get(pointer)if (isBareObject(node)) {return Object.keys(node) || []}return []}branchPaths(pointer) {pointer = Pointer.create(pointer)const object = this.get(pointer)if (!isBareObject(object)) {return []}const copse = coppice(object, pointer.path)const paths = new Set()for (const path in copse) {const subpointer = Pointer.create(path)paths.add(subpointer.branch_path)}return Array.from(paths)}mark(pointer) {pointer = Pointer.create(pointer)if (pointer == null) {return}pointer.root = ['action']this.queue(pointer, Date.now())}queue(pointer, value) {pointer = Pointer.create(pointer)if (pointer == null) {return}pointer.flag = 'q'if (!isBareObject(value)) {this.set(pointer, value, { force: true })this.delete(pointer, { silent: true })return}const copse = coppice(value, pointer.path)for (const path in copse) {const value = copse[path]this.set(path, value, { force: true })this.delete(path, { silent: true })}}push(pointer, value, queue = true) {pointer = Pointer.create(pointer)if (pointer == null) {return}const existing = this.get(pointer) || []if (!Array.isArray(existing)) {return}const modified = _.uniq(_.concat(existing, value))if (queue) {this.queue(pointer, modified)} else {this.merge(pointer, modified)}}pull(pointer, value, queue = true) {pointer = Pointer.create(pointer)if (pointer == null) {return}const existing = this.get(pointer) || []if (!Array.isArray(existing)) {return}const modified = _.without(existing, value)if (queue) {this.queue(pointer, modified)} else {this.merge(pointer, modified)}}add(pointer, value, queue = true) {pointer = Pointer.create(pointer)if (pointer == null) {return}const collection = this.get(pointer) || {}if (typeof collection !== 'object') {return}let id = 1while (collection[id.toString()] != null) {id++}const added = Pointer.create(`${pointer.path}/${id}`)if (queue) {this.queue(added, value)} else {this.merge(added, value)}return id}}export { Convenience }
import { ChainableGroup } from '../chainable.js'const Chain = superclass =>class extends superclass {chain() {return new ChainableGroup(this)}}export { Chain }
import { Pointer } from '../pointer.js'import { isEmpty, isBareObject } from '../utils.js'class Datastore {constructor() {Object.defineProperties(this, {_root: {value: Object.create(null)}})}get(pointer) {pointer = Pointer.create(pointer)if (pointer == null) {return}let left = this._rootfor (const step of pointer.steps) {left = left[step]if (left == null) {break}}return left}set(pointer, value, { force = false, silent = false } = {}) {pointer = Pointer.create(pointer)if (pointer == null) {return}if (value == null) {this.delete(pointer, { force, silent })return}if (pointer.length === 0) {return}if (!force && _.isEqual(value, this.get(pointer))) {return}const last = pointer.steps.slice(-1)[0]let left = this._rootpointer.steps.slice(0, -1).forEach((step, i) => {if (!isBareObject(left[step])) {const subpointer = Pointer.create(pointer.steps.slice(0, i + 1))const object = Object.create(null)left[step] = object// if (!silent) {// this.publish(subpointer, object)// }}left = left[step]})left[last] = valueif (!silent) {this.publish(pointer, value)}}delete(pointer, { force = false, silent = false } = {}) {pointer = Pointer.create(pointer)if (pointer == null) {return}if (pointer.length === 0) {this.clear()return}if (!force && this.get(pointer) == null) {return}for (let i = pointer.length - 1; i > -1; i--) {const subpointer = pointer.slice(0, i)const right = this.get(subpointer)if (isBareObject(right)) {const key = pointer.steps[i]delete right[key]if (!isEmpty(right)) {break}}}if (!silent) {this.publish(pointer, null)}}clear() {for (const key in this._root) {this.delete([key])}}// publish(pointer, value, phase) {}publish() {}}export { Datastore }
import { Injectable } from './chainable/injectable.js'import { Linkable } from './chainable/linkable.js'import { Pathable } from './chainable/pathable.js'import { Pipable } from './chainable/pipable.js'import { Preferable } from './chainable/preferable.js'import { Stubbable } from './chainable/stubbable.js'class ChainableGroup {constructor(datastore) {this.datastore = datastoreObject.defineProperties(this, {links: {value: {}},paths: {value: {}},pipes: {value: {}},prefs: {value: {}},props: {value: {}},stubs: {value: {}}})this.get = this.get.bind(this)this.set = this.set.bind(this)this.keys = this.keys.bind(this)}link(name, topic_or_path, ...callbacks) {if (this.links[name] instanceof Linkable) {const notify = this.links[name].relink(topic_or_path, callbacks)if (notify) {this.notify('relink', name)}return this}this.links[name] = new Linkable(this, name, topic_or_path, ...callbacks)this.notify('link', name)return this}path(name, topic_or_path, ...callbacks) {if (this.paths[name] instanceof Pathable) {const notify = this.paths[name].repath(topic_or_path, callbacks)if (notify) {this.notify('repath', name)}return this}this.paths[name] = new Pathable(this, name, topic_or_path, ...callbacks)this.notify('path', name)return this}pipe(to_name, from_name, replacement, ...callbacks) {const pipe = this.pipes[to_name]if (pipe instanceof Pipable) {pipe.callbacks = callbackspipe.update(pipe.value)return this}this.pipes[to_name] = new Pipable(this, to_name, from_name, replacement, ...callbacks)this.notify('pipe', 'name')return this}prefer(name, prefs, default_value, ...callbacks) {if (this.prefs[name] instanceof Preferable) {throw new Error(`prefer("${name}", ...) has already been called.`)}this.prefs[name] = new Preferable(this, name, prefs, default_value, ...callbacks)this.notify('prefer', name)return this}prop(name, value) {if (this.props[name] instanceof Injectable) {this.props[name].update(value)return this}const prop = (this.props[name] = new Injectable(this, name, value))const stub = this.stubs[name]if (stub instanceof Stubbable) {prop.dependents = stub.dependentsdelete this.stubs[name]}this.notify('prop', name)return this}addDependent(name, dependent, position) {let chainable = this.links[name] || this.pipes[name] || this.prefs[name] || this.props[name]if (chainable == null) {chainable = this.stubs[name] = new Stubbable(this, name)}chainable.addDependent(dependent, position)}notify(method, group_name) {for (const name in this.props) {const chainable = this.props[name]chainable.notify()}for (const name in this.links) {const chainable = this.links[name]chainable.notify()}for (const name in this.pipes) {const chainable = this.pipes[name]chainable.notify()}}destroy() {for (const name in this.links) {const chainable = this.links[name]chainable.destroy()}for (const name in this.pipes) {const chainable = this.pipes[name]chainable.destroy()}}get(name) {const path = _.get(this.links, [name, 'topic'], _.get(this.props, [name, 'value']))if (typeof path !== 'string') {return}return this.datastore.get(path)}set(name, value) {const path = _.get(this.links, [name, 'topic'], _.get(this.props, [name, 'value']))if (typeof path !== 'string') {return}this.datastore.set(path, value)}keys(name) {const path = _.get(this.links, [name, 'topic'], _.get(this.props, [name, 'value']))if (typeof path !== 'string') {return []}return this.datastore.keys(path)}queue(name, value) {const path = _.get(this.links, [name, 'topic'], _.get(this.props, [name, 'value']))if (typeof path !== 'string') {return}this.datastore.queue(path, value)}mark(name) {const path = _.get(this.links, [name, 'topic'], _.get(this.props, [name, 'value']))if (typeof path !== 'string') {return}this.datastore.mark(path)}push(name, value, queue = true) {const path = _.get(this.links, [name, 'topic'], _.get(this.props, [name, 'value']))console.log('pushing', name, path, value, queue)if (typeof path !== 'string') {return}console.log('pushing', path, value, queue)this.datastore.push(path, value, queue)}pull(name, value) {const path = _.get(this.links, [name, 'topic'], _.get(this.props, [name, 'value']))if (typeof path !== 'string') {return}this.datastore.pull(path, value)}add(name, value) {const path = _.get(this.links, [name, 'topic'], _.get(this.props, [name, 'value']))if (typeof path !== 'string') {return}return this.datastore.add(path, value)}read(name, value) {const path = _.get(this.links, [name, 'topic'], _.get(this.props, [name, 'value']))if (typeof path !== 'string') {return}return this.datastore.read(path, value)}}export { ChainableGroup, Injectable, Linkable, Pathable, Pipable, Preferable, Stubbable }
import { Base } from './base.js'class Stubbable extends Base {constructor(group, name) {super(group, name)}}export { Stubbable }
import { Base } from './base.js'class Preferable extends Base {constructor(group, name, preferences, default_value, ...callbacks) {super(group, name, ...callbacks)this.steps = preferencesthis.default_value = default_valuethis.steps.forEach((step, i) => {this.group.addDependent(step, this, i)})}toString() {return `[Preferable ${JSON.stringify(this.name)}]`}update() {this.value = this.default_valuefor (const step of this.steps) {if (step != null) {this.value = stepbreak}}if (this.callbacks.length > 0) {this.callbacks.forEach(callback => {callback(this.value)})}this.notify()}}export { Preferable }
import { Base } from './base.js'class Pipable extends Base {constructor(group, name, from_name, replacement, ...callbacks) {super(group, name, ...callbacks)this.from_name = from_namethis.replacement = replacementthis.group.addDependent(this.from_name, this, null)}toString() {return `[Pipable ${JSON.stringify(this.name)}]`}update(value) {if (typeof this.replacement === 'string') {if (value == null) {return}this.value = value[this.replacement]if (this.callbacks.length > 0) {this.callbacks.forEach(callback => {callback(this.value)})}this.notify()return}if (typeof this.replacement === 'function') {if (value === undefined) {return}const next = this.replacement.call(this, value)if (next === this.value) returnthis.value = nextconsole.log(`${this}.update()`, this.value)if (this.callbacks.length > 0) {this.callbacks.forEach(callback => {callback(this.value)})}this.notify()}}}export { Pipable }
import { Pointer } from '../pointer.js'import { Base } from './base.js'class Pathable extends Base {constructor(group, name, topic_or_path, ...callbacks) {super(group, name, ...callbacks)this.repath(topic_or_path, callbacks)}toString() {return `[Pathable ${JSON.stringify(this.name)}]`}repath(topic_or_path, callbacks) {this.callbacks = callbacksif (this.topic_or_path === topic_or_path) {return false}this.topic_or_path = topic_or_paththis.pointer = Pointer.create(topic_or_path)this.steps = []this.is_static = truethis.pointer.steps.forEach((step, i) => {if (step.startsWith(':')) {step = step.slice(1)this.is_static = falsethis.group.addDependent(step, this, i)this.steps.push(null)} else {this.steps.push(step)}})this.update()return true}update() {let path = ''for (const step of this.steps) {if (step) {if (step.startsWith('/')) {path = `${path}${step}`} else {path = `${path}/${step}`}}}this.value = pathif (!this.value) {return}if (this.callbacks.length > 0) {this.callbacks.forEach(callback => {callback(this.value)})}this.notify()}destroy() {if (typeof this.topic !== 'string') {return}this.datastore.unsubscribe(this.topic, this)}}export { Pathable }
import { Pointer } from '../pointer.js'import { Base } from './base.js'class Linkable extends Base {constructor(group, name, topic_or_path, ...callbacks) {super(group, name, ...callbacks)this.relink(topic_or_path, callbacks)}toString() {return `[Linkable ${JSON.stringify(this.name)}]`}relink(topic_or_path, callbacks) {if (callbacks.length > 1) {if (callbacks.slice(-1)[0] === true) {callbacks = callbacks.slice(0, -1)this.topic_or_path = null // force relink}}this.callbacks = callbacksif (this.topic_or_path === topic_or_path) {return false}this.topic_or_path = topic_or_paththis.pointer = Pointer.create(topic_or_path)this.steps = []this.is_static = truethis.pointer.steps.forEach((step, i) => {if (step.startsWith(':')) {step = step.slice(1)this.is_static = falsethis.group.addDependent(step, this, i)this.steps.push(null)} else {this.steps.push(step)}})this.update()return true}update() {if (this.topic != null) {this.datastore.unsubscribe(this.topic, this)}this.topic = ''for (const step of this.steps) {if (typeof step !== 'string') {delete this.topicreturn}if (this.topic != '') {this.topic = `${this.topic}/`}if (step.startsWith('/')) {this.topic = `${this.topic}${step.slice(1)}`} else {this.topic = `${this.topic}${step}`}}this.datastore.subscribe(this.topic, this, (topic, pointer, value) => {this.value = valueif (this.callbacks.length > 0) {this.callbacks.forEach(callback => {callback(this.value, pointer)})}this.notify()})}destroy() {if (typeof this.topic !== 'string') {return}this.datastore.unsubscribe(this.topic, this)}}export { Linkable }
import { Base } from './base.js'class Injectable extends Base {constructor(group, name, value) {super(group, name)this.value = value}toString() {return `[Injectable ${JSON.stringify(this.name)}]`}update(value) {this.value = valuethis.notify()}}export { Injectable }
class Base {constructor(group, name, ...callbacks) {this.group = groupthis.name = namethis.callbacks = callbacksthis.datastore = this.group.datastore}addDependent(dependent, position) {if (!(this.dependents instanceof Map)) {this.dependents = new Map()}this.dependents.set(dependent, position)}notify() {if (!(this.dependents instanceof Map)) {return}this.dependents.forEach((position, dependent) => {if (position != null) {// Linkable, Preferable, Pathabledependent.steps[position] = this.valuedependent.update()} else {// Pipabledependent.update(this.value)}})}destroy() {}}export { Base }
{"name": "@djinlist/datastore","version": "0.0.0","type": "module","license": "Apache-2.0","private": true,"main": "src/index.js","svelte": "src/index.js","dependencies": {"lodash": "^4.17.15"}}
import Benchmark from 'benchmark'const suite = new Benchmark.Suite()function isEmpty1(obj) {for (const prop in obj) {if (Object.prototype.hasOwnProperty.call(obj, prop)) {return false}}return true}function isEmpty2(obj) {return Object.getOwnPropertyNames(obj).length === 0}suite.add('for ... in', function() {isEmpty1({ a: 1, b: 2, c: 3 })})suite.add('keys', function() {isEmpty2({ a: 1, b: 2, c: 3 })})suite.on('abort', function() {console.log('Aborted')})suite.on('error', function(event) {console.log(String(JSON.stringify(event)))})suite.on('cycle', function(event) {console.log(String(event.target))})suite.on('complete', function() {console.log('Fastest is ' + this.filter('fastest').map('name'))})suite.run()
import { Pointer } from '@djinlist/datastore'import { v4 as uuidv4 } from 'uuid'import net from 'net'const RECONNECT_TIME = 5 // secondsclass IPCClient {constructor(socket_path, client_id, { subscriptions = [], publications = [] } = {}) {this.name = socket_path.split('/').slice(-1)this.client_id = client_idthis.destroyed = falsethis.socket_path = socket_paththis.readers = {}this.subscriptions = subscriptions || []this.publications = publications || []this.debounceConnect = _.debounce(this.connect.bind(this), 1000, {leading: true,trailing: false})this.debounceConnect()}emptyPromise() {return new Promise(resolve => resolve(undefined))}subscribe(pointer, immediate = true) {pointer = Pointer.create(pointer)// console.log(`SUBSCRIBE (R): ${pointer.path} ${immediate}`)this.subscriptions = _.union(this.subscriptions, [pointer.topic])return immediate ? this.read(pointer) : this.emptyPromise()}unsubscribe(pointer) {pointer = Pointer.create(pointer)// console.log(`UNSUBSCRIBE (R): ${pointer.path}`)this.subscriptions = _.without(this.subscriptions, pointer.topic)}async read(pointer) {pointer = Pointer.create(pointer)// console.log(`READ (R): ${pointer.path}`)return new Promise(resolve => {message = {o: 'r',p: pointer.path,id: new uuidv4()}this.readers[uuid] = resolvethis.send(message)})}async onRead(message) {const resolver = this.readers(message.id)if (!resolver) returnpointer = Pointer.create(message.p)resolver(pointer, message.v)}async write(pointer, value) {pointer = Pointer.create(pointer)// console.log(`WRITE: ${pointer.path}`)const message = {o: 'w',p: pointer.path,v: value}this.send(message)}async merge(pointer, value) {pointer = Pointer.create(pointer)// console.log(`MERGE: ${pointer.path}`)const message = {o: 'm',p: pointer.path,v: value}this.send(message)}async destroy(pointer) {pointer = Pointer.create(pointer)// console.log(`DESTROY: ${pointer.path}`)const message = {o: 'd',p: pointer.path}this.send(message)}/** Connect*/delayConnect() {if (this.connect_timeout) returnconsole.log(`Reconnecting in ${RECONNECT_TIME}s.`)this.connect_timeout = setTimeout(() => {this.debounceConnect()this.connect_timeout = null}, RECONNECT_TIME * 1000)}connect() {if (this.socket && (this.socket.connecting || this.socket.connected)) returntry {console.log(`connect() ${this.socket_path}`)this.socket = net.createConnection(this.socket_path, () => {console.log('Connected')this.socket.connected = truethis.subscriptions.forEach(subscription => {try {const topic = subscription[0]const immediate = subscription[1]this.subscribe(topic, immediate)} catch (e) {console.log(`Error subscribing to ${subscription}:\n${e.stack}`)console.log('Subscriptions should be `[["topic/1", immediate], ["topic/2", immediate]]')}})this.publications.forEach(publication => {try {this.subscribe(publication)} catch (e) {console.log(`Error subscribing to ${publication}:\n${e.stack}`)console.log('Publications should be `["topic/1", "topic/2"]')}})})this.socket.data_buffer = new Buffer.alloc(0)this.socket.on('data', data => {this.socket.data_buffer = Buffer.concat([this.socket.data_buffer, data])this.readBuffer()})this.socket.on('error', e => {console.log(`Error in socket:\n${e.stack}`)})this.socket.on('close', erred => {console.log('Closed')this.socket.connected = falsetry {this.socket.destroy()} catch (e) {console.log(`Error destroying socket:\n${e.stack}`)}if (erred) {console.log('Socket closed due to an error')}this.delayConnect()})} catch (e) {console.log(`Error connecting to socket:\n${e.stack}`)this.delayConnect()}}/** Send*/send(message) {const payload = JSON.stringify(message)// console.log(`#send() ${payload}`)let header = []for (let i = 0; i <= 3; i++) {header.push(Math.floor(Buffer.byteLength(payload, 'utf-8') / Math.pow(256, 3 - i)) % 256)}const buffer = Buffer.concat([Buffer.from(header), Buffer.from(payload)])try {this.socket && this.socket.write(buffer)} catch (e) {console.log(e)}}/** Receive*/getMessageLength(buffer) {// console.log(`#getMessageLength() ${readable(buffer.slice(0, 4).toString())}`)return buffer.readUInt32BE(0)}readBuffer() {if (this.socket.reading) returnthis.socket.reading = truewhile (this.socket.data_buffer.length >= 4) {const length = this.getMessageLength(this.socket.data_buffer)if (this.socket.data_buffer.length < 4 + length) breakconst encoded = this.socket.data_buffer.slice(4, length + 4)this.socket.data_buffer = this.socket.data_buffer.slice(length + 4)try {const message = JSON.parse(encoded)const pointer = Pointer.create(message)if (message.o !== 'p' && pointer.leaf !== 'last_seen' && pointer.leaf !== 'log') {console.log(`#parse ${encoded}`)}this.parse(message)} catch (e) {console.log(`Error processing message:\n${e.stack}`)console.log(` Buffer: '${readable(this.socket.data_buffer.toString())}'`)console.log(` Clearing buffer`)this.socket.data_buffer = new Buffer.alloc(0)}}this.socket.reading = false}parse(message) {switch (message.o) {case 'r':breakthis.onRead(message)case 'p':this.onPublish(message)break}}/** Cleanup*/cleanup() {this.socket.destroy()this.destroyed = true}}export { IPCClient }
export { IPCClient } from './ipc_client.js'
{"name": "@djinlist/datastore-client","version": "3.0.0","type": "module","license": "UNLICENSED","private": true,"main": "src/index.js","dependencies": {"@djinlist/datastore": "0.0.0","lodash": "^4.17.15"}}
/*** Test Dependencies*/import './helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { TopicTree } from '../src/topic_tree.js'describe('TopicTree', () => {describe('#values', () => {it('matches "/setup/rooms/1/name"', () => {const tree = new TopicTree()tree.add('setup', 3000)tree.add('setup/rooms', 3300)tree.add('setup/rooms/1', 3330)tree.add('setup/rooms/1/name', 3333)tree.add('#', 1000)tree.add('setup/rooms/#', 3310)tree.add('setup/rooms/+/name', 3323)tree.add('setup/rooms/+', 3320)tree.add('setup/rooms/+/power', -3323)tree.add('setup/+/+/name', 3223)const values = tree.values('setup/rooms/1/name')expect(values).to.eql([3333, 3323, 3310, 3223, 1000])})})describe('#entries', () => {it('matches "/setup/rooms/1/name"', () => {const tree = new TopicTree()tree.add('setup', 3000)tree.add('setup/rooms', 3300)tree.add('setup/rooms/1', 3330)tree.add('setup/rooms/1/name', 3333)tree.add('#', 1000)tree.add('setup/rooms/#', 3310)tree.add('setup/rooms/+/name', 3323)tree.add('setup/rooms/+', 3320)tree.add('setup/rooms/+/power', -3323)tree.add('setup/+/+/name', 3223)const entries = tree.entries('setup/rooms/1/name')expect(entries).to.eql([['setup/rooms/1/name', 3333],['setup/rooms/+/name', 3323],['setup/rooms/#', 3310],['setup/+/+/name', 3223],['#', 1000]])})})})
---base:'#initialize_test':- label: 'MUST Initialize from path'without_wildcards: '/some/path/without/wildcards'with_wildcard: '/some/+/with/wildcards'with_wildcards: '/some/+/+/wildcards'with_end_wildcard: '/some/path/with/#'- label: 'MUST Initialize from topic'without_wildcards: 'some/topic/without/wildcards'with_wildcard: 'some/+/with/wildcards'with_wildcards: 'some/+/+/wildcards'with_end_wildcard: 'some/topic/with/#'- label: 'MUST Initialize from steps'without_wildcards: ['some', 'steps', 'without', 'wildcards']with_wildcard: ['some', '+', 'with', 'wildcards']with_wildcards: ['some', '+', '+', 'wildcards']with_end_wildcard: ['some', 'steps', 'with', '#']'#initialize_edge_case':- data: ''expectations:path: ''topic: ''steps: ['']wildcard: false- data: []expectations:path: ''topic: ''steps: ['']wildcard: false# - data: ['']# expectations:# path: ''# topic: '#'# steps: []# wildcard: true- data: '#'expectations:path: ''topic: '#'steps: ['#']wildcard: true# - data: '/#'# expectations:# path: ''# topic: '#'# steps: []# wildcard: true- data: ['#']expectations:path: ''topic: '#'steps: ['#']wildcard: true- data: '/a'expectations:path: '/a'topic: 'a'steps: ['a']wildcard: false- data: 'a'expectations:path: '/a'topic: 'a'steps: ['a']wildcard: false- data: ['a']expectations:path: '/a'topic: 'a'steps: ['a']wildcard: false- data: '/actions/components/1/speakers/1/volume+' # Not a wildcardexpectations:path: '/actions/components/1/speakers/1/volume+'topic: 'actions/components/1/speakers/1/volume+'steps: ['actions', 'components', '1', 'speakers', '1', 'volume+']wildcard: false- data: '/actions/components/#/speakers/1/volume+' # Invalid wildcardexpectations:path: '/actions/components/#/speakers/1/volume+'topic: 'actions/components/#/speakers/1/volume+'steps: ['actions', 'components', '#', 'speakers', '1', 'volume+']wildcard: false'#path=':- label: FLAGGED - MUST replace path "/a"args: ''call: ['path', '/a']expectations: ['/a', undefined, undefined, '/a', ['a'], 'a', 1 ,'/a']- label: FLAGGED - MUST replace path "/a/b"args: ''call: ['path', '/a/b']expectations: ['/a/b', undefined, undefined, '/a/b', ['a', 'b'], 'a/b', 2 ,'/a/b']'#steps=':- label: MUST replace steps ["a"]args: []call: ['steps', ['a']]expectations: [undefined, ['a'], undefined, '/a', ['a'], 'a', 1 ,'a']- label: MUST replace steps ["a", "b"]args: []call: ['steps', ['a', 'b']]expectations: [undefined, ['a', 'b'], undefined, '/a/b', ['a', 'b'], 'a/b', 2 ,'a/b']'#topic=':- label: MUST replace topic "a"args: '#'call: ['topic', 'a']expectations: [undefined, undefined, 'a', '/a', ['a'], 'a', 1, 'a']- label: MUST replace topic "a/b"args: '#'call: ['topic', 'a/b']expectations: [undefined, undefined, 'a/b', '/a/b', ['a', 'b'], 'a/b', 2 ,'a/b']controlenvy:'#getters':- steps: ['q', 'state', 'drivers', 'e1911ae7-4e71-4fc5-847e-cb79ca6c9b2b', 'name']# - steps: ['drivers', 'e1911ae7-4e71-4fc5-847e-cb79ca6c9b2b', 'q', 'state', 'connections', '1', 'status']# - steps: ['drivers', 'e1911ae7-4e71-4fc5-847e-cb79ca6c9b2b', '', 'state', 'connections', '+', 'status']# - steps: ['drivers', 'e1911ae7-4e71-4fc5-847e-cb79ca6c9b2b', '', 'state', 'connections', '1', '#']- steps: [null, 'action', 'components', '1', 'switchers', '1', 'inputs', '1', 'power']# - steps: ['drivers', '00000000-0000-0000-0000-000000000000', '', '', '#']'#flag=':- label: /setup/rooms/1/nametests:- label: MUST set queue flagcall: [flag, q]expectations: [[flag, q], [topic, q/setup/rooms/1/name]]- label: MUST remove queue flagcall: [flag, undefined]expectations: [[flag, null], [topic, setup/rooms/1/name]]'#trunk_path=':- label: /setup/rooms/1/nametests:- label: MUST set trunk_pathcall: [trunk_path, /rooms/2]expectations: [[trunk_path, /rooms/2], [topic, setup/rooms/2/name]]- label: MUST remove trunk_pathcall: [trunk_path, undefined]expectations: [[trunk_path, null], [topic, setup/name]]'#branch_path=':- label: /setup/rooms/1/nametests:- label: MUST set branch_pathcall: ['branch_path', '/components/1/displays/1']expectations: [['branch_path', '/components/1/displays/1'], ['topic', 'setup/components/1/displays/1/name']]- label: MUST remove branch_pathcall: ['branch_path', undefined]expectations: [['branch_path', null], [topic, setup/name]]'#leaf=':- label: '/setup/rooms/1/name'tests:- label: MUST set leafcall: [leaf, description]expectations: [[leaf, description], [topic, setup/rooms/1/description]]- label: MUST remove leafcall: [leaf, undefined]expectations: [[leaf, null], [topic, setup/rooms/1]]extension:'#createWithDefaults':# - label: adds defaults when missing# call: power# expectation: /state/components/1/power# - label: merges defaults when partially missing# call: /state/name# expectation: /state/components/1/name- label: does not modify when all default values are definedcall: /drivers/00000000-0000-0000-0000-000000000000/q/setup/components/1/nameexpectation: drivers/00000000-0000-0000-0000-000000000000/q/setup/components/1/name'#fromMessage':- label: MUST create pointer from topic.call: { o: 's', t: 'setup/#' }expectations: [[topic, setup/#]]- label: MUST create pointer from write message.call: {o: "w", p: "/q/state/components/1/power", v: true}expectations: [[topic, q/state/components/1/power]]- label: MUST create pointer from subscribe message.call: {o: "s", p: "/#", i: true}expectations: [[topic, '#']]- label: MUST create pointer from publish message.call: { o: "d", p: "/setup", v: {"name":"Test","components":{"1":{"name":"Component 1"},"2":{"name":"Component 2"}}}}expectations: [[topic, setup]]inclusion:'#toMessage':- label: MUST create message object from full pointerpointer: /state/components/1/powercall: ['w', true]expectation: { o: "w", p: "/state/components/1/power", v: true}# - label: MUST create message object from wildcard pointer# pointer: /## call: ['s', null, { i: true }]# expectation: { o: "s", t: "#", i: true}- label: FLAGGED - MUST create empty message objectpointer: ''call: ['d']expectation: { o: "d", p: "" }- label: MUST create delete messagepointer: /setup/components/1call: ['d']expectation: { o: "d", p: "/setup/components/1"}'#queue':- label: adds a 'q' flag to a pointerpointer: /state/components/1/powercall: [queue, null]expectation: [topic, q/state/components/1/power]- label: does not modify pointers that are queuedpointer: /q/state/components/1/powercall: [queue, null]expectation: [topic, q/state/components/1/power]'#dequeue':- label: removes a 'q' flag from a pointerpointer: /q/action/components/1/powercall: [dequeue, null]expectation: [topic, action/components/1/power]- label: does not modify pointers that are not queuedpointer: /action/components/1/powercall: [dequeue, null]expectation: [topic, action/components/1/power]# '#changeRoot':# - label: MUST change a root# pointer: /action/components/1/power# call: [changeRoot, state]# expectation: [topic, /state/components/1/power]# '#changeGrove':# - label: MUST change a trunk# pointer: /action/components/1/power# call: [changeGrove, /systems/00000000-0000-0000-0000-000000000000]# expectation: [topic, /systems/00000000-0000-0000-0000-000000000000/action/components/1/power]'#changeTrunk':- label: MUST change a trunkpointer: /action/components/1/powercall: [changeTrunk, /rooms/1]expectation: [topic, action/rooms/1/power]'#changeBranch':- label: MUST change a branchpointer: /action/components/1/powercall: [changeBranch, /rooms/1/components/1]expectation: [topic, action/rooms/1/components/1/power]
---base:'#initialize': [['MUST initialize',[],[[], {}]]]'#clear': [['MUST clear the root',[['set', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604'],['set', '/setup/driver/connected', true],['clear']],[[], {}]]]'#set': [['MUST set a single value "/a", true',[['set', '/a', true]],[['a'], true]],['MUST set a single value "/a/b", true',[['set', '/a/b', true]],[['a', 'b'], true]],['MUST set a single value "/a/b/c", true',[['set', '/a/b/c', true]],[['a', 'b', 'c'], true]]]'#get': [['MUST return a #set value at a path',[['set', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604']],['get', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604']],['MUST return nil for a #delete(d) value at a path',[['set', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604'],['delete', '/setup/driver/uuid']],['get', '/setup/driver/uuid', undefined]]]'#delete': [['MUST set a single value "/a", true',[['set', '/a', true],['delete', '/a']],[[], {}]],['MUST delete a single value "/a/b", true',[['set', '/a/b', true],['delete', '/a/b']],[[], {}]],['MUST delete a single value "/a/b/c", true',[['set', '/a/b/c', true],['delete', '/a/b/c']],[[], {}]],['MUST not clear the root when deleting a root key',[['set', '/a1', true],['set', '/a2', true],['delete', '/a2']],[[], { 'a1': true }]]]convenience:'#each': [['MUST call with the elements of an array',[['each', '/setup/rooms/1/area_paths']],['/areas/1', '/areas/2']]]'#has': [['MUST return false when path does not exist', ['has', '/setup/areas/2'], false],['MUST return true when path does exist', ['has', '/setup/rooms/1/name'], true],['MUST return true when path does exist but is an empty hash', ['has', '/setup/components'], true]]'#includes': [['MUST return true if the found array has a value', ['includes', '/setup/rooms/1/area_paths', '/areas/1'], true],['MUST return false if the found value is not an array', ['includes', '/setup/areas/1', '/rooms/11'], false],['MUST return false if the found array does not have a value', ['includes', '/setup/areas/1/room_paths', '/rooms/11'], false]]'#any': [['MUST return true if node is a hash and is not empty', ['any', '/setup/rooms/1'], true],['MUST return false if node is an empty hash', ['any', '/setup/drivers'], false],['MUST return false if node is an array', ['any', '/setup/areas/1/room_paths'], false],['MUST return false if node is an empty hash', ['any', '/setup/areas/1/name'], false]]'#keys': [['MUST return an array of the keys at a node', ['keys', '/setup/rooms'], ['1', '2']],['MUST return an empty array if the node is primitive', ['keys', '/setup/areas/1/name'], []],['MUST return an empty array if the node is an array', ['keys', '/setup/areas/1/room_paths'], []]]'#branchPaths': [['MUST return branch paths that exist at a passed path', ['branchPaths', '/setup/components/1'], ['/components/1', '/components/1/displays/1']]]'#push': [["'/setup/rooms/2/area_paths', '/areas/2'",[['push', '/setup/rooms/1/area_paths', '/areas/2']],[['/setup/rooms/1/area_paths', ['/areas/1', '/areas/2']]]]]'#pull': [["'/setup/rooms/2/area_paths', '/areas/2'",[['pull', '/setup/rooms/1/area_paths', '/areas/2']],[['/setup/rooms/1/area_paths', ['/areas/1']]]]]'#add': [["'/setup/rooms', { name: 'Dining Room' }",[['add', '/setup/rooms', { 'name': 'Dining Room'}]],[['/setup/rooms/3', { 'name': 'Dining Room' }]]],["'/setup/components', { 'name': 'Component 3' }",[['add', '/setup/components', { 'name': 'Component 3'}]],[['/setup/components/3', { 'name': 'Component 3' }]]]]external:'#read': [['MUST read a datastore node when passed a standard topic, "/setup"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup', { 'rooms': { '1': { 'name': 'Kitchen' }, '2': { 'name': 'Breakfast Room' }}}]],['MUST read a coppiced datastore node when passed a standard topic "/setup" and { coppiced: true }',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup', { coppiced: true }, { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]],['MUST read a coppiced datastore node when passed a MQTT topic, "/setup/rooms/+/name"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/name', { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]],['MUST read a coppiced datastore node when passed a standard topic "/setup/rooms/+/name" and { coppiced: true }',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/name', { coppiced: true }, { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]],['MUST return an empty value when MQTT topic is deeper than existing data "/setup/rooms/+/+/#"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/+/#', {}]],['MUST return an empty value when MQTT topic is deeper than existing data "/setup/rooms/+/+/+"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/+/+', {}]],['MUST return an empty value when MQTT topic is deeper than existing data "/setup/rooms/+/+/*"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/+/*', {}]],['MUST read a coppiced datastore node when passed a MQTT topic, "systems/local/setup/components/+/displays/+/name"',[],['read', 'systems/local/setup/components/+/displays/+/name', {'/systems/local/setup/components/1/displays/1/name': 'Kitchen Display','/systems/local/setup/components/2/displays/1/name': 'Breakfast Room Display'}]]]'#search': [['MUST find a single matched primitive from a standard topic "setup/rooms/1/name"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['search', 'setup/rooms/1/name', { '/setup/rooms/1/name': 'Kitchen' }]],['MUST find a multiple matched primitives from a MQTT topic "setup/rooms/+/name"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['search', 'setup/rooms/+/name', { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]],['MUST find a multiple matched primitives from a MQTT topic "setup/rooms/#"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['search', 'setup/rooms/#', { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]]]'#write': [['MUST overwrite a datastore node "setup/rooms/1/name"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room'],['write', '/setup/rooms', { '1': { 'name': 'Dining Room' }}]],['read', 'setup/rooms', { '1': { 'name': 'Dining Room' } }]]]'#merge': [['MUST integrate value paths in a datastore node setup/rooms/1/name',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room'],['merge', '/setup/rooms', { '3': { 'name': 'Dining Room' } }]],['read', 'setup/rooms', { '1': { 'name': 'Kitchen' }, '2': { 'name': 'Breakfast Room' }, '3': { 'name': 'Dining Room' } }]],['MUST set values to a new node',[['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['read', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['MUST overwrite primitive values',[['merge', '/state/components/1/calendars/1/update/organizer/email', 'rspec@controlenvy.com'],['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['read', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['MUST overwrite primitive values',[['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com'],['merge', '/state/components/1/calendars/1/update/organizer', 'controlenvy']],['read', '/state/components/1/calendars/1/update/organizer', 'controlenvy']],['MUST overwrite nodes when the path becomes a primitive value',[['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com'],['merge', '/state/components/1/calendars/1/update/organizer', 'controlenvy']],['read', '/state/components/1/calendars/1/update/organizer', 'controlenvy']],['MUST overwrite primitive values when the path becomes a node',[['merge', '/state/components/1/calendars/1/update/organizer', 'controlenvy'],['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['read', '/state/components/1/calendars/1/update/organizer', { 'email': 'spec@controlenvy.com' }]]]pubsub:'#topic_tree': [['MUST add MQTT topics to the topic tree "state/components/+/status"',[['subscribe', 'state/components/+/status', '#subscriber', '#callback']],[['state/components/1/status', 'state/components/+/status', '#subscriber']]],['MUST remove MQTT topic from the topic tree "state/components/+/status"',[['subscribe', 'state/components/+/status', '#subscriber', '#callback'],['unsubscribe', 'state/components/+/status', '#subscriber', '#callback']],[['state/components/1/status', null]]],['MUST not affect other subscribers when identical topic is removed from the topic tree',[['subscribe', 'state/components/1/status', '#subscriber_1', '#callback'],['subscribe', 'state/components/1/status', '#subscriber_2', '#callback'],['unsubscribe', 'state/components/1/status', '#subscriber_1', '#callback']],[['state/components/1/status', 'state/components/1/status', '#subscriber_2']]],['MUST not affect other subscriber when one topic is removed from the topic tree',[['subscribe', 'state/components/+/status', '#subscriber', '#callback'],['subscribe', 'state/components/1/status', '#subscriber', '#callback'],['unsubscribe', 'state/components/+/status', '#subscriber', '#callback']],[['state/components/1/status', 'state/components/1/status', '#subscriber']]]]'#subscribe': [['MUST publish a single matched exact topic "setup/rooms/1/name"',[['subscribe', 'setup/rooms/1/name', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['setup/rooms/1/name', '/setup/rooms/1/name', 'Kitchen' ]]],['MUST publish a single matched exact topic "setup/rooms/1/name" with immediate flag',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Patio'],['subscribe', 'setup/rooms/1/name', '#subscriber', '#callback']],[['setup/rooms/1/name', '/setup/rooms/1/name', 'Kitchen' ]]],['MUST publish the root wildcard topic "#"',[['subscribe', '#', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['#', '/setup', {}],['#', '/setup/rooms', {}],['#', '/setup/rooms/1', {}],['#', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish a single matched wildcard topic containing "#", "setup/#"',[['write', '/setup/rooms/1/name', 'Patio'],['subscribe', 'setup/#', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1/name', 'Kitchen']],[['setup/#', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish a single matched wildcard topic containing "+", "setup/rooms/+/name"',[['subscribe', 'setup/rooms/+/name', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['setup/rooms/+/name', '/setup/rooms/1/name', 'Kitchen' ]]],['MUST publish a single matched wildcard topic containing "*", "setup/rooms/*"',[['subscribe', 'setup/rooms/*', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['setup/rooms/*', '/setup/rooms', { '1': { } }],['setup/rooms/*', '/setup/rooms', { '1': { 'name': 'Kitchen' } }]]],['MUST publish a single matched wildcard topic ending with "+", "setup/rooms/1/+"',[['subscribe', 'setup/rooms/1/+', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['setup/rooms/1/+', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish a wildcard topic with two "+", "setup/+/+"',[['subscribe', 'setup/+/+', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1/name', 'Kitchen']],[['setup/+/+', '/setup/rooms/1', {}]]],['MUST publish a wildcard topic with "+" and "#", "setup/+/#"',[['subscribe', 'setup/+/#', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1/name', 'Kitchen']],[['setup/+/#', '/setup/rooms/1', {}],['setup/+/#', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish a wildcard topic with "+" and "*", "setup/+/*"',[['subscribe', 'setup/+/*', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1/name', 'Kitchen']],[['setup/+/*', '/setup/rooms', { '1': {} }],['setup/+/*', '/setup/rooms', { '1': { 'name': 'Kitchen' }}]]],['MUST publish the root wildcard topic "#" with immediate flag',[['set', '/setup/rooms/1/name', 'Kitchen'],['subscribe', '#', '#subscriber', '#callback']],[['#', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish multiple matching paths with immediate flag',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Patio'],['subscribe', '#', '#subscriber', '#callback']],[['#', '/setup/rooms/1/name', 'Kitchen'],['#', '/setup/rooms/2/name', 'Patio']]],['MUST publish multiple matched subscribers',[['subscribe', 'setup/rooms/+/name', '#subscriber', '#callback', { immediate: false }],['subscribe', 'setup/rooms/1/name', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Patio']],[['setup/rooms/1/name', '/setup/rooms/1/name', 'Patio'],['setup/rooms/+/name', '/setup/rooms/1/name', 'Patio']]],['FLAGGED > MUST publish a null value for a single matched wildcard topic ending in "*", "setup/*"',[['write', '/setup/rooms/1/name', 'Kitchen'],['subscribe', 'setup/*', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1', null]],[['setup/*', '/setup', null]]],['FLAGGED > MUST publish a single matched topic if a parent node is deleted, "setup/rooms/1/name"',[['write', '/setup/rooms/1/name', 'Kitchen'],['subscribe', 'setup/rooms/1/name', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1', null]],[['setup/rooms/+/name', '/setup/rooms/1/name', null]]],['FLAGGED > MUST publish a single matched wildcard topic if a parent node is deleted, "setup/rooms/1/name"',[['write', '/setup/rooms/1/name', 'Kitchen'],['subscribe', 'setup/rooms/+/name', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1', null]],[['setup/rooms/+/name', '/setup/rooms/1/name', null]]]]'#unsubscribe': [['MUST NOT error if pointer has no subscribers',[['subscribe', 'expected/to/error', '#subscriber', '#callback', { immediate: false }]],[]],['MUST NOT error if pointer has no subscribers',[['subscribe', 'expected/to/error', '#subscriber', '#callback', { immediate: false }]],[]]]
/*** Test Dependencies*/import './helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { defaults } from '../../src/v3-compatible/schema.js'describe('Schema', () => {describe('#defaults', () => {describe('string', () => {it('const', () => {const schema = {type: 'string',const: 'Hello, Const!'}const result = defaults(schema)expect(result).to.eql('Hello, Const!')})it('default', () => {const schema = {type: 'string',default: 'Hello, Default!'}const result = defaults(schema)expect(result).to.eql('Hello, Default!')})it('const and default', () => {const schema = {type: 'string',const: 'Hello, Const!',default: 'Hello, Default!'}const result = defaults(schema)expect(result).to.eql('Hello, Const!')})})describe('object', () => {it('Control Envy component with source', () => {const schema = {type: 'object',properties: {name: {type: 'string',default: 'Component'},sources: {type: 'object',properties: {'1': {type: 'object',properties: {name: {type: 'string',default: 'Source 1'}}}}}}}const result = defaults(schema)expect(result).to.be.eql({name: 'Component',sources: {'1': {name: 'Source 1'}}})})})})})
---base:'#initialize_test':- label: 'MUST Initialize from path'without_wildcards: '/some/path/without/wildcards'with_wildcard: '/some/+/with/wildcards'with_wildcards: '/some/+/+/wildcards'with_end_wildcard: '/some/path/with/#'- label: 'MUST Initialize from topic'without_wildcards: 'some/topic/without/wildcards'with_wildcard: 'some/+/with/wildcards'with_wildcards: 'some/+/+/wildcards'with_end_wildcard: 'some/topic/with/#'- label: 'MUST Initialize from steps'without_wildcards: ['some', 'steps', 'without', 'wildcards']with_wildcard: ['some', '+', 'with', 'wildcards']with_wildcards: ['some', '+', '+', 'wildcards']with_end_wildcard: ['some', 'steps', 'with', '#']'#initialize_edge_case':- data: ''expectations:path: ''topic: ''steps: []wildcard: false- data: []expectations:path: ''topic: ''steps: []wildcard: false# - data: ['']# expectations:# path: ''# topic: '#'# steps: []# wildcard: true- data: '#'expectations:path: '/#'topic: '#'steps: ['#']wildcard: true# - data: '/#'# expectations:# path: ''# topic: '#'# steps: []# wildcard: true- data: ['#']expectations:path: '/#'topic: '#'steps: ['#']wildcard: true- data: '/a'expectations:path: '/a'topic: 'a'steps: ['a']wildcard: false- data: 'a'expectations:path: '/a'topic: 'a'steps: ['a']wildcard: false- data: ['a']expectations:path: '/a'topic: 'a'steps: ['a']wildcard: false- data: '/actions/components/1/speakers/1/volume+' # Not a wildcardexpectations:path: '/actions/components/1/speakers/1/volume+'topic: 'actions/components/1/speakers/1/volume+'steps: ['actions', 'components', '1', 'speakers', '1', 'volume+']wildcard: false- data: '/actions/components/#/speakers/1/volume+' # Invalid wildcardexpectations:path: '/actions/components/#/speakers/1/volume+'topic: 'actions/components/#/speakers/1/volume+'steps: ['actions', 'components', '#', 'speakers', '1', 'volume+']wildcard: false'#path=':- label: MUST replace path "/a"args: ''call: ['path', '/a']expectations: ['/a', undefined, undefined, '/a', ['a'], 'a', 1 ,'/a']- label: MUST replace path "/a/b"args: ''call: ['path', '/a/b']expectations: ['/a/b', undefined, undefined, '/a/b', ['a', 'b'], 'a/b', 2 ,'/a/b']'#steps=':- label: MUST replace steps ["a"]args: []call: ['steps', ['a']]expectations: [undefined, ['a'], undefined, '/a', ['a'], 'a', 1 ,'/a']- label: MUST replace steps ["a", "b"]args: []call: ['steps', ['a', 'b']]expectations: [undefined, ['a', 'b'], undefined, '/a/b', ['a', 'b'], 'a/b', 2 ,'/a/b']'#topic=':- label: FLAGGED - MUST replace topic "a"args: '#'call: ['topic', 'a']expectations: [undefined, undefined, 'a', '/a', ['a'], 'a', 1, 'a']- label: FLAGGED - MUST replace topic "a/b"args: '#'call: ['topic', 'a/b']expectations: [undefined, undefined, 'a/b', '/a/b', ['a', 'b'], 'a/b', 2 ,'a/b']controlenvy:'#getters':- steps: ['q', 'state', 'drivers', 'e1911ae7-4e71-4fc5-847e-cb79ca6c9b2b', 'name']- steps: [null, 'action', 'components', '1', 'switchers', '1', 'inputs', '1', 'power']'#flag=':- label: /setup/rooms/1/nametests:- label: MUST set queue flagcall: [flag, q]expectations: [[flag, q], [path, /q/setup/rooms/1/name]]- label: MUST remove queue flagcall: [flag, undefined]expectations: [[flag, null], [path, /setup/rooms/1/name]]'#trunk_path=':- label: /setup/rooms/1/nametests:- label: MUST set trunk_pathcall: [trunk_path, /rooms/2]expectations: [[trunk_path, /rooms/2], [path, /setup/rooms/2/name]]- label: MUST remove trunk_pathcall: [trunk_path, undefined]expectations: [[trunk_path, null], [path, /setup/name]]'#branch_path=':- label: /setup/rooms/1/nametests:- label: MUST set branch_pathcall: ['branch_path', '/components/1/displays/1']expectations: [['branch_path', '/components/1/displays/1'], ['path', '/setup/components/1/displays/1/name']]- label: MUST remove branch_pathcall: ['branch_path', undefined]expectations: [['branch_path', null], ['path', '/setup/name']]'#leaf=':- label: '/setup/rooms/1/name'tests:- label: MUST set leafcall: [leaf, description]expectations: [[leaf, description], [path, /setup/rooms/1/description]]- label: MUST remove leafcall: [leaf, undefined]expectations: [[leaf, null], [path, /setup/rooms/1]]extension:'#createWithDefaults':# - label: adds defaults when missing# call: power# expectation: /state/components/1/power# - label: merges defaults when partially missing# call: /state/name# expectation: /state/components/1/name- label: does not modify when all default values are definedcall: /drivers/00000000-0000-0000-0000-000000000000/q/setup/components/1/nameexpectation: /drivers/00000000-0000-0000-0000-000000000000/q/setup/components/1/name'#fromMessage':- label: FLAGGED - MUST create pointer from topic.call: { o: 's', t: 'setup/#' }expectations: [[topic, setup/#]]- label: MUST create pointer from write message.call: {o: "w", p: "/q/state/components/1/power", v: true}expectations: [[path, /q/state/components/1/power]]- label: MUST create pointer from subscribe message.call: {o: "s", p: "/#", i: true}expectations: [[path, /#]]- label: MUST create pointer from publish message.call: { o: "d", p: "/setup", v: {"name":"Test","components":{"1":{"name":"Component 1"},"2":{"name":"Component 2"}}}}expectations: [[path, /setup]]inclusion:'#toMessage':- label: MUST create message object from full pointerpointer: /state/components/1/powercall: ['w', true]expectation: { o: "w", p: "/state/components/1/power", v: true}# - label: MUST create message object from wildcard pointer# pointer: /## call: ['s', null, { i: true }]# expectation: { o: "s", t: "#", i: true}- label: FLAGGED - MUST create emptypointer: ''call: ['d']expectation: { o: "d", p: "" }- label: MUST create delete message objectpointer: /setup/components/1call: ['d']expectation: { o: "d", p: "/setup/components/1"}'#queue':- label: adds a 'q' flag to a pointerpointer: /state/components/1/powercall: [queue, null]expectation: [path, /q/state/components/1/power]- label: does not modify pointers that are queuedpointer: /q/state/components/1/powercall: [queue, null]expectation: [path, /q/state/components/1/power]'#dequeue':- label: removes a 'q' flag from a pointerpointer: /q/action/components/1/powercall: [dequeue, null]expectation: [path, /action/components/1/power]- label: does not modify pointers that are not queuedpointer: /action/components/1/powercall: [dequeue, null]expectation: [path, /action/components/1/power]# '#changeRoot':# - label: MUST change a root# pointer: /action/components/1/power# call: [changeRoot, state]# expectation: [path, /state/components/1/power]# '#changeGrove':# - label: MUST change a trunk# pointer: /action/components/1/power# call: [changeGrove, /systems/00000000-0000-0000-0000-000000000000]# expectation: [path, /systems/00000000-0000-0000-0000-000000000000/action/components/1/power]'#changeTrunk':- label: MUST change a trunkpointer: /action/components/1/powercall: [changeTrunk, /rooms/1]expectation: [path, /action/rooms/1/power]'#changeBranch':- label: MUST change a branchpointer: /action/components/1/powercall: [changeBranch, /rooms/1/components/1]expectation: [path, /action/rooms/1/components/1/power]
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Topic } from '../../../src/v3-compatible/topic.js'const functions = {'#initialize_test': initialize_test,'#initialize_edge_case': initialize_edge_case,'#path=': basic_property_test,'#topic=': basic_property_test,'#steps=': basic_property_test}function initialize_edge_case({ data, expectations }) {it(`initializes with edge cases ${JSON.stringify(data)}`, () => {const topic = new Topic(data)// expect(topic.path).to.be.eql(expectations['path'])expect(topic.topic).to.be.eql(expectations['topic'])expect(topic.steps).to.be.eql(expectations['steps'])expect(topic.length).to.be.eql(expectations['steps'].length)expect(topic.is_wildcard).to.be.eql(expectations['wildcard'])})}function initialize_test(data) {const label = data['label']delete data['label']_.forEach(data, (value, key) => {it(label, () => {const topic = new Topic(value)const length = 4const wild = key.startsWith('with_')if (typeof value == 'array') {test_topic(topic, `/${value.join('/')}`, value.join('/'), value, length, wild)} else if ( typeof value == 'string' ) {if (value.match(/^[\/].*/)) {test_topic(topic, value, value.slice(1), value.slice(1).split('/'), length, wild)} else if (value.match(/^[^\/].*/)) {test_topic(topic, `/${value}`, value, value.split('/'), length, wild)}}})})}function test_topic(topic, path, pattern, steps, length, wildcard) { //expect(topic.topic).to.be.eql(pattern)expect(topic.steps).to.be.eql(steps)expect(topic.length).to.be.eql(length)expect(topic.is_wildcard).to.be.eql(wildcard)}function basic_property_test({ label, args, call, expectations }) {it(label, () => {const topic = new Topic(args)if (call) {topic[call[0]] = call[1]}// expect(topic.path).to.equal(expectations[3])expect(topic.steps).to.eql(expectations[4])expect(topic.topic).to.equal(expectations[5])expect(topic.length).to.equal(expectations[6])expect(topic == expectations[7]).to.be.true})}const tests = requireYAML('../topic.yaml')['base']describe('Topic', () => {_.forEach(tests, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from '../../../src/v3-compatible/pointer.js'const functions = {'#initialize_test': initialize_test,'#initialize_edge_case': initialize_edge_case,'#path=': basic_property_test,'#topic=': basic_property_test,'#steps=': basic_property_test}function initialize_edge_case({ data, expectations }) {it(`initializes with edge cases ${JSON.stringify(data)}`, () => {const pointer = new Pointer(data)expect(pointer.path).to.be.eql(expectations['path'])// expect(pointer.topic).to.be.eql(expectations['topic'])expect(pointer.steps).to.be.eql(expectations['steps'])expect(pointer.length).to.be.eql(expectations['steps'].length)expect(pointer.is_wildcard).to.be.eql(expectations['wildcard'])})}function initialize_test(data) {const label = data['label']delete data['label']_.forEach(data, (value, key) => {it(label, () => {const pointer = new Pointer(value)const length = 4const wild = key.startsWith('with_')if (typeof value == 'array') {test_pointer(pointer, `/${value.join('/')}`, value.join('/'), value, length, wild)} else if ( typeof value == 'string' ) {if (value.match(/^[\/].*/)) {test_pointer(pointer, value, value.slice(1), value.slice(1).split('/'), length, wild)} else if (value.match(/^[^\/].*/)) {test_pointer(pointer, `/${value}`, value, value.split('/'), length, wild)}}})})}function test_pointer(pointer, path, topic, steps, length, wildcard) {expect(pointer.path).to.be.eql(path)expect(pointer.steps).to.be.eql(steps)expect(pointer.length).to.be.eql(length)expect(pointer.is_wildcard).to.be.eql(wildcard)}function basic_property_test({ label, args, call, expectations }) {it(label, () => {const pointer = new Pointer(args)if (call) {pointer[call[0]] = call[1]}expect(pointer.path).to.equal(expectations[3])expect(pointer.steps).to.eql(expectations[4])// expect(pointer.topic).to.equal(expectations[5])expect(pointer.length).to.equal(expectations[6])expect(pointer == expectations[7]).to.be.true})}const tests = requireYAML('../pointer.yaml')['base']describe('Pointer', () => {_.forEach(tests, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Topic } from '../../../src/v3-compatible/topic.js'const functions = {'#grove_path=': basic_properties_test,'#flag=': basic_properties_test,'#trunk_path=': basic_properties_test,'#branch_path=': basic_properties_test,'#leaf=': basic_properties_test,'#getters': getters,'#createWithDefaults': createWithDefaults,'#fromMessage': fromMessage,'#toMessage': toMessage,'#queue': mutate_test,'#dequeue': mutate_test,'#changeRoot': mutate_test,'#changeGrove': mutate_test,'#changeTrunk': mutate_test,'#changeBranch': mutate_test}function basic_properties_test({ label, tests }) {const pointer = new Topic(label)tests.forEach(test => {it(`${test['label']} with ${label}`, () => {const [property, value] = test['call']pointer[property] = valuetest['expectations'].forEach(([property, value]) => {expect(pointer[property]).to.be.eql(value)})})})}function getters({steps}) {const topix = `${steps.join('/')}`.replace(/\/\/+/, '/')it(`MUST correctly parse ${topix}`, () => {const pointer = new Topic(topix)const trunk_path = `/${steps.slice(2,4).join('/')}`const flag = steps[2] == '' ? null : steps[0]const root = steps[3] == '' ? null : steps[1]let branch_path = `/${steps.slice(2,-1).join('/')}`.replace(/\/\/+/, '/')branch_path = (branch_path == '/' ? null : branch_path)const twig_path = ((steps.slice(-1)[0] == '') ? null : `/${steps.slice(-1)[0]}`)const leaf = steps.slice(-1)[0]expect(pointer.trunk_path).to.be.eql(trunk_path)expect(pointer.flag).to.be.eql(flag)expect(pointer.root).to.be.eql(root)expect(pointer.branch_path).to.be.eql(branch_path)expect(pointer.twig_path).to.be.eql(twig_path)expect(pointer.leaf).to.be.eql(leaf)})}function createWithDefaults ({ label, call, expectation}) {it(label, () => {const defaults = {trunk_path: '/components/1',root: 'state'}const pointer = Topic.createWithDefaults(call, defaults)expect(pointer.topic).to.be.eql(expectation)})}function toMessage({label, pointer, call, expectation}) {it(label, () => {pointer = new Topic(pointer)expect(pointer.toMessage(...call)).to.be.eql(expectation)})}function fromMessage({label, call, expectations}) {const pointer = Topic.fromMessage(call)it(label, () => {expectations.forEach(([property, value]) => {expect(pointer[property]).to.be.eql(value)})})}function mutate_test({ label, pointer, call, expectation }) {pointer = new Topic(pointer)it(label, () => {if (!call[1]) {pointer = pointer[call[0]]()} else {pointer = pointer[call[0]](...call.slice(1))}const [property, value] = expectationexpect(pointer[property]).to.be.eql(value)})}const tests_1 = requireYAML('../topic.yaml')['controlenvy']const tests_2 = requireYAML('../topic.yaml')['inclusion']const tests_3 = requireYAML('../topic.yaml')['extension']describe('Topic', () => {_.forEach(tests_1, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})describe('Topic Inclusions', () => {_.forEach(tests_2, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})describe('Topic Extensions', () => {_.forEach(tests_3, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from '../../../src/v3-compatible/pointer.js'const functions = {'#grove_path=': basic_properties_test,'#flag=': basic_properties_test,'#trunk_path=': basic_properties_test,'#branch_path=': basic_properties_test,'#leaf=': basic_properties_test,'#getters': getters,'#createWithDefaults': createWithDefaults,'#fromMessage': fromMessage,'#toMessage': toMessage,'#queue': mutate_test,'#dequeue': mutate_test,'#changeRoot': mutate_test,'#changeGrove': mutate_test,'#changeTrunk': mutate_test,'#changeBranch': mutate_test}function basic_properties_test({ label, tests }) {const pointer = new Pointer(label)tests.forEach(test => {it(`${test['label']} with ${label}`, () => {const [property, value] = test['call']pointer[property] = valuetest['expectations'].forEach(([property, value]) => {expect(pointer[property]).to.be.eql(value)})})})}function getters({steps}) {const path = `/${steps.join('/')}`.replace(/\/\/+/, '/')it(`MUST correctly parse ${path}`, () => {const pointer = new Pointer(path)const trunk_path = `/${steps.slice(2,4).join('/')}`const flag = steps[2] == '' ? null : steps[0]const root = steps[3] == '' ? null : steps[1]let branch_path = `/${steps.slice(2,-1).join('/')}`.replace(/\/\/+/, '/')branch_path = (branch_path == '/' ? null : branch_path)const twig_path = ((steps.slice(-1)[0] == '') ? null : `/${steps.slice(-1)[0]}`)const leaf = steps.slice(-1)[0]expect(pointer.trunk_path).to.be.eql(trunk_path)expect(pointer.flag).to.be.eql(flag)expect(pointer.root).to.be.eql(root)expect(pointer.branch_path).to.be.eql(branch_path)expect(pointer.twig_path).to.be.eql(twig_path)expect(pointer.leaf).to.be.eql(leaf)})}function createWithDefaults ({ label, call, expectation}) {it(label, () => {const defaults = {trunk_path: '/components/1',root: 'state'}const pointer = Pointer.createWithDefaults(call, defaults)expect(pointer.path).to.be.eql(expectation)})}function toMessage({label, pointer, call, expectation}) {it(label, () => {pointer = new Pointer(pointer)expect(pointer.toMessage(...call)).to.be.eql(expectation)})}function fromMessage({label, call, expectations}) {const pointer = Pointer.fromMessage(call)it(label, () => {expectations.forEach(([property, value]) => {expect(pointer[property]).to.be.eql(value)})})}function mutate_test({ label, pointer, call, expectation }) {pointer = new Pointer(pointer)it(label, () => {if (!call[1]) {pointer = pointer[call[0]]()} else {pointer = pointer[call[0]](...call.slice(1))}const [property, value] = expectationexpect(pointer[property]).to.be.eql(value)})}const tests_1 = requireYAML('../pointer.yaml')['controlenvy']const tests_2 = requireYAML('../pointer.yaml')['inclusion']const tests_3 = requireYAML('../pointer.yaml')['extension']describe('Pointer', () => {_.forEach(tests_1, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})describe('Pointer Inclusions', () => {_.forEach(tests_2, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})describe('Pointer Extensions', () => {_.forEach(tests_3, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
import { createRequire } from 'module';const require = createRequire(import.meta.url);global.window = {}global.requestAnimationFrame = function() {}global._ = require('lodash')const YAML = require('js-yaml')const fs = require('fs')const path = require('path')function _getCallerFile() {var originalFunc = Error.prepareStackTracevar callerfiletry {var error = new Error()var currentfileError.prepareStackTrace = function(error, stack) {return stack}currentfile = error.stack.shift().getFileName()while (error.stack.length) {callerfile = error.stack.shift().getFileName()if (currentfile !== callerfile) break}} catch (e) {// do nothing}Error.prepareStackTrace = originalFuncreturn callerfile}global.requireYAML = function(file_path) {const dirname = path.dirname(_getCallerFile()).slice('file://'.length)const yaml = fs.readFileSync(path.resolve(dirname, file_path))const json = YAML.load(yaml.toString())convertUndefinedStringToUndefined(json)return json}function convertUndefinedStringToUndefined(json) {if (typeof json == 'object' || Array.isArray(json)) {for (var i in json) {_convertUndefinedStringToUndefined(json, i, json[i])}}}function _convertUndefinedStringToUndefined(parent, key, child) {if (typeof child == 'object' || Array.isArray(child)) {for (var i in child) {_convertUndefinedStringToUndefined(child, i, child[i])}} else if (child == 'undefined') {parent[key] = undefined}}global.parseDatastoreMethod = function(datastore, method, args, { subscriber = {}, callback = () => {} }= {}) {switch (method) {case 'subscribe':if (args.length == 4) {datastore.subscribe(args[0], subscriber, callback, args.slice(-1)[0])} else {datastore.subscribe(args[0], subscriber, callback)}returncase 'unsubscribe':datastore.unsubscribe(args[0], subscriber)returndefault:datastore[method](...args)return}}
systems:local:setup:rooms:'1':name: Kitchencomponent_paths:- /components/1display_paths:- /components/1/displays/1'2':name: Breakfast Roomcomponent_paths:- /components/2display_paths:- /components/2/displays/1components:'1':name: Displaylocation_paths:- /rooms/1displays:'1':name: Kitchen Display'2':name: Displaylocation_paths:- /rooms/2displays:'1':name: Breakfast Room Display
---schema:systems:setup.json:type: object$override:html:edit: /override/area.jsadditionalProperties: falserequired:- name- room_pathsproperties:name:title: Nametype: stringdefault: New Arearoom_paths:title: Rooms$override:html:edit: /override/area/rooms.jstype: arrayadditionalItems: falseitems:type: stringpattern: ^/rooms/\\d+$format: relationshipinverse: area_paths$schema: http://json-schema.org/draft-07/schema#$id: https://v3.schema.controlenvy.com/areas.json
---type: objectproperties:setup:type: objectproperties:rooms:type: objectpattern_properties:'^\d+$':type: objectproperties:component_paths:type: arrayitems:type: stringformat: relationshippattern: '^/components/\d+$'inverse: location_pathsdisplay_paths:type: arrayitems:type: stringformat: relationshippattern: '^/components/\d+/displays/\d+$'inverse: room_pathscomponents:type: objectpattern_properties:'^\d+$':type: objectproperties:location_paths:type: arrayitems:type: stringformat: relationshippattern: '^/rooms/\d+$'inverse: component_pathsdisplays:type: objectpattern_properties:'^\d+$':type: objectproperties:room_paths:type: arrayitems:type: stringformat: relationshippattern: '^/rooms/\d+$'inverse: display_paths
---setup:rooms:'1':component_paths:- /components/1
---setup:components:'1':name: Component 1location_paths:- /rooms/1'2':name: Component 2'3':name: Component 3displays:'1':name: Display 1
---setup:areas:'1':$schema: /areas.jsoncomponent_paths:- /components/1
---base:'#initialize':- label: 'MUST initialize'calls: []expectation: ['get', '', {}]'#set':- label: 'MUST set a single value "/a", true'calls:- ['set', '/a', true]expectation: ['get', '/a', true]- label: 'MUST set a single value "/a/b", true'calls:- ['set', '/a/b', true]expectation: ['get', '/a/b', true]- label: 'MUST set a single value "/a/b/c", true'calls:- ['set', '/a/b/c', true]expectation: ['get', '/a/b/c', true]'#get':- label: 'MUST return a #set value at a path'calls:- ['set', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604']expectation: ['get', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604']- label: 'MUST return a #set hash'calls:- ['set', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604']expectation: ['get', '/setup/driver', {'uuid': 'cc88b93a-3556-4e44-9b81-c45aa1ba9604'}]- label: 'MUST return nil for a #delete(d) value at a path'calls:- ['set', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604']- ['delete', '/setup/driver/uuid']expectation: ['get', '/setup/driver/uuid', null]'#delete':- label: 'MUST set a single value "/a", true'calls:- ['set', '/a', true]- ['delete', '/a']expectation: ['get', '', {}]- label: 'MUST not error when no endpoint exists'calls:- ['delete', '/a']expectation: ['get', '/a', undefined]- label: 'MUST delete a single value "/a/b", true'calls:- ['set', '/a/b', true]- ['delete', '/a/b']expectation: ['get', '', {}]- label: 'MUST delete a single value "/a/b/c", true'calls:- ['set', '/a/b/c', true]- ['delete', '/a/b/c']expectation: ['get', '', {}]- label: 'MUST not clear the root when deleting a root key'calls:- ['set', '/a1', true]- ['set', '/a2', true]- ['delete', '/a2']expectation: ['get', '', { 'a1': true }]- label: 'MUST delete a key from a node'calls:- ['set', '/components/1/name', 'My Component']- ['set', '/components/1/id', 1]- ['delete', '/components/1/name']expectation: ['get', '/components/1/id', 1]convenience:'#each':- label: 'MUST call with the elements of an array'calls:- ['each', '/setup/rooms/1/area_paths']expectations: ['/areas/1', '/areas/2']'#has':- label: 'MUST return false when path does not exist'call: ['has', '/setup/areas/2']expectation: false- label: 'MUST return true when path does exist'call: ['has', '/setup/rooms/1/name']expectation: true- label: 'MUST return true when path does exist but is an empty hash'call: ['has', '/setup/components']expectation: true'#includes':- label: 'MUST return true if the found array has a value'call: ['includes', '/setup/rooms/1/area_paths', '/areas/1']expectation: true- label: 'MUST return false if the found value is not an array'call: ['includes', '/setup/areas/1', '/rooms/11']expectation: false- label: 'MUST return false if the found array does not have a value'call: ['includes', '/setup/areas/1/room_paths', '/rooms/11']expectation: false'#any':- label: 'MUST return true if node is a hash and is not empty'call: ['any', '/setup/rooms/1']expectation: true- label: 'MUST return false if node is an empty hash'call: ['any', '/setup/drivers']expectation: false- label: 'MUST return false if node is an array'call: ['any', '/setup/areas/1/room_paths']expectation: false- label: 'MUST return false if node is an empty hash'call: ['any', '/setup/areas/1/name']expectation: false'#keys':- label: 'MUST return an array of the keys at a node'call: ['keys', '/setup/rooms']expectation: ['1', '2']- label: 'MUST return an empty array if the node is primitive'call: ['keys', '/setup/areas/1/name']expectation: []- label: 'MUST return an empty array if the node is an array'call: ['keys', '/setup/areas/1/room_paths']expectation: []'#branchPaths':- label: 'MUST return branch paths that exist at a passed path'call: ['branchPaths', '/setup/components/1']expectation: ['/components/1', '/components/1/displays/1']'#push':- label: "'/setup/rooms/2/area_paths', '/areas/2'"calls:- ['push', '/setup/rooms/1/area_paths', '/areas/2']spies:- ['/setup/rooms/1/area_paths', ['/areas/1', '/areas/2']]'#pull':- label: "'/setup/rooms/2/area_paths', '/areas/2'"calls:- ['pull', '/setup/rooms/1/area_paths', '/areas/2']spies:- ['/setup/rooms/1/area_paths', ['/areas/1']]'#add':- label: "'/setup/rooms', { name: 'Dining Room' }"calls:- ['add', '/setup/rooms', { 'name': 'Dining Room'}]spies:- ['/setup/rooms/3', { 'name': 'Dining Room' }]- label: "'/setup/components', { 'name': 'Component 3' }"calls:- ['add', '/setup/components', { 'name': 'Component 3'}]spies:- ['/setup/components/3', { 'name': 'Component 3' }]'#queue':- label: 'MUST publish a queued value "setup/rooms/1/name"'calls:- ['subscribe', 'q/setup/rooms/#', '#subscriber_1', '#callback', { immediate: false }]- ['queue', '/setup/rooms/1/name', 'Kitchen']spies:- ['q/setup/rooms/#', '/q/setup/rooms/1/name', 'Kitchen']- label: 'MUST publish multiple queued values'calls:- ['subscribe', 'q/setup/#', '#subscriber_1', '#callback', { immediate: false }]- ['queue', '/setup/rooms/1', { 'name': 'Kitchen', 'id': '1' }]spies:- ['q/setup/#', '/q/setup/rooms/1/name', 'Kitchen']- ['q/setup/#', '/q/setup/rooms/1/id', '1']- label: 'MUST publish multiple queued values with "." in the leaf'description: >This test ensures that schema and other files that are often meta data can loadcalls:- ['subscribe', 'q/setup/rooms/#', '#subscriber_1', '#callback', { immediate: false }]- ['queue', '/setup/rooms/1', { 'name.test': 'Kitchen', 'id.test': '1' }]spies:- ['q/setup/rooms/#', '/q/setup/rooms/1/name.test', 'Kitchen']- ['q/setup/rooms/#', '/q/setup/rooms/1/id.test', '1']external:'#read':- label: 'MUST read a datastore node when passed a standard topic, "/setup"'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Breakfast Room']expectation: ['read', '/setup', { 'rooms': { '1': { 'name': 'Kitchen' }, '2': { 'name': 'Breakfast Room' }}}]- label: 'MUST read a coppiced datastore node when passed a standard topic "/setup" and { coppiced: true }'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Breakfast Room']expectation: ['read', '/setup', { coppiced: true }, { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]- label: 'MUST read a coppiced datastore path/primitive object when coppice is true'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Breakfast Room']expectation: ['read', '/setup/rooms/1/name', { coppiced: true }, { '/setup/rooms/1/name': 'Kitchen' }]- label: 'MUST read a coppice when passed a filtering standard topic "/setup/rooms/+/name" and { coppiced: true }'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Breakfast Room']expectation: ['read', '/setup/rooms/+/name', { coppiced: true }, { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]- label: 'MUST return an empty value when MQTT topic is deeper than existing data "/setup/rooms/+/+/#"'description: >Edge case to prevent failure of the datastore. We may want to return null instead of an empty value. This edge case should never happen, but couldif a linked path errors or is slow to update.calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Breakfast Room']expectation: ['read', '/setup/rooms/+/+/#', {}]- label: 'MUST return an empty value when MQTT topic is deeper than existing data "/setup/rooms/+/+/+"'description: >Edge case to prevent failure of the datastore. We may want to return null instead of an empty value. This edge case should never happen, but couldif a linked path errors or is slow to update.calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Breakfast Room']expectation: ['read', '/setup/rooms/+/+/+', {}]- label: 'MUST return an empty value when MQTT topic is deeper than existing data "/setup/rooms/+/+/*"'description: >Edge case to prevent failure of the datastore. We may want to return null instead of an empty value. This edge case should never happen, but couldif a linked path errors or is slow to update.calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Breakfast Room']expectation: ['read', '/setup/rooms/+/+/*', {}]- label: 'MUST read a coppiced datastore node when passed a MQTT topic, "systems/local/setup/components/+/displays/+/name"'calls: []expectation: ['read', 'systems/local/setup/components/+/displays/+/name', {'/systems/local/setup/components/1/displays/1/name': 'Kitchen Display','/systems/local/setup/components/2/displays/1/name': 'Breakfast Room Display'}]'#write':- label: 'MUST overwrite a datastore node "setup/rooms/1/name"'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Breakfast Room']- ['write', '/setup/rooms', { '1': { 'name': 'Dining Room' }}]expectation: ['read', '/setup/rooms', { '1': { 'name': 'Dining Room' } }]- label: 'MUST write null'calls:- ['write', '/setup/rooms/1/name', null]expectation: ['read', '/setup/rooms/1/name', null]- label: 'MUST NOT error when writing null to a non-existant node'calls:- ['write', '/setup/rooms/5/name', null]expectation: ['read', '/setup/rooms/5/name', null]'#merge':- label: 'MUST integrate value paths in a datastore node setup/rooms/1/name'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Breakfast Room']- ['merge', '/setup/rooms', { '3': { 'name': 'Dining Room' } }]expectation: ['read', 'setup/rooms', { '1': { 'name': 'Kitchen' }, '2': { 'name': 'Breakfast Room' }, '3': { 'name': 'Dining Room' } }]- label: 'MUST set values to a new node'calls:- ['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']expectation: ['read', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']- label: 'MUST overwrite primitive values'calls:- ['merge', '/state/components/1/calendars/1/update/organizer/email', 'rspec@controlenvy.com']- ['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']expectation: ['read', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']- label: 'MUST overwrite primitive values'calls:- ['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']- ['merge', '/state/components/1/calendars/1/update/organizer', 'controlenvy']expectation: ['read', '/state/components/1/calendars/1/update/organizer', 'controlenvy']- label: 'MUST overwrite nodes when the path becomes a primitive value'calls:- ['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']- ['merge', '/state/components/1/calendars/1/update/organizer', 'controlenvy']expectation: ['read', '/state/components/1/calendars/1/update/organizer', 'controlenvy']- label: 'MUST overwrite primitive values when the path becomes a node'calls:- ['merge', '/state/components/1/calendars/1/update/organizer', 'controlenvy']- ['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']expectation: ['read', '/state/components/1/calendars/1/update/organizer', { 'email': 'spec@controlenvy.com' }]pubsub:'#topic_tree':- label: 'MUST add MQTT topics to the topic tree "state/components/+/status"'calls:- ['subscribe', 'state/components/+/status', '#subscriber_1', '#callback']expectations:- ['/state/components/1/status', ['state/components/+/status'], '#subscriber_1']- label: 'MUST remove MQTT topic from the topic tree "state/components/+/status"'calls:- ['subscribe', 'state/components/+/status', '#subscriber_1', '#callback']- ['unsubscribe', 'state/components/+/status', '#subscriber_1', '#callback']expectations:- ['/state/components/1/status', null]- label: 'MUST not affect other subscribers when identical topic is removed from the topic tree'calls:- ['subscribe', 'state/components/1/status', '#subscriber_1', '#callback']- ['subscribe', 'state/components/1/status', '#subscriber_2', '#callback']- ['unsubscribe', 'state/components/1/status', '#subscriber_1', '#callback']expectations:- ['/state/components/1/status', ['state/components/1/status'], '#subscriber_2']- label: 'MUST not affect other subscriber when one topic is removed from the topic tree'calls:- ['subscribe', 'state/components/+/status', '#subscriber_1', '#callback']- ['subscribe', 'state/components/1/status', '#subscriber_1', '#callback']- ['unsubscribe', 'state/components/+/status', '#subscriber_1', '#callback']expectations:- ['/state/components/1/status', ['state/components/1/status'], '#subscriber_1']- label: MUST NOT error when unsubscribing from a path topic with no subscriberscalls:- ['unsubscribe', 'state/components/1/status', '#subscriber_1', '#callback']expectations:- ['/state/components/1/status', null]'#subscribe':- label: 'MUST publish a single matched exact topic "setup/rooms/1/name"'calls:- ['subscribe', 'setup/rooms/1/name', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/rooms/1/name', '/setup/rooms/1/name', 'Kitchen' ]- label: 'MUST publish null if a matched exact topic is not found "setup/rooms/1/name"'calls:- ['subscribe', 'setup/rooms/4/name', '#subscriber_1', '#callback']spies:- ['setup/rooms/4/name', '/setup/rooms/4/name', null ]- label: 'MUST publish a single matched exact topic "setup/rooms/1/name" with immediate flag'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Patio']- ['subscribe', 'setup/rooms/1/name', '#subscriber_1', '#callback']spies:- ['setup/rooms/1/name', '/setup/rooms/1/name', 'Kitchen' ]- label: 'MUST publish the root wildcard topic "#"'calls:- ['subscribe', '#', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/1/name', 'Kitchen']spies:- ['#', '/setup/rooms/1/name', 'Kitchen']- label: 'MUST subscribe to multiple layered nodes'calls:- ['subscribe', 'setup/#', '#subscriber_1', '#callback', { immediate: false }]- ['subscribe', 'setup/rooms/#', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/rooms/#', '/setup/rooms/1/name', 'Kitchen']- ['setup/#', '/setup/rooms/1/name', 'Kitchen']- label: 'MUST subscribe to a queued datastore value'calls:- ['subscribe', 'q/state/#', '#subscriber_1', '#callback', { immediate: false }]- ['queue', 'state/components/1/speakers/1/volume', 50]- ['queue', 'state/components/1/speakers/1/mute', true]spies:- ['q/state/#', '/q/state/components/1/speakers/1/volume', 50]- ['q/state/#', '/q/state/components/1/speakers/1/mute', true]- label: 'MUST publish a single matched wildcard topic containing "#", "setup/#"'calls:- ['write', '/setup/rooms/1/name', 'Patio']- ['subscribe', 'setup/#', '#subscriber_1', '#callback', { immediate: false }]- ['write', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/#', '/setup/rooms/1/name', 'Kitchen']- label: 'MUST publish a single matched wildcard topic containing "+", "setup/rooms/+/name"'calls:- ['subscribe', 'setup/rooms/+/name', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/rooms/+/name', '/setup/rooms/1/name', 'Kitchen']- label: 'MUST publish a single matched wildcard topic containing "*", "setup/rooms/*"'calls:- ['subscribe', 'setup/rooms/*', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/rooms/*', '/setup/rooms', { '1': null }]- ['setup/rooms/*', '/setup/rooms', { '1': { 'name': null } }]- ['setup/rooms/*', '/setup/rooms', { '1': { 'name': 'Kitchen' } }]- label: 'EDGE CASE - MUST publish a single matched wildcard topic containing "*" at the leaf, "setup/rooms/1/*"'calls:- ['subscribe', 'setup/rooms/1/*', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/rooms/1/*', '/setup/rooms/1', { 'name': null }]- ['setup/rooms/1/*', '/setup/rooms/1', { 'name': 'Kitchen' }]- label: 'MUST publish a single matched wildcard topic containing "*" at the leaf, and sustain writes and deletes'calls:- ['subscribe', 'setup/rooms/4/*', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/4/name', 'Kitchen']- ['set', '/setup/rooms/4/name', null]- ['set', '/setup/rooms/4/id', 1]spies:- ['setup/rooms/4/*', '/setup/rooms/4', { 'name': null }]- ['setup/rooms/4/*', '/setup/rooms/4', { 'name': 'Kitchen' }]- ['setup/rooms/4/*', '/setup/rooms/4', { 'name': null }]- ['setup/rooms/4/*', '/setup/rooms/4', null]- ['setup/rooms/4/*', '/setup/rooms/4', { 'id': null }]- ['setup/rooms/4/*', '/setup/rooms/4', { 'id': 1 }]- label: 'FLAGGED MUST publish null when a node matching an exact topic is removed'calls:- ['set', '/setup/rooms/4/name', 'Kitchen']- ['subscribe', 'setup/rooms/4', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/4/name', null]spies:- ['setup/rooms/4', '/setup/rooms/4', null]- label: 'FLAGGED MUST publish null when a node matching a wildcard subscription is removed'calls:- ['set', '/setup/rooms/4/name', 'Kitchen']- ['subscribe', 'setup/rooms/+', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/4/name', null]spies:- ['setup/rooms/+', '/setup/rooms/4', null]- label: 'EDGE CASE - MUST unsubscribe from a wildcard ending in /*'calls:- ['subscribe', 'setup/rooms/4/*', '#subscriber_1', '#callback', { immediate: false }]- ['unsubscribe', 'setup/rooms/4/*', '#subscriber_1']- ['subscribe', 'setup/rooms/+/*', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/4/name', 'Kitchen']spies:- ['setup/rooms/+/*', '/setup/rooms/4', { 'name': null }]- ['setup/rooms/+/*', '/setup/rooms/4', { 'name': 'Kitchen' }]- label: 'MUST publish a single matched wildcard topic ending with "+", "setup/rooms/1/+"'calls:- ['subscribe', 'setup/rooms/1/+', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/rooms/1/+', '/setup/rooms/1/name', {}]- ['setup/rooms/1/+', '/setup/rooms/1/name', 'Kitchen']- label: 'MUST publish a new node for a single matched wildcard intermediate ending with "+", "setup/rooms/+"'calls:- ['subscribe', 'setup/rooms/+', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/rooms/+', '/setup/rooms/1', {}]- label: 'MUST publish a single matched wildcard intermediate ending with "+", "setup/rooms/+" with immediate flag'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['subscribe', 'setup/rooms/+', '#subscriber_1', '#callback']spies:- ['setup/rooms/+', '/setup/rooms/1', { 'name': 'Kitchen' }]- label: 'MUST publish a single matched exact topic when a path is deleted using { force: true }'calls:- ['subscribe', 'setup/rooms/1/name', '#subscriber_1', '#callback', { immediate: false }]- ['delete', 'setup/rooms/1/name', { force: true }]spies:- ['setup/rooms/1/name', '/setup/rooms/1/name', null]- label: 'MUST publish multiple matched wildcard intermediates ending with "+", "setup/rooms/+" with immediate flag'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/4/name', 'Lounge']- ['subscribe', 'setup/rooms/+', '#subscriber_1', '#callback']spies:- ['setup/rooms/+', '/setup/rooms/1', { 'name': 'Kitchen' }]- ['setup/rooms/+', '/setup/rooms/4', { 'name': 'Lounge' }]- label: 'MUST publish a wildcard topic with two "+", "setup/+/+"'calls:- ['subscribe', 'setup/rooms/+/+', '#subscriber_1', '#callback', { immediate: false }]- ['write', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/rooms/+/+', '/setup/rooms/1/name', {}]- ['setup/rooms/+/+', '/setup/rooms/1/name', 'Kitchen']- label: 'MUST publish a wildcard topic with "+" and "#", "setup/+/#"'calls:- ['subscribe', 'setup/+/#', '#subscriber_1', '#callback', { immediate: false }]- ['write', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/+/#', '/setup/rooms/1/name', 'Kitchen']- label: 'MUST publish a wildcard topic with "+" and "*", "setup/+/*"'calls:- ['subscribe', 'setup/+/*', '#subscriber_1', '#callback', { immediate: false }]- ['write', '/setup/rooms/1/name', 'Kitchen']spies:- ['setup/+/*', '/setup/rooms', { '1': null}]- ['setup/+/*', '/setup/rooms', { '1': { 'name': null }}]- ['setup/+/*', '/setup/rooms', { '1': { 'name': 'Kitchen' }}]- label: 'FLAGGED MUST publish the root wildcard topic "#" with immediate flag'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['subscribe', '#', '#subscriber_1', '#callback']spies:- ['#', '/setup/rooms/1/name', 'Kitchen']- label: 'FLAGGED MUST publish multiple matching paths with immediate flag'calls:- ['set', '/setup/rooms/1/name', 'Kitchen']- ['set', '/setup/rooms/2/name', 'Patio']- ['subscribe', '#', '#subscriber_1', '#callback']spies:- ['#', '/setup/rooms/1/name', 'Kitchen']- ['#', '/setup/rooms/2/name', 'Patio']- label: 'MUST publish multiple matched subscribers'calls:- ['subscribe', 'setup/rooms/+/name', '#subscriber_1', '#callback', { immediate: false }]- ['subscribe', 'setup/rooms/1/name', '#subscriber_1', '#callback', { immediate: false }]- ['set', '/setup/rooms/1/name', 'Patio']spies:- ['setup/rooms/1/name', '/setup/rooms/1/name', 'Patio']- ['setup/rooms/+/name', '/setup/rooms/1/name', 'Patio']- label: 'FLAGGED > MUST publish a null value for a single matched wildcard topic ending in "*", "setup/*"'calls:- ['write', '/setup/rooms/1/name', 'Kitchen']- ['subscribe', 'setup/*', '#subscriber_1', '#callback', { immediate: false }]- ['write', '/setup/rooms/1', null]spies:- ['setup/*', '/setup', null]- label: 'FLAGGED > MUST publish a single matched topic if a parent node is deleted, "setup/rooms/1/name"'calls:- ['write', '/setup/rooms/1/name', 'Kitchen']- ['subscribe', 'setup/rooms/1/name', '#subscriber_1', '#callback', { immediate: false }]- ['write', '/setup/rooms/1', null]spies:- ['setup/rooms/+/name', '/setup/rooms/1/name', null]- label: 'FLAGGED > MUST publish a single matched wildcard topic if a parent node is deleted, "setup/rooms/1/name"'calls:- ['write', '/setup/rooms/1/name', 'Kitchen']- ['subscribe', 'setup/rooms/+/name', '#subscriber_1', '#callback', { immediate: false }]- ['write', '/setup/rooms/1', null]spies:- ['setup/rooms/+/name', '/setup/rooms/1/name', null]'#unsubscribe':- label: 'MUST NOT error if pointer has no subscribers'calls:- ['subscribe', 'expected/to/error', '#subscriber_1', '#callback', { immediate: false }]spies: []- label: 'MUST NOT error if pointer has no subscribers'calls:- ['subscribe', 'expected/to/error', '#subscriber_1', '#callback', { immediate: false }]spies: []
/*** Test Dependencies*/import './helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Datastore } from '../../src/v3-compatible/datastore.js'let datastoredescribe('Datastore', () => {beforeEach(() => {datastore = new Datastore()})describe('#set', () => {it('"/a", true', async () => {await datastore.set('/a', true)// const result = datastore._root['a']const result = datastore.get('/a')expect(result).to.equal(true)})it('"/a/b", true', async () => {await datastore.set('/a/b', true)// const result = datastore._root['a']['b']const result = datastore.get('/a/b')expect(result).to.equal(true)})it('"/a/b/c", true', async () => {await datastore.set('/a/b/c', true)// const result = datastore._root['a']['b']['c']const result = datastore.get('/a/b/c')expect(result).to.equal(true)})})describe('#delete', () => {it('"/a"', async () => {datastore.set('/a', true)datastore.delete('/a')// const result = datastore._rootconst result = datastore.get('/a')expect(result).not.to.exist})it('"/a/b"', async () => {datastore.set('/a/b', true)datastore.delete('/a/b')// const result = datastore._rootconst result = datastore.get('/a')expect(result).not.to.exist})it('"/a/b/c"', async () => {datastore.set('/a/b/c', true)datastore.delete('/a/b/c')// const result = datastore._rootconst result = datastore.get('/a')expect(result).not.to.exist})it('"/a2"', async () => {await datastore.set('/a1', true)await datastore.set('/a2', true)await datastore.delete('/a2')// const result = datastore._rootconst result = datastore.get('/a1')expect(result).to.eql(true)})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer as Pointer4 } from '../../../src/datastore/pointer.js'import { Topic as Topic4 } from '../../../src/datastore/topic.js'import { Base } from '../../../src/v3-compatible/datastore/base.js'import { Convenience } from '../../../src/v3-compatible/datastore/convenience.js'import { External } from '../../../src/v3-compatible/datastore/external.js'import { PubSub } from '../../../src/v3-compatible/datastore/pubsub.js'class Datastore extends PubSub(Convenience(External(Base))) {}function subscribe({ label, calls, spies }) {it(label, () => {const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer.path, (value ? JSON.parse(JSON.stringify(value)) : value))}const subscribers = {'#subscriber_1': { 1: 'test' },'#subscriber_2': { 2: 'test' }}for (var args of calls) {const subscriber = ['subscribe', 'unsubscribe'].includes(args[0]) ? subscribers[args[2]] : nullparseDatastoreMethod(datastore, args[0], args.slice(1), { callback, subscriber })}for (const idx in spies) {const expectation = spies[idx]expect(spy.getCall(idx)).to.have.been.calledWith(expectation[0], expectation[1], expectation[2])}})}function topicTree({ label, calls, expectations }) {it(label, async () => {for (var args of calls) {parseDatastoreMethod(datastore, args[0], args.slice(1), { subscriber: args[2] })}expectations.forEach(expectation => {const pointer = new Pointer4(expectation[0])const entries = Array.from(datastore.v4.topics(pointer))if (expectation[1]) {for (var idx in expectation[1]) {expect(entries[idx].pattern).to.be.eql(expectation[1][idx])}} else {if (entries.length == 0) {expect(entries).to.be.a('array').that.has.lengthOf(0)} else {expect(entries[0].tree.value).to.be.a('set').that.has.lengthOf(0)}}})})}const functions = {'subscribe': subscribe,'topic_tree': topicTree,'unsubscribe': subscribe}const tests = requireYAML('../datastore.yaml')['pubsub']let datastoredescribe('Datastore', () => {beforeEach(() => {Pointer4.cache.clear()Topic4.cache.clear()datastore = new Datastore})_.forEach(tests, (unit_tests, unit) => {describe(unit, () => {_.forEach(unit_tests, unit_test => {functions[unit.slice(1)](unit_test)})})})})
/* global parseDatastoreMethod *//*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Base } from '../../../src/v3-compatible/datastore/base.js'import { External } from '../../../src/v3-compatible/datastore/external.js'import { Pointer } from '../../../src/datastore/pointer.js'import { Topic } from '../../../src/datastore/topic.js'class Datastore extends External(Base) {}let datastorefunction base({ label, calls, expectation }) {it(label, async () => {for (var call of calls) {await parseDatastoreMethod(datastore, call[0], call.slice(1))}const result = datastore[expectation[0]](...expectation.slice(1,-1))expect(result).to.be.eql(expectation.slice(-1)[0])})}const functions = {'#read': base,'#search': base,'#write': base,'#merge': base}const yaml = requireYAML('../fixtures/system.yaml')const tests = requireYAML('../datastore.yaml')['external']describe('Datastore External', () => {beforeEach(async () => {Pointer.cache.clear()Topic.cache.clear()datastore = new Datastore()await datastore.init(yaml)})_.forEach(tests, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer as Pointer3 } from '../../../src/v3-compatible/pointer.js'import { Pointer } from '../../../src/datastore/pointer.js'import { Topic } from '../../../src/datastore/topic.js'import { Base } from '../../../src/v3-compatible/datastore/base.js'import { Convenience } from '../../../src/v3-compatible/datastore/convenience.js'import { PubSub } from '../../../src/v3-compatible/datastore/pubsub.js'class Datastore extends PubSub(Convenience(Base)) {}let datastorefunction base({ label, call, expectation }) {it(label, () => {const result = datastore[call[0]](...call.slice(1))expect(result).to.be.eql(expectation)})}function publishTest({ label, calls, spies }) {it(label, async () => {const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, (value ? JSON.parse(JSON.stringify(value)) : value))}for (var args of calls) {parseDatastoreMethod(datastore, args[0], args.slice(1), { callback })}const parsed_spies = []let rowfor (var i in spies) {row = spies[i].slice()row[1] = sinon.match(new Pointer3(row[1]).isEqual)parsed_spies.push(row)}if (parsed_spies.length > 0) {parsed_spies.forEach((parsed_spy, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...parsed_spy)})} else {expect(spy).not.to.have.been.called}})}function callbackTest({ label, calls, expectations }) {it(label, async () => {const callback = sinon.spy()for (const row of calls) {parseDatastoreMethod(datastore, row[0], [row[1], callback])}_.forEach(expectations, expectation => {expect(callback).to.have.been.calledWith(expectation)})})}function queueTest({ label, calls, spies}) {it(label, async () => {datastore.queue = sinon.spy()for (const call of calls) {parseDatastoreMethod(datastore, call[0], call.slice(1))}const parsed_spies = []let rowfor (var i in spies) {row = spies[i].slice()parsed_spies.push(row)}_.forEach(parsed_spies, spy => {expect(datastore.queue).to.have.been.calledWith(...spy)})})}const functions = {'#has': base,'#includes': base,'#any': base,'#keys': base,'#branchPaths': base,'#each': callbackTest,'#queue': publishTest,'#push': queueTest,'#pull': queueTest,'#add': queueTest}const data = {'/setup/rooms/1/name': 'Room 1','/setup/rooms/2/name': 'Room 2','/setup/rooms/1/area_paths': ['/areas/1', '/areas/2'],'/setup/components/1/name': 'Component 1','/setup/components/1/displays/1/name': 'Display 1','/setup/drivers': {},'/q/setup/components/2/name': 'Component 2'}const tests = requireYAML('../datastore.yaml')['convenience']describe('Datastore Convenience', () => {beforeEach(() => {Pointer.cache.clear()Topic.cache.clear()datastore = new Datastore_.forEach(data, (value, path) => {datastore.set(path, value)})})_.forEach(tests, (array, label) => {describe(label, () => {array.forEach(data => {functions[label](data)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Datastore } from '../../../src/v3-compatible/datastore.js'import { Pointer } from '../../../src/datastore/pointer.js'import { Topic } from '../../../src/datastore/topic.js'let datastoreasync function base({ label, calls, expectation }) {it(label, async () => {for (const row of calls) {parseDatastoreMethod(datastore, row[0], row.slice(1))}const result = datastore[expectation[0]](...expectation.slice(1,-1))expect(result).to.be.eql(expectation.slice(-1)[0])})}const functions = {'#initialize': base,'#clear': base,'#set': base,'#get': base,'#delete':base}const tests = requireYAML('../datastore.yaml')['base']describe('Datastore.Base', () => {beforeEach(() => {Pointer.cache.clear()Topic.cache.clear()datastore = new Datastore()})_.forEach(tests, (array, label) => {describe(label, () => {for (const data of array) {functions[label](data)}})})})
/*** Test Dependencies*/import './helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { TopicTree } from '../src/topic_tree.js'describe('TopicTree', () => {describe('#values', () => {it('matches "/setup/rooms/1/name"', () => {const tree = new TopicTree()tree.add('setup', 3000)tree.add('setup/rooms', 3300)tree.add('setup/rooms/1', 3330)tree.add('setup/rooms/1/name', 3333)tree.add('#', 1000)tree.add('setup/rooms/#', 3310)tree.add('setup/rooms/+/name', 3323)tree.add('setup/rooms/+', 3320)tree.add('setup/rooms/+/power', -3323)tree.add('setup/+/+/name', 3223)const values = tree.values('setup/rooms/1/name')expect(values).to.eql([3333, 3323, 3310, 3223, 1000])})})describe('#entries', () => {it('matches "/setup/rooms/1/name"', () => {const tree = new TopicTree()tree.add('setup', 3000)tree.add('setup/rooms', 3300)tree.add('setup/rooms/1', 3330)tree.add('setup/rooms/1/name', 3333)tree.add('#', 1000)tree.add('setup/rooms/#', 3310)tree.add('setup/rooms/+/name', 3323)tree.add('setup/rooms/+', 3320)tree.add('setup/rooms/+/power', -3323)tree.add('setup/+/+/name', 3223)const entries = tree.entries('setup/rooms/1/name')expect(entries).to.eql([['setup/rooms/1/name', 3333],['setup/rooms/+/name', 3323],['setup/rooms/#', 3310],['setup/+/+/name', 3223],['#', 1000]])})})})
---base:'#initialize': [['MUST initialize',[],[[], {}]]]'#clear': [['MUST clear the root',[['set', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604'],['set', '/setup/driver/connected', true],['clear']],[[], {}]]]'#set': [['MUST set a single value "/a", true',[['set', '/a', true]],[['a'], true]],['MUST set a single value "/a/b", true',[['set', '/a/b', true]],[['a', 'b'], true]],['MUST set a single value "/a/b/c", true',[['set', '/a/b/c', true]],[['a', 'b', 'c'], true]]]'#get': [['MUST return a #set value at a path',[['set', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604']],['get', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604']],['MUST return nil for a #delete(d) value at a path',[['set', '/setup/driver/uuid', 'cc88b93a-3556-4e44-9b81-c45aa1ba9604'],['delete', '/setup/driver/uuid']],['get', '/setup/driver/uuid', undefined]]]'#delete': [['MUST set a single value "/a", true',[['set', '/a', true],['delete', '/a']],[[], {}]],['MUST delete a single value "/a/b", true',[['set', '/a/b', true],['delete', '/a/b']],[[], {}]],['MUST delete a single value "/a/b/c", true',[['set', '/a/b/c', true],['delete', '/a/b/c']],[[], {}]],['MUST not clear the root when deleting a root key',[['set', '/a1', true],['set', '/a2', true],['delete', '/a2']],[[], { 'a1': true }]]]convenience:'#each': [['MUST call with the elements of an array',[['each', '/setup/rooms/1/area_paths']],['/areas/1', '/areas/2']]]'#has': [['MUST return false when path does not exist', ['has', '/setup/areas/2'], false],['MUST return true when path does exist', ['has', '/setup/rooms/1/name'], true],['MUST return true when path does exist but is an empty hash', ['has', '/setup/components'], true]]'#includes': [['MUST return true if the found array has a value', ['includes', '/setup/rooms/1/area_paths', '/areas/1'], true],['MUST return false if the found value is not an array', ['includes', '/setup/areas/1', '/rooms/11'], false],['MUST return false if the found array does not have a value', ['includes', '/setup/areas/1/room_paths', '/rooms/11'], false]]'#any': [['MUST return true if node is a hash and is not empty', ['any', '/setup/rooms/1'], true],['MUST return false if node is an empty hash', ['any', '/setup/drivers'], false],['MUST return false if node is an array', ['any', '/setup/areas/1/room_paths'], false],['MUST return false if node is an empty hash', ['any', '/setup/areas/1/name'], false]]'#keys': [['MUST return an array of the keys at a node', ['keys', '/setup/rooms'], ['1', '2']],['MUST return an empty array if the node is primitive', ['keys', '/setup/areas/1/name'], []],['MUST return an empty array if the node is an array', ['keys', '/setup/areas/1/room_paths'], []]]'#branchPaths': [['MUST return branch paths that exist at a passed path', ['branchPaths', '/setup/components/1'], ['/components/1', '/components/1/displays/1']]]'#push': [["'/setup/rooms/2/area_paths', '/areas/2'",[['push', '/setup/rooms/1/area_paths', '/areas/2']],[['/setup/rooms/1/area_paths', ['/areas/1', '/areas/2']]]]]'#pull': [["'/setup/rooms/2/area_paths', '/areas/2'",[['pull', '/setup/rooms/1/area_paths', '/areas/2']],[['/setup/rooms/1/area_paths', ['/areas/1']]]]]'#add': [["'/setup/rooms', { name: 'Dining Room' }",[['add', '/setup/rooms', { 'name': 'Dining Room'}]],[['/setup/rooms/3', { 'name': 'Dining Room' }]]],["'/setup/components', { 'name': 'Component 3' }",[['add', '/setup/components', { 'name': 'Component 3'}]],[['/setup/components/3', { 'name': 'Component 3' }]]]]external:'#read': [['MUST read a datastore node when passed a standard topic, "/setup"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup', { 'rooms': { '1': { 'name': 'Kitchen' }, '2': { 'name': 'Breakfast Room' }}}]],['MUST read a coppiced datastore node when passed a standard topic "/setup" and { coppiced: true }',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup', { coppiced: true }, { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]],['MUST read a coppiced datastore node when passed a MQTT topic, "/setup/rooms/+/name"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/name', { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]],['MUST read a coppiced datastore node when passed a standard topic "/setup/rooms/+/name" and { coppiced: true }',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/name', { coppiced: true }, { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]],['MUST return an empty value when MQTT topic is deeper than existing data "/setup/rooms/+/+/#"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/+/#', {}]],['MUST return an empty value when MQTT topic is deeper than existing data "/setup/rooms/+/+/+"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/+/+', {}]],['MUST return an empty value when MQTT topic is deeper than existing data "/setup/rooms/+/+/*"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['read', '/setup/rooms/+/+/*', {}]],['MUST read a coppiced datastore node when passed a MQTT topic, "systems/local/setup/components/+/displays/+/name"',[],['read', 'systems/local/setup/components/+/displays/+/name', {'/systems/local/setup/components/1/displays/1/name': 'Kitchen Display','/systems/local/setup/components/2/displays/1/name': 'Breakfast Room Display'}]]]'#search': [['MUST find a single matched primitive from a standard topic "setup/rooms/1/name"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['search', 'setup/rooms/1/name', { '/setup/rooms/1/name': 'Kitchen' }]],['MUST find a multiple matched primitives from a MQTT topic "setup/rooms/+/name"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['search', 'setup/rooms/+/name', { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]],['MUST find a multiple matched primitives from a MQTT topic "setup/rooms/#"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room']],['search', 'setup/rooms/#', { '/setup/rooms/1/name': 'Kitchen', '/setup/rooms/2/name': 'Breakfast Room' }]]]'#write': [['MUST overwrite a datastore node "setup/rooms/1/name"',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room'],['write', '/setup/rooms', { '1': { 'name': 'Dining Room' }}]],['read', 'setup/rooms', { '1': { 'name': 'Dining Room' } }]]]'#merge': [['MUST integrate value paths in a datastore node setup/rooms/1/name',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Breakfast Room'],['merge', '/setup/rooms', { '3': { 'name': 'Dining Room' } }]],['read', 'setup/rooms', { '1': { 'name': 'Kitchen' }, '2': { 'name': 'Breakfast Room' }, '3': { 'name': 'Dining Room' } }]],['MUST set values to a new node',[['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['read', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['MUST overwrite primitive values',[['merge', '/state/components/1/calendars/1/update/organizer/email', 'rspec@controlenvy.com'],['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['read', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['MUST overwrite primitive values',[['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com'],['merge', '/state/components/1/calendars/1/update/organizer', 'controlenvy']],['read', '/state/components/1/calendars/1/update/organizer', 'controlenvy']],['MUST overwrite nodes when the path becomes a primitive value',[['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com'],['merge', '/state/components/1/calendars/1/update/organizer', 'controlenvy']],['read', '/state/components/1/calendars/1/update/organizer', 'controlenvy']],['MUST overwrite primitive values when the path becomes a node',[['merge', '/state/components/1/calendars/1/update/organizer', 'controlenvy'],['merge', '/state/components/1/calendars/1/update/organizer/email', 'spec@controlenvy.com']],['read', '/state/components/1/calendars/1/update/organizer', { 'email': 'spec@controlenvy.com' }]]]pubsub:'#topic_tree': [['MUST add MQTT topics to the topic tree "state/components/+/status"',[['subscribe', 'state/components/+/status', '#subscriber', '#callback']],[['state/components/1/status', 'state/components/+/status', '#subscriber']]],['MUST remove MQTT topic from the topic tree "state/components/+/status"',[['subscribe', 'state/components/+/status', '#subscriber', '#callback'],['unsubscribe', 'state/components/+/status', '#subscriber', '#callback']],[['state/components/1/status', null]]],['MUST not affect other subscribers when identical topic is removed from the topic tree',[['subscribe', 'state/components/1/status', '#subscriber_1', '#callback'],['subscribe', 'state/components/1/status', '#subscriber_2', '#callback'],['unsubscribe', 'state/components/1/status', '#subscriber_1', '#callback']],[['state/components/1/status', 'state/components/1/status', '#subscriber_2']]],['MUST not affect other subscriber when one topic is removed from the topic tree',[['subscribe', 'state/components/+/status', '#subscriber', '#callback'],['subscribe', 'state/components/1/status', '#subscriber', '#callback'],['unsubscribe', 'state/components/+/status', '#subscriber', '#callback']],[['state/components/1/status', 'state/components/1/status', '#subscriber']]]]'#subscribe': [['MUST publish a single matched exact topic "setup/rooms/1/name"',[['subscribe', 'setup/rooms/1/name', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['setup/rooms/1/name', '/setup/rooms/1/name', 'Kitchen' ]]],['MUST publish a single matched exact topic "setup/rooms/1/name" with immediate flag',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Patio'],['subscribe', 'setup/rooms/1/name', '#subscriber', '#callback']],[['setup/rooms/1/name', '/setup/rooms/1/name', 'Kitchen' ]]],['MUST publish the root wildcard topic "#"',[['subscribe', '#', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['#', '/setup', {}],['#', '/setup/rooms', {}],['#', '/setup/rooms/1', {}],['#', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish a single matched wildcard topic containing "#", "setup/#"',[['write', '/setup/rooms/1/name', 'Patio'],['subscribe', 'setup/#', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1/name', 'Kitchen']],[['setup/#', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish a single matched wildcard topic containing "+", "setup/rooms/+/name"',[['subscribe', 'setup/rooms/+/name', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['setup/rooms/+/name', '/setup/rooms/1/name', 'Kitchen' ]]],['MUST publish a single matched wildcard topic containing "*", "setup/rooms/*"',[['subscribe', 'setup/rooms/*', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['setup/rooms/*', '/setup/rooms', { '1': { } }],['setup/rooms/*', '/setup/rooms', { '1': { 'name': 'Kitchen' } }]]],['MUST publish a single matched wildcard topic ending with "+", "setup/rooms/1/+"',[['subscribe', 'setup/rooms/1/+', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Kitchen']],[['setup/rooms/1/+', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish a wildcard topic with two "+", "setup/+/+"',[['subscribe', 'setup/+/+', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1/name', 'Kitchen']],[['setup/+/+', '/setup/rooms/1', {}]]],['MUST publish a wildcard topic with "+" and "#", "setup/+/#"',[['subscribe', 'setup/+/#', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1/name', 'Kitchen']],[['setup/+/#', '/setup/rooms/1', {}],['setup/+/#', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish a wildcard topic with "+" and "*", "setup/+/*"',[['subscribe', 'setup/+/*', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1/name', 'Kitchen']],[['setup/+/*', '/setup/rooms', { '1': {} }],['setup/+/*', '/setup/rooms', { '1': { 'name': 'Kitchen' }}]]],['MUST publish the root wildcard topic "#" with immediate flag',[['set', '/setup/rooms/1/name', 'Kitchen'],['subscribe', '#', '#subscriber', '#callback']],[['#', '/setup/rooms/1/name', 'Kitchen']]],['MUST publish multiple matching paths with immediate flag',[['set', '/setup/rooms/1/name', 'Kitchen'],['set', '/setup/rooms/2/name', 'Patio'],['subscribe', '#', '#subscriber', '#callback']],[['#', '/setup/rooms/1/name', 'Kitchen'],['#', '/setup/rooms/2/name', 'Patio']]],['MUST publish multiple matched subscribers',[['subscribe', 'setup/rooms/+/name', '#subscriber', '#callback', { immediate: false }],['subscribe', 'setup/rooms/1/name', '#subscriber', '#callback', { immediate: false }],['set', '/setup/rooms/1/name', 'Patio']],[['setup/rooms/1/name', '/setup/rooms/1/name', 'Patio'],['setup/rooms/+/name', '/setup/rooms/1/name', 'Patio']]],['FLAGGED > MUST publish a null value for a single matched wildcard topic ending in "*", "setup/*"',[['write', '/setup/rooms/1/name', 'Kitchen'],['subscribe', 'setup/*', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1', null]],[['setup/*', '/setup', null]]],['FLAGGED > MUST publish a single matched topic if a parent node is deleted, "setup/rooms/1/name"',[['write', '/setup/rooms/1/name', 'Kitchen'],['subscribe', 'setup/rooms/1/name', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1', null]],[['setup/rooms/+/name', '/setup/rooms/1/name', null]]],['FLAGGED > MUST publish a single matched wildcard topic if a parent node is deleted, "setup/rooms/1/name"',[['write', '/setup/rooms/1/name', 'Kitchen'],['subscribe', 'setup/rooms/+/name', '#subscriber', '#callback', { immediate: false }],['write', '/setup/rooms/1', null]],[['setup/rooms/+/name', '/setup/rooms/1/name', null]]]]'#unsubscribe': [['MUST NOT error if pointer has no subscribers',[['subscribe', 'expected/to/error', '#subscriber', '#callback', { immediate: false }]],[]],['MUST NOT error if pointer has no subscribers',[['subscribe', 'expected/to/error', '#subscriber', '#callback', { immediate: false }]],[]]]
/*** Test Dependencies*/import './helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { defaults } from '../src/schema.js'describe('Schema', () => {describe('#defaults', () => {describe('string', () => {it('const', () => {const schema = {type: 'string',const: 'Hello, Const!'}const result = defaults(schema)expect(result).to.eql('Hello, Const!')})it('default', () => {const schema = {type: 'string',default: 'Hello, Default!'}const result = defaults(schema)expect(result).to.eql('Hello, Default!')})it('const and default', () => {const schema = {type: 'string',const: 'Hello, Const!',default: 'Hello, Default!'}const result = defaults(schema)expect(result).to.eql('Hello, Const!')})})describe('object', () => {it('Control Envy component with source', () => {const schema = {type: 'object',properties: {name: {type: 'string',default: 'Component'},sources: {type: 'object',properties: {'1': {type: 'object',properties: {name: {type: 'string',default: 'Source 1'}}}}}}}const result = defaults(schema)expect(result).to.be.eql({name: 'Component',sources: {'1': {name: 'Source 1'}}})})})})})
/*** Test Dependencies*/import './helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from '../src/pointer.js'describe('Pointer', () => {describe('new Pointer', () => {it('""', () => {const pointer = new Pointer('')expect(pointer._path).to.equal('')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('')expect(pointer.steps).to.eql([])expect(pointer.topic).to.equal('#')expect(pointer.length).to.equal(0)expect(pointer == '').to.be.true})it('"/a"', () => {const pointer = new Pointer('/a')expect(pointer._path).to.equal('/a')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('"/a/b"', () => {const pointer = new Pointer('/a/b')expect(pointer._path).to.equal('/a/b')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})it('[]', () => {const pointer = new Pointer([])expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql([])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('')expect(pointer.steps).to.eql([])expect(pointer.topic).to.equal('#')expect(pointer.length).to.equal(0)expect(pointer == '').to.be.true})it('["a"]', () => {const pointer = new Pointer(['a'])expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql(['a'])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('["a", "b"]', () => {const pointer = new Pointer(['a', 'b'])expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql(['a', 'b'])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})it('"#"', () => {const pointer = new Pointer('#')expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('#')expect(pointer.path).to.equal('')expect(pointer.steps).to.eql([])expect(pointer.topic).to.equal('#')expect(pointer.length).to.equal(0)expect(pointer == '').to.be.true})it('"a"', () => {const pointer = new Pointer('a')expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('a')expect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('"a/b"', () => {const pointer = new Pointer('a/b')expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('a/b')expect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})})describe('pointer.path =', () => {it('"/a"', () => {const pointer = new Pointer('')pointer.path = '/a'expect(pointer._path).to.equal('/a')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('"/a/b"', () => {const pointer = new Pointer('')pointer.path = '/a/b'expect(pointer._path).to.equal('/a/b')expect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})})describe('pointer.steps =', () => {it('["a"]', () => {const pointer = new Pointer([])pointer.steps = ['a']expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql(['a'])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('["a", "b"]', () => {const pointer = new Pointer([])pointer.steps = ['a', 'b']expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.eql(['a', 'b'])expect(pointer._topic).to.be.undefinedexpect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})})describe('pointer.topic =', () => {it('"a"', () => {const pointer = new Pointer('#')pointer.topic = 'a'expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('a')expect(pointer.path).to.equal('/a')expect(pointer.steps).to.eql(['a'])expect(pointer.topic).to.equal('a')expect(pointer.length).to.equal(1)expect(pointer == '/a').to.be.true})it('"a/b"', () => {const pointer = new Pointer('#')pointer.topic = 'a/b'expect(pointer._path).to.be.undefinedexpect(pointer._steps).to.be.undefinedexpect(pointer._topic).to.equal('a/b')expect(pointer.path).to.equal('/a/b')expect(pointer.steps).to.eql(['a', 'b'])expect(pointer.topic).to.equal('a/b')expect(pointer.length).to.equal(2)expect(pointer == '/a/b').to.be.true})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from '../../src/pointer.js'const functions = {'#grove_path=': basic_properties_test,'#flag=': basic_properties_test,'#trunk_path=': basic_properties_test,'#branch_path=': basic_properties_test,'#leaf=': basic_properties_test,'#getters': getters,'#createWithDefaults': createWithDefaults,'#fromMessage': fromMessage,'#toMessage': toMessage,'#queue': mutate_test,'#dequeue': mutate_test,'#changeRoot': mutate_test,'#changeGrove': mutate_test,'#changeTrunk': mutate_test,'#changeBranch': mutate_test}function basic_properties_test({ label, tests }) {const pointer = new Pointer(label)tests.forEach(test => {it(`${test['label']} with ${label}`, () => {const [property, value] = test['call']pointer[property] = valuetest['expectations'].forEach(([property, value]) => {expect(pointer[property]).to.be.eql(value)})})})}function getters({steps}) {const path = `/${steps.join('/')}`.replace(/\/\/+/, '/')it(`MUST correctly parse ${path}`, () => {const pointer = new Pointer(path)const trunk_path = `/${steps.slice(4,6).join('/')}`const flag = steps[2] == '' ? null : steps[2]const root = steps[3] == '' ? null : steps[3]let branch_path = `/${steps.slice(4,-1).join('/')}`.replace(/\/\/+/, '/')branch_path = (branch_path == '/' ? null : branch_path)const twig_path = ((steps.slice(-1)[0] == '') ? null : `/${steps.slice(-1)[0]}`)const leaf = steps.slice(-1)[0]expect(pointer.trunk_path).to.be.eql(trunk_path)expect(pointer.flag).to.be.eql(flag)expect(pointer.root).to.be.eql(root)expect(pointer.branch_path).to.be.eql(branch_path)expect(pointer.twig_path).to.be.eql(twig_path)expect(pointer.leaf).to.be.eql(leaf)})}function createWithDefaults ({ label, call, expectation}) {it(label, () => {const defaults = {trunk_path: '/components/1',root: 'state'}const pointer = Pointer.createWithDefaults(call, defaults)expect(pointer.path).to.be.eql(expectation)})}function toMessage({label, pointer, call, expectation}) {it(label, () => {pointer = new Pointer(pointer)expect(pointer.toMessage(...call)).to.be.eql(expectation)})}function fromMessage({label, call, expectations}) {const pointer = Pointer.fromMessage(call)it(label, () => {expectations.forEach(([property, value]) => {expect(pointer[property]).to.be.eql(value)})})}function mutate_test({ label, pointer, call, expectation }) {pointer = new Pointer(pointer)it(label, () => {if (!call[1]) {pointer = pointer[call[0]]()} else {pointer = pointer[call[0]](...call.slice(1))}const [property, value] = expectationexpect(pointer[property]).to.be.eql(value)})}const tests_1 = requireYAML('../pointer.yaml')['controlenvy']const tests_2 = requireYAML('../pointer.yaml')['inclusion']const tests_3 = requireYAML('../pointer.yaml')['extension']describe('Pointer', () => {_.forEach(tests_1, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})describe('Pointer Inclusions', () => {_.forEach(tests_2, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})describe('Pointer Extensions', () => {_.forEach(tests_3, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from '../../src/pointer/base.js'const functions = {'#initialize_test': initialize_test,'#initialize_edge_case': initialize_edge_case,'#path=': basic_property_test,'#topic=': basic_property_test,'#steps=': basic_property_test}function initialize_edge_case({ data, expectations }) {it(`initializes with edge cases ${JSON.stringify(data)}`, () => {const pointer = new Pointer(data)expect(pointer.path).to.be.eql(expectations['path'])expect(pointer.topic).to.be.eql(expectations['topic'])expect(pointer.steps).to.be.eql(expectations['steps'])expect(pointer.length).to.be.eql(expectations['steps'].length)expect(pointer.is_wildcard).to.be.eql(expectations['wildcard'])})}function initialize_test(data) {const label = data['label']delete data['label']_.forEach(data, (value, key) => {it(label, () => {const pointer = new Pointer(value)const length = 4const wild = key.startsWith('with_')if (typeof value == 'array') {test_pointer(pointer, `/${value.join('/')}`, value.join('/'), value, length, wild)} else if ( typeof value == 'string' ) {if (value.match(/^[\/].*/)) {test_pointer(pointer, value, value.slice(1), value.slice(1).split('/'), length, wild)} else if (value.match(/^[^\/].*/)) {test_pointer(pointer, `/${value}`, value, value.split('/'), length, wild)}}})})}function test_pointer(pointer, path, topic, steps, length, wildcard) {expect(pointer.path).to.be.eql(path)expect(pointer.topic).to.be.eql(topic)expect(pointer.steps).to.be.eql(steps)expect(pointer.length).to.be.eql(length)expect(pointer.is_wildcard).to.be.eql(wildcard)}function basic_property_test({ label, args, call, expectations }) {it(label, () => {const pointer = new Pointer(args)if (call) {pointer[call[0]] = call[1]}expect(pointer._path).to.equal(expectations[0])expect(pointer._steps).to.eql(expectations[1])expect(pointer._topic).to.eql(expectations[2])expect(pointer.path).to.equal(expectations[3])expect(pointer.steps).to.eql(expectations[4])expect(pointer.topic).to.equal(expectations[5])expect(pointer.length).to.equal(expectations[6])expect(pointer == expectations[7]).to.be.true})}const tests = requireYAML('../pointer.yaml')['base']describe('Pointer', () => {_.forEach(tests, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
import { createRequire } from 'module';const require = createRequire(import.meta.url);global.window = {}global.requestAnimationFrame = function() {}global._ = require('lodash')const YAML = require('js-yaml')const fs = require('fs')const path = require('path')function _getCallerFile() {var originalFunc = Error.prepareStackTracevar callerfiletry {var error = new Error()var currentfileError.prepareStackTrace = function(error, stack) {return stack}currentfile = error.stack.shift().getFileName()while (error.stack.length) {callerfile = error.stack.shift().getFileName()if (currentfile !== callerfile) break}} catch (e) {// do nothing}Error.prepareStackTrace = originalFuncreturn callerfile}global.requireYAML = function(file_path) {const dirname = path.dirname(_getCallerFile()).slice('file://'.length)const yaml = fs.readFileSync(path.resolve(dirname, file_path))const json = YAML.load(yaml.toString())convertUndefinedStringToUndefined(json)return json}function convertUndefinedStringToUndefined(json) {if (typeof json == 'object' || Array.isArray(json)) {for (var i in json) {_convertUndefinedStringToUndefined(json, i, json[i])}}}function _convertUndefinedStringToUndefined(parent, key, child) {if (typeof child == 'object' || Array.isArray(child)) {for (var i in child) {_convertUndefinedStringToUndefined(child, i, child[i])}} else if (child == 'undefined') {parent[key] = undefined}}global.parseDatastoreMethod = function(datastore, method, args, { subscriber = {}, callback = () => {} }= {}) {switch (method) {case 'subscribe':if (args.length == 4) {datastore.subscribe(args[0], subscriber, callback, args.slice(-1)[0])} else {datastore.subscribe(args[0], subscriber, callback)}returncase 'unsubscribe':if (args.length == 3) {datastore.unsubscribe(args[0], subscriber, callback)} else {datastore.unsubscribe(args[0], subscriber, callback, args.slice(-1)[0])}default:datastore[method](...args)return}}
systems:local:setup:rooms:'1':name: Kitchencomponent_paths:- /components/1display_paths:- /components/1/displays/1'2':name: Breakfast Roomcomponent_paths:- /components/2display_paths:- /components/2/displays/1components:'1':name: Displaylocation_paths:- /rooms/1displays:'1':name: Kitchen Display'2':name: Displaylocation_paths:- /rooms/2displays:'1':name: Breakfast Room Display
---schema:systems:setup.json:type: object$override:html:edit: /override/area.jsadditionalProperties: falserequired:- name- room_pathsproperties:name:title: Nametype: stringdefault: New Arearoom_paths:title: Rooms$override:html:edit: /override/area/rooms.jstype: arrayadditionalItems: falseitems:type: stringpattern: ^/rooms/\\d+$format: relationshipinverse: area_paths$schema: http://json-schema.org/draft-07/schema#$id: https://v3.schema.controlenvy.com/areas.json
---type: objectproperties:setup:type: objectproperties:rooms:type: objectpattern_properties:'^\d+$':type: objectproperties:component_paths:type: arrayitems:type: stringformat: relationshippattern: '^/components/\d+$'inverse: location_pathsdisplay_paths:type: arrayitems:type: stringformat: relationshippattern: '^/components/\d+/displays/\d+$'inverse: room_pathscomponents:type: objectpattern_properties:'^\d+$':type: objectproperties:location_paths:type: arrayitems:type: stringformat: relationshippattern: '^/rooms/\d+$'inverse: component_pathsdisplays:type: objectpattern_properties:'^\d+$':type: objectproperties:room_paths:type: arrayitems:type: stringformat: relationshippattern: '^/rooms/\d+$'inverse: display_paths
---systems:2175edf8-5dac-4b9d-9ba5-8f830bef452a:setup:rooms:'1':component_paths:- /components/1
---systems:2175edf8-5dac-4b9d-9ba5-8f830bef452a:setup:components:'1':name: Component 1location_paths:- /rooms/1'2':name: Component 2'3':name: Component 3displays:'1':name: Display 1
---systems:2175edf8-5dac-4b9d-9ba5-8f830bef452a:setup:areas:'1':$schema: /areas.jsoncomponent_paths:- /components/1
chain:'#group':- label: MUST create a single injectableinstructions:- [call, prop, system_path, systems/1]- [expect, props, system_path, systems/1]- label: MUST link a single dependentinstructions:- [set, /systems/1/setup/name, System 1]- [call, prop, system_path, systems/1]- [call, link, system_name, :system_path/setup/name]- [expect, links, system_name, System 1]- label: MUST update a single linkinstructions:- [set, /systems/1/setup/name, System 1]- [call, prop, system_path, systems/1]- [expect, props, system_path, systems/1]- [call, prop, system_path, systems/2]- [expect, props, system_path, systems/2]- label: MUST update dependentsinstructions:- [set, /systems/1/setup/name, System 1]- [set, /systems/2/setup/name, System 2]- [call, prop, system_path, systems/1]- [call, link, system_name, :system_path/setup/name]- [expect, links, system_name, System 1]- [call, prop, system_path, systems/2]- [expect, links, system_name, System 2]- label: MUST create multiple linksinstructions:- [set, /setup/components/1/sources/1/name, Apple TV]- [call, prop, source_path, components/1/sources/1]- [call, link, source_name, setup/:source_path/name]- [expect, props, source_path, components/1/sources/1]- [expect, links, source_name, Apple TV]- label: MUST update dependantsinstructions:- [set, /setup/components/1/sources/1/name, Apple TV]- [set, /setup/components/2/sources/1/name, Kaleidescape]- [call, prop, source_path, components/1/sources/1]- [call, link, source_name, setup/:source_path/name]- [expect, props, source_path, components/1/sources/1]- [expect, links, source_name, Apple TV]- [call, prop, source_path, components/2/sources/1]- [expect, links, source_name, Kaleidescape]- label: MUST update dependant when updatedinstructions:- [set, /setup/rooms/1/source_path, /components/1/sources/1]- [set, /setup/components/1/sources/1/name, Apple TV]- [set, /setup/components/2/sources/1/name, Kaleidescape]- [call, link, source_path, setup/rooms/1/source_path]- [call, link, source_name, setup/:source_path/name]- [expect, links, source_path, /components/1/sources/1]- [expect, links, source_name, Apple TV]- [set, /setup/rooms/1/source_path, /components/2/sources/1]- [expect, links, source_path, /components/2/sources/1]- [expect, links, source_name, Kaleidescape]- label: MUST update relinkinstructions:- [set, /setup/rooms/1/source_path, /components/1/sources/1]- [set, /setup/components/1/sources/1/name, Kaleidescape]- [call, link, source_path, setup/rooms/1/source_path]- [call, link, source_name, setup/:source_path/name]- [spy, callback]- [expect, spy, false, callback]- [call, link, source_name, setup/:source_path/name, spy:callback, true]- [expect, spy, true, callback]- [expect, links, source_name, Kaleidescape]- label: MUST evaluate function in pipeinstructions:- [set, /setup/components/1/sources/1/name, Apple TV]- [set, /setup/components/2/sources/1/name, Kaleidescape]- [call, prop, source_path, components/1/sources/1]- [call, pipe, source_trunk_path, source_path, "eval:value => { return `/${value.split('/').slice(0, 2).join('/')}` }"]- [expect, pipes, source_trunk_path, /components/1]- [call, pipe, source_trunk_path_length, source_trunk_path, length]- [expect, pipes, source_trunk_path_length, 13]- label: MUST call callbacks with default stringinstructions:- [set, /setup/components/1/sources/1/name, Apple TV]- [spy, one]- [call, link, source_name, setup/components/1/sources/1/name, spy:one, { type: string, value: 'default' }]- [expect, spy, [0, Apple TV], one]- [set, /setup/components/1/sources/1/name, null]- [expect, spy, [1, default], one]- label: MUST call callbacks with default numberinstructions:- [set, /setup/components/1/speakers/1/volume, 50]- [spy, one]- [call, link, source_name, setup/components/1/speakers/1/volume, spy:one, { type: number, value: 0 }]- [expect, spy, [0, 50], one]- [set, /setup/components/1/speakers/1/volume, null]- [expect, spy, [1, 0], one]- label: MUST call callbacks with default arrayinstructions:- [set, /setup/components/1/speakers/1/source_paths, [/components/2/sources/1]]- [spy, one]- [call, link, source_name, setup/components/1/speakers/1/source_paths, spy:one, { type: array, value: [] }]- [expect, spy, [0, [/components/2/sources/1]], one]- [set, /setup/components/1/speakers/1/source_paths, null]- [expect, spy, [1, []], one]
/*** Test Dependencies*/import './helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Datastore } from '../src/datastore.js'const datastore = new Datastore()describe('Datastore', () => {describe('#set', () => {it('"/a", true', async () => {await datastore.set('/a', true)// const result = datastore._root['a']const result = datastore.get('/a')expect(result).to.equal(true)})it('"/a/b", true', async () => {await datastore.set('/a/b', true)// const result = datastore._root['a']['b']const result = datastore.get('/a/b')expect(result).to.equal(true)})it('"/a/b/c", true', async () => {await datastore.set('/a/b/c', true)// const result = datastore._root['a']['b']['c']const result = datastore.get('/a/b/c')expect(result).to.equal(true)})})describe('#delete', () => {it('"/a"', async () => {await datastore.set('/a', true)await datastore.delete('/a')// const result = datastore._rootconst result = datastore.get('/a')expect(result).not.to.exist})it('"/a/b"', async () => {await datastore.set('/a/b', true)await datastore.delete('/a/b')// const result = datastore._rootconst result = datastore.get('/a')expect(result).not.to.exist})it('"/a/b/c"', async () => {await datastore.set('/a/b/c', true)await datastore.delete('/a/b/c')// const result = datastore._rootconst result = datastore.get('/a')expect(result).not.to.exist})it('"/a2"', async () => {await datastore.set('/a1', true)await datastore.set('/a2', true)await datastore.delete('/a2')// const result = datastore._rootconst result = datastore.get('/a1')expect(result).to.eql(true)})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from '../../src/pointer.js'import { Datastore } from '../../src/datastore.js'const datastore = new Datastore()datastore.write('', {setup: {rooms: {'1': {name: 'Kitchen',source_path: '/components/1/sources/1'}},components: {'1': {sources: {'1': {name: 'Apple TV'}}},'2': {sources: {'1': {name: 'Kaleidescape'}}}}},systems: {'1': {setup: {name: 'System 1'}},'2': {setup: {name: 'System 2'}}}})describe('datastore.transaction()', () => {let transactionbefore(() => {transaction = datastore.transaction()})after(() => {transaction.destroy()})it('reads null if no transaction or datastore path found', () => {const result = transaction.read('/systems/3/setup/name')expect(result).to.eql(undefined)})it('reads the datastore if no transaction path found', () => {const result = transaction.read('/systems/1/setup/name')expect(result).to.eql('System 1')})it('sets coppice values', () => {transaction.write('/systems/1/setup/name', 'Foo')expect(transaction.coppice.get('/systems/1/setup/name')).to.eql('Foo')})it('dequeues coppice values', () => {transaction.write('/systems/1/q/setup/name', 'Foo')expect(transaction.coppice.get('/systems/1/setup/name')).to.eql('Foo')})it('prefers coppice values when path is present', () => {transaction.write('/systems/1/setup/name', 'Foo')const result = transaction.read('/systems/1/setup/name')expect(result).to.eql('Foo')})it('prefers coppice values when dequeued path is present', () => {transaction.write('/systems/1/setup/name', 'Foo')const result = transaction.read('/systems/1/q/setup/name')expect(result).to.eql('Foo')})it('overwrites coppice values when path is present', () => {transaction.write('/systems/1/setup/name', 'Foo')expect(transaction.read('/systems/1/setup/name')).to.eql('Foo')transaction.write('/systems/1/setup/name', 'Bar')expect(transaction.read('/systems/1/setup/name')).to.eql('Bar')})it('queues coppice values when sending', () => {const spy = sinon.spy()const subscriber = {}const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('systems/+/q/setup/#', subscriber, callback, { immediate: false })transaction.write('/systems/2/setup/name', 'Foo')transaction.write('/systems/1/setup/name', 'Foo')transaction.send()const calls = [['systems/+/q/setup/#', sinon.match(new Pointer('/systems/1/q/setup/name').isEqual), 'Foo'],['systems/+/q/setup/#', sinon.match(new Pointer('/systems/2/q/setup/name').isEqual), 'Foo']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport { Datastore as Base } from '../../src/datastore/base.js'import { Convenience } from '../../src/datastore/convenience.js'import { External } from '../../src/datastore/external.js'import { Schema } from '../../src/datastore/schema.js'import { Transact } from '../../src/datastore/transact.js'const Test = superclass =>class extends superclass {queue(pointer, value) {this.merge(pointer, value)}}class Datastore extends Test(Schema(Transact(Convenience(External(Base))))) {}describe('Datastore.Schema', () => {describe('#pushWithSchema', () => {it('pushes to relationship arrays and inverse relationship arrays', () => {const datastore = new Datastore()const room_yaml = requireYAML('../fixtures/room.yaml')const component_yaml = requireYAML('../fixtures/component.yaml')const schema_yaml = requireYAML('../fixtures/schema.yaml')datastore.merge('', room_yaml)datastore.merge('', component_yaml)let component_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths')let location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/2/location_paths')expect(component_paths).to.eql(['/components/1'])expect(location_paths).to.be.undefinedconst schema = schema_yaml.properties.setup.properties.rooms.pattern_properties['^\\d+$'].properties.component_pathsdatastore.pushWithSchema('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths', '/components/2', schema)component_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths')location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/2/location_paths')expect(component_paths).to.eql(['/components/1', '/components/2'])expect(location_paths).to.eql(['/rooms/1'])})it('pushes to instances and inverse relationship arrays', () => {const datastore = new Datastore()const room_yaml = requireYAML('../fixtures/room.yaml')const component_yaml = requireYAML('../fixtures/component.yaml')const schema_yaml = requireYAML('../fixtures/schema.yaml')datastore.merge('', room_yaml)datastore.merge('', component_yaml)let component_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths')let location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/2/location_paths')expect(component_paths).to.eql(['/components/1'])expect(location_paths).to.be.undefinedconst schema = schema_yaml.properties.setup.properties.rooms.pattern_properties['^\\d+$']datastore.pushWithSchema('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1', '/components/2', schema)component_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths')location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/2/location_paths')expect(component_paths).to.eql(['/components/1', '/components/2'])expect(location_paths).to.eql(['/rooms/1'])})it('deeply pushes to instances and inverse relationship arrays', () => {const datastore = new Datastore()const room_yaml = requireYAML('../fixtures/room.yaml')const component_yaml = requireYAML('../fixtures/component.yaml')const schema_yaml = requireYAML('../fixtures/schema.yaml')datastore.merge('', room_yaml)datastore.merge('', component_yaml)let room_component_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths')let room_display_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/display_paths')expect(room_component_paths).to.eql(['/components/1'])expect(room_display_paths).to.be.undefinedlet component_location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/3/location_paths')let component_display_room_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/3/displays/1/room_paths')expect(component_location_paths).to.be.undefinedexpect(component_display_room_paths).to.be.undefinedconst schema = schema_yaml.properties.setup.properties.components.pattern_properties['^\\d+$']datastore.pushWithSchema('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/3', '/rooms/1', schema)room_component_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths')room_display_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/display_paths')expect(room_component_paths).to.eql(['/components/1', '/components/3'])expect(room_display_paths).to.eql(['/components/3/displays/1'])component_location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/3/location_paths')component_display_room_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/3/displays/1/room_paths')expect(component_location_paths).to.eql(['/rooms/1'])expect(component_display_room_paths).to.eql(['/rooms/1'])})})describe('#destroyWithSchema', () => {it('destroys relationship arrays and inverse relationship arrays', () => {const datastore = new Datastore()const room_yaml = requireYAML('../fixtures/room.yaml')const component_yaml = requireYAML('../fixtures/component.yaml')const schema_yaml = requireYAML('../fixtures/schema.yaml')datastore.merge('', room_yaml)datastore.merge('', component_yaml)let component_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths')let location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/1/location_paths')expect(component_paths).to.eql(['/components/1'])expect(location_paths).to.eql(['/rooms/1'])const schema = schema_yaml.properties.setup.properties.rooms.pattern_properties['^\\d+$'].properties.component_pathsdatastore.destroyWithSchema('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths', schema)component_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1/component_paths')location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/1/location_paths')expect(component_paths).to.be.undefinedexpect(location_paths).to.eql([])})it('destroys instances and inverse relationship arrays', () => {const datastore = new Datastore()const room_yaml = requireYAML('../fixtures/room.yaml')const component_yaml = requireYAML('../fixtures/component.yaml')const schema_yaml = requireYAML('../fixtures/schema.yaml')datastore.merge('', room_yaml)datastore.merge('', component_yaml)let room = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1')let location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/1/location_paths')expect(room).to.eql({ component_paths: ['/components/1'] })expect(location_paths).to.eql(['/rooms/1'])const schema = schema_yaml.properties.setup.properties.rooms.pattern_properties['^\\d+$']datastore.destroyWithSchema('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1', schema)room = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/rooms/1')location_paths = datastore.get('/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/components/1/location_paths')expect(room).to.be.undefinedexpect(location_paths).to.eql([])})})// describe('#destroyWithSchema', () => {// it('finds a root schema', () => {// const datastore = new Datastore()// const area_yaml = requireYAML('../fixtures/area.yaml')// const schema_tree_yaml = requireYAML('../fixtures/schema_tree.yaml')// datastore.merge('', area_yaml)// datastore.merge('', schema_tree_yaml)// let path = '/systems/2175edf8-5dac-4b9d-9ba5-8f830bef452a/setup/areas/1'// let [schema, schema_path] = datastore.schema(path)// expect(schema_path).to.eql('/schema/areas.json')// expect(schema).to.eql(datastore.read('/schema/areas.json'))// })// })})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from '../../src/pointer.js'import { Datastore as Base } from '../../src/datastore/base.js'import { External } from '../../src/datastore/external.js'import { PubSub } from '../../src/datastore/pubsub.js'class Datastore extends PubSub(External(Base)) {}function subscribe({ label, calls, spies }) {it(label, () => {const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, (value ? JSON.parse(JSON.stringify(value)) : value))}_.forEach(calls, args => {parseDatastoreMethod(datastore, args[0], args.slice(1), { callback })})const parsed_spies = []let rowfor (var i in spies) {row = spies[i].slice()row[1] = sinon.match(new Pointer(row[1]).isEqual)parsed_spies.push(row)}if (parsed_spies.length > 0) {parsed_spies.forEach((parsed_spy, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...parsed_spy)})} else {expect(spy).not.to.have.been.called}})}function topicTree({ label, calls, expectations }) {it(label, () => {_.forEach(calls, args => {parseDatastoreMethod(datastore, args[0], args.slice(1), { subscriber: args[2] })})expectations.forEach(expectation => {const entries = datastore._topic_tree.entries(expectation[0])if (expectation[1]) {expect(entries[0][0]).to.be.eql(expectation[1])expect(entries[0][1]).to.be.a('map').that.has.key(expectation[2])} else {expect(entries[0][1]).to.be.a('map').that.has.lengthOf(0)}})})}const functions = {'subscribe': subscribe,'topic_tree': topicTree,'unsubscribe': subscribe}const tests = requireYAML('../datastore.yaml')['pubsub']let datastoredescribe('Datastore', () => {beforeEach(() => {datastore = new Datastore})_.forEach(tests, (unit_tests, unit) => {describe(unit, () => {_.forEach(unit_tests, unit_test => {functions[unit.slice(1)](unit_test)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from '../../src/pointer.js'import { Datastore as Base } from '../../src/datastore/base.js'import { External } from '../../src/datastore/external.js'import { PubSub } from '../../src/datastore/pubsub.js'import { Hooks } from '../../src/datastore/hooks.js'class Datastore extends Hooks(PubSub(External(Base))) {}describe('Datastore', () => {describe('#hooks', () => {describe('"/setup/rooms/1/name", "Kitchen"', () => {it('#subscribe "when/setup/rooms/*"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/setup/rooms/*', subscriber, callback, {immediate: false})datastore.set('/setup/rooms/1/name', 'Kitchen')const calls = [['when/setup/rooms/*', sinon.match(new Pointer('/setup/rooms/1').isEqual), { 1: {} }],['when/setup/rooms/*', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), { 1: { name: 'Kitchen' } }]]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "#"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('#', subscriber, callback, { immediate: false })datastore.set('/setup/rooms/1/name', 'Kitchen')const calls = [['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen'],['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen'],['#', sinon.match(new Pointer('/setup').isEqual), {}],['#', sinon.match(new Pointer('/setup/rooms').isEqual), {}],['#', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}],['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen'],['#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "when/#"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/#', subscriber, callback, {immediate: false})datastore.set('/setup/rooms/1/name', 'Kitchen')const calls = [['when/#', sinon.match(new Pointer('/setup').isEqual), {}],['when/#', sinon.match(new Pointer('/setup/rooms').isEqual), {}],['when/#', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}],['when/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "before/setup/#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()const pointer = Pointer.create('/setup/rooms/1/name')datastore.subscribe('before/setup/#', subscriber, callback, {immediate: false})datastore.write(pointer, 'Kitchen')expect(callback).to.have.been.calledWith('before/setup/#', sinon.match(pointer.isEqual), 'Kitchen')})it('#subscribe "when/setup/#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()const pointer = Pointer.create('/setup/rooms/1/name')datastore.subscribe('when/setup/#', subscriber, callback, {immediate: false})datastore.write(pointer, 'Kitchen')expect(callback).to.have.been.calledWith('when/setup/#', sinon.match(pointer.isEqual), 'Kitchen')})it('#subscribe "after/setup/#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()const pointer = Pointer.create('/setup/rooms/1/name')datastore.subscribe('after/setup/#', subscriber, callback, {immediate: false})datastore.write(pointer, 'Kitchen')expect(callback).to.have.been.calledWith('after/setup/#', sinon.match(pointer.isEqual), 'Kitchen')})it('#subscribe "when/#", immediate', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.set('/setup/rooms/1/name', 'Kitchen')datastore.subscribe('when/#', subscriber, callback)const calls = [['when/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "when/setup/#", immediate', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.write('/setup/rooms/1/name', 'Kitchen')datastore.subscribe('when/setup/#', subscriber, callback)const calls = [['when/setup/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "async/setup/#", immediate', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.write('/setup/rooms/1/name', 'Kitchen')datastore.subscribe('async/setup/#', subscriber, callback)const calls = [['async/setup/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#unsubscribe "when/#"', () => {const datastore = new Datastore()const subscriber = {}const callback = sinon.spy()datastore.subscribe('when/#', subscriber, callback)datastore.unsubscribe('when/#', subscriber)const map = datastore._topic_tree._root['when']['#']._valueexpect(map).to.be.emptyexpect(() => {datastore.set('/expected/to/error', false)}).to.not.throw()})it('#subscribe "when/setup/+/+"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/setup/+/+', subscriber, callback, {immediate: false})datastore.write('/setup/rooms/1/name', 'Kitchen')const calls = [['when/setup/+/+', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}]]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "when/setup/+/#"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/setup/+/#', subscriber, callback, {immediate: false})datastore.write('/setup/rooms/1/name', 'Kitchen')const calls = [['when/setup/+/#', sinon.match(new Pointer('/setup/rooms/1').isEqual), {}],['when/setup/+/#', sinon.match(new Pointer('/setup/rooms/1/name').isEqual), 'Kitchen']]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})it('#subscribe "when/setup/+/*"', () => {const datastore = new Datastore()const subscriber = {}const spy = sinon.spy()const callback = (topic, pointer, value) => {spy(topic, pointer, JSON.parse(JSON.stringify(value)))}datastore.subscribe('when/setup/+/*', subscriber, callback, {immediate: false})datastore.write('/setup/rooms/1/name', 'Kitchen')const calls = [['when/setup/+/*',sinon.match(new Pointer('/setup/rooms/1').isEqual),{'1': {}}],['when/setup/+/*',sinon.match(new Pointer('/setup/rooms/1/name').isEqual),{'1': {name: 'Kitchen'}}]]calls.forEach((call, i) => {expect(spy.getCall(i)).to.have.been.calledWith(...call)})})})})})
/* global parseDatastoreMethod *//*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Datastore as Base } from '../../src/datastore/base.js'import { External } from '../../src/datastore/external.js'class Datastore extends External(Base) {}let datastorefunction base({ label, calls, expectation }) {it(label, () => {_.forEach(calls, call => {parseDatastoreMethod(datastore, call[0], call.slice(1))})const result = datastore[expectation[0]](...expectation.slice(1,-1))expect(result).to.be.eql(expectation.slice(-1)[0])})}const functions = {'#read': base,'#search': base,'#write': base,'#merge': base}const yaml = requireYAML('../fixtures/system.yaml')const tests = requireYAML('../datastore.yaml')['external']describe('Datastore External', () => {beforeEach(() => {datastore = new Datastore()datastore.write('', yaml)})_.forEach(tests, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Pointer } from '../../src/pointer.js'import { Datastore as Base } from '../../src/datastore/base.js'import { Convenience } from '../../src/datastore/convenience.js'class Datastore extends Convenience(Base) {}let datastorefunction base({ label, call, expectation }) {it(label, () => {const result = datastore[call[0]](...call.slice(1))expect(result).to.be.eql(expectation)})}function callbackTest({ label, calls, expectations }) {it(label, () => {const callback = sinon.spy()_.forEach(calls, row => {parseDatastoreMethod(datastore, row[0], [row[1], callback])})_.forEach(expectations, expectation => {expect(callback).to.have.been.calledWith(expectation)})})}function queueTest({ label, calls, spies}) {it(label, () => {datastore.queue = sinon.spy()_.forEach(calls, call => {parseDatastoreMethod(datastore, call[0], call.slice(1))})const parsed_spies = []let rowfor (var i in spies) {row = spies[i].slice()row[0] = sinon.match(new Pointer(row[0]).isEqual)parsed_spies.push(row)}_.forEach(parsed_spies, spy => {expect(datastore.queue).to.have.been.calledWith(...spy)})})}const functions = {'#has': base,'#includes': base,'#any': base,'#keys': base,'#branchPaths': base,'#each': callbackTest,'#push': queueTest,'#pull': queueTest,'#add': queueTest}const data = {'/setup/rooms/1/name': 'Room 1','/setup/rooms/2/name': 'Room 2','/setup/rooms/1/area_paths': ['/areas/1', '/areas/2'],'/setup/components/1/name': 'Component 1','/setup/components/1/displays/1/name': 'Display 1','/setup/drivers': {},'/q/setup/components/2/name': 'Component 2'}const tests = requireYAML('../datastore.yaml')['convenience']describe('Datastore Convenience', () => {beforeEach(() => {datastore = new Datastore_.forEach(data, (value, path) => {datastore.set(path, value)})})_.forEach(tests, (array, label) => {describe(label, () => {array.forEach(data => {functions[label](data)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinon from 'sinon'import sinonChai from 'sinon-chai'chai.use(sinonChai)import { Datastore } from '../../src/datastore.js'import { Pointer } from '../../src/datastore/pointer.js'import { Topic } from '../../src/datastore/topic.js'let datastorelet chainconst types = {props: 'Injectable',links: 'Linkable',pipes: 'Pipable',paths: 'Pathable'}async function parseCall(chain, spies, instruction) {instruction.forEach((value, idx) => {if (typeof value === 'string') {if (value.startsWith('spy')) {instruction[idx] = spies[value.replace('spy:', '')]} else if (value.startsWith('eval')) {instruction[idx] = eval(value.replace('eval:', ''))}}})await chain[instruction[1]](...instruction.slice(2))}function parseSpy(spies, instruction) {spies[instruction[1]] = sinon.spy()}function parseExpectation(chain, spies, instruction) {switch(instruction[1]) {case 'spy':if (instruction[2] === true) {expect(spies[instruction[3]]).to.have.been.called} else if (instruction[2] === false) {expect(spies[instruction[3]]).not.to.have.been.called} else if (Array.isArray(instruction[2])) {let test = spies[instruction[3]].getCall(instruction[2][0]).argsexpect(test).to.deep.include(...instruction[2].slice(1))}returndefault:expect(Object.keys(chain[instruction[1]])).to.have.include(instruction[2])expect(chain[instruction[1]][instruction[2]].constructor.name).to.eql(types[instruction[1]])expect(chain[instruction[1]][instruction[2]].value).to.eql(instruction[3])return}}function base({ label, instructions }) {it(label, async () => {const spies = {}for (var instruction of instructions) {switch(instruction[0]) {case 'spy':parseSpy(spies, instruction)breakcase 'call':await parseCall(chain, spies, instruction)breakcase 'set':await datastore.set(instruction[1], instruction[2])breakcase 'expect':parseExpectation(chain, spies, instruction)break}}})}const functions = {'#group': base}const tests = requireYAML('../datastore.yaml')['chain']describe('Chain', () => {beforeEach(() => {datastore = new Datastore()chain = datastore.chain()})afterEach(() => {chain.destroy()Pointer.cache.clear()Topic.cache.clear()})_.forEach(tests, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
/*** Test Dependencies*/import '../helper.js'import chai from 'chai'const expect = chai.expectimport sinonChai from 'sinon-chai'chai.use(sinonChai)import { Datastore } from '../../src/datastore/base.js'let datastorefunction root({ label, calls, expectation }) {it(label, () => {_.forEach(calls, row => {parseDatastoreMethod(datastore, row[0], row.slice(1))})const [steps, value] = expectationlet result = datastore._rootfor (var i in steps) {result = result[steps[i]]}expect(result).to.be.eql(value)})}function base({ label, calls, expectation }) {it(label, () => {_.forEach(calls, row => {parseDatastoreMethod(datastore, row[0], row.slice(1))})const result = datastore[expectation[0]](...expectation.slice(1,-1))expect(result).to.be.eql(expectation.slice(-1)[0])})}const functions = {'#initialize': root,'#clear': root,'#set': root,'#get': base,'#delete':root}const tests = requireYAML('../datastore.yaml')['base']describe('Datastore.Base', () => {beforeEach(() => {datastore = new Datastore()})_.forEach(tests, (array, label) => {describe(label, () => {array.forEach((data) => {functions[label](data)})})})})
import { Datastore as Base, DatastoreEvent, Pointer, Topic } from './datastore/base.js'import API from './datastore/api.js'import Convenience from './datastore/convenience.js'import Svelte from './datastore/svelte.js'const Datastore = class extends Convenience(API(Base)) {constructor() {super()this.svelte = this.svelte.bind(this)}/** Extensions*/svelte(pattern, placeholder, mapper) {return new Svelte(this, pattern, placeholder, mapper)}/** Experimental*/pick(topic) {this._pick(topic)}_pick(topic, idx = 0, tree = this.data_tree, result = {}) {if (topic.steps.length === idx && tree.value) {result[topic.steps[idx]] = tree.valuereturn result}let level = topic.steps[idx]switch (level) {case '+':for (const child of topic.tree) {result[child.key] = {}this._pick(topic, idx + 1, child, result[child.key])}return resultcase '#':result[level] = topic.tree.toJSON()return resultdefault:if (topic.tree.has(level)) {result[level] = {}return this._pick(topic, idx, topic.get(level), result[level])}}return result}}const toPointer = (text) => {if (text[0] != '/') return textreturn text.replace(/~/g, '~0').replace(/\//g, '~1')}const toPath = (text) => {if (text.slice(0, 2) != '~1') return textreturn text.replace(/~1/g, '/').replace(/~0/g, '~')}const datastore = new Datastore()export default Datastoreexport { coppice } from './datastore/helpers/coppice.js'export { isCoppice } from './datastore/helpers/is_coppice.js'export { isTraversable } from './datastore/helpers/is_traversable.js'export { isEmpty } from './datastore/helpers/is_empty.js'export { Datastore, DatastoreEvent, Pointer, Topic, datastore, toPointer, toPath }
const Tree = class extends Map {constructor(key, parent) {super()this.key = keythis.parent = parent}toJSON() {if (typeof this.value !== 'undefined') {return this.value}if (this.size === 0) {return null}// TODO: @thomascowan Check JSPERF => BM self-tailing vs existingconst object = {}this.forEach((child, key) => {object[key] = child.toJSON()})return object}add(key) {const child = new this.constructor(key, this)this.set(key, child)return child}}export default Treeexport { Tree }
import Parser from './parser.js'const CACHE = new Map ()const Topic = class extends(Parser) {// FIXME: use class field `static cache = new Map()` when availablestatic get cache() {return CACHE}constructor(pattern_or_levels, { cache = false } = {}) {super()const pattern = typeof pattern_or_levels === 'string'? pattern_or_levels: `${pattern_or_levels.join('/')}`const cached = this.constructor.cache.get(pattern)if (cached instanceof this.constructor) {return cached}const steps = Array.isArray(pattern_or_levels)? pattern_or_levels: Object.freeze(pattern_or_levels.split('/'))Object.defineProperties(this, {pattern: {value: pattern,enumerable: true},steps: {value: steps,enumerable: true},wildcard: {value: steps.some(level => level.match(/\+|#/)),enumerable: true}})if (cache) {this.constructor.cache.set(pattern, this)}}toString() {return this.pattern}}export default Topicexport { Topic }
import DatastoreEvent from "./event.js"import Pointer from "./pointer.js"import Topic from "./topic.js"const DatastoreSvelte = class {constructor(datastore, pattern, placeholder, mapper) {if (typeof datastore === 'undefined') {throw new Error(`new DatastoreSvelte({ pattern: ${pattern} }))`)}this.datastore = datastorethis.topic = new Topic(pattern)this.placeholder = placeholderthis.mapper = mapperthis.subscribe = this.subscribe.bind(this)this.unsubscribe = this.unsubscribe.bind(this)}get() {if (!this.topic.wilcard) {return this.datastore.get(`/${this.topic.pattern}`)?.toJSON()} else {return [...this.datastore.pointers(this.topic)]}}set(value) {if (!this.topic.wilcard) {if (this.topic.pattern.startsWith('session')) {this.datastore.set(`/${this.topic.pattern}`, value)} else {this.datastore.queue(`/${this.topic.pattern}`, value)}} else {for (const pointer of this.pointers(this.topic)) {if (this.topic.pattern.startsWith('session')) {this.datastore.set(`/${this.topic.pattern}`, value)} else {this.datastore.queue(`/${this.topic.pattern}`, value)}}}}subscribe(subscription) {if (this.topic.wildcard) {this.subscription = (topic) => {try {let value// FIXME: @chrismassey change from mapper to reducerconst pointers = [...this.datastore.pointers(topic)]value = pointers.map((pointer) => {if (typeof this.mapper === 'function') {return this.mapper(pointer)}return pointer.tree.value ?? pointer.tree.key})if (value.length === 0) {if (Array.isArray(this.placeholder)) {value = this.placeholder} else {value = [this.placeholder]}}subscription(value)} catch (error) {console.error(`DatastoreSvelte#subscription`, { this: this, topic }, error)}}this.subscription(this.topic)} else {this.subscription = (topic, event) => {const value = event.pointer.tree?.toJSON() ?? this.placeholdersubscription(value)}this.subscription(this.topic, new DatastoreEvent(DatastoreEvent.TREE_CHANGED,new Pointer(`/${this.topic.pattern}`)))}// FIXME: @chrismassey add support for `datastore.subscribe(topic, /* ... */)`this.datastore.subscribe(this.topic.pattern, this.subscription, { immediate: false })return this.unsubscribe}unsubscribe() {this.datastore.unsubscribe(this.topic.pattern, this.subscription)delete this.datastoredelete this.subscriptiondelete this.topic}}export default DatastoreSvelteexport { DatastoreSvelte }
import Parser from './parser.js'const CACHE = new Map()const Pointer = class extends(Parser) {// FIXME: use class field `static cache = new Map()` when availablestatic get cache() {return CACHE}constructor(path_or_steps, { cache = false } = {}) {super()const path = typeof path_or_steps === 'string'? path_or_steps: path_or_steps.length > 0 ? `/${path_or_steps.join('/')}` : ''const cached = this.constructor.cache.get(path)if (cached instanceof this.constructor) {return cached}const steps = Array.isArray(path_or_steps)? path_or_steps: Object.freeze(path_or_steps.split('/').slice(1))Object.defineProperties(this, {path: {value: path,enumerable: true},steps: {value: steps,enumerable: true}})if (cache) {this.constructor.cache.set(path, this)}}toString() {return this.path}}export default Pointerexport { Pointer }
export default class Parser {get flag() {if (!this._parsed) this.parse()if (this._flag_start == null) {return null}return this.steps[this._flag_start]}changeFlag(flag) {if (!this._parsed) this.parse()return new this.constructor([flag, this.root, ...this.branch, this.leaf].filter(step => step))}dequeue() {return (this.flag === null) ? this : this.changeFlag('')}queue() {return (this.flag === 'q') ? this : this.changeFlag('q')}isWildcard() {return this.steps.find(step => ['+', '#'].includes(step))}get root() {if (!this._parsed) this.parse()if (this._root_start == null) {return null}return this.steps[this._root_start]}changeRoot(root) {if (!this._parsed) this.parse()return new this.constructor([this.flag, root, ...this.branch, this.leaf].filter(step => step))}get trunk_path() {if (!this._parsed) this.parse()if (this._trunk_start == null) {return null}return `/${this.steps.slice(this._trunk_start, this._trunk_end).join('/')}`}get trunk() {if (!this._parsed) this.parse()if (this._trunk_start == null) {return null}return this.steps.slice(this._trunk_start, this._trunk_end)}changeTrunk(trunk) {if (!this._parsed) this.parse()let new_branch = trunk.split('/').slice(trunk.startsWith('/'))if (this._trunk_start && this._branch_start) {new_branch = new_branch.concat(this.steps.slice(this._trunk_end, this._branch_end))}return new this.constructor([this.flag, this.root, ...new_branch, this.leaf].filter(step => step))}get branch_path() {if (!this._parsed) this.parse()if (this._branch_start == null) {return null}return `/${this.steps.slice(this._branch_start, this._branch_end).join('/')}`}get branch() {if (!this._parsed) this.parse()if (this._branch_start == null) {return null}return this.steps.slice(this._branch_start, this._branch_end)}sliceBranch(start, end) {return '/' + this.branch.slice(start, end).join('/')}changeBranch(branch) {if (!this._parsed) this.parse()let new_branch = branch.split('/').slice(branch.startsWith('/'))return new this.constructor([this.flag, this.root, ...new_branch, this.leaf].filter(step => step))}get leaf() {if (!this._parsed) this.parse()return this.steps[this.steps.length - 1]}changeLeaf(leaf) {if (!this._parsed) this.parse()console.log({branch: this.branch, path: this.path})return new this.constructor([this.flag, this.root, ...this.branch, leaf].filter(step => step))}slice(start, end = null) {return new this.constructor(this.steps.slice(start, end))}replace(target, replacement) {return new this.constructor(`/${this.steps.join('/')}`.replace(target, replacement).split('/').slice(1))}parse() {let cursor = 0Object.defineProperties(this, {_flag_start: {writable: true,value: undefined},_root_start: {writable: true,value: undefined},_trunk_start: {writable: true,value: undefined},_trunk_end: {writable: true,value: undefined},_branch_start: {writable: true,value: undefined},_branch_end: {writable: true,value: undefined}})switch (this.steps[cursor]) {case 'q':this._flag_start = cursorcursor += 1breakcase 'session':this._root_start = cursorcursor += 1this.parseSession(cursor)return}switch (this.steps[cursor]) {case 'action':case 'state':case 'setup':case '+':this._root_start = cursorcursor += 1break}switch (this.steps[cursor]) {case 'users':case 'events':case 'items':this._trunk_start = cursorthis._branch_start = cursorthis._trunk_end = cursor + 2breakcase 'admin':this._trunk_start = cursorthis._branch_start = cursorthis._trunk_end = cursor + 1breakcase 'services':this._trunk_start = cursorthis._branch_start = cursorthis._trunk_end = cursor + 1cursor += 1this.parseService(cursor)return}this._branch_end = this.steps.length - 1this._parsed = true}parseSession(cursor) {switch (this.steps[cursor]) {case 'path':case 'redirect':case 'user':this.twig_start = cursorcursor += 1this.twig_end = this.steps.lengthreturncase 'services':this._trunk_start = cursorthis._branch_start = cursorthis._trunk_end = cursor + 1cursor += 1this.parseService(cursor)return}this._parsed = true}parseService(cursor) {switch (this.steps[cursor]) {case 'spotify':case 'apple-music':this._branch_end = cursor + 1cursor += 1break}this._branch_end = this.steps.length - 1this._parsed = true}}
const isTraversable = (value) => {if (value == null) {return false}if (Array.isArray(value)) {return false}if (typeof value === 'object') {return true}return false}export { isTraversable }export default isTraversable
function isEmpty(object) {for (const key in object) {if (isBareObject(object)) {return false}if (Object.prototype.hasOwnProperty.call(object, key)) {return false}}return true}export { isEmpty}
function isCoppice(value) {if (typeof value === 'object') {for (const key in value) {return key.startsWith('/')}}return false}export { isCoppice }
import { isTraversable } from './is_traversable.js'const coppice = (object, prefix = '', result = {}) => {for (const key in object) {const path = `${prefix}/${key}`const value = object[key]if (isTraversable(value)) {coppice(value, path, result)} else {result[path] = value}}return result}export { coppice }export default coppice
import Tree from './tree.js'const TREE_ADDED = '+'const TREE_DELETED = '-'const TREE_CHANGED = '='const DatastoreEvent = class {// FIXME: use class field `static TREE_ADDED = '+'` when availablestatic get TREE_ADDED() {return TREE_ADDED}// FIXME: use class field `static TREE_DELETED = '-'` when availablestatic get TREE_DELETED() {return TREE_DELETED}// FIXME: use class field `static TREE_CHANGED = '='` when availablestatic get TREE_CHANGED() {return TREE_CHANGED}constructor(type, pointer) {this.type = typethis.pointer = pointer}get tree() {return this.pointer?.tree}get value() {return this.tree?.value}}export default DatastoreEventexport { DatastoreEvent }
// import DatastoreEvent from './event.js'// import Pointer from './pointer.js'import Pointer from './pointer.js'import Topic from './topic.js'const DatastoreConvenience = (Base) => class extends Base {create(pattern, value, { force = false, silent = false, leaf = null } = {}) {const topic = new Topic(pattern)if (topic.wildcard) {const partial_paths = pattern.split('+')if (partial_paths.length != 2) {throw new SyntaxError(`${pattern} is not a valid pattern for datastore.create(pattern, value)!`)}const node_path = partial_paths[0].slice(0, -1)let leafif (partial_paths[1] !== '') {leaf = partial_paths[1].slice(1)if (leaf.endsWith('/#')) {console.warn(`datastore.create: Ignoring '/#' in ${pattern}.`)leaf = partial_paths[1].slice(0, -2)}}return this._create(node_path, value, { force, silent, leaf })} else {const is_primative = typeof value !== 'object' || Array.isArray(value)if (is_primative && leaf == null) {throw new SyntaxError(`${pattern} requires a leaf in datastore.create(path, primative_value, { leaf })!`)}return this._create(pattern, value, { force, silent, leaf })}}_create(node_path, value, { force = false, silent = false, leaf = null } = {}) {node_path = node_path[0] !== '/' ? `/${node_path}` : node_pathlet id = 1while (true) {const existing = this.get(`${node_path}/${id}`)if (existing == null) {break}id++}const path = leaf == null ? `${node_path}/${id}` : `${node_path}/${id}/${leaf}`this.queue(path, value, { force, silent }) // Merge because write calls a delete; removes a step.const branch_path = `/${node_path.split('/').slice(2).join('/')}/${id}`return { path, branch_path, value, id }}queue(path, value) {// Standardize pathpath = path[0] !== '/' ? `/${path}` : path// Queue pathpath = !path.startsWith('/q') ? `/q${path}` : paththis.merge(path, value, { force: true, cache: false })return { path, value }}mark(path) {const value = Date.now()return this.queue(path, value)}push(path, value, { force = false, silent = false, duplicates = false } = {}) {const array = datastore.get(path)?.value ?? []if (!Array.isArray(array)) {throw new TypeError(`${array} at ${path} is not an array!`)}if (duplicates || !array.includes(value)) {array.push(value)}this.queue(path, array, { silent })return { path, value: array }}pull(path, value, { silent = false } = {}) {let array = datastore.get(path)?.value ?? []if (!Array.isArray(array)) {throw new TypeError(`${array} at ${path} is not an array!`)}array = array.filter(element => element != value)this.queue(path, array, { silent })return { path, value: array }}createWithSchema(pattern, schema, relationship_path) {const { path, branch_path } = this.create(pattern, schema.default)this._createWithSchema(path, schema, relationship_path)return { path, branch_path }// this.set(`${path}/$schema`, schema_path) // FIXME: @taylorzane include $schema in schema.default.}_createWithSchema(path, schema, relationship_path) {switch (schema.type) {case 'array': {if (schema.items?.format !== 'relationship') return// const values must be reciprocalif (schema.const != null) {const collection = []for (const relative of schema.const) {if (!relative.startsWith('~/')) {continue}const steps = path.split('/').slice(1)const trimmed = relative.slice(1)const prefix = `/${steps.slice(1, 3).join('/')}`collection.push(`${prefix}${trimmed}`)}this.queue(path, collection)} else {const pattern = new RegExp(schema.items?.pattern)if (!pattern.test(relationship_path)) returnconst inverse_name = schema.items?.inverseif (inverse_name == null) returnconst steps = path.split('/').slice(1)const branch_path = `/${steps.slice(1, -1).join('/')}`const inverse_path = `/${steps[0]}${relationship_path}/${inverse_name}`this.push(path, relationship_path)this.push(inverse_path, branch_path)}break}case 'object': {for (const property in schema.properties) {const next_path = `${path}/${property}`const next_schema = schema.properties[property]this._createWithSchema(next_path, next_schema, relationship_path)}const object = this.read(path)for (const pattern in schema.patternProperties) {const regexp = new RegExp(pattern)for (const property in object) {if (!regexp.test(property)) {continue}const next_path = `${path}/${property}`const next_schema = schema.patternProperties[pattern]this._createWithSchema(next_path, next_schema, relationship_path)}}}}}destroyWithSchema(path, schema, { queue = true } = {}) {const transaction = {}this._destroyWithSchema(path, schema, transaction)for (const [path, value] of Object.entries(transaction)) {if (queue) {this.queue(path, value)} else {this.set(path, value)}}}_destroyWithSchema(path, schema, transaction) {if (schema === null || schema === undefined) {transaction[path] = nullreturn}const pointer = new Pointer(path)switch (schema.type) {case 'array': {const items = schema.itemsif (schema.items == null) returnconst format = items.formatif (format !== 'relationship') returnconst obverse = this.read(path)if (!Array.isArray(obverse)) returnconst inverse_name = items.inverseif (inverse_name == null) returnconst branch_path = `/${pointer.steps.slice(1, -1).join('/')}`for (const inverse_branch_path of obverse) {const inverse_path = `/${pointer.steps[0]}${inverse_branch_path}/${inverse_name}`const existing = transaction[inverse_path] ?? datastore.read(inverse_path)if (!Array.isArray(existing)) continuetransaction[inverse_path] = existing.filter(element => element != branch_path)}break}case 'object': {for (const property in schema.properties) {const next_path = `${path}/${property}`const next_schema = schema.properties[property]this.destroyWithSchema(next_path, next_schema, transaction)}for (const pattern in schema.patternProperties) {const regexp = new RegExp(pattern)const object = this.read(path)for (const property in object) {if (!regexp.test(property)) {continue}const next_path = `${path}/${property}`const next_schema = schema.patternProperties[pattern]this.destroyWithSchema(next_path, next_schema, transaction)}}}}transaction[pointer.path] = null}init(json) {for (const key in json) {this.merge(`/${key}`, json[key])}}clear() {for (const key in this.data_tree) {this.delete(`/${key}`)}}}export default DatastoreConvenienceexport { DatastoreConvenience }
import DatastoreEvent from './event.js'import Pointer from './pointer.js'import Topic from './topic.js'import Tree from './tree.js'const Datastore = class {constructor() {this.data_tree = new Tree()this.topic_tree = new Tree()}get(path) {const pointer = Pointer.cache.get(path)if (pointer instanceof Pointer) {return pointer.tree}if (path === '') {return this.data_tree}}set(path, value, { silent = false, cache = true } = {}) {if (path === '') {return}if (value == null) {return this.delete(path, { silent })}let pointer = Pointer.cache.get(path)let treeif (pointer instanceof Pointer) {tree = pointer.tree}if (tree == null || !tree.parent.has(tree.key)) {tree = this.data_treeconst steps = path.split('/').slice(1)for (const [i, step] of steps.entries()) {if (tree.has(step)) {tree = tree.get(step)} else {tree = tree.add(step)tree.pointer = new Pointer(steps.slice(0, i + 1), { cache })tree.pointer.tree = treeif (!silent) {this.publish(DatastoreEvent.TREE_ADDED, tree.pointer)}}}pointer = tree.pointer}tree.value = valueif (!silent) {this.publish(DatastoreEvent.TREE_CHANGED, pointer)}return tree}delete(path, { silent = false, force = false } = {}) {if (path === '') {return}let pointer = Pointer.cache.get(path)if (pointer instanceof Pointer) {return this._delete(pointer.tree, { silent })} else if (force) {pointer = new Pointer(path, { cache: false })this.publish(DatastoreEvent.TREE_DELETED, pointer)}}_delete(tree, { silent = false } = {}) {if (tree.size > 0) {for (const child of tree.values()) {this._delete(child, { silent })}}tree.value = nullif (!silent) {this.publish(DatastoreEvent.TREE_CHANGED, tree.pointer)}tree.parent.delete(tree.key)if (!silent) {this.publish(DatastoreEvent.TREE_DELETED, tree.pointer)}}subscribe(pattern, callback, { immediate = true } = {}) {let topic = Topic.cache.get(pattern)let treeif (topic instanceof Topic) {tree = topic.tree} else {tree = this.topic_treeconst levels = pattern.split('/')for (const [i, level] of levels.entries()) {if (tree.has(level)) {tree = tree.get(level)} else {tree = tree.add(level)tree.topic = new Topic(levels.slice(0, i + 1), { cache: true })tree.topic.tree = treetree.value = new Set()}}topic = tree.topic}tree.value.add(callback)if (immediate !== true) {return topic}// console.log('datastore.subscribe()', topic)for (const pointer of this.pointers(topic)) {const event = new DatastoreEvent(DatastoreEvent.TREE_CHANGED, pointer)callback(topic, event)}return topic}unsubscribe(pattern, callback) {const topic = Topic.cache.get(pattern)if (topic instanceof Topic) {topic.tree.value?.delete(callback)}}publish(type, pointer) {const event = new DatastoreEvent(type, pointer)for (const topic of this.topics(pointer)) {const callbacks = topic.tree.valuefor (const callback of callbacks) {// console.log({ t: topic.pattern, p: pointer.path, callback})callback(topic, event)}}}* leaves() {yield* this._leaves(this.data_tree)}* _leaves(tree) {if (typeof tree.value !== 'undefined') {return yield tree}for (const child of tree.values()) {yield* this._leaves(child)}}* nodes() {yield* this._nodes(this.data_tree)}* _nodes(tree) {yield treefor (const child of tree.values()) {yield* this._nodes(child)}}* pointers2() {// FIXME: @chrismassey this name is horrible and not descriptiveyield* this._pointers2(this.data_tree)}* _pointers2(pointer) {// FIXME: @chrismassey this name is horrible and not descriptiveyield pointerfor (const child of pointer.tree.values()) {yield* this._pointers2(child.pointer)}}* pointers(topic) {if (typeof topic === 'string') {topic = new Topic(topic)}yield* this._pointers(topic, 0, this.data_tree)}* _pointers(topic, i, tree) {if (topic.steps.length <= i) {if (topic.steps[i - 1] && topic.steps[i - 1] !== '#') {yield tree.pointer}return}const level = topic.steps[i++]if (level !== '+' && level !== '#') {const child = tree.get(level)if (child instanceof Tree) {yield* this._pointers(topic, i, child)}}if (level === '+') {for (const child of tree.values()) {yield* this._pointers(topic, i, child)}}if (level === '#') {// yield* this._pointers2(tree.pointer)for (const child of tree.values()) {yield* this._pointers2(child.pointer)}}}* topics(pointer) {if (typeof pointer === 'string') {pointer = new Pointer(pointer)}yield* this._topics(pointer, 0, this.topic_tree)}* _topics(pointer, i, tree) {if (pointer.steps.length === i) {if (tree.value instanceof Set) {yield tree.topic}return}const step = pointer.steps[i++]const exact_match = tree.get(step)if (exact_match instanceof Tree) {yield* this._topics(pointer, i, exact_match)}const single_wildcard = tree.get('+')if (single_wildcard instanceof Tree) {yield* this._topics(pointer, i, single_wildcard)}const multi_wildcard = tree.get('#')if (multi_wildcard instanceof Tree) {yield multi_wildcard.topic}}}export default Datastoreexport { Datastore, DatastoreEvent, Pointer, Topic }
// import DatastoreEvent from './event.js'// import Pointer from './pointer.js'// import Topic from './topic.js'// import Tree from './tree.js'const DatastoreAPI = (Base) => class extends Base {read(path) {path = path.startsWith('/') ? path : '/' + path// TODO: @chrismassey @davidmassey @thomascowan add support to read wildcards.if (/\/(\+\/|#)/.test(path)) {let output = {}for (const pointer of this.pointers(new Topic(path.slice(1)))) {output[pointer.path] = pointer.tree?.value}return output} else {return this.get(path)?.toJSON()}}write(path, value, { silent = false, force = false, cache = true } = {}) {if (path === '') {return}path = path.startsWith('/') ? path : '/' + pathif (!force && this._isEqual(path, value)) {return value}this.delete(path, { silent: true })if (value == null) {return}this.merge(path, value, { silent, force })}search(topic) {console.log(this.data_tree.toJSON())topic = topic.startsWith('/') ? topic.slice(1) : topiclet output = {}for (const pointer of this.pointers(topic)) {console.log(pointer)output[pointer.path] = pointer.tree?.value}return output}merge(path, value, { silent = false, force = false, cache = true } = {}) {if (path === '') {return}path = path.startsWith('/') ? path : '/' + pathif (!force && this._isEqual(path, value)) {return}if (value == null) {this.delete(path, { force, silent })return}if (typeof value !== 'object' || Array.isArray(value)) {this.set(path, value, { silent, cache })return}for (const key in value) {this.merge(`${path}/${key}`, value[key], { silent, force })}}_isEqual(path, value) {return this.get(path)?.value == value}}export default DatastoreAPIexport { DatastoreAPI }
{"name": "@controlenvy/datastore","version": "4.0.0","type": "module","license": "Apache-2.0","private": true,"main": "src/datastore.js","browser": "src/datastore.js","scripts": {"test": "mocha --expose-gc test/v3-compatible/**/*.js"},"exports": {".": {"default": "./src/datastore.js"}},"devDependencies": {"chai": "^4.3.0","mocha": "^8.2.1","sinon": "^9.2.4","sinon-chai": "^3.5.0"},"dependencies": {"lodash": "^4.17.15"}}
import Benchmark from 'benchmark'const suite = new Benchmark.Suite()function isEmpty1(obj) {for (const prop in obj) {if (Object.prototype.hasOwnProperty.call(obj, prop)) {return false}}return true}function isEmpty2(obj) {return Object.getOwnPropertyNames(obj).length === 0}suite.add('for ... in', function() {isEmpty1({ a: 1, b: 2, c: 3 })})suite.add('keys', function() {isEmpty2({ a: 1, b: 2, c: 3 })})suite.on('abort', function() {console.log('Aborted')})suite.on('error', function(event) {console.log(String(JSON.stringify(event)))})suite.on('cycle', function(event) {console.log(String(event.target))})suite.on('complete', function() {console.log('Fastest is ' + this.filter('fastest').map('name'))})suite.run()
#### Test InstructionsDatastore tests are defined in `shared/datastore/test/*.yaml` and are implemented using interfaces defined in `shared/datastore/test/**/*.js` and `controller/drivers/lib/datastore/spec/**/*.rb`.```bash# Ruby Datastore testscd controller/drivers/lib/datastorebundle exec rspec -f d spec/**/*.rb# Javascript Datastore testscd shared/datastore/testmocha test/**/*.js # mocha is a global dependancy```The structure of the yaml file may vary from test to test, though a QC process is ongoing. Generally:```yamlFILE_NAME:'#FUNCTION_NAME': # contains an array of objects that represent the test- label: 'SHOULD be a label using rfc 2119 terminology'calls: [] # can be plural or singular, and may vary between testsexpectation: [[], {}] # can be plural or singular, and may vary between tests- label: 'SHOULD be a label using rfc 2119 terminology'calls: [] # can be plural or singular, and may vary between testsexpectation: [[], {}] # can be plural or singular, and may vary between tests```
node_modules/.reify-cache/config/env.js*.log*.sublime-workspace
packages:- djiny
lockfileVersion: 5.3importers:djiny:specifiers:'@fortawesome/free-brands-svg-icons': ^5.12.0'@fortawesome/free-solid-svg-icons': ^5.12.0'@sveltejs/adapter-node': ^1.0.0-next'@sveltejs/kit': nexteslint: ^7.22.0eslint-config-prettier: ^8.1.0eslint-plugin-svelte3: ^3.2.0lodash: ^4.17.21node-sass: ^6.0.1prettier: ~2.2.1prettier-plugin-svelte: ^2.2.0query-string: 4.3.2sanitize.css: ^12.0.1sass: ^1.35.2secure-remote-password: ^0.3.1svelte: ^3.34.0svelte-preprocess: ^4.7.3uuid: ^8.0.0dependencies:'@fortawesome/free-brands-svg-icons': 5.15.4'@fortawesome/free-solid-svg-icons': 5.15.4sanitize.css: 12.0.1sass: 1.37.5devDependencies:'@sveltejs/adapter-node': 1.0.0-next.0'@sveltejs/kit': 1.0.0-next.146_svelte@3.42.1eslint: 7.32.0eslint-config-prettier: 8.3.0_eslint@7.32.0eslint-plugin-svelte3: 3.2.0_eslint@7.32.0+svelte@3.42.1lodash: 4.17.21node-sass: 6.0.1prettier: 2.2.1prettier-plugin-svelte: 2.3.1_prettier@2.2.1+svelte@3.42.1query-string: 4.3.2secure-remote-password: 0.3.1svelte: 3.42.1svelte-preprocess: 4.7.4_6197623e5ed34153d1bcd9290e2954d7uuid: 8.3.2packages:/@babel/code-frame/7.12.11:resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==}dependencies:'@babel/highlight': 7.14.5dev: true/@babel/code-frame/7.14.5:resolution: {integrity: sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==}engines: {node: '>=6.9.0'}dependencies:'@babel/highlight': 7.14.5dev: true/@babel/helper-validator-identifier/7.14.9:resolution: {integrity: sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==}engines: {node: '>=6.9.0'}dev: true/@babel/highlight/7.14.5:resolution: {integrity: sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==}engines: {node: '>=6.9.0'}dependencies:'@babel/helper-validator-identifier': 7.14.9chalk: 2.4.2js-tokens: 4.0.0dev: true/@eslint/eslintrc/0.4.3:resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==}engines: {node: ^10.12.0 || >=12.0.0}dependencies:ajv: 6.12.6debug: 4.3.2espree: 7.3.1globals: 13.10.0ignore: 4.0.6import-fresh: 3.3.0js-yaml: 3.14.1minimatch: 3.0.4strip-json-comments: 3.1.1transitivePeerDependencies:- supports-colordev: true/@fortawesome/fontawesome-common-types/0.2.36:resolution: {integrity: sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==, tarball: '@fortawesome/fontawesome-common-types/-/0.2.36/fontawesome-common-types-0.2.36.tgz'}engines: {node: '>=6'}requiresBuild: truedev: false/@fortawesome/free-brands-svg-icons/5.15.4:resolution: {integrity: sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==, tarball: '@fortawesome/free-brands-svg-icons/-/5.15.4/free-brands-svg-icons-5.15.4.tgz'}engines: {node: '>=6'}requiresBuild: truedependencies:'@fortawesome/fontawesome-common-types': 0.2.36dev: false/@fortawesome/free-solid-svg-icons/5.15.4:resolution: {integrity: sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==, tarball: '@fortawesome/free-solid-svg-icons/-/5.15.4/free-solid-svg-icons-5.15.4.tgz'}engines: {node: '>=6'}requiresBuild: truedependencies:'@fortawesome/fontawesome-common-types': 0.2.36dev: false/@humanwhocodes/config-array/0.5.0:resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==}engines: {node: '>=10.10.0'}dependencies:'@humanwhocodes/object-schema': 1.2.0debug: 4.3.2minimatch: 3.0.4transitivePeerDependencies:- supports-colordev: true/@humanwhocodes/object-schema/1.2.0:resolution: {integrity: sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==}dev: true/@rollup/pluginutils/4.1.1:resolution: {integrity: sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==}engines: {node: '>= 8.0.0'}dependencies:estree-walker: 2.0.2picomatch: 2.3.0dev: true/@sveltejs/adapter-node/1.0.0-next.0:resolution: {integrity: sha512-NNdBGSARtrvLWLIUem/RKWYJ7cKq4xn0nxSASKogLSYL5SOrb6AgHunWQXv8VqDZ0jT7xQCORgASn006/HmaYQ==}dependencies:'@sveltejs/app-utils': 1.0.0-next.0dev: true/@sveltejs/app-utils/1.0.0-next.0:resolution: {integrity: sha512-4fCuD+aLrq/iFVJWosIv8oi43tv4o7bGZVyNHMBamdIaVTcydRaPjxQCTM8OQPRVftHyrTIIvT3uH03kAxHZ0Q==}dependencies:mime: 2.5.2dev: true/@sveltejs/kit/1.0.0-next.146_svelte@3.42.1:resolution: {integrity: sha512-MSatcaCRfjl88Prd5mW4pNOJ3Gsr525+Vjr24MoKtyTt6PZQmTfQsDVwyP93exn/6w2xl9uMCW6cFpDVBu7jSg==}engines: {node: ^12.20 || >=14.13}hasBin: truepeerDependencies:svelte: ^3.34.0dependencies:'@sveltejs/vite-plugin-svelte': 1.0.0-next.15_svelte@3.42.1+vite@2.4.4cheap-watch: 1.0.3sade: 1.7.4svelte: 3.42.1vite: 2.4.4transitivePeerDependencies:- diff-match-patch- supports-colordev: true/@sveltejs/vite-plugin-svelte/1.0.0-next.15_svelte@3.42.1+vite@2.4.4:resolution: {integrity: sha512-8yGX7PxaqtvWw+GHiO2DV7lZ4M7DwIrFq+PgZGZ9X09PuoSeaWszm76GWQXJMKHoPPhdA9084662en9qbv4aRw==}engines: {node: ^12.20 || ^14.13.1 || >= 16}peerDependencies:diff-match-patch: ^1.0.5svelte: ^3.34.0vite: ^2.3.7peerDependenciesMeta:diff-match-patch:optional: truedependencies:'@rollup/pluginutils': 4.1.1debug: 4.3.2kleur: 4.1.4magic-string: 0.25.7require-relative: 0.8.7svelte: 3.42.1svelte-hmr: 0.14.7_svelte@3.42.1vite: 2.4.4transitivePeerDependencies:- supports-colordev: true/@types/minimist/1.2.2:resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}dev: true/@types/node/16.4.13:resolution: {integrity: sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==}dev: true/@types/normalize-package-data/2.4.1:resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}dev: true/@types/pug/2.0.5:resolution: {integrity: sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA==}dev: true/@types/sass/1.16.1:resolution: {integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ==}dependencies:'@types/node': 16.4.13dev: true/abbrev/1.1.1:resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}dev: true/acorn-jsx/5.3.2_acorn@7.4.1:resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}peerDependencies:acorn: ^6.0.0 || ^7.0.0 || ^8.0.0dependencies:acorn: 7.4.1dev: true/acorn/7.4.1:resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}engines: {node: '>=0.4.0'}hasBin: truedev: true/ajv/6.12.6:resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}dependencies:fast-deep-equal: 3.1.3fast-json-stable-stringify: 2.1.0json-schema-traverse: 0.4.1uri-js: 4.4.1dev: true/ajv/8.6.2:resolution: {integrity: sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==}dependencies:fast-deep-equal: 3.1.3json-schema-traverse: 1.0.0require-from-string: 2.0.2uri-js: 4.4.1dev: true/amdefine/1.0.1:resolution: {integrity: sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=}engines: {node: '>=0.4.2'}dev: true/ansi-colors/4.1.1:resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}engines: {node: '>=6'}dev: true/ansi-regex/2.1.1:resolution: {integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=}engines: {node: '>=0.10.0'}dev: true/ansi-regex/4.1.0:resolution: {integrity: sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==}engines: {node: '>=6'}dev: true/ansi-regex/5.0.0:resolution: {integrity: sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==}engines: {node: '>=8'}dev: true/ansi-styles/2.2.1:resolution: {integrity: sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=}engines: {node: '>=0.10.0'}dev: true/ansi-styles/3.2.1:resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}engines: {node: '>=4'}dependencies:color-convert: 1.9.3dev: true/ansi-styles/4.3.0:resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}engines: {node: '>=8'}dependencies:color-convert: 2.0.1dev: true/anymatch/3.1.2:resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}engines: {node: '>= 8'}dependencies:normalize-path: 3.0.0picomatch: 2.3.0dev: false/aproba/1.2.0:resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==}dev: true/are-we-there-yet/1.1.5:resolution: {integrity: sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==}dependencies:delegates: 1.0.0readable-stream: 2.3.7dev: true/argparse/1.0.10:resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}dependencies:sprintf-js: 1.0.3dev: true/array-buffer-to-hex/1.0.0:resolution: {integrity: sha512-arycdkxgK1cj6s03GDb96tlCxOl1n3kg9M2OHseUc6Pqyqp+lgfceFPmG507eI5V+oxOSEnlOw/dFc7LXBXF4Q==}dev: true/arrify/1.0.1:resolution: {integrity: sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=}engines: {node: '>=0.10.0'}dev: true/asn1/0.2.4:resolution: {integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==}dependencies:safer-buffer: 2.1.2dev: true/assert-plus/1.0.0:resolution: {integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=}engines: {node: '>=0.8'}dev: true/astral-regex/2.0.0:resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}engines: {node: '>=8'}dev: true/async-foreach/0.1.3:resolution: {integrity: sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=}dev: true/asynckit/0.4.0:resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=}dev: true/aws-sign2/0.7.0:resolution: {integrity: sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=}dev: true/aws4/1.11.0:resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}dev: true/balanced-match/1.0.2:resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}dev: true/bcrypt-pbkdf/1.0.2:resolution: {integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=}dependencies:tweetnacl: 0.14.5dev: true/binary-extensions/2.2.0:resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}engines: {node: '>=8'}dev: false/brace-expansion/1.1.11:resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}dependencies:balanced-match: 1.0.2concat-map: 0.0.1dev: true/braces/3.0.2:resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}engines: {node: '>=8'}dependencies:fill-range: 7.0.1dev: false/callsites/3.1.0:resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}engines: {node: '>=6'}dev: true/camelcase-keys/6.2.2:resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}engines: {node: '>=8'}dependencies:camelcase: 5.3.1map-obj: 4.2.1quick-lru: 4.0.1dev: true/camelcase/5.3.1:resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}engines: {node: '>=6'}dev: true/caseless/0.12.0:resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=}dev: true/chalk/1.1.3:resolution: {integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=}engines: {node: '>=0.10.0'}dependencies:ansi-styles: 2.2.1escape-string-regexp: 1.0.5has-ansi: 2.0.0strip-ansi: 3.0.1supports-color: 2.0.0dev: true/chalk/2.4.2:resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}engines: {node: '>=4'}dependencies:ansi-styles: 3.2.1escape-string-regexp: 1.0.5supports-color: 5.5.0dev: true/chalk/4.1.2:resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}engines: {node: '>=10'}dependencies:ansi-styles: 4.3.0supports-color: 7.2.0dev: true/cheap-watch/1.0.3:resolution: {integrity: sha512-xC5CruMhLzjPwJ5ecUxGu1uGmwJQykUhqd2QrCrYbwvsFYdRyviu6jG9+pccwDXJR/OpmOTOJ9yLFunVgQu9wg==}engines: {node: '>=8'}dev: true/chokidar/3.5.2:resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==}engines: {node: '>= 8.10.0'}dependencies:anymatch: 3.1.2braces: 3.0.2glob-parent: 5.1.2is-binary-path: 2.1.0is-glob: 4.0.1normalize-path: 3.0.0readdirp: 3.6.0optionalDependencies:fsevents: 2.3.2dev: false/chownr/2.0.0:resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}engines: {node: '>=10'}dev: true/cliui/5.0.0:resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==}dependencies:string-width: 3.1.0strip-ansi: 5.2.0wrap-ansi: 5.1.0dev: true/code-point-at/1.1.0:resolution: {integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=}engines: {node: '>=0.10.0'}dev: true/color-convert/1.9.3:resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}dependencies:color-name: 1.1.3dev: true/color-convert/2.0.1:resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}engines: {node: '>=7.0.0'}dependencies:color-name: 1.1.4dev: true/color-name/1.1.3:resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=}dev: true/color-name/1.1.4:resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}dev: true/colorette/1.2.2:resolution: {integrity: sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==}dev: true/combined-stream/1.0.8:resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}engines: {node: '>= 0.8'}dependencies:delayed-stream: 1.0.0dev: true/concat-map/0.0.1:resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}dev: true/console-control-strings/1.1.0:resolution: {integrity: sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=}dev: true/core-util-is/1.0.2:resolution: {integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=}dev: true/cross-spawn/7.0.3:resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}engines: {node: '>= 8'}dependencies:path-key: 3.1.1shebang-command: 2.0.0which: 2.0.2dev: true/crypto-digest-sync/1.0.0:resolution: {integrity: sha512-UQBOB5z+HF4iA8shKQ3PPwhCmdFAihwcytD1Qh4uiz78x04cZZmKtZ1F1VyAjkrA8uEZqXt2tMXfj3dJHtcbng==}dev: true/crypto-random-hex/1.0.0:resolution: {integrity: sha512-1DuZQ03El13TRgfrqbbjW40Gvi4OKInny/Wxqj23/JMXe214C/3Tlz92bKXWDW3NZT5RjXUGdYW4qiIOUPf+cA==}dependencies:array-buffer-to-hex: 1.0.0dev: true/dashdash/1.14.1:resolution: {integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=}engines: {node: '>=0.10'}dependencies:assert-plus: 1.0.0dev: true/debug/4.3.2:resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}engines: {node: '>=6.0'}peerDependencies:supports-color: '*'peerDependenciesMeta:supports-color:optional: truedependencies:ms: 2.1.2dev: true/decamelize-keys/1.1.0:resolution: {integrity: sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=}engines: {node: '>=0.10.0'}dependencies:decamelize: 1.2.0map-obj: 1.0.1dev: true/decamelize/1.2.0:resolution: {integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=}engines: {node: '>=0.10.0'}dev: true/deep-is/0.1.3:resolution: {integrity: sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=}dev: true/delayed-stream/1.0.0:resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=}engines: {node: '>=0.4.0'}dev: true/delegates/1.0.0:resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=}dev: true/detect-indent/6.1.0:resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}engines: {node: '>=8'}dev: true/doctrine/3.0.0:resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}engines: {node: '>=6.0.0'}dependencies:esutils: 2.0.3dev: true/ecc-jsbn/0.1.2:resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=}dependencies:jsbn: 0.1.1safer-buffer: 2.1.2dev: true/emoji-regex/7.0.3:resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==}dev: true/emoji-regex/8.0.0:resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}dev: true/encode-utf8/1.0.3:resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}dev: true/enquirer/2.3.6:resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==}engines: {node: '>=8.6'}dependencies:ansi-colors: 4.1.1dev: true/env-paths/2.2.1:resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}engines: {node: '>=6'}dev: true/error-ex/1.3.2:resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}dependencies:is-arrayish: 0.2.1dev: true/esbuild/0.12.19:resolution: {integrity: sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w==}hasBin: truerequiresBuild: truedev: true/escape-string-regexp/1.0.5:resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}engines: {node: '>=0.8.0'}dev: true/escape-string-regexp/4.0.0:resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}engines: {node: '>=10'}dev: true/eslint-config-prettier/8.3.0_eslint@7.32.0:resolution: {integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==}hasBin: truepeerDependencies:eslint: '>=7.0.0'dependencies:eslint: 7.32.0dev: true/eslint-plugin-svelte3/3.2.0_eslint@7.32.0+svelte@3.42.1:resolution: {integrity: sha512-qdWB1QN21dEozsJFdR8XlEhMnsS6aKHjsXWuNmchYwxoet5I6QdCr1Xcq62++IzRBMCNCeH4waXqSOAdqrZzgA==}engines: {node: '>=10'}peerDependencies:eslint: '>=6.0.0'svelte: ^3.2.0dependencies:eslint: 7.32.0svelte: 3.42.1dev: true/eslint-scope/5.1.1:resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}engines: {node: '>=8.0.0'}dependencies:esrecurse: 4.3.0estraverse: 4.3.0dev: true/eslint-utils/2.1.0:resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==}engines: {node: '>=6'}dependencies:eslint-visitor-keys: 1.3.0dev: true/eslint-visitor-keys/1.3.0:resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==}engines: {node: '>=4'}dev: true/eslint-visitor-keys/2.1.0:resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}engines: {node: '>=10'}dev: true/eslint/7.32.0:resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==}engines: {node: ^10.12.0 || >=12.0.0}hasBin: truedependencies:'@babel/code-frame': 7.12.11'@eslint/eslintrc': 0.4.3'@humanwhocodes/config-array': 0.5.0ajv: 6.12.6chalk: 4.1.2cross-spawn: 7.0.3debug: 4.3.2doctrine: 3.0.0enquirer: 2.3.6escape-string-regexp: 4.0.0eslint-scope: 5.1.1eslint-utils: 2.1.0eslint-visitor-keys: 2.1.0espree: 7.3.1esquery: 1.4.0esutils: 2.0.3fast-deep-equal: 3.1.3file-entry-cache: 6.0.1functional-red-black-tree: 1.0.1glob-parent: 5.1.2globals: 13.10.0ignore: 4.0.6import-fresh: 3.3.0imurmurhash: 0.1.4is-glob: 4.0.1js-yaml: 3.14.1json-stable-stringify-without-jsonify: 1.0.1levn: 0.4.1lodash.merge: 4.6.2minimatch: 3.0.4natural-compare: 1.4.0optionator: 0.9.1progress: 2.0.3regexpp: 3.2.0semver: 7.3.5strip-ansi: 6.0.0strip-json-comments: 3.1.1table: 6.7.1text-table: 0.2.0v8-compile-cache: 2.3.0transitivePeerDependencies:- supports-colordev: true/espree/7.3.1:resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==}engines: {node: ^10.12.0 || >=12.0.0}dependencies:acorn: 7.4.1acorn-jsx: 5.3.2_acorn@7.4.1eslint-visitor-keys: 1.3.0dev: true/esprima/4.0.1:resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}engines: {node: '>=4'}hasBin: truedev: true/esquery/1.4.0:resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==}engines: {node: '>=0.10'}dependencies:estraverse: 5.2.0dev: true/esrecurse/4.3.0:resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}engines: {node: '>=4.0'}dependencies:estraverse: 5.2.0dev: true/estraverse/4.3.0:resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}engines: {node: '>=4.0'}dev: true/estraverse/5.2.0:resolution: {integrity: sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==}engines: {node: '>=4.0'}dev: true/estree-walker/2.0.2:resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}dev: true/esutils/2.0.3:resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}engines: {node: '>=0.10.0'}dev: true/extend/3.0.2:resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}dev: true/extsprintf/1.3.0:resolution: {integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=}engines: {'0': node >=0.6.0}dev: true/fast-deep-equal/3.1.3:resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}dev: true/fast-json-stable-stringify/2.1.0:resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}dev: true/fast-levenshtein/2.0.6:resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=}dev: true/file-entry-cache/6.0.1:resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}engines: {node: ^10.12.0 || >=12.0.0}dependencies:flat-cache: 3.0.4dev: true/fill-range/7.0.1:resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}engines: {node: '>=8'}dependencies:to-regex-range: 5.0.1dev: false/find-up/3.0.0:resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}engines: {node: '>=6'}dependencies:locate-path: 3.0.0dev: true/find-up/4.1.0:resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}engines: {node: '>=8'}dependencies:locate-path: 5.0.0path-exists: 4.0.0dev: true/flat-cache/3.0.4:resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}engines: {node: ^10.12.0 || >=12.0.0}dependencies:flatted: 3.2.2rimraf: 3.0.2dev: true/flatted/3.2.2:resolution: {integrity: sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==}dev: true/forever-agent/0.6.1:resolution: {integrity: sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=}dev: true/form-data/2.3.3:resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}engines: {node: '>= 0.12'}dependencies:asynckit: 0.4.0combined-stream: 1.0.8mime-types: 2.1.32dev: true/fs-minipass/2.1.0:resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}engines: {node: '>= 8'}dependencies:minipass: 3.1.3dev: true/fs.realpath/1.0.0:resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}dev: true/fsevents/2.3.2:resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}os: [darwin]optional: true/function-bind/1.1.1:resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}dev: true/functional-red-black-tree/1.0.1:resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=}dev: true/gauge/2.7.4:resolution: {integrity: sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=}dependencies:aproba: 1.2.0console-control-strings: 1.1.0has-unicode: 2.0.1object-assign: 4.1.1signal-exit: 3.0.3string-width: 1.0.2strip-ansi: 3.0.1wide-align: 1.1.3dev: true/gaze/1.1.3:resolution: {integrity: sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==}engines: {node: '>= 4.0.0'}dependencies:globule: 1.3.2dev: true/get-caller-file/2.0.5:resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}engines: {node: 6.* || 8.* || >= 10.*}dev: true/get-stdin/4.0.1:resolution: {integrity: sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=}engines: {node: '>=0.10.0'}dev: true/getpass/0.1.7:resolution: {integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=}dependencies:assert-plus: 1.0.0dev: true/glob-parent/5.1.2:resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}engines: {node: '>= 6'}dependencies:is-glob: 4.0.1/glob/7.1.7:resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==}dependencies:fs.realpath: 1.0.0inflight: 1.0.6inherits: 2.0.4minimatch: 3.0.4once: 1.4.0path-is-absolute: 1.0.1dev: true/globals/13.10.0:resolution: {integrity: sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==}engines: {node: '>=8'}dependencies:type-fest: 0.20.2dev: true/globule/1.3.2:resolution: {integrity: sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==}engines: {node: '>= 0.10'}dependencies:glob: 7.1.7lodash: 4.17.21minimatch: 3.0.4dev: true/graceful-fs/4.2.8:resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==}dev: true/har-schema/2.0.0:resolution: {integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=}engines: {node: '>=4'}dev: true/har-validator/5.1.5:resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==}engines: {node: '>=6'}deprecated: this library is no longer supporteddependencies:ajv: 6.12.6har-schema: 2.0.0dev: true/hard-rejection/2.1.0:resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}engines: {node: '>=6'}dev: true/has-ansi/2.0.0:resolution: {integrity: sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=}engines: {node: '>=0.10.0'}dependencies:ansi-regex: 2.1.1dev: true/has-flag/3.0.0:resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=}engines: {node: '>=4'}dev: true/has-flag/4.0.0:resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}engines: {node: '>=8'}dev: true/has-unicode/2.0.1:resolution: {integrity: sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=}dev: true/has/1.0.3:resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}engines: {node: '>= 0.4.0'}dependencies:function-bind: 1.1.1dev: true/hex-to-array-buffer/1.1.0:resolution: {integrity: sha512-vvl3IM8FfT1uOnHtEqyjkDK9Luqz6MQrH82qIvVnjyXxRhkeaEZyRRPiBgf2yym3nweRVEfayxt/1SoTXZYd4Q==}dev: true/hosted-git-info/2.8.9:resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}dev: true/hosted-git-info/4.0.2:resolution: {integrity: sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==}engines: {node: '>=10'}dependencies:lru-cache: 6.0.0dev: true/http-signature/1.2.0:resolution: {integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=}engines: {node: '>=0.8', npm: '>=1.3.7'}dependencies:assert-plus: 1.0.0jsprim: 1.4.1sshpk: 1.16.1dev: true/ignore/4.0.6:resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}engines: {node: '>= 4'}dev: true/import-fresh/3.3.0:resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}engines: {node: '>=6'}dependencies:parent-module: 1.0.1resolve-from: 4.0.0dev: true/imurmurhash/0.1.4:resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=}engines: {node: '>=0.8.19'}dev: true/indent-string/4.0.0:resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}engines: {node: '>=8'}dev: true/inflight/1.0.6:resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}dependencies:once: 1.4.0wrappy: 1.0.2dev: true/inherits/2.0.4:resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}dev: true/is-arrayish/0.2.1:resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=}dev: true/is-binary-path/2.1.0:resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}engines: {node: '>=8'}dependencies:binary-extensions: 2.2.0dev: false/is-core-module/2.5.0:resolution: {integrity: sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==}dependencies:has: 1.0.3dev: true/is-extglob/2.1.1:resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=}engines: {node: '>=0.10.0'}/is-fullwidth-code-point/1.0.0:resolution: {integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=}engines: {node: '>=0.10.0'}dependencies:number-is-nan: 1.0.1dev: true/is-fullwidth-code-point/2.0.0:resolution: {integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=}engines: {node: '>=4'}dev: true/is-fullwidth-code-point/3.0.0:resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}engines: {node: '>=8'}dev: true/is-glob/4.0.1:resolution: {integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==}engines: {node: '>=0.10.0'}dependencies:is-extglob: 2.1.1/is-number/7.0.0:resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}engines: {node: '>=0.12.0'}dev: false/is-plain-obj/1.1.0:resolution: {integrity: sha1-caUMhCnfync8kqOQpKA7OfzVHT4=}engines: {node: '>=0.10.0'}dev: true/is-typedarray/1.0.0:resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=}dev: true/isarray/1.0.0:resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=}dev: true/isexe/2.0.0:resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=}dev: true/isstream/0.1.2:resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=}dev: true/js-base64/2.6.4:resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}dev: true/js-tokens/4.0.0:resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}dev: true/js-yaml/3.14.1:resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}hasBin: truedependencies:argparse: 1.0.10esprima: 4.0.1dev: true/jsbn/0.1.1:resolution: {integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM=}dev: true/jsbn/1.1.0:resolution: {integrity: sha1-sBMHyym2GKHtJux56RH4A8TaAEA=}dev: true/json-parse-even-better-errors/2.3.1:resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}dev: true/json-schema-traverse/0.4.1:resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}dev: true/json-schema-traverse/1.0.0:resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}dev: true/json-schema/0.2.3:resolution: {integrity: sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=}dev: true/json-stable-stringify-without-jsonify/1.0.1:resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=}dev: true/json-stringify-safe/5.0.1:resolution: {integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=}dev: true/jsprim/1.4.1:resolution: {integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=}engines: {'0': node >=0.6.0}dependencies:assert-plus: 1.0.0extsprintf: 1.3.0json-schema: 0.2.3verror: 1.10.0dev: true/kind-of/6.0.3:resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}engines: {node: '>=0.10.0'}dev: true/kleur/4.1.4:resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==}engines: {node: '>=6'}dev: true/levn/0.4.1:resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}engines: {node: '>= 0.8.0'}dependencies:prelude-ls: 1.2.1type-check: 0.4.0dev: true/lines-and-columns/1.1.6:resolution: {integrity: sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=}dev: true/locate-path/3.0.0:resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}engines: {node: '>=6'}dependencies:p-locate: 3.0.0path-exists: 3.0.0dev: true/locate-path/5.0.0:resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}engines: {node: '>=8'}dependencies:p-locate: 4.1.0dev: true/lodash.clonedeep/4.5.0:resolution: {integrity: sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=}dev: true/lodash.merge/4.6.2:resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}dev: true/lodash.truncate/4.4.2:resolution: {integrity: sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=}dev: true/lodash/4.17.21:resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}dev: true/lru-cache/6.0.0:resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}engines: {node: '>=10'}dependencies:yallist: 4.0.0dev: true/magic-string/0.25.7:resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}dependencies:sourcemap-codec: 1.4.8dev: true/map-obj/1.0.1:resolution: {integrity: sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=}engines: {node: '>=0.10.0'}dev: true/map-obj/4.2.1:resolution: {integrity: sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==}engines: {node: '>=8'}dev: true/meow/9.0.0:resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}engines: {node: '>=10'}dependencies:'@types/minimist': 1.2.2camelcase-keys: 6.2.2decamelize: 1.2.0decamelize-keys: 1.1.0hard-rejection: 2.1.0minimist-options: 4.1.0normalize-package-data: 3.0.2read-pkg-up: 7.0.1redent: 3.0.0trim-newlines: 3.0.1type-fest: 0.18.1yargs-parser: 20.2.9dev: true/mime-db/1.49.0:resolution: {integrity: sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==}engines: {node: '>= 0.6'}dev: true/mime-types/2.1.32:resolution: {integrity: sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==}engines: {node: '>= 0.6'}dependencies:mime-db: 1.49.0dev: true/mime/2.5.2:resolution: {integrity: sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==}engines: {node: '>=4.0.0'}hasBin: truedev: true/min-indent/1.0.1:resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}engines: {node: '>=4'}dev: true/minimatch/3.0.4:resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}dependencies:brace-expansion: 1.1.11dev: true/minimist-options/4.1.0:resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}engines: {node: '>= 6'}dependencies:arrify: 1.0.1is-plain-obj: 1.1.0kind-of: 6.0.3dev: true/minipass/3.1.3:resolution: {integrity: sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==}engines: {node: '>=8'}dependencies:yallist: 4.0.0dev: true/minizlib/2.1.2:resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}engines: {node: '>= 8'}dependencies:minipass: 3.1.3yallist: 4.0.0dev: true/mkdirp/1.0.4:resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}engines: {node: '>=10'}hasBin: truedev: true/mri/1.1.6:resolution: {integrity: sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==}engines: {node: '>=4'}dev: true/ms/2.1.2:resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}dev: true/nan/2.15.0:resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==}dev: true/nanoid/3.1.23:resolution: {integrity: sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==}engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}hasBin: truedev: true/natural-compare/1.4.0:resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=}dev: true/node-gyp/7.1.2:resolution: {integrity: sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==}engines: {node: '>= 10.12.0'}hasBin: truedependencies:env-paths: 2.2.1glob: 7.1.7graceful-fs: 4.2.8nopt: 5.0.0npmlog: 4.1.2request: 2.88.2rimraf: 3.0.2semver: 7.3.5tar: 6.1.7which: 2.0.2dev: true/node-sass/6.0.1:resolution: {integrity: sha512-f+Rbqt92Ful9gX0cGtdYwjTrWAaGURgaK5rZCWOgCNyGWusFYHhbqCCBoFBeat+HKETOU02AyTxNhJV0YZf2jQ==}engines: {node: '>=12'}hasBin: truerequiresBuild: truedependencies:async-foreach: 0.1.3chalk: 1.1.3cross-spawn: 7.0.3gaze: 1.1.3get-stdin: 4.0.1glob: 7.1.7lodash: 4.17.21meow: 9.0.0nan: 2.15.0node-gyp: 7.1.2npmlog: 4.1.2request: 2.88.2sass-graph: 2.2.5stdout-stream: 1.4.1true-case-path: 1.0.3dev: true/nopt/5.0.0:resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}engines: {node: '>=6'}hasBin: truedependencies:abbrev: 1.1.1dev: true/normalize-package-data/2.5.0:resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}dependencies:hosted-git-info: 2.8.9resolve: 1.20.0semver: 5.7.1validate-npm-package-license: 3.0.4dev: true/normalize-package-data/3.0.2:resolution: {integrity: sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==}engines: {node: '>=10'}dependencies:hosted-git-info: 4.0.2resolve: 1.20.0semver: 7.3.5validate-npm-package-license: 3.0.4dev: true/normalize-path/3.0.0:resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}engines: {node: '>=0.10.0'}dev: false/npmlog/4.1.2:resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==}dependencies:are-we-there-yet: 1.1.5console-control-strings: 1.1.0gauge: 2.7.4set-blocking: 2.0.0dev: true/number-is-nan/1.0.1:resolution: {integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=}engines: {node: '>=0.10.0'}dev: true/oauth-sign/0.9.0:resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}dev: true/object-assign/4.1.1:resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}engines: {node: '>=0.10.0'}dev: true/once/1.4.0:resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}dependencies:wrappy: 1.0.2dev: true/optionator/0.9.1:resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}engines: {node: '>= 0.8.0'}dependencies:deep-is: 0.1.3fast-levenshtein: 2.0.6levn: 0.4.1prelude-ls: 1.2.1type-check: 0.4.0word-wrap: 1.2.3dev: true/p-limit/2.3.0:resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}engines: {node: '>=6'}dependencies:p-try: 2.2.0dev: true/p-locate/3.0.0:resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}engines: {node: '>=6'}dependencies:p-limit: 2.3.0dev: true/p-locate/4.1.0:resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}engines: {node: '>=8'}dependencies:p-limit: 2.3.0dev: true/p-try/2.2.0:resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}engines: {node: '>=6'}dev: true/pad-start/1.0.2:resolution: {integrity: sha1-I+W6s+lkRrYoFs/28VCXXwQNGxQ=}dev: true/parent-module/1.0.1:resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}engines: {node: '>=6'}dependencies:callsites: 3.1.0dev: true/parse-json/5.2.0:resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}engines: {node: '>=8'}dependencies:'@babel/code-frame': 7.14.5error-ex: 1.3.2json-parse-even-better-errors: 2.3.1lines-and-columns: 1.1.6dev: true/path-exists/3.0.0:resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=}engines: {node: '>=4'}dev: true/path-exists/4.0.0:resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}engines: {node: '>=8'}dev: true/path-is-absolute/1.0.1:resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}engines: {node: '>=0.10.0'}dev: true/path-key/3.1.1:resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}engines: {node: '>=8'}dev: true/path-parse/1.0.7:resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}dev: true/performance-now/2.1.0:resolution: {integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=}dev: true/picomatch/2.3.0:resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}engines: {node: '>=8.6'}/postcss/8.3.6:resolution: {integrity: sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==}engines: {node: ^10 || ^12 || >=14}dependencies:colorette: 1.2.2nanoid: 3.1.23source-map-js: 0.6.2dev: true/prelude-ls/1.2.1:resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}engines: {node: '>= 0.8.0'}dev: true/prettier-plugin-svelte/2.3.1_prettier@2.2.1+svelte@3.42.1:resolution: {integrity: sha512-F1/r6OYoBq8Zgurhs1MN25tdrhPw0JW5JjioPRqpxbYdmrZ3gY/DzHGs0B6zwd4DLyRsfGB2gqhxUCbHt/D1fw==}peerDependencies:prettier: ^1.16.4 || ^2.0.0svelte: ^3.2.0dependencies:prettier: 2.2.1svelte: 3.42.1dev: true/prettier/2.2.1:resolution: {integrity: sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==}engines: {node: '>=10.13.0'}hasBin: truedev: true/process-nextick-args/2.0.1:resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}dev: true/progress/2.0.3:resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}engines: {node: '>=0.4.0'}dev: true/psl/1.8.0:resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==}dev: true/punycode/2.1.1:resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}engines: {node: '>=6'}dev: true/qs/6.5.2:resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==}engines: {node: '>=0.6'}dev: true/query-string/4.3.2:resolution: {integrity: sha1-7A/XZfWKUAMaOWjCQxOG+JR6XN0=}engines: {node: '>=0.10.0'}dependencies:object-assign: 4.1.1strict-uri-encode: 1.1.0dev: true/quick-lru/4.0.1:resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==}engines: {node: '>=8'}dev: true/read-pkg-up/7.0.1:resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}engines: {node: '>=8'}dependencies:find-up: 4.1.0read-pkg: 5.2.0type-fest: 0.8.1dev: true/read-pkg/5.2.0:resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}engines: {node: '>=8'}dependencies:'@types/normalize-package-data': 2.4.1normalize-package-data: 2.5.0parse-json: 5.2.0type-fest: 0.6.0dev: true/readable-stream/2.3.7:resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}dependencies:core-util-is: 1.0.2inherits: 2.0.4isarray: 1.0.0process-nextick-args: 2.0.1safe-buffer: 5.1.2string_decoder: 1.1.1util-deprecate: 1.0.2dev: true/readdirp/3.6.0:resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}engines: {node: '>=8.10.0'}dependencies:picomatch: 2.3.0dev: false/redent/3.0.0:resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}engines: {node: '>=8'}dependencies:indent-string: 4.0.0strip-indent: 3.0.0dev: true/regexpp/3.2.0:resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}engines: {node: '>=8'}dev: true/request/2.88.2:resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}engines: {node: '>= 6'}deprecated: request has been deprecated, see https://github.com/request/request/issues/3142dependencies:aws-sign2: 0.7.0aws4: 1.11.0caseless: 0.12.0combined-stream: 1.0.8extend: 3.0.2forever-agent: 0.6.1form-data: 2.3.3har-validator: 5.1.5http-signature: 1.2.0is-typedarray: 1.0.0isstream: 0.1.2json-stringify-safe: 5.0.1mime-types: 2.1.32oauth-sign: 0.9.0performance-now: 2.1.0qs: 6.5.2safe-buffer: 5.2.1tough-cookie: 2.5.0tunnel-agent: 0.6.0uuid: 3.4.0dev: true/require-directory/2.1.1:resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=}engines: {node: '>=0.10.0'}dev: true/require-from-string/2.0.2:resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}engines: {node: '>=0.10.0'}dev: true/require-main-filename/2.0.0:resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}dev: true/require-relative/0.8.7:resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=}dev: true/resolve-from/4.0.0:resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}engines: {node: '>=4'}dev: true/resolve/1.20.0:resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}dependencies:is-core-module: 2.5.0path-parse: 1.0.7dev: true/rimraf/3.0.2:resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}hasBin: truedependencies:glob: 7.1.7dev: true/rollup/2.56.1:resolution: {integrity: sha512-KkrsNjeiTfGJMUFBi/PNfj3fnt70akqdoNXOjlzwo98uA1qrlkmgt6SGaK5OwhyDYCVnJb6jb2Xa2wbI47P4Nw==}engines: {node: '>=10.0.0'}hasBin: trueoptionalDependencies:fsevents: 2.3.2dev: true/sade/1.7.4:resolution: {integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==}engines: {node: '>= 6'}dependencies:mri: 1.1.6dev: true/safe-buffer/5.1.2:resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}dev: true/safe-buffer/5.2.1:resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}dev: true/safer-buffer/2.1.2:resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}dev: true/sanitize.css/12.0.1:resolution: {integrity: sha512-QbusSBnWHaRBZeTxsJyknwI0q+q6m1NtLBmB76JfW/rdVN7Ws6Zz70w65+430/ouVcdNVT3qwrDgrM6PaYyRtw==}dev: false/sass-graph/2.2.5:resolution: {integrity: sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==}hasBin: truedependencies:glob: 7.1.7lodash: 4.17.21scss-tokenizer: 0.2.3yargs: 13.3.2dev: true/sass/1.37.5:resolution: {integrity: sha512-Cx3ewxz9QB/ErnVIiWg2cH0kiYZ0FPvheDTVC6BsiEGBTZKKZJ1Gq5Kq6jy3PKtL6+EJ8NIoaBW/RSd2R6cZOA==}engines: {node: '>=8.9.0'}hasBin: truedependencies:chokidar: 3.5.2dev: false/scss-tokenizer/0.2.3:resolution: {integrity: sha1-jrBtualyMzOCTT9VMGQRSYR85dE=}dependencies:js-base64: 2.6.4source-map: 0.4.4dev: true/secure-remote-password/0.3.1:resolution: {integrity: sha512-iEp/qLRfb9XYhfKFrPFfdeD7KVreCjhDKSTRP1G1nRIO0Sw1hjnVHD58ymOhiy9Zf5quHbDIbG9cTupji7qwnA==}dependencies:array-buffer-to-hex: 1.0.0crypto-digest-sync: 1.0.0crypto-random-hex: 1.0.0encode-utf8: 1.0.3hex-to-array-buffer: 1.1.0jsbn: 1.1.0pad-start: 1.0.2dev: true/semver/5.7.1:resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}hasBin: truedev: true/semver/7.3.5:resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}engines: {node: '>=10'}hasBin: truedependencies:lru-cache: 6.0.0dev: true/set-blocking/2.0.0:resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=}dev: true/shebang-command/2.0.0:resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}engines: {node: '>=8'}dependencies:shebang-regex: 3.0.0dev: true/shebang-regex/3.0.0:resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}engines: {node: '>=8'}dev: true/signal-exit/3.0.3:resolution: {integrity: sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==}dev: true/slice-ansi/4.0.0:resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}engines: {node: '>=10'}dependencies:ansi-styles: 4.3.0astral-regex: 2.0.0is-fullwidth-code-point: 3.0.0dev: true/source-map-js/0.6.2:resolution: {integrity: sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==}engines: {node: '>=0.10.0'}dev: true/source-map/0.4.4:resolution: {integrity: sha1-66T12pwNyZneaAMti092FzZSA2s=}engines: {node: '>=0.8.0'}dependencies:amdefine: 1.0.1dev: true/sourcemap-codec/1.4.8:resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}dev: true/spdx-correct/3.1.1:resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}dependencies:spdx-expression-parse: 3.0.1spdx-license-ids: 3.0.10dev: true/spdx-exceptions/2.3.0:resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==}dev: true/spdx-expression-parse/3.0.1:resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}dependencies:spdx-exceptions: 2.3.0spdx-license-ids: 3.0.10dev: true/spdx-license-ids/3.0.10:resolution: {integrity: sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==}dev: true/sprintf-js/1.0.3:resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=}dev: true/sshpk/1.16.1:resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==}engines: {node: '>=0.10.0'}hasBin: truedependencies:asn1: 0.2.4assert-plus: 1.0.0bcrypt-pbkdf: 1.0.2dashdash: 1.14.1ecc-jsbn: 0.1.2getpass: 0.1.7jsbn: 0.1.1safer-buffer: 2.1.2tweetnacl: 0.14.5dev: true/stdout-stream/1.4.1:resolution: {integrity: sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==}dependencies:readable-stream: 2.3.7dev: true/strict-uri-encode/1.1.0:resolution: {integrity: sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=}engines: {node: '>=0.10.0'}dev: true/string-width/1.0.2:resolution: {integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=}engines: {node: '>=0.10.0'}dependencies:code-point-at: 1.1.0is-fullwidth-code-point: 1.0.0strip-ansi: 3.0.1dev: true/string-width/3.1.0:resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}engines: {node: '>=6'}dependencies:emoji-regex: 7.0.3is-fullwidth-code-point: 2.0.0strip-ansi: 5.2.0dev: true/string-width/4.2.2:resolution: {integrity: sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==}engines: {node: '>=8'}dependencies:emoji-regex: 8.0.0is-fullwidth-code-point: 3.0.0strip-ansi: 6.0.0dev: true/string_decoder/1.1.1:resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}dependencies:safe-buffer: 5.1.2dev: true/strip-ansi/3.0.1:resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=}engines: {node: '>=0.10.0'}dependencies:ansi-regex: 2.1.1dev: true/strip-ansi/5.2.0:resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==}engines: {node: '>=6'}dependencies:ansi-regex: 4.1.0dev: true/strip-ansi/6.0.0:resolution: {integrity: sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==}engines: {node: '>=8'}dependencies:ansi-regex: 5.0.0dev: true/strip-indent/3.0.0:resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}engines: {node: '>=8'}dependencies:min-indent: 1.0.1dev: true/strip-json-comments/3.1.1:resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}engines: {node: '>=8'}dev: true/supports-color/2.0.0:resolution: {integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=}engines: {node: '>=0.8.0'}dev: true/supports-color/5.5.0:resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}engines: {node: '>=4'}dependencies:has-flag: 3.0.0dev: true/supports-color/7.2.0:resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}engines: {node: '>=8'}dependencies:has-flag: 4.0.0dev: true/svelte-hmr/0.14.7_svelte@3.42.1:resolution: {integrity: sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==}peerDependencies:svelte: '>=3.19.0'dependencies:svelte: 3.42.1dev: true/svelte-preprocess/4.7.4_6197623e5ed34153d1bcd9290e2954d7:resolution: {integrity: sha512-mDAmaltQl6e5zU2VEtoWEf7eLTfuOTGr9zt+BpA3AGHo8MIhKiNSPE9OLTCTOMgj0vj/uL9QBbaNmpG4G1CgIA==}engines: {node: '>= 9.11.2'}requiresBuild: truepeerDependencies:'@babel/core': ^7.10.2coffeescript: ^2.5.1less: ^3.11.3node-sass: '*'postcss: ^7 || ^8postcss-load-config: ^2.1.0 || ^3.0.0pug: ^3.0.0sass: ^1.26.8stylus: ^0.54.7sugarss: ^2.0.0svelte: ^3.23.0typescript: ^3.9.5 || ^4.0.0peerDependenciesMeta:'@babel/core':optional: truecoffeescript:optional: trueless:optional: truenode-sass:optional: truepostcss:optional: truepostcss-load-config:optional: truepug:optional: truesass:optional: truestylus:optional: truesugarss:optional: truetypescript:optional: truedependencies:'@types/pug': 2.0.5'@types/sass': 1.16.1detect-indent: 6.1.0node-sass: 6.0.1sass: 1.37.5strip-indent: 3.0.0svelte: 3.42.1dev: true/svelte/3.42.1:resolution: {integrity: sha512-XtExLd2JAU3T7M2g/DkO3UNj/3n1WdTXrfL63OZ5nZq7nAqd9wQw+lR4Pv/wkVbrWbAIPfLDX47UjFdmnY+YtQ==}engines: {node: '>= 8'}dev: true/table/6.7.1:resolution: {integrity: sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==}engines: {node: '>=10.0.0'}dependencies:ajv: 8.6.2lodash.clonedeep: 4.5.0lodash.truncate: 4.4.2slice-ansi: 4.0.0string-width: 4.2.2strip-ansi: 6.0.0dev: true/tar/6.1.7:resolution: {integrity: sha512-PBoRkOJU0X3lejJ8GaRCsobjXTgFofRDSPdSUhRSdlwJfifRlQBwGXitDItdGFu0/h0XDMCkig0RN1iT7DBxhA==}engines: {node: '>= 10'}dependencies:chownr: 2.0.0fs-minipass: 2.1.0minipass: 3.1.3minizlib: 2.1.2mkdirp: 1.0.4yallist: 4.0.0dev: true/text-table/0.2.0:resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=}dev: true/to-regex-range/5.0.1:resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}engines: {node: '>=8.0'}dependencies:is-number: 7.0.0dev: false/tough-cookie/2.5.0:resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}engines: {node: '>=0.8'}dependencies:psl: 1.8.0punycode: 2.1.1dev: true/trim-newlines/3.0.1:resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}engines: {node: '>=8'}dev: true/true-case-path/1.0.3:resolution: {integrity: sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==}dependencies:glob: 7.1.7dev: true/tunnel-agent/0.6.0:resolution: {integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=}dependencies:safe-buffer: 5.2.1dev: true/tweetnacl/0.14.5:resolution: {integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=}dev: true/type-check/0.4.0:resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}engines: {node: '>= 0.8.0'}dependencies:prelude-ls: 1.2.1dev: true/type-fest/0.18.1:resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==}engines: {node: '>=10'}dev: true/type-fest/0.20.2:resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}engines: {node: '>=10'}dev: true/type-fest/0.6.0:resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==}engines: {node: '>=8'}dev: true/type-fest/0.8.1:resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}engines: {node: '>=8'}dev: true/uri-js/4.4.1:resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}dependencies:punycode: 2.1.1dev: true/util-deprecate/1.0.2:resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}dev: true/uuid/3.4.0:resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.hasBin: truedev: true/uuid/8.3.2:resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}hasBin: truedev: true/v8-compile-cache/2.3.0:resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}dev: true/validate-npm-package-license/3.0.4:resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}dependencies:spdx-correct: 3.1.1spdx-expression-parse: 3.0.1dev: true/verror/1.10.0:resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=}engines: {'0': node >=0.6.0}dependencies:assert-plus: 1.0.0core-util-is: 1.0.2extsprintf: 1.3.0dev: true/vite/2.4.4:resolution: {integrity: sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw==}engines: {node: '>=12.0.0'}hasBin: truedependencies:esbuild: 0.12.19postcss: 8.3.6resolve: 1.20.0rollup: 2.56.1optionalDependencies:fsevents: 2.3.2dev: true/which-module/2.0.0:resolution: {integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=}dev: true/which/2.0.2:resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}engines: {node: '>= 8'}hasBin: truedependencies:isexe: 2.0.0dev: true/wide-align/1.1.3:resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==}dependencies:string-width: 1.0.2dev: true/word-wrap/1.2.3:resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}engines: {node: '>=0.10.0'}dev: true/wrap-ansi/5.1.0:resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==}engines: {node: '>=6'}dependencies:ansi-styles: 3.2.1string-width: 3.1.0strip-ansi: 5.2.0dev: true/wrappy/1.0.2:resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}dev: true/y18n/4.0.3:resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}dev: true/yallist/4.0.0:resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}dev: true/yargs-parser/13.1.2:resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==}dependencies:camelcase: 5.3.1decamelize: 1.2.0dev: true/yargs-parser/20.2.9:resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}engines: {node: '>=10'}dev: true/yargs/13.3.2:resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==}dependencies:cliui: 5.0.0find-up: 3.0.0get-caller-file: 2.0.5require-directory: 2.1.1require-main-filename: 2.0.0set-blocking: 2.0.0string-width: 3.1.0which-module: 2.0.0y18n: 4.0.3yargs-parser: 13.1.2dev: true
{"requires": true,"lockfileVersion": 1,"dependencies": {"@types/estree": {"version": "file:node_modules/.pnpm/@types/estree@0.0.44/node_modules/@types/estree"},"@types/node": {"version": "file:node_modules/.pnpm/@types/node@14.0.5/node_modules/@types/node"},"@types/resolve": {"version": "file:node_modules/.pnpm/@types/resolve@0.0.8/node_modules/@types/resolve","requires": {"@types/node": "*"}},"eslint-plugin-svelte3": {"version": "2.7.3","resolved": "https://registry.npmjs.org/eslint-plugin-svelte3/-/eslint-plugin-svelte3-2.7.3.tgz","integrity": "sha512-p6HhxyICX9x/x+8WSy6AVk2bmv9ayoznoTSyCvK47th/k/07ksuJixMwbGX9qxJVAmPBaYMjEIMSEZtJHPIN7w==","dev": true}}}
const pkg = require('./package.json');/** @type {import('vite').UserConfig} */export default {ssr: {noExternal: Object.keys(pkg.dependencies || {})},};
//import node from '@sveltejs/adapter-node'import preprocess from 'svelte-preprocess'/** @type {import('@sveltejs/kit').Config} */const config = {kit: {ssr: false,target: '#svelte',vite: {optimizeDeps: {include: ['secure-remote-password/client']},server: {fs: {allow: ['../..']}}}},preprocess: preprocess()}export default config
body {margin: 0;font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;font-size: 14px;line-height: 1.5;color: #333;}h1, h2, h3, h4, h5, h6 {margin: 0 0 0.5em 0;font-weight: 400;line-height: 1.2;}h1 {font-size: 2em;}a {color: inherit;}code {font-family: menlo, inconsolata, monospace;font-size: calc(1em - 2px);color: #555;background-color: #f0f0f0;padding: 0.2em 0.4em;border-radius: 2px;}@media (min-width: 400px) {body {font-size: 16px;}}
<script>import querystring from 'query-string'import { goto } from '$app/navigation'import { onMount } from 'svelte'let access_token, expiryonMount(() => {const hash = querystring.parse(window?.location?.hash ?? '#')if (hash.token_type === 'Bearer') {access_token = hash.access_tokenexpiry = Math.round((new Date()).getTime() / 1000) + Number(hash.expires_in)}})$: user_id = datastore.svelte('session/user_id')$: if ($user_id && access_token && expiry) {datastore.queue(`/state/users/${$user_id}/services/spotify/client/expiry`, expiry)datastore.queue(`/state/users/${$user_id}/services/spotify/client/token`, access_token)}$: name = datastore.svelte(`session/user/name`)$: email = datastore.svelte(`session/user/email`)$: user_name = datastore.svelte(`state/users/${$user_id}/name`)$: accepted_store = datastore.svelte(`action/users/${$user_id}/accept`)let accepted = false$: accepted = accepted || $accepted_store$: if (accepted) {datastore.queue(`/state/users/${$user_id}/name`, name)datastore.queue(`/state/users/${$user_id}/email`, email)}$: rejected = datastore.svelte(`action/users/${$user_id}/reject`, false)$: event_id = datastore.svelte(`state/users/${$user_id}/event_id`)$: console.log({user_id: $user_id, accepted, event_id: $event_id, test: $user_id && accepted && $event_id})$: if ($user_id && accepted && $event_id) {goto('/client/')}import EventSelect from '../client/_event_select.svelte'import Global from '$lib/components/global.svelte'</script><Global /><div class="wrapper">{#if !$user_id}<h1>Collecting Service Information...</h1>{:else if !$event_id}<EventSelect {$user_id} />{/if}</div><style type="text/scss">.wrapper {display: grid;grid-template: 1 1fr / 1 1fr;align-items: center;justify-items: center;width: 100%;height: 100%;}h1 {margin-top: auto;margin-bottom: auto;text-align: center;}.panel {display: grid;grid-auto-flow: row;padding: 2rem;background: hsla(0, 100%, 0%, 0.40);border-radius: 0.5rem;p {color: hsla(100, 25%, 100%, 0.8);}.buttons {display: grid;grid-auto-flow: column;align-items: center;}button {width: 80%;}input {width: 100%;padding: 0.25em;color: hsla(100, 25%, 100%, 0.8);background: rgba(255, 255, 255, 0.1);border: none;border-radius: 0.25em;&:-internal-autofill-selected {color: hsla(100, 25%, 100%, 0.8) !important;background: rgba(255, 255, 255, 0.1) !important;}}}</style>
<script>// let state = 'initializing'// let code, token, refresh_token, spotify_id// chain.link('hash', '/session/path/query/*', object => {// if (_.isPlainObject(object) && !_.isArray(object)) {// if (object.code) {// code = object.code// datastore.destroy('/session/path/query')// datastore.write('/state/services/spotify/code', code)// }// }// })// chain.link('spotify_id', '/state/services/spotify/id', string => {// if (_.isString(string)) {// spotify_id = datastore.read('/state/services/spotify/id')// token = datastore.read('/state/services/spotify/token')// refresh_token = datastore.read('/state/services/spotify/refresh_token')// expiry = datastore.read('/state/services/spotify/expiry')// state = 'initialized'// }// })// chain.link('query', '/session/path/query/*', object => {// if (_.isPlainObject(object) && !_.isArray(object) && object.error) {// datastore.destroy('/session/path/query')// }// })// chain.link('create', '/action/admin/create', (value, pointer) => {// if (pointer.steps[1] == 'admin' && value === true) {// state = 'create_requested'// authorize()// } else if (pointer.steps[1] == 'admin' && value === false) {// state = 'create_failed'// authorize()// }// })// let password = 'nova-wellington-sandfly-turducken'// const create = () => {// state = 'authorization_requested'// authorization.createAccount(`state/admin/#`, 'admin', password)// }// const authorize = () => {// state = 'authorization_requested'// authorization.startSession(`state/admin/#`, 'admin', password)// }import { onMount } from 'svelte'let state = 'initializing'let access_token, expiryonMount(() => {const hash = querystring.parse(window?.location?.hash ?? '#')if (hash.token_type === 'Bearer') {access_token = hash.access_tokenexpiry = Math.round((new Date()).getTime() / 1000) + Number(hash.expires_in)}})$: user_id = datastore.svelte('session/user_id')$: spotify_id = datastore.svelte(`state/users/${user_id}/services/spotify/admin/id`)let accepted = false$: accepted_store = datastore.svelte(`action/users/${$user_id}/accept`)$: accepted = accepted || $accepted_store$: if (accepted) {datastore.queue(`/setup/users/${$user_id}/services/spotify/admin/expiry`, expiry)datastore.queue(`/setup/users/${$user_id}/services/spotify/admin/code`, code)datastore.queue(`/setup/users/${$user_id}/services/spotify/admin/token`, token)datastore.queue(`/setup/users/${$user_id}/services/spotify/admin/refresh_token`, refresh_token)datastore.queue(`/setup/users/${$user_id}/services/spotify/admin/id`, spotify_id)state = 'complete'}import Global from '$lib/components/global.svelte'</script><Global /><div class="wrapper">{#if state == 'initializing'}<h1>Collecting service information...</h1>{:else if state == 'authorization_failed'}<h1>Authentication Failed</h1>{:else if state == 'authorized'}<h1>Saving key...</h1>{:else if state == 'complete'}<h1>Key saved to Database</h1>{:else}<h1>Unkown state: {state}</h1>{/if}</div><style type="text/scss">.wrapper {display: grid;grid-template: 1 1fr / 1 1fr;align-items: center;justify-items: center;width: 100%;height: 100%;}h1 {margin-top: auto;margin-bottom: auto;text-align: center;}.panel {display: grid;grid-auto-flow: row;padding: 2rem;background: hsla(0, 100%, 0%, 0.40);border-radius: 0.5rem;p {color: hsla(100, 25%, 100%, 0.8);}.buttons {display: grid;grid-auto-flow: column;align-items: center;}button {width: 80%;}input {width: 100%;padding: 0.25em;color: hsla(100, 25%, 100%, 0.8);background: rgba(255, 255, 255, 0.1);border: none;border-radius: 0.25em;&:-internal-autofill-selected {color: hsla(100, 25%, 100%, 0.8) !important;background: rgba(255, 255, 255, 0.1) !important;}}}</style>
<script>const inputs = Array(4)let fixme = falseimport { onMount, createEventDispatcher } from 'svelte'import lodash from 'lodash'const dispatch = createEventDispatcher()onMount(() => {fixme = true})const oninput = event => {let value = event.target.valueif (!value.match(/^[0-9]$/)) {return}let index = parseInt(event.target.dataset.index)if (index < 3) {index++inputs[index].focus()}const pin = lodash.reduce(inputs,(pin, input) => {const value = input.valueif (value.length === 1) {return `${pin}${value}`} else {return pin}},'')if (pin.match(/^[0-9]{4}$/)) {dispatch('set', pin)}}const onkeyup = event => {console.log('onkeyup event.code', event.code)switch (event.code) {case 'Backspace':breakdefault:return}let value = event.target.valuelet index = parseInt(event.target.dataset.index)if (index > 0 && (value == null || value == '')) {index--inputs[index].focus()}}</script><div display="none">{fixme}</div><div><h2>Please select a pin</h2><div class="digits"><!-- svelte-ignore a11y-autofocus --><input bind:this={inputs[0]} type="text" pattern="\d*" data-index={0} on:input={oninput} autofocus /><input bind:this={inputs[1]} type="text" pattern="\d*" data-index={1} on:input={oninput} on:keyup={onkeyup} /><input bind:this={inputs[2]} type="text" pattern="\d*" data-index={2} on:input={oninput} on:keyup={onkeyup} /><input bind:this={inputs[3]} type="text" pattern="\d*" data-index={3} on:input={oninput} on:keyup={onkeyup} /></div></div><style type="text/scss">.wrapper {display: grid;grid-template: 1 1fr / 1 1fr;align-items: center;justify-items: center;width: 100%;height: 100%;}h1 {margin-top: auto;margin-bottom: auto;text-align: center;}.digits {display: grid;grid-template-columns: repeat(4, calc(25px * 4));gap: 20px;input {font-size: calc(20px * 2);text-align: center;/* FIXME: Create new variable */background: hsla(228, 54%, 8%, 0.6);border-color: transparent;border-style: solid;border-width: 2px;/*-webkit-backdrop-filter: blur(24px);backdrop-filter: blur(24px);*/transition: border-color 100ms;&:focus {border-color: var(--green);}}}</style>
<script>import { goto } from '$app/navigation'import { onMount } from 'svelte'onMount(async () => goto('/client'))</script>
<script>import lodash from 'lodash'import { datastore } from '@controlenvy/datastore'import { subscribe } from '$lib/subscribe'import { unsubscribe } from '$lib/unsubscribe'$: user_id = datastore.svelte('session/user_id')$: session_event_id = datastore.svelte('session/event_id')$: user_event_id = datastore.svelte(`state/users/${$user_id}/event_id`)// FIXME events used to be { [id]: { name, private }}$: event_paths = datastore.svelte(`setup/events/+`, [], pointer => pointer.path.slice(6))$: user_event_name = datastore.svelte(`state/events/${user_event_id}/name`, 'None')$: session_event_name = datastore.svelte(`state/events/${$session_event_id}/name`, 'None')let event_idlet items = {}const parseEvent = (session_event_id, user_event_id) => {console.log({session_event_id: session_event_id, user_event_id})if (session_event_id &&session_event_id != user_event_id) {if (event_id != null) unsubscribe(`state/events/${event_id}/#`)subscribe(`state/events/${session_event_id}/#`, true)}event_id = session_event_id || user_event_iditems = {}}$: parseEvent($session_event_id, $user_event_id)import { onDestroy } from 'svelte'onDestroy(() => {datastore.delete('/session/local/event_id')if ($session_event_id && $session_event_id != $user_event_id) {unsubscribe(`state/events/${$session_event_id}/#`)}})const refreshEvent = (event_id) => items = {}refreshEvent(event_id)$: refreshEvent(event_id)$: data = datastore.svelte(`state/events/${event_id}/users/+/items/+/vote`, [], pointer => {const [ , , , , item_user_id, , item_id] = pointer.stepsconst { value } = pointerreturn isNumber(value) ? [item_id, item_user_id] : null})$: items = lodash.reverse(lodash.sortBy(Object.entries($data.filter(item => item).reduce((acc, [item_id, item_user_id]) => {acc[item_id] = lodash.union(items[item_id] || [], [item_user_id])return acc}, {})), ([, value]) => value.length))const changeEvent = ({ target }) => {if ($session_event_id && $session_event_id != $user_event_id) unsubscribe(`state/events/${$user_event_id}/#`)if (target.value) subscribe(`state/events/${target.value}/#`, true)$session_event_id = target.value}let max_votes = 0$: max_votes = items.length > 0 ? items[0][1].length : 0import Item from './_item.svelte'import Event from './_event.svelte'import Icon from '$lib/components/Icon.svelte'import Global from '$lib/components/global.svelte'import Select from '$lib/components/slots/select.svelte'</script><svelte:head><title>Charts</title></svelte:head><Global segment="tallies"><div class="title-row"><Icon name="chart"/><h1>Charts</h1></div><div class="select-wrap"><h2>Select Event</h2><Select><select on:blur={changeEvent} bind:value={$session_event_id}><option value={null}>None</option>{#each $event_paths as event_path}<Event {event_path} ></Event>{/each}</select></Select></div></Global><div class="scroll"><div class="wrapper">{#each items as [item_id, votes] (item_id)}<Item {event_id} {item_id} {max_votes} votes={votes.length} />{:else}<div class="fallback"><h2>No Votes Yet</h2></div>{/each}</div></div><style type="text/scss">.scroll {overflow-y: scroll;padding: 2em;max-height: 75%;margin: 0 0 1em 0;padding: 2rem;color: rgba(255, 255, 255, 0.8);background: hsla(0, 100%, 0%, 0.40);border-radius: 0.3rem;}.wrapper {display: grid;grid-template-columns: 1fr 4fr;padding: 2rem;grid-spacing: 0;.fallback {grid-column: 1 / span 2;width: 100%;text-align: center;}}.select-wrap {display: grid;grid-template-columns: auto auto;margin-bottom: 1em;}.title-row {position: relative;}.event {position: absolute;right: 0;color: var(--text-primary);}</style>
<script>export let event_idexport let item_idexport let max_votesexport let votesimport lodash from 'lodash'const __self = eval("__$$self")const chain = datastore.chain(__self)chain.prop('item_id', `/${item_id}`)$: chain.prop('item_id', `/${item_id}`)chain.prop('event_id', `/${event_id}`)$: chain.prop('event_id', `/${event_id}`)console.log(event_id, item_id)let item_name = ''chain.link('item_name', '/state/events/:event_id/items/:item_id/name', string => {if (lodash.isString(string)) {item_name = string} else {item_name = ''}})const portion = (votes, max_votes) => {if (votes && max_votes) {return Math.ceil(votes / max_votes * 200 ) / 2} else {return 0.25}}let length = portion(votes, max_votes)$: length = portion(votes, max_votes)</script><svelte:head><title>Blog</title></svelte:head><div class="name">{item_name}</div><div class="axis-wrapper"><div class="axis"><div class="data" style="width:{length}%;"/><span>{votes}</span></div></div><style type="text/scss">.axis-wrapper {width: 100%;margin: 0;overflow-y: scroll;background: repeating-linear-gradient(to right,rgba(255, 255, 255, 0.5),rgba(255, 255, 255, 0.5) 1px,hsla(0, 100%, 0%, 0) 1px,hsla(0, 100%, 0%, 0) 10%);border-left: 1.5px solid rgba(255, 255, 255, 0.5);}.axis {display: flex;width: 100%;.data {display: inline-block;align-self: center;height: 0.75rem;background-color: hsla(200, 40%, 40%, 1);}span {margin-left: 0.5rem;}}.name {padding-right: 0.5rem;overflow: hidden;font-weight: 600;white-space: nowrap;text-align: right;text-overflow: ellipsis;}</style>
<option value={id}>{$name}{$private_event ? ' (private)' : ''}</option><script>export let event_pathimport { datastore } from '@controlenvy/datastore'$: id = event_path.split('/').slice(-1)[0]$: name = datastore.svelte(`setup${event_path}/name`)$: private_event = datastore.svelte(`setup${event_path}/private`, false)</script>
<script>import Icon from '$lib/components/Icon.svelte'import Global from '$lib/components/global.svelte'import { onMount } from 'svelte'function onIntersection(entries) {entries.forEach(entry => {if (entry.isIntersecting && !entry.target.classList.contains('side-scroll-panel-transition')) {entry.target.classList.add('side-scroll-panel-transition');for (const child of entry.target.parentElement.parentElement.children) {const grandchild = child.children[0]if (grandchild === entry.target) { continue }grandchild.classList.remove('side-scroll-panel-transition')}return; // if we added the class, exit the function}})}const options = {threshold: [0.3]}let panel_record = []function observe() {const panels = document.querySelectorAll('.side-scroll-panel')const new_panels_array = Array.from(panels.values())for (const panel of panel_record) {if (new_panels_array.includes(panel)) { continue }observer.unobserve(panel)}const panels_array = Array.from(panel_record.values())for (const panel of panels) {if (panels_array.includes(panel)) { continue }observer.observe(panel)}panel_record = panels}let observeronMount(() => {observer = new IntersectionObserver(onIntersection, options)observe()})</script><Global segment="info"><div class="title-row"><Icon name="info"/><h1>Our Mission</h1></div><div class="side-scroll"><div class="side-scroll-child"><div class="side-scroll-panel"><h2>What is Djinlist?</h2><p>Djinlist, plain and simple, is built to keep the party going.</p><p>Sick and tired of Ashlee playing "Get Lucky" for the third time tonight? Democratize your party with Djinlist. We take the stress out of picking the music for your party or event.</p><h2>Mission</h2><p>We want to provide an alternative to a DJ or a difficult-to-manage play list for your party. You should spend your time with your guests, not dealing with the music. We aim to be your peace of mind that one thing won't go wrong on your big day.</p></div></div><div class="side-scroll-child"><div class="side-scroll-panel"><h2>Djinlist Public Weekly lists</h2><p>Sign-in and vote on your favorite Friday night track with others all over the world or in your city. Every Friday the Djinlist Official account publishes weekly playlists on your favorite streaming service. Vote on your party playlist for free and spend your night enjoying the tunes instead of picking the music.</p><h2>Live Events</h2><p>Every Saturday night vote for the top twenty tracks of the week in your timezone</p></div></div><div class="side-scroll-child"><div class="side-scroll-panel"><h2>Djinlist Private (Coming Soon)</h2><p>We love music and we love parties. There are lot's of great DJ's out there and we recommend using them, but not everyone can afford a private DJ. Are you worried the only DJ in town is going to spend the night hitting on your bridesmaids instead of queueing up the father-daughter dance? Don't trust your maid of honor to handle your Spotify playlist? Use us instead!</p></div></div><div class="side-scroll-child"><div class="side-scroll-panel"><h2>Supported Services</h2><p>While we are in our current proof-of-concept design cycle, we support or have plans to support the following music services:</p><ul><li>Spotify</li><li>Apple Music (planned)</li></ul><p>We plan to integrate more music services once proof of concept is complete and we can turn this passion into a living.</p></div></div></div></Global><style type="text/scss">.side-scroll-panel {align-self: center;}</style>
<script>import { goto } from '$app/navigation'import { datastore } from '@controlenvy/datastore'const authorize = (service) => {datastore.write(`/action/services/${service}/start`, (new Date()).getTime() / 1000)}$: uuid = datastore.svelte('session/user_id')$: user_path = `/users/${$uuid}`$: token = datastore.svelte(`state${user_path}/services/spotify/client/token`)$: expiry = datastore.svelte(`state/${user_path}/services/spotify/client/expiry`)let expiredfunction reauthorize(expiry) {if (typeof number !== 'number') {expired = nullreturn}expired = (((new Date()).getTime() / 1000) > number)if (expired) authorize('spotify')}$: reauthorize(expiry)$: name = datastore.svelte(`state${user_path}/name`)$: redirect = datastore.svelte('session/redirect')$: if (typeof $redirect === 'string') goto(string)const access = (sign_up) => {datastore.set('/session/sign_up', sign_up)goto('/client/account')}const explore = () => goto('/client/explore')import Icon from '$lib/components/Icon.svelte'import Global from '$lib/components/global.svelte'</script><svelte:head><title>Djiny</title></svelte:head><Global segment="home"><div class="grid-1 fill" ><div class="panel-opaque"><h1 class="title-big ctr">Djiny</h1><h2 class="ctr">Playlists with Friends</h2>{#if $token && expired === false}<div class="btn btn-big ctr" on:click|preventDefault={explore}><h2>Explore</h2></div>{:else if $token}<p>Refreshing Music Service Session...</p>{:else if $name}<div class="ctr"><div class="grid grid-responsive-columns-1fr"><p>Link a Service:</p><div class="grid-1 btn btn-rnd" on:click|preventDefault={() => authorize('spotify')}><Icon name="spotify"/></div><div class="grid-1 btn btn-rnd" on:click|preventDefault={() => authorize('apple')}><Icon name="apple"/></div></div><p class="drk-txt">Logged in as {$name}</p></div>{:else}<div class="btn btn-big" on:click|preventDefault={() => access(true)}><h2>Sign Up</h2></div><div class="btn btn-big" on:click|preventDefault={() => access(false)}><h2>Log In</h2></div>{/if}</div></div></Global>
<script>import Global from '$lib/components/global.svelte'import FirstTrack from './_explore/header/track.svelte'import FirstAlbum from './_explore/header/album.svelte'import FirstArtist from './_explore/header/artist.svelte'import FirstPlaylist from './_explore/header/playlist.svelte'import SearchResults from './_explore/search_results.svelte'import Inspect from './_explore/inspect.svelte'import List from './_explore/list.svelte'import Icon from '$lib/components/Icon.svelte'import navigate from './navigate.js'import { datastore } from '@controlenvy/datastore'import _ from 'lodash'$: text = datastore.svelte('session/services/spotify/search/text', '')$: inspect = datastore.svelte('session/services/spotify/search/inspect')const debounceFirstResults = _.debounce(firstResults, 250)let first_results, first_track, first_artist, first_album, first_playlistasync function firstResults() {first_results = nullconst path = 'https://api.spotify.com/v1/search?'const query = `q=${$text}&type=track,album,artist,playlist&limit=1`first_results = navigate(`${path}${query}`)const result = await first_resultsif (result.error) {tracks = nullartists = nullalbums = nullplaylists = null} else {first_track = result.tracks.items[0]first_artist = result.artists.items[0]first_album = result.albums.items[0]first_playlist = result.playlists.items[0]}}let search_results, view, tracks, artists, albums, playlistsasync function search(type) {search_results = nullview = typeconst path = 'https://api.spotify.com/v1/search?'const query = `q=${$text}&type=${type}`search_results = navigate(`${path}${query}`)const result = await search_resultsif (result.error) {tracks = nullartists = nullalbums = nullplaylists = null} else {tracks = result.tracksartists = result.artistsalbums = result.albumsplaylists = result.playlists}console.log({ tracks, artists, albums, playlists })}function hide() {view = null}$: if ($text != '') {debounceFirstResults()}</script><Global nav={['explore']} segment="explore"/><svelte:head><title>Djiny</title></svelte:head><div class="side-scroll"><div class="wrapper side-scroll-child"><div class="side-scroll-panel"><div class="options"><label id="track">Song{#if first_track}<FirstTrack {first_track}/>{#if view !== "track"}<button class="invisible" id="track" on:click|preventDefault={() => search("track")}><h2>More Songs</h2><Icon name="caret_right"></Icon></button>{:else}<button class="invisible" on:click|preventDefault={hide}><Icon name="caret_up"></Icon></button>{/if}{/if}</label><label id="artist">Artist{#if first_artist}<FirstArtist {first_artist}/>{#if view !== "artist"}<button class="invisible" id="artist" on:click|preventDefault={() => search("artist")}><h2>More Artists</h2><Icon name="caret_right"></Icon></button>{:else}<button class="invisible" on:click|preventDefault={hide}><Icon name="caret_up"></Icon></button>{/if}{/if}</label><label id="album">Album{#if first_album}<FirstAlbum {first_album}/>{#if view !== "album"}<button class="invisible" id="album" on:click|preventDefault={() => search("album")}><h2>More Albums</h2><Icon name="caret_right"></Icon></button>{:else}<button class="invisible" on:click|preventDefault={hide}><Icon name="caret_up"></Icon></button>{/if}{/if}</label><label id="playlist">Playlist{#if first_playlist}<FirstPlaylist {first_playlist}/>{#if view !== "playlist"}<button class="invisible" id="playlist" on:click|preventDefault={() => search("playlist")}><h2>More Playlists</h2><Icon name="caret_right"></Icon></button>{:else}<button class="invisible" on:click|preventDefault={hide}><Icon name="caret_up"></Icon></button>{/if}{/if}</label></div>{#if $text === ''}<p class="red">No search entered</p>{/if}{#if first_results}{#await first_results then result}{#if result.error}<p>{result.error.status}</p><p>{result.error.message}</p>{/if}{/await}{/if}</div></div>{#if search_results && view}<div class="wrapper side-scroll-child"><div class="side-scroll-panel">{#await search_results}<p>Expanding Search...</p>{:then result}{#if result.error}<p>{result.error.status}</p><p>{result.error.message}</p>{:else}{#if $inspect}<List active={true}><Inspect inspect={$inspect}/></List>{:else if view}<List active={true}><SearchResults {view} result={result[`${view}s`]}/></List>{/if}{/if}{/await}</div></div>{/if}</div><style type="text/scss">form {display: grid;justify-content: center;}.title_row {grid-area: 1 / 1 / span 1 / span 1;margin-right: auto;margin-left: auto;width: 50%;display: grid;grid-template-columns: max-content auto;align-content: center;h1 {margin: 1rem;font-size: 3rem;}}.options {margin: auto;display: grid;grid-template-columns: 1fr 1fr;grid-template-rows: 1fr 1fr;align-items: center;grid-gap: 1rem;width: 100%;border: none;:global(.top_result_item) {display: grid;justify-items: center;border-radius: 0.5rem;padding: 0.5rem;border: 2px solid var(--accent);background: none;:global(img) {width: 80%;min-width: 8rem;border-radius: 0.5rem;}:global(p) {color: white;}:global(h2) {color: white;}}:global(.active) {border: none;background: var(--accent);:global(p) {color: grey;}:global(h2) {color: grey;}}:global(p) {text-align: center;margin: 0;}:global(h2) {text-align: center;}.checked {font-weight: 600;color: var(--accent);border: 2px solid var(--accent);}label {color: var(--text-primary);border: 1px solid var(--text-primary);border-radius: 1em;padding: 0.5rem;height: max-content;}input {display: none;}}p {text-align: center;}.invisible {background: none;border: none;display: grid;grid-template-columns: max-content auto;grid-gap: 0.5rem;}.categories {grid-area: 1 / 1 / span 1 / span 1;justify-self: end;align-self: center;}.search_results {:global(img) {height: 4em;}:global(.active) {background: hsla(0, 100%, 100%, 0.2);}:global(.side-scroll-panel) {height: 50vh;}}.side-scroll::before {width: 5vw;content: '';}.side-scroll::after {width: 5vw;content: '';}</style>
export default function navigate(href) {const user = datastore.read("/session/user_id")const token = datastore.read(`state/users/${user}/services/spotify/token`)return fetch(href, {headers: {'Authorization': `Bearer ${token}`}}).then(data => data.json()).catch(e => console.log('LLLL', e))}
<script>import Search from './search.svelte'import Categories from './categories.svelte'import Global from '$lib/components/global.svelte'import StringInput from '$lib/components/slots/string.svelte'import { slide } from 'svelte/transition'$: show = datastore.svelte('session/tab', 'search')$: search_active = $show === 'search'$: categories_active = $show === 'categories'</script><svelte:head><title>Djiny</title></svelte:head><Global segment="explore"><div class="title_row"><div class="search" class:active={search_active} on:click|preventDefault={() => $show = 'search'}><h1>Search</h1><StringInput path="/session/services/spotify/search/text" title="Search" queue={false}/></div><div class="categories" class:active={categories_active}><h1 on:click|preventDefault={() => $show = 'categories'}>Categories</h1></div></div>{#if search_active}<Search />{:else if categories_active}<Categories />{/if}</Global><style type="text/scss">.title_row {display: grid;width: 50%;margin: auto;}.search {display: grid;grid-template-columns: max-content auto;align-items: center;h1 {margin: auto;}transform: scale(1);transform: opacity 400ms;transition-duration: 400ms;&:not(.active) {opacity: 0.5;transform: scale(0.5);}}.categories {text-align: center;transform: scale(1);transform: opacity 400ms;transition-duration: 400ms;&:not(.active) {opacity: 0.5;transform: scale(0.5);}}</style>
<script>import navigate from './navigate.js'import SearchResults from './_categories/search_results.svelte'import List from './_explore/list.svelte'import Icon from '$lib/components/Icon.svelte'import { datastore } from '@controlenvy/datastore'datastore.mark('/action/services/spotify/directories/1/start')let searches = [navigate('https://api.spotify.com/v1/browse/categories')]function onInspect({ detail }) {const { href, index } = detailif (index + 1 === searches.length) {searches = searches.concat(navigate(href))} else {searches = searches.slice(0, index + 1).concat(navigate(href))}}import { onMount } from 'svelte'let activefunction onIntersection(entries) {entries.forEach(entry => {if (entry.isIntersecting && !entry.target.classList.contains('side-scroll-panel-transition')) {let elements = entry.target.parentElement.parentElement.childrenfor (const child_idx in elements) {const grandchild = elements[child_idx].children[0]if (grandchild === entry.target) {active = +child_idxreturn}}active = null}})}const options = {threshold: [0.3]}let panel_record = []function observe() {const panels = document.querySelectorAll('.side-scroll-panel')const new_panels_array = Array.from(panels.values())for (const panel of panel_record) {if (new_panels_array.includes(panel)) { continue }observer.unobserve(panel)}const panels_array = Array.from(panel_record.values())for (const panel of panels) {if (panels_array.includes(panel)) { continue }observer.observe(panel)}panel_record = panels}function gotoSearch() {datastore.set('/session/tab', 'search')}let observeronMount(() => {observer = new IntersectionObserver(onIntersection, options)observe()})</script><div class="side-scroll">{#each searches as search, index}{#await search}<div class="wrapper side-scroll-child"><div class="side-scroll-panel"><p>Searching...</p></div></div>{:then result}{#if result.error}<div class="wrapper side-scroll-child"><div class="side-scroll-panel"><p>{result.error.status}</p><p>{result.error.message}</p></div></div>{:else}<List active={active === index}><SearchResults {index} {result} on:inspect={onInspect} on:add_or_delete={observe}/></List>{/if}{/await}{/each}</div><style type="text/scss">.side-scroll::before {width: 5vw;content: '';}.invisible {background: none;border: none;display: grid;grid-template-columns: max-content auto;grid-gap: 0.5rem;}.side-scroll::after {width: 5vw;content: '';}</style>
<script>export let viewexport let resultimport Track from './line/track.svelte'import Album from './line/album.svelte'import Artist from './line/artist.svelte'import Playlist from './line/playlist.svelte'import navigate from '../navigate.js'$: items = result.itemsconst types = {track: Track,album: Album,artist: Artist,playlist: Playlist}$: svelte_component = types[view]let nextconst onClick = () => next = navigate(result.next)</script>{#each items as item}{#if svelte_component}<svelte:component this={svelte_component} {item}/>{:else}{view}{/if}{/each}{#if next}{#await next}<p>Loading Next tracks</p>{:then info}{#if info.error}<div><p>{info.error.status}</p><p>{info.error.message}</p></div>{:else}<svelte:self {view} result={info[`${view}s`]}/>{/if}{/await}{:else if result.next}<button on:click={onClick}>Load Next</button>{/if}
<slot {title} {album_title} {image_url} {artist_string} {id} active={$active} {vote}/><script>export let track$: external_ids = track.external_ids$: isrc = external_ids.isrc$: id = track.id$: album = track.album$: images = album.images || []$: image = images[0] || {}$: image_url = image.url$: type = track.type$: title = track.name$: album_title = album.name$: release_date = album.release_date$: artists = track.artists$: artist_string = artists.map(artist => artist.name).join(', ')$: user_id = datastore.svelte('session/user_id')$: event_id = datastore.svelte(`state/users/${$user_id}/event_id`)$: active = datastore.svelte(`state/events/${$event_id}/users/${$user_id}/items/${isrc}/vote`, false)function vote() {console.log("VOTE", `/action/users/${$user_id}/events/${$event_id}/items/${isrc}/vote`, +new Date())datastore.queue(`/action/users/${$user_id}/events/${$event_id}/items/${isrc}/vote`, +new Date())}</script>
<slot {title} {image_url} {owner} {href} {id} {owner_display_name} {number_of_tracks}/><script>export let playlist$: id = playlist.id$: images = playlist.images || []$: image = images[0] || {}$: image_url = image.url$: type = playlist.type$: owner = playlist.owner$: owner_display_name = owner.display_name$: title = playlist.name$: tracks = playlist.tracks$: href = tracks.href$: number_of_tracks = tracks.total</script>
<slot {title} {image_url} {href} {id}/><script>export let artist$: id = artist.id$: images = artist.images || []$: image = images[0] || {}$: image_url = image.url$: type = artist.type$: title = artist.name$: href = artist.href</script>
<slot {title} {image_url} {artist_string} {album_type} {href} {id}/><script>export let album$: id = album.id$: images = album.images || []$: image = images[0] || {}$: image_url = image.url$: album_type = album.album_type$: title = album.name$: href = album.href$: artists = album.artists || []$: artist_string = artists.map(artist => artist.name).join(', ')// $: release_date = album.release_date// $: number_of_tracks = album.total_tracks</script>
<script>export let active</script><div class="wrapper side-scroll-child"><div class="side-scroll-panel" class:side-scroll-panel-transition={active}><slot/></div></div><style type="text/scss">.wrapper {width: 100%;:global(.item) {position: relative;height: 5em;margin: 0 1em 0 1em;display: grid;overflow: hidden;grid-template-columns: min-content;grid-gap: 1rem;grid-template-areas: 'image info buttons';:global(.info-grid) {width: 100%;color: var(--text-primary);grid-area: info;:global(.title) {font-weight: 600;}:global(.artists) {color: var(--text-secondary);}}:global(.button-wrapper) {display: grid;grid-auto-flow: column;justify-items: center;margin-left: auto;grid-template-columns: min-content min-content min-content;grid-area: buttons;}:global(.button) {display: grid;align-content: center;justify-content: center;width: 3rem;height: 3rem;margin: 0.5rem;text-align: center;background: hsla(70, 25%, 0%, 0.7);border-radius: 50%;color: hsla(0, 0%, 100%, 0.9);border: 2px solid hsla(0, 0%, 100%, 0.9);}:global(.image) {grid-area: image;height: calc(5rem);overflow-y: hidden;/*transform: translateY(calc(-5rem));*/}}}</style>
<Track track={item} let:title let:image_url let:artist_string let:vote let:active><div class="item" class:active><img class="image" src={image_url} on:click={vote}/><div class="info-grid"><div class="title">{title}</div><div class="artists">{artist_string}</div></div></div></Track><script>export let itemimport Track from '../meta/track.svelte'</script>
<Playlist playlist={item} let:title let:image_url let:owner_display_name let:href><div class="item"><img class="image" src={image_url} on:click={() => onClick(href)}/><div class="info-grid"><div class="title">{title}</div><div class="owner">{owner_display_name}</div></div></div></Playlist><script>export let itemimport { datastore } from '@controlenvy/datastore'function onClick(href) {datastore.write("/session/services/spotify/search/inspect", href + '?limit=20')}import Playlist from '../meta/playlist.svelte'</script>
<Artist artist={item} let:title let:image_url let:id><div class="item"><img class="image" src={image_url} on:click={() => onClick(id)}/><div class="info-grid"><div class="title">{title}</div></div></div></Artist><script>export let itemimport { datastore } from '@controlenvy/datastore'function onClick(id) {const href = `https://api.spotify.com/v1/artists/${id}/top-tracks?market=ca`datastore.write("/session/services/spotify/search/inspect", href)}import Artist from '../meta/artist.svelte'</script>
<Album album={item} let:title let:image_url let:artist_string let:href><div class="item"><img class="image" src={image_url} on:click={() => onClick(href)}/><div class="info-grid"><div class="title">{title}</div><div class="artists">{artist_string}</div></div></div></Album><script>export let itemimport { datastore } from '@controlenvy/datastore'function onClick(href) {datastore.write("/session/services/spotify/search/inspect", href)}import Album from '../meta/album.svelte'</script>
<script>export let pathexport let focused = trueimport { datastore } from '@controlenvy/datastore'import { createEventDispatcher } from 'svelte'$: id = datastore.svelte(`${path}/id`)$: uri = datastore.svelte(`${path}/uri`)$: image_url = datastore.svelte(`${path}/image_url`)$: title = datastore.svelte(`${path}/title`)$: subtitle = datastore.svelte(`${path}/subtitle`)$: artists = datastore.svelte(`${path}/artists`)$: user_id = datastore.svelte('session/user_id')$: event_id = datastore.svelte(`state/users/${$user_id}/event_id`)const dispatch = createEventDispatcher()const focus = () => dispatch('focusItem', { path: path })const browse = () => datastore.mark(`${path}/browse`)const removeItem = () => chain.queue(`state/users/${$user_id}/events/${$event_id}/items/${$uri}/vote`, null)const addItem = () => {datastore.queue(`/state/events/${$event_id}/items/${$uri}/name`, item.title)datastore.queue(`/state/events/${$event_id}/items/${$uri}/artists`, item.artists)datastore.queue(`/state/events/${$event_id}/items/${$uri}/image_url`, item.image_url)if ($user_id) {chain.queue(`/state/users/${$user_id}/events/${$event_id}/items/${$uri}/vote`, + new Date())}}import Icon from '$lib/components/Icon.svelte'</script>{#if $id}<div class="item" on:click|preventDefault={focus}><img class="image" src={$image_url} alt="" /><div class="button-wrapper" class:focused={focused}>{#if $uri}<div class="button btn" on:click={addItem}>+</div><div class="button btn" on:click={removeItem}>-</div>{:else}<div class="button btn" on:click={browse}><Icon name="search"/></div>{/if}</div><div class="info-grid"><div class="title">{$title}</div>{#if $subtitle}<div class="subtitle">{$subtitle}</div>{/if}{#if $artists}<div class="artists">{$artists.join(', ')}</div>{/if}</div></div>{/if}
<script>export let response// import List from './list.svelte'import TrackItem from './inspect/track.svelte'import Album from './inspect/album.svelte'import Playlist from './inspect/playlist.svelte'$: type = response.type$: tracks = response.tracks$: items = response.items</script>{#if type === 'album'}<Album {response}/>{:else if tracks} <!--artist-->{#each tracks as track}<TrackItem {track}/>{/each}{:else if items} <!--playlist--><Playlist {response}/>{/if}
<script>export let inspectimport navigate from '../navigate'import Router from './inspect_router.svelte'import { onDestroy } from 'svelte'import { datastore } from '@controlenvy/datastore'let request$: {request = nullrequest = navigate(inspect)}function close() {datastore.delete('/session/services/spotify/search/inspect')}onDestroy(close)</script>{#if request}{#await request}<p>searching...</p>{:then response}<button on:click={close}>back</button><Router {response}/>{/await}{/if}
<script>export let trackimport Track from '../meta/track.svelte'</script><Track {track} let:title let:image_url let:artist_string let:vote let:active><div class="item" class:active><img class="image" src={image_url} on:click={vote}/><div class="info-grid"><div class="title">{title}</div><div class="artists">{artist_string}</div></div></div></Track>
<script>export let responseimport TrackItem from './track.svelte'import navigate from '../../navigate.js'$: items = response.itemslet nextfunction onClick() {next = nullnext = navigate(response.next)}</script>{#each items as item}<TrackItem track={item.track}/>{/each}{#if response.next}{#if next}{#await next}<p>Loading...</p>{:then response}<svelte:self {response}/>{/await}{:else}<button on:click={onClick}>More</button>{/if}{/if}
<script>export let image_urlexport let album_titleexport let track$: artist_string = track.artists.map(e => e.name).join(', ')$: title = track.name</script><div class="item"><img class="image" src={image_url}/><div class="info-grid"><div class="title">{album_title}</div><div class="title">{title}</div><div class="artists">{artist_string}</div></div></div>
<script>export let response$: images = response.images$: image = images[0]$: image_url = image.url$: album_title = response.name$: track_result = response.tracks$: tracks = (track_result || {}).itemsimport Track from './album_track.svelte'</script>{#each tracks as track (track.id)}<Track {image_url} {track} {album_title}/>{/each}
<script>import { datastore } from '@controlenvy/datastore'$: directory_path = datastore.svelte('session/services/spotify/directory_path')$: title = datastore.svelte(`session/services/spotify${$directory_path}/title`, '')$: item_paths_store = datastore.svelte(`session/services/spotify${$directory_path}/items/+`, [], pointer => pointer.path.slice(8))let item_paths = {}$: if ($directory_path) item_paths = {}$: item_paths = $item_paths_storeimport Grid from './grid.svelte'import List from './list.svelte'import Item from './item.svelte'import Icon from '$lib/components/Icon.svelte'let focusedconst focus = ({ detail }) => focused = detail.pathlet type = 'list'const types = {'grid': Grid,'list': List}</script><div class="body-wrapper"><div class="title-row"><Icon name="record"/><h1>{$title}</h1></div><svelte:component this={types[type]} >{#each item_paths as item_path}<Item focused={focused == item_path} path={item_path} on:focusItem={focus}/>{/each}</svelte:component></div>
<script>export let first_trackimport { datastore } from '@controlenvy/datastore'function tab() {datastore.set('/session/services/spotify/search/inspect', null)datastore.set('/session/services/spotify/search/view', 'track')}import Track from '../meta/track.svelte'</script>{#if first_track}<Track track={first_track} let:image_url let:title let:album_title let:artist_string let:vote let:active><div class="top_result_item" class:active on:click={tab}><img src={image_url} on:click={vote}/><p>{title}</p><p>{album_title}</p><p>{artist_string}</p></div></Track>{/if}
<script>export let first_playlistimport { datastore } from '@controlenvy/datastore'function tab() {datastore.set('/session/services/spotify/search/inspect', null)datastore.set('/session/services/spotify/search/view', 'playlist')}function inspect(href) {datastore.set('/session/services/spotify/search/inspect', href + '?limit=20')}import Playlist from '../meta/playlist.svelte'</script>{#if first_playlist}<Playlist playlist={first_playlist} let:image_url let:title let:number_of_tracks let:owner_display_name let:href><div class="top_result_item" on:click={tab}><img src={image_url} on:click={() => inspect(href)} /><p>{title}</p><p>{number_of_tracks} track{number_of_tracks === 1 ? '' : 's'}</p><p>Owned by {owner_display_name}</p></div></Playlist>{/if}
<script>export let first_artistimport { datastore } from '@controlenvy/datastore'function tab() {datastore.set('/session/services/spotify/search/inspect', null)datastore.set('/session/services/spotify/search/view', 'artist')}function inspect(id) {const href = `https://api.spotify.com/v1/artists/${id}/top-tracks?market=ca`datastore.set('/session/services/spotify/search/inspect', href)}import Artist from '../meta/artist.svelte'</script>{#if first_artist}<Artist artist={first_artist} let:image_url let:title let:id><div class="top_result_item" on:click={tab}><img src={image_url} on:click|preventDefault={() => inspect(id)}/><p>{title}</p></div></Artist>{/if}
<script>export let first_albumimport { datastore } from '@controlenvy/datastore'function tab() {datastore.set('/session/services/spotify/search/inspect', null)datastore.set('/session/services/spotify/search/view', 'album')}function inspect(href) {datastore.set('/session/services/spotify/search/inspect', href)}import Album from '../meta/album.svelte'</script>{#if first_album}<Album album={first_album} let:image_url let:title let:artist_string let:album_type let:href><div class="top_result_item" on:click={tab}><img src={image_url} on:click|preventDefault={() => inspect(href)}/><p>{title}</p><p>{artist_string}</p><p>{album_type}</p></div></Album>{/if}
<div class="wrapper"><slot/></div><style type="text/scss">.wrapper {display: flex;flex-wrap: wrap;align-items: center;justify-content: center;width: 100%;max-width: 100%;:global(.item) {position: relative;width: 400px;display: grid;grid-template-areas:'image''info';margin: 1em;:global(.info-grid) {grid-area: info;width: 100%;color: var(--text-primary);text-align: center;:global(.title) {font-weight: 600;}:global(.artists) {color: var(--text-secondary);}}:global(.focused .button) {color: hsla(0, 0%, 100%, 0.9);border: 2px solid hsla(0, 0%, 100%, 0.9);opacity: 1 !important;}:global(.button-wrapper) {position: absolute;bottom: 0;leftt: 12.5%;width: 75%;display: grid;grid-auto-flow: column;justify-items: center;margin: auto auto 3rem auto}:global(.button) {display: grid;align-content: center;justify-content: center;width: 5rem;height: 5rem;margin: 0.5rem;text-align: center;background: hsla(70, 25%, 0%, 0.7);border-radius: 50%;opacity: 0;}:global(.image) {grid-area: image;position: relative;width: 100%;height: auto;}}}</style>
<script>export let tracksimport Track from './track.svelte'import { createEventDispatcher } from 'svelte'import navigate from '../navigate.js'let nextfunction onClick() {next = navigate(tracks.next)}$: items = tracks.itemsconst dispatch = createEventDispatcher()function onInspect({detail}) { dispatch('inspect', detail) }</script>{#each items as item}<Track {item} on:inspect={onInspect}/>{/each}{#if next}{#await next}<p>Loading Tracks</p>{:then info}{#if info.error}<div><p>{info.error.status}</p><p>{info.error.message}</p></div>{:else}<svelte:self tracks={info.tracks}/>{/if}{/await}{:else if tracks.next}<button on:click={onClick}>Load Next Tracks</button>{/if}
<Track {track} let:title let:image_url let:artist_string let:vote let:active><div class:active><img class="image" src={image_url} on:click={vote}/><p>{title}</p><p>{artist_string}</p></div></Track><script>export let item$: track = item.track || {}import Track from '../_explore/meta/track.svelte'</script><style type="text/scss"></style>
<script>export let resultexport let index = 0import Categories from './categories.svelte'import Playlists from './playlists.svelte'import Albums from './albums.svelte'import Tracks from './tracks.svelte'import navigate from '../navigate.js'$: categories = result.categories$: playlists = result.playlists$: albums = result.albums$: tracks = result.tracksimport { onMount, onDestroy, createEventDispatcher } from 'svelte'const dispatch = createEventDispatcher()onMount(() => dispatch('add_or_delete'))onDestroy(() => dispatch('add_or_delete'))function onInspect({ detail }) {const href = detail.hrefdispatch("inspect", { href, index })}</script>{#if tracks}<h2>Tracks</h2><div class="auto-grid"><Tracks {tracks} on:inspect={onInspect}/></div>{/if}{#if categories}<h2>Categories</h2><div class="auto-grid"><Categories {categories} on:inspect={onInspect}/></div>{/if}{#if albums}<h2>Albums</h2><div class="auto-grid"><Albums {albums} on:inspect={onInspect}/></div>{/if}{#if playlists}<h2>Playlists</h2><div class="auto-grid"><Playlists {playlists} on:inspect={onInspect}/></div>{/if}<style type="text/scss">.auto-grid {display: grid;grid-auto-flow: row;grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));grid-gap: 0.5em;:global(img) {max-width: 100%;}}</style>
<script>export let playlistsimport Playlist from './playlist.svelte'import { createEventDispatcher } from 'svelte'import navigate from '../navigate.js'let nextfunction onClick() {next = navigate(playlists.next)}$: items = playlists.itemsconst dispatch = createEventDispatcher()function onInspect({detail}) { dispatch('inspect', detail) }</script>{#each items as playlist}<Playlist {playlist} on:inspect={onInspect}/>{/each}{#if next}{#await next}<p>Loading Playlists</p>{:then info}{#if info.error}<div><p>{info.error.status}</p><p>{info.error.message}</p></div>{:else}<svelte:self playlists={info.playlists}/>{/if}{/await}{:else if playlists.next}<button on:click={onClick}>Load Next Playlists</button>{/if}
<script>export let playlistimport { createEventDispatcher } from 'svelte'$: title = playlist.name$: images = playlist.images || []$: image = images[0] || {}$: image_url = image.url$: owner = playlist.owner$: owner_display_name = owner.display_name$: description = playlist.description$: href = playlist.hrefconst dispatch = createEventDispatcher()function onClick() {dispatch('inspect', { href: href + '?limit=20' })}</script><div><img src={image_url} on:click={onClick}/><p>{title}</p><p>{description}</p><p>{owner_display_name}</p></div>
<script>export let categoryimport { createEventDispatcher } from 'svelte'$: icons = category.icons$: icon = icons[0]$: image_url = icon.url$: href = category.href$: title = category.nameconst dispatch = createEventDispatcher()function onClick() {dispatch('inspect', { href: href + '/playlists' })}</script><div><img src={image_url} on:click={onClick}/><p>{title}</p></div>
<script>export let categoriesimport Category from './category.svelte'import { createEventDispatcher } from 'svelte'import navigate from '../navigate.js'let nextfunction onClick() {next = navigate(categories.next)}console.log(categories)$: items = categories.itemsconst dispatch = createEventDispatcher()function onInspect({detail}) { dispatch('inspect', detail) }</script>{#each items as category}<Category {category} on:inspect={onInspect}/>{/each}{#if next}{#await next}<p>Loading Categories</p>{:then info}{#if info.error}<div><p>{info.error.status}</p><p>{info.error.message}</p></div>{:else}<svelte:self categories={info.categories}/>{/if}{/await}{:else if categories.next}<button on:click={onClick}>Load Next Category</button>{/if}
<script>export let albums</script>
<script>export let album$: console.log(album)// const dispatch = createEventDispatcher()// function onClick(event) {// dispatch('inspect', { href })// }</script><!-- <div><img src={image_url} on:click={onClick}/><p>{title}</p></div> -->
<svelte:head><title>Account</title></svelte:head><Global segment="account">{#if $name}<section><div class="grid"><UserEvents></UserEvents></div><div class="panel"><h2>{$name}</h2><label>Created on:<p>{created_on}</p></label><label>Email:<p>{$email}</p></label><p>Services:</p><div class="panel panel_horizontal"><div><div class="btn button-circle height-min-content" class:glow={spotify_active} on:click|preventDefault={() => authorize('spotify')}><div class="grid-1 btn-rnd"><Icon name="spotify"/></div><div>Spotify</div></div></div><div><div class="btn button-circle height-min-content" class:glow={apple_active} on:click|preventDefault={() => authorize('apple')}><div class="grid-1 btn-rnd"><Icon name="apple"/></div><div>Apple</div></div></div></div><p>Select Active Event:<Select><select on:blur={changeEvent} bind:value={$user_event_id}><option value={undefined}>None</option>{#each $events as event_path}<Event {event_path} is_private={$private_events.includes(event_path)}></Event>{/each}</select></Select></p></div></section>{:else}<div class="panel">{#if $sign_up}<SignUp/>{:else}<LogIn/>{/if}</div>{/if}</Global><script>import { subscribe } from '$lib/subscribe.js'import { unsubscribe } from '$lib/unsubscribe.js'import { datastore } from '@controlenvy/datastore'$: user_id = datastore.svelte('session/user_id')$: name = datastore.svelte(`state/users/${$user_id}/name`, '')$: email = datastore.svelte(`state/users/${$user_id}/email`, '')$: created_at = datastore.svelte(`state/users/${$user_id}/created_at`)$: created_on = (typeof $created_at === 'number') ? new Date($created_at).toLocaleDateString("en") : ''$: user_event_id = datastore.svelte(`state/users/${$user_id}/event_id`)$: spotify_token = datastore.svelte(`state/users/${$user_id}/services/spotify/token`)$: spotify_active = !!$spotify_token$: apple_token = datastore.svelte(`state/users/${$user_id}/services/apple/token`)$: apple_active = !!$apple_token// $: user_event_name = datastore.svelte(`state/users/${user_event_id}/name`, '')$: events = datastore.svelte(`setup/events/+`, [], pointer => pointer.path.slice(6))$: private_events = datastore.svelte(`setup/events/+/private`, [], pointer => pointer.path.slice(6, -8))$: sign_up = datastore.svelte('session/sign_up', true)const changeEvent = ({ target }) => {if ($user_event_id) unsubscribe(`state/events/${$user_event_id}/#`)if (target.value) subscribe(`state/events/${target.value}/#`, true)$user_event_id = target.value}const authorize = (service) => {datastore.write(`/action/services/${service}/auth_user`, (new Date()).getTime() / 1000)}import { onDestroy } from 'svelte'onDestroy(() => {datastore.delete('/session/sign_up')})import Icon from '$lib/components/Icon.svelte'import SignUp from './_sign_up.svelte'import LogIn from './_log_in.svelte'import Global from '$lib/components/global.svelte'import Select from '$lib/components/slots/select.svelte'import Event from './_account/event.svelte'import UserEvents from './_account/user_events.svelte'</script><style lang="scss">.glow {.btn-rnd {box-shadow: 0px 0px 5px 2px var(--accent);}color: var(--accent);}section {display: grid;gap: 2rem;grid-template-columns: 1fr 250px;height: 100%;}label {display: grid;font-weight: 600;p {font-weight: 400;margin: 0;}}.grid {grid-template-rows: max-content 1fr;}</style>
<script>import { onMount } from 'svelte'import { authorization } from '$lib/authorization.js'import { subscribe } from '$lib/subscribe.js'import { datastore } from '@controlenvy/datastore'import { v5 as uuid } from 'uuid'let messages = new Setlet strengthconst auth = (topic, event) => {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log("#auth", {event, pointer, user_uuid, value}, event.pointer.steps[2] == user_uuid && value)if (event.pointer.steps[2] == user_uuid && value) {subscribe(`state/users/${user_uuid}/#`, true)datastore.queue(`/state/users/${user_uuid}/email`, email)datastore.queue(`/state/users/${user_uuid}/name`, name)datastore.queue(`/state/users/${user_uuid}/created_at`, + new Date)}}const create = (topic, event) => {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueif (pointer.steps[2] == user_uuid && value === true) {const password_uuid = uuid(password, namespace)startSession()} else if (pointer.steps[2] == user_uuid && value === false) {messages.add('User already exists, please sign in')messages = messages}}onMount(() => {datastore.subscribe('action/users/+/auth', auth, { immediate: false })datastore.subscribe('action/users/+/create', create, { immediate: false })return () => {datastore.unsubscribe('action/users/+/auth', auth)datastore.unsubscribe('action/users/+/create', create)}})const namespace = uuid('DJ\'IN', '00000000-0000-0000-0000-000000000000')let name = ''let email = ''let email_confirm = ''let password = ''let password_confirm = ''let password_valid = password == password_confirmfunction checkPassword(password, confirm) {password_valid = password == confirmconst invalid_characters = /[ ]/.test(password)const lower_case = /[a-z]/.test(password)const upper_case = /[A-Z]/.test(password)const digit = /\d/.test(password)const symbol = /[!@#$%^&*]/.test(password)if (password == confirm) {messages.delete('Password does not match')} else {messages.add('Password does not match')}if (password.length == 0 && confirm.length == 0) {strength = nullmessages.add('Password empty')messages = messagesreturn} else if (invalid_characters) {strength = nullmessages.add('Invalid characters')messages = messagesreturn}messages.delete('Password empty')messages.delete('Invalid characters')if (password.length > 6) {if (lower_case && upper_case && digit) {strength = 'Medium'}} else if (password.length > 10) {if (lower_case && upper_case && digit && symbol) {strength = 'High'} else if (lower_case && upper_case && digit) {strength = 'Medium'}} else {strength = 'Low'}messages = messages}$: checkPassword(password, password_confirm)let user_uuid = ''function checkEmail(email, confirm) {user_uuid = uuid(email, namespace)//chain.prop('user_uuid', `${user_uuid}`)messages.delete('User already exists, please sign in')if (email == confirm) {messages.delete('Email does not match')} else {messages.add('Email does not match')}messages = messages}$: checkEmail(email, email_confirm)let password_type = "password"const login = () => datastore.set('/session/sign_up', false)const onKeyup = ({key}) => {if (password === password_confirm && key === 'Enter') {submit()}}function submit() {console.log('submit')const password_uuid = uuid(password, namespace)authorization.createAccount(`state/users/${user_uuid}/#`, user_uuid, password_uuid)}function startSession() {console.log('startSession')const password_uuid = uuid(password, namespace)authorization.startSession(`state/users/${user_uuid}/#`, user_uuid, password_uuid)}//import Icon from '$lib/components/Icon.svelte'</script><h2>Create Account</h2><table class="width-100"><tbody class="lgt-txt"><tr><td><label for="email">Email:</label></td><td><input class="width-100" type="text" id="email" bind:value={email} /></td></tr><tr><td><label for="email">Confirm Email:</label></td><td><input class="width-100" type="text" id="email-confirm" bind:value={email_confirm} /></td></tr><tr><td><label for="username">Username:</label></td><td><input class="width-100" type="text" id="username" bind:value={name}/></td></tr><tr><td><label for="password">Password:</label></td><td><input class="width-100" type="password" id="password" bind:value={password} class:matches={!password_valid} on:keyup={onKeyup}/></td></tr><tr><td><label for="password-confirm">Confirm Password:</label></td><td><input class="width-100" type="password" id="password-confirm" bind:value={password_confirm} class:matches={!password_valid} on:keyup={onKeyup}/></td></tr>{#if strength}<tr><td/><td>Password Strength: {strength}</td></tr>{/if}{#each Array.from(messages) as message}<tr><td/><td colspan="2">{message}</td></tr>{/each}</tbody></table><div class="grid grid-responsive-columns-1fr"><div class="btn btn-big width-40" on:click|preventDefault={login}><h3>Log In</h3></div><div class="btn btn-big width-40" on:click|preventDefault={submit}><h3>Sign Up</h3></div></div><style type="text/scss">.matches {border: 1px solid red;}td {&:first-child {width: 40%;}&:last-child {width: 60%;}}</style>
<script>import { datastore } from '@controlenvy/datastore'import { onMount, onDestroy } from 'svelte'import { authorization } from '$lib/authorization.js'import { subscribe } from '$lib/subscribe.js'import { v5 as uuid } from 'uuid'const namespace = uuid('DJ\'IN', '00000000-0000-0000-0000-000000000000')let email = ''let password = ''let user_uuid = ''$: user_uuid = uuid(email, namespace)function login(topic, event) {const { pointer, value } = eventif (event.pointer.steps[2] == user_uuid && value) {subscribe(`state/users/${user_uuid}/#`, true)datastore.set('/session/user_id', user_uuid)}}onMount(() => datastore.subscribe('action/users/+/auth', login, { immediate: false }))onDestroy(() => datastore.unsubscribe('action/users/+/auth', login))let password_type = "password"const togglePasswordType = () => {password_type = password_type == 'password' ? 'text' : 'password'}const signup = () => datastore.set('/session/sign_up', true)const onKeyup = ({key}) => {if (key === 'Enter') submit()}const submit = () => {const password_uuid = uuid(password, namespace)authorization.startSession(`state/users/${user_uuid}/#`, user_uuid, password_uuid)}//import Icon from '$lib/components/Icon.svelte'</script><h2>Log In</h2><table class="width-100"><tbody class="lgt-txt"><tr><td><label for="email">Email:</label></td><td><input class="width-100" type="text" id="email" bind:value={email} /></td></tr><tr><td><label for="password">Password:</label></td><td>{#if password_type == "password"}<input class="width-100" type="password" id="password" bind:value={password} on:keyup={onKeyup}/>{:else}<input class="width-100" type="text" id="password" bind:value={password} on:keyup={onKeyup}/>{/if}</td></tr><tr on:click={togglePasswordType} >{password_type == 'password' ? 'show' : 'hide'} password</tr></tbody></table><div class="grid grid-responsive-columns-1fr"><div class="btn btn-big width-40" on:click|preventDefault={signup}><h3>Sign Up</h3></div><div class="btn btn-big width-40" on:click|preventDefault={submit}><h3>Log In</h3></div></div><style type="text/scss">td {&:first-child {width: 40%;}&:last-child {width: 60%;}}</style>
<script>export let user_idimport { datastore } from '@controlenvy/datastore'import Name from './_event_select/name.svelte'$: event_paths = datastore.svelte(`setup/events/+`, [], pointer => pointer.path.slice(6))$: current_event = datastore.svelte(`state/users/${user_id}/event_id`)const selectEvent = ({ detail: id }) => {$current_event = id}</script><div class="wrapper"><h2>Select Playlist</h2><div class="scroller">{#each $event_paths as event_path }<Name {event_path} on:select={selectEvent}></Name>{:else}<div class="event">No events detected</div>{/each}</div></div><style type="text/scss">.wrapper {display: flex;flex-direction: column;justify-content: center;width: 30%;height: 30%;background: hsla(35, 70%, 0%, 0.5);h2 {text-align: center;}.scroller {overflow-y: scroll;}}h1 {margin-top: auto;margin-bottom: auto;text-align: center;}.digits {display: grid;grid-template-columns: repeat(4, calc(25px * 4));gap: 20px;input {font-size: calc(20px * 2);text-align: center;/* FIXME: Create new variable */background: hsla(228, 54%, 8%, 0.6);border-color: transparent;border-style: solid;border-width: 2px;/*-webkit-backdrop-filter: blur(24px);backdrop-filter: blur(24px);*/transition: border-color 100ms;&:focus {border-color: var(--green);}}}</style>
<div on:click={onClick} class="btn event">{$title}</div><script>export let event_pathimport { datastore } from '@controlenvy/datastore'import { createEventDispatcher } from 'svelte'$: title = datastore.svelte(`setup${event_path}/name`)$: event_id = event_path.split('/').slice(-1)[0]const dispatch = createEventDispatcher()function onClick() {console.log("DISPATCH", event_id)dispatch('select', event_id)}</script><style type="text/scss">.event {margin: 0.5rem;text-align: center;background: hsla(0, 100%, 100%, 0.40);border-radius: 0.5rem;cursor: default;}</style>
<script>import Name from '$lib/components/name.svelte'import Theme from '$lib/components/slots/select.svelte'import { subscribe } from '$lib/subscribe.js'import { unsubscribe } from '$lib/unsubscribe.js'$: user_id = datastore.svelte('session/user_id')$: event_paths = datastore.svelte(`setup/users/${$user_id}/events`, [])$: has_events = $event_paths.length > 0$: user_event_id = datastore.svelte(`state/users/${$user_id}/event_id`)function change({ target }) {const { value } = targetif ($user_event_id) { unsubscribe(`state/events/${$user_event_id}/#`) }if (target.value) { subscribe(`state/events/${value}/#`, true) }$user_event_id = target.value}let tab = datastore.svelte('session/user_events_tab', 'manage')$: loading = ($tab == null) ? null : import(/* @vite-ignore */ `./${$tab}.svelte`)function toggleTab(value) {datastore.set('/session/user_events_tab', value)}import { onDestroy } from 'svelte'onDestroy(() => datastore.set('/session/user_events_tab', null))</script><div class="buttons"><button class:active={tab === 'manage'} on:click={() => toggleTab('manage')}>Manage Events</button><button class:active={tab === 'find'} on:click={() => toggleTab('find')}>Find Events</button>{#if has_events}<Theme><select on:change={change} bind:value={user_event_id}>{#each $event_paths as event_path}<option value={event_path}><Name path={event_path}></Name></option>{/each}</select></Theme>{:else}<p>No Linked Events</p>{/if}</div>{#if loading}{#await loading then { default: component } }<svelte:component this={component}></svelte:component>{/await}{/if}<style lang="scss">.buttons {height: min-content;}</style>
<div><Owned></Owned><Followed></Followed></div><script>import Owned from './manage/owned.svelte'import Followed from './manage/followed.svelte'</script><style lang="scss">div {display: grid;grid-template-columns: 1fr 1fr 1fr 1fr;gap: 1rem;}</style>
<div class="panel">{#if can_create}<button on:click|preventDefault>Create</button>{:else}<p>User event limit reached</p><p>Extend your limit</p>{/if}</div><script>$: user_id = datastore.svelte('session/user_id')$: user_owned_events = datastore.svelte(`setup/users/${user_id}/owned`, [])$: number_of_user_owned_events = $user_owned_events.length$: user_type = datastore.svelte(`setup/users/${user_id}/type`, 'free')$: event_limit = {free: 1,basic: 3,business: 5}[$user_type]$: can_create = number_of_user_owned_events <= event_limit</script>
{$name}<script>export let id$: name = datastore.svelte(`setup/events/${id}/name`)</script>
{#each $followed_events as followed_event}<div class="panel"><FollowedEvent id={followed_event}></FollowedEvent></div>{/each}<script>import FollowedEvent from './followed_event.svelte'$: user_id = datastore.svelte('session/user_id')$: followed_events = datastore.svelte(`setup/users/${user_id}/followed`, [])</script>
<input type="text" bind:value={search}>{#if search.length > 0}{#each $event_paths as event_path}<Filter topic="setup{event_path}/name" filter={search} flags='gi'><div>{event_path}</div></Filter>{/each}{/if}<script>import Filter from '$lib/components/slots/filter.svelte'$: event_paths = datastore.svelte('setup/events/+', [], pointer => pointer.path.slice(6))let search = ''</script><style lang="scss">input {height: min-content;}</style>
<option value={eventIdFromPath(event_path)}>{text}</option><script>export let event_pathexport let is_private$: name = datastore.svelte(`setup${event_path}/name`, '')$: text = $name + is_private ? ' (private)' : ''function eventIdFromPath(path) {return path.split('/').slice(-1)[0]}</script>
<script context="module">export const ssr = falseimport { browser } from '$app/env'import { datastore } from '@controlenvy/datastore'import { ThemeHandler } from '$lib/theme.js'async function deploy() {await import('$lib/api.js')await import('$lib/drivers/spotify.js')await import('$lib/models/user.js')const { session } = await import('$app/stores')datastore.merge('/session', {node_id: '952ede89-4c91-4df7-bdab-c6dda4257abb',ping_interval: 10000})}if (browser) {window.theme = new ThemeHandler()window.datastore = datastoredeploy()}/*** @type {import('@sveltejs/kit').Load}*/export async function load({ page, fetch, session, context }) {datastore.loaded = truereturn {}}import Background from '$lib/components/background/index.svelte'</script><script>import { page } from '$app/stores'$: _page = $page$: if (_page) {datastore.write('/session/page/host', _page.host)datastore.write('/session/page/query', Object.fromEntries(_page.query.entries()))datastore.write('/session/page/path', _page.path)datastore.write('/session/page/origin', location.origin)}</script>{#if datastore.loaded}<Background/><main id="main-scroll"><slot></slot></main>{:else}<Background/><h1>LOADING.window...</h1>{/if}<style global type="text/scss" >main {box-sizing: border-box;width: 100vw;height: calc(100vh - 5rem);margin: 0;padding: 2em;overflow-y: scroll;}:global(body) {background: radial-gradient(circle at top right, hsl(240, 20%, 0%), transparent), radial-gradient(circle at bottom right, hsl(240, 0%, 0%), hsla(240, 100%, 20%, 0.9));margin: 0;}:global(.grid-1) {display: grid;grid-template: 1 1fr / 1 1fr;align-items: center;justify-items: center;}:global(.flex-row) {display: flex;flex-direction: row;align-content: center;justify-content: space-around;}:global(.side-scroll) {display: grid;grid-auto-flow: column;grid-auto-columns: 80vw;width: 110vw;overflow-x: scroll;scroll-snap-type: x mandatory;justify-items: center;margin-left: -10vw;padding-left: 5vw;&::before {content: '';width: 20vw;}&::after {content: '';width: 20vw;}}:global(.side-scroll-child) {width: 80vw;justify-self: center;scroll-snap-align: center;align-content: center;display: grid;}:global(.side-scroll-panel) {background: var(--bg-shade);padding: 2rem;border-radius: 0.5rem;opacity: 0.6;transform: scale(0.9);height: 70vh;overflow-y: scroll;}@media (prefers-reduced-motion: no-preference) {:global(.side-scroll-panel) {transition: opacity 0.5s ease, transform 0.5s ease;}}:global(.side-scroll-panel-transition) {opacity: 1;transform: none;}:global(.fill-w) {width: 100%;}:global(.fill-h) {height: 100%;}:global(.fill) {width: 100%;height: 100%;}:global(.title-big) {font-weight: 700;font-size: 2.8em;text-transform: uppercase;}:global(.lgt-txt) {color: hsla(100, 25%, 80%);}:global(.drk-txt) {color: hsla(100, 0%, 40%);}:global(h1) {color: hsla(100, 25%, 80%);}:global(h2) {color: hsla(100, 25%, 80%);}:global(h3) {color: hsla(100, 25%, 80%);}:global(p) {color: hsla(100, 25%, 80%);}:global(.panel) {padding: 1rem;background: var(--bg-shade);border-radius: 0.5rem;}:global(svg) {width: auto;height: 2rem;fill: var(--text-primary);}:global(.opaque-8) {opacity: 0.8;}:global(.panel-opaque) {padding: 2rem;background: var(--bg-shade);border-radius: 0.5rem;opacity: 0.8;}:global(.pad-top-05em) {padding-top: 0.5em;}:global(.pad-btm-05em) {padding-bottom: 0.5em;}:global(.pad-top-btm-05em) {padding-top: 0.5em;padding-bottom: 0.5em;}:global(.ctr) {margin: auto auto;text-align: center;}:global(.btn) {transition: all .2s ease-in-out;&:active {transform: scale(0.9) !important;}&:hover {cursor: pointer;transform: scale(1.1);}}:global(.grid-flow-column) {display: grid;grid-auto-flow: column;}:global(.height-min-content) {height: min-content;}:global(.grid-align-center) {align-items: center;}:global(.grid-justify-center) {justify-items: center;}:global(.width-100) {width: 100%;}:global(.width-80) {width: 80%;}:global(.width-60) {width: 60%;}:global(.width-40) {width: 40%;}.btn-bg {background-color: hsla(0, 0%, 45%, 0.5);}:global(.btn-bg) {@extend .btn-bg;}:global(.text-ctr) {text-align: center;}:global(.text-rgt) {text-align: right;}:global(.text-lft) {text-align: left;}:global(.btn-big) {@extend .btn-bg;margin: 1rem auto;color: var(--text-primary);text-align: center;border: none;border-radius: 1rem;:global(h2), :global(h3) {padding: 0.2em;font-weight: bold;text-align: center;}}:global(.grid) {display: grid;}:global(.grid-responsive-columns-1fr) {grid-template-columns: repeat(auto-fit, minmax(1rem, 1fr));}:global(.button-circle) {display: grid;justify-content: center;text-align: center;width: max-content;grid-template-rows: min-content max-content;}:global(.panel_horizontal) {display: grid;grid-auto-flow: column;gap: 3rem;grid-auto-columns: min-content;}:global(.btn-rnd) {@extend .btn-bg;margin: 1rem auto;color: var(--text-primary);text-align: center;border: none;width: 3em;height: 3em;border-radius: 50%;}:global(.title-row) {display: grid;grid-template: 4rem / 4rem auto;align-items: center;:global(svg) {justify-self: center;z-index: 200;}:global(h1) {margin: 0;z-index: 200;}}</style>
<script>export let status;export let error;import { goto } from '$app/navigation'$: loaded = datastore.loadedconst dev = process.env.NODE_ENV === 'development';</script><svelte:head><title>{status}</title></svelte:head><div>{#if !loaded}<h1>Initializing...</h1>{:else}<h1>{status}</h1>{#if error}<p>{error.message}</p><button class="btn btn-big" on:click={() => goto('/')}>Home</button>{#if dev && error.stack}<pre>{error.stack}</pre>{/if}{/if}{/if}</div><!-- <style lang="scss">main {display: grid;grid-template: 1 fr / 1 1fr;align-items: center;justify-items: center;width: 100%;height: 100%;}h1 {margin-top: auto;margin-bottom: auto;text-align: center;}</style> -->
// WebSocket Constantsconst WEBSOCKET_READY_STATE = ['Connecting', 'Connected', 'Closing', 'Closed']const WEBSOCKET_READY_STATE_CONNECTED = 1import lodash from 'lodash'import { datastore } from '@controlenvy/datastore'export const QUALITY_DISCONNECTED = 0export const QUALITY_POOR = 1export const QUALITY_FAIR = 2export const QUALITY_GOOD = 3export const QUALITY_EXCELLENT = 4// TODO: Implement this fullyexport const quality = {DISCONNECTED: QUALITY_DISCONNECTED,POOR: QUALITY_POOR,FAIR: QUALITY_FAIR,GOOD: QUALITY_GOOD,EXCELLENT: QUALITY_EXCELLENT}export const connections = {server: null}let clientside = falselet global_window// FIXME: do we need this?if (typeof window !== 'undefined') {clientside = trueglobal_window = window} else {global_window = global}global_window.logger = {debug(...messages) {if (clientside) console.log(...messages) // eslint-disable-line},log(...messages) {if (clientside) console.log(...messages) // eslint-disable-line}}global_window.connections = connectionsglobal_window.ping_adjust = 0if (typeof WebSocket === 'undefined') {class WSShim {constructor() {}open() {}}global_window.WebSocket = WSShim}// Connectionclass WSBase {constructor() {this.events = {open: [],ready: [],message: [],close: [],error: []}this.ping_id = 0}open() {this.websocket.onopen = event => {lodash.each(this.events.open, callback => {callback(event)})}this.websocket.onmessage = event => {lodash.each(this.events.message, function(callback) {callback(event)})}this.websocket.onclose = event => {lodash.each(this.events.close, function(callback) {callback(event)})}this.websocket.onerror = event => {lodash.each(this.events.error, function(callback) {callback(event)})}}close() {this.websocket = nullthis.events = {open: [],message: [event => this.onping(event)],close: [],error: []}}ping() {setTimeout(() => {const node_id = datastore.get('/session/local/node_id')const node_path = `/nodes/${node_id}`const base_path = `${node_path}/pings/${this.ping_id++}`this.send({c: node_path,o: 'w',p: `${base_path}/started_at`,v: Date.now()})this.ping()}, datastore.get('/session/local/ping_interval'))}on(eventName, callback) {switch (eventName) {case 'open':case 'ready':case 'message':case 'close':case 'error':this.events[eventName].push(callback)}}off(eventName, callback) {switch (eventName) {case 'open':case 'ready':case 'message':case 'close':case 'error':breakdefault:return}const index = this.events[eventName].indexOf(callback)this.events[eventName].splice(index, 1)}get readyState() {return this.websocket.readyState}get connection() {return WEBSOCKET_READY_STATE[this.websocket.readyState]}}// Connectionclass WS extends WSBase {constructor(options = {}) {super()options = Object.assign({ connection: 'server', overwrite: false }, options)this.max_attempts = options.attempts || 0this.attempts = 0this.host = (() => {let host = ''if (options.host != null) {host = options.protocol || 'ws://'host += options.hostif (options.port) {host += ':' + options.port}}return host})()datastore.set('/session/local/authenticated', false)this.on('open', () => {this.attempts = 0console.log('Connection opened successfully', this.host, options)if (options.connection) {if (connections[options.connection]) {if (!options.overwrite) {return connections[options.connection]} else {connections[options.connection].overwritten = trueconnections[options.connection].close()}}connections[options.connection] = this}})this.reconnect = lodash.debounce(this.open.bind(this), 1000, {leading: false,trailing: true})this.on('ready', this.ping.bind(this))// this.on('message', event => logger.debug('onmessage', event))this.on('close', this.reconnect)this.open()}open() {if (this.overwritten) {// eslint-disable-next-line no-consoleconsole.warn('Connection overwritten. Not attempting to reconnect.')return}if (this.max_attempts > 0 && this.attempts >= this.max_attempts) {console.warn('Maximum number of connection attempts hit. No longer attempting to connect.')return}this.attempts += 1console.log('attempting to connect to', this.host, this.attempts)this.websocket = new WebSocket(this.host)typeof window !== 'undefined' &&window.addEventListener('beforeunload',() => {this.websocket.close()},false)super.open()}close() {this.events.close[0] = null // prevent automatic reconnectthis.websocket.close()super.close()}send(payload) {if (this.websocket == null) {return}const message = JSON.stringify(payload)if (this.websocket.readyState !== WEBSOCKET_READY_STATE_CONNECTED) {const callback = (called = false) => () => {if (!called) {this.send(payload)called = true}}this.on('open', callback())} else {this.websocket.send(message)}}destroy() {this.close()}get readyState() {return this.websocket.readyState}get connection() {return WEBSOCKET_READY_STATE[this.websocket.readyState]}}export { WS }
let global_windowif (typeof window !== 'undefined') {global_window = window} else {global_window = global}export const unsubscribe = (topic) => {// console.log('subscribe()', 'topic:', topic)let operation = 'u'let payloadpayload = {o: operation,t: topic}const ws = global_window.connections['node']if (ws) {ws.send(payload)}}
class ThemeHandler {constructor() {this.root = document.querySelector(':root')this.setTheme()}setTheme() {/* Base */this.root.classList.add('theme-light')this.root.style.setProperty('--text-primary', 'hsla(100, 25%, 100%, 0.8)')this.root.style.setProperty('--text-secondary', 'hsla(100, 25%, 100%, 0.4)')this.root.style.setProperty('--accent', 'hsla(50, 100%, 70%, 1)')this.root.style.setProperty('--bg-shade', 'hsla(0, 0%, 40%, 0.40)')this.root.style.setProperty('--bg-button', 'hsla(0, 0%, 45%, 0.5)')/* Semantic */}}export { ThemeHandler }
let global_windowif (typeof window !== 'undefined') {global_window = window} else {global_window = global}export const subscribe = (topic, initialize) => {// console.log('subscribe()', 'topic:', topic)let operation = 's'let payloadpayload = {o: operation,t: topic}if (initialize) { payload.i = true}const ws = global_window.connections['node']if (ws) {ws.send(payload)}}
let global_windowif (typeof window !== 'undefined') {global_window = window} else {global_window = global}export const send = (value, path) => {console.log('send()', 'value:', value, 'path:', path)let operation = 'm'let payloadif (value == null) {operation = 'd'}payload = {o: operation,p: path,v: value}const ws = global_window.connections['node']if (ws) {ws.send(payload)}}
import { datastore } from '@controlenvy/datastore'import { subscribe } from '../subscribe'import { unsubscribe } from '../unsubscribe'class User {constructor() {this.startSubscriptions()}startSubscriptions() {datastore.subscribe(`session/user_id`, this.onSessionUser.bind(this))}onSessionUser(topic, pointer, value) {console.log(`User ${value || 'logged out'}`)if (this.user == value) returndatastore.unsubscribe(`state/users/${this.user}/#`, this)if (value) {datastore.subscribe(`state/users/${value}/#`, this.onStateUser.bind(this))}this.user = value}onStateUser(topic, pointer, value) {const leaf = pointer.leafswitch (leaf) {case 'event_id':this.onEventID(pointer, value)}}onEventID(pointer, value) {if (value) {subscribe(`state/events/${value}/#`, true)} else {unsubscribe(`state/events/${value}/#`)}}}const user = new User()export { user }
import { datastore } from '@controlenvy/datastore'import querystring from 'query-string'import { EventAuth } from './spotify/event_auth.js'import { UserAuth } from './spotify/user_auth.js'// import { v5 as uuid } from 'uuid'// const NULL_UUID = '00000000-0000-0000-0000-000000000000'class Base {constructor() {Object.defineProperties(this, {directory_paths: {value: [null],writable: true},directory_idx: {value: 0,writable: true},directory_path: {value: null,writable: true},token: {value: undefined,writable: true},commmands: {value: []}})this.startSubscriptions()}startSubscriptions() {datastore.subscribe(`action/services/spotify/+`, this.onAction.bind(this))datastore.subscribe(`state/services/spotify/code`, this, this.onCode.bind(this))datastore.subscribe(`state/users/+/services/spotify/token`, this.onToken.bind(this))datastore.subscribe(`session/user_id`, this.onUser.bind(this))datastore.subscribe(`q/action/services/spotify/directories/+/#`, this.onExplore.bind(this))datastore.subscribe(`q/action/users/+/events/+/items/+/vote`, this.onVote.bind(this))}onAction(topic, event) {if (event.type !== '=') returnconst { pointer, value } = eventconsole.log(topic, pointer.path, pointer.leaf, value)if (value == null) returnswitch (pointer.leaf) {case 'auth_user':this.authorizeUser()breakcase 'auth_event':this.authorizeEvent()break}}onVote(topic, event) {if (event.type !== '=') returnconst { pointer, value } = eventif (value == null) returnconst user = pointer.steps[3]const djin_event = pointer.steps[5]const isrc = pointer.steps[7]this.sendHttp('https://api.spotify.com/v1/search?' + querystring.stringify({ q: `isrc:${isrc}`, type: "track", limit: 1, market: "ca" }),{headers: {'Authorization': `Bearer ${this.token}`}},'onVote',this.decodeVote(user, djin_event, isrc).bind(this))}decodeVote(user, event, isrc) {return (data) => {console.log({ user, event, isrc, data })const track_items = data.tracksconst tracks = track_items.itemsconst track = tracks[0]const spotify_id = track.uriconst name = track.namedatastore.queue(`/q/state/items/${isrc}/name`, name)datastore.queue(`/q/state/items/${isrc}/spotfiy_id`, spotify_id)datastore.queue(`/q/state/items/${isrc}/isrc`, isrc)datastore.queue(`/q/state/users/${user}/events/${event}/items/${isrc}/vote`, +new Date())}}handleError(error, caller) {console.error(`problem with request in ${caller}: ${error.message}`)}sendHttp(url, options, caller, callback) {fetch(url, options).then(data => data.json()).then(r => callback(r)).catch(e => this.handleError(e, caller))}onUser(topic, event) {if (event.type !== '=') returnconst { value } = eventif (value == null) returnconsole.log(`User Accepted ${value}`)this.user = valueif (this.user && this.token) this.saveToken(this.user, this.token)}onExplore(topic, event) {if (event.type !== '=') returnconst { pointer, value } = eventif (value == null) returnconst type_pointer = pointer.changeBranch(pointer.sliceBranch(0, 4)).dequeue().changeRoot('session').changeLeaf('type')const type = datastore.read(type_pointer.path)if (pointer.leaf == 'back') {return this.back()}switch (pointer.branch.length) {case 3:switch (pointer.leaf) {case 'start':this.getCategories()break}breakcase 5:console.log(type)switch (type) {case 'categories':this.exploreCategories(pointer)breakcase 'playlists':this.exploreCategory(pointer)break}breakdefault:this.getCategories()break}}exploreCategories(pointer) {switch (pointer.leaf) {case 'browse':this.getCategory(pointer)break}}explorePlaylist(pointer) {switch (pointer.leaf) {case 'browse':this.getPlaylist(pointer)break}}back() {const current_directory_path = this.directory_paththis.lastDirectory()console.log(`back ${current_directory_path} -> ${this.directory_path}`)datastore.write('/session/services/spotify/directory_path', this.directory_path)datastore.destroy(`/session/services/spotify${current_directory_path}`)}getCategories() {this.sendHttp('https://api.spotify.com/v1/browse/categories',{headers: {'Authorization': `Bearer ${this.token}`}},'getCategories',this.decodeCategories.bind(this))}decodeCategories(data) {console.log('decodeCategories', data)this.nextDirectory()datastore.write(`/session/services/spotify${this.directory_path}/title`, 'Categories')datastore.write(`/session/services/spotify${this.directory_path}/type`, 'categories')const base_path = `/session/services/spotify${this.directory_path}/items`if (data.categories && data.categories.items) {data.categories.items.forEach((item, idx) => {console.log(item)const record = {}const image_details = item.icons[0]record['url'] = item.hrefrecord['image_url'] = image_details.urlrecord['max_dimension'] = Math.min(image_details.height, image_details.width)record['id'] = item.idrecord['title'] = item.nameconst item_path = `${base_path}/${idx + 1}`datastore.write(item_path, record)})}datastore.write('/session/services/spotify/directory_path', this.directory_path)}getCategory(pointer) {pointer = pointer.dequeue().changeRoot('session').changeLeaf('id')const id = datastore.read(pointer.path)if (!id) returnthis.sendHttp(`https://api.spotify.com/v1/browse/categories/${id}/playlists`,{headers: {'Authorization': `Bearer ${this.token}`}},'getCategories',this.decodeCategory.bind(this))}decodeCategory(data) {console.log('decodeCategory', data)this.nextDirectory()datastore.write(`/session/services/spotify${this.directory_path}/title`, 'Playlists')datastore.write(`/session/services/spotify${this.directory_path}/type`, 'playlists')const base_path = `/session/services/spotify${this.directory_path}/items`if (data.playlists && data.playlists.items) {data.playlists.items.forEach((item, idx) => {const image_details = item.images[0]const record = {'title': item.name,'type': item.type,'id': item.id,'image_url': image_details.url,'url': item.href}const item_path = `${base_path}/${idx + 1}`datastore.write(item_path, record)})}datastore.write('/session/services/spotify/directory_path', this.directory_path)}getPlaylist(pointer) {pointer = pointer.dequeue().changeRoot('session').changeLeaf('id')const id = datastore.read(pointer.path)if (!id) returnthis.sendHttp(`https://api.spotify.com/v1/playlists/${id}/tracks`,{headers: {'Authorization': `Bearer ${this.token}`}},'getPlaylists',this.decodePlaylist.bind(this))}decodePlaylist(data) {console.log('decodePlaylist', data)this.nextDirectory()datastore.write(`/session/services/spotify${this.directory_path}/title`, 'Tracks')datastore.write(`/session/services/spotify${this.directory_path}/type`, 'tracks')const base_path = `/session/services/spotify${this.directory_path}/items`if (data.items) {data.items.forEach((item, idx) => {const track = item.trackconst record = {}if (!track) returnlet artistslet albumlet image_detailsswitch (track.type) {case 'track':artists = []track.artists.forEach(artist => artists.push(artist.name))album = track.albumimage_details = this.findSuitableImage(album.images)record['id'] = track.idrecord['uri'] = track.urirecord['image_url'] = image_details.urlrecord['max_dimension'] = Math.min(image_details.height, image_details.width)record['title'] = track.namerecord['subtitle'] = album.namerecord['artists'] = artistsrecord['restricted'] = track.restrictions ? true : falserecord['explicit'] = track.explicitbreakdefault:return}const item_path = `${base_path}/${idx + 1}`datastore.write(item_path, record)})}datastore.write('/session/services/spotify/directory_path', this.directory_path)}findSuitableImage(images) {if (!images) { return null }if (images.length == 1) { return images[0] }let image = images.sort(image => image.width).reverse().find(image => image.width >= 200)if (image) { return image }return images.slice(-1)[0]}nextDirectory() {this.directory_idx = this.directory_idx + 1this.directory_path = `/directories/${this.directory_idx}`}lastDirectory() {if (this.directory_idx > 0) {this.directory_idx = this.directory_idx - 1if (this.directory_idx > 0) {this.directory_path = `/directories/${this.directory_idx}`} else {this.directory_path = null}}}resetDirectory() {this.directory_idx = 0}}const Spotify = EventAuth(UserAuth(Base))const spotify = new Spotify()export { spotify }
import { datastore } from '@controlenvy/datastore'import querystring from 'query-string'// import { v5 as uuid } from 'uuid'// const NULL_UUID = '00000000-0000-0000-0000-000000000000'const UserAuth = (Base) =>class extends Base {onToken(topic, event) {if (event.type !== '=') returnconst { value } = eventif (value == null) returnconsole.log(`Token Accepted ${value}`)this.token = valueif (this.user && this.token) this.saveToken(this.user, this.token)}saveToken(user, token) {datastore.write(`/q/state/users/${user}/drivers/spotify/client/token`, token)datastore.write(`/action/users/${user}/accept`, +new Date)}authorizeUser() {var route = datastore.read('/session/page/origin')if (/localhost:/.test(route)) {route = route.match(/http:\/\/.*/)[0]} else {route = route.match(/https:\/\/(www.)?([^:]*)/)[2]}const uri = 'https://accounts.spotify.com/authorize?' +querystring.stringify({'client_id': 'fe00993ff93e4827804af16e1de312de','response_type': 'token','scope': ['user-read-email','user-read-private','user-library-read','playlist-read-collaborative','playlist-read-private','user-read-recently-played','user-top-read',],'redirect_uri': `${route}/oauth/client`}).split('%3A').join(':')datastore.write('/session/redirect', uri)}}export { UserAuth }
import querystring from 'query-string'const EventAuth = (Base) =>class extends Base {onCode(topic, pointer, value) {if (!value) returnconsole.log(`Code Received ${value}`)this.code = valuethis.authenticate()}authenticate() {const params = {"grant_type": "authorization_code","code": this.code,"redirect_uri": this.getRoute()}var formBody = [];for (var property in params) {var encodedKey = encodeURIComponent(property);var encodedValue = encodeURIComponent(params[property]);formBody.push(encodedKey + "=" + encodedValue);}formBody = formBody.join("&");this.sendHttp(`https://accounts.spotify.com/api/token`,{method: "POST",headers: {'Authorization': `Basic ZmUwMDk5M2ZmOTNlNDgyNzgwNGFmMTZlMWRlMzEyZGU6ODQ1NzQzNzhkMDg2NDQwZGI2MDczNmRiN2MxNzc1Mzg=`,'Content-Type': 'application/x-www-form-urlencoded'},body: formBody},'authenticate',this.decodeAuthenticate.bind(this))}decodeAuthenticate(data) {const user = this.userthis.token = data.access_tokendatastore.write(`/state/users/${user}/services/spotify/admin/token`, data.access_token)datastore.write(`/state/users/${user}/services/spotify/admin/refresh_token`, data.refresh_token)datastore.write(`/state/users/${user}/services/spotify/admin/expiry`, Math.round(+new Date / 1000) + data.expires_in)this.resolve()}resolve() {this.sendHttp(`https://api.spotify.com/v1/me`,{headers: {'Authorization': `Bearer ${this.token}`}},'resolve',this.decodeResolve.bind(this))}decodeResolve(data) {datastore.write(`/state/users/${this.user}/services/spotify/admin/id`, data.id)}authorizeEvent() {var route = datastore.read('/session/page/origin')if (/localhost:/.test(route)) {route = route.match(/http:\/\/.*/)[0]} else {route = route.match(/https:\/\/(www.)?([^:]*)/)[2]}const uri = 'https://accounts.spotify.com/authorize?' +querystring.stringify({'client_id': 'fe00993ff93e4827804af16e1de312de','response_type': 'code','scope': ['user-read-email','user-read-private','user-read-email','user-read-private','user-library-read','playlist-read-collaborative','playlist-read-private','user-read-recently-played','user-top-read','playlist-modify-public'].join(' '),'redirect_uri': `${route}/oauth/admin`}).split('%3A').join(':')datastore.write('/session/redirect', uri)}}export { EventAuth }
<fieldset class:invalid><input type="text" class="input_text" {value} {inputmode} {pattern} on:input={onInput} on:focus={onFocus} on:blur={onBlur}><hr class="input_underline" class:underline></fieldset><script>export let pathexport let placeholder = ''export let inputmode = 'text'export let pattern = nullexport let focused = falseexport let validate = () => trueimport { datastore } from "@controlenvy/datastore";$: text = datastore.svelte(path.slice(1), placeholder)let value = ''$: if (!focused) { value = $text }let invalid = falseconst onInput = ({ target }) => {const { value } = targetif (validate(value)) {$text = valueinvalid = false} else {invalid = true}}const onFocus = () => {focused = true}const onBlur = () => {focused = false}$: underline = focused</script><style lang="scss">fieldset {position: relative;padding: 0;border: none;border-bottom: 1px solid var(--text-primary);}input[type=text] {background: transparent;font-family: proxima-nova,sans-serif;outline: none;padding: 0.375rem 0.5rem;line-height: 1.25rem;font-size: 2rem;border-color: transparent;width: 100%;height: 100%;text-overflow: ellipsis;margin: 0;color: var(--text-primary);&.focus, &.active {background: transparent;outline: none;}}.input_underline {position: absolute;z-index: 10;width: 100%;bottom: -0.625rem;border: 0.0625rem solid var(--accent);transform: scaleX(0) ;transform-origin: left center;transition: transform 0.2s ease-in;&.underline {transform: scaleX(1) translateY(-0.0625rem);}}</style>
<script>import { onMount, onDestroy, afterUpdate, tick } from 'svelte'let interrupt = false, expanded = falselet wrapper, themed_select, themed_dropdown, select_element, selectedafterUpdate(async () => {if (wrapper) {select_element = wrapper.getElementsByTagName("SELECT")[0]if (select_element && select_element.options[select_element.selectedIndex]) {selected = select_element.options[select_element.selectedIndex].innerHTML} else {selected = 'None'}}})const select = ({ srcElement }) => {const html = srcElement.innerHTMLfor (var i = 0; i < select_element.options.length; i++) {if (select_element.options[i].innerHTML == html) {select_element.selectedIndex = i}}select_element.dispatchEvent(new Event('change'))select_element.dispatchEvent(new Event('blur'))expanded = !expandedwrapper = wrapper}let positional_class_v = 'top'let positional_class_h = ''let max_heightconst evaluateLocation = () => {if (themed_dropdown) {const { top, bottom, width, right } = themed_dropdown.getBoundingClientRect()if (positional_class_v == "top" && bottom > root_height) {positional_class_v = "bottom"} else if (positional_class_v == "bottom" && top < 0) {positional_class_v = "top"}if (right >= root_width) {positional_class_h = 'left'} else if (positional_class_h != 'left' || (width + right < root_width)) {positional_class_h = ''}}}let main, root_height, root_widthfunction evaluateMain() {main = document.querySelector('main')const bounds = main.getBoundingClientRect()root_width = bounds.rightroot_height = bounds.bottommax_height = `${root_height * 0.5}px`}// FIXME: Svelte 3.8.1 `bind:`` does not work without at least one Svelte updatelet fixme = falselet observeronMount(() => {fixme = trueevaluateMain()const options = {root: main}observer = new IntersectionObserver(handleIntersect, options)})function handleIntersect(entries) {entries.forEach(entry => {const { top, bottom } = entry.boundingClientRectif (positional_class_v == "top" && top < 0) {positional_class_v = "bottom"} else if (positional_class_v == "bottom" && bottom > root_height) {positional_class_v = "top"}})}$: if (themed_dropdown) {observer.observe(themed_dropdown)evaluateLocation()}async function toggleSelect() {interrupt = !interruptexpanded = !expandedawait tick()evaluateLocation()}const closeSelect = () => {if (interrupt) {interrupt = falsereturn}expanded = false}</script><svelte:window on:click={closeSelect} on:resize={evaluateLocation} on:resize={evaluateMain}/><div bind:this={wrapper} style="display: none;">{fixme}<slot /></div><div bind:this={themed_select} class="themed-select" class:select-active={expanded} >{#if select_element}<div class="select-selected box_shadow_inset rounded" class:select-arrow-active={expanded} on:click={toggleSelect}>{@html select_element.options[select_element.selectedIndex].innerHTML || ' '}</div><div bind:this={themed_dropdown} class="select-items bg_glass rounded {positional_class_v} {positional_class_h}" class:select-hide={!expanded} style="max-height: {max_height};">{#each select_element.children as child, child_idx (child)}{#if child.nodeName == 'OPTION'}<div class="select-option" class:same-as-selected={selected == child.innerHTML} on:click|stopPropagation={select}>{@html child.innerHTML}</div>{:else if child.nodeName == 'OPTGROUP'}<div class="select-optgroup">{child.attributes['label'].value}</div><div class="select-optgroup-border"></div>{#each (child.children || []) as grandchild (grandchild)}<div class="select-option" class:same-as-selected={selected == grandchild.innerHTML} on:click|stopPropagation={select}>{@html grandchild.innerHTML}</div>{/each}{/if}{/each}</div>{/if}</div><style type="text/scss">:global(.themed-select) {position: relative;z-index: 10;&.select-active {z-index: 11 !important;}grid-column: 2 / span 2;text-transform: capitalize;cursor: pointer;:global(select) {display: none;}:global(.select-selected) {display: grid;align-items: center;min-height: 2.5rem;padding: 0 1.75rem 0 0.5rem;border: none !important;cursor: pointer;background: var(--bg-shade);}:global(.select-selected:after) {position: absolute;top: 1.125rem;right: 0.5rem;width: 0;height: 0;border: 0.25rem solid transparent;border-color: white transparent transparent transparent;content: "";}:global(.select-selected.select-arrow-active:after) {top: 0.75rem;border-color: transparent transparent white transparent;}:global(.select-items div) {padding: 0.5rem;cursor: pointer;}:global(.select-option) {padding-left: 1.5rem !important;font-size: 0.875rem;white-space: nowrap;user-select: none;&:hover {opacity: 0.75;}}:global(.top) {top: 100%;border-radius: 0px 0px 0.5em 0.5em;}:global(.left) {left: -100% !important;}:global(.bottom) {bottom: 100%;border-radius: 0.5em 0.5em 0px 0px;}:global(.select-items) {position: absolute;right: 0;left: 0;z-index: 101;background: black;min-width: max-content;overflow-y: scroll;scrollbar-width: none;&::-webkit-scrollbar {display: none;}}:global(.select-hide) {display: none;}:global(.same-as-selected) {color: white !important;background-image: green;&:hover {opacity: 1;}:global(span) {color: darkgreen !important;}}}</style>
{#if regex.test($value)}<slot></slot>{/if}<script>export let topicexport let filterexport let flags$: value = datastore.svelte(topic)$: regex = new RegExp(filter, flags)</script>
<form><slot name="before"></slot><label class="switch" transition:fade="{{ delay: 100, duration: 200 }}"><input type="checkbox" bind:checked on:click={onClick}><span class="slider"></span></label><slot name="after"></slot></form><script>export let pathexport let attributeexport let queue = trueexport let root = "setup"export let labelimport { fade } from 'svelte/transition'import { datastore } from '@controlenvy/datastore'let focused = false$: value = datastore.svelte(`setup${path}/${attribute}`, false)// force q_value to update from the parent if the path changeslet checkedconst requeue = (path) => checked = $value$: requeue(path)$: requeue($value)const onClick = () => {if (queue) {datastore.queue(`/${root}${path}/${attribute}`, !checked)} else {datastore.set(`/${root}${path}/${attribute}`, !checked)}}</script><style lang="scss">@mixin inset {background: var(--shadow-tertiary);box-shadow: inset 0 0.25rem 0.5rem var(--shadow), inset 0 0.125rem 0.25rem var(--shadow-tertiary), 0 0.0375rem 0 var(--highlight-secondary);}form {display: grid;grid-template-columns: max-content max-content;align-items: center;gap: 1rem;}.switch {// justify-self: end;position: relative;display: inline-block;width: var(--track-width);height: var(--track-height);}.switch input {opacity: 0;width: 0;height: 0;}.slider {position: absolute;cursor: pointer;top: 0;left: 0;right: 0;bottom: 0;border-radius: 2rem;overflow: hidden;-webkit-transition: .4s;transition: .4s;@include inset;}.slider:before {position: absolute;content: "";border-radius: 2rem;height: var(--thumb-size);width: var(--thumb-size);bottom: 0.125rem;left: 0.125rem;background: linear-gradient(180deg, var(--background-400), var(--icon-primary));box-shadow: 0 0.25rem 0.5rem var(--shadow), 0 0.125rem 0.25rem var(--shadow-tertiary);-webkit-transition: .4s;transition: .4s;}/*.slider:after {position: absolute;content: "";border-radius: 2rem;height: calc(var(--thumb-size) - 4px);width: calc(var(--thumb-size) - 4px);bottom: 0.25rem;left: 0.25rem;background: linear-gradient(180deg, var(--background-200), var(--icon-primary));box-shadow: 0 0.25rem 0.5rem var(--shadow), 0 0.125rem 0.25rem var(--shadow-tertiary);-webkit-transition: .4s;transition: .4s;}*/input:checked + .slider {background: linear-gradient(0deg, var(--accent-secondary), var(--accent));box-shadow: none;}input:checked + .slider:before, input:checked + .slider:after {-webkit-transform: translateX(1.25rem);-ms-transform: translateX(1.25rem);transform: translateX(1.25rem);}.slider.round:before {border-radius: 50%;}</style>
<script>import { goto } from '$app/navigation'const deleteCookie = name => {document.cookie = name+'=; Max-Age=-99999999;'}const confirm = () => {deleteCookie(name)datastore.write('/state/users', null)datastore.write('/state/events', null)datastore.delete('/session/user_id')datastore.delete('/session/prompt')goto('/')}const cancel = () => {datastore.delete('/session/prompt')}</script><h3>Logout?</h3><div class="grid grid-responsive-columns-1fr width-100"><button class="btn btn-big" on:click|preventDefault={confirm}><h2>Confirm</h2></button><button class="btn btn-big" on:click|preventDefault={cancel}><h2>Cancel</h2></button></div>
<script>export let segmentimport { goto } from '$app/navigation'$: user_id = datastore.svelte('session/user_id')const toggleLogoutPrompt = () => {datastore.set('/session/prompt', 'log-out')}import Icon from '$lib/components/Icon.svelte'</script><li on:click={() => goto('/client/')}><span aria-current='{segment === undefined ? "page" : undefined}' class="btn"><Icon name="igloo"/>Home</span></li><li on:click={() => goto('/client/info')}><span aria-current='{segment === "info" ? "page" : undefined}' class="btn"><Icon name="info"/>Djinlist</span></li>{#if $user_id}<li on:click={toggleLogoutPrompt}><span class="btn"><Icon name="sign-out"/>Log Out</span></li><li on:click={() => goto('/client/tallies')}><span aria-current='{segment === "tallies" ? "page" : undefined}' class="btn"><Icon name="chart"/>Charts</span></li><li on:click={() => goto('/client/account')}><span aria-current='{segment === "account" ? "page" : undefined}' class="btn"><Icon name="user"/>User</span></li><li on:click={() => goto('/client/explore')}><span aria-current='{segment === "explore" ? "page" : undefined}' class="btn"><Icon name="compass"/>Explore</span></li>{/if}
{$name}<script>export let pathexport let _default = ''$: name = datastore.svelte(`setup${path}/name`, _default)</script>
<script>export let segment = ''export let nav = []$: prompt = datastore.svelte('session/prompt')import Home from './navigation/client.svelte'import LogOut from './prompts/logout.svelte'const promptComponents = {'log-out': LogOut}const navComponents = {'home': Home}</script><div id="main">{#if $prompt}<div class="grid-1 blur"><div class="grid panel-opaque width-40"><svelte:component this={promptComponents[$prompt]} /></div></div>{/if}<slot/></div><nav><ul><svelte:component this={Home} {segment} />{#each nav as child}{#if navComponents[child]}<svelte:component this={navComponents[child]} {segment} />{/if}{/each}</ul></nav><style lang="scss">#main {height: 100%;}.blur {position: fixed;bottom: 0;top: 0;left: 0;right: 0;backdrop-filter: blur(8px);display: grid;z-index: 100;#prompt {background: var(--bg-shade);border-radius: 0.5em;align-self: center;justify-self: center;}}nav {position: fixed;bottom: 0;left: 0;right: 0;height: 5em;z-index: 200;font-weight: 300;padding: 0 2em;margin: 0 -1em;background: hsl(0, 100%, 0%);box-shadow: inset 0px 5px 10px 1px hsla(0, 0%, 8%, 1);:global(svg) {fill: hsla(50, 50%, 100%, 0.3);}ul {margin: 0 auto;padding: 0;display: flex;align-content: center;justify-content: space-around;width: fit-content;border-top-right-radius: 0.5rem;:global(h3) {color: hsla(50, 50%, 100%, 0.3) !important;align-self: center;margin: 0 1rem;}:global(span) {margin: 0;display: flex;flex-direction: column;color: hsla(50, 50%, 100%, 0.3);text-align: center;text-decoration: none;padding: 1em 0.5em;}:global(li) {display: block;float: left;margin: 0 1rem;&:hover {:global(svg) {fill: hsla(50, 50%, 100%, 0.8) !important;}:global(span), :global(a) {color: hsla(50, 50%, 100%, 0.8);}}}:global([aria-current]) {position: relative;}:global([aria-current]::before) {position: absolute;content: '';width: calc(100% - 1em);height: 2px;background-color: rgb(255,62,0);display: block;top: 1px;}}}</style>
<script>export let wave_functionexport let wexport let himport { onMount, onDestroy } from 'svelte'import { random, PI, sin } from 'math'const offset = random() * 180let wavelet t = 0let framefunction animate() {const y_temp = sin(PI * (offset + t) / 50)const points = Array.from({ length: w + 1 }, (_, x) => wave_function(x, t, y_temp)).filter(x => x)wave.setAttribute('d', `M0,${w} L0,${h / 2} L${h / 2}, ${w / 2} ` + points.join(', ') + `L${w},${h}`)t += 0.1frame = requestAnimationFrame(animate)}onMount(() => {animate()})onDestroy(() => {if (frame != null) cancelAnimationFrame(frame)})</script><path bind:this={wave} id="sineWave" fill="url(#gradient)"/>
<script>import { PI, sin } from 'math'let h = 500let w = 500function buildEnvelope(w) {const bound_1 = 5 / 12 // 30 / 72const bound_2 = 7 / 12 // 38 / 72return Array.from({ length: w + 1 }, (_, p) => {const x = p / wif (x < bound_1) {return 0} else if (x <= bound_2) {return sin( ( 3 * PI / 2 ) * (x - 5.0 / 12)) ** 2} else {return sin(PI * (2.0 * x - 1))}})}$: envelope = buildEnvelope(w)const createLineFunction = (k, period, amplitude, d) => {return (p, t, y_temp) => {const y_envelope = envelope[p]if (y_envelope === 0) return nullconst y_wave = (h * amplitude) * sin(((k * p) + d * t) / period)const y = envelope[p] * y_temp * y_wavereturn `L${p},${h / 2 + y}`}}const waveParameters = [[0.2, 4, 1.0 / 10, -1],[0.25, 7, 1.0 / 7, -1],[0.5, 9, 1.0 / 8, 1],]const waveFunctions = waveParameters.reduce((acc, p) => {acc.push(createLineFunction(...p))return acc}, [])import Wave from './wave.svelte'</script><svg width="500" height="500" viewbox="0 0 500 500" preserveAspectRatio="none"><radialGradient id="gradient" cx="1" cy="0" r="1" ><stop offset="10%" style="stop-color:#9d50a4;" /><stop offset="50%" style="stop-color:#150979;" /><stop offset="100%" style="stop-color:#020024;" /></radialGradient>{#each waveFunctions as wave_function}<Wave {wave_function} {w} {h}></Wave>{/each}</svg><style>svg {position: fixed;min-height: 100vh;min-width: 100vw;background: radial-gradient(circle at right, rgba(80,164,162,1) 0%, rgba(21,9,121,1) 58%, rgba(4,2,38,1) 100%);background-size: 100vw 100vh;z-index: -5;}:global(path) {opacity: 0.33;stroke-width: 1px;}</style>
<script>import { faCaretUp, faCaretDown, faCaretRight, faCheck, faCircle, faExclamationTriangle, faTrash, faStickyNote, faWrench, faIgloo, faSearch, faUser, faRecordVinyl, faChartBar, faFolderOpen, faAngleDoubleLeft, faGuitar, faCompass, faSignOutAlt, faInfo } from '@fortawesome/free-solid-svg-icons'import { faSpotify, faApple, faCanadianMapleLeaf } from '@fortawesome/free-brands-svg-icons'export let name = 'dot'let className = ''export { className as class }export let found = trueexport let scale = 1// font awesome properties are take as additional props via metalet widthlet heightlet pathlet labellet box = `0 0 0 0`let styleconst solid_icons = {caret_up: faCaretUp,caret_down: faCaretDown,caret_right: faCaretRight,check: faCheck,circle: faCircle,exclamation_triangle: faExclamationTriangle,trash: faTrash,note: faStickyNote,wrench: faWrench,info: faInfo,igloo: faIgloo,search: faSearch,user: faUser,chart: faChartBar,folder: faFolderOpen,angle_double_left: faAngleDoubleLeft,guitar: faGuitar,compass: faCompass,record: faRecordVinyl,'sign-out': faSignOutAlt}const brand_icons = {spotify: faSpotify,apple: faApple,maple_leaf: faCanadianMapleLeaf}const classEval = (className, svgName) => {if (className != '') {if (solid_icons[svgName]) {found = truereturn [solid_icons[svgName], className]} else if (brand_icons[svgName]) {found = truereturn [brand_icons[svgName], className]} else {found = falsereturn [faCircle, className]}} else if (solid_icons[svgName]) {found = truereturn [solid_icons[svgName],solid_icons[svgName].prefix + ' ' + solid_icons[svgName].iconName]} else if (brand_icons[svgName]) {found = truereturn [brand_icons[svgName],brand_icons[svgName].prefix + ' ' + brand_icons[svgName].iconName]} else {found = falsereturn [ faCircle, 'fas fa-circle' ]}}let [data, svgClassName] = classEval(className, name)$: [data, svgClassName] = classEval(className, name)const propEval = props => {const entries = Object.entries(props)return entries.reduce((result, [key, value]) => {if (['class', 'name', 'found', 'scale'].includes(key)) {return result}if (value === true){result.push('fa-' + key)} else if (value !== false) {result.push('fa-' + key +'-' + value)}return result}, []).join(' ')}let props = propEval($$props)$: props = propEval($$props)$: {const [_width, _height /* _ligatures */ /* _unicode */, , , _svgPathData] = data.iconwidth = _widthheight = _heightpath = _svgPathDatalabel = data.iconNamebox = `0 0 ${width} ${height}`style = `font-size: ${scale}em`}</script><svgversion="1.1"class="fa-icon {className} {props}"x={0}y={0}{width}{height}data-icon={name}aria-label={label}role={label ? 'img' : 'presentation'}viewBox={box}{style}><path d={path} /></svg><style type="text/scss">svg.fa-spin {-webkit-animation-name: spin;-moz-animation-name: spin;-ms-animation-name: spin;animation-name: spin;-webkit-animation-duration: 4000ms;-moz-animation-duration: 4000ms;-ms-animation-duration: 4000ms;animation-duration: 4000ms;-webkit-animation-timing-function: linear;-moz-animation-timing-function: linear;-ms-animation-timing-function: linear;animation-timing-function: linear;-webkit-animation-iteration-count: infinite;-moz-animation-iteration-count: infinite;-ms-animation-iteration-count: infinite;animation-iteration-count: infinite;}@-moz-keyframes spin {from {-moz-transform: rotate(0deg);}to {-moz-transform: rotate(360deg);}}@-webkit-keyframes spin {from {-webkit-transform: rotate(0deg);}to {-webkit-transform: rotate(360deg);}}@keyframes spin {from {transform:rotate(0deg);}to {transform:rotate(360deg);}}</style>
let global_windowif (typeof window !== 'undefined') {global_window = window} else {global_window = global}import { datastore } from "@controlenvy/datastore"import srp from "secure-remote-password/client"class clientAuthorization {constructor() {this._authorizations = {}}createAccount(topic, uuid, password) {if (!this._authorizations[topic]) this._authorizations[topic] = {}const auth = this._authorizations[topic]const salt = srp.generateSalt()auth.privateKey = srp.derivePrivateKey(salt, uuid, password)auth.verifier = srp.deriveVerifier(auth.privateKey)const payload = {o: 'a',c: 0,t: topic,v: {u: uuid,v: auth.verifier,s: salt}}console.log('createAccount()')const ws = global_window.connections['node']console.log(payload)if (ws) {ws.send(payload)}}startSession(topic, uuid, password) {if (!this._authorizations[topic]) this._authorizations[topic] = {}const auth = this._authorizations[topic]auth.uuid = uuidauth.password = passwordauth.ephemeral = srp.generateEphemeral()const payload = {o: 'a',c: 1,t: topic,v: {u: uuid,k: auth.ephemeral.public}}console.log('startSession()')const ws = global_window.connections['node']if (ws) {ws.send(payload)}}deriveSharedKey(topic, packet, ws) {const auth = this._authorizations[topic]if (auth === undefined) { return }const salt = packet.sconst server_ephemeral = packet.kif (!auth.privateKey) {auth.privateKey = srp.derivePrivateKey(salt, auth.uuid, auth.password)}auth.session = srp.deriveSession(auth.ephemeral.secret, server_ephemeral, salt, auth.uuid, auth.privateKey)datastore.write(`/session/users/${auth.uuid}/key`, auth.session.key)const payload = {o: 'a',t: topic,c: 4,v: {u: packet.u,p: auth.session.proof}}ws.send(payload)}verifySessionKey(topic, packet) {const auth = this._authorizations[topic]if (auth === undefined) { return }const server_proof = packet.ptry {srp.verifySession(auth.ephemeral.public, auth.session, server_proof)console.log('verifySessionKey() authorized')datastore.delete(`/session/users/${auth.uuid}/key`)datastore.write(`/action/users/${auth.uuid}/auth`, true)datastore.delete(`/action/users/${auth.uuid}/auth`)return true} catch (err) {console.log('verifySessionKey() failed')console.log(err)datastore.delete(`/session/users/${auth.uuid}/key`)datastore.write(`/action/users/${auth.uuid}/auth`, false)datastore.delete(`/action/users/${auth.uuid}/auth`)return false}}requestPersistantSession(uuid, ws) {console.log(`requestPersistantSession()`)const payload = {o: 'a',c: 6,v: uuid}ws.send(payload)}setPersistantSession(cookie) {console.log(`setPersistantSession()`)console.log(cookie)const uuid = cookie.split(':').slice(0, 1)[0]datastore.write('/session/user_id', uuid)console.log(cookie)document.cookie = 'session-data=' + cookie + ';'}getPersistantSession(ws) {const cookies = document.cookie.split('; ')const session_cookie = cookies.find(item => item.startsWith('session-data'))if (!session_cookie) { return }console.log(session_cookie.split('=').slice(-1)[0])const payload = {o: 'a',c: 5,v: session_cookie.split('=').slice(-1)[0]}ws.send(payload)}pickUpSession(session_topic, packet, ws) {for (const topic of packet.s) {console.log({o: 's', p: topic, i: true})ws.send({o: 's', t: topic, i: true})}datastore.write(`/session/local/user_id`, packet.u)this.requestPersistantSession(packet.u, ws)}reset(topic = null) {if (topic === null) {this._authorizations = {}} else {delete this._authorizations[topic]}}}export const authorization = new clientAuthorization
import querystring from 'query-string'import { WS } from '$lib/websocket'import { goto } from '$app/navigation'import { send } from '$lib/send'import { Topic } from '@controlenvy/datastore'import { authorization } from './authorization.js'import lodash from 'lodash'class API {constructor() {console.log('Starting API')this.url = nullwindow.notify = this.notify.bind(this)// Open Websocketconst parseWebsocketOrigin = (string) => {if (/localhost/.test(string) || /:\d+/.test(string)) {return {host: 'localhost',protocol: 'ws://',port: 25706}} else {return {host: 'www.processor.' + string.split('.').slice(-2).join('.'),protocol: 'wss://',}}}const ws = new WS(Object.assign(parseWebsocketOrigin(location.origin), {connection: 'node'}))ws.on('open', () => {this.notify({error: 'Connected to node',duration: 1000,prompt: false,duplicate: false})authorization.getPersistantSession(ws)ws.send({ o: 's', t: 'setup/events/+/name', i: true })ws.send({ o: 's', t: 'setup/events/+/private', i: true })ws.send({ o: 's', t: 'state/items/+/#', i: true })})ws.on('message', ({ data }) => {try {const message = JSON.parse(data)console.log(`incoming ${data}`)switch (message.o) {case 'p':case 'r': {if (message.v === null) {message.v = undefined}const path = message.pconst value = message.vif (lodash.isPlainObject(message.v)) {datastore.merge(path, value)} else {datastore.set(path, value)}const queued = datastore.get('/q' + message.p)if (lodash.isEqual(message.v, queued)) {const q_path = '/q' + message.pconst q_steps = q_path.slice(1).split('/')datastore.delete(q_steps, { silent: true })}} breakcase 'a': {if (message.v === undefined) { return }console.log(message)switch (message.c) {case 0: {const topic = new Topic(message.t).replace('/#', '/create').replace('state', 'action')datastore.write('/' + topic.pattern, message.v)datastore.delete('/' + topic.pattern, message.v)if (!message.v) authorization.reset(message.t)} breakcase 2: {authorization.deriveSharedKey(message.t, message.v, ws)} breakcase 4: {if (authorization.verifySessionKey(message.t, message.v, ws) &&/\/users/.test(message.t)) {authorization.requestPersistantSession(message.v.u, ws)}} breakcase 5: {if (!message.v) { break }authorization.pickUpSession(message.t, message.v, ws)} breakcase 6: {if (!message.v) { break }authorization.setPersistantSession(message.v)} break}} break}} catch (e) {console.error('Error parsing message from server.', e, data)}})ws.on('close', () => {this.notify({error: 'Disconnected from djin node',duration: 0,duplicate: false})})datastore.subscribe('q/state/#', (topic, event) => {const { pointer, value } = eventif (lodash.isPlainObject(value) || event.type !== '=') { return }send(value, pointer.path)})datastore.subscribe('session/redirect', (pointer, event) => {const { value } = eventif (lodash.isString(value)) {datastore.write('/session/redirect', null)goto(value)}})// Bind to url parameters// datastore.subscribe('session/path/*', (topic, pointer, value) => {// try {// const { route, query, hash } = value// let next = route || '/'// localStorage.setItem('djinlist', JSON.stringify(value))// const session_query = {}// Object.keys(query || {})// .sort()// .forEach(key => {// session_query[key] = query[key]// })// const session_hash = {}// Object.keys(hash || {})// .sort()// .forEach(key => {// session_hash[key] = hash[key]// })// next = next +// (session_query && (session_query.length != 0) ? '?' + querystring.stringify(session_query) : '') +// (session_hash && (session_hash.length != 0) ? '#' + querystring.stringify(session_hash) : '')// if ((location.pathname || '' + location.query || '' + location.hash || '') === next) {// return// }// if (!('standalone' in navigator && navigator.standalone === true)) {// history.replaceState({}, '', next)// }// } catch (e) {// return// }// })}notify(details) {console.log(details)}}const api = new API()export { api }
button.big {border: none;border-radius: 1rem;text-align: center;color: hsla(100, 25%, 100%, 0.8);background-color: coral;margin: 1rem auto;h3 {font-weight: bold;text-align: center;margin: 0.5em;}}
import lodash from 'lodash-es'window._ = lodashimport { Datastore, Pointer } from '@djinlist/datastore'import { send } from './lib/send'import { authorization } from './lib/authorization'import { subscribe } from './lib/subscribe'import { unsubscribe } from './lib/unsubscribe'window.datastore = new Datastorewindow.Pointer = Pointerwindow.send = sendwindow.authorization = authorizationwindow.subscribe = subscribewindow.unsubscribe = unsubscribeimport { ThemeHandler } from './lib/theme'window.theme = new ThemeHandler()// configdatastore.set('session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')datastore.set('session/ping_interval', 10000)datastore._chain = datastore.chaindatastore.chain = svelte => {const chain = datastore._chain()if (svelte === void 0) {return chain}svelte.$$.on_destroy.push(() => {chain.destroy()})return chain}// Driversimport { Spotify } from './lib/spotify'const spotify = new Spotify(datastore)// Modelsimport { User } from './lib/user'const user = new User(datastore)import { Bootstrap } from './bootstrap.js'let bootstrapimport * as sapper from '@sapper/app';const load = () => {window.document.body.addEventListener('touchstart touchend', function(e) {e.preventDefault();e.toggleClass('hover_effect');})console.log('Loading Djinlist bootstrap')if (bootstrap != null) {console.warn(`index.js ${VERSION} load()`, 'bootstrap already exists') // eslint-disable-line no-consolereturn}new Notification({target: document.querySelector('#notifications')})bootstrap = new Bootstrap()}sapper.start({target: document.querySelector('#sapper')}).then(load)
<!doctype html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1.0'><meta name='theme-color' content='#333333'><!-- <link rel='stylesheet' href='global.css'> --><!-- <link rel='icon' type='image/png' href='favicon.png'> -->%svelte.head%</head><body><div id="svelte">%svelte.body%</div></body></html><style type="text/scss">body {height: 100vh;}</style>
import querystring from 'query-string'import { WS } from './lib/websocket'import { goto } from '@sapper/app'class API {constructor() {this.url = nullwindow.notify = this.notify.bind(this)// Open Websocketconst parseWebsocketOrigin = (string) => {if (/localhost/.test(string) || /:\d+/.test(string)) {return {host: 'localhost',protocol: 'ws://',port: 25706}} else {return {host: 'www.processor.' + string.split('.').slice(-2).join('.'),protocol: 'wss://',}}}const ws = new WS(Object.assign(parseWebsocketOrigin(location.origin), {connection: 'node'}))ws.on('open', () => {this.notify({error: 'Connected to node',duration: 1000,prompt: false,duplicate: false})authorization.getPersistantSession(ws)ws.send({ o: 's', p: '/setup/events/+/name', i: true })ws.send({ o: 's', p: '/setup/events/+/private', i: true })ws.send({ o: 's', p: '/state/items/+/#', i: true })})ws.on('message', ({ data }) => {try {const message = JSON.parse(data)console.log(`incoming ${data}`)switch (message.o) {case 'p':case 'r':if (message.v === null) {message.v = undefined}const path = message.pconst value = message.vif (_.isPlainObject(message.v)) {datastore.merge(path, value)} else {datastore.set(path, value)}const queued = datastore.get('/q' + message.p)if (_.isEqual(message.v, queued)) {const q_path = '/q' + message.pconst q_steps = q_path.slice(1).split('/')datastore.delete(q_steps, { silent: true })}breakcase 'a':console.log(message)if (message.v === undefined) { return }console.log(message.c)switch (message.c) {case 0:const pointer = Pointer.create(message.t).replace('/#', '/create').replace('state', 'action')datastore.write(pointer, message.v)datastore.destroy(pointer, message.v)if (!message.v) authorization.reset(message.t)breakcase 2:authorization.deriveSharedKey(message.t, message.v, ws)breakcase 4:if (authorization.verifySessionKey(message.t, message.v, ws) &&/\/users/.test(message.t)) {authorization.requestPersistantSession(message.v.u, ws)}breakcase 5:if (!message.v) { break }authorization.pickUpSession(message.t, message.v, ws)breakcase 6:if (!message.v) { break }authorization.setPersistantSession(message.v, ws)break}break}} catch (e) {console.error('Error parsing message from server.', e, data)}})ws.on('close', () => {this.notify({error: 'Disconnected from djin node',duration: 0,duplicate: false})})const subscriber = {}datastore.subscribe('q/state/#', subscriber, (topic, pointer, value) => {if (_.isPlainObject(value)) {return}send(value, pointer.path)})datastore.subscribe('session/redirect', subscriber, (topic, pointer, value) => {if (_.isString(value)) {datastore.write('/session/redirect', null)goto(value)}})// Bind to url parametersdatastore.subscribe('session/path/*', this, (topic, pointer, value) => {try {const { route, query, hash } = valuelet next = route || '/'localStorage.setItem('djinlist', JSON.stringify(value))const session_query = {}Object.keys(query || {}).sort().forEach(key => {session_query[key] = query[key]})const session_hash = {}Object.keys(hash || {}).sort().forEach(key => {session_hash[key] = hash[key]})next = next +(session_query && (session_query.length != 0) ? '?' + querystring.stringify(session_query) : '') +(session_hash && (session_hash.length != 0) ? '#' + querystring.stringify(session_hash) : '')if ((location.pathname || '' + location.query || '' + location.hash || '') === next) {return}if (!('standalone' in navigator && navigator.standalone === true)) {history.replaceState({}, '', next)}} catch (e) {return}})}}const api = new APIexport default api
import resolve from '@rollup/plugin-node-resolve';import replace from '@rollup/plugin-replace';import commonjs from '@rollup/plugin-commonjs';import re from 'rollup-plugin-re';import svelte from 'rollup-plugin-svelte';import { scss } from 'svelte-preprocess'import babel from 'rollup-plugin-babel';import globals from 'rollup-plugin-node-globals';import { terser } from 'rollup-plugin-terser';import config from 'sapper/config/rollup.js';import pkg from './package.json';const mode = process.env.NODE_ENV;const dev = mode === 'development';const legacy = !!process.env.SAPPER_LEGACY_BUILD;const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning);export default {client: {input: config.client.input(),output: config.client.output(),plugins: [replace({'process.browser': true,'process.env.NODE_ENV': JSON.stringify(mode)}),svelte({dev,hydratable: true,emitCss: false,css: true,preprocess: [scss()]}),resolve({browser: true,dedupe: ['svelte']}),commonjs(),re({replaces: {__$$self: '$$self'}}),legacy && babel({extensions: ['.js', '.mjs', '.html', '.svelte'],runtimeHelpers: true,exclude: ['node_modules/@babel/**'],presets: [['@babel/preset-env', {targets: '> 0.25%, not dead'}]],plugins: ['@babel/plugin-syntax-dynamic-import',['@babel/plugin-transform-runtime', {useESModules: true}]]}),!dev && terser({module: true})],onwarn,},server: {input: config.server.input(),output: config.server.output(),plugins: [replace({'process.browser': false,'process.env.NODE_ENV': JSON.stringify(mode)}),svelte({generate: 'ssr',dev,preprocess: [scss()]}),resolve({dedupe: ['svelte']}),commonjs()],external: Object.keys(pkg.dependencies).concat(require('module').builtinModules || Object.keys(process.binding('natives'))),onwarn,},serviceworker: {input: config.serviceworker.input(),output: config.serviceworker.output(),plugins: [resolve(),replace({'process.browser': true,'process.env.NODE_ENV': JSON.stringify(mode)}),commonjs(),!dev && terser()],onwarn,}};
{"name": "@djinlist/client","description": "djiny client","version": "0.0.1","scripts": {"dev": "svelte-kit dev","build": "svelte-kit build","export": "svelte-kit build","start": "PORT=5443 node __sapper__/build","lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore ."},"exports": {"./server": "./build/index.js"},"devDependencies": {"@sveltejs/adapter-node": "^1.0.0-next","@sveltejs/kit": "next","eslint": "^7.22.0","eslint-config-prettier": "^8.1.0","eslint-plugin-svelte3": "^3.2.0","lodash": "^4.17.21","node-sass": "^6.0.1","prettier": "~2.2.1","prettier-plugin-svelte": "^2.2.0","query-string": "4.3.2","sass": "^1.35.2","secure-remote-password": "^0.3.1","svelte": "^3.34.0","svelte-preprocess": "^4.7.3","uuid": "^8.0.0"},"type": "module","dependencies": {"@fortawesome/free-brands-svg-icons": "^5.12.0","@fortawesome/free-solid-svg-icons": "^5.12.0","math": "^0.0.3","sanitize.css": "^12.0.1","sass": "^1.35.2"}}
{"name": "@djinlist/client","version": "0.0.1","lockfileVersion": 2,"requires": true,"packages": {"": {"name": "@djinlist/client","version": "0.0.1","dependencies": {"@fortawesome/free-brands-svg-icons": "^5.12.0","@fortawesome/free-solid-svg-icons": "^5.12.0","math": "^0.0.3","sanitize.css": "^12.0.1","sass": "^1.35.2"},"devDependencies": {"@sveltejs/adapter-node": "^1.0.0-next","@sveltejs/kit": "next","eslint": "^7.22.0","eslint-config-prettier": "^8.1.0","eslint-plugin-svelte3": "^3.2.0","lodash": "^4.17.21","node-sass": "^6.0.1","prettier": "~2.2.1","prettier-plugin-svelte": "^2.2.0","query-string": "4.3.2","sass": "^1.35.2","secure-remote-password": "^0.3.1","svelte": "^3.34.0","svelte-preprocess": "^4.7.3","uuid": "^8.0.0"}},"../../node_modules/.pnpm/@sveltejs+adapter-static@1.0.0-next.16/node_modules/@sveltejs/adapter-static": {"version": "1.0.0-next.16","extraneous": true,"devDependencies": {"@sveltejs/kit": "1.0.0-next.142","playwright-chromium": "^1.10.0","port-authority": "^1.1.2","sirv": "^1.0.12","svelte": "^3.40.0","uvu": "^0.5.1"}},"../node_modules/.pnpm/@fortawesome+free-brands-svg-icons@5.15.4/node_modules/@fortawesome/free-brands-svg-icons": {"version": "5.15.4","hasInstallScript": true,"license": "(CC-BY-4.0 AND MIT)","dependencies": {"@fortawesome/fontawesome-common-types": "^0.2.36"},"engines": {"node": ">=6"}},"../node_modules/.pnpm/@fortawesome+free-solid-svg-icons@5.15.4/node_modules/@fortawesome/free-solid-svg-icons": {"version": "5.15.4","hasInstallScript": true,"license": "(CC-BY-4.0 AND MIT)","dependencies": {"@fortawesome/fontawesome-common-types": "^0.2.36"},"engines": {"node": ">=6"}},"../node_modules/.pnpm/@sveltejs+adapter-node@1.0.0-next.0/node_modules/@sveltejs/adapter-node": {"version": "1.0.0-next.0","dev": true,"dependencies": {"@sveltejs/app-utils": "1.0.0-next.0"},"devDependencies": {"rollup": "^2.32.0","sirv": "^1.0.7"}},"../node_modules/.pnpm/@sveltejs+kit@1.0.0-next.146_svelte@3.42.1/node_modules/@sveltejs/kit": {"version": "1.0.0-next.146","extraneous": true,"dependencies": {"@sveltejs/vite-plugin-svelte": "^1.0.0-next.14","cheap-watch": "^1.0.3","sade": "^1.7.4","vite": "^2.4.3"},"bin": {"svelte-kit": "svelte-kit.js"},"devDependencies": {"@rollup/plugin-replace": "^2.4.2","@types/amphtml-validator": "^1.0.1","@types/cookie": "^0.4.0","@types/globrex": "^0.1.1","@types/marked": "^2.0.2","@types/mime": "^2.0.3","@types/node": "^14.14.43","@types/rimraf": "^3.0.0","@types/sade": "^1.7.2","amphtml-validator": "^1.0.34","cookie": "^0.4.1","devalue": "^2.0.1","eslint": "^7.25.0","globrex": "^0.1.2","kleur": "^4.1.4","locate-character": "^2.0.5","marked": "^2.0.3","mime": "^2.5.2","node-fetch": "^3.0.0-beta.9","port-authority": "^1.1.2","rimraf": "^3.0.2","rollup": "^2.55.0","selfsigned": "^1.10.11","sirv": "^1.0.12","svelte": "^3.40.0","svelte-check": "^2.2.0","svelte2tsx": "~0.4.1","tiny-glob": "^0.2.8","uvu": "^0.5.1"},"engines": {"node": "^12.20 || >=14.13"},"peerDependencies": {"svelte": "^3.34.0"}},"../node_modules/.pnpm/eslint-config-prettier@8.3.0_eslint@7.32.0/node_modules/eslint-config-prettier": {"version": "8.3.0","dev": true,"license": "MIT","bin": {"eslint-config-prettier": "bin/cli.js"},"peerDependencies": {"eslint": ">=7.0.0"}},"../node_modules/.pnpm/eslint-plugin-svelte3@3.2.0_eslint@7.32.0+svelte@3.42.1/node_modules/eslint-plugin-svelte3": {"version": "3.2.0","dev": true,"license": "MIT","devDependencies": {"@rollup/plugin-node-resolve": "^11.2.0","@typescript-eslint/eslint-plugin": "^4.14.2","@typescript-eslint/parser": "^4.14.2","eslint": ">=6.0.0","rollup": "^2","sourcemap-codec": "1.4.8","svelte": "^3.2.0","typescript": "^4.0.0"},"engines": {"node": ">=10"},"peerDependencies": {"eslint": ">=6.0.0","svelte": "^3.2.0"}},"../node_modules/.pnpm/eslint@7.32.0/node_modules/eslint": {"version": "7.32.0","dev": true,"license": "MIT","dependencies": {"@babel/code-frame": "7.12.11","@eslint/eslintrc": "^0.4.3","@humanwhocodes/config-array": "^0.5.0","ajv": "^6.10.0","chalk": "^4.0.0","cross-spawn": "^7.0.2","debug": "^4.0.1","doctrine": "^3.0.0","enquirer": "^2.3.5","escape-string-regexp": "^4.0.0","eslint-scope": "^5.1.1","eslint-utils": "^2.1.0","eslint-visitor-keys": "^2.0.0","espree": "^7.3.1","esquery": "^1.4.0","esutils": "^2.0.2","fast-deep-equal": "^3.1.3","file-entry-cache": "^6.0.1","functional-red-black-tree": "^1.0.1","glob-parent": "^5.1.2","globals": "^13.6.0","ignore": "^4.0.6","import-fresh": "^3.0.0","imurmurhash": "^0.1.4","is-glob": "^4.0.0","js-yaml": "^3.13.1","json-stable-stringify-without-jsonify": "^1.0.1","levn": "^0.4.1","lodash.merge": "^4.6.2","minimatch": "^3.0.4","natural-compare": "^1.4.0","optionator": "^0.9.1","progress": "^2.0.0","regexpp": "^3.1.0","semver": "^7.2.1","strip-ansi": "^6.0.0","strip-json-comments": "^3.1.0","table": "^6.0.9","text-table": "^0.2.0","v8-compile-cache": "^2.0.3"},"bin": {"eslint": "bin/eslint.js"},"devDependencies": {"@babel/core": "^7.4.3","@babel/preset-env": "^7.4.3","babel-loader": "^8.0.5","chai": "^4.0.1","cheerio": "^0.22.0","common-tags": "^1.8.0","core-js": "^3.1.3","dateformat": "^3.0.3","ejs": "^3.0.2","eslint": "file:.","eslint-config-eslint": "file:packages/eslint-config-eslint","eslint-plugin-eslint-plugin": "^3.5.3","eslint-plugin-internal-rules": "file:tools/internal-rules","eslint-plugin-jsdoc": "^25.4.3","eslint-plugin-node": "^11.1.0","eslint-release": "^2.0.0","eslump": "^3.0.0","esprima": "^4.0.1","fs-teardown": "0.1.1","glob": "^7.1.6","jsdoc": "^3.5.5","karma": "^6.1.1","karma-chrome-launcher": "^3.1.0","karma-mocha": "^2.0.1","karma-mocha-reporter": "^2.2.5","karma-webpack": "^5.0.0","lint-staged": "^10.1.2","load-perf": "^0.2.0","markdownlint": "^0.19.0","markdownlint-cli": "^0.22.0","memfs": "^3.0.1","mocha": "^8.3.2","mocha-junit-reporter": "^2.0.0","node-polyfill-webpack-plugin": "^1.0.3","npm-license": "^0.3.3","nyc": "^15.0.1","proxyquire": "^2.0.1","puppeteer": "^7.1.0","recast": "^0.19.0","regenerator-runtime": "^0.13.2","shelljs": "^0.8.2","sinon": "^9.0.1","temp": "^0.9.0","webpack": "^5.23.0","webpack-cli": "^4.5.0","yorkie": "^2.0.0"},"engines": {"node": "^10.12.0 || >=12.0.0"},"funding": {"url": "https://opencollective.com/eslint"}},"../node_modules/.pnpm/lodash@4.17.21/node_modules/lodash": {"version": "4.17.21","dev": true,"license": "MIT"},"../node_modules/.pnpm/node-sass@6.0.1/node_modules/node-sass": {"version": "6.0.1","dev": true,"hasInstallScript": true,"license": "MIT","dependencies": {"async-foreach": "^0.1.3","chalk": "^1.1.1","cross-spawn": "^7.0.3","gaze": "^1.0.0","get-stdin": "^4.0.1","glob": "^7.0.3","lodash": "^4.17.15","meow": "^9.0.0","nan": "^2.13.2","node-gyp": "^7.1.0","npmlog": "^4.0.0","request": "^2.88.0","sass-graph": "2.2.5","stdout-stream": "^1.4.0","true-case-path": "^1.0.2"},"bin": {"node-sass": "bin/node-sass"},"devDependencies": {"eslint": "^7.10.0","fs-extra": "^0.30.0","mocha": "^9.0.1","nyc": "^15.1.0","rimraf": "^3.0.2","unique-temp-dir": "^1.0.0"},"engines": {"node": ">=12"}},"../node_modules/.pnpm/prettier-plugin-svelte@2.3.1_prettier@2.2.1+svelte@3.42.1/node_modules/prettier-plugin-svelte": {"version": "2.3.1","dev": true,"license": "MIT","devDependencies": {"@rollup/plugin-commonjs": "14.0.0","@rollup/plugin-node-resolve": "11.0.1","@types/node": "^10.12.18","@types/prettier": "^2.1.6","ava": "3.15.0","prettier": "^2.3.0","rollup": "2.36.0","rollup-plugin-typescript": "1.0.1","svelte": "^3.35.0","ts-node": "^9.1.1","tslib": "^2.0.3","typescript": "4.1.3"},"peerDependencies": {"prettier": "^1.16.4 || ^2.0.0","svelte": "^3.2.0"}},"../node_modules/.pnpm/prettier@2.2.1/node_modules/prettier": {"version": "2.2.1","dev": true,"license": "MIT","bin": {"prettier": "bin-prettier.js"},"engines": {"node": ">=10.13.0"}},"../node_modules/.pnpm/query-string@4.3.2/node_modules/query-string": {"version": "4.3.2","dev": true,"license": "MIT","dependencies": {"object-assign": "^4.1.0","strict-uri-encode": "^1.0.0"},"devDependencies": {"ava": "^0.17.0","xo": "^0.16.0"},"engines": {"node": ">=0.10.0"}},"../node_modules/.pnpm/sanitize.css@12.0.1/node_modules/sanitize.css": {"version": "12.0.1","license": "CC0-1.0","devDependencies": {"stylelint": "^13.6.1","stylelint-config-standard": "^20.0.0"}},"../node_modules/.pnpm/sass@1.37.5/node_modules/sass": {"version": "1.37.5","dev": true,"license": "MIT","dependencies": {"chokidar": ">=3.0.0 <4.0.0"},"bin": {"sass": "sass.js"},"engines": {"node": ">=8.9.0"}},"../node_modules/.pnpm/secure-remote-password@0.3.1/node_modules/secure-remote-password": {"version": "0.3.1","dev": true,"license": "MIT","dependencies": {"array-buffer-to-hex": "^1.0.0","crypto-digest-sync": "^1.0.0","crypto-random-hex": "^1.0.0","encode-utf8": "^1.0.1","hex-to-array-buffer": "^1.1.0","jsbn": "^1.1.0","pad-start": "^1.0.2"},"devDependencies": {"mocha": "^3.5.0","standard": "^10.0.3"}},"../node_modules/.pnpm/svelte-preprocess@4.7.4_6197623e5ed34153d1bcd9290e2954d7/node_modules/svelte-preprocess": {"version": "4.7.4","dev": true,"hasInstallScript": true,"license": "MIT","dependencies": {"@types/pug": "^2.0.4","@types/sass": "^1.16.0","detect-indent": "^6.0.0","strip-indent": "^3.0.0"},"devDependencies": {"@babel/core": "^7.10.2","@babel/preset-env": "^7.10.2","@kiwi/eslint-config": "^1.4.3","@kiwi/prettier-config": "^1.4.3","@types/jest": "^25.2.3","@types/node": "^14.0.11","@types/node-sass": "^4.11.1","@types/stylus": "^0.48.32","autoprefixer": "^9.8.0","babel-minify": "^0.5.1","coffeescript": "^2.5.1","conventional-changelog-cli": "^2.0.34","eslint": "^7.15.0","husky": "^4.2.5","jest": "^25.0.0","less": "^3.11.3","lint-staged": "^10.5.3","node-sass": "^4.14.1","postcss": "^8","postcss-easy-import": "^3.0.0","postcss-load-config": "^3.0.0","prettier": "^2.2.1","pug": "^3.0.0","sass": "^1.26.8","stylus": "^0.54.7","sugarss": "^2.0.0","svelte": "^3.23.0","ts-jest": "^25.1.0","typescript": "^3.9.5"},"engines": {"node": ">= 9.11.2"},"peerDependencies": {"@babel/core": "^7.10.2","coffeescript": "^2.5.1","less": "^3.11.3","postcss": "^7 || ^8","postcss-load-config": "^2.1.0 || ^3.0.0","pug": "^3.0.0","sass": "^1.26.8","stylus": "^0.54.7","sugarss": "^2.0.0","svelte": "^3.23.0","typescript": "^3.9.5 || ^4.0.0"},"peerDependenciesMeta": {"@babel/core": {"optional": true},"coffeescript": {"optional": true},"less": {"optional": true},"node-sass": {"optional": true},"postcss": {"optional": true},"postcss-load-config": {"optional": true},"pug": {"optional": true},"sass": {"optional": true},"stylus": {"optional": true},"sugarss": {"optional": true},"typescript": {"optional": true}}},"../node_modules/.pnpm/svelte@3.42.1/node_modules/svelte": {"version": "3.42.1","dev": true,"license": "MIT","devDependencies": {"@ampproject/remapping": "^0.3.0","@rollup/plugin-commonjs": "^11.0.0","@rollup/plugin-json": "^4.0.1","@rollup/plugin-node-resolve": "^6.0.0","@rollup/plugin-replace": "^2.3.0","@rollup/plugin-sucrase": "^3.1.0","@rollup/plugin-typescript": "^2.0.1","@rollup/plugin-virtual": "^2.0.0","@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.7.0","@types/mocha": "^7.0.0","@types/node": "^8.10.53","@typescript-eslint/eslint-plugin": "^4.9.0","@typescript-eslint/parser": "^4.9.0","acorn": "^8.4.1","agadoo": "^1.1.0","c8": "^5.0.1","code-red": "^0.2.2","codecov": "^3.5.0","css-tree": "^1.1.2","eslint": "^7.15.0","eslint-plugin-import": "^2.22.1","eslint-plugin-svelte3": "^2.7.3","estree-walker": "^3.0.0","is-reference": "^3.0.0","jsdom": "^15.2.1","kleur": "^3.0.3","locate-character": "^2.0.5","magic-string": "^0.25.3","mocha": "^7.0.0","periscopic": "^3.0.4","puppeteer": "^2.1.1","rollup": "^1.27.14","source-map": "^0.7.3","source-map-support": "^0.5.13","sourcemap-codec": "^1.4.8","tiny-glob": "^0.2.6","tslib": "^2.0.3","typescript": "^3.7.5"},"engines": {"node": ">= 8"}},"../node_modules/.pnpm/uuid@8.3.2/node_modules/uuid": {"version": "8.3.2","dev": true,"license": "MIT","bin": {"uuid": "dist/bin/uuid"},"devDependencies": {"@babel/cli": "7.11.6","@babel/core": "7.11.6","@babel/preset-env": "7.11.5","@commitlint/cli": "11.0.0","@commitlint/config-conventional": "11.0.0","@rollup/plugin-node-resolve": "9.0.0","babel-eslint": "10.1.0","bundlewatch": "0.3.1","eslint": "7.10.0","eslint-config-prettier": "6.12.0","eslint-config-standard": "14.1.1","eslint-plugin-import": "2.22.1","eslint-plugin-node": "11.1.0","eslint-plugin-prettier": "3.1.4","eslint-plugin-promise": "4.2.1","eslint-plugin-standard": "4.0.1","husky": "4.3.0","jest": "25.5.4","lint-staged": "10.4.0","npm-run-all": "4.1.5","optional-dev-dependency": "2.0.1","prettier": "2.1.2","random-seed": "0.3.0","rollup": "2.28.2","rollup-plugin-terser": "7.0.2","runmd": "1.3.2","standard-version": "9.0.0"}},"node_modules/@fortawesome/free-brands-svg-icons": {"resolved": "../node_modules/.pnpm/@fortawesome+free-brands-svg-icons@5.15.4/node_modules/@fortawesome/free-brands-svg-icons","link": true},"node_modules/@fortawesome/free-solid-svg-icons": {"resolved": "../node_modules/.pnpm/@fortawesome+free-solid-svg-icons@5.15.4/node_modules/@fortawesome/free-solid-svg-icons","link": true},"node_modules/@rollup/pluginutils": {"version": "4.1.1","resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.1.tgz","integrity": "sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==","dev": true,"dependencies": {"estree-walker": "^2.0.1","picomatch": "^2.2.2"},"engines": {"node": ">= 8.0.0"}},"node_modules/@sveltejs/adapter-node": {"resolved": "../node_modules/.pnpm/@sveltejs+adapter-node@1.0.0-next.0/node_modules/@sveltejs/adapter-node","link": true},"node_modules/@sveltejs/kit": {"version": "1.0.0-next.146","resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.146.tgz","integrity": "sha512-MSatcaCRfjl88Prd5mW4pNOJ3Gsr525+Vjr24MoKtyTt6PZQmTfQsDVwyP93exn/6w2xl9uMCW6cFpDVBu7jSg==","dev": true,"dependencies": {"@sveltejs/vite-plugin-svelte": "^1.0.0-next.14","cheap-watch": "^1.0.3","sade": "^1.7.4","vite": "^2.4.3"},"bin": {"svelte-kit": "svelte-kit.js"},"engines": {"node": "^12.20 || >=14.13"},"peerDependencies": {"svelte": "^3.34.0"}},"node_modules/@sveltejs/vite-plugin-svelte": {"version": "1.0.0-next.15","resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.15.tgz","integrity": "sha512-8yGX7PxaqtvWw+GHiO2DV7lZ4M7DwIrFq+PgZGZ9X09PuoSeaWszm76GWQXJMKHoPPhdA9084662en9qbv4aRw==","dev": true,"dependencies": {"@rollup/pluginutils": "^4.1.1","debug": "^4.3.2","kleur": "^4.1.4","magic-string": "^0.25.7","require-relative": "^0.8.7","svelte-hmr": "^0.14.7"},"engines": {"node": "^12.20 || ^14.13.1 || >= 16"},"peerDependencies": {"diff-match-patch": "^1.0.5","svelte": "^3.34.0","vite": "^2.3.7"},"peerDependenciesMeta": {"diff-match-patch": {"optional": true}}},"node_modules/cheap-watch": {"version": "1.0.3","resolved": "https://registry.npmjs.org/cheap-watch/-/cheap-watch-1.0.3.tgz","integrity": "sha512-xC5CruMhLzjPwJ5ecUxGu1uGmwJQykUhqd2QrCrYbwvsFYdRyviu6jG9+pccwDXJR/OpmOTOJ9yLFunVgQu9wg==","dev": true,"engines": {"node": ">=8"}},"node_modules/colorette": {"version": "1.3.0","resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz","integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==","dev": true},"node_modules/debug": {"version": "4.3.2","resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz","integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==","dev": true,"dependencies": {"ms": "2.1.2"},"engines": {"node": ">=6.0"},"peerDependenciesMeta": {"supports-color": {"optional": true}}},"node_modules/esbuild": {"version": "0.12.19","resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.19.tgz","integrity": "sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w==","dev": true,"hasInstallScript": true,"bin": {"esbuild": "bin/esbuild"}},"node_modules/eslint": {"resolved": "../node_modules/.pnpm/eslint@7.32.0/node_modules/eslint","link": true},"node_modules/eslint-config-prettier": {"resolved": "../node_modules/.pnpm/eslint-config-prettier@8.3.0_eslint@7.32.0/node_modules/eslint-config-prettier","link": true},"node_modules/eslint-plugin-svelte3": {"resolved": "../node_modules/.pnpm/eslint-plugin-svelte3@3.2.0_eslint@7.32.0+svelte@3.42.1/node_modules/eslint-plugin-svelte3","link": true},"node_modules/estree-walker": {"version": "2.0.2","resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz","integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==","dev": true},"node_modules/fsevents": {"version": "2.3.2","resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz","integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==","dev": true,"hasInstallScript": true,"optional": true,"os": ["darwin"],"engines": {"node": "^8.16.0 || ^10.6.0 || >=11.0.0"}},"node_modules/function-bind": {"version": "1.1.1","resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz","integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==","dev": true},"node_modules/has": {"version": "1.0.3","resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz","integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==","dev": true,"dependencies": {"function-bind": "^1.1.1"},"engines": {"node": ">= 0.4.0"}},"node_modules/is-core-module": {"version": "2.5.0","resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz","integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==","dev": true,"dependencies": {"has": "^1.0.3"},"funding": {"url": "https://github.com/sponsors/ljharb"}},"node_modules/kleur": {"version": "4.1.4","resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz","integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==","dev": true,"engines": {"node": ">=6"}},"node_modules/lodash": {"resolved": "../node_modules/.pnpm/lodash@4.17.21/node_modules/lodash","link": true},"node_modules/magic-string": {"version": "0.25.7","resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz","integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==","dev": true,"dependencies": {"sourcemap-codec": "^1.4.4"}},"node_modules/math": {"version": "0.0.3","resolved": "https://registry.npmjs.org/math/-/math-0.0.3.tgz","integrity": "sha1-hbAg/VTOELJqvqv81+H0vbxGRw8=","engines": {"node": "> 0.0.0"}},"node_modules/mri": {"version": "1.1.6","resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz","integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==","dev": true,"engines": {"node": ">=4"}},"node_modules/ms": {"version": "2.1.2","resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz","integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","dev": true},"node_modules/nanoid": {"version": "3.1.23","resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz","integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==","dev": true,"bin": {"nanoid": "bin/nanoid.cjs"},"engines": {"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"}},"node_modules/node-sass": {"resolved": "../node_modules/.pnpm/node-sass@6.0.1/node_modules/node-sass","link": true},"node_modules/path-parse": {"version": "1.0.7","resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz","integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==","dev": true},"node_modules/picomatch": {"version": "2.3.0","resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz","integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==","dev": true,"engines": {"node": ">=8.6"},"funding": {"url": "https://github.com/sponsors/jonschlinkert"}},"node_modules/postcss": {"version": "8.3.6","resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz","integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==","dev": true,"dependencies": {"colorette": "^1.2.2","nanoid": "^3.1.23","source-map-js": "^0.6.2"},"engines": {"node": "^10 || ^12 || >=14"},"funding": {"type": "opencollective","url": "https://opencollective.com/postcss/"}},"node_modules/prettier": {"resolved": "../node_modules/.pnpm/prettier@2.2.1/node_modules/prettier","link": true},"node_modules/prettier-plugin-svelte": {"resolved": "../node_modules/.pnpm/prettier-plugin-svelte@2.3.1_prettier@2.2.1+svelte@3.42.1/node_modules/prettier-plugin-svelte","link": true},"node_modules/query-string": {"resolved": "../node_modules/.pnpm/query-string@4.3.2/node_modules/query-string","link": true},"node_modules/require-relative": {"version": "0.8.7","resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz","integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=","dev": true},"node_modules/resolve": {"version": "1.20.0","resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz","integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==","dev": true,"dependencies": {"is-core-module": "^2.2.0","path-parse": "^1.0.6"},"funding": {"url": "https://github.com/sponsors/ljharb"}},"node_modules/rollup": {"version": "2.56.2","resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.2.tgz","integrity": "sha512-s8H00ZsRi29M2/lGdm1u8DJpJ9ML8SUOpVVBd33XNeEeL3NVaTiUcSBHzBdF3eAyR0l7VSpsuoVUGrRHq7aPwQ==","dev": true,"bin": {"rollup": "dist/bin/rollup"},"engines": {"node": ">=10.0.0"},"optionalDependencies": {"fsevents": "~2.3.2"}},"node_modules/sade": {"version": "1.7.4","resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz","integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==","dev": true,"dependencies": {"mri": "^1.1.0"},"engines": {"node": ">= 6"}},"node_modules/sanitize.css": {"resolved": "../node_modules/.pnpm/sanitize.css@12.0.1/node_modules/sanitize.css","link": true},"node_modules/sass": {"resolved": "../node_modules/.pnpm/sass@1.37.5/node_modules/sass","link": true},"node_modules/secure-remote-password": {"resolved": "../node_modules/.pnpm/secure-remote-password@0.3.1/node_modules/secure-remote-password","link": true},"node_modules/source-map-js": {"version": "0.6.2","resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz","integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==","dev": true,"engines": {"node": ">=0.10.0"}},"node_modules/sourcemap-codec": {"version": "1.4.8","resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz","integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==","dev": true},"node_modules/svelte": {"resolved": "../node_modules/.pnpm/svelte@3.42.1/node_modules/svelte","link": true},"node_modules/svelte-hmr": {"version": "0.14.7","resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.7.tgz","integrity": "sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==","dev": true,"peerDependencies": {"svelte": ">=3.19.0"}},"node_modules/svelte-preprocess": {"resolved": "../node_modules/.pnpm/svelte-preprocess@4.7.4_6197623e5ed34153d1bcd9290e2954d7/node_modules/svelte-preprocess","link": true},"node_modules/uuid": {"resolved": "../node_modules/.pnpm/uuid@8.3.2/node_modules/uuid","link": true},"node_modules/vite": {"version": "2.4.4","resolved": "https://registry.npmjs.org/vite/-/vite-2.4.4.tgz","integrity": "sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw==","dev": true,"dependencies": {"esbuild": "^0.12.8","postcss": "^8.3.6","resolve": "^1.20.0","rollup": "^2.38.5"},"bin": {"vite": "bin/vite.js"},"engines": {"node": ">=12.0.0"},"optionalDependencies": {"fsevents": "~2.3.2"}}},"dependencies": {"@fortawesome/free-brands-svg-icons": {"version": "file:../node_modules/.pnpm/@fortawesome+free-brands-svg-icons@5.15.4/node_modules/@fortawesome/free-brands-svg-icons","requires": {"@fortawesome/fontawesome-common-types": "^0.2.36"}},"@fortawesome/free-solid-svg-icons": {"version": "file:../node_modules/.pnpm/@fortawesome+free-solid-svg-icons@5.15.4/node_modules/@fortawesome/free-solid-svg-icons","requires": {"@fortawesome/fontawesome-common-types": "^0.2.36"}},"@rollup/pluginutils": {"version": "4.1.1","resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.1.tgz","integrity": "sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==","dev": true,"requires": {"estree-walker": "^2.0.1","picomatch": "^2.2.2"}},"@sveltejs/adapter-node": {"version": "file:../node_modules/.pnpm/@sveltejs+adapter-node@1.0.0-next.0/node_modules/@sveltejs/adapter-node","requires": {"@sveltejs/app-utils": "1.0.0-next.0","rollup": "^2.32.0","sirv": "^1.0.7"}},"@sveltejs/kit": {"version": "1.0.0-next.146","resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.146.tgz","integrity": "sha512-MSatcaCRfjl88Prd5mW4pNOJ3Gsr525+Vjr24MoKtyTt6PZQmTfQsDVwyP93exn/6w2xl9uMCW6cFpDVBu7jSg==","dev": true,"requires": {"@sveltejs/vite-plugin-svelte": "^1.0.0-next.14","cheap-watch": "^1.0.3","sade": "^1.7.4","vite": "^2.4.3"}},"@sveltejs/vite-plugin-svelte": {"version": "1.0.0-next.15","resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.15.tgz","integrity": "sha512-8yGX7PxaqtvWw+GHiO2DV7lZ4M7DwIrFq+PgZGZ9X09PuoSeaWszm76GWQXJMKHoPPhdA9084662en9qbv4aRw==","dev": true,"requires": {"@rollup/pluginutils": "^4.1.1","debug": "^4.3.2","kleur": "^4.1.4","magic-string": "^0.25.7","require-relative": "^0.8.7","svelte-hmr": "^0.14.7"}},"cheap-watch": {"version": "1.0.3","resolved": "https://registry.npmjs.org/cheap-watch/-/cheap-watch-1.0.3.tgz","integrity": "sha512-xC5CruMhLzjPwJ5ecUxGu1uGmwJQykUhqd2QrCrYbwvsFYdRyviu6jG9+pccwDXJR/OpmOTOJ9yLFunVgQu9wg==","dev": true},"colorette": {"version": "1.3.0","resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz","integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==","dev": true},"debug": {"version": "4.3.2","resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz","integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==","dev": true,"requires": {"ms": "2.1.2"}},"esbuild": {"version": "0.12.19","resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.19.tgz","integrity": "sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w==","dev": true},"eslint": {"version": "file:../node_modules/.pnpm/eslint@7.32.0/node_modules/eslint","requires": {"@babel/code-frame": "7.12.11","@babel/core": "^7.4.3","@babel/preset-env": "^7.4.3","@eslint/eslintrc": "^0.4.3","@humanwhocodes/config-array": "^0.5.0","ajv": "^6.10.0","babel-loader": "^8.0.5","chai": "^4.0.1","chalk": "^4.0.0","cheerio": "^0.22.0","common-tags": "^1.8.0","core-js": "^3.1.3","cross-spawn": "^7.0.2","dateformat": "^3.0.3","debug": "^4.0.1","doctrine": "^3.0.0","ejs": "^3.0.2","enquirer": "^2.3.5","escape-string-regexp": "^4.0.0","eslint": "file:","eslint-config-eslint": "file:packages/eslint-config-eslint","eslint-plugin-eslint-plugin": "^3.5.3","eslint-plugin-internal-rules": "file:tools/internal-rules","eslint-plugin-jsdoc": "^25.4.3","eslint-plugin-node": "^11.1.0","eslint-release": "^2.0.0","eslint-scope": "^5.1.1","eslint-utils": "^2.1.0","eslint-visitor-keys": "^2.0.0","eslump": "^3.0.0","espree": "^7.3.1","esprima": "^4.0.1","esquery": "^1.4.0","esutils": "^2.0.2","fast-deep-equal": "^3.1.3","file-entry-cache": "^6.0.1","fs-teardown": "0.1.1","functional-red-black-tree": "^1.0.1","glob": "^7.1.6","glob-parent": "^5.1.2","globals": "^13.6.0","ignore": "^4.0.6","import-fresh": "^3.0.0","imurmurhash": "^0.1.4","is-glob": "^4.0.0","js-yaml": "^3.13.1","jsdoc": "^3.5.5","json-stable-stringify-without-jsonify": "^1.0.1","karma": "^6.1.1","karma-chrome-launcher": "^3.1.0","karma-mocha": "^2.0.1","karma-mocha-reporter": "^2.2.5","karma-webpack": "^5.0.0","levn": "^0.4.1","lint-staged": "^10.1.2","load-perf": "^0.2.0","lodash.merge": "^4.6.2","markdownlint": "^0.19.0","markdownlint-cli": "^0.22.0","memfs": "^3.0.1","minimatch": "^3.0.4","mocha": "^8.3.2","mocha-junit-reporter": "^2.0.0","natural-compare": "^1.4.0","node-polyfill-webpack-plugin": "^1.0.3","npm-license": "^0.3.3","nyc": "^15.0.1","optionator": "^0.9.1","progress": "^2.0.0","proxyquire": "^2.0.1","puppeteer": "^7.1.0","recast": "^0.19.0","regenerator-runtime": "^0.13.2","regexpp": "^3.1.0","semver": "^7.2.1","shelljs": "^0.8.2","sinon": "^9.0.1","strip-ansi": "^6.0.0","strip-json-comments": "^3.1.0","table": "^6.0.9","temp": "^0.9.0","text-table": "^0.2.0","v8-compile-cache": "^2.0.3","webpack": "^5.23.0","webpack-cli": "^4.5.0","yorkie": "^2.0.0"}},"eslint-config-prettier": {"version": "file:../node_modules/.pnpm/eslint-config-prettier@8.3.0_eslint@7.32.0/node_modules/eslint-config-prettier","requires": {}},"eslint-plugin-svelte3": {"version": "file:../node_modules/.pnpm/eslint-plugin-svelte3@3.2.0_eslint@7.32.0+svelte@3.42.1/node_modules/eslint-plugin-svelte3","requires": {"@rollup/plugin-node-resolve": "^11.2.0","@typescript-eslint/eslint-plugin": "^4.14.2","@typescript-eslint/parser": "^4.14.2","eslint": ">=6.0.0","rollup": "^2","sourcemap-codec": "1.4.8","svelte": "^3.2.0","typescript": "^4.0.0"}},"estree-walker": {"version": "2.0.2","resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz","integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==","dev": true},"fsevents": {"version": "2.3.2","resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz","integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==","dev": true,"optional": true},"function-bind": {"version": "1.1.1","resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz","integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==","dev": true},"has": {"version": "1.0.3","resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz","integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==","dev": true,"requires": {"function-bind": "^1.1.1"}},"is-core-module": {"version": "2.5.0","resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz","integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==","dev": true,"requires": {"has": "^1.0.3"}},"kleur": {"version": "4.1.4","resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz","integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==","dev": true},"lodash": {"version": "file:../node_modules/.pnpm/lodash@4.17.21/node_modules/lodash"},"magic-string": {"version": "0.25.7","resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz","integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==","dev": true,"requires": {"sourcemap-codec": "^1.4.4"}},"math": {"version": "0.0.3","resolved": "https://registry.npmjs.org/math/-/math-0.0.3.tgz","integrity": "sha1-hbAg/VTOELJqvqv81+H0vbxGRw8="},"mri": {"version": "1.1.6","resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz","integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==","dev": true},"ms": {"version": "2.1.2","resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz","integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","dev": true},"nanoid": {"version": "3.1.23","resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz","integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==","dev": true},"node-sass": {"version": "file:../node_modules/.pnpm/node-sass@6.0.1/node_modules/node-sass","requires": {"async-foreach": "^0.1.3","chalk": "^1.1.1","cross-spawn": "^7.0.3","eslint": "^7.10.0","fs-extra": "^0.30.0","gaze": "^1.0.0","get-stdin": "^4.0.1","glob": "^7.0.3","lodash": "^4.17.15","meow": "^9.0.0","mocha": "^9.0.1","nan": "^2.13.2","node-gyp": "^7.1.0","npmlog": "^4.0.0","nyc": "^15.1.0","request": "^2.88.0","rimraf": "^3.0.2","sass-graph": "2.2.5","stdout-stream": "^1.4.0","true-case-path": "^1.0.2","unique-temp-dir": "^1.0.0"}},"path-parse": {"version": "1.0.7","resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz","integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==","dev": true},"picomatch": {"version": "2.3.0","resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz","integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==","dev": true},"postcss": {"version": "8.3.6","resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz","integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==","dev": true,"requires": {"colorette": "^1.2.2","nanoid": "^3.1.23","source-map-js": "^0.6.2"}},"prettier": {"version": "file:../node_modules/.pnpm/prettier@2.2.1/node_modules/prettier"},"prettier-plugin-svelte": {"version": "file:../node_modules/.pnpm/prettier-plugin-svelte@2.3.1_prettier@2.2.1+svelte@3.42.1/node_modules/prettier-plugin-svelte","requires": {"@rollup/plugin-commonjs": "14.0.0","@rollup/plugin-node-resolve": "11.0.1","@types/node": "^10.12.18","@types/prettier": "^2.1.6","ava": "3.15.0","prettier": "^2.3.0","rollup": "2.36.0","rollup-plugin-typescript": "1.0.1","svelte": "^3.35.0","ts-node": "^9.1.1","tslib": "^2.0.3","typescript": "4.1.3"}},"query-string": {"version": "file:../node_modules/.pnpm/query-string@4.3.2/node_modules/query-string","requires": {"ava": "^0.17.0","object-assign": "^4.1.0","strict-uri-encode": "^1.0.0","xo": "^0.16.0"}},"require-relative": {"version": "0.8.7","resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz","integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=","dev": true},"resolve": {"version": "1.20.0","resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz","integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==","dev": true,"requires": {"is-core-module": "^2.2.0","path-parse": "^1.0.6"}},"rollup": {"version": "2.56.2","resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.2.tgz","integrity": "sha512-s8H00ZsRi29M2/lGdm1u8DJpJ9ML8SUOpVVBd33XNeEeL3NVaTiUcSBHzBdF3eAyR0l7VSpsuoVUGrRHq7aPwQ==","dev": true,"requires": {"fsevents": "~2.3.2"}},"sade": {"version": "1.7.4","resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz","integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==","dev": true,"requires": {"mri": "^1.1.0"}},"sanitize.css": {"version": "file:../node_modules/.pnpm/sanitize.css@12.0.1/node_modules/sanitize.css","requires": {"stylelint": "^13.6.1","stylelint-config-standard": "^20.0.0"}},"sass": {"version": "file:../node_modules/.pnpm/sass@1.37.5/node_modules/sass","requires": {"chokidar": ">=3.0.0 <4.0.0"}},"secure-remote-password": {"version": "file:../node_modules/.pnpm/secure-remote-password@0.3.1/node_modules/secure-remote-password","requires": {"array-buffer-to-hex": "^1.0.0","crypto-digest-sync": "^1.0.0","crypto-random-hex": "^1.0.0","encode-utf8": "^1.0.1","hex-to-array-buffer": "^1.1.0","jsbn": "^1.1.0","mocha": "^3.5.0","pad-start": "^1.0.2","standard": "^10.0.3"}},"source-map-js": {"version": "0.6.2","resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz","integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==","dev": true},"sourcemap-codec": {"version": "1.4.8","resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz","integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==","dev": true},"svelte": {"version": "file:../node_modules/.pnpm/svelte@3.42.1/node_modules/svelte","requires": {"@ampproject/remapping": "^0.3.0","@rollup/plugin-commonjs": "^11.0.0","@rollup/plugin-json": "^4.0.1","@rollup/plugin-node-resolve": "^6.0.0","@rollup/plugin-replace": "^2.3.0","@rollup/plugin-sucrase": "^3.1.0","@rollup/plugin-typescript": "^2.0.1","@rollup/plugin-virtual": "^2.0.0","@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.7.0","@types/mocha": "^7.0.0","@types/node": "^8.10.53","@typescript-eslint/eslint-plugin": "^4.9.0","@typescript-eslint/parser": "^4.9.0","acorn": "^8.4.1","agadoo": "^1.1.0","c8": "^5.0.1","code-red": "^0.2.2","codecov": "^3.5.0","css-tree": "^1.1.2","eslint": "^7.15.0","eslint-plugin-import": "^2.22.1","eslint-plugin-svelte3": "^2.7.3","estree-walker": "^3.0.0","is-reference": "^3.0.0","jsdom": "^15.2.1","kleur": "^3.0.3","locate-character": "^2.0.5","magic-string": "^0.25.3","mocha": "^7.0.0","periscopic": "^3.0.4","puppeteer": "^2.1.1","rollup": "^1.27.14","source-map": "^0.7.3","source-map-support": "^0.5.13","sourcemap-codec": "^1.4.8","tiny-glob": "^0.2.6","tslib": "^2.0.3","typescript": "^3.7.5"}},"svelte-hmr": {"version": "0.14.7","resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.7.tgz","integrity": "sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==","dev": true,"requires": {}},"svelte-preprocess": {"version": "file:../node_modules/.pnpm/svelte-preprocess@4.7.4_6197623e5ed34153d1bcd9290e2954d7/node_modules/svelte-preprocess","requires": {"@babel/core": "^7.10.2","@babel/preset-env": "^7.10.2","@kiwi/eslint-config": "^1.4.3","@kiwi/prettier-config": "^1.4.3","@types/jest": "^25.2.3","@types/node": "^14.0.11","@types/node-sass": "^4.11.1","@types/pug": "^2.0.4","@types/sass": "^1.16.0","@types/stylus": "^0.48.32","autoprefixer": "^9.8.0","babel-minify": "^0.5.1","coffeescript": "^2.5.1","conventional-changelog-cli": "^2.0.34","detect-indent": "^6.0.0","eslint": "^7.15.0","husky": "^4.2.5","jest": "^25.0.0","less": "^3.11.3","lint-staged": "^10.5.3","node-sass": "^4.14.1","postcss": "^8","postcss-easy-import": "^3.0.0","postcss-load-config": "^3.0.0","prettier": "^2.2.1","pug": "^3.0.0","sass": "^1.26.8","strip-indent": "^3.0.0","stylus": "^0.54.7","sugarss": "^2.0.0","svelte": "^3.23.0","ts-jest": "^25.1.0","typescript": "^3.9.5"}},"uuid": {"version": "file:../node_modules/.pnpm/uuid@8.3.2/node_modules/uuid","requires": {"@babel/cli": "7.11.6","@babel/core": "7.11.6","@babel/preset-env": "7.11.5","@commitlint/cli": "11.0.0","@commitlint/config-conventional": "11.0.0","@rollup/plugin-node-resolve": "9.0.0","babel-eslint": "10.1.0","bundlewatch": "0.3.1","eslint": "7.10.0","eslint-config-prettier": "6.12.0","eslint-config-standard": "14.1.1","eslint-plugin-import": "2.22.1","eslint-plugin-node": "11.1.0","eslint-plugin-prettier": "3.1.4","eslint-plugin-promise": "4.2.1","eslint-plugin-standard": "4.0.1","husky": "4.3.0","jest": "25.5.4","lint-staged": "10.4.0","npm-run-all": "4.1.5","optional-dev-dependency": "2.0.1","prettier": "2.1.2","random-seed": "0.3.0","rollup": "2.28.2","rollup-plugin-terser": "7.0.2","runmd": "1.3.2","standard-version": "9.0.0"}},"vite": {"version": "2.4.4","resolved": "https://registry.npmjs.org/vite/-/vite-2.4.4.tgz","integrity": "sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw==","dev": true,"requires": {"esbuild": "^0.12.8","fsevents": "~2.3.2","postcss": "^8.3.6","resolve": "^1.20.0","rollup": "^2.38.5"}}}}
# sapper-templateThe default [Sapper](https://github.com/sveltejs/sapper) template, available for Rollup and webpack.## Getting started### Using `degit`[`degit`](https://github.com/Rich-Harris/degit) is a scaffolding tool that lets you create a directory from a branch in a repository. Use either the `rollup` or `webpack` branch in `sapper-template`:```bash# for Rollupnpx degit "sveltejs/sapper-template#rollup" my-app# for webpacknpx degit "sveltejs/sapper-template#webpack" my-app```### Using GitHub templatesAlternatively, you can use GitHub's template feature with the [sapper-template-rollup](https://github.com/sveltejs/sapper-template-rollup) or [sapper-template-webpack](https://github.com/sveltejs/sapper-template-webpack) repositories.### Running the projectHowever you get the code, you can install dependencies and run the project in development mode with:```bashcd my-appnpm install # or yarnnpm run dev```Open up [localhost:3000](http://localhost:3000) and start clicking around.Consult [sapper.svelte.dev](https://sapper.svelte.dev) for help getting started.## StructureSapper expects to find two directories in the root of your project — `src` and `static`.### srcThe [src](src) directory contains the entry points for your app — `client.js`, `server.js` and (optionally) a `service-worker.js` — along with a `template.html` file and a `routes` directory.#### src/routesThis is the heart of your Sapper app. There are two kinds of routes — *pages*, and *server routes*.**Pages** are Svelte components written in `.svelte` files. When a user first visits the application, they will be served a server-rendered version of the route in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel. (Sapper will preload and cache the code for these subsequent pages, so that navigation is instantaneous.)**Server routes** are modules written in `.js` files, that export functions corresponding to HTTP methods. Each function receives Express `request` and `response` objects as arguments, plus a `next` function. This is useful for creating a JSON API, for example.There are three simple rules for naming the files that define your routes:* A file called `src/routes/about.svelte` corresponds to the `/about` route. A file called `src/routes/blog/[slug].svelte` corresponds to the `/blog/:slug` route, in which case `params.slug` is available to the route* The file `src/routes/index.svelte` (or `src/routes/index.js`) corresponds to the root of your app. `src/routes/about/index.svelte` is treated the same as `src/routes/about.svelte`.* Files and directories with a leading underscore do *not* create routes. This allows you to colocate helper modules and components with the routes that depend on them — for example you could have a file called `src/routes/_helpers/datetime.js` and it would *not* create a `/_helpers/datetime` route### staticThe [static](static) directory contains any static assets that should be available. These are served using [sirv](https://github.com/lukeed/sirv).In your [service-worker.js](src/service-worker.js) file, you can import these as `files` from the generated manifest...```jsimport { files } from '@sapper/service-worker';```...so that you can cache them (though you can choose not to, for example if you don't want to cache very large files).## Bundler configSapper uses Rollup or webpack to provide code-splitting and dynamic imports, as well as compiling your Svelte components. With webpack, it also provides hot module reloading. As long as you don't do anything daft, you can edit the configuration files to add whatever plugins you'd like.## Production mode and deploymentTo start a production version of your app, run `npm run build && npm start`. This will disable live reloading, and activate the appropriate bundler plugins.You can deploy your application to any environment that supports Node 10 or above. As an example, to deploy to [ZEIT Now](https://zeit.co/now) when using `sapper export`, run these commands:```bashnpm install -g nownow```If your app can't be exported to a static site, you can use the [now-sapper](https://github.com/thgh/now-sapper) builder. You can find instructions on how to do so in its [README](https://github.com/thgh/now-sapper#basic-usage).## Using external componentsWhen using Svelte components installed from npm, such as [@sveltejs/svelte-virtual-list](https://github.com/sveltejs/svelte-virtual-list), Svelte needs the original component source (rather than any precompiled JavaScript that ships with the component). This allows the component to be rendered server-side, and also keeps your client-side app smaller.Because of that, it's essential that the bundler doesn't treat the package as an *external dependency*. You can either modify the `external` option under `server` in [rollup.config.js](rollup.config.js) or the `externals` option in [webpack.config.js](webpack.config.js), or simply install the package to `devDependencies` rather than `dependencies`, which will cause it to get bundled (and therefore compiled) with your app:```bashnpm install -D @sveltejs/svelte-virtual-list```## Bugs and feedbackSapper is in early development, and may have the odd rough edge here and there. Please be vocal over on the [Sapper issue tracker](https://github.com/sveltejs/sapper/issues).
{"useTabs": false,"singleQuote": true,"semi": false,"trailingComma": "none","printWidth": 100}
.svelte-kit/**static/**build/**node_modules/**
engine-strict=true
.DS_Store/node_modules//src/node_modules/@sapper/yarn-error.log/cypress/screenshots//__sapper__/
module.exports = {root: true,extends: ['eslint:recommended', 'prettier'],plugins: ['svelte3'],overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],parserOptions: {sourceType: 'module',ecmaVersion: 2019},env: {browser: true,es2017: true,node: true},globals: {datastore: "readonly",authorization: "readonly"}}
#!/usr/bin/env bash. $BIN_DIR/_lib.shnpm run start
.git*.lognode_modulesapp/djiny/srcapp/djiny/cypressnginx.conf./server__sapper__/dev
#!/usr/bin/env bash. $BIN_DIR/_lib.shnpm run build
#!/usr/bin/env bashsudo apt updatesudo apt install nodejssudo apt install npmbash install_nvm.shsource ~/.profileecho "[remote] $(node -v)"curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh -o install_nvm.shnvm install --ltsnpm install -g pnpmnpm install -g pm2sudo apt-get install nginxsudo chown -R $USER /var/log/nginx/error.logsudo chown -R $USER /etc/nginx/sudo mkdir /var/cache/nginxpm2 start node --name "Djiny" -- __sapper__/buildpm2 stop Djinysudo add-apt-repository ppa:certbot/certbot
#!/bin/zshautoenv_source_parentautostash WORKING_DIR=$(dirname ${0})autostash WORKING_BIN_DIR="${WORKING_DIR}/.bin"autostash alias start="${WORKING_BIN_DIR}/start.sh"autostash alias build="${WORKING_BIN_DIR}/build.sh"
body {margin: 0;font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;font-size: 14px;line-height: 1.5;color: #333;}h1, h2, h3, h4, h5, h6 {margin: 0 0 0.5em 0;font-weight: 400;line-height: 1.2;}h1 {font-size: 2em;}a {color: inherit;}code {font-family: menlo, inconsolata, monospace;font-size: calc(1em - 2px);color: #555;background-color: #f0f0f0;padding: 0.2em 0.4em;border-radius: 2px;}@media (min-width: 400px) {body {font-size: 16px;}}
<!doctype html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1.0'><meta name='theme-color' content='#333333'>%sapper.base%<link rel='stylesheet' href='global.css'><link rel='manifest' href='manifest.json' crossorigin='use-credentials'><link rel='icon' type='image/png' href='favicon.png'><!-- Sapper generates a <style> tag containing critical CSSfor the current page. CSS for the rest of the app islazily loaded when it precaches secondary pages -->%sapper.styles%<!-- This contains the contents of the <svelte:head> component, ifthe current page has one -->%sapper.head%</head><body><!-- The application will be rendered inside this element,because `src/client.js` references it --><div id='sapper'>%sapper.html%</div><!-- Sapper creates a <script> tag containing `src/client.js`and anything else it needs to hydrate the app andinitialise the router -->%sapper.scripts%</body></html><style type="text/scss">body {height: 100vh;}#sapper {height: 100vh;}</style>
import { timestamp, files, shell, routes } from '@sapper/service-worker';const ASSETS = `cache${timestamp}`;// `shell` is an array of all the files generated by the bundler,// `files` is an array of everything in the `static` directoryconst to_cache = shell.concat(files);const cached = new Set(to_cache);self.addEventListener('install', event => {event.waitUntil(caches.open(ASSETS).then(cache => cache.addAll(to_cache)).then(() => {self.skipWaiting();}));});self.addEventListener('activate', event => {event.waitUntil(caches.keys().then(async keys => {// delete old cachesfor (const key of keys) {if (key !== ASSETS) await caches.delete(key);}self.clients.claim();}));});self.addEventListener('fetch', event => {if (event.request.method !== 'GET' || event.request.headers.has('range')) return;const url = new URL(event.request.url);// don't try to handle e.g. data: URIsif (!url.protocol.startsWith('http')) return;// ignore dev server requestsif (url.hostname === self.location.hostname && url.port !== self.location.port) return;// always serve static files and bundler-generated assets from cacheif (url.host === self.location.host && cached.has(url.pathname)) {event.respondWith(caches.match(event.request));return;}// for pages, you might want to serve a shell `service-worker-index.html` file,// which Sapper has generated for you. It's not right for every// app, but if it's right for yours then uncomment this section/*if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {event.respondWith(caches.match('/service-worker-index.html'));return;}*/if (event.request.cache === 'only-if-cached') return;// for everything else, try the network first, falling back to// cache if the user is offline. (If the pages never change, you// might prefer a cache-first approach to a network-first one.)event.respondWith(caches.open(`offline${timestamp}`).then(async cache => {try {const response = await fetch(event.request);cache.put(event.request, response.clone());return response;} catch(err) {const response = await cache.match(event.request);if (response) return response;throw err;}}));});
import sirv from 'sirv';import polka from 'polka';import compression from 'compression';import * as sapper from '@sapper/server';const { PORT, NODE_ENV } = process.env;const dev = NODE_ENV === 'development';//const setCORS = (req, res, next) => {// res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8000/auth')// next()//}polka() // You can also use Express.use(compression({ threshold: 0 }),sirv('static', { dev }),sapper.middleware()).listen(PORT, err => {if (err) console.log('error', err);});
<script>import { goto } from '@sapper/app'const chain = datastore.chain(__$$self)let tokenchain.link('token', 'state/admin/token', string => {token = _.isString(string) ? string : null})chain.link('redirect', 'session/redirect', string => {if (_.isString(string)) {goto(string)}})let acceptedchain.link('accept', 'action/admin/accept', value => {if (!value) { return }accepted = value})let rejectedchain.link('reject', 'action/admin/reject', value => {if (!value) { return }rejected = value})const authorizeService = (service) => {datastore.write(`/action/services/${service}/start`, (new Date()).getTime() / 1000)}import Icon from '../components/Icon.svelte'import Global from './_global.svelte'</script><Global /><svelte:head><title>Djiny</title></svelte:head><div class="main-wrapper" ><div class="info"><h1>Djiny Admin</h1><div class="buttons"><p>Link core Spotify account</p><div class="service-wrapper"><div class="btn service-button" on:click|preventDefault={() => authorizeService('spotify')}><Icon name="spotify"/></div></div></div></div></div><style type="text/scss">h1, h2, figure, p {margin: auto auto;text-align: center;}h1 {margin: 0 0 0.5em 0;font-weight: 700;font-size: 2.8em;text-transform: uppercase;}figure {margin: 0 0 1em 0;}.main-wrapper {display: grid;grid-template: 1 1fr / 1 1fr;align-items: center;justify-items: center;width: 100%;height: 100%;}.buttons {justify-self: center;margin: 10px;padding: 1rem;font-weight: bold;text-align: center;background: rgba(0, 0, 0, 0.4);border-radius: 5px;}.service-wrapper {display: flex;flex-direction: row;align-content: center;justify-content: space-around;}.service-button {display: grid;align-content: center;justify-content: center;width: 3em;height: 3em;background-color: var(--bg-button);border-radius: 1.5em;}p {margin: 1em auto;}</style>
<script>import { goto } from '$app/navigation'const chain = datastore.chain(__$$self)let state = 'initializing'let code, token, refresh_token, spotify_idchain.link('hash', '/session/path/query/*', object => {if (_.isPlainObject(object) && !_.isArray(object)) {if (object.code) {code = object.codedatastore.destroy('/session/path/query')datastore.write('/state/services/spotify/code', code)}}})let access_token, expiryonMount(() => {const hash = querystring.parse(window?.location?.hash ?? '#')if (hash.token_type === 'Bearer') {access_token = hash.access_tokenexpiry = Math.round((new Date()).getTime() / 1000) + Number(hash.expires_in)}})chain.link('spotify_id', '/state/services/spotify/id', string => {if (_.isString(string)) {spotify_id = datastore.read('/state/services/spotify/id')token = datastore.read('/state/services/spotify/token')refresh_token = datastore.read('/state/services/spotify/refresh_token')expiry = datastore.read('/state/services/spotify/expiry')state = 'initialized'}})chain.link('query', '/session/path/query/*', object => {if (_.isPlainObject(object) && !_.isArray(object) && object.error) {datastore.destroy('/session/path/query')}})chain.link('create', '/action/admin/create', (value, pointer) => {if (pointer.steps[1] == 'admin' && value === true) {state = 'create_requested'authorize()} else if (pointer.steps[1] == 'admin' && value === false) {state = 'create_failed'authorize()}})let password = 'nova-wellington-sandfly-turducken'const create = () => {state = 'authorization_requested'authorization.createAccount(`state/admin/#`, 'admin', password)}const authorize = () => {state = 'authorization_requested'authorization.startSession(`state/admin/#`, 'admin', password)}let completeconst accept = () => {if (!spotify_id) returndatastore.queue(`/setup/admin/expiry`, expiry)datastore.queue(`/setup/admin/code`, code)datastore.queue(`/setup/admin/token`, token)datastore.queue(`/setup/admin/refresh_token`, refresh_token)datastore.queue(`/setup/admin/id`, spotify_id)state = 'complete'}chain.link('accept', 'action/admin/auth', value => {if (value === true) {state = 'authorized'accept()} else if (value === false) {state = 'authorization_failed'}})import Global from '../../../../admin/src/routes/_global.svelte'</script><Global /><div class="wrapper">{#if state == 'initializing'}<h1>Collecting service information...</h1>{:else if state == 'initialized'}<h1>Select Operation</h1><div class="buttons" on:click={create}>Create administrative credentials</div><div class="buttons" on:click={authorize}>Update administrative credentials</div>{:else if state == 'create_requested'}<h1>Initializing administrative credentials...</h1>{:else if state == 'create_failed'}<h1>Administrative credentials already established</h1><h1>Attempting to authenticate database...</h1>{:else if state == 'authorization_requested'}<h1>Authenticating database...</h1>{:else if state == 'authorization_failed'}<h1>Authentication Failed</h1>{:else if state == 'authorized'}<h1>Saving key...</h1>{:else if state == 'complete'}<h1>Key saved to Database</h1>{:else}<h1>Unkown state: {state}</h1>{/if}</div><style type="text/scss">.wrapper {display: grid;grid-template: 1 1fr / 1 1fr;align-items: center;justify-items: center;width: 100%;height: 100%;}h1 {margin-top: auto;margin-bottom: auto;text-align: center;}.panel {display: grid;grid-auto-flow: row;padding: 2rem;background: hsla(0, 100%, 0%, 0.40);border-radius: 0.5rem;p {color: hsla(100, 25%, 100%, 0.8);}.buttons {display: grid;grid-auto-flow: column;align-items: center;}button {width: 80%;}input {width: 100%;padding: 0.25em;color: hsla(100, 25%, 100%, 0.8);background: rgba(255, 255, 255, 0.1);border: none;border-radius: 0.25em;&:-internal-autofill-selected {color: hsla(100, 25%, 100%, 0.8) !important;background: rgba(255, 255, 255, 0.1) !important;}}}</style>
<script>const inputs = Array(4)let fixme = falseimport { onMount, createEventDispatcher } from 'svelte'const dispatch = createEventDispatcher()onMount(() => {fixme = true})const oninput = event => {let value = event.target.valueif (!value.match(/^[0-9]$/)) {return}let index = parseInt(event.target.dataset.index)if (index < 3) {index++inputs[index].focus()}const pin = _.reduce(inputs,(pin, input) => {const value = input.valueif (value.length === 1) {return `${pin}${value}`} else {return pin}},'')if (pin.match(/^[0-9]{4}$/)) {dispatch('set', pin)}}const onkeyup = event => {console.log('onkeyup event.code', event.code)switch (event.code) {case 'Backspace':breakdefault:return}let value = event.target.valuelet index = parseInt(event.target.dataset.index)if (index > 0 && (value == null || value == '')) {index--inputs[index].focus()}}</script><div display="none">{fixme}</div><div><h2>Please select a pin</h2><div class="digits"><!-- svelte-ignore a11y-autofocus --><input bind:this={inputs[0]} type="text" pattern="\d*" data-index={0} on:input={oninput} autofocus /><input bind:this={inputs[1]} type="text" pattern="\d*" data-index={1} on:input={oninput} on:keyup={onkeyup} /><input bind:this={inputs[2]} type="text" pattern="\d*" data-index={2} on:input={oninput} on:keyup={onkeyup} /><input bind:this={inputs[3]} type="text" pattern="\d*" data-index={3} on:input={oninput} on:keyup={onkeyup} /></div></div><style type="text/scss">.wrapper {display: grid;grid-template: 1 1fr / 1 1fr;align-items: center;justify-items: center;width: 100%;height: 100%;}h1 {margin-top: auto;margin-bottom: auto;text-align: center;}.digits {display: grid;grid-template-columns: repeat(4, calc(25px * 4));gap: 20px;input {font-size: calc(20px * 2);text-align: center;/* FIXME: Create new variable */background: hsla(228, 54%, 8%, 0.6);border-color: transparent;border-style: solid;border-width: 2px;/*-webkit-backdrop-filter: blur(24px);backdrop-filter: blur(24px);*/transition: border-color 100ms;&:focus {border-color: var(--green);}}}</style>
<style global type="text/scss" >main {box-sizing: border-box;width: 100vw;height: 100vh;margin: 0;padding: 2em;overflow-y: scroll;background: radial-gradient(circle at top right, hsla(100, 100%, 15%, 0.8), transparent), radial-gradient(ellipse at bottom, hsla(0, 100%, 30%, 0.8), hsla(245, 100%, 10%, 0.9));}:global(h1, h2, h3, p) {color: hsla(100, 25%, 100%, 0.8);}:global(.panel) {padding: 2rem;background: var(--bg-shade);border-radius: 0.5rem;}:global(svg) {width: auto;height: 2rem;fill: var(--text-primary);}:global(.btn) {&:hover {cursor: pointer;}&:active {transform: scale(0.9) !important;}}:global(button.big) {margin: 1rem auto;color: var(--text-primary);text-align: center;background-color: var(--bg-button);border: none;border-radius: 1rem;:global(h2, h3) {margin: 0.5em;font-weight: bold;text-align: center;}}:global(.title-row) {display: grid;grid-template: 4rem / 4rem auto;align-items: center;:global(svg) {justify-self: center;z-index: 200;}:global(h1) {margin: 0;z-index: 200;}}</style><main><slot></slot></main>
<script></script><slot/><style type="text/scss">.blur {position: fixed;bottom: 0;top: 0;left: 0;right: 0;backdrop-filter: blur(8px);display: grid;z-index: 100;#prompt {background: var(--bg-shade);border-radius: 0.5em;width: 33%;align-self: center;justify-self: center;}}nav {position: fixed;bottom: 0;left: 6em;height: 5em;z-index: 200;font-weight: 300;padding: 0 1em;background: hsla(0, 100%, 0%, 0.40);border-top-right-radius: 0.5rem;:global(svg) {fill: hsla(50, 50%, 100%, 0.3);}ul {margin: 0 auto;padding: 0;display: flex;align-content: center;justify-content: space-around;width: fit-content;border-top-right-radius: 0.5rem;:global(h3) {color: hsla(50, 50%, 100%, 0.3) !important;align-self: center;margin: 0 1rem;}:global(span) {margin: 0;display: flex;flex-direction: column;color: hsla(50, 50%, 100%, 0.3);text-align: center;text-decoration: none;padding: 1em 0.5em;}:global(li) {display: block;float: left;margin: 0 1rem;&:hover {:global(svg) {fill: hsla(50, 50%, 100%, 0.8) !important;}:global(a, span) {color: hsla(50, 50%, 100%, 0.8);}}}:global([aria-current]) {position: relative;}:global([aria-current]::before) {position: absolute;content: '';width: calc(100% - 1em);height: 2px;background-color: rgb(255,62,0);display: block;top: 1px;}}}</style>
<script>export let status;export let error;const dev = process.env.NODE_ENV === 'development';</script><svelte:head><title>{status}</title></svelte:head><div class="wrapper">{#if typeof datastore === 'undefined'}<h1>Initializing...</h1>{:else}<h1>{status}</h1><p>{error.message}</p>{#if dev && error.stack}<pre>{error.stack}</pre>{/if}{/if}</div><style type="text/scss">.wrapper {display: grid;grid-template: 1 1fr / 1 1fr;align-items: center;justify-items: center;width: 100%;height: 100%;}h1 {margin-top: auto;margin-bottom: auto;text-align: center;}</style>
// WebSocket Constantsconst WEBSOCKET_READY_STATE = ['Connecting', 'Connected', 'Closing', 'Closed']const WEBSOCKET_READY_STATE_CONNECTED = 1export const QUALITY_DISCONNECTED = 0export const QUALITY_POOR = 1export const QUALITY_FAIR = 2export const QUALITY_GOOD = 3export const QUALITY_EXCELLENT = 4// TODO: Implement this fullyexport const quality = {DISCONNECTED: QUALITY_DISCONNECTED,POOR: QUALITY_POOR,FAIR: QUALITY_FAIR,GOOD: QUALITY_GOOD,EXCELLENT: QUALITY_EXCELLENT}export const connections = {server: null}let clientside = falselet global_window// FIXME: do we need this?if (typeof window !== 'undefined') {clientside = trueglobal_window = window} else {global_window = global}global_window.logger = {debug(...messages) {if (clientside) console.log(...messages) // eslint-disable-line},log(...messages) {if (clientside) console.log(...messages) // eslint-disable-line}}global_window.connections = connectionsglobal_window.ping_adjust = 0if (typeof WebSocket === 'undefined') {class WSShim {constructor() {}open() {}}global_window.WebSocket = WSShim}// Connectionclass WSBase {constructor() {this.events = {open: [],ready: [],message: [],close: [],error: []}this.ping_id = 0}open() {this.websocket.onopen = event => {_.each(this.events.open, callback => {callback(event)})}this.websocket.onmessage = event => {_.each(this.events.message, function(callback) {callback(event)})}this.websocket.onclose = event => {_.each(this.events.close, function(callback) {callback(event)})}this.websocket.onerror = event => {_.each(this.events.error, function(callback) {callback(event)})}}close() {this.websocket = nullthis.events = {open: [],message: [event => this.onping(event)],close: [],error: []}}ping() {setTimeout(() => {const node_id = datastore.get('/session/node_id')const node_path = `/nodes/${node_id}`const base_path = `${node_path}/pings/${this.ping_id++}`this.send({c: node_path,o: 'w',p: `${base_path}/started_at`,v: Date.now()})this.ping()}, datastore.get('/session/ping_interval'))}on(eventName, callback) {switch (eventName) {case 'open':case 'ready':case 'message':case 'close':case 'error':this.events[eventName].push(callback)}}off(eventName, callback) {switch (eventName) {case 'open':case 'ready':case 'message':case 'close':case 'error':breakdefault:return}const index = this.events[eventName].indexOf(callback)this.events[eventName].splice(index, 1)}get readyState() {return this.websocket.readyState}get connection() {return WEBSOCKET_READY_STATE[this.websocket.readyState]}}// Connectionclass WS extends WSBase {constructor(options = {}) {super()options = Object.assign({ connection: 'server', overwrite: false }, options)this.max_attempts = options.attempts || 0this.attempts = 0this.host = (() => {let host = ''if (options.host != null) {host = options.protocol || 'ws://'host += options.hostif (options.port) {host += ':' + options.port}}return host})()datastore.set('/session/authenticated', false)this.on('open', () => {this.attempts = 0console.log('Connection opened successfully', this.host, options)if (options.connection) {if (connections[options.connection]) {if (!options.overwrite) {return connections[options.connection]} else {connections[options.connection].overwritten = trueconnections[options.connection].close()}}connections[options.connection] = this}})this.reconnect = _.debounce(this.open.bind(this), 1000, {leading: false,trailing: true})this.on('ready', this.ping.bind(this))// this.on('message', event => logger.debug('onmessage', event))this.on('close', this.reconnect)this.open()}open() {if (this.overwritten) {// eslint-disable-next-line no-consoleconsole.warn('Connection overwritten. Not attempting to reconnect.')return}if (this.max_attempts > 0 && this.attempts >= this.max_attempts) {console.warn('Maximum number of connection attempts hit. No longer attempting to connect.')return}this.attempts += 1console.log('attempting to connect to', this.host, this.attempts)this.websocket = new WebSocket(this.host)typeof window !== 'undefined' &&window.addEventListener('beforeunload',() => {this.websocket.close()},false)super.open()}close() {this.events.close[0] = null // prevent automatic reconnectthis.websocket.close()super.close()}send(payload) {if (this.websocket == null) {return}const message = JSON.stringify(payload)if (this.websocket.readyState !== WEBSOCKET_READY_STATE_CONNECTED) {const callback = (called = false) => () => {if (!called) {this.send(payload)called = true}}this.on('open', callback())} else {this.websocket.send(message)}}destroy() {this.close()}get readyState() {return this.websocket.readyState}get connection() {return WEBSOCKET_READY_STATE[this.websocket.readyState]}}export { WS }
let global_windowif (typeof window !== 'undefined') {global_window = window} else {global_window = global}export const unsubscribe = (topic) => {console.log('subscribe()', 'topic:', topic)let operation = 'u'let payloadpayload = {o: operation,p: topic}const ws = global_window.connections['node']if (ws) {ws.send(payload)}}
class ThemeHandler {constructor() {this.root = document.querySelector(':root')this.setTheme()}setTheme() {/* Base */this.root.classList.add('theme-light')this.root.style.setProperty('--text-primary', 'hsla(100, 25%, 100%, 0.8)')this.root.style.setProperty('--text-secondary', 'hsla(100, 25%, 100%, 0.4)')this.root.style.setProperty('--bg-shade', 'hsla(0, 100%, 0%, 0.40)')this.root.style.setProperty('--bg-button', 'hsl(35, 100%, 45%, 1)')/* Semantic */}}export { ThemeHandler }
let global_windowif (typeof window !== 'undefined') {global_window = window} else {global_window = global}export const subscribe = (topic, initialize) => {console.log('subscribe()', 'topic:', topic)let operation = 's'let payloadpayload = {o: operation,p: topic}if (initialize) { payload.i = true}const ws = global_window.connections['node']if (ws) {ws.send(payload)}}
let global_windowif (typeof window !== 'undefined') {global_window = window} else {global_window = global}export const send = (value, path) => {console.log('send()', 'value:', value, 'path:', path)let operation = 'm'let payloadif (value == null) {operation = 'd'}payload = {o: operation,p: path,v: value}const ws = global_window.connections['node']if (ws) {ws.send(payload)}}
let global_windowif (typeof window !== 'undefined') {global_window = window} else {global_window = global}import srp from "secure-remote-password/client"class clientAuthorization {constructor() {this._authorizations = {}}createAccount(topic, uuid, password) {if (!this._authorizations[topic]) this._authorizations[topic] = {}const auth = this._authorizations[topic]const salt = srp.generateSalt()auth.privateKey = srp.derivePrivateKey(salt, uuid, password)auth.verifier = srp.deriveVerifier(auth.privateKey)const payload = {o: 'a',c: 0,t: topic,v: {u: uuid,v: auth.verifier,s: salt}}console.log('createAccount()')const ws = global_window.connections['node']console.log(payload)if (ws) {ws.send(payload)}}startSession(topic, uuid, password) {if (!this._authorizations[topic]) this._authorizations[topic] = {}const auth = this._authorizations[topic]auth.uuid = uuidauth.password = passwordauth.ephemeral = srp.generateEphemeral()const payload = {o: 'a',c: 1,t: topic,v: {u: uuid,k: auth.ephemeral.public}}console.log('startSession()')const ws = global_window.connections['node']if (ws) {ws.send(payload)}}deriveSharedKey(topic, packet, ws) {console.log(topic, this._authorizations)const auth = this._authorizations[topic]if (auth === undefined) { return }const pointer = Pointer.create(topic)const salt = packet.sconst server_ephemeral = packet.kif (!auth.privateKey) {auth.privateKey = srp.derivePrivateKey(salt, auth.uuid, auth.password)}auth.session = srp.deriveSession(auth.ephemeral.secret, server_ephemeral, salt, auth.uuid, auth.privateKey)datastore.write(`/session${pointer.trunk_path}/key`, auth.session.key)const payload = {o: 'a',t: topic,c: 4,v: {u: packet.u,p: auth.session.proof}}ws.send(payload)}verifySessionKey(topic, packet) {const auth = this._authorizations[topic]if (auth === undefined) { return }const pointer = Pointer.create(topic)const server_proof = packet.ptry {srp.verifySession(auth.ephemeral.public, auth.session, server_proof)console.log('verifySessionKey() authorized')datastore.destroy(`/session${pointer.trunk_path}/key`)datastore.write(`/action${pointer.trunk_path}/auth`, true)datastore.destroy(`/action${pointer.trunk_path}/auth`)return true} catch (err) {console.log('verifySessionKey() failed')console.log(err)datastore.destroy(`/session${pointer.trunk_path}/key`)datastore.write(`/action${pointer.trunk_path}/auth`, false)datastore.destroy(`/action${pointer.trunk_path}/auth`)return false}}requestPersistantSession(uuid, ws) {console.log(`requestPersistantSession()`)const payload = {o: 'a',c: 6,v: uuid}ws.send(payload)}setPersistantSession(cookie, ws) {console.log(`setPersistantSession()`)console.log(cookie)const uuid = cookie.split(':').slice(0, 1)[0]datastore.write('/session/user/uuid', uuid)console.log(cookie)document.cookie = 'session-data=' + cookie + ';'}getPersistantSession(ws) {const cookies = document.cookie.split('; ')const session_cookie = cookies.find(item => item.startsWith('session-data'))if (!session_cookie) { return }console.log(session_cookie.split('=').slice(-1)[0])const payload = {o: 'a',c: 5,v: session_cookie.split('=').slice(-1)[0]}ws.send(payload)}pickUpSession(session_topic, packet, ws) {_.forEach(packet.s, topic => {console.log({o: 's', p: topic, i: true})ws.send({o: 's', p: topic, i: true})})datastore.write(`/session/user/uuid`, packet.u)this.requestPersistantSession(packet.u, ws)}reset(topic) {delete this._authorizations[topic]}reset() {this._authorizations = {}}}export const authorization = new clientAuthorization
<style type="text/scss">button.big {border: none;border-radius: 1rem;text-align: center;color: hsla(100, 25%, 100%, 0.8);background-color: coral;margin: 1rem auto;h3 {font-weight: bold;text-align: center;margin: 0.5em;}}</style>
<script>import { faCaretUp, faCaretDown, faCaretRight, faCheck, faCircle, faExclamationTriangle, faTrash, faStickyNote, faWrench, faIgloo, faSearch, faUser, faRecordVinyl, faChartBar, faFolderOpen, faAngleDoubleLeft, faGuitar, faCompass, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'import { faSpotify, faApple, faCanadianMapleLeaf } from '@fortawesome/free-brands-svg-icons'export let name = 'dot'let className = ''export { className as class }export let found = trueexport let scale = 1// font awesome properties are take as additional props via metalet widthlet heightlet pathlet labellet box = `0 0 0 0`let styleconst solid_icons = {caret_up: faCaretUp,caret_down: faCaretDown,caret_right: faCaretRight,check: faCheck,circle: faCircle,exclamation_triangle: faExclamationTriangle,trash: faTrash,note: faStickyNote,wrench: faWrench,igloo: faIgloo,search: faSearch,user: faUser,chart: faChartBar,folder: faFolderOpen,angle_double_left: faAngleDoubleLeft,guitar: faGuitar,compass: faCompass,record: faRecordVinyl,'sign-out': faSignOutAlt}const brand_icons = {spotify: faSpotify,apple: faApple,maple_leaf: faCanadianMapleLeaf}const classEval = (className, svgName) => {if (className != '') {if (solid_icons[svgName]) {found = truereturn [solid_icons[svgName], className]} else if (brand_icons[svgName]) {found = truereturn [brand_icons[svgName], className]} else {found = falsereturn [faCircle, className]}} else if (solid_icons[svgName]) {found = truereturn [solid_icons[svgName],solid_icons[svgName].prefix + ' ' + solid_icons[svgName].iconName]} else if (brand_icons[svgName]) {found = truereturn [brand_icons[svgName],brand_icons[svgName].prefix + ' ' + brand_icons[svgName].iconName]} else {found = falsereturn [ faCircle, 'fas fa-circle' ]}}let [data, svgClassName] = classEval(className, name)$: [data, svgClassName] = classEval(className, name)const propEval = props => {const entries = Object.entries(props)return entries.reduce((result, [key, value]) => {if (['class', 'name', 'found', 'scale'].includes(key)) {return result}if (value === true){result.push('fa-' + key)} else if (value !== false) {result.push('fa-' + key +'-' + value)}return result}, []).join(' ')}let props = propEval($$props)$: props = propEval($$props)$: {const [_width, _height /* _ligatures */ /* _unicode */, , , _svgPathData] = data.iconwidth = _widthheight = _heightpath = _svgPathDatalabel = data.iconNamebox = `0 0 ${width} ${height}`style = `font-size: ${scale}em`}</script><svgversion="1.1"class="fa-icon {className} {props}"x={0}y={0}{width}{height}data-icon={name}aria-label={label}role={label ? 'img' : 'presentation'}viewBox={box}{style}><path d={path} /></svg><style type="text/scss">svg.fa-spin {-webkit-animation-name: spin;-moz-animation-name: spin;-ms-animation-name: spin;animation-name: spin;-webkit-animation-duration: 4000ms;-moz-animation-duration: 4000ms;-ms-animation-duration: 4000ms;animation-duration: 4000ms;-webkit-animation-timing-function: linear;-moz-animation-timing-function: linear;-ms-animation-timing-function: linear;animation-timing-function: linear;-webkit-animation-iteration-count: infinite;-moz-animation-iteration-count: infinite;-ms-animation-iteration-count: infinite;animation-iteration-count: infinite;}@-moz-keyframes spin {from {-moz-transform: rotate(0deg);}to {-moz-transform: rotate(360deg);}}@-webkit-keyframes spin {from {-webkit-transform: rotate(0deg);}to {-webkit-transform: rotate(360deg);}}@keyframes spin {from {transform:rotate(0deg);}to {transform:rotate(360deg);}}</style>
import lodash from 'lodash-es'window._ = lodashimport { Datastore, Pointer } from '@djinlist/datastore'import { send } from './lib/send'import { authorization } from './lib/authorization'import { subscribe } from './lib/subscribe'import { unsubscribe } from './lib/unsubscribe'import Loadable from 'svelte-loadable'window.datastore = new Datastorewindow.Pointer = Pointerwindow.send = sendwindow.authorization = authorizationwindow.subscribe = subscribewindow.unsubscribe = unsubscribewindow.Loadable = Loadableimport { ThemeHandler } from './lib/theme'window.theme = new ThemeHandler()// configdatastore.set('session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')datastore.set('session/ping_interval', 10000)datastore._chain = datastore.chaindatastore.chain = svelte => {const chain = datastore._chain()if (svelte === void 0) {return chain}svelte.$$.on_destroy.push(() => {chain.destroy()})return chain}// Driversimport { Spotify } from './lib/spotify'const spotify = new Spotify(datastore)import { Bootstrap } from './bootstrap.js'let bootstrapimport * as sapper from '@sapper/app';const load = () => {window.document.body.addEventListener('touchstart touchend', function(e) {e.preventDefault();e.toggleClass('hover_effect');})console.log('Loading Djinlist bootstrap')if (bootstrap != null) {console.warn(`index.js ${VERSION} load()`, 'bootstrap already exists') // eslint-disable-line no-consolereturn}new Notification({target: document.querySelector('#notifications')})bootstrap = new Bootstrap()}sapper.start({target: document.querySelector('#sapper')}).then(load)
import querystring from 'query-string'import { WS } from './lib/websocket'import { goto } from '@sapper/app'class Bootstrap {constructor() {this.url = nullwindow.notify = this.notify.bind(this)// Open Websocketconst parseWebsocketOrigin = (string) => {if (/localhost/.test(string)) {return {host: 'localhost',protocol: 'ws://',port: 25706}} else {return {host: 'www.processor.' + string.match(/(?<=\/\/)(www.app.)?([^:]*)/)[2],protocol: 'wss://',}}}const ws = new WS(Object.assign(parseWebsocketOrigin(location.origin), {connection: 'node'}))ws.on('open', () => {this.notify({error: 'Connected to node',duration: 1000,prompt: false,duplicate: false})// authorization.getPersistantSession(ws)})ws.on('message', ({ data }) => {try {const message = JSON.parse(data)console.log(`incoming ${data}`)switch (message.o) {case 'p':case 'r':if (message.v === null) {message.v = undefined}const path = message.pconst value = message.vif (_.isPlainObject(message.v)) {datastore.merge(path, value)} else {datastore.set(path, value)}const queued = datastore.get('/q' + message.p)if (_.isEqual(message.v, queued)) {const q_path = '/q' + message.pconst q_steps = q_path.slice(1).split('/')datastore.delete(q_steps, { silent: true })}breakcase 'a':console.log(message)if (message.v === undefined) { return }console.log(message.c)switch (message.c) {case 0:const pointer = Pointer.create(message.t).replace('/#', '/create').replace('state', 'action')datastore.write(pointer, message.v)datastore.destroy(pointer, message.v)if (!message.v) authorization.reset(message.t)breakcase 2:authorization.deriveSharedKey(message.t, message.v, ws)breakcase 4:if (authorization.verifySessionKey(message.t, message.v, ws) &&/\/users/.test(message.t)) {authorization.requestPersistantSession(message.v.u, ws)}breakcase 5:if (!message.v) { break }authorization.pickUpSession(message.t, message.v, ws)breakcase 6:if (!message.v) { break }authorization.setPersistantSession(message.v, ws)break}break}} catch (e) {console.error('Error parsing message from server.', e, data)}})ws.on('close', () => {this.notify({error: 'Disconnected from djin node',duration: 0,duplicate: false})})const subscriber = {}datastore.subscribe('q/setup/#', subscriber, (topic, pointer, value) => {if (_.isPlainObject(value)) {return}send(value, pointer.path)})datastore.subscribe('session/redirect', subscriber, (topic, pointer, value) => {if (_.isString(value)) {datastore.write('/session/redirect', null)goto(value)}})// Bind to url parametersdatastore.subscribe('session/path/*', this, (topic, pointer, value) => {try {const { route, query, hash } = valuelet next = route || '/'localStorage.setItem('djinlist', JSON.stringify(value))const session_query = {}Object.keys(query || {}).sort().forEach(key => {session_query[key] = query[key]})const session_hash = {}Object.keys(hash || {}).sort().forEach(key => {session_hash[key] = hash[key]})next = next +(session_query && (session_query.length != 0) ? '?' + querystring.stringify(session_query) : '') +(session_hash && (session_hash.length != 0) ? '#' + querystring.stringify(session_hash) : '')if ((location.pathname || '' + location.query || '' + location.hash || '') === next) {return}if (!('standalone' in navigator && navigator.standalone === true)) {history.replaceState({}, '', next)}} catch (e) {return}})/* Query Parameters */if (!('standalone' in navigator && navigator.standalone === true)) {window.addEventListener('popstate', this.parse)}setTimeout(() => this.initialize(), 0)}parse() {const origin = location.originconst route = location.pathnameconst query = querystring.parse(location.search.slice(1))const hash = querystring.parse(location.hash.slice(1))let match = route.match(/^(\/\w+)*/)let evaluateRoute = ({ route, query, hash }) => {/* Setup */datastore.write('/session/path', {origin,route,query,hash})}if (match != null) {let [, route ] = matchevaluateRoute({ route, query, hash })} else {const local = localStorage.getItem('djinlist')if (local == null) {evaluateRoute({ route: null, query, hash })return}const local_json = JSON.parse(local)evaluateRoute(_.merge(local_json, {query, hash}))}return}// Initialize and define parsing methodsinitialize() {this.parse()}notify(options) {let id = 1while (datastore.has(`/session/notifications/${id}`)) {id += 1}const notification = Object.assign({},{id,time: Date.now(),duration: 2000,prompt: true,message: options.error.toString() || 'An error occurred',error: 'An error occurred',duplicate: true,overwrite: false},options)if (!notification.duplicate) {const notifications = datastore.read('/session/notifications/+')for (const n of Object.values(notifications)) {if (n.message === notification.message && !n.hidden && !n.overwrite) {return}}}datastore.write(`/session/notifications/${id}`, notification)}}export { Bootstrap }
import resolve from '@rollup/plugin-node-resolve';import replace from '@rollup/plugin-replace';import commonjs from '@rollup/plugin-commonjs';import re from 'rollup-plugin-re';import svelte from 'rollup-plugin-svelte';import { scss } from 'svelte-preprocess'import babel from 'rollup-plugin-babel';import globals from 'rollup-plugin-node-globals';import { terser } from 'rollup-plugin-terser';import config from 'sapper/config/rollup.js';import pkg from './package.json';const mode = process.env.NODE_ENV;const dev = mode === 'development';const legacy = !!process.env.SAPPER_LEGACY_BUILD;const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning);export default {client: {input: config.client.input(),output: config.client.output(),plugins: [replace({'process.browser': true,'process.env.NODE_ENV': JSON.stringify(mode)}),svelte({dev,hydratable: true,emitCss: false,css: true,preprocess: [scss()]}),resolve({browser: true,dedupe: ['svelte']}),commonjs(),re({replaces: {__$$self: '$$self'}}),legacy && babel({extensions: ['.js', '.mjs', '.html', '.svelte'],runtimeHelpers: true,exclude: ['node_modules/@babel/**'],presets: [['@babel/preset-env', {targets: '> 0.25%, not dead'}]],plugins: ['@babel/plugin-syntax-dynamic-import',['@babel/plugin-transform-runtime', {useESModules: true}]]}),!dev && terser({module: true})],onwarn,},server: {input: config.server.input(),output: config.server.output(),plugins: [replace({'process.browser': false,'process.env.NODE_ENV': JSON.stringify(mode)}),svelte({generate: 'ssr',dev,preprocess: [scss()]}),resolve({dedupe: ['svelte']}),commonjs()],external: Object.keys(pkg.dependencies).concat(require('module').builtinModules || Object.keys(process.binding('natives'))),onwarn,},serviceworker: {input: config.serviceworker.input(),output: config.serviceworker.output(),plugins: [resolve(),replace({'process.browser': true,'process.env.NODE_ENV': JSON.stringify(mode)}),commonjs(),!dev && terser()],onwarn,}};
{"name": "@djinlist/client","description": "djiny client","version": "0.0.1","scripts": {"dev": "sapper dev","build": "sapper build --legacy","export": "sapper export --legacy","start": "PORT=6443 node __sapper__/build"},"dependencies": {"@fortawesome/free-brands-svg-icons": "^5.12.0","@fortawesome/free-solid-svg-icons": "^5.12.0","compression": "^1.7.1","lodash-es": "^4.17.15","polka": "next","sass": "^1.26.9","sirv": "^0.4.0","svelte-loadable": "^1.4.0"},"devDependencies": {"@babel/core": "^7.0.0","@babel/plugin-syntax-dynamic-import": "^7.0.0","@babel/plugin-transform-runtime": "^7.0.0","@babel/preset-env": "^7.0.0","@babel/runtime": "^7.0.0","@rollup/plugin-commonjs": "11.0.2","@rollup/plugin-node-resolve": "^7.0.0","@rollup/plugin-replace": "^2.2.0","node-sass": "^6.0.1","npm-run-all": "^4.1.5","postcss": "^7.0.26","query-string": "4.3.2","rollup": "^1.20.0","rollup-plugin-babel": "^4.0.2","rollup-plugin-re": "^1.0.7","rollup-plugin-scss": "^1.0.1","rollup-plugin-svelte": "^5.0.1","rollup-plugin-terser": "^5.3.0","rollup-plugin-node-globals": "^1.4.0","sapper": "^0.27.0","uuid": "^8.0.0"}}
// ***********************************************************// This example support/index.js is processed and// loaded automatically before your test files.//// This is a great place to put global configuration and// behavior that modifies Cypress.//// You can change the location of this file or turn off// automatically serving support files with the// 'supportFile' configuration option.//// You can read more here:// https://on.cypress.io/configuration// ***********************************************************// Import commands.js using ES2015 syntax:import './commands'// Alternatively you can use CommonJS syntax:// require('./commands')
// ***********************************************// This example commands.js shows you how to// create various custom commands and overwrite// existing commands.//// For more comprehensive examples of custom// commands please read more here:// https://on.cypress.io/custom-commands// ***********************************************////// -- This is a parent command --// Cypress.Commands.add("login", (email, password) => { ... })////// -- This is a child command --// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })////// -- This is a dual command --// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })////// -- This is will overwrite an existing command --// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
// ***********************************************************// This example plugins/index.js can be used to load plugins//// You can change the location of this file or turn off loading// the plugins file with the 'pluginsFile' configuration option.//// You can read more here:// https://on.cypress.io/plugins-guide// ***********************************************************// This function is called when a project is opened or re-opened (e.g. due to// the project's config changing)module.exports = (on, config) => {// `on` is used to hook into various events Cypress emits// `config` is the resolved Cypress config}
describe('Sapper template app', () => {beforeEach(() => {cy.visit('/')});it('has the correct <h1>', () => {cy.contains('h1', 'Great success!')});it('navigates to /about', () => {cy.get('nav a').contains('about').click();cy.url().should('include', '/about');});it('navigates to /blog', () => {cy.get('nav a').contains('blog').click();cy.url().should('include', '/blog');});});
{"name": "Using fixtures to represent data","email": "hello@cypress.io","body": "Fixtures are a great way to mock data for responses to routes"}
# sapper-templateThe default [Sapper](https://github.com/sveltejs/sapper) template, available for Rollup and webpack.## Getting started### Using `degit`[`degit`](https://github.com/Rich-Harris/degit) is a scaffolding tool that lets you create a directory from a branch in a repository. Use either the `rollup` or `webpack` branch in `sapper-template`:```bash# for Rollupnpx degit "sveltejs/sapper-template#rollup" my-app# for webpacknpx degit "sveltejs/sapper-template#webpack" my-app```### Using GitHub templatesAlternatively, you can use GitHub's template feature with the [sapper-template-rollup](https://github.com/sveltejs/sapper-template-rollup) or [sapper-template-webpack](https://github.com/sveltejs/sapper-template-webpack) repositories.### Running the projectHowever you get the code, you can install dependencies and run the project in development mode with:```bashcd my-appnpm install # or yarnnpm run dev```Open up [localhost:3000](http://localhost:3000) and start clicking around.Consult [sapper.svelte.dev](https://sapper.svelte.dev) for help getting started.## StructureSapper expects to find two directories in the root of your project — `src` and `static`.### srcThe [src](src) directory contains the entry points for your app — `client.js`, `server.js` and (optionally) a `service-worker.js` — along with a `template.html` file and a `routes` directory.#### src/routesThis is the heart of your Sapper app. There are two kinds of routes — *pages*, and *server routes*.**Pages** are Svelte components written in `.svelte` files. When a user first visits the application, they will be served a server-rendered version of the route in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel. (Sapper will preload and cache the code for these subsequent pages, so that navigation is instantaneous.)**Server routes** are modules written in `.js` files, that export functions corresponding to HTTP methods. Each function receives Express `request` and `response` objects as arguments, plus a `next` function. This is useful for creating a JSON API, for example.There are three simple rules for naming the files that define your routes:* A file called `src/routes/about.svelte` corresponds to the `/about` route. A file called `src/routes/blog/[slug].svelte` corresponds to the `/blog/:slug` route, in which case `params.slug` is available to the route* The file `src/routes/index.svelte` (or `src/routes/index.js`) corresponds to the root of your app. `src/routes/about/index.svelte` is treated the same as `src/routes/about.svelte`.* Files and directories with a leading underscore do *not* create routes. This allows you to colocate helper modules and components with the routes that depend on them — for example you could have a file called `src/routes/_helpers/datetime.js` and it would *not* create a `/_helpers/datetime` route### staticThe [static](static) directory contains any static assets that should be available. These are served using [sirv](https://github.com/lukeed/sirv).In your [service-worker.js](src/service-worker.js) file, you can import these as `files` from the generated manifest...```jsimport { files } from '@sapper/service-worker';```...so that you can cache them (though you can choose not to, for example if you don't want to cache very large files).## Bundler configSapper uses Rollup or webpack to provide code-splitting and dynamic imports, as well as compiling your Svelte components. With webpack, it also provides hot module reloading. As long as you don't do anything daft, you can edit the configuration files to add whatever plugins you'd like.## Production mode and deploymentTo start a production version of your app, run `npm run build && npm start`. This will disable live reloading, and activate the appropriate bundler plugins.You can deploy your application to any environment that supports Node 10 or above. As an example, to deploy to [ZEIT Now](https://zeit.co/now) when using `sapper export`, run these commands:```bashnpm install -g nownow```If your app can't be exported to a static site, you can use the [now-sapper](https://github.com/thgh/now-sapper) builder. You can find instructions on how to do so in its [README](https://github.com/thgh/now-sapper#basic-usage).## Using external componentsWhen using Svelte components installed from npm, such as [@sveltejs/svelte-virtual-list](https://github.com/sveltejs/svelte-virtual-list), Svelte needs the original component source (rather than any precompiled JavaScript that ships with the component). This allows the component to be rendered server-side, and also keeps your client-side app smaller.Because of that, it's essential that the bundler doesn't treat the package as an *external dependency*. You can either modify the `external` option under `server` in [rollup.config.js](rollup.config.js) or the `externals` option in [webpack.config.js](webpack.config.js), or simply install the package to `devDependencies` rather than `dependencies`, which will cause it to get bundled (and therefore compiled) with your app:```bashnpm install -D @sveltejs/svelte-virtual-list```## Bugs and feedbackSapper is in early development, and may have the odd rough edge here and there. Please be vocal over on the [Sapper issue tracker](https://github.com/sveltejs/sapper/issues).
.DS_Store/node_modules//src/node_modules/@sapper/yarn-error.log/cypress/screenshots//__sapper__/
#!/usr/bin/env bash. $BIN_DIR/_lib.shnpm run start
.git*.lognode_modulesapp/djiny/srcapp/djiny/cypressnginx.conf./server__sapper__/dev
#!/usr/bin/env bash. $BIN_DIR/_lib.shnpm run build
#!/usr/bin/env bashsudo apt updatesudo apt install nodejssudo apt install npmbash install_nvm.shsource ~/.profileecho "[remote] $(node -v)"curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh -o install_nvm.shnvm install --ltsnpm install -g pnpmnpm install -g pm2sudo apt-get install nginxsudo chown -R $USER /var/log/nginx/error.logsudo chown -R $USER /etc/nginx/sudo mkdir /var/cache/nginxpm2 start node --name "Djiny" -- __sapper__/buildpm2 stop Djinysudo add-apt-repository ppa:certbot/certbot
#!/usr/bin/env bash. $BIN_DIR/_lib.sh#npm run buildrsync --progress -Pavuz --exclude-from="$WORKING_BIN_DIR/rsync-deploy.ignore" -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" "${MONO_DIR}/." "tpcowan@djinmusic.ca:/home/tpcowan/djinmusic"rsync --progress -Pavuz -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" "${MONO_DIR}/.nginx/" "tpcowan@djinmusic.ca:/etc/nginx/sites-available"rsync --progress -Pavuz -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" "${WORKING_BIN_DIR}/deploy-remote.sh" "tpcowan@djinmusic.ca:/home/tpcowan/deploy-remote.sh"echo "$HOME/.ssh/id_rsa_corda_digital_ocean"ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean -t tpcowan@processor.djinmusic.ca "sh /home/tpcowan/deploy-remote.sh"
#!/usr/bin/env bashif [[ "$(uname -s)" == "Darwin" ]]; thenecho "Don't run this on your local computer!"exit 1fiecho "[remote] linking nginx configuration"sudo ln -s /etc/nginx/sites-available/djinlist /etc/nginx/sites-enabled/echo "[remote] Updating processor"cd djinmusicpnpm install -rcd ..pm2 stop Djinypm2 start Djiny# pm2 stop processor# pm2 start processorecho "[remote] Installed"
#!/bin/zshautoenv_source_parentautostash WORKING_DIR=$(dirname ${0})autostash WORKING_BIN_DIR="${WORKING_DIR}/.bin"autostash alias start="${WORKING_BIN_DIR}/start.sh"autostash alias build="${WORKING_BIN_DIR}/build.sh"
#!/usr/bin/env bash. $BIN_DIR/_lib.shecho '🔎 Linting ...'# Lint JavaScriptJS_ERROR_CODE=0FILES=$(find $WORKING_DIR \( -name '*.js' -o -name '*.html' -o -name '*.svelte' \) -not -path "$WORKING_DIR/.reify-cache/*" -not -path "$WORKING_DIR/node_modules/*" -not -path "$WORKING_DIR/dist/*" -not -path "$WORKING_DIR/tmp/*")if [[ "${FILES:-x}" != "x" ]]; thenecho -e " $(echo "$FILES" | wc -l | awk '{print $1}') JavaScript, HTML, and Svelte files"set +eeslint -c "$MONO_DIR/.eslintrc.js" --ignore-path "$MONO_DIR/.eslintignore" $FILESJS_ERROR_CODE=$?set -efi# Lint SCSSCSS_ERROR_CODE=0FILES=$(find $WORKING_DIR \( -name '*.scss' -o -name '*.scss' -o -name '*.html' -o -name '*.svelte' \) -not -path "$WORKING_DIR/.reify-cache/*" -not -path "$WORKING_DIR/node_modules/*" -not -path "$WORKING_DIR/dist/*" -not -path "$WORKING_DIR/tmp/*")if [[ "${FILES:-x}" != "x" ]]; thenecho -e " $(echo "$FILES" | wc -l | awk '{print $1}') CSS files"set +estylelint --fix --ignore-path "$MONO_DIR/.stylelintignore" $FILESCSS_ERROR_CODE=$?set -efiif [[ $JS_ERROR_CODE -gt 0 ]] || [[ $CSS_ERROR_CODE -gt 0 ]]; thenexit 1fi
#!/bin/zshautoenv_source_parentautostash WORKING_DIR=$(dirname ${0})autostash APP_DIR="${MONO_DIR}/app"autostash APP_BIN_DIR="${APP_DIR}/.bin"autostash alias lint="${APP_BIN_DIR}/lint.sh"