SBSUV4ZJX7FZS7JUBQBSVENWODXRKTXSNLG57OGPLUXNN5SVEQJAC
packages:
- channel
importers:
content:
dependencies:
uuid: 8.3.0
ws: 6.2.1
specifiers:
uuid: ^8.0.0
ws: ^6.0.0
lockfileVersion: 5.1
packages:
/async-limiter/1.0.1:
dev: false
resolution:
integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
/uuid/8.3.0:
dev: false
hasBin: true
resolution:
integrity: sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
/ws/6.2.1:
dependencies:
async-limiter: 1.0.1
dev: false
resolution:
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) return
let 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')) &&
timestamp > + 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 + ':' + token
console.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 = 25706
const wss = new WebSocket.Server({ port })
console.log(`Listenning on ${port}`)
wss.on('connection', function connection(ws, request) {
const ip = request.socket.remoteAddress
ws.uuid = uuid()
ws.toString = () => ws.uuid
console.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_id
hello.p = '/session/node_id'
ws.send(JSON.stringify(hello))
const publish = (topic, pointer, value) => {
const message = {}
message.o = 'p'
message.p = pointer.path
message.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, channel
switch (message.o) {
case 'a':
console.dir(message)
switch (message.c) {
case 0:
// createAccount
pointer = Pointer.create(message.t)
channel = channels[pointer.trunk_steps[0]]
channel.createAuthorization(pointer, message.v, ws)
break
case 1:
// startSession
pointer = Pointer.create(message.t)
channel = channels[pointer.trunk_steps[0]]
channel.authorize(pointer, message.v, ws)
break
case 4:
// verifySession
pointer = Pointer.create(message.t)
channel = channels[pointer.trunk_steps[0]]
if (channel.prove(pointer, message.v, ws)) {
session.addSubscription(message.v.u, pointer)
}
break
case 5:
// post session
session.fetchSession(message.v, ws, channels)
break
case 6:
// fetch session
session.requestSession(message.v, ws, users)
default:
break
}
break
case 'm':
pointer = Pointer.create(message.p)
channel = channels[pointer.trunk_steps[0]]
channel.merge(pointer, message.v, ws)
break
case 'w':
pointer = Pointer.create(message.p)
channel = channels[pointer.trunk_steps[0]]
channel.write(pointer, message.v, ws)
break
case 'd':
pointer = Pointer.create(message.p)
channel = channels[pointer.trunk_steps[0]]
channel.delete(pointer, ws)
break
case 's':
pointer = Pointer.create(message.p)
channel = channels[pointer.trunk_steps[0]]
channel.subscribe(pointer, ws, publish)
if (!message.i) break
case '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 = path
response.v = value
ws.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-console
process.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) return
console.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 = eventId
const 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_in
datastore.write('/setup/admin/token', message.access_token)
datastore.write('/setup/admin/expiry', new_expiry)
}
onPlaylistItemsQueued(value, pointer) {
if (!value) return
console.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 Driver
const 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)
}
break
default:
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 playlist
const intervalDays = datastore.read(pointer.path + '/interval')
const intervalSeconds = intervalDays * 24 * 60 * 60
datastore.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 = true
if (current_list) {
for (var idx in current_list) {
if (current_list[idx] != sorted_list[idx]) { match = false }
}
} else {
match = false
}
if (match) return
datastore.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 = Pointer
import fs from 'fs'
const loadConfig = async () => {
// configuration information goes here
datastore.set('/session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')
let filePath
fs.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 = topic
permissions._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 = false
for (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 parse
if (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 = false
for (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, {})._value
if (_.isArray(subscribers[subscriber])) {
subscribers[subscriber].push(callback)
} else {
subscribers[subscriber] = [callback]
}
}
unsubscribe(subscriber) {
console.log(`#unsubscribe ${subscriber}`)
const removeSubscriber = ({ _value }) => {
if (!_value) return
delete _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) {
// 0
const salt = packet.s
const verifier = packet.v
const 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) {
// 1
const 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 ephemeral
if (salt && verifier) {
console.log(`#authorize() ${packet.u} found`)
ephemeral = srp.generateEphemeral(verifier)
datastore.write(server_secret_key_pointer, ephemeral.secret)
response.v.k = ephemeral.public
response.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 = ephemeral
response.v.s = salt
}
return response
}
prove(pointer, packet) {
// 3
const 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.u
const verifier = datastore.read(verifier_pointer)
const proof = packet.p
var session, response
try {
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.sh
node --experimental-modules --experimental-json-modules --es-module-specifier-resolution=node src/index.js
.git
*.log
node_modules
app/
nginx.conf
#!/usr/bin/env bash
. $BIN_DIR/_lib.sh
rsync --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 bash
if [[ "$(uname -s)" == "Darwin" ]]; then
echo "Don't run this on your local computer!"
exit 1
fi
echo "[remote] Updating processor"
cd djinmusic
pnpm install -r
cd ..
echo "[remote] Installed"
#!/bin/zsh
autoenv_source_parent
autostash 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) return
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')) &&
timestamp > + 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 + ':' + token
console.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 = 25706
const wss = new WebSocket.Server({ port })
console.log(`Listenning on ${port}`)
wss.on('connection', function connection(ws, request) {
const ip = request.socket.remoteAddress
ws.uuid = uuid()
ws.toString = () => ws.uuid
console.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_id
hello.p = '/session/node_id'
ws.send(JSON.stringify(hello))
const publish = (topic, pointer) => {
const message = {}
const { tree, path } = pointer
const { value } = tree
message.o = 'p'
message.t = topic
message.p = path
message.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, topic, channel
switch (message.o) {
case 'a': {
switch (message.c) {
case 0: {
// createAccount
topic = new Topic(message.t)
channel = channels[topic.trunk[0]]
channel.createAuthorization(topic, message.v, ws)
} break
case 1: {
// startSession
topic = new Topic(message.t)
channel = channels[topic.trunk[0]]
channel.authorize(topic, message.v, ws)
} break
case 4: {
// verifySession
topic = new Topic(message.t)
channel = channels[topic.trunk[0]]
if (channel.prove(topic, message.v, ws)) {
session.addSubscription(message.v.u, topic)
}
} break
case 5: {
// post session
session.fetchSession(message.v, ws, channels)
} break
case 6: {
// fetch session
session.requestSession(message.v, ws, users)
} break
default:
break
}
} break
case 'm': {
pointer = new Pointer(message.p)
channel = channels[pointer.trunk[0]]
channel.merge(pointer, message.v, ws)
} break
case 'w': {
pointer = new Pointer(message.p)
channel = channels[pointer.trunk[0]]
channel.write(pointer, message.v, ws)
} break
case 'd': {
pointer = new Pointer(message.p)
channel = channels[pointer.trunk[0]]
channel.delete(pointer, ws)
} break
case '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 = path
response.v = value
ws.send(JSON.stringify(response))
})
} break
case '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-console
process.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 !== '=') return
const { pointer } = event
const value = pointer?.tree?.value
console.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/2021
this.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 event
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(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 })
} break
case '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 !== '=') return
const { pointer } = event
switch(pointer.leaf) {
// leafs that don't require validation
case '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 validation
case 'pin': {
this.accept(pointer, pointer.value)
} break
case 'time': {
this.accept(pointer, pointer.value)
} break
case 'type': {
if (!['recurring', 'one_time'].includes(pointer.value)) return
this.accept(pointer, pointer.value)
} break
// special cases
case 'blacklist': {
if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {
datastore.push(`/setup/events/${pointer.setps[5]}/blacklist`, pointer.value)
}
} break
case 'whitelist': {
if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {
datastore.pull(`/setup/events/${pointer.setps[5]}/blacklist`, pointer.value)
}
} break
case '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)
}
}
} break
case '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 !== '=') return
const { pointer } = event
const value = pointer?.tree?.value
if (!value) return
// NOTE: check the account type and the number of owned events
// TODO: @thomascowan add support for accoun types
const 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 event
switch (account_type) {
case 'business': {
// NOTE: Placeholder do nothing for now
} break
case 'basic': {
if (owned.length >= 5) return
} break
case 'free': {
if (owned.length >= 3) return
}
}
// NOTE: create an event with the user_id
const 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 } = event
const value = pointer?.tree?.value
if (!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) return
console.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 } = event
console.log(`#initialize_playlist ${pointer.path}`)
const eventId = pointer.branch.slice(-1)[0]
const name = eventId
const 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 !== '=') return
const { pointer } = event
const value = pointer?.tree?.value
datastore.write(`/setup${pointer.branch_path}/details_synced`, false)
this.debouncePushPlaylistDetails(value, pointer)
}
pushPlaylistDetails(value, pointer) {
if (event.type !== '=') return
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, event) {
if (event.type !== '=') return
const { pointer } = event
const value = pointer?.tree?.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_in
datastore.write('/setup/admin/token', message.access_token)
datastore.write('/setup/admin/expiry', new_expiry)
}
onPlaylistItemsQueued(value, pointer) {
if (!value) return
console.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 Driver
const 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 !== '=') return
const { pointer } = event
const value = pointer?.tree?.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.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 !== '=') return
const { pointer } = event
const value = pointer?.tree?.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)
}
break
default:
this.accept(pointer, value)
break
}
}
onSetupQueued(topic, event) {
if (event.type !== '=') return
const { pointer } = event
const value = pointer?.tree?.value
console.log(`${this.toString()} #onSetupQueued: ${pointer.path}, ${JSON.stringify(value)}`)
this.accept(pointer, value)
}
onUserQueued(topic, event) {
if (event.type !== '=') return
const { pointer } = event
const value = pointer?.tree?.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 playlist
const intervalDays = datastore.read(pointer.path + '/interval')
const intervalSeconds = intervalDays * 24 * 60 * 60
datastore.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 = true
if (current_list) {
for (var idx in current_list) {
if (current_list[idx] != sorted_list[idx]) { match = false }
}
} else {
match = false
}
if (match) return
datastore.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 !== '=') return
const { pointer } = event
const value = pointer?.tree?.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.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 = Pointer
if (!global.Topic) global.Topic = Topic
import fs from 'fs'
const loadConfig = async () => {
// configuration information goes here
datastore.set('/session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')
let filePath
fs.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 rarely
all(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._root
for (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._root
for (const step of steps) {
if (node[step] == null) {
node[step] = this.createTreeNode()
}
node = node[step]
}
if (node._value == null) {
node._topic = topic
node._value = value
}
return node
}
add(topic, value) {
const node = this.getWithDefault(topic)
node._topic = topic
node._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 = topic
permissions._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 !== '=') return
const { pointer } = event
const value = pointer?.tree?.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.delete(private_path)
} else {
this.dewhitelist(syndicated_path)
datastore.write(private_path, true)
}
}
publish(topic, event) {
if (event.type !== '=') return
const { pointer } = event
console.log(`${this.toString()} #publish ${topic} @ ${pointer.path}`)
const permissions = this._permissions.entries(pointer.path.slice(1))
let whitelisted = false
for (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 parse
if (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 = false
for (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, {})._value
if (_.isArray(subscribers[subscriber])) {
subscribers[subscriber].push(callback)
} else {
subscribers[subscriber] = [callback]
}
}
unsubscribe(subscriber) {
console.log(`#unsubscribe ${subscriber}`)
const removeSubscriber = ({ _value }) => {
if (!_value) return
delete _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) {
// 0
const salt = packet.s
const verifier = packet.v
const 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) {
// 1
const 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 ephemeral
if (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.public
response.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 = ephemeral
response.v.s = salt
}
return response
}
prove(topic, packet) {
// 3
const 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.u
const verifier = datastore.read('/' + verifier_topic.pattern)
const proof = packet.p
var session, response
try {
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.sh
node --experimental-modules --experimental-json-modules --es-module-specifier-resolution=node src/index.js
.git
*.log
node_modules
app/
nginx.conf
#!/usr/bin/env bash
. $BIN_DIR/_lib.sh
rsync --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 bash
if [[ "$(uname -s)" == "Darwin" ]]; then
echo "Don't run this on your local computer!"
exit 1
fi
echo "[remote] Updating processor"
cd djinmusic
pnpm install -r
cd ..
echo "[remote] Installed"
#!/bin/zsh
autoenv_source_parent
autostash 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.3
importers:
.:
specifiers:
'@controlenvy/datastore': 4.0.0
babel-eslint: ^10.0.3
benchmark: ^2.1.4
chai: ^4.2.0
concurrently: ^5.0.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
globby: ^11.0.0
husky: ^3.0.0
ipware: ^2.0.0
jsdoctest: ^1.7.1
lint-staged: ^9.2.0
lodash: ^4.17.15
lodash-es: ^4.17.15
luxon: ^1.21.3
mocha: ^6.1.4
moment: ^2.24.0
node-watch: ^0.6.3
polka: ^0.5.2
prettier: ^1.19.1
secure-remote-password: ^0.3.1
sinon: ^7.3.2
sinon-chai: ^3.3.0
sirv: ^0.4.2
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
svelte: ^3.0.0
svelte-preprocess: ^3.3.0
svelte-preprocess-sass: ^0.2.0
dependencies:
'@controlenvy/datastore': link:lib/datastore
lodash: 4.17.21
lodash-es: 4.17.21
secure-remote-password: 0.3.1
optionalDependencies:
concurrently: 5.3.0
ipware: 2.0.0
luxon: 1.28.0
polka: 0.5.2
sirv: 0.4.6
devDependencies:
babel-eslint: 10.1.0_eslint@6.8.0
benchmark: 2.1.4
chai: 4.3.4
eslint: 6.8.0
eslint-config-prettier: 6.15.0_eslint@6.8.0
eslint-plugin-html: 6.1.2
eslint-plugin-optimize-regex: 1.2.1
eslint-plugin-prettier: 3.4.0_b77cd85fda941e232840dc83bf6b7690
eslint-plugin-svelte3: 2.7.3_eslint@6.8.0+svelte@3.42.1
globby: 11.0.4
husky: 3.1.0
jsdoctest: 1.7.1
lint-staged: 9.5.0
mocha: 6.2.3
moment: 2.29.1
node-watch: 0.6.4
prettier: 1.19.1
sinon: 7.5.0
sinon-chai: 3.7.0_chai@4.3.4+sinon@7.5.0
stylelint: 11.1.1
stylelint-config-prettier: 5.3.0_stylelint@11.1.1
stylelint-config-rational-order: 0.1.2
stylelint-config-standard: 18.3.0_stylelint@11.1.1
stylelint-order: 3.1.1_stylelint@11.1.1
stylelint-prettier: 1.2.0_prettier@1.19.1+stylelint@11.1.1
stylelint-scss: 3.20.1_stylelint@11.1.1
svelte: 3.42.1
svelte-preprocess: 3.9.12_svelte@3.42.1
svelte-preprocess-sass: 0.2.0
app/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.35
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
sanitize.css: ^12.0.1
sass: ^1.35.2
secure-remote-password: ^0.3.1
svelte: ^3.34.0
svelte-preprocess: ^4.7.3
uuid: ^8.0.0
dependencies:
'@fortawesome/free-brands-svg-icons': 5.15.4
'@fortawesome/free-solid-svg-icons': 5.15.4
sanitize.css: 12.0.1
sass: 1.37.5
devDependencies:
'@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.35
eslint: 7.32.0
eslint-config-prettier: 8.3.0_eslint@7.32.0
eslint-plugin-svelte3: 3.2.0_eslint@7.32.0+svelte@3.42.1
lodash: 4.17.21
node-sass: 6.0.1
prettier: 2.2.1
prettier-plugin-svelte: 2.3.1_prettier@2.2.1+svelte@3.42.1
query-string: 4.3.2
secure-remote-password: 0.3.1
svelte: 3.42.1
svelte-preprocess: 4.7.4_6197623e5ed34153d1bcd9290e2954d7
uuid: 8.3.2
lib/datastore:
specifiers:
chai: ^4.3.0
lodash: ^4.17.15
mocha: ^8.2.1
sinon: ^9.2.4
sinon-chai: ^3.5.0
dependencies:
lodash: 4.17.21
devDependencies:
chai: 4.3.4
mocha: 8.4.0
sinon: 9.2.4
sinon-chai: 3.7.0_chai@4.3.4+sinon@9.2.4
lib/datastore_client:
specifiers:
'@djinlist/datastore': 0.0.0
lodash: ^4.17.15
dependencies:
'@djinlist/datastore': link:../datastore_old
lodash: 4.17.21
lib/datastore_old:
specifiers:
lodash: ^4.17.15
dependencies:
lodash: 4.17.21
services/channel:
specifiers:
uuid: ^8.0.0
ws: ^6.0.0
dependencies:
uuid: 8.3.2
ws: 6.2.2
packages:
/@arr/every/1.0.1:
resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==}
engines: {node: '>=4'}
dev: false
optional: true
/@babel/code-frame/7.12.11:
resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==}
dependencies:
'@babel/highlight': 7.14.5
dev: true
/@babel/code-frame/7.14.5:
resolution: {integrity: sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/highlight': 7.14.5
dev: 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.0
convert-source-map: 1.8.0
debug: 4.3.2
gensync: 1.0.0-beta.2
json5: 2.2.0
semver: 6.3.0
source-map: 0.5.7
transitivePeerDependencies:
- supports-color
dev: true
/@babel/generator/7.15.0:
resolution: {integrity: sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.15.0
jsesc: 2.5.2
source-map: 0.5.7
dev: 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.0
dependencies:
'@babel/compat-data': 7.15.0
'@babel/core': 7.15.0
'@babel/helper-validator-option': 7.14.5
browserslist: 4.16.7
semver: 6.3.0
dev: 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.0
dev: true
/@babel/helper-get-function-arity/7.14.5:
resolution: {integrity: sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.15.0
dev: true
/@babel/helper-hoist-variables/7.14.5:
resolution: {integrity: sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.15.0
dev: 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.0
dev: 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.0
dev: 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.0
transitivePeerDependencies:
- supports-color
dev: 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.0
dev: 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.0
transitivePeerDependencies:
- supports-color
dev: true
/@babel/helper-simple-access/7.14.8:
resolution: {integrity: sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.15.0
dev: 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.0
dev: 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.0
transitivePeerDependencies:
- supports-color
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.9
chalk: 2.4.2
js-tokens: 4.0.0
dev: true
/@babel/parser/7.15.2:
resolution: {integrity: sha512-bMJXql1Ss8lFnvr11TZDH4ArtwlAS5NG9qBmdiFW2UHHm6MVoR+GDc5XE2b9K938cyjc9O6/+vjjcffLDtfuDg==}
engines: {node: '>=6.0.0'}
hasBin: true
dev: 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.0
dev: 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.0
debug: 4.3.2
globals: 11.12.0
transitivePeerDependencies:
- supports-color
dev: 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.9
to-fast-properties: 2.0.0
dev: true
/@eslint/eslintrc/0.4.3:
resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==}
engines: {node: ^10.12.0 || >=12.0.0}
dependencies:
ajv: 6.12.6
debug: 4.3.2
espree: 7.3.1
globals: 13.10.0
ignore: 4.0.6
import-fresh: 3.3.0
js-yaml: 3.14.1
minimatch: 3.0.4
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
dev: 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: true
dev: 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: true
dependencies:
'@fortawesome/fontawesome-common-types': 0.2.36
dev: 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: true
dependencies:
'@fortawesome/fontawesome-common-types': 0.2.36
dev: false
/@humanwhocodes/config-array/0.5.0:
resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==}
engines: {node: '>=10.10.0'}
dependencies:
'@humanwhocodes/object-schema': 1.2.0
debug: 4.3.2
minimatch: 3.0.4
transitivePeerDependencies:
- supports-color
dev: 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.1
glob-to-regexp: 0.3.0
dev: true
/@nodelib/fs.scandir/2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
dev: 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.5
fastq: 1.11.1
dev: true
/@polka/url/0.5.0:
resolution: {integrity: sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==}
dev: false
optional: true
/@rollup/pluginutils/4.1.1:
resolution: {integrity: sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==}
engines: {node: '>= 8.0.0'}
dependencies:
estree-walker: 2.0.2
picomatch: 2.3.0
dev: 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: true
zen-observable:
optional: true
dependencies:
any-observable: 0.3.0
rxjs: 6.6.7
dev: true
/@sinonjs/commons/1.8.3:
resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==}
dependencies:
type-detect: 4.0.8
dev: true
/@sinonjs/fake-timers/6.0.1:
resolution: {integrity: sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==}
dependencies:
'@sinonjs/commons': 1.8.3
dev: true
/@sinonjs/formatio/3.2.2:
resolution: {integrity: sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==}
dependencies:
'@sinonjs/commons': 1.8.3
'@sinonjs/samsam': 3.3.3
dev: true
/@sinonjs/samsam/3.3.3:
resolution: {integrity: sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==}
dependencies:
'@sinonjs/commons': 1.8.3
array-from: 2.1.1
lodash: 4.17.21
dev: true
/@sinonjs/samsam/5.3.1:
resolution: {integrity: sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==}
dependencies:
'@sinonjs/commons': 1.8.3
lodash.get: 4.4.2
type-detect: 4.0.8
dev: 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: true
peerDependencies:
svelte: ^3.34.0
dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.15_svelte@3.42.1+vite@2.4.4
cheap-watch: 1.0.3
sade: 1.7.4
svelte: 3.42.1
vite: 2.4.4
transitivePeerDependencies:
- diff-match-patch
- supports-color
dev: 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.5
svelte: ^3.34.0
vite: ^2.3.7
peerDependenciesMeta:
diff-match-patch:
optional: 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: 3.42.1
svelte-hmr: 0.14.7_svelte@3.42.1
vite: 2.4.4
transitivePeerDependencies:
- supports-color
dev: true
/@taylorzane/sveltejs-adapter-node/1.0.0-next.35:
resolution: {integrity: sha512-5DVAmeCgcKtU+DZ36HoglNHjPZbx52/cP3V9s/RcwAzEa9VKh+MVwxW/fYm8M7x3bD4YUhaAy5ECdKjCPxUwcw==}
dependencies:
esbuild: 0.12.19
tiny-glob: 0.2.9
dev: 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.13
dev: 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.13
dev: 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.1
dev: 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.0
dev: 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.0
dependencies:
acorn: 7.4.1
dev: true
/acorn/7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/aggregate-error/3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
dependencies:
clean-stack: 2.2.0
indent-string: 4.0.0
dev: true
/ajv/6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
dependencies:
fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0
json-schema-traverse: 0.4.1
uri-js: 4.4.1
dev: true
/ajv/8.6.2:
resolution: {integrity: sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==}
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
uri-js: 4.4.1
dev: 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.3
dev: 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.1
dev: 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.0
picomatch: 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.0
readable-stream: 2.3.7
dev: true
/argparse/1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
dependencies:
sprintf-js: 1.0.3
dev: 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.3
dev: 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.2
dev: 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: true
dev: true
/autoprefixer/9.8.6:
resolution: {integrity: sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==}
hasBin: true
dependencies:
browserslist: 4.16.7
caniuse-lite: 1.0.30001249
colorette: 1.2.2
normalize-range: 0.1.2
num2fraction: 1.2.2
postcss: 7.0.36
postcss-value-parser: 4.1.0
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
/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.0
eslint: 6.8.0
eslint-visitor-keys: 1.3.0
resolve: 1.20.0
transitivePeerDependencies:
- supports-color
dev: 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.1
class-utils: 0.3.6
component-emitter: 1.3.0
define-property: 1.0.0
isobject: 3.0.1
mixin-deep: 1.3.2
pascalcase: 0.1.1
dev: true
/bcrypt-pbkdf/1.0.2:
resolution: {integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=}
dependencies:
tweetnacl: 0.14.5
dev: true
/benchmark/2.1.4:
resolution: {integrity: sha1-CfPeMckWQl1JjMLuVloOvzwqVik=}
dependencies:
lodash: 4.17.21
platform: 1.3.6
dev: 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.2
concat-map: 0.0.1
dev: true
/braces/2.3.2:
resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
engines: {node: '>=0.10.0'}
dependencies:
arr-flatten: 1.1.0
array-unique: 0.3.2
extend-shallow: 2.0.1
fill-range: 4.0.0
isobject: 3.0.1
repeat-element: 1.1.4
snapdragon: 0.8.2
snapdragon-node: 2.1.1
split-string: 3.1.0
to-regex: 3.0.2
dev: 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: true
dependencies:
caniuse-lite: 1.0.30001249
colorette: 1.2.2
electron-to-chromium: 1.3.800
escalade: 3.1.1
node-releases: 1.1.73
dev: true
/cache-base/1.0.1:
resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==}
engines: {node: '>=0.10.0'}
dependencies:
collection-visit: 1.0.0
component-emitter: 1.3.0
get-value: 2.0.6
has-value: 1.0.0
isobject: 3.0.1
set-value: 2.0.1
to-object-path: 0.3.0
union-value: 1.0.1
unset-value: 1.0.0
dev: true
/call-bind/1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
dependencies:
function-bind: 1.1.1
get-intrinsic: 1.1.1
dev: 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.0
dev: true
/caller-path/2.0.0:
resolution: {integrity: sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=}
engines: {node: '>=4'}
dependencies:
caller-callsite: 2.0.0
dev: 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.0
map-obj: 2.0.0
quick-lru: 1.1.0
dev: true
/camelcase-keys/6.2.2:
resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}
engines: {node: '>=8'}
dependencies:
camelcase: 5.3.1
map-obj: 4.2.1
quick-lru: 4.0.1
dev: 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.0
check-error: 1.0.2
deep-eql: 3.0.1
get-func-name: 2.0.0
pathval: 1.1.1
type-detect: 4.0.8
dev: true
/chalk/1.1.3:
resolution: {integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=}
engines: {node: '>=0.10.0'}
dependencies:
ansi-styles: 2.2.1
escape-string-regexp: 1.0.5
has-ansi: 2.0.0
strip-ansi: 3.0.1
supports-color: 2.0.0
dev: true
/chalk/2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
dependencies:
ansi-styles: 3.2.1
escape-string-regexp: 1.0.5
supports-color: 5.5.0
/chalk/4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
dev: 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.2
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.1
normalize-path: 3.0.0
readdirp: 3.5.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/chokidar/3.5.2:
resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.2
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.1
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
dev: 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.0
define-property: 0.2.5
isobject: 3.0.1
static-extend: 0.1.2
dev: 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.0
dev: true
/cli-cursor/3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
dependencies:
restore-cursor: 3.1.0
dev: true
/cli-truncate/0.2.1:
resolution: {integrity: sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=}
engines: {node: '>=0.10.0'}
dependencies:
slice-ansi: 0.0.4
string-width: 1.0.2
dev: 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.0
strip-ansi: 5.2.0
wrap-ansi: 5.1.0
/cliui/7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
dependencies:
string-width: 4.2.2
strip-ansi: 6.0.0
wrap-ansi: 7.0.0
dev: true
/clone-regexp/1.0.1:
resolution: {integrity: sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==}
engines: {node: '>=0.10.0'}
dependencies:
is-regexp: 1.0.0
is-supported-regexp-flag: 1.0.1
dev: true
/clone-regexp/2.2.0:
resolution: {integrity: sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==}
engines: {node: '>=6'}
dependencies:
is-regexp: 2.1.0
dev: 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.0
object-visit: 1.0.1
dev: 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.4
dev: 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.0
dev: 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.1
dev: 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: true
dependencies:
chalk: 2.4.2
date-fns: 2.23.0
lodash: 4.17.21
read-pkg: 4.0.1
rxjs: 6.6.7
spawn-command: 0.0.2-1
supports-color: 6.1.0
tree-kill: 1.2.2
yargs: 13.3.2
dev: false
optional: true
/config-chain/1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
dependencies:
ini: 1.3.8
proto-list: 1.2.4
dev: 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.2
dev: 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.0
is-directory: 0.3.1
js-yaml: 3.14.1
parse-json: 4.0.0
dev: true
/cross-spawn/6.0.5:
resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==}
engines: {node: '>=4.8'}
dependencies:
nice-try: 1.0.5
path-key: 2.0.1
semver: 5.7.1
shebang-command: 1.2.0
which: 1.3.1
dev: true
/cross-spawn/7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
dev: 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: true
dev: true
/currently-unhandled/0.4.1:
resolution: {integrity: sha1-mI3zP+qxke95mmE2nddsF635V+o=}
engines: {node: '>=0.10.0'}
dependencies:
array-find-index: 1.0.2
dev: true
/dashdash/1.14.1:
resolution: {integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=}
engines: {node: '>=0.10'}
dependencies:
assert-plus: 1.0.0
dev: 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: false
optional: true
/debug/2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
dependencies:
ms: 2.0.0
dev: 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.1
dev: 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: true
dependencies:
ms: 2.1.2
supports-color: 8.1.1
dev: true
/debug/4.3.2:
resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: true
/decamelize-keys/1.1.0:
resolution: {integrity: sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=}
engines: {node: '>=0.10.0'}
dependencies:
decamelize: 1.2.0
map-obj: 1.0.1
dev: 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.8
dev: 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.1
dev: true
/define-property/0.2.5:
resolution: {integrity: sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=}
engines: {node: '>=0.10.0'}
dependencies:
is-descriptor: 0.1.6
dev: true
/define-property/1.0.0:
resolution: {integrity: sha1-dp66rz9KY6rTr56NMEybvnm/sOY=}
engines: {node: '>=0.10.0'}
dependencies:
is-descriptor: 1.0.2
dev: true
/define-property/2.0.2:
resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==}
engines: {node: '>=0.10.0'}
dependencies:
is-descriptor: 1.0.2
isobject: 3.0.1
dev: true
/del/5.1.0:
resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==}
engines: {node: '>=8'}
dependencies:
globby: 10.0.2
graceful-fs: 4.2.8
is-glob: 4.0.1
is-path-cwd: 2.2.0
is-path-inside: 3.0.3
p-map: 3.0.0
rimraf: 3.0.2
slash: 3.0.0
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
/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.0
dev: true
/dir-glob/3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
dependencies:
path-type: 4.0.0
dev: true
/doctrine/3.0.0:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
dependencies:
esutils: 2.0.3
dev: true
/dom-serializer/0.2.2:
resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==}
dependencies:
domelementtype: 2.2.0
entities: 2.2.0
dev: true
/dom-serializer/1.3.2:
resolution: {integrity: sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==}
dependencies:
domelementtype: 2.2.0
domhandler: 4.2.0
entities: 2.2.0
dev: 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.1
dev: true
/domhandler/4.2.0:
resolution: {integrity: sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==}
engines: {node: '>= 4'}
dependencies:
domelementtype: 2.2.0
dev: true
/domutils/1.7.0:
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
dependencies:
dom-serializer: 0.2.2
domelementtype: 1.3.1
dev: true
/domutils/2.7.0:
resolution: {integrity: sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==}
dependencies:
dom-serializer: 1.3.2
domelementtype: 2.2.0
domhandler: 4.2.0
dev: true
/dot-prop/5.3.0:
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
engines: {node: '>=8'}
dependencies:
is-obj: 2.0.0
dev: true
/dox/0.9.0:
resolution: {integrity: sha1-vpewhcufSgt+gINdVH53uGh9Cgw=}
hasBin: true
dependencies:
commander: 2.9.0
jsdoctypeparser: 1.2.0
markdown-it: 7.0.1
dev: true
/ecc-jsbn/0.1.2:
resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=}
dependencies:
jsbn: 0.1.1
safer-buffer: 2.1.2
dev: true
/editorconfig/0.15.3:
resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==}
hasBin: true
dependencies:
commander: 2.20.3
lru-cache: 4.1.5
semver: 5.7.1
sigmund: 1.0.1
dev: 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.0
dev: true
/enquirer/2.3.6:
resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==}
engines: {node: '>=8.6'}
dependencies:
ansi-colors: 4.1.1
dev: 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.2
es-to-primitive: 1.2.1
function-bind: 1.1.1
get-intrinsic: 1.1.1
has: 1.0.3
has-symbols: 1.0.2
internal-slot: 1.0.3
is-callable: 1.2.4
is-negative-zero: 2.0.1
is-regex: 1.1.4
is-string: 1.0.7
object-inspect: 1.11.0
object-keys: 1.1.1
object.assign: 4.1.2
string.prototype.trimend: 1.0.4
string.prototype.trimstart: 1.0.4
unbox-primitive: 1.0.1
dev: true
/es-to-primitive/1.2.1:
resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
engines: {node: '>= 0.4'}
dependencies:
is-callable: 1.2.4
is-date-object: 1.0.5
is-symbol: 1.0.4
dev: true
/esbuild/0.12.19:
resolution: {integrity: sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w==}
hasBin: true
requiresBuild: true
dev: 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: true
peerDependencies:
eslint: '>=3.14.1'
dependencies:
eslint: 6.8.0
get-stdin: 6.0.0
dev: true
/eslint-config-prettier/8.3.0_eslint@7.32.0:
resolution: {integrity: sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
eslint: 7.32.0
dev: true
/eslint-plugin-html/6.1.2:
resolution: {integrity: sha512-bhBIRyZFqI4EoF12lGDHAmgfff8eLXx6R52/K3ESQhsxzCzIE6hdebS7Py651f7U3RBotqroUnC3L29bR7qJWQ==}
dependencies:
htmlparser2: 6.1.0
dev: true
/eslint-plugin-optimize-regex/1.2.1:
resolution: {integrity: sha512-fUaU7Tj1G/KSTDTABJw4Wp427Rl7RPl9ViYTu1Jrv36fJw4DFhd4elPdXiuYtdPsNsvzn9GcVlKEssGIVjw0UQ==}
engines: {node: '>=10'}
dependencies:
regexp-tree: 0.1.23
dev: 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: true
dependencies:
eslint: 6.8.0
eslint-config-prettier: 6.15.0_eslint@6.8.0
prettier: 1.19.1
prettier-linter-helpers: 1.0.0
dev: 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.0
dependencies:
eslint: 6.8.0
svelte: 3.42.1
dev: 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.0
dependencies:
eslint: 7.32.0
svelte: 3.42.1
dev: true
/eslint-scope/5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
dependencies:
esrecurse: 4.3.0
estraverse: 4.3.0
dev: true
/eslint-utils/1.4.3:
resolution: {integrity: sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==}
engines: {node: '>=6'}
dependencies:
eslint-visitor-keys: 1.3.0
dev: true
/eslint-utils/2.1.0:
resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==}
engines: {node: '>=6'}
dependencies:
eslint-visitor-keys: 1.3.0
dev: 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: true
dependencies:
'@babel/code-frame': 7.14.5
ajv: 6.12.6
chalk: 2.4.2
cross-spawn: 6.0.5
debug: 4.3.2
doctrine: 3.0.0
eslint-scope: 5.1.1
eslint-utils: 1.4.3
eslint-visitor-keys: 1.3.0
espree: 6.2.1
esquery: 1.4.0
esutils: 2.0.3
file-entry-cache: 5.0.1
functional-red-black-tree: 1.0.1
glob-parent: 5.1.2
globals: 12.4.0
ignore: 4.0.6
import-fresh: 3.3.0
imurmurhash: 0.1.4
inquirer: 7.3.3
is-glob: 4.0.1
js-yaml: 3.14.1
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.3.0
lodash: 4.17.21
minimatch: 3.0.4
mkdirp: 0.5.5
natural-compare: 1.4.0
optionator: 0.8.3
progress: 2.0.3
regexpp: 2.0.1
semver: 6.3.0
strip-ansi: 5.2.0
strip-json-comments: 3.1.1
table: 5.4.6
text-table: 0.2.0
v8-compile-cache: 2.3.0
transitivePeerDependencies:
- supports-color
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: true
dependencies:
'@babel/code-frame': 7.12.11
'@eslint/eslintrc': 0.4.3
'@humanwhocodes/config-array': 0.5.0
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.2
doctrine: 3.0.0
enquirer: 2.3.6
escape-string-regexp: 4.0.0
eslint-scope: 5.1.1
eslint-utils: 2.1.0
eslint-visitor-keys: 2.1.0
espree: 7.3.1
esquery: 1.4.0
esutils: 2.0.3
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.10.0
ignore: 4.0.6
import-fresh: 3.3.0
imurmurhash: 0.1.4
is-glob: 4.0.1
js-yaml: 3.14.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.3
regexpp: 3.2.0
semver: 7.3.5
strip-ansi: 6.0.0
strip-json-comments: 3.1.1
table: 6.7.1
text-table: 0.2.0
v8-compile-cache: 2.3.0
transitivePeerDependencies:
- supports-color
dev: true
/espree/6.2.1:
resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==}
engines: {node: '>=6.0.0'}
dependencies:
acorn: 7.4.1
acorn-jsx: 5.3.2_acorn@7.4.1
eslint-visitor-keys: 1.3.0
dev: 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.1
acorn-jsx: 5.3.2_acorn@7.4.1
eslint-visitor-keys: 1.3.0
dev: true
/esprima/4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
hasBin: true
dev: true
/esquery/1.4.0:
resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==}
engines: {node: '>=0.10'}
dependencies:
estraverse: 5.2.0
dev: true
/esrecurse/4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
dependencies:
estraverse: 5.2.0
dev: 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.5
get-stream: 4.1.0
is-stream: 1.1.0
npm-run-path: 2.0.2
p-finally: 1.0.0
signal-exit: 3.0.3
strip-eof: 1.0.0
dev: 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.3
get-stream: 5.2.0
is-stream: 2.0.1
merge-stream: 2.0.0
npm-run-path: 3.1.0
onetime: 5.1.2
p-finally: 2.0.1
signal-exit: 3.0.3
strip-final-newline: 2.0.0
dev: true
/execall/1.0.0:
resolution: {integrity: sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M=}
engines: {node: '>=0.10.0'}
dependencies:
clone-regexp: 1.0.1
dev: true
/execall/2.0.0:
resolution: {integrity: sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==}
engines: {node: '>=8'}
dependencies:
clone-regexp: 2.2.0
dev: true
/expand-brackets/2.1.4:
resolution: {integrity: sha1-t3c14xXOMPa27/D4OwQVGiJEliI=}
engines: {node: '>=0.10.0'}
dependencies:
debug: 2.6.9
define-property: 0.2.5
extend-shallow: 2.0.1
posix-character-classes: 0.1.1
regex-not: 1.0.2
snapdragon: 0.8.2
to-regex: 3.0.2
dev: true
/extend-shallow/2.0.1:
resolution: {integrity: sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=}
engines: {node: '>=0.10.0'}
dependencies:
is-extendable: 0.1.1
dev: true
/extend-shallow/3.0.2:
resolution: {integrity: sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=}
engines: {node: '>=0.10.0'}
dependencies:
assign-symbols: 1.0.0
is-extendable: 1.0.1
dev: 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.0
iconv-lite: 0.4.24
tmp: 0.0.33
dev: true
/extglob/2.0.4:
resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==}
engines: {node: '>=0.10.0'}
dependencies:
array-unique: 0.3.2
define-property: 1.0.0
expand-brackets: 2.1.4
extend-shallow: 2.0.1
fragment-cache: 0.2.1
regex-not: 1.0.2
snapdragon: 0.8.2
to-regex: 3.0.2
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-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.3
glob-parent: 3.1.0
is-glob: 4.0.1
merge2: 1.4.1
micromatch: 3.1.10
dev: 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.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.4
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
/fastq/1.11.1:
resolution: {integrity: sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==}
dependencies:
reusify: 1.0.4
dev: true
/figures/1.7.0:
resolution: {integrity: sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=}
engines: {node: '>=0.10.0'}
dependencies:
escape-string-regexp: 1.0.5
object-assign: 4.1.1
dev: true
/figures/2.0.0:
resolution: {integrity: sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=}
engines: {node: '>=4'}
dependencies:
escape-string-regexp: 1.0.5
dev: true
/figures/3.2.0:
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
engines: {node: '>=8'}
dependencies:
escape-string-regexp: 1.0.5
dev: true
/file-entry-cache/4.0.0:
resolution: {integrity: sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==}
engines: {node: '>=4'}
dependencies:
flat-cache: 2.0.1
dev: true
/file-entry-cache/5.0.1:
resolution: {integrity: sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==}
engines: {node: '>=4'}
dependencies:
flat-cache: 2.0.1
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.4
dev: true
/fill-range/4.0.0:
resolution: {integrity: sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=}
engines: {node: '>=0.10.0'}
dependencies:
extend-shallow: 2.0.1
is-number: 3.0.0
repeat-string: 1.6.1
to-regex-range: 2.1.1
dev: 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.0
dev: 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.0
path-exists: 4.0.0
dev: true
/find-up/5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
dev: true
/flat-cache/2.0.1:
resolution: {integrity: sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==}
engines: {node: '>=4'}
dependencies:
flatted: 2.0.2
rimraf: 2.6.3
write: 1.0.3
dev: true
/flat-cache/3.0.4:
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
engines: {node: ^10.12.0 || >=12.0.0}
dependencies:
flatted: 3.2.2
rimraf: 3.0.2
dev: true
/flat/4.1.1:
resolution: {integrity: sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==}
hasBin: true
dependencies:
is-buffer: 2.0.5
dev: true
/flat/5.0.2:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
dev: 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.0
combined-stream: 1.0.8
mime-types: 2.1.32
dev: true
/fragment-cache/0.2.1:
resolution: {integrity: sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=}
engines: {node: '>=0.10.0'}
dependencies:
map-cache: 0.2.2
dev: true
/fs-minipass/2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.1.3
dev: 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.0
console-control-strings: 1.1.0
has-unicode: 2.0.1
object-assign: 4.1.1
signal-exit: 3.0.3
string-width: 1.0.2
strip-ansi: 3.0.1
wide-align: 1.1.3
dev: true
/gaze/1.1.3:
resolution: {integrity: sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==}
engines: {node: '>= 4.0.0'}
dependencies:
globule: 1.3.2
dev: 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.1
has: 1.0.3
has-symbols: 1.0.2
dev: 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.0
dev: true
/get-stream/5.2.0:
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
engines: {node: '>=8'}
dependencies:
pump: 3.0.0
dev: 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.0
dev: true
/glob-parent/3.1.0:
resolution: {integrity: sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=}
dependencies:
is-glob: 3.1.0
path-dirname: 1.0.2
dev: 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.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/glob/7.1.6:
resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/glob/7.1.7:
resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/global-modules/2.0.0:
resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==}
engines: {node: '>=6'}
dependencies:
global-prefix: 3.0.0
dev: true
/global-prefix/3.0.0:
resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==}
engines: {node: '>=6'}
dependencies:
ini: 1.3.8
kind-of: 6.0.3
which: 1.3.1
dev: 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.1
dev: true
/globals/13.10.0:
resolution: {integrity: sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==}
engines: {node: '>=8'}
dependencies:
type-fest: 0.20.2
dev: 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.4
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.2.7
glob: 7.1.7
ignore: 5.1.8
merge2: 1.4.1
slash: 3.0.0
dev: true
/globby/11.0.4:
resolution: {integrity: sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==}
engines: {node: '>=10'}
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.2.7
ignore: 5.1.8
merge2: 1.4.1
slash: 3.0.0
dev: true
/globby/9.2.0:
resolution: {integrity: sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==}
engines: {node: '>=6'}
dependencies:
'@types/glob': 7.1.4
array-union: 1.0.2
dir-glob: 2.2.2
fast-glob: 2.2.7
glob: 7.1.7
ignore: 4.0.6
pify: 4.0.1
slash: 2.0.0
dev: 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.7
lodash: 4.17.21
minimatch: 3.0.4
dev: true
/gonzales-pe/4.3.0:
resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==}
engines: {node: '>=0.6.0'}
hasBin: true
dependencies:
minimist: 1.2.5
dev: 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 supported
dependencies:
ajv: 6.12.6
har-schema: 2.0.0
dev: 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.1
dev: 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.2
dev: 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.6
has-values: 0.1.4
isobject: 2.1.0
dev: true
/has-value/1.0.0:
resolution: {integrity: sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=}
engines: {node: '>=0.10.0'}
dependencies:
get-value: 2.0.6
has-values: 1.0.0
isobject: 3.0.1
dev: 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.0
kind-of: 4.0.0
dev: 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: true
dev: 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.0
dev: 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.1
domhandler: 2.4.2
domutils: 1.7.0
entities: 1.1.2
inherits: 2.0.4
readable-stream: 3.6.0
dev: true
/htmlparser2/6.1.0:
resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
dependencies:
domelementtype: 2.2.0
domhandler: 4.2.0
domutils: 2.7.0
entities: 2.2.0
dev: true
/http-signature/1.2.0:
resolution: {integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=}
engines: {node: '>=0.8', npm: '>=1.3.7'}
dependencies:
assert-plus: 1.0.0
jsprim: 1.4.1
sshpk: 1.16.1
dev: true
/husky/3.1.0:
resolution: {integrity: sha512-FJkPoHHB+6s4a+jwPqBudBDvYZsoQW5/HBuMSehC8qDiCe50kpcxeqFoDSlow+9I6wg47YxBoT3WxaURlrDIIQ==}
engines: {node: '>=8.6.0'}
hasBin: true
requiresBuild: true
dependencies:
chalk: 2.4.2
ci-info: 2.0.0
cosmiconfig: 5.2.1
execa: 1.0.0
get-stdin: 7.0.0
opencollective-postinstall: 2.0.3
pkg-dir: 4.2.0
please-upgrade-node: 3.2.0
read-pkg: 5.2.0
run-node: 1.0.0
slash: 3.0.0
dev: true
/iconv-lite/0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: 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.0
resolve-from: 3.0.0
dev: true
/import-fresh/3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
dev: 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.0
wrappy: 1.0.2
dev: 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.2
chalk: 4.1.2
cli-cursor: 3.1.0
cli-width: 3.0.0
external-editor: 3.1.0
figures: 3.2.0
lodash: 4.17.21
mute-stream: 0.0.8
run-async: 2.4.1
rxjs: 6.6.7
string-width: 4.2.2
strip-ansi: 6.0.0
through: 2.3.8
dev: true
/internal-slot/1.0.3:
resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
engines: {node: '>= 0.4'}
dependencies:
get-intrinsic: 1.1.1
has: 1.0.3
side-channel: 1.0.4
dev: true
/ipware/2.0.0:
resolution: {integrity: sha512-xOqLoYyWIBcO6ANVZK7GgnuH9V7wbqDf6/MrRi4I+Ae1A11m6jcaNRtMSezypkupRUjiSSLq3F/DTvAM1cZfQw==}
dev: false
optional: true
/is-accessor-descriptor/0.1.6:
resolution: {integrity: sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=}
engines: {node: '>=0.10.0'}
dependencies:
kind-of: 3.2.2
dev: true
/is-accessor-descriptor/1.0.0:
resolution: {integrity: sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==}
engines: {node: '>=0.10.0'}
dependencies:
kind-of: 6.0.3
dev: 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.4
is-decimal: 1.0.4
dev: 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.2
has-tostringtag: 1.0.0
dev: 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.2
dev: true
/is-data-descriptor/1.0.0:
resolution: {integrity: sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==}
engines: {node: '>=0.10.0'}
dependencies:
kind-of: 6.0.3
dev: true
/is-date-object/1.0.5:
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
engines: {node: '>= 0.4'}
dependencies:
has-tostringtag: 1.0.0
dev: 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.6
is-data-descriptor: 0.1.4
kind-of: 5.1.0
dev: true
/is-descriptor/1.0.2:
resolution: {integrity: sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==}
engines: {node: '>=0.10.0'}
dependencies:
is-accessor-descriptor: 1.0.0
is-data-descriptor: 1.0.0
kind-of: 6.0.3
dev: 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.4
dev: 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.1
dev: 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.1
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-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.0
dev: true
/is-number/3.0.0:
resolution: {integrity: sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=}
engines: {node: '>=0.10.0'}
dependencies:
kind-of: 3.2.2
dev: 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.0
dev: 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.1
dev: 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.2
has-tostringtag: 1.0.0
dev: 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.0
dev: 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.2
dev: 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.0
dev: 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: true
dependencies:
config-chain: 1.1.13
editorconfig: 0.15.3
glob: 7.1.7
nopt: 5.0.0
dev: true
/js-tokens/4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: true
/js-yaml/3.13.1:
resolution: {integrity: sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==}
hasBin: true
dependencies:
argparse: 1.0.10
esprima: 4.0.1
dev: true
/js-yaml/3.14.1:
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
hasBin: true
dependencies:
argparse: 1.0.10
esprima: 4.0.1
dev: true
/js-yaml/4.0.0:
resolution: {integrity: sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==}
hasBin: true
dependencies:
argparse: 2.0.1
dev: 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: true
dependencies:
commander: 2.20.3
dox: 0.9.0
js-beautify: 1.14.0
lodash: 4.17.21
should: 11.2.1
uglify-js: 3.14.1
dev: true
/jsdoctypeparser/1.2.0:
resolution: {integrity: sha1-597cFToRhJ/8UUEUSuhqfvDCU5I=}
dependencies:
lodash: 3.10.1
dev: true
/jsesc/2.5.2:
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
engines: {node: '>=4'}
hasBin: true
dev: 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: true
dependencies:
minimist: 1.2.5
dev: true
/jsprim/1.4.1:
resolution: {integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=}
engines: {'0': node >=0.6.0}
dependencies:
assert-plus: 1.0.0
extsprintf: 1.3.0
json-schema: 0.2.3
verror: 1.10.0
dev: 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.6
dev: true
/kind-of/4.0.0:
resolution: {integrity: sha1-IIE989cSkosgc3hpGkUGb65y3Vc=}
engines: {node: '>=0.10.0'}
dependencies:
is-buffer: 1.1.6
dev: 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.2
type-check: 0.3.2
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.1
type-check: 0.4.0
dev: 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.6
dev: true
/lint-staged/9.5.0:
resolution: {integrity: sha512-nawMob9cb/G1J98nb8v3VC/E8rcX1rryUYXVZ69aT9kde6YWX+uvNOEHY5yf2gcWcTJGiD0kqXmCnS3oD75GIA==}
hasBin: true
dependencies:
chalk: 2.4.2
commander: 2.20.3
cosmiconfig: 5.2.1
debug: 4.3.2
dedent: 0.7.0
del: 5.1.0
execa: 2.1.0
listr: 0.14.3
log-symbols: 3.0.0
micromatch: 4.0.4
normalize-path: 3.0.0
please-upgrade-node: 3.2.0
string-argv: 0.3.1
stringify-object: 3.3.0
transitivePeerDependencies:
- supports-color
- zen-observable
dev: 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.2
dependencies:
chalk: 1.1.3
cli-truncate: 0.2.1
elegant-spinner: 1.0.1
figures: 1.7.0
indent-string: 3.2.0
listr: 0.14.3
log-symbols: 1.0.2
log-update: 2.3.0
strip-ansi: 3.0.1
dev: true
/listr-verbose-renderer/0.5.0:
resolution: {integrity: sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==}
engines: {node: '>=4'}
dependencies:
chalk: 2.4.2
cli-cursor: 2.1.0
date-fns: 1.30.1
figures: 2.0.0
dev: 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.7
is-observable: 1.1.0
is-promise: 2.2.2
is-stream: 1.1.0
listr-silent-renderer: 1.1.1
listr-update-renderer: 0.5.0_listr@0.14.3
listr-verbose-renderer: 0.5.0
p-map: 2.1.0
rxjs: 6.6.7
transitivePeerDependencies:
- zen-observable
dev: true
/load-json-file/4.0.0:
resolution: {integrity: sha1-L19Fq5HjMhYjT9U62rZo607AmTs=}
engines: {node: '>=4'}
dependencies:
graceful-fs: 4.2.8
parse-json: 4.0.0
pify: 3.0.0
strip-bom: 3.0.0
dev: true
/locate-path/2.0.0:
resolution: {integrity: sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=}
engines: {node: '>=4'}
dependencies:
p-locate: 2.0.0
path-exists: 3.0.0
dev: true
/locate-path/3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
engines: {node: '>=6'}
dependencies:
p-locate: 3.0.0
path-exists: 3.0.0
/locate-path/5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
dependencies:
p-locate: 4.1.0
dev: true
/locate-path/6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
dependencies:
p-locate: 5.0.0
dev: 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.3
dev: true
/log-symbols/2.2.0:
resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==}
engines: {node: '>=4'}
dependencies:
chalk: 2.4.2
dev: true
/log-symbols/3.0.0:
resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==}
engines: {node: '>=8'}
dependencies:
chalk: 2.4.2
dev: true
/log-symbols/4.0.0:
resolution: {integrity: sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==}
engines: {node: '>=10'}
dependencies:
chalk: 4.1.2
dev: true
/log-update/2.3.0:
resolution: {integrity: sha1-iDKP19HOeTiykoN0bwsbwSayRwg=}
engines: {node: '>=4'}
dependencies:
ansi-escapes: 3.2.0
cli-cursor: 2.1.0
wrap-ansi: 3.0.1
dev: 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.3
dev: 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.1
signal-exit: 3.0.3
dev: true
/lru-cache/4.1.5:
resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
dependencies:
pseudomap: 1.0.2
yallist: 2.1.2
dev: true
/lru-cache/6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
dependencies:
yallist: 4.0.0
dev: true
/luxon/1.28.0:
resolution: {integrity: sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==}
dev: false
optional: true
/magic-string/0.25.7:
resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}
dependencies:
sourcemap-codec: 1.4.8
dev: 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.1
dev: 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: true
dependencies:
argparse: 1.0.10
entities: 1.1.2
linkify-it: 2.2.0
mdurl: 1.0.1
uc.micro: 1.0.6
dev: 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.1
dev: false
optional: 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.1
dev: 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.0
decamelize-keys: 1.1.0
loud-rejection: 1.6.0
minimist-options: 3.0.2
normalize-package-data: 2.5.0
read-pkg-up: 3.0.0
redent: 2.0.0
trim-newlines: 2.0.0
yargs-parser: 10.1.0
dev: true
/meow/9.0.0:
resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}
engines: {node: '>=10'}
dependencies:
'@types/minimist': 1.2.2
camelcase-keys: 6.2.2
decamelize: 1.2.0
decamelize-keys: 1.1.0
hard-rejection: 2.1.0
minimist-options: 4.1.0
normalize-package-data: 3.0.2
read-pkg-up: 7.0.1
redent: 3.0.0
trim-newlines: 3.0.1
type-fest: 0.18.1
yargs-parser: 20.2.9
dev: 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.0
array-unique: 0.3.2
braces: 2.3.2
define-property: 2.0.2
extend-shallow: 3.0.2
extglob: 2.0.4
fragment-cache: 0.2.1
kind-of: 6.0.3
nanomatch: 1.2.13
object.pick: 1.3.0
regex-not: 1.0.2
snapdragon: 0.8.2
to-regex: 3.0.2
dev: true
/micromatch/4.0.4:
resolution: {integrity: sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==}
engines: {node: '>=8.6'}
dependencies:
braces: 3.0.2
picomatch: 2.3.0
dev: 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.0
dev: true
/mime/2.5.2:
resolution: {integrity: sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==}
engines: {node: '>=4.0.0'}
hasBin: true
dev: false
optional: 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.11
dev: true
/minimist-options/3.0.2:
resolution: {integrity: sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==}
engines: {node: '>= 4'}
dependencies:
arrify: 1.0.1
is-plain-obj: 1.1.0
dev: true
/minimist-options/4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
engines: {node: '>= 6'}
dependencies:
arrify: 1.0.1
is-plain-obj: 1.1.0
kind-of: 6.0.3
dev: 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.0
dev: true
/minizlib/2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.1.3
yallist: 4.0.0
dev: true
/mixin-deep/1.3.2:
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
engines: {node: '>=0.10.0'}
dependencies:
for-in: 1.0.2
is-extendable: 1.0.1
dev: 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: true
dependencies:
minimist: 1.2.5
dev: true
/mkdirp/0.5.5:
resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==}
hasBin: true
dependencies:
minimist: 1.2.5
dev: true
/mkdirp/1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
dev: true
/mocha/6.2.3:
resolution: {integrity: sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==}
engines: {node: '>= 6.0.0'}
hasBin: true
dependencies:
ansi-colors: 3.2.3
browser-stdout: 1.3.1
debug: 3.2.6
diff: 3.5.0
escape-string-regexp: 1.0.5
find-up: 3.0.0
glob: 7.1.3
growl: 1.10.5
he: 1.2.0
js-yaml: 3.13.1
log-symbols: 2.2.0
minimatch: 3.0.4
mkdirp: 0.5.4
ms: 2.1.1
node-environment-flags: 1.0.5
object.assign: 4.1.0
strip-json-comments: 2.0.1
supports-color: 6.0.0
which: 1.3.1
wide-align: 1.1.3
yargs: 13.3.2
yargs-parser: 13.1.2
yargs-unparser: 1.6.0
dev: true
/mocha/8.4.0:
resolution: {integrity: sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==}
engines: {node: '>= 10.12.0'}
hasBin: true
dependencies:
'@ungap/promise-all-settled': 1.1.2
ansi-colors: 4.1.1
browser-stdout: 1.3.1
chokidar: 3.5.1
debug: 4.3.1_supports-color@8.1.1
diff: 5.0.0
escape-string-regexp: 4.0.0
find-up: 5.0.0
glob: 7.1.6
growl: 1.10.5
he: 1.2.0
js-yaml: 4.0.0
log-symbols: 4.0.0
minimatch: 3.0.4
ms: 2.1.3
nanoid: 3.1.20
serialize-javascript: 5.0.1
strip-json-comments: 3.1.1
supports-color: 8.1.1
which: 2.0.2
wide-align: 1.1.3
workerpool: 6.1.0
yargs: 16.2.0
yargs-parser: 20.2.4
yargs-unparser: 2.0.0
dev: 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: true
dev: true
/nanoid/3.1.23:
resolution: {integrity: sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/nanomatch/1.2.13:
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
engines: {node: '>=0.10.0'}
dependencies:
arr-diff: 4.0.0
array-unique: 0.3.2
define-property: 2.0.2
extend-shallow: 3.0.2
fragment-cache: 0.2.1
is-windows: 1.0.2
kind-of: 6.0.3
object.pick: 1.3.0
regex-not: 1.0.2
snapdragon: 0.8.2
to-regex: 3.0.2
dev: 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.1
just-extend: 4.2.1
lolex: 5.1.2
path-to-regexp: 1.8.0
dev: 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.1
just-extend: 4.2.1
path-to-regexp: 1.8.0
dev: true
/node-environment-flags/1.0.5:
resolution: {integrity: sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==}
dependencies:
object.getownpropertydescriptors: 2.1.2
semver: 5.7.1
dev: true
/node-gyp/7.1.2:
resolution: {integrity: sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==}
engines: {node: '>= 10.12.0'}
hasBin: true
dependencies:
env-paths: 2.2.1
glob: 7.1.7
graceful-fs: 4.2.8
nopt: 5.0.0
npmlog: 4.1.2
request: 2.88.2
rimraf: 3.0.2
semver: 7.3.5
tar: 6.1.7
which: 2.0.2
dev: 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: true
requiresBuild: true
dependencies:
async-foreach: 0.1.3
chalk: 1.1.3
cross-spawn: 7.0.3
gaze: 1.1.3
get-stdin: 4.0.1
glob: 7.1.7
lodash: 4.17.21
meow: 9.0.0
nan: 2.15.0
node-gyp: 7.1.2
npmlog: 4.1.2
request: 2.88.2
sass-graph: 2.2.5
stdout-stream: 1.4.1
true-case-path: 1.0.3
dev: 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: true
dependencies:
abbrev: 1.1.1
dev: true
/normalize-package-data/2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
hosted-git-info: 2.8.9
resolve: 1.20.0
semver: 5.7.1
validate-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.2
resolve: 1.20.0
semver: 7.3.5
validate-npm-package-license: 3.0.4
dev: 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.1
dev: true
/npm-run-path/3.1.0:
resolution: {integrity: sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==}
engines: {node: '>=8'}
dependencies:
path-key: 3.1.1
dev: true
/npmlog/4.1.2:
resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==}
dependencies:
are-we-there-yet: 1.1.5
console-control-strings: 1.1.0
gauge: 2.7.4
set-blocking: 2.0.0
dev: 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.1
define-property: 0.2.5
kind-of: 3.2.2
dev: 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.1
dev: true
/object.assign/4.1.0:
resolution: {integrity: sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==}
engines: {node: '>= 0.4'}
dependencies:
define-properties: 1.1.3
function-bind: 1.1.1
has-symbols: 1.0.2
object-keys: 1.1.1
dev: true
/object.assign/4.1.2:
resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
define-properties: 1.1.3
has-symbols: 1.0.2
object-keys: 1.1.1
dev: true
/object.getownpropertydescriptors/2.1.2:
resolution: {integrity: sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==}
engines: {node: '>= 0.8'}
dependencies:
call-bind: 1.0.2
define-properties: 1.1.3
es-abstract: 1.18.5
dev: true
/object.pick/1.3.0:
resolution: {integrity: sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=}
engines: {node: '>=0.10.0'}
dependencies:
isobject: 3.0.1
dev: true
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: true
/onetime/2.0.1:
resolution: {integrity: sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=}
engines: {node: '>=4'}
dependencies:
mimic-fn: 1.2.0
dev: true
/onetime/5.1.2:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
dependencies:
mimic-fn: 2.1.0
dev: true
/opencollective-postinstall/2.0.3:
resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==}
hasBin: true
dev: true
/optionator/0.8.3:
resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==}
engines: {node: '>= 0.8.0'}
dependencies:
deep-is: 0.1.3
fast-levenshtein: 2.0.6
levn: 0.3.0
prelude-ls: 1.1.2
type-check: 0.3.2
word-wrap: 1.2.3
dev: true
/optionator/0.9.1:
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
engines: {node: '>= 0.8.0'}
dependencies:
deep-is: 0.1.3
fast-levenshtein: 2.0.6
levn: 0.4.1
prelude-ls: 1.2.1
type-check: 0.4.0
word-wrap: 1.2.3
dev: 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.0
dev: 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.0
dev: true
/p-locate/2.0.0:
resolution: {integrity: sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=}
engines: {node: '>=4'}
dependencies:
p-limit: 1.3.0
dev: 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.0
dev: true
/p-locate/5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
dependencies:
p-limit: 3.1.0
dev: 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.0
dev: 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.0
dev: true
/parse-entities/1.2.2:
resolution: {integrity: sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==}
dependencies:
character-entities: 1.2.4
character-entities-legacy: 1.1.4
character-reference-invalid: 1.1.4
is-alphanumerical: 1.0.4
is-decimal: 1.0.4
is-hexadecimal: 1.0.4
dev: true
/parse-json/4.0.0:
resolution: {integrity: sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=}
engines: {node: '>=4'}
dependencies:
error-ex: 1.3.2
json-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.5
error-ex: 1.3.2
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.1.6
dev: 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.1
dev: true
/path-type/3.0.0:
resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}
engines: {node: '>=4'}
dependencies:
pify: 3.0.0
dev: 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.0
dev: 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.0
dev: true
/polka/0.5.2:
resolution: {integrity: sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==}
dependencies:
'@polka/url': 0.5.0
trouter: 2.0.1
dev: false
optional: 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.1
postcss: 7.0.36
postcss-syntax: 0.36.2_postcss@7.0.36
dev: 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.0
postcss: 7.0.36
postcss-syntax: 0.36.2_postcss@7.0.36
transitivePeerDependencies:
- supports-color
dev: true
/postcss-less/3.1.4:
resolution: {integrity: sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==}
engines: {node: '>=6.14.4'}
dependencies:
postcss: 7.0.36
dev: 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.36
postcss-syntax: 0.36.2_postcss@7.0.36
remark: 10.0.1
unist-util-find-all-after: 1.0.5
dev: 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.2
lodash: 4.17.21
log-symbols: 2.2.0
postcss: 7.0.36
dev: 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.36
dev: true
/postcss-sass/0.3.5:
resolution: {integrity: sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A==}
dependencies:
gonzales-pe: 4.3.0
postcss: 7.0.36
dev: true
/postcss-sass/0.4.4:
resolution: {integrity: sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==}
dependencies:
gonzales-pe: 4.3.0
postcss: 7.0.36
dev: true
/postcss-scss/2.1.1:
resolution: {integrity: sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==}
engines: {node: '>=6.0.0'}
dependencies:
postcss: 7.0.36
dev: true
/postcss-selector-parser/3.1.2:
resolution: {integrity: sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==}
engines: {node: '>=8'}
dependencies:
dot-prop: 5.3.0
indexes-of: 1.0.1
uniq: 1.0.1
dev: true
/postcss-selector-parser/6.0.6:
resolution: {integrity: sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==}
engines: {node: '>=4'}
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
dev: true
/postcss-sorting/4.1.0:
resolution: {integrity: sha512-r4T2oQd1giURJdHQ/RMb72dKZCuLOdWx2B/XhXN1Y1ZdnwXsKH896Qz6vD4tFy9xSjpKNYhlZoJmWyhH/7JUQw==}
engines: {node: '>=6.14.3'}
dependencies:
lodash: 4.17.21
postcss: 7.0.36
dev: true
/postcss-sorting/5.0.1:
resolution: {integrity: sha512-Y9fUFkIhfrm6i0Ta3n+89j56EFqaNRdUKqXyRp6kvTcSXnmgEjaVowCXH+JBe9+YKWqd4nc28r2sgwnzJalccA==}
engines: {node: '>=8.7.0'}
dependencies:
lodash: 4.17.21
postcss: 7.0.36
dev: 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.36
dev: 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.2
source-map: 0.6.1
supports-color: 6.1.0
dev: true
/postcss/8.3.6:
resolution: {integrity: sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
colorette: 1.2.2
nanoid: 3.1.23
source-map-js: 0.6.2
dev: 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.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.0
svelte: ^3.2.0
dependencies:
prettier: 2.2.1
svelte: 3.42.1
dev: true
/prettier/1.19.1:
resolution: {integrity: sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==}
engines: {node: '>=4'}
hasBin: true
dev: true
/prettier/2.2.1:
resolution: {integrity: sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: 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.4
once: 1.4.0
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.1
strict-uri-encode: 1.1.0
dev: 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.1
dev: true
/read-pkg-up/3.0.0:
resolution: {integrity: sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=}
engines: {node: '>=4'}
dependencies:
find-up: 2.1.0
read-pkg: 3.0.0
dev: true
/read-pkg-up/7.0.1:
resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
engines: {node: '>=8'}
dependencies:
find-up: 4.1.0
read-pkg: 5.2.0
type-fest: 0.8.1
dev: true
/read-pkg/3.0.0:
resolution: {integrity: sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=}
engines: {node: '>=4'}
dependencies:
load-json-file: 4.0.0
normalize-package-data: 2.5.0
path-type: 3.0.0
dev: true
/read-pkg/4.0.1:
resolution: {integrity: sha1-ljYlN48+HE1IyFhytabsfV0JMjc=}
engines: {node: '>=6'}
dependencies:
normalize-package-data: 2.5.0
parse-json: 4.0.0
pify: 3.0.0
dev: false
optional: true
/read-pkg/5.2.0:
resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
engines: {node: '>=8'}
dependencies:
'@types/normalize-package-data': 2.4.1
normalize-package-data: 2.5.0
parse-json: 5.2.0
type-fest: 0.6.0
dev: true
/readable-stream/2.3.7:
resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}
dependencies:
core-util-is: 1.0.2
inherits: 2.0.4
isarray: 1.0.0
process-nextick-args: 2.0.1
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
dev: true
/readable-stream/3.6.0:
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: true
/readdirp/3.5.0:
resolution: {integrity: sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.0
dev: true
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.0
dev: false
/redent/2.0.0:
resolution: {integrity: sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=}
engines: {node: '>=4'}
dependencies:
indent-string: 3.2.0
strip-indent: 2.0.0
dev: true
/redent/3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
dependencies:
indent-string: 4.0.0
strip-indent: 3.0.0
dev: true
/regex-not/1.0.2:
resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}
engines: {node: '>=0.10.0'}
dependencies:
extend-shallow: 3.0.2
safe-regex: 1.1.0
dev: true
/regexp-tree/0.1.23:
resolution: {integrity: sha512-+7HWfb4Bvu8Rs2eQTUIpX9I/PlQkYOuTNbRpKLJlQpSgwSkzFYh+pUj0gtvglnOZLKB6YgnIgRuJ2/IlpL48qw==}
hasBin: true
dev: 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.6
is-alphabetical: 1.0.4
is-decimal: 1.0.4
is-whitespace-character: 1.0.4
is-word-character: 1.0.4
markdown-escapes: 1.0.4
parse-entities: 1.2.2
repeat-string: 1.6.1
state-toggle: 1.0.3
trim: 0.0.1
trim-trailing-lines: 1.1.4
unherit: 1.1.3
unist-util-remove-position: 1.1.4
vfile-location: 2.0.6
xtend: 4.0.2
dev: true
/remark-stringify/6.0.4:
resolution: {integrity: sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==}
dependencies:
ccount: 1.1.0
is-alphanumeric: 1.0.0
is-decimal: 1.0.4
is-whitespace-character: 1.0.4
longest-streak: 2.0.4
markdown-escapes: 1.0.4
markdown-table: 1.1.3
mdast-util-compact: 1.0.4
parse-entities: 1.2.2
repeat-string: 1.6.1
state-toggle: 1.0.3
stringify-entities: 1.3.2
unherit: 1.1.3
xtend: 4.0.2
dev: true
/remark/10.0.1:
resolution: {integrity: sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==}
dependencies:
remark-parse: 6.0.3
remark-stringify: 6.0.4
unified: 7.1.0
dev: 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/3142
dependencies:
aws-sign2: 0.7.0
aws4: 1.11.0
caseless: 0.12.0
combined-stream: 1.0.8
extend: 3.0.2
forever-agent: 0.6.1
form-data: 2.3.3
har-validator: 5.1.5
http-signature: 1.2.0
is-typedarray: 1.0.0
isstream: 0.1.2
json-stringify-safe: 5.0.1
mime-types: 2.1.32
oauth-sign: 0.9.0
performance-now: 2.1.0
qs: 6.5.2
safe-buffer: 5.2.1
tough-cookie: 2.5.0
tunnel-agent: 0.6.0
uuid: 3.4.0
dev: 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#deprecated
dev: true
/resolve/1.20.0:
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
dependencies:
is-core-module: 2.5.0
path-parse: 1.0.7
/restore-cursor/2.0.0:
resolution: {integrity: sha1-n37ih/gv0ybU/RYpI9YhKe7g368=}
engines: {node: '>=4'}
dependencies:
onetime: 2.0.1
signal-exit: 3.0.3
dev: true
/restore-cursor/3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
dependencies:
onetime: 5.1.2
signal-exit: 3.0.3
dev: 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: true
dependencies:
glob: 7.1.7
dev: true
/rimraf/3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
dependencies:
glob: 7.1.7
dev: true
/rollup/2.56.1:
resolution: {integrity: sha512-KkrsNjeiTfGJMUFBi/PNfj3fnt70akqdoNXOjlzwo98uA1qrlkmgt6SGaK5OwhyDYCVnJb6jb2Xa2wbI47P4Nw==}
engines: {node: '>=10.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: 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: true
dev: true
/run-parallel/1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
dependencies:
queue-microtask: 1.2.3
dev: 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.6
dev: 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.15
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: true
dependencies:
glob: 7.1.7
lodash: 4.17.21
scss-tokenizer: 0.2.3
yargs: 13.3.2
dev: true
/sass/1.37.5:
resolution: {integrity: sha512-Cx3ewxz9QB/ErnVIiWg2cH0kiYZ0FPvheDTVC6BsiEGBTZKKZJ1Gq5Kq6jy3PKtL6+EJ8NIoaBW/RSd2R6cZOA==}
engines: {node: '>=8.9.0'}
hasBin: true
dependencies:
chokidar: 3.5.2
dev: false
/scss-tokenizer/0.2.3:
resolution: {integrity: sha1-jrBtualyMzOCTT9VMGQRSYR85dE=}
dependencies:
js-base64: 2.6.4
source-map: 0.4.4
dev: true
/secure-remote-password/0.3.1:
resolution: {integrity: sha512-iEp/qLRfb9XYhfKFrPFfdeD7KVreCjhDKSTRP1G1nRIO0Sw1hjnVHD58ymOhiy9Zf5quHbDIbG9cTupji7qwnA==}
dependencies:
array-buffer-to-hex: 1.0.0
crypto-digest-sync: 1.0.0
crypto-random-hex: 1.0.0
encode-utf8: 1.0.3
hex-to-array-buffer: 1.1.0
jsbn: 1.1.0
pad-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: true
dev: true
/semver/7.3.5:
resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}
engines: {node: '>=10'}
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: true
/serialize-javascript/5.0.1:
resolution: {integrity: sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==}
dependencies:
randombytes: 2.1.0
dev: 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.1
is-extendable: 0.1.1
is-plain-object: 2.0.4
split-string: 3.1.0
dev: true
/shebang-command/1.2.0:
resolution: {integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=}
engines: {node: '>=0.10.0'}
dependencies:
shebang-regex: 1.0.0
dev: true
/shebang-command/2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
dependencies:
shebang-regex: 3.0.0
dev: 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.0
dev: true
/should-format/3.0.3:
resolution: {integrity: sha1-m/yPdPo5IFxT04w01xcwPidxJPE=}
dependencies:
should-type: 1.4.0
should-type-adaptors: 1.1.0
dev: true
/should-type-adaptors/1.1.0:
resolution: {integrity: sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==}
dependencies:
should-type: 1.4.0
should-util: 1.0.1
dev: 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.1
should-format: 3.0.3
should-type: 1.4.0
should-type-adaptors: 1.1.0
should-util: 1.0.1
dev: true
/side-channel/1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
call-bind: 1.0.2
get-intrinsic: 1.1.1
object-inspect: 1.11.0
dev: 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.0
sinon: '>=4.0.0'
dependencies:
chai: 4.3.4
sinon: 7.5.0
dev: true
/sinon-chai/3.7.0_chai@4.3.4+sinon@9.2.4:
resolution: {integrity: sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==}
peerDependencies:
chai: ^4.0.0
sinon: '>=4.0.0'
dependencies:
chai: 4.3.4
sinon: 9.2.4
dev: 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.3
diff: 3.5.0
lolex: 4.2.0
nise: 1.5.3
supports-color: 5.5.0
dev: 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.1
diff: 4.0.2
nise: 4.1.0
supports-color: 7.2.0
dev: true
/sirv/0.4.6:
resolution: {integrity: sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ==}
engines: {node: '>= 6'}
dependencies:
'@polka/url': 0.5.0
mime: 2.5.2
dev: false
optional: 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.1
astral-regex: 1.0.0
is-fullwidth-code-point: 2.0.0
dev: true
/slice-ansi/4.0.0:
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
astral-regex: 2.0.0
is-fullwidth-code-point: 3.0.0
dev: true
/snapdragon-node/2.1.1:
resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==}
engines: {node: '>=0.10.0'}
dependencies:
define-property: 1.0.0
isobject: 3.0.1
snapdragon-util: 3.0.1
dev: true
/snapdragon-util/3.0.1:
resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==}
engines: {node: '>=0.10.0'}
dependencies:
kind-of: 3.2.2
dev: true
/snapdragon/0.8.2:
resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
engines: {node: '>=0.10.0'}
dependencies:
base: 0.11.2
debug: 2.6.9
define-property: 0.2.5
extend-shallow: 2.0.1
map-cache: 0.2.2
source-map: 0.5.7
source-map-resolve: 0.5.3
use: 3.1.1
dev: 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.2
decode-uri-component: 0.2.0
resolve-url: 0.2.1
source-map-url: 0.4.1
urix: 0.1.0
dev: 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.1
dev: 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: false
optional: true
/spdx-correct/3.1.1:
resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
dependencies:
spdx-expression-parse: 3.0.1
spdx-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.0
spdx-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: true
dev: 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.2
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: true
dependencies:
asn1: 0.2.4
assert-plus: 1.0.0
bcrypt-pbkdf: 1.0.2
dashdash: 1.14.1
ecc-jsbn: 0.1.2
getpass: 0.1.7
jsbn: 0.1.1
safer-buffer: 2.1.2
tweetnacl: 0.14.5
dev: 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.5
object-copy: 0.1.0
dev: true
/stdout-stream/1.4.1:
resolution: {integrity: sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==}
dependencies:
readable-stream: 2.3.7
dev: 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.0
is-fullwidth-code-point: 1.0.0
strip-ansi: 3.0.1
dev: true
/string-width/2.1.1:
resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}
engines: {node: '>=4'}
dependencies:
is-fullwidth-code-point: 2.0.0
strip-ansi: 4.0.0
dev: true
/string-width/3.1.0:
resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}
engines: {node: '>=6'}
dependencies:
emoji-regex: 7.0.3
is-fullwidth-code-point: 2.0.0
strip-ansi: 5.2.0
/string-width/4.2.2:
resolution: {integrity: sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.0
dev: true
/string.prototype.trimend/1.0.4:
resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==}
dependencies:
call-bind: 1.0.2
define-properties: 1.1.3
dev: true
/string.prototype.trimstart/1.0.4:
resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==}
dependencies:
call-bind: 1.0.2
define-properties: 1.1.3
dev: true
/string_decoder/1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
dependencies:
safe-buffer: 5.1.2
dev: true
/string_decoder/1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies:
safe-buffer: 5.2.1
dev: true
/stringify-entities/1.3.2:
resolution: {integrity: sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==}
dependencies:
character-entities-html4: 1.1.4
character-entities-legacy: 1.1.4
is-alphanumerical: 1.0.4
is-hexadecimal: 1.0.4
dev: true
/stringify-object/3.3.0:
resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
engines: {node: '>=4'}
dependencies:
get-own-enumerable-property-symbols: 3.0.2
is-obj: 1.0.1
is-regexp: 1.0.0
dev: true
/strip-ansi/3.0.1:
resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=}
engines: {node: '>=0.10.0'}
dependencies:
ansi-regex: 2.1.1
dev: true
/strip-ansi/4.0.0:
resolution: {integrity: sha1-qEeQIusaw2iocTibY1JixQXuNo8=}
engines: {node: '>=4'}
dependencies:
ansi-regex: 3.0.0
dev: 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.0
dev: 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.1
dev: 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: true
peerDependencies:
stylelint: ^9.1.1 || ^10.0.0 || ^11.0.0
dependencies:
stylelint: 11.1.1
dev: true
/stylelint-config-rational-order/0.1.2:
resolution: {integrity: sha512-Qo7ZQaihCwTqijfZg4sbdQQHtugOX/B1/fYh018EiDZHW+lkqH9uHOnsDwDPGZrYJuB6CoyI7MZh2ecw2dOkew==}
dependencies:
stylelint: 9.10.1
stylelint-order: 2.2.1_stylelint@9.10.1
transitivePeerDependencies:
- supports-color
dev: 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.0
dependencies:
stylelint: 11.1.1
dev: 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.0
dependencies:
stylelint: 11.1.1
stylelint-config-recommended: 2.2.0_stylelint@11.1.1
dev: 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.0
dependencies:
lodash: 4.17.21
postcss: 7.0.36
postcss-sorting: 4.1.0
stylelint: 9.10.1
dev: 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.21
postcss: 7.0.36
postcss-sorting: 5.0.1
stylelint: 11.1.1
dev: 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.1
prettier-linter-helpers: 1.0.0
stylelint: 11.1.1
dev: 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.0
dependencies:
lodash: 4.17.21
postcss-media-query-parser: 0.2.3
postcss-resolve-nested-selector: 0.1.1
postcss-selector-parser: 6.0.6
postcss-value-parser: 4.1.0
stylelint: 11.1.1
dev: true
/stylelint/11.1.1:
resolution: {integrity: sha512-Vx6TAJsxG6qksiFvxQTKriQhp1CqUWdpTDITEkAjTR+l+8Af7qNlvrUDXfpuFJgXh/ayF8xdMSKE+SstcsPmMA==}
engines: {node: '>=8.7.0'}
hasBin: true
dependencies:
autoprefixer: 9.8.6
balanced-match: 1.0.2
chalk: 2.4.2
cosmiconfig: 5.2.1
debug: 4.3.2
execall: 2.0.0
file-entry-cache: 5.0.1
get-stdin: 7.0.0
global-modules: 2.0.0
globby: 9.2.0
globjoin: 0.1.4
html-tags: 3.1.0
ignore: 5.1.8
import-lazy: 4.0.0
imurmurhash: 0.1.4
known-css-properties: 0.16.0
leven: 3.1.0
lodash: 4.17.21
log-symbols: 3.0.0
mathml-tag-names: 2.1.3
meow: 5.0.0
micromatch: 4.0.4
normalize-selector: 0.2.0
postcss: 7.0.36
postcss-html: 0.36.0_2b33a41d320e3e2012e5b3b0fadc703b
postcss-jsx: 0.36.4_2b33a41d320e3e2012e5b3b0fadc703b
postcss-less: 3.1.4
postcss-markdown: 0.36.0_2b33a41d320e3e2012e5b3b0fadc703b
postcss-media-query-parser: 0.2.3
postcss-reporter: 6.0.1
postcss-resolve-nested-selector: 0.1.1
postcss-safe-parser: 4.0.2
postcss-sass: 0.4.4
postcss-scss: 2.1.1
postcss-selector-parser: 3.1.2
postcss-syntax: 0.36.2_postcss@7.0.36
postcss-value-parser: 4.1.0
resolve-from: 5.0.0
signal-exit: 3.0.3
slash: 3.0.0
specificity: 0.4.1
string-width: 4.2.2
strip-ansi: 5.2.0
style-search: 0.1.0
sugarss: 2.0.0
svg-tags: 1.0.0
table: 5.4.6
v8-compile-cache: 2.3.0
transitivePeerDependencies:
- supports-color
dev: true
/stylelint/9.10.1:
resolution: {integrity: sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ==}
engines: {node: '>=6'}
hasBin: true
dependencies:
autoprefixer: 9.8.6
balanced-match: 1.0.2
chalk: 2.4.2
cosmiconfig: 5.2.1
debug: 4.3.2
execall: 1.0.0
file-entry-cache: 4.0.0
get-stdin: 6.0.0
global-modules: 2.0.0
globby: 9.2.0
globjoin: 0.1.4
html-tags: 2.0.0
ignore: 5.1.8
import-lazy: 3.1.0
imurmurhash: 0.1.4
known-css-properties: 0.11.0
leven: 2.1.0
lodash: 4.17.21
log-symbols: 2.2.0
mathml-tag-names: 2.1.3
meow: 5.0.0
micromatch: 3.1.10
normalize-selector: 0.2.0
pify: 4.0.1
postcss: 7.0.36
postcss-html: 0.36.0_2b33a41d320e3e2012e5b3b0fadc703b
postcss-jsx: 0.36.4_2b33a41d320e3e2012e5b3b0fadc703b
postcss-less: 3.1.4
postcss-markdown: 0.36.0_2b33a41d320e3e2012e5b3b0fadc703b
postcss-media-query-parser: 0.2.3
postcss-reporter: 6.0.1
postcss-resolve-nested-selector: 0.1.1
postcss-safe-parser: 4.0.2
postcss-sass: 0.3.5
postcss-scss: 2.1.1
postcss-selector-parser: 3.1.2
postcss-syntax: 0.36.2_postcss@7.0.36
postcss-value-parser: 3.3.1
resolve-from: 4.0.0
signal-exit: 3.0.3
slash: 2.0.0
specificity: 0.4.1
string-width: 3.1.0
style-search: 0.1.0
sugarss: 2.0.0
svg-tags: 1.0.0
table: 5.4.6
transitivePeerDependencies:
- supports-color
dev: true
/sugarss/2.0.0:
resolution: {integrity: sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==}
dependencies:
postcss: 7.0.36
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.0
/supports-color/6.0.0:
resolution: {integrity: sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==}
engines: {node: '>=6'}
dependencies:
has-flag: 3.0.0
dev: 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.0
dev: true
/supports-color/8.1.1:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
dev: true
/svelte-hmr/0.14.7_svelte@3.42.1:
resolution: {integrity: sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==}
peerDependencies:
svelte: '>=3.19.0'
dependencies:
svelte: 3.42.1
dev: 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.0
dev: true
/svelte-preprocess/3.9.12_svelte@3.42.1:
resolution: {integrity: sha512-OX8a7drmlYcX/bLKbtRTvcc0lYu5Ub78D4B/GVxac2zeyrj1e5vEJU6BsxFbc/8kFDqI6BgsCLZAqsFDr/KrDQ==}
engines: {node: '>= 7.6.0'}
requiresBuild: true
peerDependencies:
'@babel/core': ^7.10.2
coffeescript: ^2.5.1
less: ^3.11.3
node-sass: '*'
postcss: ^7.0.32
postcss-load-config: ^2.1.0
pug: ^3.0.0
sass: ^1.26.8
stylus: ^0.54.7
svelte: ^3.23.0
typescript: ^3.9.5
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
svelte:
optional: true
typescript:
optional: true
dependencies:
'@types/pug': 2.0.5
'@types/sass': 1.16.1
detect-indent: 6.1.0
strip-indent: 3.0.0
svelte: 3.42.1
dev: true
/svelte-preprocess/4.7.4_6197623e5ed34153d1bcd9290e2954d7:
resolution: {integrity: sha512-mDAmaltQl6e5zU2VEtoWEf7eLTfuOTGr9zt+BpA3AGHo8MIhKiNSPE9OLTCTOMgj0vj/uL9QBbaNmpG4G1CgIA==}
engines: {node: '>= 9.11.2'}
requiresBuild: true
peerDependencies:
'@babel/core': ^7.10.2
coffeescript: ^2.5.1
less: ^3.11.3
node-sass: '*'
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
dependencies:
'@types/pug': 2.0.5
'@types/sass': 1.16.1
detect-indent: 6.1.0
node-sass: 6.0.1
sass: 1.37.5
strip-indent: 3.0.0
svelte: 3.42.1
dev: 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.6
lodash: 4.17.21
slice-ansi: 2.1.0
string-width: 3.1.0
dev: true
/table/6.7.1:
resolution: {integrity: sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==}
engines: {node: '>=10.0.0'}
dependencies:
ajv: 8.6.2
lodash.clonedeep: 4.5.0
lodash.truncate: 4.4.2
slice-ansi: 4.0.0
string-width: 4.2.2
strip-ansi: 6.0.0
dev: true
/tar/6.1.7:
resolution: {integrity: sha512-PBoRkOJU0X3lejJ8GaRCsobjXTgFofRDSPdSUhRSdlwJfifRlQBwGXitDItdGFu0/h0XDMCkig0RN1iT7DBxhA==}
engines: {node: '>= 10'}
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 3.1.3
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
dev: 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.0
globrex: 0.1.2
dev: true
/tmp/0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
engines: {node: '>=0.6.0'}
dependencies:
os-tmpdir: 1.0.2
dev: 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.2
dev: true
/to-regex-range/2.1.1:
resolution: {integrity: sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=}
engines: {node: '>=0.10.0'}
dependencies:
is-number: 3.0.0
repeat-string: 1.6.1
dev: 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.2
extend-shallow: 3.0.2
regex-not: 1.0.2
safe-regex: 1.1.0
dev: true
/tough-cookie/2.5.0:
resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
engines: {node: '>=0.8'}
dependencies:
psl: 1.8.0
punycode: 2.1.1
dev: true
/tree-kill/1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
dev: false
optional: 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.0
dev: false
optional: true
/true-case-path/1.0.3:
resolution: {integrity: sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==}
dependencies:
glob: 7.1.7
dev: 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.1
dev: 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.2
dev: true
/type-check/0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
dependencies:
prelude-ls: 1.2.1
dev: 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: true
dev: true
/unbox-primitive/1.0.1:
resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==}
dependencies:
function-bind: 1.1.1
has-bigints: 1.0.1
has-symbols: 1.0.2
which-boxed-primitive: 1.0.2
dev: true
/unherit/1.1.3:
resolution: {integrity: sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==}
dependencies:
inherits: 2.0.4
xtend: 4.0.2
dev: true
/unified/7.1.0:
resolution: {integrity: sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==}
dependencies:
'@types/unist': 2.0.6
'@types/vfile': 3.0.2
bail: 1.0.5
extend: 3.0.2
is-plain-obj: 1.1.0
trough: 1.0.5
vfile: 3.0.1
x-is-string: 0.1.0
dev: 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.0
get-value: 2.0.6
is-extendable: 0.1.1
set-value: 2.0.1
dev: 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.0
dev: 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.1
dev: 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.6
dev: true
/unist-util-visit-parents/2.1.2:
resolution: {integrity: sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==}
dependencies:
unist-util-is: 3.0.0
dev: true
/unist-util-visit/1.4.1:
resolution: {integrity: sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==}
dependencies:
unist-util-visit-parents: 2.1.2
dev: true
/unset-value/1.0.0:
resolution: {integrity: sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=}
engines: {node: '>=0.10.0'}
dependencies:
has-value: 0.3.1
isobject: 3.0.1
dev: true
/uri-js/4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
punycode: 2.1.1
dev: true
/urix/0.1.0:
resolution: {integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=}
deprecated: Please see https://github.com/lydell/urix#deprecated
dev: 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: true
dev: 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.1
spdx-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.0
core-util-is: 1.0.2
extsprintf: 1.3.0
dev: 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.2
dev: true
/vfile-message/3.0.1:
resolution: {integrity: sha512-gYmSHcZZUEtYpTmaWaFJwsuUD70/rTY4v09COp8TGtOkix6gGxb/a8iTQByIY9ciTk9GwAwIXd/J9OPfM4Bvaw==}
dependencies:
'@types/unist': 2.0.6
unist-util-stringify-position: 3.0.0
dev: true
/vfile/3.0.1:
resolution: {integrity: sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==}
dependencies:
is-buffer: 2.0.5
replace-ext: 1.0.0
unist-util-stringify-position: 1.1.2
vfile-message: 1.1.1
dev: true
/vite/2.4.4:
resolution: {integrity: sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw==}
engines: {node: '>=12.0.0'}
hasBin: true
dependencies:
esbuild: 0.12.19
postcss: 8.3.6
resolve: 1.20.0
rollup: 2.56.1
optionalDependencies:
fsevents: 2.3.2
dev: true
/which-boxed-primitive/1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
dependencies:
is-bigint: 1.0.3
is-boolean-object: 1.1.2
is-number-object: 1.0.6
is-string: 1.0.7
is-symbol: 1.0.4
dev: true
/which-module/2.0.0:
resolution: {integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=}
/which/1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true
dependencies:
isexe: 2.0.0
dev: true
/which/2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
dependencies:
isexe: 2.0.0
dev: true
/wide-align/1.1.3:
resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==}
dependencies:
string-width: 2.1.1
dev: 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.1
strip-ansi: 4.0.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.1
string-width: 3.1.0
strip-ansi: 5.2.0
/wrap-ansi/7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.2
strip-ansi: 6.0.0
dev: 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.5
dev: true
/ws/6.2.2:
resolution: {integrity: sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==}
dependencies:
async-limiter: 1.0.1
dev: 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.0
dev: true
/yargs-parser/13.1.2:
resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==}
dependencies:
camelcase: 5.3.1
decamelize: 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.1
lodash: 4.17.21
yargs: 13.3.2
dev: true
/yargs-unparser/2.0.0:
resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
engines: {node: '>=10'}
dependencies:
camelcase: 6.2.0
decamelize: 4.0.0
flat: 5.0.2
is-plain-obj: 2.1.0
dev: true
/yargs/13.3.2:
resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==}
dependencies:
cliui: 5.0.0
find-up: 3.0.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 3.1.0
which-module: 2.0.0
y18n: 4.0.3
yargs-parser: 13.1.2
/yargs/16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
dependencies:
cliui: 7.0.4
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.2
y18n: 5.0.8
yargs-parser: 20.2.4
dev: 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.undefined
expect(pointer._topic).to.be.undefined
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.equal('/a')
expect(pointer._steps).to.be.undefined
expect(pointer._topic).to.be.undefined
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.equal('/a/b')
expect(pointer._steps).to.be.undefined
expect(pointer._topic).to.be.undefined
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
})
it('[]', () => {
const pointer = new Pointer([])
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.eql([])
expect(pointer._topic).to.be.undefined
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.undefined
expect(pointer._steps).to.eql(['a'])
expect(pointer._topic).to.be.undefined
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.undefined
expect(pointer._steps).to.eql(['a', 'b'])
expect(pointer._topic).to.be.undefined
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
})
it('"#"', () => {
const pointer = new Pointer('#')
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.be.undefined
expect(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.undefined
expect(pointer._steps).to.be.undefined
expect(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.undefined
expect(pointer._steps).to.be.undefined
expect(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.undefined
expect(pointer._topic).to.be.undefined
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.path = '/a/b'
expect(pointer._path).to.equal('/a/b')
expect(pointer._steps).to.be.undefined
expect(pointer._topic).to.be.undefined
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.steps =', () => {
it('["a"]', () => {
const pointer = new Pointer([])
pointer.steps = ['a']
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.eql(['a'])
expect(pointer._topic).to.be.undefined
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.steps = ['a', 'b']
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.eql(['a', 'b'])
expect(pointer._topic).to.be.undefined
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.topic =', () => {
it('"a"', () => {
const pointer = new Pointer('#')
pointer.topic = 'a'
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.be.undefined
expect(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.undefined
expect(pointer._steps).to.be.undefined
expect(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] = test
describe(`"${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 = undefined
expect(pointer.grove_path).to.be.null
expect(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 = undefined
expect(pointer.flag).to.be.null
expect(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 = undefined
expect(pointer.trunk_path).to.be.null
expect(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 = undefined
expect(pointer.branch_path).to.be.null
expect(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 = undefined
expect(pointer.leaf).to.be.null
expect(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] = test
describe(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.prepareStackTrace
var callerfile
try {
var error = new Error()
var currentfile
Error.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 = originalFunc
return 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: Kitchen
component_paths:
- /components/1
display_paths:
- /components/1/displays/1
'2':
name: Breakfast Room
component_paths:
- /components/2
display_paths:
- /components/2/displays/1
components:
'1':
name: Display
location_paths:
- /rooms/1
displays:
'1':
name: Kitchen Display
'2':
name: Display
location_paths:
- /rooms/2
displays:
'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 1
location_paths:
- /rooms/1
'2':
name: Component 2
'3':
name: Component 3
displays:
'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._root
expect(result).to.eql({})
})
it('"/a/b"', () => {
datastore.set('/a/b', true)
datastore.delete('/a/b')
const result = datastore._root
expect(result).to.eql({})
})
it('"/a/b/c"', () => {
datastore.set('/a/b/c', true)
datastore.delete('/a/b/c')
const result = datastore._root
expect(result).to.eql({})
})
it('"/a2"', () => {
datastore.set('/a1', true)
datastore.set('/a2', true)
datastore.delete('/a2')
const result = datastore._root
expect(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['#']._value
expect(map).to.be.empty
expect(() => {
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']['#']._value
expect(map).to.be.empty
expect(() => {
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 topic
it('"/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 chain
describe('.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.called
datastore.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._root
expect(result).to.eql({})
})
it('"/a/b"', () => {
datastore.set('/a/b', true)
datastore.delete('/a/b')
const result = datastore._root
expect(result).to.eql({})
})
it('"/a/b/c"', () => {
datastore.set('/a/b/c', true)
datastore.delete('/a/b/c')
const result = datastore._root
expect(result).to.eql({})
})
it('"/a2"', () => {
datastore.set('/a1', true)
datastore.set('/a2', true)
datastore.delete('/a2')
const result = datastore._root
expect(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 group
describe('.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 callback
before(() => {
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.undefined
expect(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 rarely
all(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._root
for (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._root
for (const step of steps) {
if (node[step] == null) {
node[step] = this.createTreeNode()
}
node = node[step]
}
if (node._value == null) {
node._topic = topic
node._value = value
}
return node
}
add(topic, value) {
const node = this.getWithDefault(topic)
node._topic = topic
node._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 this
return this.replace(`/${this.root}/`, `/q/${this.root}/`)
}
dequeue() {
if (this.flag != 'q') return this
return 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 text
return text.replace(/~/g, '~0').replace(/\//g, '~1')
}
const toPath = text => {
if (text.slice(0, 2) != '~1') return text
return 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 = 0
if (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 = 0
if (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 = 0
if (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 = 0
if (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 = null
this._twig_end = null
}
return
}
if (this.leaf == null) {
this._twig_start = this.steps.length
this._twig_end = this._twig_start + 1
}
this.steps[this._twig_end - 1] = value
this.steps = this.steps // eslint-disable-line
}
parse() {
let cursor = 0
Object.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 = cursor
cursor += 1
break
case 'session':
this._root_start = cursor
cursor += 1
this.parseSession(cursor)
return
}
switch (this.steps[cursor]) {
case 'action':
case 'state':
case 'setup':
case '+':
this._root_start = cursor
cursor += 1
break
}
switch (this.steps[cursor]) {
case 'users':
case 'events':
case 'items':
this._trunk_start = cursor
this._branch_start = cursor
this._trunk_end = cursor + 2
break
case 'admin':
this._trunk_start = cursor
this._branch_start = cursor
this._trunk_end = cursor + 1
break
case 'services':
this._trunk_start = cursor
this._trunk_end = cursor + 1
cursor += 1
this.parseService(cursor)
return
}
const limit = this.steps.length - 1
while (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 += 2
this._branch_end = cursor
}
if (cursor < this.steps.length) {
this._twig_start = cursor
this._twig_end = this.steps.length
}
this._parsed = true
}
parseSession(cursor) {
switch (this.steps[cursor]) {
case 'path':
case 'redirect':
case 'user':
this.twig_start = cursor
cursor += 1
this.twig_end = this.steps.length
return
case 'services':
this._trunk_start = cursor
this._trunk_end = cursor + 1
cursor += 1
this.parseService(cursor)
return
}
this._parsed = true
}
parseService(cursor) {
switch (this.steps[cursor]) {
case 'spotify':
case 'apple-music':
this._branch_start = cursor
this._branch_end = cursor + 1
cursor += 1
break
}
const limit = this.steps.length - 1
while (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 += 2
this._branch_end = cursor
}
if (cursor < this.steps.length) {
this._twig_start = cursor
this._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_topic
return
}
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 = value
return
}
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 = value
return
}
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.topic
const subscription_maps = this._topic_tree.entries(topic)
if (subscription_maps == null) {
return
}
subscription_maps.forEach(entry => {
const [topic, subscription_map] = entry
if (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] = entry
subscription_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 topics
const subscription_map = this._topic_tree.getWithDefault(topic, new Map())._value
subscription_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') return
callback(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) return
subscriber_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)._value
if (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._root
pointer.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, 'when')
}
}
left = left[step]
})
left[last] = value
if (!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] = entry
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 = `${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] = entry
subscription_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 topics
const subscription_map = this._topic_tree.getWithDefault(topic, new Map())._value
subscription_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] = key
const next = node[key]
if (steps.length - 1 === pivot) {
const path = `/${steps.join('/')}`
results[path] = next
continue
}
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] = next
return 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 = 1
while (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._root
for (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._root
pointer.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] = value
if (!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 = datastore
Object.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 = callbacks
pipe.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.dependents
delete 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 = preferences
this.default_value = default_value
this.steps.forEach((step, i) => {
this.group.addDependent(step, this, i)
})
}
toString() {
return `[Preferable ${JSON.stringify(this.name)}]`
}
update() {
this.value = this.default_value
for (const step of this.steps) {
if (step != null) {
this.value = step
break
}
}
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_name
this.replacement = replacement
this.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) return
this.value = next
console.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 = callbacks
if (this.topic_or_path === topic_or_path) {
return false
}
this.topic_or_path = topic_or_path
this.pointer = Pointer.create(topic_or_path)
this.steps = []
this.is_static = true
this.pointer.steps.forEach((step, i) => {
if (step.startsWith(':')) {
step = step.slice(1)
this.is_static = false
this.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 = path
if (!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 = callbacks
if (this.topic_or_path === topic_or_path) {
return false
}
this.topic_or_path = topic_or_path
this.pointer = Pointer.create(topic_or_path)
this.steps = []
this.is_static = true
this.pointer.steps.forEach((step, i) => {
if (step.startsWith(':')) {
step = step.slice(1)
this.is_static = false
this.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.topic
return
}
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 = value
if (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 = value
this.notify()
}
}
export { Injectable }
class Base {
constructor(group, name, ...callbacks) {
this.group = group
this.name = name
this.callbacks = callbacks
this.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, Pathable
dependent.steps[position] = this.value
dependent.update()
} else {
// Pipable
dependent.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 // seconds
class IPCClient {
constructor(socket_path, client_id, { subscriptions = [], publications = [] } = {}) {
this.name = socket_path.split('/').slice(-1)
this.client_id = client_id
this.destroyed = false
this.socket_path = socket_path
this.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] = resolve
this.send(message)
})
}
async onRead(message) {
const resolver = this.readers(message.id)
if (!resolver) return
pointer = 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) return
console.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)) return
try {
console.log(`connect() ${this.socket_path}`)
this.socket = net.createConnection(this.socket_path, () => {
console.log('Connected')
this.socket.connected = true
this.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 = false
try {
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) return
this.socket.reading = true
while (this.socket.data_buffer.length >= 4) {
const length = this.getMessageLength(this.socket.data_buffer)
if (this.socket.data_buffer.length < 4 + length) break
const 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':
break
this.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.expect
import 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 wildcard
expectations:
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 wildcard
expectations:
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/name
tests:
- label: MUST set queue flag
call: [flag, q]
expectations: [[flag, q], [topic, q/setup/rooms/1/name]]
- label: MUST remove queue flag
call: [flag, undefined]
expectations: [[flag, null], [topic, setup/rooms/1/name]]
'#trunk_path=':
- label: /setup/rooms/1/name
tests:
- label: MUST set trunk_path
call: [trunk_path, /rooms/2]
expectations: [[trunk_path, /rooms/2], [topic, setup/rooms/2/name]]
- label: MUST remove trunk_path
call: [trunk_path, undefined]
expectations: [[trunk_path, null], [topic, setup/name]]
'#branch_path=':
- label: /setup/rooms/1/name
tests:
- label: MUST set branch_path
call: ['branch_path', '/components/1/displays/1']
expectations: [['branch_path', '/components/1/displays/1'], ['topic', 'setup/components/1/displays/1/name']]
- label: MUST remove branch_path
call: ['branch_path', undefined]
expectations: [['branch_path', null], [topic, setup/name]]
'#leaf=':
- label: '/setup/rooms/1/name'
tests:
- label: MUST set leaf
call: [leaf, description]
expectations: [[leaf, description], [topic, setup/rooms/1/description]]
- label: MUST remove leaf
call: [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 defined
call: /drivers/00000000-0000-0000-0000-000000000000/q/setup/components/1/name
expectation: 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 pointer
pointer: /state/components/1/power
call: ['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 object
pointer: ''
call: ['d']
expectation: { o: "d", p: "" }
- label: MUST create delete message
pointer: /setup/components/1
call: ['d']
expectation: { o: "d", p: "/setup/components/1"}
'#queue':
- label: adds a 'q' flag to a pointer
pointer: /state/components/1/power
call: [queue, null]
expectation: [topic, q/state/components/1/power]
- label: does not modify pointers that are queued
pointer: /q/state/components/1/power
call: [queue, null]
expectation: [topic, q/state/components/1/power]
'#dequeue':
- label: removes a 'q' flag from a pointer
pointer: /q/action/components/1/power
call: [dequeue, null]
expectation: [topic, action/components/1/power]
- label: does not modify pointers that are not queued
pointer: /action/components/1/power
call: [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 trunk
pointer: /action/components/1/power
call: [changeTrunk, /rooms/1]
expectation: [topic, action/rooms/1/power]
'#changeBranch':
- label: MUST change a branch
pointer: /action/components/1/power
call: [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.expect
import 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 wildcard
expectations:
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 wildcard
expectations:
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/name
tests:
- label: MUST set queue flag
call: [flag, q]
expectations: [[flag, q], [path, /q/setup/rooms/1/name]]
- label: MUST remove queue flag
call: [flag, undefined]
expectations: [[flag, null], [path, /setup/rooms/1/name]]
'#trunk_path=':
- label: /setup/rooms/1/name
tests:
- label: MUST set trunk_path
call: [trunk_path, /rooms/2]
expectations: [[trunk_path, /rooms/2], [path, /setup/rooms/2/name]]
- label: MUST remove trunk_path
call: [trunk_path, undefined]
expectations: [[trunk_path, null], [path, /setup/name]]
'#branch_path=':
- label: /setup/rooms/1/name
tests:
- label: MUST set branch_path
call: ['branch_path', '/components/1/displays/1']
expectations: [['branch_path', '/components/1/displays/1'], ['path', '/setup/components/1/displays/1/name']]
- label: MUST remove branch_path
call: ['branch_path', undefined]
expectations: [['branch_path', null], ['path', '/setup/name']]
'#leaf=':
- label: '/setup/rooms/1/name'
tests:
- label: MUST set leaf
call: [leaf, description]
expectations: [[leaf, description], [path, /setup/rooms/1/description]]
- label: MUST remove leaf
call: [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 defined
call: /drivers/00000000-0000-0000-0000-000000000000/q/setup/components/1/name
expectation: /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 pointer
pointer: /state/components/1/power
call: ['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
pointer: ''
call: ['d']
expectation: { o: "d", p: "" }
- label: MUST create delete message object
pointer: /setup/components/1
call: ['d']
expectation: { o: "d", p: "/setup/components/1"}
'#queue':
- label: adds a 'q' flag to a pointer
pointer: /state/components/1/power
call: [queue, null]
expectation: [path, /q/state/components/1/power]
- label: does not modify pointers that are queued
pointer: /q/state/components/1/power
call: [queue, null]
expectation: [path, /q/state/components/1/power]
'#dequeue':
- label: removes a 'q' flag from a pointer
pointer: /q/action/components/1/power
call: [dequeue, null]
expectation: [path, /action/components/1/power]
- label: does not modify pointers that are not queued
pointer: /action/components/1/power
call: [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 trunk
pointer: /action/components/1/power
call: [changeTrunk, /rooms/1]
expectation: [path, /action/rooms/1/power]
'#changeBranch':
- label: MUST change a branch
pointer: /action/components/1/power
call: [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.expect
import 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 = 4
const 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.expect
import 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 = 4
const 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.expect
import 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] = value
test['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] = expectation
expect(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.expect
import 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] = value
test['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] = expectation
expect(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.prepareStackTrace
var callerfile
try {
var error = new Error()
var currentfile
Error.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 = originalFunc
return 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)
}
return
case 'unsubscribe':
datastore.unsubscribe(args[0], subscriber)
return
default:
datastore[method](...args)
return
}
}
systems:
local:
setup:
rooms:
'1':
name: Kitchen
component_paths:
- /components/1
display_paths:
- /components/1/displays/1
'2':
name: Breakfast Room
component_paths:
- /components/2
display_paths:
- /components/2/displays/1
components:
'1':
name: Display
location_paths:
- /rooms/1
displays:
'1':
name: Kitchen Display
'2':
name: Display
location_paths:
- /rooms/2
displays:
'1':
name: Breakfast Room Display
---
schema:
systems:
setup.json:
type: object
$override:
html:
edit: /override/area.js
additionalProperties: false
required:
- name
- room_paths
properties:
name:
title: Name
type: string
default: New Area
room_paths:
title: Rooms
$override:
html:
edit: /override/area/rooms.js
type: array
additionalItems: false
items:
type: string
pattern: ^/rooms/\\d+$
format: relationship
inverse: area_paths
$schema: http://json-schema.org/draft-07/schema#
$id: https://v3.schema.controlenvy.com/areas.json
---
type: object
properties:
setup:
type: object
properties:
rooms:
type: object
pattern_properties:
'^\d+$':
type: object
properties:
component_paths:
type: array
items:
type: string
format: relationship
pattern: '^/components/\d+$'
inverse: location_paths
display_paths:
type: array
items:
type: string
format: relationship
pattern: '^/components/\d+/displays/\d+$'
inverse: room_paths
components:
type: object
pattern_properties:
'^\d+$':
type: object
properties:
location_paths:
type: array
items:
type: string
format: relationship
pattern: '^/rooms/\d+$'
inverse: component_paths
displays:
type: object
pattern_properties:
'^\d+$':
type: object
properties:
room_paths:
type: array
items:
type: string
format: relationship
pattern: '^/rooms/\d+$'
inverse: display_paths
---
setup:
rooms:
'1':
component_paths:
- /components/1
---
setup:
components:
'1':
name: Component 1
location_paths:
- /rooms/1
'2':
name: Component 2
'3':
name: Component 3
displays:
'1':
name: Display 1
---
setup:
areas:
'1':
$schema: /areas.json
component_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 load
calls:
- ['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 could
if 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 could
if 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 could
if 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 subscribers
calls:
- ['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.expect
import sinonChai from 'sinon-chai'
chai.use(sinonChai)
import { Datastore } from '../../src/v3-compatible/datastore.js'
let datastore
describe('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._root
const 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._root
const 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._root
const 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._root
const result = datastore.get('/a1')
expect(result).to.eql(true)
})
})
})
/**
* Test Dependencies
*/
import '../helper.js'
import chai from 'chai'
const expect = chai.expect
import 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]] : null
parseDatastoreMethod(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 datastore
describe('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.expect
import 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 datastore
function 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.expect
import 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 datastore
function 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 row
for (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 row
for (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.expect
import 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 datastore
async 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.expect
import 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.expect
import 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.expect
import 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.undefined
expect(pointer._topic).to.be.undefined
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.equal('/a')
expect(pointer._steps).to.be.undefined
expect(pointer._topic).to.be.undefined
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.equal('/a/b')
expect(pointer._steps).to.be.undefined
expect(pointer._topic).to.be.undefined
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
})
it('[]', () => {
const pointer = new Pointer([])
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.eql([])
expect(pointer._topic).to.be.undefined
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.undefined
expect(pointer._steps).to.eql(['a'])
expect(pointer._topic).to.be.undefined
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.undefined
expect(pointer._steps).to.eql(['a', 'b'])
expect(pointer._topic).to.be.undefined
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
})
it('"#"', () => {
const pointer = new Pointer('#')
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.be.undefined
expect(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.undefined
expect(pointer._steps).to.be.undefined
expect(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.undefined
expect(pointer._steps).to.be.undefined
expect(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.undefined
expect(pointer._topic).to.be.undefined
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.path = '/a/b'
expect(pointer._path).to.equal('/a/b')
expect(pointer._steps).to.be.undefined
expect(pointer._topic).to.be.undefined
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.steps =', () => {
it('["a"]', () => {
const pointer = new Pointer([])
pointer.steps = ['a']
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.eql(['a'])
expect(pointer._topic).to.be.undefined
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.steps = ['a', 'b']
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.eql(['a', 'b'])
expect(pointer._topic).to.be.undefined
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.topic =', () => {
it('"a"', () => {
const pointer = new Pointer('#')
pointer.topic = 'a'
expect(pointer._path).to.be.undefined
expect(pointer._steps).to.be.undefined
expect(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.undefined
expect(pointer._steps).to.be.undefined
expect(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.expect
import 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] = value
test['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] = expectation
expect(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.expect
import 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 = 4
const 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.prepareStackTrace
var callerfile
try {
var error = new Error()
var currentfile
Error.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 = originalFunc
return 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)
}
return
case '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: Kitchen
component_paths:
- /components/1
display_paths:
- /components/1/displays/1
'2':
name: Breakfast Room
component_paths:
- /components/2
display_paths:
- /components/2/displays/1
components:
'1':
name: Display
location_paths:
- /rooms/1
displays:
'1':
name: Kitchen Display
'2':
name: Display
location_paths:
- /rooms/2
displays:
'1':
name: Breakfast Room Display
---
schema:
systems:
setup.json:
type: object
$override:
html:
edit: /override/area.js
additionalProperties: false
required:
- name
- room_paths
properties:
name:
title: Name
type: string
default: New Area
room_paths:
title: Rooms
$override:
html:
edit: /override/area/rooms.js
type: array
additionalItems: false
items:
type: string
pattern: ^/rooms/\\d+$
format: relationship
inverse: area_paths
$schema: http://json-schema.org/draft-07/schema#
$id: https://v3.schema.controlenvy.com/areas.json
---
type: object
properties:
setup:
type: object
properties:
rooms:
type: object
pattern_properties:
'^\d+$':
type: object
properties:
component_paths:
type: array
items:
type: string
format: relationship
pattern: '^/components/\d+$'
inverse: location_paths
display_paths:
type: array
items:
type: string
format: relationship
pattern: '^/components/\d+/displays/\d+$'
inverse: room_paths
components:
type: object
pattern_properties:
'^\d+$':
type: object
properties:
location_paths:
type: array
items:
type: string
format: relationship
pattern: '^/rooms/\d+$'
inverse: component_paths
displays:
type: object
pattern_properties:
'^\d+$':
type: object
properties:
room_paths:
type: array
items:
type: string
format: relationship
pattern: '^/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 1
location_paths:
- /rooms/1
'2':
name: Component 2
'3':
name: Component 3
displays:
'1':
name: Display 1
---
systems:
2175edf8-5dac-4b9d-9ba5-8f830bef452a:
setup:
areas:
'1':
$schema: /areas.json
component_paths:
- /components/1
chain:
'#group':
- label: MUST create a single injectable
instructions:
- [call, prop, system_path, systems/1]
- [expect, props, system_path, systems/1]
- label: MUST link a single dependent
instructions:
- [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 link
instructions:
- [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 dependents
instructions:
- [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 links
instructions:
- [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 dependants
instructions:
- [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 updated
instructions:
- [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 relink
instructions:
- [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 pipe
instructions:
- [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 string
instructions:
- [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 number
instructions:
- [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 array
instructions:
- [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.expect
import 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._root
const 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._root
const 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._root
const 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._root
const result = datastore.get('/a1')
expect(result).to.eql(true)
})
})
})
/**
* Test Dependencies
*/
import '../helper.js'
import chai from 'chai'
const expect = chai.expect
import 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 transaction
before(() => {
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.expect
import { 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.undefined
const schema = schema_yaml.properties.setup.properties.rooms.pattern_properties['^\\d+$'].properties.component_paths
datastore.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.undefined
const 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.undefined
let 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.undefined
expect(component_display_room_paths).to.be.undefined
const 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_paths
datastore.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.undefined
expect(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.undefined
expect(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.expect
import 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 row
for (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 datastore
describe('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.expect
import 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']['#']._value
expect(map).to.be.empty
expect(() => {
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.expect
import 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 datastore
function 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.expect
import 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 datastore
function 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 row
for (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.expect
import 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 datastore
let chain
const 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]).args
expect(test).to.deep.include(...instruction[2].slice(1))
}
return
default:
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)
break
case 'call':
await parseCall(chain, spies, instruction)
break
case 'set':
await datastore.set(instruction[1], instruction[2])
break
case '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.expect
import sinonChai from 'sinon-chai'
chai.use(sinonChai)
import { Datastore } from '../../src/datastore/base.js'
let datastore
function root({ label, calls, expectation }) {
it(label, () => {
_.forEach(calls, row => {
parseDatastoreMethod(datastore, row[0], row.slice(1))
})
const [steps, value] = expectation
let result = datastore._root
for (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.value
return 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 result
case '#':
result[level] = topic.tree.toJSON()
return result
default:
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 text
return text.replace(/~/g, '~0').replace(/\//g, '~1')
}
const toPath = (text) => {
if (text.slice(0, 2) != '~1') return text
return text.replace(/~1/g, '/').replace(/~0/g, '~')
}
const datastore = new Datastore()
export default Datastore
export { 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 = key
this.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 existing
const 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 Tree
export { Tree }
import Parser from './parser.js'
const CACHE = new Map ()
const Topic = class extends(Parser) {
// FIXME: use class field `static cache = new Map()` when available
static 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 Topic
export { 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 = datastore
this.topic = new Topic(pattern)
this.placeholder = placeholder
this.mapper = mapper
this.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 reducer
const 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.placeholder
subscription(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.datastore
delete this.subscription
delete this.topic
}
}
export default DatastoreSvelte
export { DatastoreSvelte }
import Parser from './parser.js'
const CACHE = new Map()
const Pointer = class extends(Parser) {
// FIXME: use class field `static cache = new Map()` when available
static 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 Pointer
export { 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 = 0
Object.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 = cursor
cursor += 1
break
case 'session':
this._root_start = cursor
cursor += 1
this.parseSession(cursor)
return
}
switch (this.steps[cursor]) {
case 'action':
case 'state':
case 'setup':
case '+':
this._root_start = cursor
cursor += 1
break
}
switch (this.steps[cursor]) {
case 'users':
case 'events':
case 'items':
this._trunk_start = cursor
this._branch_start = cursor
this._trunk_end = cursor + 2
break
case 'admin':
this._trunk_start = cursor
this._branch_start = cursor
this._trunk_end = cursor + 1
break
case 'services':
this._trunk_start = cursor
this._branch_start = cursor
this._trunk_end = cursor + 1
cursor += 1
this.parseService(cursor)
return
}
this._branch_end = this.steps.length - 1
this._parsed = true
}
parseSession(cursor) {
switch (this.steps[cursor]) {
case 'path':
case 'redirect':
case 'user':
this.twig_start = cursor
cursor += 1
this.twig_end = this.steps.length
return
case 'services':
this._trunk_start = cursor
this._branch_start = cursor
this._trunk_end = cursor + 1
cursor += 1
this.parseService(cursor)
return
}
this._parsed = true
}
parseService(cursor) {
switch (this.steps[cursor]) {
case 'spotify':
case 'apple-music':
this._branch_end = cursor + 1
cursor += 1
break
}
this._branch_end = this.steps.length - 1
this._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 available
static get TREE_ADDED() {
return TREE_ADDED
}
// FIXME: use class field `static TREE_DELETED = '-'` when available
static get TREE_DELETED() {
return TREE_DELETED
}
// FIXME: use class field `static TREE_CHANGED = '='` when available
static get TREE_CHANGED() {
return TREE_CHANGED
}
constructor(type, pointer) {
this.type = type
this.pointer = pointer
}
get tree() {
return this.pointer?.tree
}
get value() {
return this.tree?.value
}
}
export default DatastoreEvent
export { 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 leaf
if (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_path
let id = 1
while (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 path
path = path[0] !== '/' ? `/${path}` : path
// Queue path
path = !path.startsWith('/q') ? `/q${path}` : path
this.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 reciprocal
if (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)) return
const inverse_name = schema.items?.inverse
if (inverse_name == null) return
const 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] = null
return
}
const pointer = new Pointer(path)
switch (schema.type) {
case 'array': {
const items = schema.items
if (schema.items == null) return
const format = items.format
if (format !== 'relationship') return
const obverse = this.read(path)
if (!Array.isArray(obverse)) return
const inverse_name = items.inverse
if (inverse_name == null) return
const 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)) continue
transaction[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 DatastoreConvenience
export { 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 tree
if (pointer instanceof Pointer) {
tree = pointer.tree
}
if (tree == null || !tree.parent.has(tree.key)) {
tree = this.data_tree
const 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 = tree
if (!silent) {
this.publish(DatastoreEvent.TREE_ADDED, tree.pointer)
}
}
}
pointer = tree.pointer
}
tree.value = value
if (!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 = null
if (!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 tree
if (topic instanceof Topic) {
tree = topic.tree
} else {
tree = this.topic_tree
const 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 = tree
tree.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.value
for (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 tree
for (const child of tree.values()) {
yield* this._nodes(child)
}
}
* pointers2() {
// FIXME: @chrismassey this name is horrible and not descriptive
yield* this._pointers2(this.data_tree)
}
* _pointers2(pointer) {
// FIXME: @chrismassey this name is horrible and not descriptive
yield pointer
for (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 Datastore
export { 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 : '/' + path
if (!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) : topic
let 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 : '/' + path
if (!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 DatastoreAPI
export { 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 Instructions
Datastore 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 tests
cd controller/drivers/lib/datastore
bundle exec rspec -f d spec/**/*.rb
# Javascript Datastore tests
cd shared/datastore/test
mocha 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:
```yaml
FILE_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 tests
expectation: [[], {}] # 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 tests
expectation: [[], {}] # can be plural or singular, and may vary between tests
```
node_modules/
.reify-cache/
config/env.js
*.log
*.sublime-workspace
packages:
- djiny
lockfileVersion: 5.3
importers:
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': 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
sanitize.css: ^12.0.1
sass: ^1.35.2
secure-remote-password: ^0.3.1
svelte: ^3.34.0
svelte-preprocess: ^4.7.3
uuid: ^8.0.0
dependencies:
'@fortawesome/free-brands-svg-icons': 5.15.4
'@fortawesome/free-solid-svg-icons': 5.15.4
sanitize.css: 12.0.1
sass: 1.37.5
devDependencies:
'@sveltejs/adapter-node': 1.0.0-next.0
'@sveltejs/kit': 1.0.0-next.146_svelte@3.42.1
eslint: 7.32.0
eslint-config-prettier: 8.3.0_eslint@7.32.0
eslint-plugin-svelte3: 3.2.0_eslint@7.32.0+svelte@3.42.1
lodash: 4.17.21
node-sass: 6.0.1
prettier: 2.2.1
prettier-plugin-svelte: 2.3.1_prettier@2.2.1+svelte@3.42.1
query-string: 4.3.2
secure-remote-password: 0.3.1
svelte: 3.42.1
svelte-preprocess: 4.7.4_6197623e5ed34153d1bcd9290e2954d7
uuid: 8.3.2
packages:
/@babel/code-frame/7.12.11:
resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==}
dependencies:
'@babel/highlight': 7.14.5
dev: true
/@babel/code-frame/7.14.5:
resolution: {integrity: sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/highlight': 7.14.5
dev: 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.9
chalk: 2.4.2
js-tokens: 4.0.0
dev: true
/@eslint/eslintrc/0.4.3:
resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==}
engines: {node: ^10.12.0 || >=12.0.0}
dependencies:
ajv: 6.12.6
debug: 4.3.2
espree: 7.3.1
globals: 13.10.0
ignore: 4.0.6
import-fresh: 3.3.0
js-yaml: 3.14.1
minimatch: 3.0.4
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
dev: 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: true
dev: 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: true
dependencies:
'@fortawesome/fontawesome-common-types': 0.2.36
dev: 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: true
dependencies:
'@fortawesome/fontawesome-common-types': 0.2.36
dev: false
/@humanwhocodes/config-array/0.5.0:
resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==}
engines: {node: '>=10.10.0'}
dependencies:
'@humanwhocodes/object-schema': 1.2.0
debug: 4.3.2
minimatch: 3.0.4
transitivePeerDependencies:
- supports-color
dev: 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.2
picomatch: 2.3.0
dev: true
/@sveltejs/adapter-node/1.0.0-next.0:
resolution: {integrity: sha512-NNdBGSARtrvLWLIUem/RKWYJ7cKq4xn0nxSASKogLSYL5SOrb6AgHunWQXv8VqDZ0jT7xQCORgASn006/HmaYQ==}
dependencies:
'@sveltejs/app-utils': 1.0.0-next.0
dev: true
/@sveltejs/app-utils/1.0.0-next.0:
resolution: {integrity: sha512-4fCuD+aLrq/iFVJWosIv8oi43tv4o7bGZVyNHMBamdIaVTcydRaPjxQCTM8OQPRVftHyrTIIvT3uH03kAxHZ0Q==}
dependencies:
mime: 2.5.2
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: true
peerDependencies:
svelte: ^3.34.0
dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.15_svelte@3.42.1+vite@2.4.4
cheap-watch: 1.0.3
sade: 1.7.4
svelte: 3.42.1
vite: 2.4.4
transitivePeerDependencies:
- diff-match-patch
- supports-color
dev: 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.5
svelte: ^3.34.0
vite: ^2.3.7
peerDependenciesMeta:
diff-match-patch:
optional: 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: 3.42.1
svelte-hmr: 0.14.7_svelte@3.42.1
vite: 2.4.4
transitivePeerDependencies:
- supports-color
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.13
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.0
dependencies:
acorn: 7.4.1
dev: true
/acorn/7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/ajv/6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
dependencies:
fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0
json-schema-traverse: 0.4.1
uri-js: 4.4.1
dev: true
/ajv/8.6.2:
resolution: {integrity: sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==}
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
uri-js: 4.4.1
dev: 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.3
dev: true
/ansi-styles/4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: true
/anymatch/3.1.2:
resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.0
dev: 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.0
readable-stream: 2.3.7
dev: true
/argparse/1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
dependencies:
sprintf-js: 1.0.3
dev: 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.2
dev: 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.5
dev: 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.2
concat-map: 0.0.1
dev: true
/braces/3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: 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.1
map-obj: 4.2.1
quick-lru: 4.0.1
dev: 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.1
escape-string-regexp: 1.0.5
has-ansi: 2.0.0
strip-ansi: 3.0.1
supports-color: 2.0.0
dev: true
/chalk/2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
dependencies:
ansi-styles: 3.2.1
escape-string-regexp: 1.0.5
supports-color: 5.5.0
dev: true
/chalk/4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
dev: 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.2
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.1
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
dev: 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.0
strip-ansi: 5.2.0
wrap-ansi: 5.1.0
dev: 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.3
dev: true
/color-convert/2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: 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.0
dev: 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.1
shebang-command: 2.0.0
which: 2.0.2
dev: 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.0
dev: true
/dashdash/1.14.1:
resolution: {integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=}
engines: {node: '>=0.10'}
dependencies:
assert-plus: 1.0.0
dev: true
/debug/4.3.2:
resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: true
/decamelize-keys/1.1.0:
resolution: {integrity: sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=}
engines: {node: '>=0.10.0'}
dependencies:
decamelize: 1.2.0
map-obj: 1.0.1
dev: 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.3
dev: true
/ecc-jsbn/0.1.2:
resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=}
dependencies:
jsbn: 0.1.1
safer-buffer: 2.1.2
dev: 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.1
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
dev: true
/esbuild/0.12.19:
resolution: {integrity: sha512-5NuT1G6THW7l3fsSCDkcPepn24R0XtyPjKoqKHD8LfhqMXzCdz0mrS9HgO6hIhzVT7zt0T+JGbzCqF5AH8hS9w==}
hasBin: true
requiresBuild: true
dev: 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: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
eslint: 7.32.0
dev: 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.0
dependencies:
eslint: 7.32.0
svelte: 3.42.1
dev: true
/eslint-scope/5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
dependencies:
esrecurse: 4.3.0
estraverse: 4.3.0
dev: true
/eslint-utils/2.1.0:
resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==}
engines: {node: '>=6'}
dependencies:
eslint-visitor-keys: 1.3.0
dev: 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: true
dependencies:
'@babel/code-frame': 7.12.11
'@eslint/eslintrc': 0.4.3
'@humanwhocodes/config-array': 0.5.0
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.2
doctrine: 3.0.0
enquirer: 2.3.6
escape-string-regexp: 4.0.0
eslint-scope: 5.1.1
eslint-utils: 2.1.0
eslint-visitor-keys: 2.1.0
espree: 7.3.1
esquery: 1.4.0
esutils: 2.0.3
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.10.0
ignore: 4.0.6
import-fresh: 3.3.0
imurmurhash: 0.1.4
is-glob: 4.0.1
js-yaml: 3.14.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.3
regexpp: 3.2.0
semver: 7.3.5
strip-ansi: 6.0.0
strip-json-comments: 3.1.1
table: 6.7.1
text-table: 0.2.0
v8-compile-cache: 2.3.0
transitivePeerDependencies:
- supports-color
dev: 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.1
acorn-jsx: 5.3.2_acorn@7.4.1
eslint-visitor-keys: 1.3.0
dev: true
/esprima/4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
hasBin: true
dev: true
/esquery/1.4.0:
resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==}
engines: {node: '>=0.10'}
dependencies:
estraverse: 5.2.0
dev: true
/esrecurse/4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
dependencies:
estraverse: 5.2.0
dev: 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.4
dev: true
/fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: false
/find-up/3.0.0:
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
engines: {node: '>=6'}
dependencies:
locate-path: 3.0.0
dev: true
/find-up/4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
dev: true
/flat-cache/3.0.4:
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
engines: {node: ^10.12.0 || >=12.0.0}
dependencies:
flatted: 3.2.2
rimraf: 3.0.2
dev: 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.0
combined-stream: 1.0.8
mime-types: 2.1.32
dev: true
/fs-minipass/2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.1.3
dev: 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.0
console-control-strings: 1.1.0
has-unicode: 2.0.1
object-assign: 4.1.1
signal-exit: 3.0.3
string-width: 1.0.2
strip-ansi: 3.0.1
wide-align: 1.1.3
dev: true
/gaze/1.1.3:
resolution: {integrity: sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==}
engines: {node: '>= 4.0.0'}
dependencies:
globule: 1.3.2
dev: 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.0
dev: 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.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/globals/13.10.0:
resolution: {integrity: sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==}
engines: {node: '>=8'}
dependencies:
type-fest: 0.20.2
dev: true
/globule/1.3.2:
resolution: {integrity: sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==}
engines: {node: '>= 0.10'}
dependencies:
glob: 7.1.7
lodash: 4.17.21
minimatch: 3.0.4
dev: 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 supported
dependencies:
ajv: 6.12.6
har-schema: 2.0.0
dev: 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.1
dev: 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.1
dev: 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.0
dev: true
/http-signature/1.2.0:
resolution: {integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=}
engines: {node: '>=0.8', npm: '>=1.3.7'}
dependencies:
assert-plus: 1.0.0
jsprim: 1.4.1
sshpk: 1.16.1
dev: 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.1
resolve-from: 4.0.0
dev: 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.0
wrappy: 1.0.2
dev: 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.0
dev: false
/is-core-module/2.5.0:
resolution: {integrity: sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==}
dependencies:
has: 1.0.3
dev: 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.1
dev: 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: true
dependencies:
argparse: 1.0.10
esprima: 4.0.1
dev: 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.0
extsprintf: 1.3.0
json-schema: 0.2.3
verror: 1.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
/levn/0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
dev: 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.0
path-exists: 3.0.0
dev: true
/locate-path/5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
dependencies:
p-locate: 4.1.0
dev: 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.0
dev: true
/magic-string/0.25.7:
resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}
dependencies:
sourcemap-codec: 1.4.8
dev: 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.2
camelcase-keys: 6.2.2
decamelize: 1.2.0
decamelize-keys: 1.1.0
hard-rejection: 2.1.0
minimist-options: 4.1.0
normalize-package-data: 3.0.2
read-pkg-up: 7.0.1
redent: 3.0.0
trim-newlines: 3.0.1
type-fest: 0.18.1
yargs-parser: 20.2.9
dev: 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.0
dev: true
/mime/2.5.2:
resolution: {integrity: sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==}
engines: {node: '>=4.0.0'}
hasBin: true
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.11
dev: true
/minimist-options/4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
engines: {node: '>= 6'}
dependencies:
arrify: 1.0.1
is-plain-obj: 1.1.0
kind-of: 6.0.3
dev: true
/minipass/3.1.3:
resolution: {integrity: sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==}
engines: {node: '>=8'}
dependencies:
yallist: 4.0.0
dev: true
/minizlib/2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
dependencies:
minipass: 3.1.3
yallist: 4.0.0
dev: true
/mkdirp/1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
dev: 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: true
dev: 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: true
dependencies:
env-paths: 2.2.1
glob: 7.1.7
graceful-fs: 4.2.8
nopt: 5.0.0
npmlog: 4.1.2
request: 2.88.2
rimraf: 3.0.2
semver: 7.3.5
tar: 6.1.7
which: 2.0.2
dev: true
/node-sass/6.0.1:
resolution: {integrity: sha512-f+Rbqt92Ful9gX0cGtdYwjTrWAaGURgaK5rZCWOgCNyGWusFYHhbqCCBoFBeat+HKETOU02AyTxNhJV0YZf2jQ==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
dependencies:
async-foreach: 0.1.3
chalk: 1.1.3
cross-spawn: 7.0.3
gaze: 1.1.3
get-stdin: 4.0.1
glob: 7.1.7
lodash: 4.17.21
meow: 9.0.0
nan: 2.15.0
node-gyp: 7.1.2
npmlog: 4.1.2
request: 2.88.2
sass-graph: 2.2.5
stdout-stream: 1.4.1
true-case-path: 1.0.3
dev: true
/nopt/5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'}
hasBin: true
dependencies:
abbrev: 1.1.1
dev: true
/normalize-package-data/2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
hosted-git-info: 2.8.9
resolve: 1.20.0
semver: 5.7.1
validate-npm-package-license: 3.0.4
dev: true
/normalize-package-data/3.0.2:
resolution: {integrity: sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==}
engines: {node: '>=10'}
dependencies:
hosted-git-info: 4.0.2
resolve: 1.20.0
semver: 7.3.5
validate-npm-package-license: 3.0.4
dev: 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.5
console-control-strings: 1.1.0
gauge: 2.7.4
set-blocking: 2.0.0
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
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: true
/optionator/0.9.1:
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
engines: {node: '>= 0.8.0'}
dependencies:
deep-is: 0.1.3
fast-levenshtein: 2.0.6
levn: 0.4.1
prelude-ls: 1.2.1
type-check: 0.4.0
word-wrap: 1.2.3
dev: true
/p-limit/2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
dependencies:
p-try: 2.2.0
dev: true
/p-locate/3.0.0:
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
engines: {node: '>=6'}
dependencies:
p-limit: 2.3.0
dev: true
/p-locate/4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
dependencies:
p-limit: 2.3.0
dev: 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.0
dev: true
/parse-json/5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
dependencies:
'@babel/code-frame': 7.14.5
error-ex: 1.3.2
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.1.6
dev: 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.2
nanoid: 3.1.23
source-map-js: 0.6.2
dev: 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.0
svelte: ^3.2.0
dependencies:
prettier: 2.2.1
svelte: 3.42.1
dev: true
/prettier/2.2.1:
resolution: {integrity: sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: 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.1
strict-uri-encode: 1.1.0
dev: 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.0
read-pkg: 5.2.0
type-fest: 0.8.1
dev: true
/read-pkg/5.2.0:
resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
engines: {node: '>=8'}
dependencies:
'@types/normalize-package-data': 2.4.1
normalize-package-data: 2.5.0
parse-json: 5.2.0
type-fest: 0.6.0
dev: true
/readable-stream/2.3.7:
resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}
dependencies:
core-util-is: 1.0.2
inherits: 2.0.4
isarray: 1.0.0
process-nextick-args: 2.0.1
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
dev: true
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.0
dev: false
/redent/3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
dependencies:
indent-string: 4.0.0
strip-indent: 3.0.0
dev: 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/3142
dependencies:
aws-sign2: 0.7.0
aws4: 1.11.0
caseless: 0.12.0
combined-stream: 1.0.8
extend: 3.0.2
forever-agent: 0.6.1
form-data: 2.3.3
har-validator: 5.1.5
http-signature: 1.2.0
is-typedarray: 1.0.0
isstream: 0.1.2
json-stringify-safe: 5.0.1
mime-types: 2.1.32
oauth-sign: 0.9.0
performance-now: 2.1.0
qs: 6.5.2
safe-buffer: 5.2.1
tough-cookie: 2.5.0
tunnel-agent: 0.6.0
uuid: 3.4.0
dev: 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.0
path-parse: 1.0.7
dev: true
/rimraf/3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
dependencies:
glob: 7.1.7
dev: true
/rollup/2.56.1:
resolution: {integrity: sha512-KkrsNjeiTfGJMUFBi/PNfj3fnt70akqdoNXOjlzwo98uA1qrlkmgt6SGaK5OwhyDYCVnJb6jb2Xa2wbI47P4Nw==}
engines: {node: '>=10.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: true
/sade/1.7.4:
resolution: {integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==}
engines: {node: '>= 6'}
dependencies:
mri: 1.1.6
dev: 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: true
dependencies:
glob: 7.1.7
lodash: 4.17.21
scss-tokenizer: 0.2.3
yargs: 13.3.2
dev: true
/sass/1.37.5:
resolution: {integrity: sha512-Cx3ewxz9QB/ErnVIiWg2cH0kiYZ0FPvheDTVC6BsiEGBTZKKZJ1Gq5Kq6jy3PKtL6+EJ8NIoaBW/RSd2R6cZOA==}
engines: {node: '>=8.9.0'}
hasBin: true
dependencies:
chokidar: 3.5.2
dev: false
/scss-tokenizer/0.2.3:
resolution: {integrity: sha1-jrBtualyMzOCTT9VMGQRSYR85dE=}
dependencies:
js-base64: 2.6.4
source-map: 0.4.4
dev: true
/secure-remote-password/0.3.1:
resolution: {integrity: sha512-iEp/qLRfb9XYhfKFrPFfdeD7KVreCjhDKSTRP1G1nRIO0Sw1hjnVHD58ymOhiy9Zf5quHbDIbG9cTupji7qwnA==}
dependencies:
array-buffer-to-hex: 1.0.0
crypto-digest-sync: 1.0.0
crypto-random-hex: 1.0.0
encode-utf8: 1.0.3
hex-to-array-buffer: 1.1.0
jsbn: 1.1.0
pad-start: 1.0.2
dev: true
/semver/5.7.1:
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
hasBin: true
dev: true
/semver/7.3.5:
resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}
engines: {node: '>=10'}
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: 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.0
dev: 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.0
astral-regex: 2.0.0
is-fullwidth-code-point: 3.0.0
dev: 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.1
dev: 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.1
spdx-license-ids: 3.0.10
dev: 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.0
spdx-license-ids: 3.0.10
dev: 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: true
dependencies:
asn1: 0.2.4
assert-plus: 1.0.0
bcrypt-pbkdf: 1.0.2
dashdash: 1.14.1
ecc-jsbn: 0.1.2
getpass: 0.1.7
jsbn: 0.1.1
safer-buffer: 2.1.2
tweetnacl: 0.14.5
dev: true
/stdout-stream/1.4.1:
resolution: {integrity: sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==}
dependencies:
readable-stream: 2.3.7
dev: 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.0
is-fullwidth-code-point: 1.0.0
strip-ansi: 3.0.1
dev: true
/string-width/3.1.0:
resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}
engines: {node: '>=6'}
dependencies:
emoji-regex: 7.0.3
is-fullwidth-code-point: 2.0.0
strip-ansi: 5.2.0
dev: true
/string-width/4.2.2:
resolution: {integrity: sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.0
dev: true
/string_decoder/1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
dependencies:
safe-buffer: 5.1.2
dev: true
/strip-ansi/3.0.1:
resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=}
engines: {node: '>=0.10.0'}
dependencies:
ansi-regex: 2.1.1
dev: true
/strip-ansi/5.2.0:
resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==}
engines: {node: '>=6'}
dependencies:
ansi-regex: 4.1.0
dev: true
/strip-ansi/6.0.0:
resolution: {integrity: sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==}
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.0
dev: true
/strip-indent/3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
dependencies:
min-indent: 1.0.1
dev: 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.0
dev: true
/supports-color/7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
dev: true
/svelte-hmr/0.14.7_svelte@3.42.1:
resolution: {integrity: sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==}
peerDependencies:
svelte: '>=3.19.0'
dependencies:
svelte: 3.42.1
dev: true
/svelte-preprocess/4.7.4_6197623e5ed34153d1bcd9290e2954d7:
resolution: {integrity: sha512-mDAmaltQl6e5zU2VEtoWEf7eLTfuOTGr9zt+BpA3AGHo8MIhKiNSPE9OLTCTOMgj0vj/uL9QBbaNmpG4G1CgIA==}
engines: {node: '>= 9.11.2'}
requiresBuild: true
peerDependencies:
'@babel/core': ^7.10.2
coffeescript: ^2.5.1
less: ^3.11.3
node-sass: '*'
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
dependencies:
'@types/pug': 2.0.5
'@types/sass': 1.16.1
detect-indent: 6.1.0
node-sass: 6.0.1
sass: 1.37.5
strip-indent: 3.0.0
svelte: 3.42.1
dev: 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.2
lodash.clonedeep: 4.5.0
lodash.truncate: 4.4.2
slice-ansi: 4.0.0
string-width: 4.2.2
strip-ansi: 6.0.0
dev: true
/tar/6.1.7:
resolution: {integrity: sha512-PBoRkOJU0X3lejJ8GaRCsobjXTgFofRDSPdSUhRSdlwJfifRlQBwGXitDItdGFu0/h0XDMCkig0RN1iT7DBxhA==}
engines: {node: '>= 10'}
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 3.1.3
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
dev: 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.0
dev: false
/tough-cookie/2.5.0:
resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
engines: {node: '>=0.8'}
dependencies:
psl: 1.8.0
punycode: 2.1.1
dev: 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.7
dev: true
/tunnel-agent/0.6.0:
resolution: {integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=}
dependencies:
safe-buffer: 5.2.1
dev: 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.1
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.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.1
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: true
dev: true
/uuid/8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
dev: 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.1
spdx-expression-parse: 3.0.1
dev: true
/verror/1.10.0:
resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=}
engines: {'0': node >=0.6.0}
dependencies:
assert-plus: 1.0.0
core-util-is: 1.0.2
extsprintf: 1.3.0
dev: true
/vite/2.4.4:
resolution: {integrity: sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw==}
engines: {node: '>=12.0.0'}
hasBin: true
dependencies:
esbuild: 0.12.19
postcss: 8.3.6
resolve: 1.20.0
rollup: 2.56.1
optionalDependencies:
fsevents: 2.3.2
dev: 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: true
dependencies:
isexe: 2.0.0
dev: true
/wide-align/1.1.3:
resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==}
dependencies:
string-width: 1.0.2
dev: 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.1
string-width: 3.1.0
strip-ansi: 5.2.0
dev: 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.1
decamelize: 1.2.0
dev: 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.0
find-up: 3.0.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 3.1.0
which-module: 2.0.0
y18n: 4.0.3
yargs-parser: 13.1.2
dev: 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, expiry
onMount(() => {
const hash = querystring.parse(window?.location?.hash ?? '#')
if (hash.token_type === 'Bearer') {
access_token = hash.access_token
expiry = 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, expiry
onMount(() => {
const hash = querystring.parse(window?.location?.hash ?? '#')
if (hash.token_type === 'Bearer') {
access_token = hash.access_token
expiry = 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 = false
import { onMount, createEventDispatcher } from 'svelte'
import lodash from 'lodash'
const dispatch = createEventDispatcher()
onMount(() => {
fixme = true
})
const oninput = event => {
let value = event.target.value
if (!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.value
if (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':
break
default:
return
}
let value = event.target.value
let 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_id
let 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_id
items = {}
}
$: 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.steps
const { value } = pointer
return 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 : 0
import 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_id
export let item_id
export let max_votes
export let votes
import 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_path
import { 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 observer
onMount(() => {
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 expired
function reauthorize(expiry) {
if (typeof number !== 'number') {
expired = null
return
}
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_playlist
async function firstResults() {
first_results = null
const 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_results
if (result.error) {
tracks = null
artists = null
albums = null
playlists = 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, playlists
async function search(type) {
search_results = null
view = type
const path = 'https://api.spotify.com/v1/search?'
const query = `q=${$text}&type=${type}`
search_results = navigate(`${path}${query}`)
const result = await search_results
if (result.error) {
tracks = null
artists = null
albums = null
playlists = null
} else {
tracks = result.tracks
artists = result.artists
albums = result.albums
playlists = 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 } = detail
if (index + 1 === searches.length) {
searches = searches.concat(navigate(href))
} else {
searches = searches.slice(0, index + 1).concat(navigate(href))
}
}
import { onMount } from 'svelte'
let active
function onIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting && !entry.target.classList.contains('side-scroll-panel-transition')) {
let elements = entry.target.parentElement.parentElement.children
for (const child_idx in elements) {
const grandchild = elements[child_idx].children[0]
if (grandchild === entry.target) {
active = +child_idx
return
}
}
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 observer
onMount(() => {
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 view
export let result
import 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.items
const types = {
track: Track,
album: Album,
artist: Artist,
playlist: Playlist
}
$: svelte_component = types[view]
let next
const 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 item
import 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 item
import { 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 item
import { 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 item
import { 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 path
export let focused = true
import { 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 inspect
import navigate from '../navigate'
import Router from './inspect_router.svelte'
import { onDestroy } from 'svelte'
import { datastore } from '@controlenvy/datastore'
let request
$: {
request = null
request = 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 track
import 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 response
import TrackItem from './track.svelte'
import navigate from '../../navigate.js'
$: items = response.items
let next
function onClick() {
next = null
next = 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_url
export let album_title
export 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 || {}).items
import 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_store
import Grid from './grid.svelte'
import List from './list.svelte'
import Item from './item.svelte'
import Icon from '$lib/components/Icon.svelte'
let focused
const focus = ({ detail }) => focused = detail.path
let 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_track
import { 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_playlist
import { 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_artist
import { 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_album
import { 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 tracks
import Track from './track.svelte'
import { createEventDispatcher } from 'svelte'
import navigate from '../navigate.js'
let next
function onClick() {
next = navigate(tracks.next)
}
$: items = tracks.items
const 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 result
export let index = 0
import 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.tracks
import { onMount, onDestroy, createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
onMount(() => dispatch('add_or_delete'))
onDestroy(() => dispatch('add_or_delete'))
function onInspect({ detail }) {
const href = detail.href
dispatch("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 playlists
import Playlist from './playlist.svelte'
import { createEventDispatcher } from 'svelte'
import navigate from '../navigate.js'
let next
function onClick() {
next = navigate(playlists.next)
}
$: items = playlists.items
const 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 playlist
import { 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.href
const 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 category
import { createEventDispatcher } from 'svelte'
$: icons = category.icons
$: icon = icons[0]
$: image_url = icon.url
$: href = category.href
$: title = category.name
const 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 categories
import Category from './category.svelte'
import { createEventDispatcher } from 'svelte'
import navigate from '../navigate.js'
let next
function onClick() {
next = navigate(categories.next)
}
console.log(categories)
$: items = categories.items
const 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 Set
let strength
const auth = (topic, event) => {
if (event.type !== '=') return
const { pointer } = event
const value = pointer?.tree?.value
console.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 !== '=') return
const { pointer } = event
const value = pointer?.tree?.value
if (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_confirm
function checkPassword(password, confirm) {
password_valid = password == confirm
const 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 = null
messages.add('Password empty')
messages = messages
return
} else if (invalid_characters) {
strength = null
messages.add('Invalid characters')
messages = messages
return
}
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 } = event
if (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_id
import { 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_path
import { 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 } = target
if ($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_path
export 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 = false
import { 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 = datastore
deploy()
}
/**
* @type {import('@sveltejs/kit').Load}
*/
export async function load({ page, fetch, session, context }) {
datastore.loaded = true
return {}
}
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.loaded
const 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 Constants
const WEBSOCKET_READY_STATE = ['Connecting', 'Connected', 'Closing', 'Closed']
const WEBSOCKET_READY_STATE_CONNECTED = 1
import lodash from 'lodash'
import { datastore } from '@controlenvy/datastore'
export const QUALITY_DISCONNECTED = 0
export const QUALITY_POOR = 1
export const QUALITY_FAIR = 2
export const QUALITY_GOOD = 3
export const QUALITY_EXCELLENT = 4
// TODO: Implement this fully
export const quality = {
DISCONNECTED: QUALITY_DISCONNECTED,
POOR: QUALITY_POOR,
FAIR: QUALITY_FAIR,
GOOD: QUALITY_GOOD,
EXCELLENT: QUALITY_EXCELLENT
}
export const connections = {
server: null
}
let clientside = false
let global_window
// FIXME: do we need this?
if (typeof window !== 'undefined') {
clientside = true
global_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 = connections
global_window.ping_adjust = 0
if (typeof WebSocket === 'undefined') {
class WSShim {
constructor() {}
open() {}
}
global_window.WebSocket = WSShim
}
// Connection
class 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 = null
this.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':
break
default:
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]
}
}
// Connection
class WS extends WSBase {
constructor(options = {}) {
super()
options = Object.assign({ connection: 'server', overwrite: false }, options)
this.max_attempts = options.attempts || 0
this.attempts = 0
this.host = (() => {
let host = ''
if (options.host != null) {
host = options.protocol || 'ws://'
host += options.host
if (options.port) {
host += ':' + options.port
}
}
return host
})()
datastore.set('/session/local/authenticated', false)
this.on('open', () => {
this.attempts = 0
console.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 = true
connections[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-console
console.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 += 1
console.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 reconnect
this.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_window
if (typeof window !== 'undefined') {
global_window = window
} else {
global_window = global
}
export const unsubscribe = (topic) => {
// console.log('subscribe()', 'topic:', topic)
let operation = 'u'
let payload
payload = {
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_window
if (typeof window !== 'undefined') {
global_window = window
} else {
global_window = global
}
export const subscribe = (topic, initialize) => {
// console.log('subscribe()', 'topic:', topic)
let operation = 's'
let payload
payload = {
o: operation,
t: topic
}
if (initialize) { payload.i = true}
const ws = global_window.connections['node']
if (ws) {
ws.send(payload)
}
}
let global_window
if (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 payload
if (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) return
datastore.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.leaf
switch (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 !== '=') return
const { pointer, value } = event
console.log(topic, pointer.path, pointer.leaf, value)
if (value == null) return
switch (pointer.leaf) {
case 'auth_user':
this.authorizeUser()
break
case 'auth_event':
this.authorizeEvent()
break
}
}
onVote(topic, event) {
if (event.type !== '=') return
const { pointer, value } = event
if (value == null) return
const 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.tracks
const tracks = track_items.items
const track = tracks[0]
const spotify_id = track.uri
const name = track.name
datastore.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 !== '=') return
const { value } = event
if (value == null) return
console.log(`User Accepted ${value}`)
this.user = value
if (this.user && this.token) this.saveToken(this.user, this.token)
}
onExplore(topic, event) {
if (event.type !== '=') return
const { pointer, value } = event
if (value == null) return
const 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
}
break
case 5:
console.log(type)
switch (type) {
case 'categories':
this.exploreCategories(pointer)
break
case 'playlists':
this.exploreCategory(pointer)
break
}
break
default:
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_path
this.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.href
record['image_url'] = image_details.url
record['max_dimension'] = Math.min(image_details.height, image_details.width)
record['id'] = item.id
record['title'] = item.name
const 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) return
this.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) return
this.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.track
const record = {}
if (!track) return
let artists
let album
let image_details
switch (track.type) {
case 'track':
artists = []
track.artists.forEach(artist => artists.push(artist.name))
album = track.album
image_details = this.findSuitableImage(album.images)
record['id'] = track.id
record['uri'] = track.uri
record['image_url'] = image_details.url
record['max_dimension'] = Math.min(image_details.height, image_details.width)
record['title'] = track.name
record['subtitle'] = album.name
record['artists'] = artists
record['restricted'] = track.restrictions ? true : false
record['explicit'] = track.explicit
break
default:
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 + 1
this.directory_path = `/directories/${this.directory_idx}`
}
lastDirectory() {
if (this.directory_idx > 0) {
this.directory_idx = this.directory_idx - 1
if (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 !== '=') return
const { value } = event
if (value == null) return
console.log(`Token Accepted ${value}`)
this.token = value
if (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) return
console.log(`Code Received ${value}`)
this.code = value
this.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.user
this.token = data.access_token
datastore.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 path
export let placeholder = ''
export let inputmode = 'text'
export let pattern = null
export let focused = false
export let validate = () => true
import { datastore } from "@controlenvy/datastore";
$: text = datastore.svelte(path.slice(1), placeholder)
let value = ''
$: if (!focused) { value = $text }
let invalid = false
const onInput = ({ target }) => {
const { value } = target
if (validate(value)) {
$text = value
invalid = 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 = false
let wrapper, themed_select, themed_dropdown, select_element, selected
afterUpdate(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.innerHTML
for (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 = !expanded
wrapper = wrapper
}
let positional_class_v = 'top'
let positional_class_h = ''
let max_height
const 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_width
function evaluateMain() {
main = document.querySelector('main')
const bounds = main.getBoundingClientRect()
root_width = bounds.right
root_height = bounds.bottom
max_height = `${root_height * 0.5}px`
}
// FIXME: Svelte 3.8.1 `bind:`` does not work without at least one Svelte update
let fixme = false
let observer
onMount(() => {
fixme = true
evaluateMain()
const options = {
root: main
}
observer = new IntersectionObserver(handleIntersect, options)
})
function handleIntersect(entries) {
entries.forEach(entry => {
const { top, bottom } = entry.boundingClientRect
if (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 = !interrupt
expanded = !expanded
await tick()
evaluateLocation()
}
const closeSelect = () => {
if (interrupt) {
interrupt = false
return
}
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 topic
export let filter
export 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 path
export let attribute
export let queue = true
export let root = "setup"
export let label
import { 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 changes
let checked
const 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 segment
import { 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 path
export 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_function
export let w
export let h
import { onMount, onDestroy } from 'svelte'
import { random, PI, sin } from 'math'
const offset = random() * 180
let wave
let t = 0
let frame
function 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.1
frame = 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 = 500
let w = 500
function buildEnvelope(w) {
const bound_1 = 5 / 12 // 30 / 72
const bound_2 = 7 / 12 // 38 / 72
return Array.from({ length: w + 1 }, (_, p) => {
const x = p / w
if (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 null
const y_wave = (h * amplitude) * sin(((k * p) + d * t) / period)
const y = envelope[p] * y_temp * y_wave
return `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 = true
export let scale = 1
// font awesome properties are take as additional props via meta
let width
let height
let path
let label
let box = `0 0 0 0`
let style
const 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 = true
return [solid_icons[svgName], className]
} else if (brand_icons[svgName]) {
found = true
return [brand_icons[svgName], className]
} else {
found = false
return [faCircle, className]
}
} else if (solid_icons[svgName]) {
found = true
return [
solid_icons[svgName],
solid_icons[svgName].prefix + ' ' + solid_icons[svgName].iconName
]
} else if (brand_icons[svgName]) {
found = true
return [
brand_icons[svgName],
brand_icons[svgName].prefix + ' ' + brand_icons[svgName].iconName
]
} else {
found = false
return [ 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.icon
width = _width
height = _height
path = _svgPathData
label = data.iconName
box = `0 0 ${width} ${height}`
style = `font-size: ${scale}em`
}
</script>
<svg
version="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_window
if (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 = uuid
auth.password = password
auth.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.s
const server_ephemeral = packet.k
if (!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.p
try {
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 = null
window.notify = this.notify.bind(this)
// Open Websocket
const 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.p
const value = message.v
if (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.p
const q_steps = q_path.slice(1).split('/')
datastore.delete(q_steps, { silent: true })
}
} break
case '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)
} break
case 2: {
authorization.deriveSharedKey(message.t, message.v, ws)
} break
case 4: {
if (authorization.verifySessionKey(message.t, message.v, ws) &&
/\/users/.test(message.t)) {
authorization.requestPersistantSession(message.v.u, ws)
}
} break
case 5: {
if (!message.v) { break }
authorization.pickUpSession(message.t, message.v, ws)
} break
case 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 } = event
if (lodash.isPlainObject(value) || event.type !== '=') { return }
send(value, pointer.path)
})
datastore.subscribe('session/redirect', (pointer, event) => {
const { value } = event
if (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._ = lodash
import { 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 Datastore
window.Pointer = Pointer
window.send = send
window.authorization = authorization
window.subscribe = subscribe
window.unsubscribe = unsubscribe
import { ThemeHandler } from './lib/theme'
window.theme = new ThemeHandler()
// config
datastore.set('session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')
datastore.set('session/ping_interval', 10000)
datastore._chain = datastore.chain
datastore.chain = svelte => {
const chain = datastore._chain()
if (svelte === void 0) {
return chain
}
svelte.$$.on_destroy.push(() => {
chain.destroy()
})
return chain
}
// Drivers
import { Spotify } from './lib/spotify'
const spotify = new Spotify(datastore)
// Models
import { User } from './lib/user'
const user = new User(datastore)
import { Bootstrap } from './bootstrap.js'
let bootstrap
import * 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-console
return
}
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 = null
window.notify = this.notify.bind(this)
// Open Websocket
const 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.p
const value = message.v
if (_.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.p
const q_steps = q_path.slice(1).split('/')
datastore.delete(q_steps, { silent: true })
}
break
case '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)
break
case 2:
authorization.deriveSharedKey(message.t, message.v, ws)
break
case 4:
if (authorization.verifySessionKey(message.t, message.v, ws) &&
/\/users/.test(message.t)) {
authorization.requestPersistantSession(message.v.u, ws)
}
break
case 5:
if (!message.v) { break }
authorization.pickUpSession(message.t, message.v, ws)
break
case 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 parameters
datastore.subscribe('session/path/*', this, (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
}
})
}
}
const api = new API
export 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-template
The 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 Rollup
npx degit "sveltejs/sapper-template#rollup" my-app
# for webpack
npx degit "sveltejs/sapper-template#webpack" my-app
```
### Using GitHub templates
Alternatively, 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 project
However you get the code, you can install dependencies and run the project in development mode with:
```bash
cd my-app
npm install # or yarn
npm 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.
## Structure
Sapper expects to find two directories in the root of your project — `src` and `static`.
### src
The [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/routes
This 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
### static
The [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...
```js
import { 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 config
Sapper 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 deployment
To 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:
```bash
npm install -g now
now
```
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 components
When 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:
```bash
npm install -D @sveltejs/svelte-virtual-list
```
## Bugs and feedback
Sapper 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.sh
npm run start
.git
*.log
node_modules
app/djiny/src
app/djiny/cypress
nginx.conf
./server
__sapper__/dev
#!/usr/bin/env bash
. $BIN_DIR/_lib.sh
npm run build
#!/usr/bin/env bash
sudo apt update
sudo apt install nodejs
sudo apt install npm
bash install_nvm.sh
source ~/.profile
echo "[remote] $(node -v)"
curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh -o install_nvm.sh
nvm install --lts
npm install -g pnpm
npm install -g pm2
sudo apt-get install nginx
sudo chown -R $USER /var/log/nginx/error.log
sudo chown -R $USER /etc/nginx/
sudo mkdir /var/cache/nginx
pm2 start node --name "Djiny" -- __sapper__/build
pm2 stop Djiny
sudo add-apt-repository ppa:certbot/certbot
#!/bin/zsh
autoenv_source_parent
autostash 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 CSS
for the current page. CSS for the rest of the app is
lazily loaded when it precaches secondary pages -->
%sapper.styles%
<!-- This contains the contents of the <svelte:head> component, if
the 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 and
initialise 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` directory
const 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 caches
for (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: URIs
if (!url.protocol.startsWith('http')) return;
// ignore dev server requests
if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
// always serve static files and bundler-generated assets from cache
if (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 token
chain.link('token', 'state/admin/token', string => {
token = _.isString(string) ? string : null
})
chain.link('redirect', 'session/redirect', string => {
if (_.isString(string)) {
goto(string)
}
})
let accepted
chain.link('accept', 'action/admin/accept', value => {
if (!value) { return }
accepted = value
})
let rejected
chain.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_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)
}
}
})
let access_token, expiry
onMount(() => {
const hash = querystring.parse(window?.location?.hash ?? '#')
if (hash.token_type === 'Bearer') {
access_token = hash.access_token
expiry = 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 complete
const accept = () => {
if (!spotify_id) return
datastore.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 = false
import { onMount, createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
onMount(() => {
fixme = true
})
const oninput = event => {
let value = event.target.value
if (!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.value
if (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':
break
default:
return
}
let value = event.target.value
let 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 Constants
const WEBSOCKET_READY_STATE = ['Connecting', 'Connected', 'Closing', 'Closed']
const WEBSOCKET_READY_STATE_CONNECTED = 1
export const QUALITY_DISCONNECTED = 0
export const QUALITY_POOR = 1
export const QUALITY_FAIR = 2
export const QUALITY_GOOD = 3
export const QUALITY_EXCELLENT = 4
// TODO: Implement this fully
export const quality = {
DISCONNECTED: QUALITY_DISCONNECTED,
POOR: QUALITY_POOR,
FAIR: QUALITY_FAIR,
GOOD: QUALITY_GOOD,
EXCELLENT: QUALITY_EXCELLENT
}
export const connections = {
server: null
}
let clientside = false
let global_window
// FIXME: do we need this?
if (typeof window !== 'undefined') {
clientside = true
global_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 = connections
global_window.ping_adjust = 0
if (typeof WebSocket === 'undefined') {
class WSShim {
constructor() {}
open() {}
}
global_window.WebSocket = WSShim
}
// Connection
class 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 = null
this.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':
break
default:
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]
}
}
// Connection
class WS extends WSBase {
constructor(options = {}) {
super()
options = Object.assign({ connection: 'server', overwrite: false }, options)
this.max_attempts = options.attempts || 0
this.attempts = 0
this.host = (() => {
let host = ''
if (options.host != null) {
host = options.protocol || 'ws://'
host += options.host
if (options.port) {
host += ':' + options.port
}
}
return host
})()
datastore.set('/session/authenticated', false)
this.on('open', () => {
this.attempts = 0
console.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 = true
connections[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-console
console.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 += 1
console.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 reconnect
this.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_window
if (typeof window !== 'undefined') {
global_window = window
} else {
global_window = global
}
export const unsubscribe = (topic) => {
console.log('subscribe()', 'topic:', topic)
let operation = 'u'
let payload
payload = {
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_window
if (typeof window !== 'undefined') {
global_window = window
} else {
global_window = global
}
export const subscribe = (topic, initialize) => {
console.log('subscribe()', 'topic:', topic)
let operation = 's'
let payload
payload = {
o: operation,
p: topic
}
if (initialize) { payload.i = true}
const ws = global_window.connections['node']
if (ws) {
ws.send(payload)
}
}
let global_window
if (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 payload
if (value == null) {
operation = 'd'
}
payload = {
o: operation,
p: path,
v: value
}
const ws = global_window.connections['node']
if (ws) {
ws.send(payload)
}
}
let global_window
if (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 = uuid
auth.password = password
auth.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.s
const server_ephemeral = packet.k
if (!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.p
try {
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 = true
export let scale = 1
// font awesome properties are take as additional props via meta
let width
let height
let path
let label
let box = `0 0 0 0`
let style
const 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 = true
return [solid_icons[svgName], className]
} else if (brand_icons[svgName]) {
found = true
return [brand_icons[svgName], className]
} else {
found = false
return [faCircle, className]
}
} else if (solid_icons[svgName]) {
found = true
return [
solid_icons[svgName],
solid_icons[svgName].prefix + ' ' + solid_icons[svgName].iconName
]
} else if (brand_icons[svgName]) {
found = true
return [
brand_icons[svgName],
brand_icons[svgName].prefix + ' ' + brand_icons[svgName].iconName
]
} else {
found = false
return [ 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.icon
width = _width
height = _height
path = _svgPathData
label = data.iconName
box = `0 0 ${width} ${height}`
style = `font-size: ${scale}em`
}
</script>
<svg
version="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._ = lodash
import { 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 Datastore
window.Pointer = Pointer
window.send = send
window.authorization = authorization
window.subscribe = subscribe
window.unsubscribe = unsubscribe
window.Loadable = Loadable
import { ThemeHandler } from './lib/theme'
window.theme = new ThemeHandler()
// config
datastore.set('session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')
datastore.set('session/ping_interval', 10000)
datastore._chain = datastore.chain
datastore.chain = svelte => {
const chain = datastore._chain()
if (svelte === void 0) {
return chain
}
svelte.$$.on_destroy.push(() => {
chain.destroy()
})
return chain
}
// Drivers
import { Spotify } from './lib/spotify'
const spotify = new Spotify(datastore)
import { Bootstrap } from './bootstrap.js'
let bootstrap
import * 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-console
return
}
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 = null
window.notify = this.notify.bind(this)
// Open Websocket
const 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.p
const value = message.v
if (_.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.p
const q_steps = q_path.slice(1).split('/')
datastore.delete(q_steps, { silent: true })
}
break
case '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)
break
case 2:
authorization.deriveSharedKey(message.t, message.v, ws)
break
case 4:
if (authorization.verifySessionKey(message.t, message.v, ws) &&
/\/users/.test(message.t)) {
authorization.requestPersistantSession(message.v.u, ws)
}
break
case 5:
if (!message.v) { break }
authorization.pickUpSession(message.t, message.v, ws)
break
case 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 parameters
datastore.subscribe('session/path/*', this, (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
}
})
/* Query Parameters */
if (!('standalone' in navigator && navigator.standalone === true)) {
window.addEventListener('popstate', this.parse)
}
setTimeout(() => this.initialize(), 0)
}
parse() {
const origin = location.origin
const route = location.pathname
const 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 ] = match
evaluateRoute({ 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 methods
initialize() {
this.parse()
}
notify(options) {
let id = 1
while (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-template
The 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 Rollup
npx degit "sveltejs/sapper-template#rollup" my-app
# for webpack
npx degit "sveltejs/sapper-template#webpack" my-app
```
### Using GitHub templates
Alternatively, 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 project
However you get the code, you can install dependencies and run the project in development mode with:
```bash
cd my-app
npm install # or yarn
npm 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.
## Structure
Sapper expects to find two directories in the root of your project — `src` and `static`.
### src
The [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/routes
This 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
### static
The [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...
```js
import { 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 config
Sapper 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 deployment
To 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:
```bash
npm install -g now
now
```
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 components
When 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:
```bash
npm install -D @sveltejs/svelte-virtual-list
```
## Bugs and feedback
Sapper 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.sh
npm run start
.git
*.log
node_modules
app/djiny/src
app/djiny/cypress
nginx.conf
./server
__sapper__/dev
#!/usr/bin/env bash
. $BIN_DIR/_lib.sh
npm run build
#!/usr/bin/env bash
sudo apt update
sudo apt install nodejs
sudo apt install npm
bash install_nvm.sh
source ~/.profile
echo "[remote] $(node -v)"
curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh -o install_nvm.sh
nvm install --lts
npm install -g pnpm
npm install -g pm2
sudo apt-get install nginx
sudo chown -R $USER /var/log/nginx/error.log
sudo chown -R $USER /etc/nginx/
sudo mkdir /var/cache/nginx
pm2 start node --name "Djiny" -- __sapper__/build
pm2 stop Djiny
sudo add-apt-repository ppa:certbot/certbot
#!/usr/bin/env bash
. $BIN_DIR/_lib.sh
#npm run build
rsync --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 bash
if [[ "$(uname -s)" == "Darwin" ]]; then
echo "Don't run this on your local computer!"
exit 1
fi
echo "[remote] linking nginx configuration"
sudo ln -s /etc/nginx/sites-available/djinlist /etc/nginx/sites-enabled/
echo "[remote] Updating processor"
cd djinmusic
pnpm install -r
cd ..
pm2 stop Djiny
pm2 start Djiny
# pm2 stop processor
# pm2 start processor
echo "[remote] Installed"
#!/bin/zsh
autoenv_source_parent
autostash 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.sh
echo '🔎 Linting ...'
# Lint JavaScript
JS_ERROR_CODE=0
FILES=$(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" ]]; then
echo -e " $(echo "$FILES" | wc -l | awk '{print $1}') JavaScript, HTML, and Svelte files"
set +e
eslint -c "$MONO_DIR/.eslintrc.js" --ignore-path "$MONO_DIR/.eslintignore" $FILES
JS_ERROR_CODE=$?
set -e
fi
# Lint SCSS
CSS_ERROR_CODE=0
FILES=$(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" ]]; then
echo -e " $(echo "$FILES" | wc -l | awk '{print $1}') CSS files"
set +e
stylelint --fix --ignore-path "$MONO_DIR/.stylelintignore" $FILES
CSS_ERROR_CODE=$?
set -e
fi
if [[ $JS_ERROR_CODE -gt 0 ]] || [[ $CSS_ERROR_CODE -gt 0 ]]; then
exit 1
fi
#!/bin/zsh
autoenv_source_parent
autostash 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"