@mblaney/holster 2.1.0 → 2.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblaney/holster",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Holster is a service that allows synchronising data between devices using Node, Deno, Bun or the browser.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -14,17 +14,20 @@
14
14
  "type-check": "tsc -p tsconfig.json",
15
15
  "type-check:tests": "tsc -p tsconfig.test.json"
16
16
  },
17
+ "bin": {
18
+ "holster-user-storage": "utils/user-storage.js"
19
+ },
17
20
  "files": [
18
21
  "src",
19
22
  "ts/src",
20
23
  "build",
21
- "dist"
24
+ "dist",
25
+ "utils"
22
26
  ],
23
27
  "keywords": [],
24
28
  "author": "Malcolm Blaney",
25
29
  "license": "Zlib, MIT, Apache-2.0",
26
30
  "dependencies": {
27
- "express": "^5.1.0",
28
31
  "fs": "^0.0.1-security",
29
32
  "ws": "^8.18.0",
30
33
  "zod": "^4.1.13"
@@ -33,6 +36,7 @@
33
36
  "@types/node": "^24.10.1",
34
37
  "@types/ws": "^8.18.1",
35
38
  "esbuild": "0.25.3",
39
+ "express": "^5.1.0",
36
40
  "mock-socket": "^9.3.1",
37
41
  "prettier": "^3.4.2",
38
42
  "tsx": "^4.0.0",
package/src/wire.js CHANGED
@@ -297,19 +297,23 @@ const Wire = opt => {
297
297
  `[HOLSTER] Per-user storage limits enabled (${userCount} user${userCount !== 1 ? "s" : ""}, default: ${defLimitStr})`,
298
298
  )
299
299
  const sorted = [...userStorage.entries()].sort(([, a], [, b]) => b - a)
300
- const formatUser = pub => {
300
+ const logUser = pub => {
301
301
  const total = userStorage.get(pub) ?? 0
302
302
  const limit = userLimits.get(pub) ?? defaultLimit
303
303
  const limitStr = limit === 0 ? "blocked" : formatBytes(limit)
304
- return ` ${pub} — used: ${formatBytes(total)} / limit: ${limitStr}`
304
+ console.log(
305
+ ` ${pub.slice(0, 16)}... used: ${formatBytes(total)} / limit: ${limitStr}`,
306
+ )
305
307
  }
306
308
  if (userCount <= 5) {
307
- sorted.forEach(([pub]) => console.log(formatUser(pub)))
309
+ sorted.forEach(([pub]) => logUser(pub))
308
310
  } else {
309
311
  console.log(" Top 5 by storage used:")
310
- sorted.slice(0, 5).forEach(([pub]) => console.log(formatUser(pub)))
311
- console.log(` Run \`node examples/user-storage.js\` to view all users`)
312
+ sorted.slice(0, 5).forEach(([pub]) => logUser(pub))
312
313
  }
314
+ console.log(
315
+ " Run `npx holster-user-storage` to view full keys and all users",
316
+ )
313
317
  }
314
318
 
315
319
  // The check function is required because user data must provide a public key
@@ -441,7 +445,7 @@ const Wire = opt => {
441
445
  if ((userStorage.get(pub) ?? 0) >= limit) continue
442
446
  toStore[soul] = node
443
447
  const oldBytes = oldGraphBytes[soul] ?? 0
444
- const newBytes = JSON.stringify(node).length
448
+ const newBytes = graph[soul] ? JSON.stringify(graph[soul]).length : 0
445
449
  userStorage.set(
446
450
  pub,
447
451
  Math.max(0, (userStorage.get(pub) ?? 0) - oldBytes + newBytes),
@@ -664,7 +668,9 @@ const Wire = opt => {
664
668
  }
665
669
 
666
670
  const oldBytes = oldGraphBytes[soul] ?? 0
667
- const newBytes = JSON.stringify(node).length
671
+ const newBytes = graph[soul]
672
+ ? JSON.stringify(graph[soul]).length
673
+ : 0
668
674
  userStorage.set(pub, Math.max(0, current - oldBytes + newBytes))
669
675
  saveUserStorage()
670
676
  }
package/ts/src/wire.ts CHANGED
@@ -353,19 +353,23 @@ const Wire = (opt: HolsterOptions): WireAPI => {
353
353
  `[HOLSTER] Per-user storage limits enabled (${userCount} user${userCount !== 1 ? "s" : ""}, default: ${defLimitStr})`,
354
354
  )
355
355
  const sorted = [...userStorage.entries()].sort(([, a], [, b]) => b - a)
356
- const formatUser = (pub: string): string => {
356
+ const logUser = (pub: string): void => {
357
357
  const total = userStorage.get(pub) ?? 0
358
358
  const limit = userLimits.get(pub) ?? defaultLimit
359
359
  const limitStr = limit === 0 ? "blocked" : formatBytes(limit)
360
- return ` ${pub} — used: ${formatBytes(total)} / limit: ${limitStr}`
360
+ console.log(
361
+ ` ${pub.slice(0, 16)}... used: ${formatBytes(total)} / limit: ${limitStr}`,
362
+ )
361
363
  }
362
364
  if (userCount <= 5) {
363
- sorted.forEach(([pub]) => console.log(formatUser(pub)))
365
+ sorted.forEach(([pub]) => logUser(pub))
364
366
  } else {
365
367
  console.log(" Top 5 by storage used:")
366
- sorted.slice(0, 5).forEach(([pub]) => console.log(formatUser(pub)))
367
- console.log(` Run \`node examples/user-storage.js\` to view all users`)
368
+ sorted.slice(0, 5).forEach(([pub]) => logUser(pub))
368
369
  }
370
+ console.log(
371
+ " Run `npx holster-user-storage` to view full keys and all users",
372
+ )
369
373
  }
370
374
 
371
375
  const check = async (
@@ -493,7 +497,7 @@ const Wire = (opt: HolsterOptions): WireAPI => {
493
497
  if ((userStorage.get(pub) ?? 0) >= limit) continue
494
498
  toStore[soul] = node
495
499
  const oldBytes = oldGraphBytes[soul] ?? 0
496
- const newBytes = JSON.stringify(node).length
500
+ const newBytes = graph[soul] ? JSON.stringify(graph[soul]).length : 0
497
501
  userStorage.set(
498
502
  pub,
499
503
  Math.max(0, (userStorage.get(pub) ?? 0) - oldBytes + newBytes),
@@ -705,7 +709,9 @@ const Wire = (opt: HolsterOptions): WireAPI => {
705
709
  return
706
710
  }
707
711
  const oldBytes = oldGraphBytes[soul] ?? 0
708
- const newBytes = JSON.stringify(node).length
712
+ const newBytes = graph[soul]
713
+ ? JSON.stringify(graph[soul]).length
714
+ : 0
709
715
  userStorage.set(pub, Math.max(0, current - oldBytes + newBytes))
710
716
  saveUserStorage()
711
717
  }
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ // Displays per-user storage stats for a Holster relay with userLimit enabled.
3
+ // Usage: node utils/user-storage.js [dir] [--limit N] [--offset N]
4
+
5
+ import {readFileSync, existsSync} from "node:fs"
6
+ import {resolve} from "node:path"
7
+
8
+ const args = process.argv.slice(2)
9
+ const dir = resolve(args.find(a => !a.startsWith("--")) || ".")
10
+
11
+ if (!existsSync(dir)) {
12
+ console.error(`Error: directory not found: ${dir}`)
13
+ process.exit(1)
14
+ }
15
+
16
+ const getFlag = (name, defaultVal) => {
17
+ const idx = args.indexOf(`--${name}`)
18
+ if (idx === -1) return defaultVal
19
+ const val = parseInt(args[idx + 1], 10)
20
+ return isNaN(val) ? defaultVal : val
21
+ }
22
+
23
+ const limit = getFlag("limit", 10)
24
+ const offset = getFlag("offset", 0)
25
+
26
+ const readJSON = file => {
27
+ try {
28
+ return {data: JSON.parse(readFileSync(`${dir}/${file}`, "utf8"))}
29
+ } catch (err) {
30
+ if (err.code === "ENOENT") return {missing: true}
31
+ return {error: err.message}
32
+ }
33
+ }
34
+
35
+ const formatBytes = bytes => {
36
+ if (bytes < 1024) return `${bytes} B`
37
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`
38
+ if (bytes < 1073741824) return `${(bytes / 1048576).toFixed(1)} MB`
39
+ return `${(bytes / 1073741824).toFixed(1)} GB`
40
+ }
41
+
42
+ const DEFAULT_STORAGE_LIMIT = 1 // MB
43
+
44
+ const userStorage = readJSON(".user_storage.json")
45
+ const userLimit = readJSON(".user_limit.json")
46
+
47
+ console.log(`Per-user storage report for ${dir}:`)
48
+
49
+ if (userStorage.missing) {
50
+ console.log(" No user storage recorded yet.")
51
+ } else if (userStorage.error) {
52
+ console.error(` Error reading .user_storage.json: ${userStorage.error}`)
53
+ process.exit(1)
54
+ }
55
+
56
+ if (userLimit.missing) {
57
+ console.log(" No per-user limits defined.")
58
+ } else if (userLimit.error) {
59
+ console.error(` Error reading .user_limit.json: ${userLimit.error}`)
60
+ process.exit(1)
61
+ }
62
+
63
+ const users = Object.entries(userStorage.data || {})
64
+ .map(([pub, total]) => {
65
+ return {pub, total}
66
+ })
67
+ .sort((a, b) => b.total - a.total)
68
+
69
+ const page = users.slice(offset, offset + limit)
70
+
71
+ console.log(` Total users: ${users.length}`)
72
+ if (page.length > 0) {
73
+ console.log(
74
+ ` Showing full public keys for ${page.length} user${page.length !== 1 ? "s" : ""} (offset: ${offset}, limit: ${limit})`,
75
+ )
76
+ }
77
+
78
+ const data = userLimit.data || {}
79
+ for (const {pub, total} of page) {
80
+ const pubLimit =
81
+ typeof data[pub] === "number" ? data[pub] : DEFAULT_STORAGE_LIMIT
82
+ const limitStr =
83
+ pubLimit === 0 ? "0 B (blocked)" : formatBytes(pubLimit * 1048576)
84
+ console.log(` ${pub}`)
85
+ console.log(` used: ${formatBytes(total)} / limit: ${limitStr}`)
86
+ }