better code: client-server shared code
Massimo Melina committed
Sep 6, 2023 at 12:47 UTC
acce3ff90b81da954d1a882b0a6d6e30a02e597a
6 files changed
+237
-317
frontend/src/i18n.ts
+4
-2
@@ -1,4 +1,6 @@
1
-import { findFirst, getHFS } from './misc'
1
+// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
+
3
+import { findDefined, getHFS } from './misc'
4
import { createElement as h, Fragment } from 'react'
5
import { proxy, useSnapshot } from 'valtio'
6
@@ -33,7 +35,7 @@ export function t(keyOrTpl: string | string[] | TemplateStringsArray, params?: a
35
let selectedLang = '' // keep track of where we find the translation
36
const { langs, embedded } = state
37
for (const key of keys) {
36
- found = findFirst(langs, lang => translations[selectedLang=lang]?.translate?.[key])
38
+ found = findDefined(langs, lang => translations[selectedLang=lang]?.translate?.[key])
39
if (found) break
40
if (selectedLang && langs[0] !== embedded && !warns.has(key)) {
41
warns.add(key)
shared/index.ts
+1
-120
@@ -5,13 +5,7 @@ import { apiCall } from './api'
5
export * from './react'
6
export * from './dialogs'
7
export * from './srp'
8
-
9
-export const REPO_URL = 'https://github.com/rejetto/hfs/'
10
-export const WIKI_URL = REPO_URL + 'wiki/'
11
-
12
-export type Dict<T=any> = Record<string, T>
13
-export type Falsy = false | null | undefined | '' | 0
14
-type Truthy<T> = T extends false | '' | 0 | null | undefined ? never : T
8
+export * from '../src/cross'
9
10
(window as any)._ = _
11
@@ -29,62 +23,6 @@ function getScriptAttr(k: string) {
23
24
export const urlParams = Object.fromEntries(new URLSearchParams(window.location.search).entries())
25
32
-const MULTIPLIERS = ['', 'K', 'M', 'G', 'T']
33
-export function formatBytes(n: number, { post='B', k=1024, digits=NaN }={}) {
34
- if (isNaN(Number(n)) || n < 0)
35
- return ''
36
- const i = n && Math.floor(Math.log2(n) / Math.log2(k))
37
- n /= k ** i
38
- const nAsString = i && !isNaN(digits) ? n.toFixed(digits)
39
- : _.round(n, isNaN(digits) ? (n >= 100 ? 0 : 1) : digits)
40
- return nAsString + ' ' + (MULTIPLIERS[i]||'') + post
41
-} // formatBytes
42
-
43
-export function prefix(pre:string, v:string|number|undefined|null|false, post:string='') {
44
- return v ? pre+v+post : ''
45
-}
46
-
47
-export function wait<T=undefined>(ms: number, val?: T): Promise<T> {
48
- return new Promise(res=> setTimeout(res,ms,val))
49
-}
50
-
51
-export function objSameKeys<S extends object,VR=any>(src: S, newValue:(value:Truthy<S[keyof S]>, key:keyof S)=>VR) {
52
- return Object.fromEntries(Object.entries(src).map(([k,v]) => [k, newValue(v,k as keyof S)])) as { [K in keyof S]:VR }
53
-}
54
-
55
-export function enforceFinal(sub:string, s:string) {
56
- return !s || s.endsWith(sub) ? s : s+sub
57
-}
58
-
59
-export function truthy<T>(value: T): value is Truthy<T> {
60
- return Boolean(value)
61
-}
62
-
63
-export function onlyTruthy<T>(arr: T[]) {
64
- return arr.filter(truthy)
65
-}
66
-
67
-export function setHidden(dest: object, src:object) {
68
- return Object.defineProperties(dest, objSameKeys(src as any, value => ({
69
- enumerable: false,
70
- writable: true,
71
- value,
72
- })))
73
-}
74
-
75
-export function try_(cb: () => any, onException?: (e:any) => any) {
76
- try {
77
- return cb()
78
- }
79
- catch(e) {
80
- return onException?.(e)
81
- }
82
-}
83
-
84
-export function with_<T,RT>(par:T, cb: (par:T) => RT) {
85
- return cb(par)
86
-}
87
-
26
export function domOn<K extends keyof WindowEventMap>(eventName: K, cb: (ev: WindowEventMap[K]) => void, { target=window }={}) {
27
target.addEventListener(eventName, cb)
28
return () => target.removeEventListener(eventName, cb)
@@ -96,14 +34,6 @@ export function restartAnimation(e: HTMLElement, animation: string) {
34
e.style.animation = animation
35
}
36
99
-export function findFirst<I, O>(a: I[] | Record<string, I>, cb:(v:I, k: string | number)=>O): any {
100
- if (a) for (const k in a) {
101
- const ret = cb((a as any)[k] as I, k)
102
- if (ret !== undefined)
103
- return ret
104
- }
105
-}
106
-
37
export function selectFiles(cb: (list: FileList | null)=>void, { accept='', multiple=true, folder=false }={}) {
38
const el = Object.assign(document.createElement('input'), {
39
type: 'file',
@@ -133,27 +63,6 @@ export function readFile(f: File | Blob): Promise<string | undefined> {
63
})
64
}
65
136
-export function formatPerc(p: number) {
137
- return (p*100).toFixed(1) + '%'
138
-}
139
-
140
-export function wantArray<T>(x?: void | T | T[]) {
141
- return x == null ? [] : Array.isArray(x) ? x : [x]
142
-}
143
-
144
-export function _log(...args: any[]) {
145
- console.log('**', ...args)
146
- return args[args.length-1]
147
-}
148
-
149
-type PendingPromise<T> = Promise<T> & { resolve: (value: T) => void, reject: (reason?: any) => void }
150
-export function pendingPromise<T>() {
151
- let takeOut
152
- const ret = new Promise<T>((resolve, reject) =>
153
- takeOut = { resolve, reject })
154
- return Object.assign(ret, takeOut) as PendingPromise<T>
155
-}
156
-
66
export function isMobile() {
67
return window.innerWidth < 800
68
}
@@ -166,10 +75,6 @@ export function getPrefixUrl() {
75
return getHFS().prefixUrl || ''
76
}
77
169
-export function basename(path: string) {
170
- return path.slice(path.lastIndexOf('/') + 1 || path.lastIndexOf('\\') + 1)
171
-}
172
-
78
export function makeSessionRefresher(state: any) {
79
return function sessionRefresher(response: any) {
80
if (!response) return
@@ -183,27 +88,3 @@ export function makeSessionRefresher(state: any) {
88
setTimeout(() => apiCall('refresh_session').then(sessionRefresher), t)
89
}
90
}
186
-
187
-export function tryJson(s?: string) {
188
- try { return s && JSON.parse(s) }
189
- catch {}
190
-}
191
-
192
-export function swap<T>(obj: T, k1: keyof T, k2: keyof T) {
193
- const temp = obj[k1]
194
- obj[k1] = obj[k2]
195
- obj[k2] = temp
196
- return obj
197
-}
198
-
199
-export function isOrderedEqual(a: any, b: any): boolean {
200
- return _.isEqualWith(a, b, (a1, b1) => {
201
- if (!_.isPlainObject(a1) || !_.isPlainObject(b1)) return
202
- const ka = Object.keys(a1)
203
- const kb = Object.keys(b1)
204
- return ka.length === kb.length && ka.every((ka1, i) => {
205
- const kb1 = kb[i]
206
- return ka1 === kb1 && isOrderedEqual(a1[ka1], b1[kb1])
207
- })
208
- })
209
-}
\ No newline at end of file
src/cross.ts
new
+218
@@ -0,0 +1,218 @@
1
+// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
+// all content here is shared between client and server
3
+import _ from 'lodash'
4
+
5
+export const REPO_URL = 'https://github.com/rejetto/hfs/'
6
+export const WIKI_URL = REPO_URL + 'wiki/'
7
+
8
+export type Dict<T=any> = Record<string, T>
9
+export type Falsy = false | null | undefined | '' | 0
10
+type Truthy<T> = T extends false | '' | 0 | null | undefined ? never : T
11
+export type Callback<IN=void, OUT=void> = (x:IN) => OUT
12
+export type Promisable<T> = T | Promise<T>
13
+
14
+const MULTIPLIERS = ['', 'K', 'M', 'G', 'T']
15
+export function formatBytes(n: number, { post='B', k=1024, digits=NaN }={}) {
16
+ if (isNaN(Number(n)) || n < 0)
17
+ return ''
18
+ const i = n && Math.floor(Math.log2(n) / Math.log2(k))
19
+ n /= k ** i
20
+ const nAsString = i && !isNaN(digits) ? n.toFixed(digits)
21
+ : _.round(n, isNaN(digits) ? (n >= 100 ? 0 : 1) : digits)
22
+ return nAsString + ' ' + (MULTIPLIERS[i]||'') + post
23
+} // formatBytes
24
+
25
+export function prefix(pre:string, v:string|number|undefined|null|false, post:string='') {
26
+ return v ? pre+v+post : ''
27
+}
28
+
29
+export function wait<T=undefined>(ms: number, val?: T): Promise<T | undefined> {
30
+ return new Promise(res=> setTimeout(res,ms,val))
31
+}
32
+
33
+export function objSameKeys<S extends object,VR=any>(src: S, newValue:(value:Truthy<S[keyof S]>, key:keyof S)=>VR) {
34
+ return Object.fromEntries(Object.entries(src).map(([k,v]) => [k, newValue(v,k as keyof S)])) as { [K in keyof S]:VR }
35
+}
36
+
37
+export function enforceFinal(sub:string, s:string) {
38
+ return !s || s.endsWith(sub) ? s : s+sub
39
+}
40
+
41
+export function truthy<T>(value: T): value is Truthy<T> {
42
+ return Boolean(value)
43
+}
44
+
45
+export function onlyTruthy<T>(arr: T[]) {
46
+ return arr.filter(truthy)
47
+}
48
+
49
+export function setHidden<T, ADD>(dest: T, src: ADD) {
50
+ return Object.defineProperties(dest, newObj(src as any, value => ({
51
+ enumerable: false,
52
+ writable: true,
53
+ value,
54
+ }))) as T & ADD
55
+}
56
+
57
+export function try_(cb: () => any, onException?: (e:any) => any) {
58
+ try {
59
+ return cb()
60
+ }
61
+ catch(e) {
62
+ return onException?.(e)
63
+ }
64
+}
65
+
66
+export function with_<T,RT>(par:T, cb: (par:T) => RT) {
67
+ return cb(par)
68
+}
69
+
70
+export function formatPerc(p: number) {
71
+ return (p*100).toFixed(1) + '%'
72
+}
73
+
74
+export function wantArray<T>(x?: void | T | T[]) {
75
+ return x == null ? [] : Array.isArray(x) ? x : [x]
76
+}
77
+
78
+export function _log(...args: any[]) {
79
+ console.log('**', ...args)
80
+ return args[args.length-1]
81
+}
82
+
83
+export type PendingPromise<T=unknown> = Promise<T> & { resolve: (value?: T) => void, reject: (reason?: any) => void }
84
+export function pendingPromise<T>() {
85
+ let takeOut
86
+ const ret = new Promise<T>((resolve, reject) =>
87
+ takeOut = { resolve, reject })
88
+ return Object.assign(ret, takeOut) as PendingPromise<T>
89
+}
90
+
91
+export function basename(path: string) {
92
+ return path.slice(path.lastIndexOf('/') + 1 || path.lastIndexOf('\\') + 1)
93
+}
94
+
95
+export function tryJson(s?: string) {
96
+ try { return s && JSON.parse(s) }
97
+ catch {}
98
+}
99
+
100
+export function swap<T>(obj: T, k1: keyof T, k2: keyof T) {
101
+ const temp = obj[k1]
102
+ obj[k1] = obj[k2]
103
+ obj[k2] = temp
104
+ return obj
105
+}
106
+
107
+export function isOrderedEqual(a: any, b: any): boolean {
108
+ return _.isEqualWith(a, b, (a1, b1) => {
109
+ if (!_.isPlainObject(a1) || !_.isPlainObject(b1)) return
110
+ const ka = Object.keys(a1)
111
+ const kb = Object.keys(b1)
112
+ return ka.length === kb.length && ka.every((ka1, i) => {
113
+ const kb1 = kb[i]
114
+ return ka1 === kb1 && isOrderedEqual(a1[ka1], b1[kb1])
115
+ })
116
+ })
117
+}
118
+
119
+export function findDefined<I, O>(a: I[] | Record<string, I>, cb:(v:I, k: string | number)=>O): any {
120
+ if (a) for (const k in a) {
121
+ const ret = cb((a as any)[k] as I, k)
122
+ if (ret !== undefined)
123
+ return ret
124
+ }
125
+}
126
+
127
+export function removeStarting(sub: string, s: string) {
128
+ return s.startsWith(sub) ? s.slice(sub.length) : s
129
+}
130
+
131
+export function newObj<S extends (object | undefined | null),VR=any>(
132
+ src: S,
133
+ returnNewValue: (value:Truthy<S[keyof S]>, key: Exclude<keyof S, symbol>, setK:(newK?: string)=>true, depth: number) => any,
134
+ recur: boolean | number=false
135
+) {
136
+ if (!src)
137
+ return {}
138
+ const pairs = Object.entries(src).map( ([k,v]) => {
139
+ if (typeof k === 'symbol') return
140
+ let _k: undefined | typeof k = k
141
+ const curDepth = typeof recur === 'number' ? recur : 0
142
+ let newV = returnNewValue(v, k as Exclude<keyof S, symbol>, (newK) => {
143
+ _k = newK
144
+ return true // for convenient expression concatenation
145
+ }, curDepth)
146
+ if ((recur !== false || returnNewValue.length === 4) // if callback is using depth parameter, then it wants recursion
147
+ && _.isPlainObject(newV)) // is it recurrable?
148
+ newV = newObj(newV, returnNewValue, curDepth + 1)
149
+ return _k !== undefined && [_k, newV]
150
+ })
151
+ return Object.fromEntries(onlyTruthy(pairs)) as S extends undefined | null ? S : { [K in keyof S]:VR }
152
+}
153
+
154
+export async function waitFor<T>(cb: ()=> T, { interval=200, timeout=Infinity }={}) {
155
+ const started = Date.now()
156
+ while (1) {
157
+ const res = await cb()
158
+ if (res)
159
+ return res
160
+ if (Date.now() - started >= timeout)
161
+ return
162
+ await wait(interval)
163
+ }
164
+}
165
+
166
+export function getOrSet<T>(o: Record<string,T>, k:string, creator:()=>T): T {
167
+ return k in o ? o[k]!
168
+ : (o[k] = creator())
169
+}
170
+
171
+// 10 chars is 51+bits, 8 is 41+bits
172
+export function randomId(len = 10): string {
173
+ if (len > 10)
174
+ return randomId(10) + randomId(len - 10)
175
+ return Math.random()
176
+ .toString(36)
177
+ .substring(2, 2+len)
178
+ .replace(/l/g, 'L'); // avoid confusion reading l1
179
+}
180
+
181
+export function objRenameKey(o: Dict | undefined, from: string, to: string) {
182
+ if (!o || !o.hasOwnProperty(from) || from === to) return
183
+ o[to] = o[from]
184
+ delete o[from]
185
+ return true
186
+}
187
+
188
+export function typedKeys<T extends {}>(o: T) {
189
+ return Object.keys(o) as (keyof T)[]
190
+}
191
+
192
+export function typedEntries<T extends {}>(o: T): [keyof T, T[keyof T]][] {
193
+ return Object.entries(o) as [keyof T, T[keyof T]][];
194
+}
195
+
196
+export function hasProp<T extends object>(obj: T, key: PropertyKey): key is keyof T {
197
+ return key in obj;
198
+}
199
+
200
+export function throw_(err: any) {
201
+ throw err
202
+}
203
+
204
+export async function* filterMapGenerator<IN,OUT>(generator: AsyncIterableIterator<IN>, filterMap: (el: IN) => Promise<OUT>) {
205
+ for await (const x of generator) {
206
+ const res:OUT = await filterMap(x)
207
+ if (res !== undefined)
208
+ yield res as Exclude<OUT,undefined>
209
+ }
210
+}
211
+
212
+export async function asyncGeneratorToArray<T>(generator: AsyncIterable<T>): Promise<T[]> {
213
+ const ret: T[] = []
214
+ for await(const x of generator)
215
+ ret.push(x)
216
+ return ret
217
+}
218
+
src/misc.ts
+11
-154
@@ -7,96 +7,14 @@ import Koa from 'koa'
7
import { Connection } from './connections'
8
import assert from 'assert'
9
export * from './util-http'
10
-export * from './util-generators'
10
export * from './util-files'
12
-import debounceAsync from './debounceAsync'
11
+export * from './cross'
12
import { Readable } from 'stream'
13
import { matcher } from 'micromatch'
14
import cidr from 'cidr-tools'
15
+import debounceAsync from './debounceAsync'
16
export { debounceAsync }
17
18
-export type Callback<IN=void, OUT=void> = (x:IN) => OUT
19
-export type Dict<T = any> = Record<string, T>
20
-export type Promisable<T> = T | Promise<T>
21
-
22
-export function enforceFinal(sub:string, s:string) {
23
- return s.endsWith(sub) ? s : s+sub
24
-}
25
-
26
-export function removeStarting(sub: string, s: string) {
27
- return s.startsWith(sub) ? s.slice(sub.length) : s
28
-}
29
-
30
-export function prefix(pre:string, v:string|number|undefined, post:string='') {
31
- return v ? pre+v+post : ''
32
-}
33
-
34
-export function setHidden<T, ADD>(dest: T, src: ADD) {
35
- return Object.defineProperties(dest, newObj(src as any, value => ({
36
- enumerable: false,
37
- writable: true,
38
- value,
39
- }))) as T & ADD
40
-}
41
-
42
-export function newObj<S extends (object | undefined | null),VR=any>(
43
- src: S,
44
- returnNewValue: (value:Truthy<S[keyof S]>, key: Exclude<keyof S, symbol>, setK:(newK?: string)=>true, depth: number) => any,
45
- recur: boolean | number=false
46
-) {
47
- if (!src)
48
- return {}
49
- const pairs = Object.entries(src).map( ([k,v]) => {
50
- if (typeof k === 'symbol') return
51
- let _k: undefined | typeof k = k
52
- const curDepth = typeof recur === 'number' ? recur : 0
53
- let newV = returnNewValue(v, k as Exclude<keyof S, symbol>, (newK) => {
54
- _k = newK
55
- return true // for convenient expression concatenation
56
- }, curDepth)
57
- if ((recur !== false || returnNewValue.length === 4) // if callback is using depth parameter, then it wants recursion
58
- && _.isPlainObject(newV)) // is it recurrable?
59
- newV = newObj(newV, returnNewValue, curDepth + 1)
60
- return _k !== undefined && [_k, newV]
61
- })
62
- return Object.fromEntries(onlyTruthy(pairs)) as S extends undefined | null ? S : { [K in keyof S]:VR }
63
-}
64
-
65
-export function wait(ms: number) {
66
- return new Promise(res=> setTimeout(res,ms))
67
-}
68
-
69
-export async function waitFor<T>(cb: ()=> T, { interval=200, timeout=Infinity }={}) {
70
- const started = Date.now()
71
- while (1) {
72
- const res = await cb()
73
- if (res)
74
- return res
75
- if (Date.now() - started >= timeout)
76
- return
77
- await wait(interval)
78
- }
79
-}
80
-
81
-export function wantArray<T>(x?: void | T | T[]) {
82
- return x == null ? [] : Array.isArray(x) ? x : [x]
83
-}
84
-
85
-export function getOrSet<T>(o: Record<string,T>, k:string, creator:()=>T): T {
86
- return k in o ? o[k]!
87
- : (o[k] = creator())
88
-}
89
-
90
-// 10 chars is 51+bits, 8 is 41+bits
91
-export function randomId(len = 10): string {
92
- if (len > 10)
93
- return randomId(10) + randomId(len - 10)
94
- return Math.random()
95
- .toString(36)
96
- .substring(2, 2+len)
97
- .replace(/l/g, 'L'); // avoid confusion reading l1
98
-}
99
-
18
type ProcessExitHandler = (signal:string) => any
19
const cbs = new Set<ProcessExitHandler>()
20
export function onProcessExit(cb: ProcessExitHandler) {
@@ -123,24 +41,6 @@ export function pattern2filter(pattern: string){
41
!s || !pattern || re.test(basename(s))
42
}
43
126
-type Truthy<T> = T extends false | '' | 0 | null | undefined ? never : T
127
-
128
-export function truthy<T>(value: T): value is Truthy<T> {
129
- return Boolean(value)
130
-}
131
-
132
-export function onlyTruthy<T>(arr: T[]) {
133
- return arr.filter(truthy)
134
-}
135
-
136
-export type PendingPromise<T=unknown> = Promise<T> & { resolve: (value?: T) => void, reject: (reason?: any) => void }
137
-export function pendingPromise<T>() {
138
- let takeOut
139
- const ret = new Promise<T>((resolve, reject) =>
140
- takeOut = { resolve, reject })
141
- return Object.assign(ret, takeOut) as PendingPromise<T>
142
-}
143
-
44
// install multiple handlers and returns a handy 'uninstall' function which requires no parameter. Pass a map {event:handler}
45
export function onOff(em: EventEmitter, events: { [eventName:string]: (...args: any[]) => void }) {
46
events = { ...events } // avoid later modifications, as we need this later for uninstallation
@@ -154,29 +54,6 @@ export function onOff(em: EventEmitter, events: { [eventName:string]: (...args:
54
}
55
}
56
157
-export function objRenameKey(o: Dict | undefined, from: string, to: string) {
158
- if (!o || !o.hasOwnProperty(from) || from === to) return
159
- o[to] = o[from]
160
- delete o[from]
161
- return true
162
-}
163
-
164
-export function typedKeys<T extends {}>(o: T) {
165
- return Object.keys(o) as (keyof T)[]
166
-}
167
-
168
-export function typedEntries<T extends {}>(o: T): [keyof T, T[keyof T]][] {
169
- return Object.entries(o) as [keyof T, T[keyof T]][];
170
-}
171
-
172
-export function hasProp<T extends object>(obj: T, key: PropertyKey): key is keyof T {
173
- return key in obj;
174
-}
175
-
176
-export function with_<T,RT>(par:T, cb: (par:T) => RT) {
177
- return cb(par)
178
-}
179
-
57
export function isLocalHost(c: Connection | Koa.Context) {
58
const ip = c.socket.remoteAddress // don't use Context.ip as it is subject to proxied ips, and that's no use for localhost detection
59
return ip && (ip === '::1' || ip.endsWith('127.0.0.1'))
@@ -195,11 +72,6 @@ export function makeNetMatcher(mask: string, emptyMaskReturns=false) {
72
neg !== all.some(x => cidr.contains(x, ip))
73
}
74
198
-export function netmaskToCIDR(netmask: string) {
199
- return netmask.split('.').map(x => Number(x).toString(2)) // to binary
200
- .join('').split('1').length - 1 // Count '1' bits
201
-}
202
-
75
export function makeMatcher(mask: string, emptyMaskReturns=false) {
76
return mask ? matcher(mask.replace(/^(!)?/, '$1(') + ')') // adding () will allow us to use the pipe at root level
77
: () => emptyMaskReturns
@@ -217,11 +89,6 @@ export function same(a: any, b: any) {
89
catch { return false }
90
}
91
220
-export function tryJson(s?: string) {
221
- try { return s ? JSON.parse(s) : undefined }
222
- catch {}
223
-}
224
-
92
export async function stream2string(stream: Readable): Promise<string> {
93
return new Promise((resolve, reject) => {
94
let data = ''
@@ -239,23 +106,13 @@ export async function stream2string(stream: Readable): Promise<string> {
106
})
107
}
108
242
-export function try_(cb: () => any, onException?: (e:any) => any) {
243
- try {
244
- return cb()
245
- }
246
- catch(e) {
247
- return onException?.(e)
248
- }
249
-}
250
-
251
-export function throw_(err: any) {
252
- throw err
253
-}
254
-
255
-export function findFirst<I, O>(a: I[] | Record<string, I>, cb:(v:I, k: string | number)=>O): any {
256
- if (a) for (const k in a) {
257
- const ret = cb((a as any)[k] as I, k)
258
- if (ret !== undefined)
259
- return ret
260
- }
109
+export function asyncGeneratorToReadable<T>(generator: AsyncIterable<T>) {
110
+ const iterator = generator[Symbol.asyncIterator]()
111
+ return new Readable({
112
+ objectMode: true,
113
+ read() {
114
+ iterator.next().then(it =>
115
+ this.push(it.done ? null : it.value))
116
+ }
117
+ })
118
}
src/util-generators.ts
deleted
-31
@@ -1,31 +0,0 @@
1
-// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
-// callback can return undefined to skip element
4
-import { Readable } from 'stream'
5
-
6
-export async function* filterMapGenerator<IN,OUT>(generator: AsyncIterableIterator<IN>, filterMap: (el: IN) => Promise<OUT>) {
7
- for await (const x of generator) {
8
- const res:OUT = await filterMap(x)
9
- if (res !== undefined)
10
- yield res as Exclude<OUT,undefined>
11
- }
12
-}
13
-
14
-export async function asyncGeneratorToArray<T>(generator: AsyncIterable<T>): Promise<T[]> {
15
- const ret: T[] = []
16
- for await(const x of generator)
17
- ret.push(x)
18
- return ret
19
-}
20
-
21
-export function asyncGeneratorToReadable<T>(generator: AsyncIterable<T>) {
22
- const iterator = generator[Symbol.asyncIterator]()
23
- return new Readable({
24
- objectMode: true,
25
- read() {
26
- iterator.next().then(it =>
27
- this.push(it.done ? null : it.value))
28
- }
29
- })
30
-}
31
-
tests/test.ts
+3
-10
@@ -5,6 +5,7 @@ import { srpSequence } from '@hfs/shared/srp'
5
import { createReadStream, rmSync } from 'fs'
6
import { join } from 'path'
7
import _ from 'lodash'
8
+import { findDefined } from '../src/cross'
9
/*
10
import { PORT, srv } from '../src'
11
@@ -165,10 +166,10 @@ function req(methodUrl: string, test:Tester, requestOptions: AxiosRequestConfig<
166
|| re && !(typeof data === 'string' && re.test(data)) && 'expected content '+String(re)+' got '+(data || '-empty-')
167
|| inList && !inList.every(x => isInList(data, x)) && 'expected in list '+inList
168
|| outList && !outList.every(x => !isInList(data, x)) && 'expected not in list '+outList
168
- || permInList && findFirst(permInList, (v, k) => {
169
+ || permInList && findDefined(permInList, (v, k) => {
170
const got = _.find(data.list, { n: k })?.p
171
const negate = v[0] === '!'
171
- return findFirst(v.slice(negate ? 1 : 0).split(''), char =>
172
+ return findDefined(v.slice(negate ? 1 : 0).split(''), char =>
173
got?.includes(char) === negate ? `expected perm ${v} on ${k}, got ${got}` : undefined)
174
})
175
|| test.empty && data && 'expected empty body'
@@ -195,12 +196,4 @@ function reqList(uri:string, tester:Tester, params?: object) {
196
197
function isInList(res:any, name:string) {
198
return Array.isArray(res?.list) && Boolean((res.list as any[]).find(x => x.n===name))
198
-}
199
-
200
-export function findFirst<I, O>(a: I[] | Record<string, I>, cb:(v:I, k: string | number)=>O): any {
201
- if (a) for (const k in a) {
202
- const ret = cb((a as any)[k] as I, k)
203
- if (ret !== undefined)
204
- return ret
205
- }
199
}
\ No newline at end of file