dsh-sessions-manager 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +12 -1
- package/README.md +12 -1
- package/lib/client.js +147 -32
- package/lib/client.js.map +3 -3
- package/lib/index.js +299 -24
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
- package/src/client/index.jsx +96 -34
- package/src/client/logic.js +59 -0
- package/src/index.js +77 -0
- package/src/markdown.js +175 -0
- package/src/star-index.js +109 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Durable "starred sessions" index (schema v3).
|
|
2
|
+
//
|
|
3
|
+
// Deliberately mirrors the recycle-bin index in src/index.js: version field,
|
|
4
|
+
// automatic upgrade of older shapes, atomic write (tmp + rename) and a single
|
|
5
|
+
// chained mutation queue so two concurrent requests can never clobber each
|
|
6
|
+
// other. Extracted from the host bundle so it can be unit-tested directly —
|
|
7
|
+
// pass `dir` to point the index at a temp directory.
|
|
8
|
+
import { mkdir, rename, writeFile } from 'node:fs/promises'
|
|
9
|
+
import { readFileSync } from 'node:fs'
|
|
10
|
+
import { homedir } from 'node:os'
|
|
11
|
+
import { join } from 'node:path'
|
|
12
|
+
|
|
13
|
+
// v3 is the first star schema; it starts at 3 so it can never be confused with
|
|
14
|
+
// the recycle bin's v1/v2 documents even if a file is copied between them.
|
|
15
|
+
export const STAR_SCHEMA_VERSION = 3
|
|
16
|
+
|
|
17
|
+
const DEFAULT_STAR_DIR = join(homedir(), '.dsh', 'sessions-manager')
|
|
18
|
+
|
|
19
|
+
function isSafeSessionId(value) {
|
|
20
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 200 && !/[\\/\0]/.test(value) && value !== '.' && value !== '..'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Coerce anything on disk (or nothing at all) into a valid v3 store.
|
|
25
|
+
* Accepts a bare array of ids (the pre-schema shape) and upgrades it.
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeStarStore(raw) {
|
|
28
|
+
const legacy = Array.isArray(raw) ? raw : null
|
|
29
|
+
const source = legacy || (raw && typeof raw === 'object' ? raw : null)
|
|
30
|
+
const ids = source && Array.isArray(source.starredSessionIds) ? source.starredSessionIds : (legacy || [])
|
|
31
|
+
const clean = []
|
|
32
|
+
const seen = new Set()
|
|
33
|
+
for (const id of ids) {
|
|
34
|
+
// Strings only: silently coercing a number into an id would let junk into
|
|
35
|
+
// the index and mask a caller bug.
|
|
36
|
+
if (!isSafeSessionId(id)) continue
|
|
37
|
+
if (seen.has(id)) continue
|
|
38
|
+
seen.add(id)
|
|
39
|
+
clean.push(id)
|
|
40
|
+
}
|
|
41
|
+
return { schemaVersion: STAR_SCHEMA_VERSION, starredSessionIds: clean }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Open the star index.
|
|
46
|
+
* @param {object} [options]
|
|
47
|
+
* @param {string} [options.dir] - Directory holding the index (tests inject a temp dir).
|
|
48
|
+
* @param {string} [options.indexPath] - Full index path, overriding `dir`.
|
|
49
|
+
*/
|
|
50
|
+
export function createStarIndex(options = {}) {
|
|
51
|
+
const dir = options.dir || process.env.DSH_SESSIONS_MANAGER_STAR_DIR || DEFAULT_STAR_DIR
|
|
52
|
+
const indexPath = options.indexPath || join(dir, 'star.json')
|
|
53
|
+
let mutation = Promise.resolve()
|
|
54
|
+
|
|
55
|
+
async function read() {
|
|
56
|
+
try {
|
|
57
|
+
return normalizeStarStore(JSON.parse(readFileSync(indexPath, 'utf8')))
|
|
58
|
+
} catch {
|
|
59
|
+
return normalizeStarStore(null)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function write(store) {
|
|
64
|
+
await mkdir(dir, { recursive: true })
|
|
65
|
+
const tmp = join(dir, `.star-${process.pid}-${Date.now()}.tmp`)
|
|
66
|
+
await writeFile(tmp, JSON.stringify(normalizeStarStore(store), null, 2), { encoding: 'utf8', mode: 0o600 })
|
|
67
|
+
await rename(tmp, indexPath)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Serialize read-modify-write cycles: every mutator sees the store as left by
|
|
71
|
+
// the previous one, and a rejected mutator still keeps the chain alive.
|
|
72
|
+
function mutate(mutator) {
|
|
73
|
+
const operation = mutation.then(async () => {
|
|
74
|
+
const store = await read()
|
|
75
|
+
const result = await mutator(store)
|
|
76
|
+
await write(store)
|
|
77
|
+
return result
|
|
78
|
+
})
|
|
79
|
+
mutation = operation.catch(() => {})
|
|
80
|
+
return operation
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Star or unstar sessions.
|
|
85
|
+
* @param {string[]} ids - Session ids to change.
|
|
86
|
+
* @param {boolean} starred - true to star, false to unstar.
|
|
87
|
+
* @returns {Promise<string[]>} The full starred set after the change.
|
|
88
|
+
*/
|
|
89
|
+
function setStarred(ids, starred) {
|
|
90
|
+
const wanted = (Array.isArray(ids) ? ids : []).filter(isSafeSessionId).map(String)
|
|
91
|
+
return mutate((store) => {
|
|
92
|
+
const set = new Set(store.starredSessionIds)
|
|
93
|
+
for (const id of wanted) {
|
|
94
|
+
if (starred) set.add(id)
|
|
95
|
+
else set.delete(id)
|
|
96
|
+
}
|
|
97
|
+
store.starredSessionIds = [...set]
|
|
98
|
+
return store.starredSessionIds
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Drop ids once their session is gone (purged / deleted), otherwise the index
|
|
103
|
+
// would grow forever with ids that can never be listed again.
|
|
104
|
+
function removeIds(ids) {
|
|
105
|
+
return setStarred(ids, false)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { read, write, mutate, setStarred, removeIds, indexPath, dir }
|
|
109
|
+
}
|