@mblaney/holster 2.1.1 → 2.1.3

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.1",
3
+ "version": "2.1.3",
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,11 +14,15 @@
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",
package/src/wire.js CHANGED
@@ -297,19 +297,21 @@ 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("Run npx holster-user-storage to view full keys and all users")
313
315
  }
314
316
 
315
317
  // The check function is required because user data must provide a public key
package/ts/src/wire.ts CHANGED
@@ -353,19 +353,21 @@ 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("Run npx holster-user-storage to view full keys and all users")
369
371
  }
370
372
 
371
373
  const check = async (
@@ -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
+ }