• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cameri / nostream / 24611474000

18 Apr 2026 06:49PM UTC coverage: 68.308%. First build
24611474000

Pull #500

github

web-flow
Merge 79fe36e56 into 58ab5276b
Pull Request #500: chore: inline 5 dependencies, removing external packages

928 of 1469 branches covered (63.17%)

Branch coverage included in aggregate %.

117 of 143 new or added lines in 4 files covered. (81.82%)

2415 of 3425 relevant lines covered (70.51%)

11.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.39
/src/utils/transform.ts
1
import {
2✔
2
  always,
3
  applySpec,
4
  cond,
5
  equals,
6
  ifElse,
7
  is,
8
  isNil,
9
  multiply,
10
  path,
11
  pathSatisfies,
12
  pipe,
13
  prop,
14
  propSatisfies,
15
  T,
16
} from 'ramda'
17

18
import { Invoice, InvoiceStatus, InvoiceUnit } from '../@types/invoice'
2✔
19
import { User } from '../@types/user'
20

21
const BECH32_ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
2✔
22
const BECH32_ALPHABET_MAP: Record<string, number> = {}
2✔
23
for (let i = 0; i < BECH32_ALPHABET.length; i++) { BECH32_ALPHABET_MAP[BECH32_ALPHABET[i]] = i }
64✔
24

25
function bech32PolymodStep(pre: number): number {
26
  const b = pre >> 25
545✔
27
  return (((pre & 0x1ffffff) << 5) ^
545✔
28
    (-((b >> 0) & 1) & 0x3b6a57b2) ^
29
    (-((b >> 1) & 1) & 0x26508e6d) ^
30
    (-((b >> 2) & 1) & 0x1ea119fa) ^
31
    (-((b >> 3) & 1) & 0x3d4233dd) ^
32
    (-((b >> 4) & 1) & 0x2a1462b3))
33
}
34

35
function bech32PrefixChk(prefix: string): number {
36
  let chk = 1
9✔
37
  for (let i = 0; i < prefix.length; ++i) {
9✔
38
    const c = prefix.charCodeAt(i)
36✔
39
    chk = bech32PolymodStep(chk) ^ (c >> 5)
36✔
40
  }
41
  chk = bech32PolymodStep(chk)
9✔
42
  for (let i = 0; i < prefix.length; ++i) {
9✔
43
    chk = bech32PolymodStep(chk) ^ (prefix.charCodeAt(i) & 0x1f)
36✔
44
  }
45
  return chk
9✔
46
}
47

48
function bech32Convert(data: number[], inBits: number, outBits: number, pad: boolean): number[] {
49
  let value = 0, bits = 0
8✔
50
  const maxV = (1 << outBits) - 1
8✔
51
  const maxAcc = (1 << (inBits + outBits - 1)) - 1
8✔
52
  const maxInput = (1 << inBits) - 1
8✔
53
  const result: number[] = []
8✔
54
  for (const byte of data) {
8✔
55
    if (!Number.isInteger(byte) || byte < 0 || byte > maxInput) {
256!
NEW
56
      throw new Error(`Invalid value for ${inBits}-bit input: ${byte}`)
×
57
    }
58
    value = ((value << inBits) | byte) & maxAcc
256✔
59
    bits += inBits
256✔
60
    while (bits >= outBits) {
256✔
61
      bits -= outBits
408✔
62
      result.push((value >> bits) & maxV)
408✔
63
    }
64
  }
65
  if (pad) {
8!
66
    if (bits > 0) { result.push((value << (outBits - bits)) & maxV) }
8!
NEW
67
  } else if (bits >= inBits || ((value << (outBits - bits)) & maxV) !== 0) {
×
NEW
68
    throw new Error('Invalid bech32 padding')
×
69
  }
70
  return result
8✔
71
}
72

73
function bech32Decode(str: string): { prefix: string; words: number[] } {
74
  const lower = str.toLowerCase()
1✔
75
  const split = lower.lastIndexOf('1')
1✔
76
  if (split < 1 || split + 7 > str.length) { throw new Error(`Invalid bech32: ${str}`) }
1!
77
  const prefix = lower.slice(0, split)
1✔
78
  const wordChars = lower.slice(split + 1)
1✔
79
  let chk = bech32PrefixChk(prefix)
1✔
80
  const words: number[] = []
1✔
81
  for (let i = 0; i < wordChars.length; ++i) {
1✔
82
    const v = BECH32_ALPHABET_MAP[wordChars[i]]
1✔
83
    if (v === undefined) { throw new Error(`Unknown bech32 character: ${wordChars[i]}`) }
1!
NEW
84
    chk = bech32PolymodStep(chk) ^ v
×
NEW
85
    if (i + 6 < wordChars.length) { words.push(v) }
×
86
  }
NEW
87
  if (chk !== 1) { throw new Error('Invalid bech32 checksum') }
×
NEW
88
  return { prefix, words }
×
89
}
90

91
function bech32Encode(prefix: string, words: number[]): string {
92
  prefix = prefix.toLowerCase()
8✔
93
  let chk = bech32PrefixChk(prefix)
8✔
94
  let result = prefix + '1'
8✔
95
  for (const w of words) {
8✔
96
    chk = bech32PolymodStep(chk) ^ w
416✔
97
    result += BECH32_ALPHABET[w]
416✔
98
  }
99
  for (let i = 0; i < 6; ++i) { chk = bech32PolymodStep(chk) }
48✔
100
  chk ^= 1
8✔
101
  for (let i = 0; i < 6; ++i) { result += BECH32_ALPHABET[(chk >> ((5 - i) * 5)) & 0x1f] }
48✔
102
  return result
8✔
103
}
104

105
export const toJSON = (input: any) => JSON.stringify(input)
33✔
106

107
export const toBuffer = (input: any) => Buffer.from(input, 'hex')
337✔
108

109
export const fromBuffer = (input: Buffer) => input.toString('hex')
117✔
110

111
export const toBigInt = (input: string | number): bigint => BigInt(input)
11✔
112

113
export const fromBigInt = (input: bigint) => input.toString()
2✔
114

115
const addTime = (ms: number) => (input: Date) => new Date(input.getTime() + ms)
5✔
116

117
export const fromDBInvoice = applySpec<Invoice>({
2✔
118
  id: prop('id') as () => string,
119
  pubkey: pipe(prop('pubkey') as () => Buffer, fromBuffer),
120
  bolt11: prop('bolt11'),
121
  amountRequested: pipe(prop('amount_requested') as () => string, toBigInt),
122
  amountPaid: ifElse(
123
    propSatisfies(isNil, 'amount_paid'),
124
    always(undefined),
125
    pipe(prop('amount_paid') as () => string, toBigInt),
126
  ),
127
  unit: prop('unit'),
128
  status: prop('status'),
129
  description: prop('description'),
130
  confirmedAt: prop('confirmed_at'),
131
  expiresAt: prop('expires_at'),
132
  updatedAt: prop('updated_at'),
133
  createdAt: prop('created_at'),
134
  verifyURL: prop('verify_url'),
135
})
136

137
export const fromDBUser = applySpec<User>({
2✔
138
  pubkey: pipe(prop('pubkey') as () => Buffer, fromBuffer),
139
  isAdmitted: prop('is_admitted'),
140
  isVanished: prop('is_vanished'),
141
  balance: prop('balance'),
142
  createdAt: prop('created_at'),
143
  updatedAt: prop('updated_at'),
144
})
145

146
export const fromBech32 = (input: string) => {
2✔
147
  const normalizedInput = input.toLowerCase()
1✔
148

149
  if (input !== normalizedInput && input !== input.toUpperCase()) {
1!
NEW
150
    throw new Error('Bech32 mixed-case input is invalid')
×
151
  }
152

153
  const { prefix, words } = bech32Decode(input)
1✔
NEW
154
  if (!normalizedInput.startsWith(prefix)) {
×
155
    throw new Error(`Bech32 invalid prefix: ${prefix}`)
×
156
  }
157

NEW
158
  return Buffer.from(
×
159
    bech32Convert(words, 5, 8, false).slice(0, 32)
160
  ).toString('hex')
161
}
162

163
export const toBech32 = (prefix: string) => (input: string): string => {
9✔
164
  return bech32Encode(prefix, bech32Convert(Array.from(Buffer.from(input, 'hex')), 8, 5, true))
8✔
165
}
166

167
export const toDate = (input: string | number) => new Date(input)
17✔
168

169
export const fromZebedeeInvoice = applySpec<Invoice>({
2✔
170
  id: prop('id'),
171
  pubkey: prop('internalId'),
172
  bolt11: path(['invoice', 'request']),
173
  amountRequested: pipe(prop('amount') as () => string, toBigInt),
174
  description: prop('description'),
175
  unit: prop('unit'),
176
  status: prop('status'),
177
  expiresAt: ifElse(propSatisfies(is(String), 'expiresAt'), pipe(prop('expiresAt'), toDate), always(null)),
178
  confirmedAt: ifElse(propSatisfies(is(String), 'confirmedAt'), pipe(prop('confirmedAt'), toDate), always(null)),
179
  createdAt: ifElse(propSatisfies(is(String), 'createdAt'), pipe(prop('createdAt'), toDate), always(null)),
180
  rawResponse: toJSON,
181
})
182

183
export const fromNodelessInvoice = applySpec<Invoice>({
2✔
184
  id: prop('id'),
185
  pubkey: path(['metadata', 'requestId']),
186
  bolt11: prop('lightningInvoice'),
187
  amountRequested: pipe(prop('satsAmount') as () => number, toBigInt),
188
  description: path(['metadata', 'description']),
189
  unit: path(['metadata', 'unit']),
190
  status: pipe(
191
    prop('status'),
192
    cond([
193
      [equals('new'), always(InvoiceStatus.PENDING)],
194
      [equals('pending_confirmation'), always(InvoiceStatus.PENDING)],
195
      [equals('underpaid'), always(InvoiceStatus.PENDING)],
196
      [equals('in_flight'), always(InvoiceStatus.PENDING)],
197
      [equals('paid'), always(InvoiceStatus.COMPLETED)],
198
      [equals('overpaid'), always(InvoiceStatus.COMPLETED)],
199
      [equals('expired'), always(InvoiceStatus.EXPIRED)],
200
    ]),
201
  ),
202
  expiresAt: ifElse(
203
    propSatisfies(is(String), 'expiresAt'),
204
    pipe(prop('expiresAt'), toDate),
205
    ifElse(propSatisfies(is(String), 'createdAt'), pipe(prop('createdAt'), toDate, addTime(15 * 60000)), always(null)),
206
  ),
207
  confirmedAt: cond([
208
    [propSatisfies(is(String), 'paidAt'), pipe(prop('paidAt'), toDate)],
209
    [T, always(null)],
210
  ]),
211
  createdAt: ifElse(propSatisfies(is(String), 'createdAt'), pipe(prop('createdAt'), toDate), always(null)),
212
  // rawResponse: toJSON,
213
})
214

215
export const fromOpenNodeInvoice = applySpec<Invoice>({
2✔
216
  id: prop('id'),
217
  pubkey: prop('order_id'),
218
  bolt11: ifElse(
219
    pathSatisfies(is(String), ['lightning_invoice', 'payreq']),
220
    path(['lightning_invoice', 'payreq']),
221
    path(['lightning', 'payreq']),
222
  ),
223
  amountRequested: pipe(
224
    ifElse(propSatisfies(is(Number), 'amount'), prop('amount'), prop('price')) as () => number,
225
    toBigInt,
226
  ),
227
  description: prop('description'),
228
  unit: always(InvoiceUnit.SATS),
229
  status: pipe(
230
    prop('status'),
231
    cond([
232
      [equals('expired'), always(InvoiceStatus.EXPIRED)],
233
      [equals('refunded'), always(InvoiceStatus.EXPIRED)],
234
      [equals('unpaid'), always(InvoiceStatus.PENDING)],
235
      [equals('processing'), always(InvoiceStatus.PENDING)],
236
      [equals('underpaid'), always(InvoiceStatus.PENDING)],
237
      [equals('paid'), always(InvoiceStatus.COMPLETED)],
238
    ]),
239
  ),
240
  expiresAt: pipe(
241
    cond([
242
      [pathSatisfies(is(String), ['lightning', 'expires_at']), path(['lightning', 'expires_at'])],
243
      [
244
        pathSatisfies(is(Number), ['lightning_invoice', 'expires_at']),
245
        pipe(path(['lightning_invoice', 'expires_at']), multiply(1000)),
246
      ],
247
    ]),
248
    toDate,
249
  ),
250
  confirmedAt: cond([
251
    [propSatisfies(equals('paid'), 'status'), () => new Date()],
×
252
    [T, always(null)],
253
  ]),
254
  createdAt: pipe(
255
    ifElse(propSatisfies(is(Number), 'created_at'), pipe(prop('created_at'), multiply(1000)), prop('created_at')),
256
    toDate,
257
  ),
258
  rawResponse: toJSON,
259
})
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc