| … | … | @@ -0,0 +1,15 @@ |
| 1 | # Dependencies | |
| 2 | node_modules/ | |
| 3 | ||
| 4 | # Storage directories created by example CLIs | |
| 5 | .hyper-issues-*/ | |
| 6 | ||
| 7 | # Editor and OS cruft | |
| 8 | .DS_Store | |
| 9 | *.log | |
| 10 | *.swp | |
| 11 | .vscode/ | |
| 12 | .idea/ | |
| 13 | ||
| 14 | # Test artifacts | |
| 15 | coverage/ |
| … | … | @@ -0,0 +1,229 @@ |
| 1 | #!/usr/bin/env node | |
| 2 | /** | |
| 3 | * Mini CLI example for hyper-issues | |
| 4 | * | |
| 5 | * Usage: | |
| 6 | * node examples/cli.js init Create a tracker | |
| 7 | * node examples/cli.js info Show key + writers + counts | |
| 8 | * node examples/cli.js create <title> [-- body...] Create an issue | |
| 9 | * node examples/cli.js list [open|closed|all] List issues | |
| 10 | * node examples/cli.js show <id> Show issue + comments | |
| 11 | * node examples/cli.js close <id> Close an issue | |
| 12 | * node examples/cli.js reopen <id> Reopen an issue | |
| 13 | * node examples/cli.js comment <id> <body...> Add a comment | |
| 14 | * node examples/cli.js label <id> <name1,name2,...> Add labels | |
| 15 | * node examples/cli.js init-labels Create default labels | |
| 16 | * node examples/cli.js demo Run a full demo | |
| 17 | * | |
| 18 | * Storage: HYPER_ISSUES_STORE env var (default: ./.hyper-issues-cli) | |
| 19 | * | |
| 20 | * NOTE: this CLI uses its own storage path. The web example uses a different | |
| 21 | * default. They don't share state — RocksDB single-writer lock. | |
| 22 | */ | |
| 23 | ||
| 24 | import Corestore from 'corestore' | |
| 25 | import { HyperIssues } from '../index.js' | |
| 26 | ||
| 27 | const storagePath = process.env.HYPER_ISSUES_STORE || './.hyper-issues-cli' | |
| 28 | const author = process.env.USER || 'me' | |
| 29 | const args = process.argv.slice(2) | |
| 30 | const cmd = args[0] | |
| 31 | ||
| 32 | async function open () { | |
| 33 | const store = new Corestore(storagePath) | |
| 34 | await store.ready() | |
| 35 | const tracker = new HyperIssues(store, { namespace: 'issues' }) | |
| 36 | await tracker.ready() | |
| 37 | return { tracker, store } | |
| 38 | } | |
| 39 | ||
| 40 | async function close ({ tracker, store }) { | |
| 41 | await tracker.close() | |
| 42 | await store.close() | |
| 43 | } | |
| 44 | ||
| 45 | async function main () { | |
| 46 | if (cmd === 'init') { | |
| 47 | const ctx = await open() | |
| 48 | console.log('Tracker initialized at: %s', storagePath) | |
| 49 | console.log(' key: %s', ctx.tracker.keyHex) | |
| 50 | console.log(' localKey: %s', ctx.tracker.localKeyHex) | |
| 51 | console.log(' writable: %s', ctx.tracker.writable) | |
| 52 | await close(ctx) | |
| 53 | return | |
| 54 | } | |
| 55 | ||
| 56 | if (cmd === 'info') { | |
| 57 | const ctx = await open() | |
| 58 | const counts = await ctx.tracker.counts() | |
| 59 | const writers = await ctx.tracker.listWriters() | |
| 60 | const labels = await ctx.tracker.listLabels() | |
| 61 | console.log('Tracker: %s', storagePath) | |
| 62 | console.log(' key: %s', ctx.tracker.keyHex) | |
| 63 | console.log(' localKey: %s', ctx.tracker.localKeyHex) | |
| 64 | console.log(' writable: %s', ctx.tracker.writable) | |
| 65 | console.log(' issues: %d open, %d closed', counts.open, counts.closed) | |
| 66 | console.log(' writers: %d', writers.length) | |
| 67 | console.log(' labels: %d', labels.length) | |
| 68 | await close(ctx) | |
| 69 | return | |
| 70 | } | |
| 71 | ||
| 72 | if (cmd === 'create') { | |
| 73 | const dashIdx = args.indexOf('--') | |
| 74 | const title = dashIdx > 1 ? args.slice(1, dashIdx).join(' ') : args.slice(1).join(' ') | |
| 75 | const body = dashIdx > 1 ? args.slice(dashIdx + 1).join(' ') : '' | |
| 76 | if (!title) { console.error('Usage: cli.js create <title> [-- body...]'); process.exit(1) } | |
| 77 | const ctx = await open() | |
| 78 | const id = await ctx.tracker.create({ title, body, author }) | |
| 79 | console.log('Created issue #%d: %s', id, title) | |
| 80 | await close(ctx) | |
| 81 | return | |
| 82 | } | |
| 83 | ||
| 84 | if (cmd === 'list') { | |
| 85 | const status = args[1] || 'all' | |
| 86 | const ctx = await open() | |
| 87 | const issues = await ctx.tracker.list({ status, sort: 'newest' }) | |
| 88 | if (issues.length === 0) { | |
| 89 | console.log('(no %s issues)', status) | |
| 90 | } else { | |
| 91 | for (const i of issues) { | |
| 92 | const marker = i.status === 'closed' ? '[x]' : '[ ]' | |
| 93 | const labels = (i.labels || []).map(l => '#' + l).join(' ') | |
| 94 | console.log('%s #%-3d %s %s', marker, i.id, i.title, labels) | |
| 95 | } | |
| 96 | console.log('\n%d issues', issues.length) | |
| 97 | } | |
| 98 | await close(ctx) | |
| 99 | return | |
| 100 | } | |
| 101 | ||
| 102 | if (cmd === 'show') { | |
| 103 | const id = parseInt(args[1]) | |
| 104 | if (!id) { console.error('Usage: cli.js show <id>'); process.exit(1) } | |
| 105 | const ctx = await open() | |
| 106 | const issue = await ctx.tracker.get(id) | |
| 107 | if (!issue) { | |
| 108 | console.error('Issue #%d not found', id) | |
| 109 | await close(ctx) | |
| 110 | process.exit(1) | |
| 111 | } | |
| 112 | console.log('#%d %s [%s]', issue.id, issue.title, issue.status) | |
| 113 | console.log('by %s · %s', issue.author, new Date(issue.createdAt).toISOString()) | |
| 114 | if ((issue.labels || []).length > 0) console.log('labels: %s', issue.labels.map(l => '#' + l).join(' ')) | |
| 115 | if (issue.body) console.log('\n%s', issue.body) | |
| 116 | ||
| 117 | const comments = await ctx.tracker.getComments(id) | |
| 118 | if (comments.length > 0) { | |
| 119 | console.log('\n--- %d comments ---', comments.length) | |
| 120 | for (const c of comments) { | |
| 121 | console.log(' by %s · %s', c.author, new Date(c.createdAt).toISOString()) | |
| 122 | console.log(' %s\n', c.body) | |
| 123 | } | |
| 124 | } | |
| 125 | ||
| 126 | await close(ctx) | |
| 127 | return | |
| 128 | } | |
| 129 | ||
| 130 | if (cmd === 'close' || cmd === 'reopen') { | |
| 131 | const id = parseInt(args[1]) | |
| 132 | if (!id) { console.error('Usage: cli.js ' + cmd + ' <id>'); process.exit(1) } | |
| 133 | const ctx = await open() | |
| 134 | const status = cmd === 'close' ? 'closed' : 'open' | |
| 135 | await ctx.tracker.update(id, { status }, author) | |
| 136 | console.log('Issue #%d → %s', id, status) | |
| 137 | await close(ctx) | |
| 138 | return | |
| 139 | } | |
| 140 | ||
| 141 | if (cmd === 'comment') { | |
| 142 | const id = parseInt(args[1]) | |
| 143 | const body = args.slice(2).join(' ') | |
| 144 | if (!id || !body) { console.error('Usage: cli.js comment <id> <body...>'); process.exit(1) } | |
| 145 | const ctx = await open() | |
| 146 | const cId = await ctx.tracker.comment(id, { body, author }) | |
| 147 | console.log('Comment %s added to #%d', cId.slice(0, 12), id) | |
| 148 | await close(ctx) | |
| 149 | return | |
| 150 | } | |
| 151 | ||
| 152 | if (cmd === 'label') { | |
| 153 | const id = parseInt(args[1]) | |
| 154 | const labels = (args[2] || '').split(',').map(l => l.trim()).filter(Boolean) | |
| 155 | if (!id || labels.length === 0) { console.error('Usage: cli.js label <id> <name1,name2,...>'); process.exit(1) } | |
| 156 | const ctx = await open() | |
| 157 | await ctx.tracker.addLabels(id, labels, author) | |
| 158 | console.log('Added labels to #%d: %s', id, labels.join(', ')) | |
| 159 | await close(ctx) | |
| 160 | return | |
| 161 | } | |
| 162 | ||
| 163 | if (cmd === 'init-labels') { | |
| 164 | const ctx = await open() | |
| 165 | const labels = await ctx.tracker.initializeDefaultLabels() | |
| 166 | console.log('Initialized %d default labels:', labels.length) | |
| 167 | for (const l of labels) console.log(' #%s', l.name) | |
| 168 | await close(ctx) | |
| 169 | return | |
| 170 | } | |
| 171 | ||
| 172 | if (cmd === 'demo') { | |
| 173 | const ctx = await open() | |
| 174 | console.log('--- hyper-issues demo ---\n') | |
| 175 | ||
| 176 | console.log('1. Initialize default labels') | |
| 177 | await ctx.tracker.initializeDefaultLabels() | |
| 178 | ||
| 179 | console.log('2. Create three issues') | |
| 180 | const id1 = await ctx.tracker.create({ title: 'Login button broken', body: 'Clicking does nothing', author: 'alice' }) | |
| 181 | const id2 = await ctx.tracker.create({ title: 'Add dark mode', body: 'It would be nice', author: 'bob' }) | |
| 182 | const id3 = await ctx.tracker.create({ title: 'Update README', author: 'alice' }) | |
| 183 | ||
| 184 | console.log('3. Add labels') | |
| 185 | await ctx.tracker.addLabels(id1, ['bug'], 'alice') | |
| 186 | await ctx.tracker.addLabels(id2, ['enhancement'], 'bob') | |
| 187 | await ctx.tracker.addLabels(id3, ['documentation'], 'alice') | |
| 188 | ||
| 189 | console.log('4. Comment on issue #1') | |
| 190 | await ctx.tracker.comment(id1, { body: 'I can reproduce on Firefox 130', author: 'bob' }) | |
| 191 | await ctx.tracker.comment(id1, { body: 'Found the bug — fix incoming', author: 'carol' }) | |
| 192 | ||
| 193 | console.log('5. Close issue #3') | |
| 194 | await ctx.tracker.update(id3, { status: 'closed' }, 'alice') | |
| 195 | ||
| 196 | console.log('\n6. List all issues:') | |
| 197 | const all = await ctx.tracker.list({ sort: 'oldest' }) | |
| 198 | for (const i of all) { | |
| 199 | const marker = i.status === 'closed' ? '[x]' : '[ ]' | |
| 200 | const labels = (i.labels || []).map(l => '#' + l).join(' ') | |
| 201 | console.log(' %s #%d %s %s', marker, i.id, i.title, labels) | |
| 202 | } | |
| 203 | ||
| 204 | const counts = await ctx.tracker.counts() | |
| 205 | console.log('\n%d open · %d closed · %d total', counts.open, counts.closed, counts.total) | |
| 206 | ||
| 207 | await close(ctx) | |
| 208 | return | |
| 209 | } | |
| 210 | ||
| 211 | console.log('hyper-issues example CLI') | |
| 212 | console.log(' init Create a tracker') | |
| 213 | console.log(' info Show key + counts + writers') | |
| 214 | console.log(' create <title> [-- body...] Create an issue') | |
| 215 | console.log(' list [open|closed|all] List issues') | |
| 216 | console.log(' show <id> Show issue + comments') | |
| 217 | console.log(' close <id> | reopen <id> Change status') | |
| 218 | console.log(' comment <id> <body...> Add a comment') | |
| 219 | console.log(' label <id> <name1,name2,...> Add labels') | |
| 220 | console.log(' init-labels Create default labels') | |
| 221 | console.log(' demo Run a full demo') | |
| 222 | console.log('') | |
| 223 | console.log('Storage: HYPER_ISSUES_STORE env var (default: ./.hyper-issues-cli)') | |
| 224 | console.log('Author: USER env var (default: me)') | |
| 225 | console.log('Note: this CLI does NOT share state with examples/web.js — they use') | |
| 226 | console.log('different storage paths because RocksDB only allows one writer.') | |
| 227 | } | |
| 228 | ||
| 229 | main().catch((err) => { console.error(err.message); process.exit(1) }) |
| … | … | @@ -0,0 +1,254 @@ |
| 1 | #!/usr/bin/env node | |
| 2 | /** | |
| 3 | * Mini web example for hyper-issues | |
| 4 | * | |
| 5 | * SSR issue tracker dashboard. Pages: | |
| 6 | * GET / → list issues + create form | |
| 7 | * GET /:id → show issue + comments + comment form | |
| 8 | * POST /create → create an issue | |
| 9 | * POST /:id/close → close an issue | |
| 10 | * POST /:id/reopen → reopen an issue | |
| 11 | * POST /:id/comment → add a comment | |
| 12 | * | |
| 13 | * Usage: node examples/web.js [--port 9096] [--storage <path>] | |
| 14 | */ | |
| 15 | ||
| 16 | import http from 'http' | |
| 17 | import Corestore from 'corestore' | |
| 18 | import b4a from 'b4a' | |
| 19 | import { HyperIssues } from '../index.js' | |
| 20 | ||
| 21 | const args = process.argv.slice(2) | |
| 22 | function getFlag (name) { | |
| 23 | const i = args.indexOf('--' + name) | |
| 24 | return i !== -1 && args[i + 1] ? args[i + 1] : null | |
| 25 | } | |
| 26 | ||
| 27 | const port = parseInt(getFlag('port')) || 9096 | |
| 28 | const storagePath = getFlag('storage') || './.hyper-issues-web' | |
| 29 | ||
| 30 | const store = new Corestore(storagePath) | |
| 31 | await store.ready() | |
| 32 | const tracker = new HyperIssues(store, { namespace: 'issues' }) | |
| 33 | await tracker.ready() | |
| 34 | await tracker.initializeDefaultLabels() | |
| 35 | ||
| 36 | function escapeHtml (s) { | |
| 37 | return String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') | |
| 38 | } | |
| 39 | ||
| 40 | function collectBody (req) { | |
| 41 | return new Promise((resolve, reject) => { | |
| 42 | const parts = [] | |
| 43 | req.on('data', chunk => parts.push(chunk)) | |
| 44 | req.on('end', () => resolve(b4a.concat(parts).toString('utf8'))) | |
| 45 | req.on('error', reject) | |
| 46 | }) | |
| 47 | } | |
| 48 | ||
| 49 | const httpServer = http.createServer(async (req, res) => { | |
| 50 | const url = req.url.split('?')[0] | |
| 51 | try { | |
| 52 | if (req.method === 'GET' && url === '/') { | |
| 53 | const filter = (req.url.split('?')[1] || '').match(/status=([^&]+)/) | |
| 54 | const status = filter ? filter[1] : 'open' | |
| 55 | const issues = await tracker.list({ status, sort: 'newest' }) | |
| 56 | const counts = await tracker.counts() | |
| 57 | res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }) | |
| 58 | res.end(renderIndex(issues, counts, status)) | |
| 59 | return | |
| 60 | } | |
| 61 | ||
| 62 | const showMatch = url.match(/^\/(\d+)$/) | |
| 63 | if (req.method === 'GET' && showMatch) { | |
| 64 | const id = parseInt(showMatch[1]) | |
| 65 | const issue = await tracker.get(id) | |
| 66 | if (!issue) { | |
| 67 | res.writeHead(404, { 'Content-Type': 'text/plain' }) | |
| 68 | res.end('Not found') | |
| 69 | return | |
| 70 | } | |
| 71 | const comments = await tracker.getComments(id) | |
| 72 | res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }) | |
| 73 | res.end(renderIssue(issue, comments)) | |
| 74 | return | |
| 75 | } | |
| 76 | ||
| 77 | if (req.method === 'POST' && url === '/create') { | |
| 78 | const params = new URLSearchParams(await collectBody(req)) | |
| 79 | const title = params.get('title') | |
| 80 | const body = params.get('body') || '' | |
| 81 | const author = params.get('author') || 'anon' | |
| 82 | if (title) { | |
| 83 | const id = await tracker.create({ title, body, author }) | |
| 84 | res.writeHead(303, { Location: '/' + id }) | |
| 85 | res.end() | |
| 86 | return | |
| 87 | } | |
| 88 | res.writeHead(400) | |
| 89 | res.end('title required') | |
| 90 | return | |
| 91 | } | |
| 92 | ||
| 93 | const closeMatch = url.match(/^\/(\d+)\/(close|reopen)$/) | |
| 94 | if (req.method === 'POST' && closeMatch) { | |
| 95 | const id = parseInt(closeMatch[1]) | |
| 96 | const status = closeMatch[2] === 'close' ? 'closed' : 'open' | |
| 97 | await tracker.update(id, { status }, 'web') | |
| 98 | res.writeHead(303, { Location: '/' + id }) | |
| 99 | res.end() | |
| 100 | return | |
| 101 | } | |
| 102 | ||
| 103 | const commentMatch = url.match(/^\/(\d+)\/comment$/) | |
| 104 | if (req.method === 'POST' && commentMatch) { | |
| 105 | const id = parseInt(commentMatch[1]) | |
| 106 | const params = new URLSearchParams(await collectBody(req)) | |
| 107 | const body = params.get('body') | |
| 108 | const author = params.get('author') || 'anon' | |
| 109 | if (body) await tracker.comment(id, { body, author }) | |
| 110 | res.writeHead(303, { Location: '/' + id }) | |
| 111 | res.end() | |
| 112 | return | |
| 113 | } | |
| 114 | ||
| 115 | res.writeHead(404, { 'Content-Type': 'text/plain' }) | |
| 116 | res.end('Not found') | |
| 117 | } catch (err) { | |
| 118 | console.error(err) | |
| 119 | res.writeHead(500, { 'Content-Type': 'text/plain' }) | |
| 120 | res.end(err.message) | |
| 121 | } | |
| 122 | }) | |
| 123 | ||
| 124 | function styles () { | |
| 125 | return ` | |
| 126 | body { font-family: monospace; background: #0d1117; color: #c9d1d9; padding: 2rem; max-width: 800px; margin: 0 auto; } | |
| 127 | h1 { color: #58a6ff; } | |
| 128 | h2 { font-size: 1rem; color: #c9d1d9; margin-top: 0; } | |
| 129 | a { color: #58a6ff; text-decoration: none; } | |
| 130 | a:hover { text-decoration: underline; } | |
| 131 | .card { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 1rem; margin: 1rem 0; } | |
| 132 | .card h2 { margin: 0 0 0.5rem; font-size: 0.75rem; text-transform: uppercase; color: #8b949e; } | |
| 133 | .row { display: flex; justify-content: space-between; padding: 0.3rem 0; border-bottom: 1px solid #21262d; } | |
| 134 | .row:last-child { border-bottom: none; } | |
| 135 | .label { color: #8b949e; } | |
| 136 | .filter { display: inline-block; padding: 0.3rem 0.6rem; margin-right: 0.3rem; border-radius: 4px; background: #21262d; color: #c9d1d9; } | |
| 137 | .filter.active { background: #1f6feb; color: white; } | |
| 138 | .issue { padding: 0.6rem 0.5rem; border-bottom: 1px solid #21262d; } | |
| 139 | .issue:last-child { border-bottom: none; } | |
| 140 | .issue .id { color: #8b949e; font-size: 0.8rem; } | |
| 141 | .issue .title { color: #c9d1d9; font-weight: bold; } | |
| 142 | .issue.closed .title { color: #8b949e; text-decoration: line-through; } | |
| 143 | .issue .meta { color: #6e7681; font-size: 0.75rem; margin-top: 0.2rem; } | |
| 144 | .tag { display: inline-block; padding: 0.05rem 0.4rem; margin-right: 0.3rem; border-radius: 999px; font-size: 0.7rem; background: #1f6feb22; border: 1px solid #1f6feb44; color: #58a6ff; } | |
| 145 | .badge-open { background: #2ea04322; color: #3fb950; border: 1px solid #2ea04344; padding: 0.1rem 0.5rem; border-radius: 4px; font-size: 0.75rem; } | |
| 146 | .badge-closed { background: #8957e522; color: #a371f7; border: 1px solid #8957e544; padding: 0.1rem 0.5rem; border-radius: 4px; font-size: 0.75rem; } | |
| 147 | input, textarea, button { background: #0d1117; color: #c9d1d9; border: 1px solid #30363d; padding: 0.4rem 0.6rem; border-radius: 4px; font-family: monospace; font-size: 0.85rem; } | |
| 148 | textarea { width: 100%; min-height: 4em; } | |
| 149 | input { width: 100%; box-sizing: border-box; } | |
| 150 | button { background: #21262d; cursor: pointer; } | |
| 151 | button:hover { background: #30363d; } | |
| 152 | button.btn-primary { background: #1f6feb; border-color: #1f6feb; color: white; } | |
| 153 | button.btn-primary:hover { background: #1158c7; } | |
| 154 | .field { margin-bottom: 0.5rem; } | |
| 155 | .field label { display: block; color: #8b949e; font-size: 0.75rem; margin-bottom: 0.2rem; } | |
| 156 | .comment { padding: 0.6rem 0; border-bottom: 1px solid #21262d; } | |
| 157 | .comment:last-child { border-bottom: none; } | |
| 158 | .comment .meta { color: #8b949e; font-size: 0.75rem; } | |
| 159 | .empty { color: #484f58; font-style: italic; padding: 1rem 0; } | |
| 160 | form { margin: 0; } | |
| 161 | ` | |
| 162 | } | |
| 163 | ||
| 164 | function renderIndex (issues, counts, currentStatus) { | |
| 165 | return `<!DOCTYPE html> | |
| 166 | <html><head><title>hyper-issues</title><style>${styles()}</style></head><body> | |
| 167 | <h1><a href="/">hyper-issues</a></h1> | |
| 168 | ||
| 169 | <div class="card"> | |
| 170 | <h2>Filter</h2> | |
| 171 | <a class="filter ${currentStatus === 'open' ? 'active' : ''}" href="/?status=open">${counts.open} open</a> | |
| 172 | <a class="filter ${currentStatus === 'closed' ? 'active' : ''}" href="/?status=closed">${counts.closed} closed</a> | |
| 173 | <a class="filter ${currentStatus === 'all' ? 'active' : ''}" href="/?status=all">${counts.total} all</a> | |
| 174 | </div> | |
| 175 | ||
| 176 | <div class="card"> | |
| 177 | <h2>${issues.length} ${currentStatus} issue${issues.length === 1 ? '' : 's'}</h2> | |
| 178 | ${issues.length === 0 ? '<div class="empty">(no issues)</div>' : issues.map(i => ` | |
| 179 | <div class="issue ${i.status}"> | |
| 180 | <div> | |
| 181 | <span class="id">#${i.id}</span> | |
| 182 | <a class="title" href="/${i.id}">${escapeHtml(i.title)}</a> | |
| 183 | ${(i.labels || []).map(l => '<span class="tag">' + escapeHtml(l) + '</span>').join(' ')} | |
| 184 | </div> | |
| 185 | <div class="meta">by ${escapeHtml(i.author)} · ${new Date(i.createdAt).toLocaleString()}</div> | |
| 186 | </div>`).join('')} | |
| 187 | </div> | |
| 188 | ||
| 189 | <div class="card"> | |
| 190 | <h2>Create issue</h2> | |
| 191 | <form action="/create" method="POST"> | |
| 192 | <div class="field"><label>Title</label><input name="title" required></div> | |
| 193 | <div class="field"><label>Body</label><textarea name="body"></textarea></div> | |
| 194 | <div class="field"><label>Author</label><input name="author" value="anon"></div> | |
| 195 | <button class="btn-primary" type="submit">Create</button> | |
| 196 | </form> | |
| 197 | </div> | |
| 198 | ||
| 199 | </body></html>` | |
| 200 | } | |
| 201 | ||
| 202 | function renderIssue (issue, comments) { | |
| 203 | const badge = issue.status === 'closed' | |
| 204 | ? '<span class="badge-closed">closed</span>' | |
| 205 | : '<span class="badge-open">open</span>' | |
| 206 | return `<!DOCTYPE html> | |
| 207 | <html><head><title>hyper-issues #${issue.id}</title><style>${styles()}</style></head><body> | |
| 208 | <h1><a href="/">hyper-issues</a> / #${issue.id}</h1> | |
| 209 | ||
| 210 | <div class="card"> | |
| 211 | <h2>Issue</h2> | |
| 212 | <div style="display: flex; justify-content: space-between; align-items: center"> | |
| 213 | <div> | |
| 214 | <strong>${escapeHtml(issue.title)}</strong> | |
| 215 | ${badge} | |
| 216 | </div> | |
| 217 | <form action="/${issue.id}/${issue.status === 'open' ? 'close' : 'reopen'}" method="POST"> | |
| 218 | <button type="submit">${issue.status === 'open' ? 'Close' : 'Reopen'}</button> | |
| 219 | </form> | |
| 220 | </div> | |
| 221 | <div class="meta" style="color:#8b949e;font-size:0.75rem;margin-top:0.5rem"> | |
| 222 | by ${escapeHtml(issue.author)} · ${new Date(issue.createdAt).toLocaleString()} | |
| 223 | </div> | |
| 224 | ${(issue.labels || []).length > 0 ? '<div style="margin-top:0.5rem">' + issue.labels.map(l => '<span class="tag">' + escapeHtml(l) + '</span>').join(' ') + '</div>' : ''} | |
| 225 | ${issue.body ? '<div style="margin-top:0.8rem;white-space:pre-wrap;color:#c9d1d9">' + escapeHtml(issue.body) + '</div>' : ''} | |
| 226 | </div> | |
| 227 | ||
| 228 | <div class="card"> | |
| 229 | <h2>${comments.length} Comment${comments.length === 1 ? '' : 's'}</h2> | |
| 230 | ${comments.length === 0 ? '<div class="empty">(no comments yet)</div>' : comments.map(c => ` | |
| 231 | <div class="comment"> | |
| 232 | <div class="meta">${escapeHtml(c.author)} · ${new Date(c.createdAt).toLocaleString()}${c.editedAt ? ' (edited)' : ''}</div> | |
| 233 | <div style="white-space:pre-wrap;margin-top:0.3rem">${escapeHtml(c.body)}</div> | |
| 234 | </div>`).join('')} | |
| 235 | </div> | |
| 236 | ||
| 237 | <div class="card"> | |
| 238 | <h2>Add comment</h2> | |
| 239 | <form action="/${issue.id}/comment" method="POST"> | |
| 240 | <div class="field"><label>Body</label><textarea name="body" required></textarea></div> | |
| 241 | <div class="field"><label>Author</label><input name="author" value="anon"></div> | |
| 242 | <button class="btn-primary" type="submit">Comment</button> | |
| 243 | </form> | |
| 244 | </div> | |
| 245 | ||
| 246 | </body></html>` | |
| 247 | } | |
| 248 | ||
| 249 | httpServer.listen(port, () => { | |
| 250 | console.log('hyper-issues dashboard:') | |
| 251 | console.log(' http://localhost:%d', port) | |
| 252 | console.log(' storage: %s', storagePath) | |
| 253 | console.log(' tracker key: %s', tracker.keyHex) | |
| 254 | }) |
| … | … | @@ -0,0 +1,6 @@ |
| 1 | /** | |
| 2 | * hyper-issues — P2P issue tracking on hyper-rooms | |
| 3 | */ | |
| 4 | ||
| 5 | export { HyperIssues } from './lib/hyper-issues.js' | |
| 6 | export { issuesSchema, DEFAULT_LABELS } from './lib/schema.js' |
| … | … | @@ -0,0 +1,391 @@ |
| 1 | /** | |
| 2 | * HyperIssues — P2P issue tracking built on hyper-rooms | |
| 3 | * | |
| 4 | * Multi-writer, replicating, offline-first issue tracker. Wraps a Room with | |
| 5 | * the issuesSchema and provides a high-level API for issues, comments, | |
| 6 | * labels, and (optionally) git refs. | |
| 7 | * | |
| 8 | * Usage: | |
| 9 | * | |
| 10 | * const tracker = new HyperIssues(corestore, { namespace: 'my-project' }) | |
| 11 | * await tracker.ready() | |
| 12 | * | |
| 13 | * const id = await tracker.create({ title: 'Bug', body: 'Details', author: 'alice' }) | |
| 14 | * const issue = await tracker.get(id) | |
| 15 | * await tracker.comment(id, { body: 'reproduced', author: 'bob' }) | |
| 16 | * const issues = await tracker.list({ status: 'open' }) | |
| 17 | * | |
| 18 | * tracker.localKey // exchange via P2P for addWriter on the inviter | |
| 19 | */ | |
| 20 | ||
| 21 | import { Room } from 'hyper-rooms' | |
| 22 | import { issuesSchema, DEFAULT_LABELS } from './schema.js' | |
| 23 | ||
| 24 | export class HyperIssues { | |
| 25 | /** | |
| 26 | * @param {Corestore} corestore | |
| 27 | * @param {object} options | |
| 28 | * @param {string} [options.namespace='issues'] | |
| 29 | * @param {Buffer|string|null} [options.bootstrapKey=null] | |
| 30 | */ | |
| 31 | constructor (corestore, options = {}) { | |
| 32 | this._room = new Room(corestore, { | |
| 33 | schema: issuesSchema, | |
| 34 | namespace: options.namespace || 'issues', | |
| 35 | bootstrapKey: options.bootstrapKey || null | |
| 36 | }) | |
| 37 | } | |
| 38 | ||
| 39 | // ── Lifecycle / properties ────────────────────────────────── | |
| 40 | ||
| 41 | async ready () { | |
| 42 | await this._room.ready() | |
| 43 | } | |
| 44 | ||
| 45 | async sync () { await this._room.base.update() } | |
| 46 | async close () { await this._room.close() } | |
| 47 | ||
| 48 | get key () { return this._room.key } | |
| 49 | get keyHex () { return this._room.keyHex } | |
| 50 | get discoveryKey () { return this._room.discoveryKey } | |
| 51 | get localKey () { return this._room.localKey } | |
| 52 | get localKeyHex () { return this._room.localKeyHex } | |
| 53 | get writable () { return this._room.writable } | |
| 54 | get view () { return this._room.view } | |
| 55 | get base () { return this._room.base } | |
| 56 | get room () { return this._room } | |
| 57 | ||
| 58 | // ── Writer management (delegated to Room) ─────────────────── | |
| 59 | ||
| 60 | async addWriter (userId, options = {}) { | |
| 61 | return this._room.addWriter(userId, options) | |
| 62 | } | |
| 63 | ||
| 64 | async removeWriter (userId) { | |
| 65 | return this._room.removeWriter(userId) | |
| 66 | } | |
| 67 | ||
| 68 | async listWriters () { | |
| 69 | return this._room.listWriters() | |
| 70 | } | |
| 71 | ||
| 72 | async getWriter (userId) { | |
| 73 | return this._room.getWriter(userId) | |
| 74 | } | |
| 75 | ||
| 76 | // ── Issues ────────────────────────────────────────────────── | |
| 77 | ||
| 78 | /** | |
| 79 | * Create an issue with auto-incrementing ID. | |
| 80 | * @param {object} options | |
| 81 | * @param {string} options.title | |
| 82 | * @param {string} [options.body] | |
| 83 | * @param {string} options.author | |
| 84 | * @param {string[]} [options.labels] | |
| 85 | * @param {string[]} [options.images] | |
| 86 | * @returns {Promise<number>} the issue ID | |
| 87 | */ | |
| 88 | async create (options) { | |
| 89 | const { title, body, author, labels, images } = options | |
| 90 | await this._room.base.update() | |
| 91 | const counter = await this._room.view.get('counter:issues') | |
| 92 | const id = counter ? counter.value.next : 1 | |
| 93 | ||
| 94 | await this._room.append({ | |
| 95 | type: 'create-issue', | |
| 96 | id, | |
| 97 | title, | |
| 98 | body: body || '', | |
| 99 | userId: author, | |
| 100 | labels: labels || [], | |
| 101 | images: images || [], | |
| 102 | timestamp: Date.now() | |
| 103 | }) | |
| 104 | return id | |
| 105 | } | |
| 106 | ||
| 107 | /** | |
| 108 | * Update an issue (title, body, status, labels, images). | |
| 109 | * @param {number} id | |
| 110 | * @param {object} updates | |
| 111 | * @param {string} userId | |
| 112 | */ | |
| 113 | async update (id, updates, userId) { | |
| 114 | await this._room.append({ | |
| 115 | type: 'update-issue', | |
| 116 | id, | |
| 117 | ...updates, | |
| 118 | userId, | |
| 119 | timestamp: Date.now() | |
| 120 | }) | |
| 121 | } | |
| 122 | ||
| 123 | /** Get a single issue by ID. */ | |
| 124 | async get (id) { | |
| 125 | await this._room.base.update() | |
| 126 | const entry = await this._room.view.get(`issue:${id}`) | |
| 127 | return entry ? entry.value : null | |
| 128 | } | |
| 129 | ||
| 130 | /** | |
| 131 | * List issues with optional filtering and sorting. | |
| 132 | * @param {object} [filters] | |
| 133 | * @param {'open'|'closed'|'all'} [filters.status='all'] | |
| 134 | * @param {string[]} [filters.labels] - must have ALL listed labels | |
| 135 | * @param {string} [filters.search] - search in title and body | |
| 136 | * @param {string} [filters.author] - filter by author userId | |
| 137 | * @param {'newest'|'oldest'|'recently-updated'} [filters.sort='newest'] | |
| 138 | * @param {number} [filters.limit=0] | |
| 139 | * @param {number} [filters.offset=0] | |
| 140 | */ | |
| 141 | async list (filters = {}) { | |
| 142 | const { | |
| 143 | status = 'all', | |
| 144 | labels = [], | |
| 145 | search = '', | |
| 146 | author = '', | |
| 147 | sort = 'newest', | |
| 148 | limit = 0, | |
| 149 | offset = 0 | |
| 150 | } = filters | |
| 151 | ||
| 152 | await this._room.base.update() | |
| 153 | const issues = [] | |
| 154 | ||
| 155 | for await (const entry of this._room.view.createReadStream({ | |
| 156 | gt: 'issue:', lt: 'issue:\xff' | |
| 157 | })) { | |
| 158 | const issue = entry.value | |
| 159 | ||
| 160 | if (status !== 'all' && issue.status !== status) continue | |
| 161 | ||
| 162 | if (labels.length > 0) { | |
| 163 | const issueLabels = issue.labels || [] | |
| 164 | const hasAll = labels.every(l => { | |
| 165 | const normalized = l.toLowerCase().replace(/\s+/g, '-') | |
| 166 | return issueLabels.includes(normalized) | |
| 167 | }) | |
| 168 | if (!hasAll) continue | |
| 169 | } | |
| 170 | ||
| 171 | if (search) { | |
| 172 | const s = search.toLowerCase() | |
| 173 | const titleMatch = issue.title && issue.title.toLowerCase().includes(s) | |
| 174 | const bodyMatch = issue.body && issue.body.toLowerCase().includes(s) | |
| 175 | if (!titleMatch && !bodyMatch) continue | |
| 176 | } | |
| 177 | ||
| 178 | if (author && issue.author !== author) continue | |
| 179 | issues.push(issue) | |
| 180 | } | |
| 181 | ||
| 182 | switch (sort) { | |
| 183 | case 'oldest': | |
| 184 | issues.sort((a, b) => a.id - b.id) | |
| 185 | break | |
| 186 | case 'recently-updated': | |
| 187 | issues.sort((a, b) => (b.updatedAt || b.createdAt) - (a.updatedAt || a.createdAt)) | |
| 188 | break | |
| 189 | default: | |
| 190 | issues.sort((a, b) => b.id - a.id) | |
| 191 | break | |
| 192 | } | |
| 193 | ||
| 194 | const total = issues.length | |
| 195 | const paginated = limit > 0 ? issues.slice(offset, offset + limit) : issues | |
| 196 | ||
| 197 | if (limit > 0) { | |
| 198 | return { items: paginated, total, hasMore: offset + limit < total } | |
| 199 | } | |
| 200 | return issues | |
| 201 | } | |
| 202 | ||
| 203 | /** Issue counts by status. */ | |
| 204 | async counts () { | |
| 205 | await this._room.base.update() | |
| 206 | let open = 0 | |
| 207 | let closed = 0 | |
| 208 | for await (const entry of this._room.view.createReadStream({ | |
| 209 | gt: 'issue:', lt: 'issue:\xff' | |
| 210 | })) { | |
| 211 | if (entry.value.status === 'closed') closed++ | |
| 212 | else open++ | |
| 213 | } | |
| 214 | return { open, closed, total: open + closed } | |
| 215 | } | |
| 216 | ||
| 217 | // ── Comments ──────────────────────────────────────────────── | |
| 218 | ||
| 219 | async comment (issueId, options) { | |
| 220 | const { body, author } = options | |
| 221 | const commentId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}` | |
| 222 | ||
| 223 | await this._room.append({ | |
| 224 | type: 'create-comment', | |
| 225 | issueId, | |
| 226 | commentId, | |
| 227 | body, | |
| 228 | userId: author, | |
| 229 | timestamp: Date.now() | |
| 230 | }) | |
| 231 | return commentId | |
| 232 | } | |
| 233 | ||
| 234 | async getComments (issueId) { | |
| 235 | await this._room.base.update() | |
| 236 | const comments = [] | |
| 237 | for await (const entry of this._room.view.createReadStream({ | |
| 238 | gt: `comment:${issueId}:`, lt: `comment:${issueId}:\xff` | |
| 239 | })) { | |
| 240 | comments.push({ | |
| 241 | commentId: entry.key.split(':').slice(2).join(':'), | |
| 242 | ...entry.value | |
| 243 | }) | |
| 244 | } | |
| 245 | return comments | |
| 246 | } | |
| 247 | ||
| 248 | async editComment (issueId, commentId, body, userId) { | |
| 249 | await this._room.append({ | |
| 250 | type: 'update-comment', | |
| 251 | issueId, | |
| 252 | commentId, | |
| 253 | body, | |
| 254 | userId, | |
| 255 | timestamp: Date.now() | |
| 256 | }) | |
| 257 | } | |
| 258 | ||
| 259 | async deleteComment (issueId, commentId, userId) { | |
| 260 | await this._room.append({ | |
| 261 | type: 'delete-comment', | |
| 262 | issueId, | |
| 263 | commentId, | |
| 264 | userId, | |
| 265 | timestamp: Date.now() | |
| 266 | }) | |
| 267 | } | |
| 268 | ||
| 269 | // ── Labels ────────────────────────────────────────────────── | |
| 270 | ||
| 271 | async createLabel (name, color, description, displayName) { | |
| 272 | await this._room.append({ | |
| 273 | type: 'create-label', | |
| 274 | name, | |
| 275 | color: color || '#ccc', | |
| 276 | description: description || '', | |
| 277 | displayName: displayName || name | |
| 278 | }) | |
| 279 | } | |
| 280 | ||
| 281 | async updateLabel (name, updates) { | |
| 282 | await this._room.append({ type: 'update-label', name, ...updates }) | |
| 283 | } | |
| 284 | ||
| 285 | async deleteLabel (name) { | |
| 286 | await this._room.append({ type: 'delete-label', name }) | |
| 287 | } | |
| 288 | ||
| 289 | async getLabel (name) { | |
| 290 | await this._room.base.update() | |
| 291 | const normalized = name.toLowerCase().replace(/\s+/g, '-') | |
| 292 | const entry = await this._room.view.get(`label:${normalized}`) | |
| 293 | return entry ? { name: normalized, ...entry.value } : null | |
| 294 | } | |
| 295 | ||
| 296 | async listLabels () { | |
| 297 | await this._room.base.update() | |
| 298 | const labels = [] | |
| 299 | for await (const entry of this._room.view.createReadStream({ | |
| 300 | gt: 'label:', lt: 'label:\xff' | |
| 301 | })) { | |
| 302 | labels.push({ name: entry.key.slice('label:'.length), ...entry.value }) | |
| 303 | } | |
| 304 | return labels.sort((a, b) => a.name.localeCompare(b.name)) | |
| 305 | } | |
| 306 | ||
| 307 | async initializeDefaultLabels () { | |
| 308 | const existing = await this.listLabels() | |
| 309 | if (existing.length > 0) return existing | |
| 310 | for (const label of DEFAULT_LABELS) { | |
| 311 | await this.createLabel(label.name, label.color, label.description, label.name) | |
| 312 | } | |
| 313 | return this.listLabels() | |
| 314 | } | |
| 315 | ||
| 316 | async addLabels (id, labelNames, userId) { | |
| 317 | const issue = await this.get(id) | |
| 318 | if (!issue) throw new Error('Issue not found') | |
| 319 | ||
| 320 | const currentLabels = issue.labels || [] | |
| 321 | const newLabels = [...currentLabels] | |
| 322 | for (const labelName of labelNames) { | |
| 323 | const normalized = labelName.toLowerCase().replace(/\s+/g, '-') | |
| 324 | const label = await this.getLabel(normalized) | |
| 325 | if (!label) throw new Error(`Label "${labelName}" not found`) | |
| 326 | if (!newLabels.includes(normalized)) newLabels.push(normalized) | |
| 327 | } | |
| 328 | await this.update(id, { labels: newLabels }, userId) | |
| 329 | return this.get(id) | |
| 330 | } | |
| 331 | ||
| 332 | async removeLabels (id, labelNames, userId) { | |
| 333 | const issue = await this.get(id) | |
| 334 | if (!issue) throw new Error('Issue not found') | |
| 335 | const toRemove = new Set(labelNames.map(l => l.toLowerCase().replace(/\s+/g, '-'))) | |
| 336 | const newLabels = (issue.labels || []).filter(l => !toRemove.has(l)) | |
| 337 | await this.update(id, { labels: newLabels }, userId) | |
| 338 | return this.get(id) | |
| 339 | } | |
| 340 | ||
| 341 | async setLabels (id, labelNames, userId) { | |
| 342 | const normalized = [] | |
| 343 | for (const labelName of labelNames) { | |
| 344 | const n = labelName.toLowerCase().replace(/\s+/g, '-') | |
| 345 | const label = await this.getLabel(n) | |
| 346 | if (!label) throw new Error(`Label "${labelName}" not found`) | |
| 347 | normalized.push(n) | |
| 348 | } | |
| 349 | await this.update(id, { labels: normalized }, userId) | |
| 350 | return this.get(id) | |
| 351 | } | |
| 352 | ||
| 353 | // ── Refs (optional, for git integration) ──────────────────── | |
| 354 | ||
| 355 | async updateRef (ref, hash, userId) { | |
| 356 | await this._room.append({ type: 'update-ref', ref, hash, userId, timestamp: Date.now() }) | |
| 357 | } | |
| 358 | ||
| 359 | async deleteRef (ref, userId) { | |
| 360 | await this._room.append({ type: 'delete-ref', ref, userId, timestamp: Date.now() }) | |
| 361 | } | |
| 362 | ||
| 363 | async getRef (ref) { | |
| 364 | await this._room.base.update() | |
| 365 | const entry = await this._room.view.get(`ref:${ref}`) | |
| 366 | return entry ? entry.value : null | |
| 367 | } | |
| 368 | ||
| 369 | async listRefs () { | |
| 370 | await this._room.base.update() | |
| 371 | const refs = [] | |
| 372 | for await (const entry of this._room.view.createReadStream({ | |
| 373 | gt: 'ref:', lt: 'ref:\xff' | |
| 374 | })) { | |
| 375 | refs.push({ ref: entry.key.slice('ref:'.length), ...entry.value }) | |
| 376 | } | |
| 377 | return refs | |
| 378 | } | |
| 379 | ||
| 380 | // ── Meta ──────────────────────────────────────────────────── | |
| 381 | ||
| 382 | async setMeta (key, value) { | |
| 383 | await this._room.append({ type: 'set-meta', key, value }) | |
| 384 | } | |
| 385 | ||
| 386 | async getMeta (key) { | |
| 387 | await this._room.base.update() | |
| 388 | const entry = await this._room.view.get(`meta:${key}`) | |
| 389 | return entry ? entry.value.value : null | |
| 390 | } | |
| 391 | } |
| … | … | @@ -0,0 +1,189 @@ |
| 1 | /** | |
| 2 | * issuesSchema — hyper-rooms schema for issue tracking | |
| 3 | * | |
| 4 | * Op types (writer ops are handled by Room): | |
| 5 | * create-issue, update-issue | |
| 6 | * create-comment, update-comment, delete-comment | |
| 7 | * create-label, update-label, delete-label | |
| 8 | * update-ref, delete-ref (optional, for git integration) | |
| 9 | * set-meta | |
| 10 | * | |
| 11 | * View schema (Hyperbee keys): | |
| 12 | * issue:{id} → { id, title, body, author, status, labels, images, createdAt, updatedAt } | |
| 13 | * comment:{issueId}:{id} → { body, author, createdAt, editedAt?, editedBy? } | |
| 14 | * label:{name} → { color, description, displayName } | |
| 15 | * ref:{refPath} → { hash, updatedBy, timestamp } | |
| 16 | * meta:{key} → { value } | |
| 17 | * counter:issues → { next: number } | |
| 18 | */ | |
| 19 | ||
| 20 | import { jsonSchema } from 'hyper-rooms' | |
| 21 | ||
| 22 | export const issuesSchema = jsonSchema({ | |
| 23 | validate (op) { | |
| 24 | switch (op.type) { | |
| 25 | case 'create-issue': | |
| 26 | if (typeof op.id !== 'number') throw new Error('create-issue requires numeric id') | |
| 27 | if (!op.title) throw new Error('create-issue requires title') | |
| 28 | if (!op.userId) throw new Error('create-issue requires userId') | |
| 29 | break | |
| 30 | case 'update-issue': | |
| 31 | if (typeof op.id !== 'number') throw new Error('update-issue requires numeric id') | |
| 32 | if (!op.userId) throw new Error('update-issue requires userId') | |
| 33 | break | |
| 34 | case 'create-comment': | |
| 35 | if (typeof op.issueId !== 'number') throw new Error('create-comment requires numeric issueId') | |
| 36 | if (!op.commentId) throw new Error('create-comment requires commentId') | |
| 37 | if (!op.body) throw new Error('create-comment requires body') | |
| 38 | if (!op.userId) throw new Error('create-comment requires userId') | |
| 39 | break | |
| 40 | case 'update-comment': | |
| 41 | case 'delete-comment': | |
| 42 | if (typeof op.issueId !== 'number') throw new Error(op.type + ' requires numeric issueId') | |
| 43 | if (!op.commentId) throw new Error(op.type + ' requires commentId') | |
| 44 | break | |
| 45 | case 'create-label': | |
| 46 | case 'update-label': | |
| 47 | case 'delete-label': | |
| 48 | if (!op.name) throw new Error(op.type + ' requires name') | |
| 49 | break | |
| 50 | case 'update-ref': | |
| 51 | if (!op.ref) throw new Error('update-ref requires ref') | |
| 52 | if (!op.hash) throw new Error('update-ref requires hash') | |
| 53 | break | |
| 54 | case 'delete-ref': | |
| 55 | if (!op.ref) throw new Error('delete-ref requires ref') | |
| 56 | break | |
| 57 | case 'set-meta': | |
| 58 | if (!op.key) throw new Error('set-meta requires key') | |
| 59 | break | |
| 60 | } | |
| 61 | }, | |
| 62 | ||
| 63 | async apply (batch, op, host) { | |
| 64 | switch (op.type) { | |
| 65 | case 'create-issue': { | |
| 66 | await batch.put(`issue:${op.id}`, { | |
| 67 | id: op.id, | |
| 68 | title: op.title, | |
| 69 | body: op.body || '', | |
| 70 | author: op.userId, | |
| 71 | status: op.status || 'open', | |
| 72 | labels: op.labels || [], | |
| 73 | images: op.images || [], | |
| 74 | createdAt: op.timestamp || Date.now(), | |
| 75 | updatedAt: op.timestamp || Date.now() | |
| 76 | }) | |
| 77 | // Bump the auto-increment counter | |
| 78 | const counter = await batch.get('counter:issues') | |
| 79 | const currentNext = counter ? counter.value.next : 0 | |
| 80 | if (op.id >= currentNext) { | |
| 81 | await batch.put('counter:issues', { next: op.id + 1 }) | |
| 82 | } | |
| 83 | break | |
| 84 | } | |
| 85 | ||
| 86 | case 'update-issue': { | |
| 87 | const entry = await batch.get(`issue:${op.id}`) | |
| 88 | if (!entry) break | |
| 89 | const issue = entry.value | |
| 90 | if (op.title !== undefined) issue.title = op.title | |
| 91 | if (op.body !== undefined) issue.body = op.body | |
| 92 | if (op.status !== undefined) issue.status = op.status | |
| 93 | if (op.labels !== undefined) issue.labels = op.labels | |
| 94 | if (op.images !== undefined) issue.images = op.images | |
| 95 | issue.updatedAt = op.timestamp || Date.now() | |
| 96 | await batch.put(`issue:${op.id}`, issue) | |
| 97 | break | |
| 98 | } | |
| 99 | ||
| 100 | case 'create-comment': { | |
| 101 | await batch.put(`comment:${op.issueId}:${op.commentId}`, { | |
| 102 | body: op.body, | |
| 103 | author: op.userId, | |
| 104 | createdAt: op.timestamp || Date.now() | |
| 105 | }) | |
| 106 | // Bump the issue's updatedAt | |
| 107 | const issueEntry = await batch.get(`issue:${op.issueId}`) | |
| 108 | if (issueEntry) { | |
| 109 | issueEntry.value.updatedAt = op.timestamp || Date.now() | |
| 110 | await batch.put(`issue:${op.issueId}`, issueEntry.value) | |
| 111 | } | |
| 112 | break | |
| 113 | } | |
| 114 | ||
| 115 | case 'update-comment': { | |
| 116 | const key = `comment:${op.issueId}:${op.commentId}` | |
| 117 | const existing = await batch.get(key) | |
| 118 | if (existing) { | |
| 119 | existing.value.body = op.body | |
| 120 | existing.value.editedAt = op.timestamp || Date.now() | |
| 121 | existing.value.editedBy = op.userId | |
| 122 | await batch.put(key, existing.value) | |
| 123 | } | |
| 124 | break | |
| 125 | } | |
| 126 | ||
| 127 | case 'delete-comment': { | |
| 128 | await batch.del(`comment:${op.issueId}:${op.commentId}`) | |
| 129 | break | |
| 130 | } | |
| 131 | ||
| 132 | case 'create-label': { | |
| 133 | await batch.put(`label:${op.name}`, { | |
| 134 | color: op.color || '#ccc', | |
| 135 | description: op.description || '', | |
| 136 | displayName: op.displayName || op.name | |
| 137 | }) | |
| 138 | break | |
| 139 | } | |
| 140 | ||
| 141 | case 'update-label': { | |
| 142 | const labelEntry = await batch.get(`label:${op.name}`) | |
| 143 | if (labelEntry) { | |
| 144 | const label = labelEntry.value | |
| 145 | if (op.color !== undefined) label.color = op.color | |
| 146 | if (op.description !== undefined) label.description = op.description | |
| 147 | if (op.displayName !== undefined) label.displayName = op.displayName | |
| 148 | await batch.put(`label:${op.name}`, label) | |
| 149 | } | |
| 150 | break | |
| 151 | } | |
| 152 | ||
| 153 | case 'delete-label': { | |
| 154 | await batch.del(`label:${op.name}`) | |
| 155 | break | |
| 156 | } | |
| 157 | ||
| 158 | case 'update-ref': { | |
| 159 | await batch.put(`ref:${op.ref}`, { | |
| 160 | hash: op.hash, | |
| 161 | updatedBy: op.userId, | |
| 162 | timestamp: op.timestamp || Date.now() | |
| 163 | }) | |
| 164 | break | |
| 165 | } | |
| 166 | ||
| 167 | case 'delete-ref': { | |
| 168 | await batch.del(`ref:${op.ref}`) | |
| 169 | break | |
| 170 | } | |
| 171 | ||
| 172 | case 'set-meta': { | |
| 173 | await batch.put(`meta:${op.key}`, { value: op.value }) | |
| 174 | break | |
| 175 | } | |
| 176 | } | |
| 177 | } | |
| 178 | }) | |
| 179 | ||
| 180 | export const DEFAULT_LABELS = [ | |
| 181 | { name: 'bug', color: '#d73a4a', description: 'Something isn\'t working' }, | |
| 182 | { name: 'enhancement', color: '#a2eeef', description: 'New feature or request' }, | |
| 183 | { name: 'documentation', color: '#0075ca', description: 'Improvements or additions to documentation' }, | |
| 184 | { name: 'question', color: '#d876e3', description: 'Further information is requested' }, | |
| 185 | { name: 'help-wanted', color: '#008672', description: 'Extra attention is needed' }, | |
| 186 | { name: 'good-first-issue', color: '#7057ff', description: 'Good for newcomers' }, | |
| 187 | { name: 'duplicate', color: '#cfd3d7', description: 'This issue already exists' }, | |
| 188 | { name: 'wontfix', color: '#ffffff', description: 'This will not be worked on' } | |
| 189 | ] |
| … | … | @@ -0,0 +1,1428 @@ |
| 1 | { | |
| 2 | "name": "hyper-issues", | |
| 3 | "version": "0.2.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "hyper-issues", | |
| 9 | "version": "0.2.0", | |
| 10 | "dependencies": { | |
| 11 | "hyper-rooms": "file:../hyper-rooms" | |
| 12 | }, | |
| 13 | "devDependencies": { | |
| 14 | "autobase-test-helpers": "^2.0.0", | |
| 15 | "brittle": "^3.0.0", | |
| 16 | "corestore": "^7.0.0" | |
| 17 | } | |
| 18 | }, | |
| 19 | "../hyper-rooms": { | |
| 20 | "version": "0.1.0", | |
| 21 | "dependencies": { | |
| 22 | "autobase": "^7.20.0", | |
| 23 | "b4a": "^1.6.0", | |
| 24 | "hyperbee": "^2.20.0", | |
| 25 | "hypercore-crypto": "^3.4.0", | |
| 26 | "ready-resource": "^1.1.0" | |
| 27 | }, | |
| 28 | "devDependencies": { | |
| 29 | "autobase-test-helpers": "^2.0.0", | |
| 30 | "brittle": "^3.0.0", | |
| 31 | "corestore": "^7.0.0" | |
| 32 | } | |
| 33 | }, | |
| 34 | "node_modules/@hyperswarm/secret-stream": { | |
| 35 | "version": "6.9.1", | |
| 36 | "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz", | |
| 37 | "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==", | |
| 38 | "dev": true, | |
| 39 | "license": "Apache-2.0", | |
| 40 | "dependencies": { | |
| 41 | "b4a": "^1.1.0", | |
| 42 | "hypercore-crypto": "^3.3.1", | |
| 43 | "noise-curve-ed": "^2.0.1", | |
| 44 | "noise-handshake": "^4.0.0", | |
| 45 | "sodium-secretstream": "^1.1.0", | |
| 46 | "sodium-universal": "^5.0.0", | |
| 47 | "streamx": "^2.14.0", | |
| 48 | "timeout-refresh": "^2.0.0", | |
| 49 | "unslab": "^1.3.0" | |
| 50 | } | |
| 51 | }, | |
| 52 | "node_modules/@jridgewell/resolve-uri": { | |
| 53 | "version": "3.1.2", | |
| 54 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 55 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 56 | "dev": true, | |
| 57 | "license": "MIT", | |
| 58 | "engines": { | |
| 59 | "node": ">=6.0.0" | |
| 60 | } | |
| 61 | }, | |
| 62 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 63 | "version": "1.5.5", | |
| 64 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 65 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 66 | "dev": true, | |
| 67 | "license": "MIT" | |
| 68 | }, | |
| 69 | "node_modules/@jridgewell/trace-mapping": { | |
| 70 | "version": "0.3.31", | |
| 71 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 72 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 73 | "dev": true, | |
| 74 | "license": "MIT", | |
| 75 | "dependencies": { | |
| 76 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 77 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 78 | } | |
| 79 | }, | |
| 80 | "node_modules/@types/istanbul-lib-coverage": { | |
| 81 | "version": "2.0.6", | |
| 82 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 83 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 84 | "dev": true, | |
| 85 | "license": "MIT" | |
| 86 | }, | |
| 87 | "node_modules/autobase-test-helpers": { | |
| 88 | "version": "2.1.3", | |
| 89 | "resolved": "https://registry.npmjs.org/autobase-test-helpers/-/autobase-test-helpers-2.1.3.tgz", | |
| 90 | "integrity": "sha512-DYaBLakwMeWv32VcW0StyKZ6mQjA2HV8qRlnIx0XeM93/nw8l13DdiGXZf9EAB14nhKG5drHQIOQrJAiW5BOfg==", | |
| 91 | "dev": true, | |
| 92 | "license": "Apache-2.0", | |
| 93 | "dependencies": { | |
| 94 | "b4a": "^1.6.4" | |
| 95 | } | |
| 96 | }, | |
| 97 | "node_modules/b4a": { | |
| 98 | "version": "1.8.0", | |
| 99 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", | |
| 100 | "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", | |
| 101 | "dev": true, | |
| 102 | "license": "Apache-2.0", | |
| 103 | "peerDependencies": { | |
| 104 | "react-native-b4a": "*" | |
| 105 | }, | |
| 106 | "peerDependenciesMeta": { | |
| 107 | "react-native-b4a": { | |
| 108 | "optional": true | |
| 109 | } | |
| 110 | } | |
| 111 | }, | |
| 112 | "node_modules/bare-abort": { | |
| 113 | "version": "2.0.13", | |
| 114 | "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz", | |
| 115 | "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==", | |
| 116 | "dev": true, | |
| 117 | "license": "Apache-2.0" | |
| 118 | }, | |
| 119 | "node_modules/bare-addon-resolve": { | |
| 120 | "version": "1.10.0", | |
| 121 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 122 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 123 | "dev": true, | |
| 124 | "license": "Apache-2.0", | |
| 125 | "dependencies": { | |
| 126 | "bare-module-resolve": "^1.10.0", | |
| 127 | "bare-semver": "^1.0.0" | |
| 128 | }, | |
| 129 | "peerDependencies": { | |
| 130 | "bare-url": "*" | |
| 131 | }, | |
| 132 | "peerDependenciesMeta": { | |
| 133 | "bare-url": { | |
| 134 | "optional": true | |
| 135 | } | |
| 136 | } | |
| 137 | }, | |
| 138 | "node_modules/bare-ansi-escapes": { | |
| 139 | "version": "2.2.3", | |
| 140 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 141 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 142 | "dev": true, | |
| 143 | "license": "Apache-2.0", | |
| 144 | "dependencies": { | |
| 145 | "bare-stream": "^2.6.5" | |
| 146 | }, | |
| 147 | "peerDependencies": { | |
| 148 | "bare-buffer": "*" | |
| 149 | }, | |
| 150 | "peerDependenciesMeta": { | |
| 151 | "bare-buffer": { | |
| 152 | "optional": true | |
| 153 | } | |
| 154 | } | |
| 155 | }, | |
| 156 | "node_modules/bare-assert": { | |
| 157 | "version": "1.2.0", | |
| 158 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 159 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 160 | "dev": true, | |
| 161 | "license": "Apache-2.0", | |
| 162 | "dependencies": { | |
| 163 | "bare-inspect": "^3.1.2" | |
| 164 | } | |
| 165 | }, | |
| 166 | "node_modules/bare-bundle": { | |
| 167 | "version": "1.10.0", | |
| 168 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 169 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 170 | "dev": true, | |
| 171 | "license": "Apache-2.0", | |
| 172 | "peerDependencies": { | |
| 173 | "bare-buffer": "*", | |
| 174 | "bare-url": "*" | |
| 175 | }, | |
| 176 | "peerDependenciesMeta": { | |
| 177 | "bare-buffer": { | |
| 178 | "optional": true | |
| 179 | }, | |
| 180 | "bare-url": { | |
| 181 | "optional": true | |
| 182 | } | |
| 183 | } | |
| 184 | }, | |
| 185 | "node_modules/bare-cov": { | |
| 186 | "version": "1.2.1", | |
| 187 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 188 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 189 | "dev": true, | |
| 190 | "license": "Apache-2.0", | |
| 191 | "dependencies": { | |
| 192 | "bare-fs": "^4.1.2", | |
| 193 | "bare-inspector": "^4.0.1", | |
| 194 | "bare-path": "^3.0.0", | |
| 195 | "bare-process": "^4.2.1", | |
| 196 | "bare-url": "^2.1.5", | |
| 197 | "bare-v8-to-istanbul": "^1.0.2", | |
| 198 | "picomatch": "^4.0.2", | |
| 199 | "which-runtime": "^1.2.1" | |
| 200 | } | |
| 201 | }, | |
| 202 | "node_modules/bare-crypto": { | |
| 203 | "version": "1.13.4", | |
| 204 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.4.tgz", | |
| 205 | "integrity": "sha512-JiCZ5l2YOG1y8J7yy1BCAKTCZrPnHLb7pDRIdurBTOn5oIwBQDIv8iH5Pl2V85vzjl1NZXRfNY4HZLsE942jJA==", | |
| 206 | "dev": true, | |
| 207 | "license": "Apache-2.0", | |
| 208 | "dependencies": { | |
| 209 | "bare-assert": "^1.2.0", | |
| 210 | "bare-stream": "^2.6.3" | |
| 211 | }, | |
| 212 | "peerDependencies": { | |
| 213 | "bare-buffer": "*" | |
| 214 | }, | |
| 215 | "peerDependenciesMeta": { | |
| 216 | "bare-buffer": { | |
| 217 | "optional": true | |
| 218 | } | |
| 219 | } | |
| 220 | }, | |
| 221 | "node_modules/bare-debug-log": { | |
| 222 | "version": "2.0.0", | |
| 223 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 224 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 225 | "dev": true, | |
| 226 | "license": "Apache-2.0", | |
| 227 | "dependencies": { | |
| 228 | "bare-os": "^3.0.1" | |
| 229 | } | |
| 230 | }, | |
| 231 | "node_modules/bare-dns": { | |
| 232 | "version": "2.1.4", | |
| 233 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 234 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 235 | "dev": true, | |
| 236 | "license": "Apache-2.0", | |
| 237 | "engines": { | |
| 238 | "bare": ">=1.7.0" | |
| 239 | } | |
| 240 | }, | |
| 241 | "node_modules/bare-encoding": { | |
| 242 | "version": "1.0.3", | |
| 243 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 244 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 245 | "dev": true, | |
| 246 | "license": "Apache-2.0", | |
| 247 | "peerDependencies": { | |
| 248 | "bare-buffer": "*" | |
| 249 | }, | |
| 250 | "peerDependenciesMeta": { | |
| 251 | "bare-buffer": { | |
| 252 | "optional": true | |
| 253 | } | |
| 254 | } | |
| 255 | }, | |
| 256 | "node_modules/bare-env": { | |
| 257 | "version": "3.0.0", | |
| 258 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 259 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 260 | "dev": true, | |
| 261 | "license": "Apache-2.0", | |
| 262 | "dependencies": { | |
| 263 | "bare-os": "^3.0.1" | |
| 264 | } | |
| 265 | }, | |
| 266 | "node_modules/bare-events": { | |
| 267 | "version": "2.8.2", | |
| 268 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 269 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 270 | "dev": true, | |
| 271 | "license": "Apache-2.0", | |
| 272 | "peerDependencies": { | |
| 273 | "bare-abort-controller": "*" | |
| 274 | }, | |
| 275 | "peerDependenciesMeta": { | |
| 276 | "bare-abort-controller": { | |
| 277 | "optional": true | |
| 278 | } | |
| 279 | } | |
| 280 | }, | |
| 281 | "node_modules/bare-format": { | |
| 282 | "version": "1.0.2", | |
| 283 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz", | |
| 284 | "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==", | |
| 285 | "dev": true, | |
| 286 | "license": "Apache-2.0", | |
| 287 | "dependencies": { | |
| 288 | "bare-inspect": "^3.0.0" | |
| 289 | } | |
| 290 | }, | |
| 291 | "node_modules/bare-fs": { | |
| 292 | "version": "4.7.0", | |
| 293 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.0.tgz", | |
| 294 | "integrity": "sha512-xzqKsCFxAek9aezYhjJuJRXBIaYlg/0OGDTZp+T8eYmYMlm66cs6cYko02drIyjN2CBbi+I6L7YfXyqpqtKRXA==", | |
| 295 | "dev": true, | |
| 296 | "license": "Apache-2.0", | |
| 297 | "dependencies": { | |
| 298 | "bare-events": "^2.5.4", | |
| 299 | "bare-path": "^3.0.0", | |
| 300 | "bare-stream": "^2.6.4", | |
| 301 | "bare-url": "^2.2.2", | |
| 302 | "fast-fifo": "^1.3.2" | |
| 303 | }, | |
| 304 | "engines": { | |
| 305 | "bare": ">=1.16.0" | |
| 306 | }, | |
| 307 | "peerDependencies": { | |
| 308 | "bare-buffer": "*" | |
| 309 | }, | |
| 310 | "peerDependenciesMeta": { | |
| 311 | "bare-buffer": { | |
| 312 | "optional": true | |
| 313 | } | |
| 314 | } | |
| 315 | }, | |
| 316 | "node_modules/bare-hrtime": { | |
| 317 | "version": "2.1.1", | |
| 318 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 319 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 320 | "dev": true, | |
| 321 | "license": "Apache-2.0" | |
| 322 | }, | |
| 323 | "node_modules/bare-http-parser": { | |
| 324 | "version": "1.1.3", | |
| 325 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.3.tgz", | |
| 326 | "integrity": "sha512-+dhVvQi6brHq14L/XHNRQ+TLuVE76VjRmMt61wVEtS+Od8xUslfMHWJN/ZjIIt3RtTG6vPuA+x9cOh7KrkBJsA==", | |
| 327 | "dev": true, | |
| 328 | "license": "Apache-2.0" | |
| 329 | }, | |
| 330 | "node_modules/bare-http1": { | |
| 331 | "version": "4.5.6", | |
| 332 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.6.tgz", | |
| 333 | "integrity": "sha512-31OAwMkSU+z1VuUOCk65hx3aWQgzCfH/zQ6LGxbJtmiy2Czsw0+uvOBM9YkqaL6zUSTSYG2pLbL0v/TjME3Buw==", | |
| 334 | "dev": true, | |
| 335 | "license": "Apache-2.0", | |
| 336 | "dependencies": { | |
| 337 | "bare-events": "^2.6.0", | |
| 338 | "bare-http-parser": "^1.1.1", | |
| 339 | "bare-stream": "^2.10.0", | |
| 340 | "bare-tcp": "^2.2.0" | |
| 341 | }, | |
| 342 | "peerDependencies": { | |
| 343 | "bare-buffer": "*", | |
| 344 | "bare-url": "*" | |
| 345 | }, | |
| 346 | "peerDependenciesMeta": { | |
| 347 | "bare-buffer": { | |
| 348 | "optional": true | |
| 349 | }, | |
| 350 | "bare-url": { | |
| 351 | "optional": true | |
| 352 | } | |
| 353 | } | |
| 354 | }, | |
| 355 | "node_modules/bare-https": { | |
| 356 | "version": "2.1.3", | |
| 357 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.3.tgz", | |
| 358 | "integrity": "sha512-0TI/mJXQGYXmG7UUyWEG+KCJusayIAQLywUjFAskDoKuxfqVnGF+M/mTMrEV8J64DaIdU+5x761FvgmT7M68tA==", | |
| 359 | "dev": true, | |
| 360 | "license": "Apache-2.0", | |
| 361 | "dependencies": { | |
| 362 | "bare-http1": "^4.4.0", | |
| 363 | "bare-tcp": "^2.2.0", | |
| 364 | "bare-tls": "^2.0.0" | |
| 365 | } | |
| 366 | }, | |
| 367 | "node_modules/bare-inspect": { | |
| 368 | "version": "3.1.4", | |
| 369 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 370 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 371 | "dev": true, | |
| 372 | "license": "Apache-2.0", | |
| 373 | "dependencies": { | |
| 374 | "bare-ansi-escapes": "^2.1.0", | |
| 375 | "bare-type": "^1.0.0" | |
| 376 | }, | |
| 377 | "engines": { | |
| 378 | "bare": ">=1.18.0" | |
| 379 | } | |
| 380 | }, | |
| 381 | "node_modules/bare-inspector": { | |
| 382 | "version": "4.0.3", | |
| 383 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 384 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 385 | "dev": true, | |
| 386 | "license": "Apache-2.0", | |
| 387 | "dependencies": { | |
| 388 | "bare-events": "^2.1.0", | |
| 389 | "bare-http1": "^4.0.0", | |
| 390 | "bare-stream": "^2.0.0", | |
| 391 | "bare-url": "^2.0.0", | |
| 392 | "bare-ws": "^2.0.0" | |
| 393 | }, | |
| 394 | "engines": { | |
| 395 | "bare": ">=1.2.0" | |
| 396 | }, | |
| 397 | "peerDependencies": { | |
| 398 | "bare-tcp": "*" | |
| 399 | }, | |
| 400 | "peerDependenciesMeta": { | |
| 401 | "bare-tcp": { | |
| 402 | "optional": true | |
| 403 | } | |
| 404 | } | |
| 405 | }, | |
| 406 | "node_modules/bare-module": { | |
| 407 | "version": "6.1.3", | |
| 408 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 409 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 410 | "dev": true, | |
| 411 | "license": "Apache-2.0", | |
| 412 | "dependencies": { | |
| 413 | "bare-bundle": "^1.3.0", | |
| 414 | "bare-module-lexer": "^1.0.0", | |
| 415 | "bare-module-resolve": "^1.8.0", | |
| 416 | "bare-path": "^3.0.0", | |
| 417 | "bare-url": "^2.0.1" | |
| 418 | }, | |
| 419 | "engines": { | |
| 420 | "bare": ">=1.23.0" | |
| 421 | }, | |
| 422 | "peerDependencies": { | |
| 423 | "bare-buffer": "*" | |
| 424 | }, | |
| 425 | "peerDependenciesMeta": { | |
| 426 | "bare-buffer": { | |
| 427 | "optional": true | |
| 428 | } | |
| 429 | } | |
| 430 | }, | |
| 431 | "node_modules/bare-module-lexer": { | |
| 432 | "version": "1.4.7", | |
| 433 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 434 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 435 | "dev": true, | |
| 436 | "license": "Apache-2.0", | |
| 437 | "dependencies": { | |
| 438 | "require-addon": "^1.0.2" | |
| 439 | }, | |
| 440 | "peerDependencies": { | |
| 441 | "bare-buffer": "*" | |
| 442 | }, | |
| 443 | "peerDependenciesMeta": { | |
| 444 | "bare-buffer": { | |
| 445 | "optional": true | |
| 446 | } | |
| 447 | } | |
| 448 | }, | |
| 449 | "node_modules/bare-module-resolve": { | |
| 450 | "version": "1.12.1", | |
| 451 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 452 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 453 | "dev": true, | |
| 454 | "license": "Apache-2.0", | |
| 455 | "dependencies": { | |
| 456 | "bare-semver": "^1.0.0" | |
| 457 | }, | |
| 458 | "peerDependencies": { | |
| 459 | "bare-url": "*" | |
| 460 | }, | |
| 461 | "peerDependenciesMeta": { | |
| 462 | "bare-url": { | |
| 463 | "optional": true | |
| 464 | } | |
| 465 | } | |
| 466 | }, | |
| 467 | "node_modules/bare-net": { | |
| 468 | "version": "2.3.1", | |
| 469 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.1.tgz", | |
| 470 | "integrity": "sha512-MypSqDKpDU2Xt7FIfazn5yGvRnV09gFcIPHGWstW0gxuzA4tucTcwJSZeos97C4F89vtU5oGwXDN/HrGN6Y4Jw==", | |
| 471 | "dev": true, | |
| 472 | "license": "Apache-2.0", | |
| 473 | "dependencies": { | |
| 474 | "bare-events": "^2.2.2", | |
| 475 | "bare-pipe": "^4.0.0", | |
| 476 | "bare-stream": "^2.0.0", | |
| 477 | "bare-tcp": "^2.0.0" | |
| 478 | } | |
| 479 | }, | |
| 480 | "node_modules/bare-os": { | |
| 481 | "version": "3.8.7", | |
| 482 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz", | |
| 483 | "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==", | |
| 484 | "dev": true, | |
| 485 | "license": "Apache-2.0", | |
| 486 | "engines": { | |
| 487 | "bare": ">=1.14.0" | |
| 488 | } | |
| 489 | }, | |
| 490 | "node_modules/bare-path": { | |
| 491 | "version": "3.0.0", | |
| 492 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 493 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 494 | "dev": true, | |
| 495 | "license": "Apache-2.0", | |
| 496 | "dependencies": { | |
| 497 | "bare-os": "^3.0.1" | |
| 498 | } | |
| 499 | }, | |
| 500 | "node_modules/bare-pipe": { | |
| 501 | "version": "4.1.5", | |
| 502 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.5.tgz", | |
| 503 | "integrity": "sha512-6OfxaG8JSkRh3Gc4hzHRsxNt+yu2PpN7lrv1V+T78GdknWQkVGwiEvu4m+1nbfk8cMVQ0TGxRvQ90XA4rhnTuw==", | |
| 504 | "dev": true, | |
| 505 | "license": "Apache-2.0", | |
| 506 | "dependencies": { | |
| 507 | "bare-events": "^2.0.0", | |
| 508 | "bare-stream": "^2.0.0" | |
| 509 | }, | |
| 510 | "engines": { | |
| 511 | "bare": ">=1.16.0" | |
| 512 | } | |
| 513 | }, | |
| 514 | "node_modules/bare-process": { | |
| 515 | "version": "4.4.1", | |
| 516 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.4.1.tgz", | |
| 517 | "integrity": "sha512-JfcTtymq6akqM2bdyTsP4A4GYJceBzb91k/ECmFps75uFf6uO33SM1bOZsRAqyRXExabsQJLn5S41NBl8HTM4A==", | |
| 518 | "dev": true, | |
| 519 | "license": "Apache-2.0", | |
| 520 | "dependencies": { | |
| 521 | "bare-abort": "^2.0.13", | |
| 522 | "bare-env": "^3.0.0", | |
| 523 | "bare-events": "^2.3.1", | |
| 524 | "bare-hrtime": "^2.0.0", | |
| 525 | "bare-os": "^3.7.1", | |
| 526 | "bare-signals": "^4.0.0", | |
| 527 | "bare-stdio": "^1.0.1" | |
| 528 | } | |
| 529 | }, | |
| 530 | "node_modules/bare-semver": { | |
| 531 | "version": "1.0.3", | |
| 532 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.3.tgz", | |
| 533 | "integrity": "sha512-HS/A30bi2+PiRJfU6R4+Kp+6KeLSCSByjYM2iiobOKzLAvtu1CT+S8xWfiU7wz0erknjkUoC+yXy108tzIuP5Q==", | |
| 534 | "dev": true, | |
| 535 | "license": "Apache-2.0" | |
| 536 | }, | |
| 537 | "node_modules/bare-signals": { | |
| 538 | "version": "4.2.0", | |
| 539 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 540 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 541 | "dev": true, | |
| 542 | "license": "Apache-2.0", | |
| 543 | "dependencies": { | |
| 544 | "bare-events": "^2.5.3", | |
| 545 | "bare-os": "^3.3.1" | |
| 546 | }, | |
| 547 | "engines": { | |
| 548 | "bare": ">=1.7.0" | |
| 549 | } | |
| 550 | }, | |
| 551 | "node_modules/bare-stdio": { | |
| 552 | "version": "1.0.2", | |
| 553 | "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.2.tgz", | |
| 554 | "integrity": "sha512-3WJDqtvVGP4f+j68kyEC05umOYNwKJ1xG+YAXL8yZ605WgNqiRhVaFq+mVIhBt2eKNp7pa5vCQdhOt1pNh79SA==", | |
| 555 | "dev": true, | |
| 556 | "license": "Apache-2.0", | |
| 557 | "dependencies": { | |
| 558 | "bare-fs": "^4.5.2", | |
| 559 | "bare-pipe": "^4.1.5", | |
| 560 | "bare-tty": "^5.0.3" | |
| 561 | } | |
| 562 | }, | |
| 563 | "node_modules/bare-stream": { | |
| 564 | "version": "2.13.0", | |
| 565 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.0.tgz", | |
| 566 | "integrity": "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==", | |
| 567 | "dev": true, | |
| 568 | "license": "Apache-2.0", | |
| 569 | "dependencies": { | |
| 570 | "streamx": "^2.25.0", | |
| 571 | "teex": "^1.0.1" | |
| 572 | }, | |
| 573 | "peerDependencies": { | |
| 574 | "bare-abort-controller": "*", | |
| 575 | "bare-buffer": "*", | |
| 576 | "bare-events": "*" | |
| 577 | }, | |
| 578 | "peerDependenciesMeta": { | |
| 579 | "bare-abort-controller": { | |
| 580 | "optional": true | |
| 581 | }, | |
| 582 | "bare-buffer": { | |
| 583 | "optional": true | |
| 584 | }, | |
| 585 | "bare-events": { | |
| 586 | "optional": true | |
| 587 | } | |
| 588 | } | |
| 589 | }, | |
| 590 | "node_modules/bare-stylize": { | |
| 591 | "version": "0.0.1", | |
| 592 | "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz", | |
| 593 | "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==", | |
| 594 | "dev": true, | |
| 595 | "license": "Apache-2.0", | |
| 596 | "dependencies": { | |
| 597 | "bare-ansi-escapes": "^2.2.3", | |
| 598 | "bare-process": "^4.2.1" | |
| 599 | } | |
| 600 | }, | |
| 601 | "node_modules/bare-subprocess": { | |
| 602 | "version": "5.2.3", | |
| 603 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz", | |
| 604 | "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==", | |
| 605 | "dev": true, | |
| 606 | "license": "Apache-2.0", | |
| 607 | "dependencies": { | |
| 608 | "bare-env": "^3.0.0", | |
| 609 | "bare-events": "^2.5.4", | |
| 610 | "bare-os": "^3.0.1", | |
| 611 | "bare-pipe": "^4.0.0", | |
| 612 | "bare-url": "^2.2.2" | |
| 613 | }, | |
| 614 | "engines": { | |
| 615 | "bare": ">=1.7.0" | |
| 616 | }, | |
| 617 | "peerDependencies": { | |
| 618 | "bare-buffer": "*" | |
| 619 | }, | |
| 620 | "peerDependenciesMeta": { | |
| 621 | "bare-buffer": { | |
| 622 | "optional": true | |
| 623 | } | |
| 624 | } | |
| 625 | }, | |
| 626 | "node_modules/bare-tcp": { | |
| 627 | "version": "2.2.7", | |
| 628 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.7.tgz", | |
| 629 | "integrity": "sha512-rjpqNQ2cOCkNo3NeYA/W4GTK3DRkl8sDHO3uos+AEswUjLC8XXMQF8WrJCSjlIowCbS6NUVxKE92X5RGXjyefg==", | |
| 630 | "dev": true, | |
| 631 | "license": "Apache-2.0", | |
| 632 | "dependencies": { | |
| 633 | "bare-dns": "^2.0.4", | |
| 634 | "bare-events": "^2.5.4", | |
| 635 | "bare-stream": "^2.6.4" | |
| 636 | }, | |
| 637 | "engines": { | |
| 638 | "bare": ">=1.16.0" | |
| 639 | } | |
| 640 | }, | |
| 641 | "node_modules/bare-tls": { | |
| 642 | "version": "2.2.3", | |
| 643 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.2.3.tgz", | |
| 644 | "integrity": "sha512-dPYBGEXtgLceRFMfGaF2/rqR86/xMxMyrM9ootO/gaRKL/z2uNHJs7aP7IOBtnJF8eUCt5qwMzbKfrKgDIxPLg==", | |
| 645 | "dev": true, | |
| 646 | "license": "Apache-2.0", | |
| 647 | "dependencies": { | |
| 648 | "bare-net": "^2.0.1", | |
| 649 | "bare-stream": "^2.6.4" | |
| 650 | }, | |
| 651 | "engines": { | |
| 652 | "bare": ">=1.7.0" | |
| 653 | } | |
| 654 | }, | |
| 655 | "node_modules/bare-tty": { | |
| 656 | "version": "5.1.0", | |
| 657 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.0.tgz", | |
| 658 | "integrity": "sha512-EZLvW4A+XiJgI3TW+e1pMME9PIJsfEXe/DA/WSKzIkq/v7Yarpv/rvG6Z5pGnpo4V/Bd+qopwnCLSR71hMMBYA==", | |
| 659 | "dev": true, | |
| 660 | "license": "Apache-2.0", | |
| 661 | "dependencies": { | |
| 662 | "bare-events": "^2.2.0", | |
| 663 | "bare-signals": "^4.0.0", | |
| 664 | "bare-stream": "^2.0.0" | |
| 665 | }, | |
| 666 | "engines": { | |
| 667 | "bare": ">=1.16.0" | |
| 668 | } | |
| 669 | }, | |
| 670 | "node_modules/bare-type": { | |
| 671 | "version": "1.1.0", | |
| 672 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 673 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 674 | "dev": true, | |
| 675 | "license": "Apache-2.0", | |
| 676 | "engines": { | |
| 677 | "bare": ">=1.2.0" | |
| 678 | } | |
| 679 | }, | |
| 680 | "node_modules/bare-url": { | |
| 681 | "version": "2.4.0", | |
| 682 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", | |
| 683 | "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", | |
| 684 | "dev": true, | |
| 685 | "license": "Apache-2.0", | |
| 686 | "peer": true, | |
| 687 | "dependencies": { | |
| 688 | "bare-path": "^3.0.0" | |
| 689 | } | |
| 690 | }, | |
| 691 | "node_modules/bare-utils": { | |
| 692 | "version": "1.6.0", | |
| 693 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz", | |
| 694 | "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==", | |
| 695 | "dev": true, | |
| 696 | "license": "Apache-2.0", | |
| 697 | "dependencies": { | |
| 698 | "bare-debug-log": "^2.0.0", | |
| 699 | "bare-encoding": "^1.0.0", | |
| 700 | "bare-format": "^1.0.0", | |
| 701 | "bare-inspect": "^3.0.0", | |
| 702 | "bare-stylize": "^0.0.1", | |
| 703 | "bare-type": "^1.0.6" | |
| 704 | } | |
| 705 | }, | |
| 706 | "node_modules/bare-v8-to-istanbul": { | |
| 707 | "version": "1.0.2", | |
| 708 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 709 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 710 | "dev": true, | |
| 711 | "license": "Apache-2.0", | |
| 712 | "dependencies": { | |
| 713 | "bare-assert": "^1.0.2", | |
| 714 | "bare-fs": "^4.1.2", | |
| 715 | "bare-module": "^6.1.2", | |
| 716 | "bare-path": "^3.0.0", | |
| 717 | "bare-process": "^4.2.0", | |
| 718 | "bare-url": "^2.1.5", | |
| 719 | "bare-utils": "^1.2.0", | |
| 720 | "v8-to-istanbul": "^9.3.0", | |
| 721 | "which-runtime": "^1.2.1" | |
| 722 | } | |
| 723 | }, | |
| 724 | "node_modules/bare-ws": { | |
| 725 | "version": "2.1.0", | |
| 726 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.1.0.tgz", | |
| 727 | "integrity": "sha512-2gEWlPK9iyBchACdIY6oQXgmDz3KLrChdwrPgmU3IVXOFTnxTqSUT27WE/+izd4QojHj/SsqDkQiD2HDvuTdAA==", | |
| 728 | "dev": true, | |
| 729 | "license": "Apache-2.0", | |
| 730 | "dependencies": { | |
| 731 | "bare-crypto": "^1.2.0", | |
| 732 | "bare-events": "^2.3.1", | |
| 733 | "bare-http1": "^4.0.0", | |
| 734 | "bare-https": "^2.0.0", | |
| 735 | "bare-stream": "^2.1.2" | |
| 736 | }, | |
| 737 | "peerDependencies": { | |
| 738 | "bare-buffer": "*", | |
| 739 | "bare-url": "*" | |
| 740 | }, | |
| 741 | "peerDependenciesMeta": { | |
| 742 | "bare-buffer": { | |
| 743 | "optional": true | |
| 744 | }, | |
| 745 | "bare-url": { | |
| 746 | "optional": true | |
| 747 | } | |
| 748 | } | |
| 749 | }, | |
| 750 | "node_modules/big-sparse-array": { | |
| 751 | "version": "1.0.3", | |
| 752 | "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz", | |
| 753 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", | |
| 754 | "dev": true, | |
| 755 | "license": "MIT" | |
| 756 | }, | |
| 757 | "node_modules/brittle": { | |
| 758 | "version": "3.19.1", | |
| 759 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 760 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 761 | "dev": true, | |
| 762 | "license": "Apache-2.0", | |
| 763 | "dependencies": { | |
| 764 | "b4a": "^1.6.0", | |
| 765 | "bare-assert": "^1.0.2", | |
| 766 | "bare-cov": "^1.1.0", | |
| 767 | "bare-fs": "^4.1.6", | |
| 768 | "bare-os": "^3.6.1", | |
| 769 | "bare-path": "^3.0.0", | |
| 770 | "bare-process": "^4.2.1", | |
| 771 | "bare-subprocess": "^5.0.0", | |
| 772 | "bare-url": "^2.1.6", | |
| 773 | "error-stack-parser": "^2.1.4", | |
| 774 | "globbie": "^1.0.2", | |
| 775 | "paparam": "^1.6.2", | |
| 776 | "same-object": "^1.0.2", | |
| 777 | "test-tmp": "^1.4.0", | |
| 778 | "tmatch": "^5.0.0" | |
| 779 | }, | |
| 780 | "bin": { | |
| 781 | "brittle": "bin/node.js", | |
| 782 | "brittle-bare": "bin/bare.js", | |
| 783 | "brittle-node": "bin/node.js", | |
| 784 | "brittle-pear": "bin/pear.js" | |
| 785 | } | |
| 786 | }, | |
| 787 | "node_modules/compact-encoding": { | |
| 788 | "version": "2.19.2", | |
| 789 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.19.2.tgz", | |
| 790 | "integrity": "sha512-/YjhHQE/5L4F7l5Bht69dRbP9RV6zoJPeowi8bMKQxNKe3Nh6hOY8pBGoVE9fz5GaWfEd8fWJ2aU9sB4KZuMYg==", | |
| 791 | "dev": true, | |
| 792 | "license": "Apache-2.0", | |
| 793 | "dependencies": { | |
| 794 | "b4a": "^1.3.0" | |
| 795 | } | |
| 796 | }, | |
| 797 | "node_modules/convert-source-map": { | |
| 798 | "version": "2.0.0", | |
| 799 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 800 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 801 | "dev": true, | |
| 802 | "license": "MIT" | |
| 803 | }, | |
| 804 | "node_modules/corestore": { | |
| 805 | "version": "7.9.2", | |
| 806 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.9.2.tgz", | |
| 807 | "integrity": "sha512-dyIktVSVLu0skZ1UM4yODbNbZ46dBpTHypzwlwET1btVuS3YIf66Zz8Tx8h3Xa8nbLO2USGqrYtvPYzSRj2wCA==", | |
| 808 | "dev": true, | |
| 809 | "license": "MIT", | |
| 810 | "dependencies": { | |
| 811 | "b4a": "^1.6.7", | |
| 812 | "hypercore": "^11.19.0", | |
| 813 | "hypercore-crypto": "^3.4.2", | |
| 814 | "hypercore-errors": "^1.4.0", | |
| 815 | "hypercore-id-encoding": "^1.3.0", | |
| 816 | "ready-resource": "^1.1.1", | |
| 817 | "sodium-universal": "^5.0.1", | |
| 818 | "which-runtime": "^1.2.1" | |
| 819 | } | |
| 820 | }, | |
| 821 | "node_modules/device-file": { | |
| 822 | "version": "2.3.1", | |
| 823 | "resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz", | |
| 824 | "integrity": "sha512-bmON44lwxJPle9N2OcH4tqM44pMGZKT8G6OkzXkz0urvqQ9LKkoQdTS6w0ztYfmLdUE27R4UUmx0+y2gEz4Jug==", | |
| 825 | "dev": true, | |
| 826 | "license": "Apache-2.0", | |
| 827 | "dependencies": { | |
| 828 | "b4a": "^1.6.7", | |
| 829 | "bare-fs": "^4.0.1", | |
| 830 | "bare-path": "^3.0.0", | |
| 831 | "fd-lock": "^2.1.0", | |
| 832 | "fs-native-extensions": "^1.4.0", | |
| 833 | "ready-resource": "^1.2.0" | |
| 834 | } | |
| 835 | }, | |
| 836 | "node_modules/error-stack-parser": { | |
| 837 | "version": "2.1.4", | |
| 838 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 839 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 840 | "dev": true, | |
| 841 | "license": "MIT", | |
| 842 | "dependencies": { | |
| 843 | "stackframe": "^1.3.4" | |
| 844 | } | |
| 845 | }, | |
| 846 | "node_modules/events-universal": { | |
| 847 | "version": "1.0.1", | |
| 848 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 849 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 850 | "dev": true, | |
| 851 | "license": "Apache-2.0", | |
| 852 | "dependencies": { | |
| 853 | "bare-events": "^2.7.0" | |
| 854 | } | |
| 855 | }, | |
| 856 | "node_modules/fast-fifo": { | |
| 857 | "version": "1.3.2", | |
| 858 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 859 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 860 | "dev": true, | |
| 861 | "license": "MIT" | |
| 862 | }, | |
| 863 | "node_modules/fd-lock": { | |
| 864 | "version": "2.1.1", | |
| 865 | "resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-2.1.1.tgz", | |
| 866 | "integrity": "sha512-H3VkcWFl39Rk0xBokDcUyBcVs6VrYTUo/DhDWMzJOF99wXWDAvmvq/GlLTS2NTehsznZhp3fXVyrtB2ldDXhwg==", | |
| 867 | "dev": true, | |
| 868 | "license": "Apache-2.0", | |
| 869 | "dependencies": { | |
| 870 | "bare-fs": "^4.5.0", | |
| 871 | "fs-native-extensions": "^1.4.4", | |
| 872 | "ready-resource": "^1.2.0", | |
| 873 | "resource-on-exit": "^1.0.0" | |
| 874 | } | |
| 875 | }, | |
| 876 | "node_modules/flat-tree": { | |
| 877 | "version": "1.13.0", | |
| 878 | "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz", | |
| 879 | "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==", | |
| 880 | "dev": true, | |
| 881 | "license": "MIT" | |
| 882 | }, | |
| 883 | "node_modules/fs-native-extensions": { | |
| 884 | "version": "1.4.5", | |
| 885 | "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz", | |
| 886 | "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==", | |
| 887 | "dev": true, | |
| 888 | "license": "Apache-2.0", | |
| 889 | "dependencies": { | |
| 890 | "require-addon": "^1.1.0", | |
| 891 | "which-runtime": "^1.2.0" | |
| 892 | } | |
| 893 | }, | |
| 894 | "node_modules/generate-object-property": { | |
| 895 | "version": "2.0.0", | |
| 896 | "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-2.0.0.tgz", | |
| 897 | "integrity": "sha512-KwuURPyqn2Mz8DdV29pJwQu0Y7tcsbkULr82eeOcY/ZllFK6I9Wm8dsRByIu7CKWlFi9BdW1b3mcXMp/kQBQsw==", | |
| 898 | "dev": true, | |
| 899 | "license": "MIT", | |
| 900 | "dependencies": { | |
| 901 | "is-property": "^1.0.0" | |
| 902 | } | |
| 903 | }, | |
| 904 | "node_modules/generate-string": { | |
| 905 | "version": "1.0.1", | |
| 906 | "resolved": "https://registry.npmjs.org/generate-string/-/generate-string-1.0.1.tgz", | |
| 907 | "integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==", | |
| 908 | "dev": true, | |
| 909 | "license": "MIT" | |
| 910 | }, | |
| 911 | "node_modules/globbie": { | |
| 912 | "version": "1.0.3", | |
| 913 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 914 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 915 | "dev": true, | |
| 916 | "license": "Apache-2.0", | |
| 917 | "dependencies": { | |
| 918 | "bare-fs": "^4.1.2", | |
| 919 | "bare-path": "^3.0.0", | |
| 920 | "bare-process": "^4.2.1", | |
| 921 | "picomatch": "^4.0.2" | |
| 922 | } | |
| 923 | }, | |
| 924 | "node_modules/hyper-rooms": { | |
| 925 | "resolved": "../hyper-rooms", | |
| 926 | "link": true | |
| 927 | }, | |
| 928 | "node_modules/hypercore": { | |
| 929 | "version": "11.28.1", | |
| 930 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.28.1.tgz", | |
| 931 | "integrity": "sha512-d8jqUilG7Gri8wT+sRdkFTYRPMHGyvBz/NCYHf5rX7+LoivRNRxQm5nWChT7caiUKbcsDak6h7IiEQ5wbD1wCg==", | |
| 932 | "dev": true, | |
| 933 | "license": "MIT", | |
| 934 | "dependencies": { | |
| 935 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 936 | "b4a": "^1.1.0", | |
| 937 | "bare-events": "^2.2.0", | |
| 938 | "big-sparse-array": "^1.0.3", | |
| 939 | "compact-encoding": "^2.11.0", | |
| 940 | "fast-fifo": "^1.3.0", | |
| 941 | "flat-tree": "^1.9.0", | |
| 942 | "hypercore-crypto": "^3.2.1", | |
| 943 | "hypercore-errors": "^1.5.0", | |
| 944 | "hypercore-id-encoding": "^1.2.0", | |
| 945 | "hypercore-storage": "^2.8.0", | |
| 946 | "is-options": "^1.0.1", | |
| 947 | "nanoassert": "^2.0.0", | |
| 948 | "protomux": "^3.5.0", | |
| 949 | "quickbit-universal": "^2.2.0", | |
| 950 | "random-array-iterator": "^1.0.0", | |
| 951 | "safety-catch": "^1.0.1", | |
| 952 | "sodium-universal": "^5.0.1", | |
| 953 | "streamx": "^2.12.4", | |
| 954 | "unslab": "^1.3.0", | |
| 955 | "z32": "^1.0.0" | |
| 956 | } | |
| 957 | }, | |
| 958 | "node_modules/hypercore-crypto": { | |
| 959 | "version": "3.6.1", | |
| 960 | "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", | |
| 961 | "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", | |
| 962 | "dev": true, | |
| 963 | "license": "MIT", | |
| 964 | "dependencies": { | |
| 965 | "b4a": "^1.6.6", | |
| 966 | "compact-encoding": "^2.15.0", | |
| 967 | "sodium-universal": "^5.0.0" | |
| 968 | } | |
| 969 | }, | |
| 970 | "node_modules/hypercore-errors": { | |
| 971 | "version": "1.5.0", | |
| 972 | "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz", | |
| 973 | "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==", | |
| 974 | "dev": true, | |
| 975 | "license": "Apache-2.0", | |
| 976 | "dependencies": { | |
| 977 | "hypercore-id-encoding": "^1.3.0" | |
| 978 | } | |
| 979 | }, | |
| 980 | "node_modules/hypercore-id-encoding": { | |
| 981 | "version": "1.3.0", | |
| 982 | "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz", | |
| 983 | "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==", | |
| 984 | "dev": true, | |
| 985 | "license": "Apache-2.0", | |
| 986 | "dependencies": { | |
| 987 | "b4a": "^1.5.3", | |
| 988 | "z32": "^1.0.0" | |
| 989 | } | |
| 990 | }, | |
| 991 | "node_modules/hypercore-storage": { | |
| 992 | "version": "2.8.0", | |
| 993 | "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-2.8.0.tgz", | |
| 994 | "integrity": "sha512-s35HllFMYvrkSHvkIIklVFmCI/bRVNO7H3pi4mKvWl0XLyhGGGM+NtJ7fpsh82oPRCmL9XtMRO+pT6LiMAO8MA==", | |
| 995 | "dev": true, | |
| 996 | "license": "Apache-2.0", | |
| 997 | "dependencies": { | |
| 998 | "b4a": "^1.6.7", | |
| 999 | "bare-fs": "^4.0.1", | |
| 1000 | "bare-path": "^3.0.0", | |
| 1001 | "compact-encoding": "^2.16.0", | |
| 1002 | "device-file": "^2.1.2", | |
| 1003 | "flat-tree": "^1.12.1", | |
| 1004 | "hypercore-crypto": "^3.4.2", | |
| 1005 | "hyperschema": "^1.7.0", | |
| 1006 | "index-encoder": "^3.3.2", | |
| 1007 | "resolve-reject-promise": "^1.0.0", | |
| 1008 | "rocksdb-native": "^3.11.0", | |
| 1009 | "scope-lock": "^1.2.4", | |
| 1010 | "streamx": "^2.21.1" | |
| 1011 | } | |
| 1012 | }, | |
| 1013 | "node_modules/hyperschema": { | |
| 1014 | "version": "1.20.1", | |
| 1015 | "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.20.1.tgz", | |
| 1016 | "integrity": "sha512-7gnvaTNUs8FqdrU3NNE6nzhOneGyDlUlpROu9YFlHA61fE8ppKKf2gLZjZjsx/enNt+VFN4ITxy8ijypfyWBLw==", | |
| 1017 | "dev": true, | |
| 1018 | "license": "Apache-2.0", | |
| 1019 | "dependencies": { | |
| 1020 | "bare-fs": "^4.0.1", | |
| 1021 | "compact-encoding": "^2.19.0", | |
| 1022 | "generate-object-property": "^2.0.0", | |
| 1023 | "generate-string": "^1.0.1" | |
| 1024 | } | |
| 1025 | }, | |
| 1026 | "node_modules/index-encoder": { | |
| 1027 | "version": "3.5.0", | |
| 1028 | "resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz", | |
| 1029 | "integrity": "sha512-idZ1cxtZz2dRV6rUiaP9Xo99UjXbSzjcMacoQmxUMu/A7fEQcNPngvwDJYeWelQUS5XFlY71/or70lKn6XnwbQ==", | |
| 1030 | "dev": true, | |
| 1031 | "license": "Apache-2.0", | |
| 1032 | "dependencies": { | |
| 1033 | "b4a": "^1.6.4" | |
| 1034 | } | |
| 1035 | }, | |
| 1036 | "node_modules/is-options": { | |
| 1037 | "version": "1.0.2", | |
| 1038 | "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz", | |
| 1039 | "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==", | |
| 1040 | "dev": true, | |
| 1041 | "license": "MIT", | |
| 1042 | "dependencies": { | |
| 1043 | "b4a": "^1.1.1" | |
| 1044 | } | |
| 1045 | }, | |
| 1046 | "node_modules/is-property": { | |
| 1047 | "version": "1.0.2", | |
| 1048 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", | |
| 1049 | "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", | |
| 1050 | "dev": true, | |
| 1051 | "license": "MIT" | |
| 1052 | }, | |
| 1053 | "node_modules/nanoassert": { | |
| 1054 | "version": "2.0.0", | |
| 1055 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", | |
| 1056 | "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", | |
| 1057 | "dev": true, | |
| 1058 | "license": "ISC" | |
| 1059 | }, | |
| 1060 | "node_modules/noise-curve-ed": { | |
| 1061 | "version": "2.1.0", | |
| 1062 | "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz", | |
| 1063 | "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==", | |
| 1064 | "dev": true, | |
| 1065 | "license": "ISC", | |
| 1066 | "dependencies": { | |
| 1067 | "b4a": "^1.1.0", | |
| 1068 | "nanoassert": "^2.0.0", | |
| 1069 | "sodium-universal": "^5.0.0" | |
| 1070 | } | |
| 1071 | }, | |
| 1072 | "node_modules/noise-handshake": { | |
| 1073 | "version": "4.2.0", | |
| 1074 | "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz", | |
| 1075 | "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==", | |
| 1076 | "dev": true, | |
| 1077 | "license": "Apache-2.0", | |
| 1078 | "dependencies": { | |
| 1079 | "b4a": "^1.1.0", | |
| 1080 | "nanoassert": "^2.0.0", | |
| 1081 | "sodium-universal": "^5.0.0" | |
| 1082 | } | |
| 1083 | }, | |
| 1084 | "node_modules/paparam": { | |
| 1085 | "version": "1.10.1", | |
| 1086 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.1.tgz", | |
| 1087 | "integrity": "sha512-viyQI64VIja0Za3njIzhoEP8ZVkgPowhZPuG0E96NwBfYJ6ZIyrrlhWGtFPkdN7eYLl2L8CTWL+wla0evm1KKQ==", | |
| 1088 | "dev": true, | |
| 1089 | "license": "Apache-2.0" | |
| 1090 | }, | |
| 1091 | "node_modules/picomatch": { | |
| 1092 | "version": "4.0.4", | |
| 1093 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", | |
| 1094 | "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", | |
| 1095 | "dev": true, | |
| 1096 | "license": "MIT", | |
| 1097 | "engines": { | |
| 1098 | "node": ">=12" | |
| 1099 | }, | |
| 1100 | "funding": { | |
| 1101 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1102 | } | |
| 1103 | }, | |
| 1104 | "node_modules/protomux": { | |
| 1105 | "version": "3.10.1", | |
| 1106 | "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz", | |
| 1107 | "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==", | |
| 1108 | "dev": true, | |
| 1109 | "license": "MIT", | |
| 1110 | "dependencies": { | |
| 1111 | "b4a": "^1.3.1", | |
| 1112 | "compact-encoding": "^2.5.1", | |
| 1113 | "queue-tick": "^1.0.0", | |
| 1114 | "safety-catch": "^1.0.1", | |
| 1115 | "unslab": "^1.3.0" | |
| 1116 | } | |
| 1117 | }, | |
| 1118 | "node_modules/queue-tick": { | |
| 1119 | "version": "1.0.1", | |
| 1120 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", | |
| 1121 | "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", | |
| 1122 | "dev": true, | |
| 1123 | "license": "MIT" | |
| 1124 | }, | |
| 1125 | "node_modules/quickbit-native": { | |
| 1126 | "version": "2.4.8", | |
| 1127 | "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz", | |
| 1128 | "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==", | |
| 1129 | "dev": true, | |
| 1130 | "license": "Apache-2.0", | |
| 1131 | "optional": true, | |
| 1132 | "dependencies": { | |
| 1133 | "require-addon": "^1.1.0" | |
| 1134 | } | |
| 1135 | }, | |
| 1136 | "node_modules/quickbit-universal": { | |
| 1137 | "version": "2.2.0", | |
| 1138 | "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz", | |
| 1139 | "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==", | |
| 1140 | "dev": true, | |
| 1141 | "license": "ISC", | |
| 1142 | "dependencies": { | |
| 1143 | "b4a": "^1.6.0", | |
| 1144 | "simdle-universal": "^1.1.0" | |
| 1145 | }, | |
| 1146 | "optionalDependencies": { | |
| 1147 | "quickbit-native": "^2.2.0" | |
| 1148 | } | |
| 1149 | }, | |
| 1150 | "node_modules/random-array-iterator": { | |
| 1151 | "version": "1.0.0", | |
| 1152 | "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz", | |
| 1153 | "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==", | |
| 1154 | "dev": true, | |
| 1155 | "license": "MIT" | |
| 1156 | }, | |
| 1157 | "node_modules/ready-resource": { | |
| 1158 | "version": "1.2.0", | |
| 1159 | "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz", | |
| 1160 | "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==", | |
| 1161 | "dev": true, | |
| 1162 | "license": "MIT", | |
| 1163 | "dependencies": { | |
| 1164 | "bare-events": "^2.2.0" | |
| 1165 | } | |
| 1166 | }, | |
| 1167 | "node_modules/refcounter": { | |
| 1168 | "version": "1.0.0", | |
| 1169 | "resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz", | |
| 1170 | "integrity": "sha512-1WosVzUy0kPUaPMEtlNDwm99UsteALIhXXR8rerELoa63WkYIXAl0hxgwPFrIYBRWZPGUyekQ04FRtPJ7dHk9w==", | |
| 1171 | "dev": true, | |
| 1172 | "license": "Apache-2.0" | |
| 1173 | }, | |
| 1174 | "node_modules/require-addon": { | |
| 1175 | "version": "1.2.0", | |
| 1176 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1177 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1178 | "dev": true, | |
| 1179 | "license": "Apache-2.0", | |
| 1180 | "dependencies": { | |
| 1181 | "bare-addon-resolve": "^1.3.0" | |
| 1182 | }, | |
| 1183 | "engines": { | |
| 1184 | "bare": ">=1.10.0" | |
| 1185 | } | |
| 1186 | }, | |
| 1187 | "node_modules/resolve-reject-promise": { | |
| 1188 | "version": "1.1.0", | |
| 1189 | "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz", | |
| 1190 | "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==", | |
| 1191 | "dev": true, | |
| 1192 | "license": "MIT" | |
| 1193 | }, | |
| 1194 | "node_modules/resource-on-exit": { | |
| 1195 | "version": "1.0.0", | |
| 1196 | "resolved": "https://registry.npmjs.org/resource-on-exit/-/resource-on-exit-1.0.0.tgz", | |
| 1197 | "integrity": "sha512-ViJwJAknCkLRJRPR+9SISQQ7R5eRgtdIHLJsM2hHx1MweAJbJxJ5XnMjjq0Lc7ZGv44ufzAqds1nKxiVkdy4ag==", | |
| 1198 | "dev": true, | |
| 1199 | "license": "Apache-2.0" | |
| 1200 | }, | |
| 1201 | "node_modules/rocksdb-native": { | |
| 1202 | "version": "3.15.0", | |
| 1203 | "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.15.0.tgz", | |
| 1204 | "integrity": "sha512-czYx0hOzKAy+HYmJROZm7thvJ1tHFWHNkGI3nomumXWzrJghJdrWo4MIhehGX4OTff0lnOCoB2zu6ekzIKY0rQ==", | |
| 1205 | "dev": true, | |
| 1206 | "license": "Apache-2.0", | |
| 1207 | "dependencies": { | |
| 1208 | "compact-encoding": "^2.15.0", | |
| 1209 | "ready-resource": "^1.0.0", | |
| 1210 | "refcounter": "^1.0.0", | |
| 1211 | "require-addon": "^1.0.2", | |
| 1212 | "resolve-reject-promise": "^1.1.0", | |
| 1213 | "signal-promise": "^1.0.3", | |
| 1214 | "streamx": "^2.16.1" | |
| 1215 | }, | |
| 1216 | "engines": { | |
| 1217 | "bare": ">=1.16.0" | |
| 1218 | } | |
| 1219 | }, | |
| 1220 | "node_modules/safety-catch": { | |
| 1221 | "version": "1.0.3", | |
| 1222 | "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.3.tgz", | |
| 1223 | "integrity": "sha512-Zq+J1TefpoEq/HTUabo0YXX5MNvttjWYODGohgPBO2jfko8Wqx3JYMgE823szDFVamdH5PlpByvfiWScTdSYDA==", | |
| 1224 | "dev": true, | |
| 1225 | "license": "MIT" | |
| 1226 | }, | |
| 1227 | "node_modules/same-object": { | |
| 1228 | "version": "1.0.2", | |
| 1229 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1230 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1231 | "dev": true, | |
| 1232 | "license": "MIT" | |
| 1233 | }, | |
| 1234 | "node_modules/scope-lock": { | |
| 1235 | "version": "1.2.4", | |
| 1236 | "resolved": "https://registry.npmjs.org/scope-lock/-/scope-lock-1.2.4.tgz", | |
| 1237 | "integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==", | |
| 1238 | "dev": true, | |
| 1239 | "license": "Apache-2.0" | |
| 1240 | }, | |
| 1241 | "node_modules/signal-promise": { | |
| 1242 | "version": "1.0.3", | |
| 1243 | "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz", | |
| 1244 | "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==", | |
| 1245 | "dev": true, | |
| 1246 | "license": "MIT" | |
| 1247 | }, | |
| 1248 | "node_modules/simdle-native": { | |
| 1249 | "version": "1.3.9", | |
| 1250 | "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz", | |
| 1251 | "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==", | |
| 1252 | "dev": true, | |
| 1253 | "license": "Apache-2.0", | |
| 1254 | "optional": true, | |
| 1255 | "dependencies": { | |
| 1256 | "b4a": "^1.6.0", | |
| 1257 | "require-addon": "^1.1.0" | |
| 1258 | } | |
| 1259 | }, | |
| 1260 | "node_modules/simdle-universal": { | |
| 1261 | "version": "1.1.2", | |
| 1262 | "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz", | |
| 1263 | "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==", | |
| 1264 | "dev": true, | |
| 1265 | "license": "ISC", | |
| 1266 | "dependencies": { | |
| 1267 | "b4a": "^1.6.0" | |
| 1268 | }, | |
| 1269 | "optionalDependencies": { | |
| 1270 | "simdle-native": "^1.1.1" | |
| 1271 | } | |
| 1272 | }, | |
| 1273 | "node_modules/sodium-native": { | |
| 1274 | "version": "5.1.0", | |
| 1275 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz", | |
| 1276 | "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==", | |
| 1277 | "dev": true, | |
| 1278 | "license": "MIT", | |
| 1279 | "dependencies": { | |
| 1280 | "bare-assert": "^1.2.0", | |
| 1281 | "require-addon": "^1.1.0", | |
| 1282 | "which-runtime": "^1.2.1" | |
| 1283 | }, | |
| 1284 | "engines": { | |
| 1285 | "bare": ">=1.16.0" | |
| 1286 | } | |
| 1287 | }, | |
| 1288 | "node_modules/sodium-secretstream": { | |
| 1289 | "version": "1.2.0", | |
| 1290 | "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", | |
| 1291 | "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", | |
| 1292 | "dev": true, | |
| 1293 | "license": "MIT", | |
| 1294 | "dependencies": { | |
| 1295 | "b4a": "^1.1.1", | |
| 1296 | "sodium-universal": "^5.0.0" | |
| 1297 | } | |
| 1298 | }, | |
| 1299 | "node_modules/sodium-universal": { | |
| 1300 | "version": "5.0.1", | |
| 1301 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1302 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1303 | "dev": true, | |
| 1304 | "license": "MIT", | |
| 1305 | "dependencies": { | |
| 1306 | "sodium-native": "^5.0.1" | |
| 1307 | }, | |
| 1308 | "peerDependencies": { | |
| 1309 | "sodium-javascript": "~0.8.0" | |
| 1310 | }, | |
| 1311 | "peerDependenciesMeta": { | |
| 1312 | "sodium-javascript": { | |
| 1313 | "optional": true | |
| 1314 | } | |
| 1315 | } | |
| 1316 | }, | |
| 1317 | "node_modules/stackframe": { | |
| 1318 | "version": "1.3.4", | |
| 1319 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1320 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1321 | "dev": true, | |
| 1322 | "license": "MIT" | |
| 1323 | }, | |
| 1324 | "node_modules/streamx": { | |
| 1325 | "version": "2.25.0", | |
| 1326 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", | |
| 1327 | "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", | |
| 1328 | "dev": true, | |
| 1329 | "license": "MIT", | |
| 1330 | "dependencies": { | |
| 1331 | "events-universal": "^1.0.0", | |
| 1332 | "fast-fifo": "^1.3.2", | |
| 1333 | "text-decoder": "^1.1.0" | |
| 1334 | } | |
| 1335 | }, | |
| 1336 | "node_modules/teex": { | |
| 1337 | "version": "1.0.1", | |
| 1338 | "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", | |
| 1339 | "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", | |
| 1340 | "dev": true, | |
| 1341 | "license": "MIT", | |
| 1342 | "dependencies": { | |
| 1343 | "streamx": "^2.12.5" | |
| 1344 | } | |
| 1345 | }, | |
| 1346 | "node_modules/test-tmp": { | |
| 1347 | "version": "1.4.0", | |
| 1348 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1349 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1350 | "dev": true, | |
| 1351 | "license": "MIT", | |
| 1352 | "dependencies": { | |
| 1353 | "bare-fs": "^4.0.1", | |
| 1354 | "bare-os": "^3.3.0", | |
| 1355 | "bare-path": "^3.0.0" | |
| 1356 | } | |
| 1357 | }, | |
| 1358 | "node_modules/text-decoder": { | |
| 1359 | "version": "1.2.7", | |
| 1360 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", | |
| 1361 | "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", | |
| 1362 | "dev": true, | |
| 1363 | "license": "Apache-2.0", | |
| 1364 | "dependencies": { | |
| 1365 | "b4a": "^1.6.4" | |
| 1366 | } | |
| 1367 | }, | |
| 1368 | "node_modules/timeout-refresh": { | |
| 1369 | "version": "2.0.1", | |
| 1370 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz", | |
| 1371 | "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", | |
| 1372 | "dev": true, | |
| 1373 | "license": "MIT" | |
| 1374 | }, | |
| 1375 | "node_modules/tmatch": { | |
| 1376 | "version": "5.0.0", | |
| 1377 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1378 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1379 | "dev": true, | |
| 1380 | "license": "ISC", | |
| 1381 | "engines": { | |
| 1382 | "node": ">=8" | |
| 1383 | } | |
| 1384 | }, | |
| 1385 | "node_modules/unslab": { | |
| 1386 | "version": "1.3.0", | |
| 1387 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", | |
| 1388 | "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==", | |
| 1389 | "dev": true, | |
| 1390 | "license": "Apache-2.0", | |
| 1391 | "dependencies": { | |
| 1392 | "b4a": "^1.6.6" | |
| 1393 | } | |
| 1394 | }, | |
| 1395 | "node_modules/v8-to-istanbul": { | |
| 1396 | "version": "9.3.0", | |
| 1397 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1398 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1399 | "dev": true, | |
| 1400 | "license": "ISC", | |
| 1401 | "dependencies": { | |
| 1402 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1403 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1404 | "convert-source-map": "^2.0.0" | |
| 1405 | }, | |
| 1406 | "engines": { | |
| 1407 | "node": ">=10.12.0" | |
| 1408 | } | |
| 1409 | }, | |
| 1410 | "node_modules/which-runtime": { | |
| 1411 | "version": "1.3.2", | |
| 1412 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 1413 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 1414 | "dev": true, | |
| 1415 | "license": "Apache-2.0" | |
| 1416 | }, | |
| 1417 | "node_modules/z32": { | |
| 1418 | "version": "1.1.0", | |
| 1419 | "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz", | |
| 1420 | "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==", | |
| 1421 | "dev": true, | |
| 1422 | "license": "MIT", | |
| 1423 | "dependencies": { | |
| 1424 | "b4a": "^1.5.3" | |
| 1425 | } | |
| 1426 | } | |
| 1427 | } | |
| 1428 | } |
| … | … | @@ -0,0 +1,18 @@ |
| 1 | { | |
| 2 | "name": "hyper-issues", | |
| 3 | "version": "0.2.0", | |
| 4 | "description": "P2P issue tracking — a hyper-rooms schema for issues, comments, labels, refs", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "scripts": { | |
| 8 | "test": "brittle test/*.test.js" | |
| 9 | }, | |
| 10 | "dependencies": { | |
| 11 | "hyper-rooms": "file:../hyper-rooms" | |
| 12 | }, | |
| 13 | "devDependencies": { | |
| 14 | "autobase-test-helpers": "^2.0.0", | |
| 15 | "brittle": "^3.0.0", | |
| 16 | "corestore": "^7.0.0" | |
| 17 | } | |
| 18 | } |
| … | … | @@ -0,0 +1,316 @@ |
| 1 | import test from 'brittle' | |
| 2 | import Corestore from 'corestore' | |
| 3 | import { mkdtempSync } from 'fs' | |
| 4 | import { join } from 'path' | |
| 5 | import { tmpdir } from 'os' | |
| 6 | import { replicateAndSync } from 'autobase-test-helpers' | |
| 7 | import { HyperIssues } from '../index.js' | |
| 8 | ||
| 9 | function tmpStore () { | |
| 10 | const dir = mkdtempSync(join(tmpdir(), 'hyper-issues-test-')) | |
| 11 | return new Corestore(dir) | |
| 12 | } | |
| 13 | ||
| 14 | async function makeTracker (t, namespace = 'test') { | |
| 15 | const store = tmpStore() | |
| 16 | const tracker = new HyperIssues(store, { namespace }) | |
| 17 | await tracker.ready() | |
| 18 | t.teardown(async () => { | |
| 19 | try { await tracker.close() } catch {} | |
| 20 | try { await store.close() } catch {} | |
| 21 | }) | |
| 22 | return tracker | |
| 23 | } | |
| 24 | ||
| 25 | test('create and retrieve issue', async (t) => { | |
| 26 | const tracker = await makeTracker(t, 'test-1') | |
| 27 | ||
| 28 | const id = await tracker.create({ | |
| 29 | title: 'First bug', | |
| 30 | body: 'Something is broken', | |
| 31 | author: 'user-1' | |
| 32 | }) | |
| 33 | ||
| 34 | t.is(id, 1) | |
| 35 | ||
| 36 | const issue = await tracker.get(id) | |
| 37 | t.is(issue.title, 'First bug') | |
| 38 | t.is(issue.body, 'Something is broken') | |
| 39 | t.is(issue.author, 'user-1') | |
| 40 | t.is(issue.status, 'open') | |
| 41 | }) | |
| 42 | ||
| 43 | test('update issue status', async (t) => { | |
| 44 | const tracker = await makeTracker(t, 'test-2') | |
| 45 | ||
| 46 | const id = await tracker.create({ title: 'Bug', author: 'user-1' }) | |
| 47 | await tracker.update(id, { status: 'closed' }, 'user-1') | |
| 48 | ||
| 49 | const issue = await tracker.get(id) | |
| 50 | t.is(issue.status, 'closed') | |
| 51 | }) | |
| 52 | ||
| 53 | test('list issues with filters', async (t) => { | |
| 54 | const tracker = await makeTracker(t, 'test-3') | |
| 55 | ||
| 56 | await tracker.create({ title: 'Open bug', author: 'user-1' }) | |
| 57 | await tracker.create({ title: 'Another open', author: 'user-2' }) | |
| 58 | const closedId = await tracker.create({ title: 'Closed one', author: 'user-1' }) | |
| 59 | await tracker.update(closedId, { status: 'closed' }, 'user-1') | |
| 60 | ||
| 61 | const open = await tracker.list({ status: 'open' }) | |
| 62 | t.is(open.length, 2) | |
| 63 | ||
| 64 | const closed = await tracker.list({ status: 'closed' }) | |
| 65 | t.is(closed.length, 1) | |
| 66 | ||
| 67 | const all = await tracker.list() | |
| 68 | t.is(all.length, 3) | |
| 69 | ||
| 70 | const byAuthor = await tracker.list({ author: 'user-1' }) | |
| 71 | t.is(byAuthor.length, 2) | |
| 72 | ||
| 73 | const searched = await tracker.list({ search: 'another' }) | |
| 74 | t.is(searched.length, 1) | |
| 75 | }) | |
| 76 | ||
| 77 | test('comments', async (t) => { | |
| 78 | const tracker = await makeTracker(t, 'test-4') | |
| 79 | ||
| 80 | const id = await tracker.create({ title: 'Bug', author: 'user-1' }) | |
| 81 | ||
| 82 | const commentId = await tracker.comment(id, { | |
| 83 | body: 'I can reproduce this', | |
| 84 | author: 'user-2' | |
| 85 | }) | |
| 86 | ||
| 87 | t.ok(commentId) | |
| 88 | ||
| 89 | const comments = await tracker.getComments(id) | |
| 90 | t.is(comments.length, 1) | |
| 91 | t.is(comments[0].body, 'I can reproduce this') | |
| 92 | t.is(comments[0].author, 'user-2') | |
| 93 | }) | |
| 94 | ||
| 95 | test('edit and delete comments', async (t) => { | |
| 96 | const tracker = await makeTracker(t, 'test-4b') | |
| 97 | ||
| 98 | const id = await tracker.create({ title: 'Bug', author: 'user-1' }) | |
| 99 | const cId = await tracker.comment(id, { body: 'first', author: 'user-2' }) | |
| 100 | ||
| 101 | await tracker.editComment(id, cId, 'edited', 'user-2') | |
| 102 | let comments = await tracker.getComments(id) | |
| 103 | t.is(comments[0].body, 'edited') | |
| 104 | t.ok(comments[0].editedAt) | |
| 105 | t.is(comments[0].editedBy, 'user-2') | |
| 106 | ||
| 107 | await tracker.deleteComment(id, cId, 'user-2') | |
| 108 | comments = await tracker.getComments(id) | |
| 109 | t.is(comments.length, 0) | |
| 110 | }) | |
| 111 | ||
| 112 | test('labels', async (t) => { | |
| 113 | const tracker = await makeTracker(t, 'test-5') | |
| 114 | ||
| 115 | await tracker.createLabel('bug', '#ff0000', 'Something broken', 'Bug') | |
| 116 | await tracker.createLabel('feature', '#00ff00', 'New feature', 'Feature') | |
| 117 | ||
| 118 | const labels = await tracker.listLabels() | |
| 119 | t.is(labels.length, 2) | |
| 120 | ||
| 121 | const bug = await tracker.getLabel('bug') | |
| 122 | t.is(bug.color, '#ff0000') | |
| 123 | t.is(bug.displayName, 'Bug') | |
| 124 | ||
| 125 | await tracker.updateLabel('bug', { color: '#aa0000' }) | |
| 126 | const updated = await tracker.getLabel('bug') | |
| 127 | t.is(updated.color, '#aa0000') | |
| 128 | ||
| 129 | await tracker.deleteLabel('feature') | |
| 130 | const after = await tracker.listLabels() | |
| 131 | t.is(after.length, 1) | |
| 132 | }) | |
| 133 | ||
| 134 | test('issue counts', async (t) => { | |
| 135 | const tracker = await makeTracker(t, 'test-6') | |
| 136 | ||
| 137 | await tracker.create({ title: 'one', author: 'user-1' }) | |
| 138 | await tracker.create({ title: 'two', author: 'user-1' }) | |
| 139 | const id3 = await tracker.create({ title: 'three', author: 'user-1' }) | |
| 140 | await tracker.update(id3, { status: 'closed' }, 'user-1') | |
| 141 | ||
| 142 | const counts = await tracker.counts() | |
| 143 | t.is(counts.open, 2) | |
| 144 | t.is(counts.closed, 1) | |
| 145 | t.is(counts.total, 3) | |
| 146 | }) | |
| 147 | ||
| 148 | test('auto-incrementing IDs', async (t) => { | |
| 149 | const tracker = await makeTracker(t, 'test-7') | |
| 150 | ||
| 151 | const id1 = await tracker.create({ title: 'a', author: 'u' }) | |
| 152 | const id2 = await tracker.create({ title: 'b', author: 'u' }) | |
| 153 | const id3 = await tracker.create({ title: 'c', author: 'u' }) | |
| 154 | ||
| 155 | t.is(id1, 1) | |
| 156 | t.is(id2, 2) | |
| 157 | t.is(id3, 3) | |
| 158 | }) | |
| 159 | ||
| 160 | test('refs (optional git feature)', async (t) => { | |
| 161 | const tracker = await makeTracker(t, 'test-8') | |
| 162 | ||
| 163 | await tracker.updateRef('refs/heads/main', 'abc123', 'user-1') | |
| 164 | await tracker.updateRef('refs/heads/dev', 'def456', 'user-1') | |
| 165 | ||
| 166 | const main = await tracker.getRef('refs/heads/main') | |
| 167 | t.is(main.hash, 'abc123') | |
| 168 | ||
| 169 | const refs = await tracker.listRefs() | |
| 170 | t.is(refs.length, 2) | |
| 171 | ||
| 172 | await tracker.deleteRef('refs/heads/dev', 'user-1') | |
| 173 | const after = await tracker.listRefs() | |
| 174 | t.is(after.length, 1) | |
| 175 | }) | |
| 176 | ||
| 177 | test('meta key-value storage', async (t) => { | |
| 178 | const tracker = await makeTracker(t, 'test-9') | |
| 179 | ||
| 180 | await tracker.setMeta('description', 'My project') | |
| 181 | await tracker.setMeta('homepage', 'https://example.com') | |
| 182 | ||
| 183 | t.is(await tracker.getMeta('description'), 'My project') | |
| 184 | t.is(await tracker.getMeta('homepage'), 'https://example.com') | |
| 185 | t.is(await tracker.getMeta('missing'), null) | |
| 186 | }) | |
| 187 | ||
| 188 | test('key properties', async (t) => { | |
| 189 | const tracker = await makeTracker(t, 'test-10') | |
| 190 | ||
| 191 | t.ok(tracker.key, 'has key') | |
| 192 | t.is(typeof tracker.keyHex, 'string') | |
| 193 | t.is(tracker.keyHex.length, 64, 'keyHex is 32 bytes hex') | |
| 194 | t.ok(tracker.localKey, 'has localKey') | |
| 195 | t.is(tracker.writable, true, 'first writer is writable') | |
| 196 | }) | |
| 197 | ||
| 198 | test('add labels to an issue', async (t) => { | |
| 199 | const tracker = await makeTracker(t, 'test-11') | |
| 200 | ||
| 201 | await tracker.createLabel('bug', '#ff0000') | |
| 202 | await tracker.createLabel('urgent', '#ffaa00') | |
| 203 | ||
| 204 | const id = await tracker.create({ title: 'Bug', author: 'user-1' }) | |
| 205 | await tracker.addLabels(id, ['bug', 'urgent'], 'user-1') | |
| 206 | ||
| 207 | const issue = await tracker.get(id) | |
| 208 | t.alike(issue.labels.sort(), ['bug', 'urgent']) | |
| 209 | ||
| 210 | await tracker.removeLabels(id, ['urgent'], 'user-1') | |
| 211 | const after = await tracker.get(id) | |
| 212 | t.alike(after.labels, ['bug']) | |
| 213 | }) | |
| 214 | ||
| 215 | test('initializeDefaultLabels', async (t) => { | |
| 216 | const tracker = await makeTracker(t, 'test-12') | |
| 217 | ||
| 218 | const labels = await tracker.initializeDefaultLabels() | |
| 219 | t.ok(labels.length > 0, 'creates default labels') | |
| 220 | t.ok(labels.find(l => l.name === 'bug')) | |
| 221 | t.ok(labels.find(l => l.name === 'enhancement')) | |
| 222 | ||
| 223 | // Calling again should not duplicate | |
| 224 | await tracker.initializeDefaultLabels() | |
| 225 | const second = await tracker.listLabels() | |
| 226 | t.is(second.length, labels.length) | |
| 227 | }) | |
| 228 | ||
| 229 | // ── Multi-writer replication ────────────────────────────────── | |
| 230 | ||
| 231 | test('two trackers replicate via autobase', async (t) => { | |
| 232 | const storeA = tmpStore() | |
| 233 | const storeB = tmpStore() | |
| 234 | ||
| 235 | const trackerA = new HyperIssues(storeA, { namespace: 'shared' }) | |
| 236 | await trackerA.ready() | |
| 237 | ||
| 238 | // A creates an issue and a label | |
| 239 | await trackerA.createLabel('bug', '#ff0000') | |
| 240 | await trackerA.create({ title: 'shared bug', author: 'alice' }) | |
| 241 | await trackerA.base.update() | |
| 242 | ||
| 243 | const trackerB = new HyperIssues(storeB, { | |
| 244 | namespace: 'shared', | |
| 245 | bootstrapKey: trackerA.keyHex | |
| 246 | }) | |
| 247 | await trackerB.ready() | |
| 248 | ||
| 249 | await replicateAndSync([trackerA.base, trackerB.base]) | |
| 250 | ||
| 251 | // B should see A's data | |
| 252 | const issue = await trackerB.get(1) | |
| 253 | t.ok(issue, 'B received the issue') | |
| 254 | t.is(issue.title, 'shared bug') | |
| 255 | ||
| 256 | const labels = await trackerB.listLabels() | |
| 257 | t.is(labels.length, 1) | |
| 258 | t.is(labels[0].name, 'bug') | |
| 259 | ||
| 260 | t.teardown(async () => { | |
| 261 | try { await trackerA.close() } catch {} | |
| 262 | try { await trackerB.close() } catch {} | |
| 263 | try { await storeA.close() } catch {} | |
| 264 | try { await storeB.close() } catch {} | |
| 265 | }) | |
| 266 | }) | |
| 267 | ||
| 268 | test('two writers can both append after addWriter', async (t) => { | |
| 269 | const storeA = tmpStore() | |
| 270 | const storeB = tmpStore() | |
| 271 | ||
| 272 | const trackerA = new HyperIssues(storeA, { namespace: 'shared2' }) | |
| 273 | await trackerA.ready() | |
| 274 | ||
| 275 | const trackerB = new HyperIssues(storeB, { | |
| 276 | namespace: 'shared2', | |
| 277 | bootstrapKey: trackerA.keyHex | |
| 278 | }) | |
| 279 | await trackerB.ready() | |
| 280 | ||
| 281 | // Initial sync so B knows the room structure | |
| 282 | await replicateAndSync([trackerA.base, trackerB.base]) | |
| 283 | ||
| 284 | // A authorizes B as a writer | |
| 285 | await trackerA.addWriter('bob', { writerKey: trackerB.localKeyHex, role: 'writer' }) | |
| 286 | await replicateAndSync([trackerA.base, trackerB.base]) | |
| 287 | ||
| 288 | t.ok(trackerB.writable, 'B is now writable') | |
| 289 | ||
| 290 | // Alice creates first, sync, then Bob creates. | |
| 291 | // (Concurrent create() from two writers picks the same auto-increment ID | |
| 292 | // because each writer reads its local counter independently. This is a | |
| 293 | // known limitation of auto-increment in multiwriter — to avoid the | |
| 294 | // collision, sync between creates so each peer sees the latest counter. | |
| 295 | // For collision-free concurrent IDs, use UUID-style IDs in the schema.) | |
| 296 | await trackerA.create({ title: 'from alice', author: 'alice' }) | |
| 297 | await replicateAndSync([trackerA.base, trackerB.base]) | |
| 298 | await trackerB.create({ title: 'from bob', author: 'bob' }) | |
| 299 | ||
| 300 | await replicateAndSync([trackerA.base, trackerB.base]) | |
| 301 | ||
| 302 | // Both should see both issues | |
| 303 | const allA = await trackerA.list({ sort: 'oldest' }) | |
| 304 | const allB = await trackerB.list({ sort: 'oldest' }) | |
| 305 | ||
| 306 | t.is(allA.length, 2, 'A sees both') | |
| 307 | t.is(allB.length, 2, 'B sees both') | |
| 308 | t.alike(allA.map(i => i.title).sort(), ['from alice', 'from bob']) | |
| 309 | ||
| 310 | t.teardown(async () => { | |
| 311 | try { await trackerA.close() } catch {} | |
| 312 | try { await trackerB.close() } catch {} | |
| 313 | try { await storeA.close() } catch {} | |
| 314 | try { await storeB.close() } catch {} | |
| 315 | }) | |
| 316 | }) |