@juzi/file-box 1.7.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/LICENSE +201 -0
- package/README.md +613 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/src/config.d.ts +4 -0
- package/dist/cjs/src/config.d.ts.map +1 -0
- package/dist/cjs/src/config.js +9 -0
- package/dist/cjs/src/config.js.map +1 -0
- package/dist/cjs/src/file-box.d.ts +209 -0
- package/dist/cjs/src/file-box.d.ts.map +1 -0
- package/dist/cjs/src/file-box.js +804 -0
- package/dist/cjs/src/file-box.js.map +1 -0
- package/dist/cjs/src/file-box.spec.d.ts +14 -0
- package/dist/cjs/src/file-box.spec.d.ts.map +1 -0
- package/dist/cjs/src/file-box.spec.js +393 -0
- package/dist/cjs/src/file-box.spec.js.map +1 -0
- package/dist/cjs/src/file-box.type.d.ts +103 -0
- package/dist/cjs/src/file-box.type.d.ts.map +1 -0
- package/dist/cjs/src/file-box.type.js +34 -0
- package/dist/cjs/src/file-box.type.js.map +1 -0
- package/dist/cjs/src/interface.d.ts +25 -0
- package/dist/cjs/src/interface.d.ts.map +1 -0
- package/dist/cjs/src/interface.js +3 -0
- package/dist/cjs/src/interface.js.map +1 -0
- package/dist/cjs/src/interface.spec.d.ts +3 -0
- package/dist/cjs/src/interface.spec.d.ts.map +1 -0
- package/dist/cjs/src/interface.spec.js +11 -0
- package/dist/cjs/src/interface.spec.js.map +1 -0
- package/dist/cjs/src/misc.d.ts +17 -0
- package/dist/cjs/src/misc.d.ts.map +1 -0
- package/dist/cjs/src/misc.js +137 -0
- package/dist/cjs/src/misc.js.map +1 -0
- package/dist/cjs/src/misc.spec.d.ts +3 -0
- package/dist/cjs/src/misc.spec.d.ts.map +1 -0
- package/dist/cjs/src/misc.spec.js +51 -0
- package/dist/cjs/src/misc.spec.js.map +1 -0
- package/dist/cjs/src/mod.d.ts +9 -0
- package/dist/cjs/src/mod.d.ts.map +1 -0
- package/dist/cjs/src/mod.js +12 -0
- package/dist/cjs/src/mod.js.map +1 -0
- package/dist/cjs/src/pure-functions/sized-chunk-transformer.d.ts +13 -0
- package/dist/cjs/src/pure-functions/sized-chunk-transformer.d.ts.map +1 -0
- package/dist/cjs/src/pure-functions/sized-chunk-transformer.js +45 -0
- package/dist/cjs/src/pure-functions/sized-chunk-transformer.js.map +1 -0
- package/dist/cjs/src/pure-functions/sized-chunk-transformer.spec.d.ts +3 -0
- package/dist/cjs/src/pure-functions/sized-chunk-transformer.spec.d.ts.map +1 -0
- package/dist/cjs/src/pure-functions/sized-chunk-transformer.spec.js +39 -0
- package/dist/cjs/src/pure-functions/sized-chunk-transformer.spec.js.map +1 -0
- package/dist/cjs/src/qrcode.d.ts +6 -0
- package/dist/cjs/src/qrcode.d.ts.map +1 -0
- package/dist/cjs/src/qrcode.js +35 -0
- package/dist/cjs/src/qrcode.js.map +1 -0
- package/dist/cjs/src/qrcode.spec.d.ts +3 -0
- package/dist/cjs/src/qrcode.spec.d.ts.map +1 -0
- package/dist/cjs/src/qrcode.spec.js +47 -0
- package/dist/cjs/src/qrcode.spec.js.map +1 -0
- package/dist/cjs/src/urn-registry/mod.d.ts +3 -0
- package/dist/cjs/src/urn-registry/mod.d.ts.map +1 -0
- package/dist/cjs/src/urn-registry/mod.js +6 -0
- package/dist/cjs/src/urn-registry/mod.js.map +1 -0
- package/dist/cjs/src/urn-registry/random-uuid.d.ts +4 -0
- package/dist/cjs/src/urn-registry/random-uuid.d.ts.map +1 -0
- package/dist/cjs/src/urn-registry/random-uuid.js +30 -0
- package/dist/cjs/src/urn-registry/random-uuid.js.map +1 -0
- package/dist/cjs/src/urn-registry/uniform-resource-name-registry.d.ts +76 -0
- package/dist/cjs/src/urn-registry/uniform-resource-name-registry.d.ts.map +1 -0
- package/dist/cjs/src/urn-registry/uniform-resource-name-registry.js +259 -0
- package/dist/cjs/src/urn-registry/uniform-resource-name-registry.js.map +1 -0
- package/dist/cjs/src/urn-registry/uniform-resource-name-registry.spec.d.ts +3 -0
- package/dist/cjs/src/urn-registry/uniform-resource-name-registry.spec.d.ts.map +1 -0
- package/dist/cjs/src/urn-registry/uniform-resource-name-registry.spec.js +81 -0
- package/dist/cjs/src/urn-registry/uniform-resource-name-registry.spec.js.map +1 -0
- package/dist/cjs/src/urn-registry/uuid-to-big-int.d.ts +8 -0
- package/dist/cjs/src/urn-registry/uuid-to-big-int.d.ts.map +1 -0
- package/dist/cjs/src/urn-registry/uuid-to-big-int.js +16 -0
- package/dist/cjs/src/urn-registry/uuid-to-big-int.js.map +1 -0
- package/dist/cjs/src/urn-registry/uuid-to-big-int.spec.d.ts +3 -0
- package/dist/cjs/src/urn-registry/uuid-to-big-int.spec.d.ts.map +1 -0
- package/dist/cjs/src/urn-registry/uuid-to-big-int.spec.js +17 -0
- package/dist/cjs/src/urn-registry/uuid-to-big-int.spec.js.map +1 -0
- package/dist/cjs/src/version.d.ts +5 -0
- package/dist/cjs/src/version.d.ts.map +1 -0
- package/dist/cjs/src/version.js +8 -0
- package/dist/cjs/src/version.js.map +1 -0
- package/dist/cjs/src/version.spec.d.ts +3 -0
- package/dist/cjs/src/version.spec.d.ts.map +1 -0
- package/dist/cjs/src/version.spec.js +9 -0
- package/dist/cjs/src/version.spec.js.map +1 -0
- package/dist/cjs/tests/integration.spec.d.ts +3 -0
- package/dist/cjs/tests/integration.spec.d.ts.map +1 -0
- package/dist/cjs/tests/integration.spec.js +8 -0
- package/dist/cjs/tests/integration.spec.js.map +1 -0
- package/dist/cjs/tests/network-timeout.spec.d.ts +3 -0
- package/dist/cjs/tests/network-timeout.spec.d.ts.map +1 -0
- package/dist/cjs/tests/network-timeout.spec.js +121 -0
- package/dist/cjs/tests/network-timeout.spec.js.map +1 -0
- package/dist/esm/src/config.d.ts +4 -0
- package/dist/esm/src/config.d.ts.map +1 -0
- package/dist/esm/src/config.js +5 -0
- package/dist/esm/src/config.js.map +1 -0
- package/dist/esm/src/file-box.d.ts +209 -0
- package/dist/esm/src/file-box.d.ts.map +1 -0
- package/dist/esm/src/file-box.js +775 -0
- package/dist/esm/src/file-box.js.map +1 -0
- package/dist/esm/src/file-box.spec.d.ts +14 -0
- package/dist/esm/src/file-box.spec.d.ts.map +1 -0
- package/dist/esm/src/file-box.spec.js +386 -0
- package/dist/esm/src/file-box.spec.js.map +1 -0
- package/dist/esm/src/file-box.type.d.ts +103 -0
- package/dist/esm/src/file-box.type.d.ts.map +1 -0
- package/dist/esm/src/file-box.type.js +31 -0
- package/dist/esm/src/file-box.type.js.map +1 -0
- package/dist/esm/src/interface.d.ts +25 -0
- package/dist/esm/src/interface.d.ts.map +1 -0
- package/dist/esm/src/interface.js +2 -0
- package/dist/esm/src/interface.js.map +1 -0
- package/dist/esm/src/interface.spec.d.ts +3 -0
- package/dist/esm/src/interface.spec.d.ts.map +1 -0
- package/dist/esm/src/interface.spec.js +9 -0
- package/dist/esm/src/interface.spec.js.map +1 -0
- package/dist/esm/src/misc.d.ts +17 -0
- package/dist/esm/src/misc.d.ts.map +1 -0
- package/dist/esm/src/misc.js +126 -0
- package/dist/esm/src/misc.js.map +1 -0
- package/dist/esm/src/misc.spec.d.ts +3 -0
- package/dist/esm/src/misc.spec.d.ts.map +1 -0
- package/dist/esm/src/misc.spec.js +49 -0
- package/dist/esm/src/misc.spec.js.map +1 -0
- package/dist/esm/src/mod.d.ts +9 -0
- package/dist/esm/src/mod.d.ts.map +1 -0
- package/dist/esm/src/mod.js +6 -0
- package/dist/esm/src/mod.js.map +1 -0
- package/dist/esm/src/pure-functions/sized-chunk-transformer.d.ts +13 -0
- package/dist/esm/src/pure-functions/sized-chunk-transformer.d.ts.map +1 -0
- package/dist/esm/src/pure-functions/sized-chunk-transformer.js +39 -0
- package/dist/esm/src/pure-functions/sized-chunk-transformer.js.map +1 -0
- package/dist/esm/src/pure-functions/sized-chunk-transformer.spec.d.ts +3 -0
- package/dist/esm/src/pure-functions/sized-chunk-transformer.spec.d.ts.map +1 -0
- package/dist/esm/src/pure-functions/sized-chunk-transformer.spec.js +37 -0
- package/dist/esm/src/pure-functions/sized-chunk-transformer.spec.js.map +1 -0
- package/dist/esm/src/qrcode.d.ts +6 -0
- package/dist/esm/src/qrcode.d.ts.map +1 -0
- package/dist/esm/src/qrcode.js +27 -0
- package/dist/esm/src/qrcode.js.map +1 -0
- package/dist/esm/src/qrcode.spec.d.ts +3 -0
- package/dist/esm/src/qrcode.spec.d.ts.map +1 -0
- package/dist/esm/src/qrcode.spec.js +45 -0
- package/dist/esm/src/qrcode.spec.js.map +1 -0
- package/dist/esm/src/urn-registry/mod.d.ts +3 -0
- package/dist/esm/src/urn-registry/mod.d.ts.map +1 -0
- package/dist/esm/src/urn-registry/mod.js +3 -0
- package/dist/esm/src/urn-registry/mod.js.map +1 -0
- package/dist/esm/src/urn-registry/random-uuid.d.ts +4 -0
- package/dist/esm/src/urn-registry/random-uuid.d.ts.map +1 -0
- package/dist/esm/src/urn-registry/random-uuid.js +4 -0
- package/dist/esm/src/urn-registry/random-uuid.js.map +1 -0
- package/dist/esm/src/urn-registry/uniform-resource-name-registry.d.ts +76 -0
- package/dist/esm/src/urn-registry/uniform-resource-name-registry.d.ts.map +1 -0
- package/dist/esm/src/urn-registry/uniform-resource-name-registry.js +253 -0
- package/dist/esm/src/urn-registry/uniform-resource-name-registry.js.map +1 -0
- package/dist/esm/src/urn-registry/uniform-resource-name-registry.spec.d.ts +3 -0
- package/dist/esm/src/urn-registry/uniform-resource-name-registry.spec.d.ts.map +1 -0
- package/dist/esm/src/urn-registry/uniform-resource-name-registry.spec.js +79 -0
- package/dist/esm/src/urn-registry/uniform-resource-name-registry.spec.js.map +1 -0
- package/dist/esm/src/urn-registry/uuid-to-big-int.d.ts +8 -0
- package/dist/esm/src/urn-registry/uuid-to-big-int.d.ts.map +1 -0
- package/dist/esm/src/urn-registry/uuid-to-big-int.js +13 -0
- package/dist/esm/src/urn-registry/uuid-to-big-int.js.map +1 -0
- package/dist/esm/src/urn-registry/uuid-to-big-int.spec.d.ts +3 -0
- package/dist/esm/src/urn-registry/uuid-to-big-int.spec.d.ts.map +1 -0
- package/dist/esm/src/urn-registry/uuid-to-big-int.spec.js +15 -0
- package/dist/esm/src/urn-registry/uuid-to-big-int.spec.js.map +1 -0
- package/dist/esm/src/version.d.ts +5 -0
- package/dist/esm/src/version.d.ts.map +1 -0
- package/dist/esm/src/version.js +5 -0
- package/dist/esm/src/version.js.map +1 -0
- package/dist/esm/src/version.spec.d.ts +3 -0
- package/dist/esm/src/version.spec.d.ts.map +1 -0
- package/dist/esm/src/version.spec.js +7 -0
- package/dist/esm/src/version.spec.js.map +1 -0
- package/dist/esm/tests/integration.spec.d.ts +3 -0
- package/dist/esm/tests/integration.spec.d.ts.map +1 -0
- package/dist/esm/tests/integration.spec.js +6 -0
- package/dist/esm/tests/integration.spec.js.map +1 -0
- package/dist/esm/tests/network-timeout.spec.d.ts +3 -0
- package/dist/esm/tests/network-timeout.spec.d.ts.map +1 -0
- package/dist/esm/tests/network-timeout.spec.js +119 -0
- package/dist/esm/tests/network-timeout.spec.js.map +1 -0
- package/package.json +85 -0
- package/src/config.ts +5 -0
- package/src/file-box.spec.ts +480 -0
- package/src/file-box.ts +1042 -0
- package/src/file-box.type.ts +145 -0
- package/src/interface.spec.ts +17 -0
- package/src/interface.ts +47 -0
- package/src/misc.spec.ts +70 -0
- package/src/misc.ts +157 -0
- package/src/mod.ts +33 -0
- package/src/pure-functions/sized-chunk-transformer.spec.ts +52 -0
- package/src/pure-functions/sized-chunk-transformer.ts +49 -0
- package/src/qrcode.spec.ts +55 -0
- package/src/qrcode.ts +38 -0
- package/src/typings.d.ts +1 -0
- package/src/urn-registry/mod.ts +7 -0
- package/src/urn-registry/random-uuid.ts +7 -0
- package/src/urn-registry/uniform-resource-name-registry.spec.ts +109 -0
- package/src/urn-registry/uniform-resource-name-registry.ts +342 -0
- package/src/urn-registry/uuid-to-big-int.spec.ts +19 -0
- package/src/urn-registry/uuid-to-big-int.ts +16 -0
- package/src/version.spec.ts +9 -0
- package/src/version.ts +4 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
test,
|
|
5
|
+
sinon,
|
|
6
|
+
} from 'tstest'
|
|
7
|
+
|
|
8
|
+
import { FileBox } from '../file-box.js'
|
|
9
|
+
|
|
10
|
+
import { UniformResourceNameRegistry } from './uniform-resource-name-registry.js'
|
|
11
|
+
|
|
12
|
+
test('UniformResourceNameRegistry class', async t => {
|
|
13
|
+
const QRCODE = 'test qrcode'
|
|
14
|
+
|
|
15
|
+
const urnRegistry = new UniformResourceNameRegistry()
|
|
16
|
+
await urnRegistry.init()
|
|
17
|
+
|
|
18
|
+
const fileBox = FileBox.fromQRCode(QRCODE)
|
|
19
|
+
const stream = await fileBox.toStream()
|
|
20
|
+
|
|
21
|
+
const uuid = await urnRegistry.save(stream)
|
|
22
|
+
|
|
23
|
+
const stream2 = await urnRegistry.load(uuid)
|
|
24
|
+
t.ok(stream2, 'should load stream')
|
|
25
|
+
|
|
26
|
+
const fileBox2 = FileBox.fromStream(stream2!, 'test')
|
|
27
|
+
const qrcode = await fileBox2.toQRCode()
|
|
28
|
+
|
|
29
|
+
t.equal(qrcode, QRCODE, 'should get back the qrcode data')
|
|
30
|
+
|
|
31
|
+
await t.resolves(() => urnRegistry.load(uuid), 'should not reject when load a UUID again')
|
|
32
|
+
|
|
33
|
+
await urnRegistry.destroy()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('expireMilliseconds: in time', async t => {
|
|
37
|
+
const expireMilliseconds = 3
|
|
38
|
+
const urnRegistry = new UniformResourceNameRegistry({
|
|
39
|
+
expireMilliseconds,
|
|
40
|
+
})
|
|
41
|
+
await urnRegistry.init()
|
|
42
|
+
|
|
43
|
+
const uuid = await urnRegistry.save(await FileBox.fromQRCode('qr').toStream())
|
|
44
|
+
await new Promise(resolve => setTimeout(resolve, 1))
|
|
45
|
+
await t.resolves(() => urnRegistry.load(uuid), `should not expire after 1ms (with ${expireMilliseconds}ms expire)`)
|
|
46
|
+
|
|
47
|
+
await urnRegistry.destroy()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('expireMilliseconds: time out', async t => {
|
|
51
|
+
const sandbox = sinon.createSandbox({
|
|
52
|
+
useFakeTimers: true,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const expireMilliseconds = 10 * 60 * 1000 // 10 minute
|
|
56
|
+
const urnRegistry = new UniformResourceNameRegistry({
|
|
57
|
+
expireMilliseconds,
|
|
58
|
+
})
|
|
59
|
+
await urnRegistry.init()
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Time: 0
|
|
63
|
+
*/
|
|
64
|
+
const uuid1 = await urnRegistry.save(await FileBox.fromQRCode('qr').toStream())
|
|
65
|
+
await t.resolves(() => urnRegistry.load(uuid1), 'should load uuid1 successfully')
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Time: 5
|
|
69
|
+
*/
|
|
70
|
+
await sandbox.clock.tickAsync(5 * 60 * 1000)
|
|
71
|
+
const uuid2 = await urnRegistry.save(await FileBox.fromQRCode('qr2').toStream())
|
|
72
|
+
await t.resolves(() => urnRegistry.load(uuid1), 'should load uuid1 successfully after 5 minutes')
|
|
73
|
+
await t.resolves(() => urnRegistry.load(uuid2), 'should load uuid2 successfully')
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Time: 11
|
|
77
|
+
*/
|
|
78
|
+
await sandbox.clock.tickAsync(6 * 60 * 1000)
|
|
79
|
+
await t.rejects(() => urnRegistry.load(uuid1), 'should load uuid1 fail because it is expired')
|
|
80
|
+
await t.resolves(() => urnRegistry.load(uuid2), 'should load uuid2 successfully 6 minutes after it has been saved')
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Time 20
|
|
84
|
+
*/
|
|
85
|
+
await sandbox.clock.tickAsync(9 * 60 * 1000)
|
|
86
|
+
await t.rejects(() => urnRegistry.load(uuid2), 'should load uuid2 fail because it is expired')
|
|
87
|
+
|
|
88
|
+
await urnRegistry.destroy()
|
|
89
|
+
sandbox.restore()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('URN FileBox helper smoke testing', async t => {
|
|
93
|
+
const QRCODE = 'test qrcode'
|
|
94
|
+
|
|
95
|
+
const urnRegistry = new UniformResourceNameRegistry()
|
|
96
|
+
|
|
97
|
+
const UUIDFileBox = urnRegistry.getFileBox()
|
|
98
|
+
|
|
99
|
+
const uuid = await UUIDFileBox
|
|
100
|
+
.fromQRCode(QRCODE)
|
|
101
|
+
.toUuid()
|
|
102
|
+
|
|
103
|
+
const qrcode = await UUIDFileBox
|
|
104
|
+
.fromUuid(uuid)
|
|
105
|
+
.toQRCode()
|
|
106
|
+
|
|
107
|
+
t.equal(qrcode, QRCODE, 'should get back the qrcode data')
|
|
108
|
+
urnRegistry.destroy()
|
|
109
|
+
})
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Huan(202110): Assignment and Resolution of Uniform Resource Names
|
|
3
|
+
* https://datatracker.ietf.org/wg/urn/about/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace
|
|
8
|
+
* ------------------------------------------------------------
|
|
9
|
+
* This specification defines a Uniform Resource Name namespace for
|
|
10
|
+
* UUIDs (Universally Unique IDentifier), also known as GUIDs (Globally
|
|
11
|
+
* Unique IDentifier). A UUID is 128 bits long, and can guarantee
|
|
12
|
+
* uniqueness across space and time. UUIDs were originally used in the
|
|
13
|
+
* Apollo Network Computing System and later in the Open Software
|
|
14
|
+
* Foundation's (OSF) Distributed Computing Environment (DCE), and then
|
|
15
|
+
* in Microsoft Windows platforms.
|
|
16
|
+
*
|
|
17
|
+
* The information here is meant to be a concise guide for those wishing
|
|
18
|
+
* to implement services using UUIDs as URNs. Nothing in this document
|
|
19
|
+
* should be construed to override the DCE standards that defined UUIDs.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* RFC 2141: Uniform Resource Names (URNs) Syntax
|
|
24
|
+
* ----------------------------------------------
|
|
25
|
+
* Uniform Resource Names (URNs) are intended to serve as persistent,
|
|
26
|
+
* location-independent, resource identifiers. This document sets
|
|
27
|
+
* forward the canonical syntax for URNs. A discussion of both existing
|
|
28
|
+
* legacy and new namespaces and requirements for URN presentation and
|
|
29
|
+
* transmission are presented. Finally, there is a discussion of URN
|
|
30
|
+
* equivalence and how to determine it.
|
|
31
|
+
*/
|
|
32
|
+
import fs from 'fs'
|
|
33
|
+
import os from 'os'
|
|
34
|
+
import path from 'path'
|
|
35
|
+
|
|
36
|
+
import type { Readable } from 'stream'
|
|
37
|
+
|
|
38
|
+
import { instanceToClass } from 'clone-class'
|
|
39
|
+
import { log } from 'brolog'
|
|
40
|
+
|
|
41
|
+
import {
|
|
42
|
+
FileBox,
|
|
43
|
+
} from '../file-box.js'
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
randomUuid,
|
|
47
|
+
} from './random-uuid.js'
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A UUID will be only keep for a certain time.
|
|
51
|
+
*/
|
|
52
|
+
const DEFAULT_UUID_EXPIRE_MINUTES = 30
|
|
53
|
+
const DEFAULT_UUID_PURGE_INTERVAL_MINUTES = 1
|
|
54
|
+
|
|
55
|
+
interface UniformResourceNameRegistryOptions {
|
|
56
|
+
expireMilliseconds? : number,
|
|
57
|
+
storeDir? : string,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class UniformResourceNameRegistry {
|
|
61
|
+
|
|
62
|
+
protected static processExitMap = new WeakMap<
|
|
63
|
+
UniformResourceNameRegistry,
|
|
64
|
+
Function
|
|
65
|
+
>()
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The directory that store all UUID files
|
|
69
|
+
*/
|
|
70
|
+
protected storeDir: string
|
|
71
|
+
|
|
72
|
+
protected expireMilliseconds: number
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Key: expiretime
|
|
76
|
+
* Value: the array of UUID that will expires after the `expiretime` (Key)
|
|
77
|
+
*/
|
|
78
|
+
protected uuidExpiringTable: Map<
|
|
79
|
+
number,
|
|
80
|
+
string[]
|
|
81
|
+
>
|
|
82
|
+
|
|
83
|
+
protected purgerTimer?: ReturnType<typeof setInterval>
|
|
84
|
+
|
|
85
|
+
constructor (
|
|
86
|
+
options: UniformResourceNameRegistryOptions = {},
|
|
87
|
+
) {
|
|
88
|
+
log.verbose('UniformResourceNameRegistry', 'constructor("%s")', JSON.stringify(options))
|
|
89
|
+
|
|
90
|
+
this.uuidExpiringTable = new Map()
|
|
91
|
+
|
|
92
|
+
this.expireMilliseconds = options.expireMilliseconds ?? (DEFAULT_UUID_EXPIRE_MINUTES * 60 * 1000 * 1000)
|
|
93
|
+
this.storeDir = options.storeDir || path.join(
|
|
94
|
+
os.tmpdir(),
|
|
95
|
+
'file-box-urn-registry.' + String(process.pid),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Return a FileBox Interface with the current URN Registry for conience
|
|
101
|
+
*/
|
|
102
|
+
getFileBox (): typeof FileBox {
|
|
103
|
+
this.init()
|
|
104
|
+
|
|
105
|
+
class UUIDFileBox extends FileBox {}
|
|
106
|
+
UUIDFileBox.setUuidLoader(this.load.bind(this))
|
|
107
|
+
UUIDFileBox.setUuidSaver(this.save.bind(this))
|
|
108
|
+
return UUIDFileBox
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* init the UUID registry
|
|
113
|
+
*
|
|
114
|
+
* must be called before use.
|
|
115
|
+
*/
|
|
116
|
+
init () {
|
|
117
|
+
log.verbose('UniformResourceNameRegistry', 'init()')
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const stat = fs.statSync(this.storeDir)
|
|
121
|
+
if (!stat.isDirectory()) {
|
|
122
|
+
throw new Error(this.storeDir + ' is Not a directory')
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
126
|
+
fs.mkdirSync(this.storeDir, { recursive: true })
|
|
127
|
+
} else {
|
|
128
|
+
throw e
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!this.purgerTimer) {
|
|
133
|
+
this.addProcessExitListener()
|
|
134
|
+
|
|
135
|
+
this.purgerTimer = setInterval(
|
|
136
|
+
() => this.purgeExpiredUuid(),
|
|
137
|
+
DEFAULT_UUID_PURGE_INTERVAL_MINUTES * 60 * 1000,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected purgeExpiredUuid () {
|
|
144
|
+
log.verbose('UniformResourceNameRegistry', 'purgeExpiredUuid()')
|
|
145
|
+
const expireTimeList = [ ...this.uuidExpiringTable.keys() ]
|
|
146
|
+
.sort((a, b) => Number(a) - Number(b))
|
|
147
|
+
|
|
148
|
+
for (const expireTime of expireTimeList) {
|
|
149
|
+
if (Date.now() < expireTime) {
|
|
150
|
+
// The earliest expire time is in the future
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const uuidList = this.uuidExpiringTable.get(expireTime) || []
|
|
155
|
+
this.uuidExpiringTable.delete(expireTime)
|
|
156
|
+
|
|
157
|
+
for (const uuid of uuidList) {
|
|
158
|
+
this.purge(uuid).catch(console.error)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Clean up by calling this.destroy() before process exit
|
|
165
|
+
*/
|
|
166
|
+
protected addProcessExitListener () {
|
|
167
|
+
log.verbose('UniformResourceNameRegistry', 'addProcessExitListener()')
|
|
168
|
+
|
|
169
|
+
const Klass = instanceToClass(this, UniformResourceNameRegistry)
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* If we have already registered the listener, do nothing.
|
|
173
|
+
*/
|
|
174
|
+
if (Klass.processExitMap.has(this)) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const destroyCallback = () => this.destroy()
|
|
179
|
+
|
|
180
|
+
process.addListener('exit', destroyCallback)
|
|
181
|
+
Klass.processExitMap.set(
|
|
182
|
+
this,
|
|
183
|
+
() => process.removeListener('exit', destroyCallback),
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
protected uuidFileName (uuid: string): string {
|
|
188
|
+
return path.join(
|
|
189
|
+
this.storeDir,
|
|
190
|
+
uuid + '.dat',
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @deprecated use `load()` instead
|
|
196
|
+
*/
|
|
197
|
+
async resolve (uuid: string): Promise<Readable> {
|
|
198
|
+
log.warn('UniformResourceNameRegistry', 'resolve() is deprecated: use `load()` instead.\n%s', new Error().stack)
|
|
199
|
+
return this.load(uuid)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* `resolve()` can only be used once.
|
|
204
|
+
* after resolve(), the UUID will be not exist any more
|
|
205
|
+
*/
|
|
206
|
+
async load (uuid: string): Promise<Readable> {
|
|
207
|
+
log.verbose('UniformResourceNameRegistry', 'load(%s)', uuid)
|
|
208
|
+
|
|
209
|
+
const filename = this.uuidFileName(uuid)
|
|
210
|
+
const stream = fs.createReadStream(filename)
|
|
211
|
+
|
|
212
|
+
await new Promise<void>((resolve, reject) => {
|
|
213
|
+
stream.on('ready', resolve)
|
|
214
|
+
stream.on('error', reject)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
return stream
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @deprecated use `save()` instead
|
|
222
|
+
*/
|
|
223
|
+
async register (stream: Readable): Promise<string> {
|
|
224
|
+
log.verbose('UniformResourceNameRegistry', 'register() deprecated: use save() instead.\n%s', new Error().stack)
|
|
225
|
+
return this.save(stream)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Save the `Readable` stream and return a random UUID
|
|
230
|
+
* The UUID will be expired after MAX_KEEP_MINUTES
|
|
231
|
+
*/
|
|
232
|
+
async save (stream: Readable): Promise<string> {
|
|
233
|
+
log.verbose('UniformResourceNameRegistry', 'save(stream)')
|
|
234
|
+
|
|
235
|
+
const uuid = randomUuid()
|
|
236
|
+
|
|
237
|
+
const fileStream = fs.createWriteStream(this.uuidFileName(uuid))
|
|
238
|
+
const future = new Promise<void>((resolve, reject) => {
|
|
239
|
+
stream.on('end', resolve)
|
|
240
|
+
stream.on('error', reject)
|
|
241
|
+
fileStream.on('error', reject)
|
|
242
|
+
})
|
|
243
|
+
stream.pipe(fileStream)
|
|
244
|
+
await future
|
|
245
|
+
|
|
246
|
+
this.addToExpiringTable(uuid)
|
|
247
|
+
|
|
248
|
+
return uuid
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Set a timer to execute delete callback after `expireMilliseconds`
|
|
253
|
+
*/
|
|
254
|
+
protected addToExpiringTable (uuid: string): void {
|
|
255
|
+
log.verbose('UniformResourceNameRegistry', 'addToExpiringTable(%s)', uuid)
|
|
256
|
+
|
|
257
|
+
const expireTime = Date.now() + this.expireMilliseconds
|
|
258
|
+
const expireTimeInterval = DEFAULT_UUID_PURGE_INTERVAL_MINUTES * 60 * 1000
|
|
259
|
+
|
|
260
|
+
// https://stackoverflow.com/a/22687090
|
|
261
|
+
const expireTimeNearestMinute = Math.ceil(expireTime / expireTimeInterval) * expireTimeInterval
|
|
262
|
+
|
|
263
|
+
const uuidList = this.uuidExpiringTable.get(expireTime) || []
|
|
264
|
+
uuidList.push(uuid)
|
|
265
|
+
this.uuidExpiringTable.set(expireTimeNearestMinute, uuidList)
|
|
266
|
+
|
|
267
|
+
log.silly('UniformResourceNameRegistry', 'addToExpiringTable() uuidList.length = %s, expireTime = %s',
|
|
268
|
+
uuidList.length,
|
|
269
|
+
expireTimeNearestMinute,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
protected async purge (uuid: string): Promise<void> {
|
|
274
|
+
log.verbose('UniformResourceNameRegistry', 'purge(%s)', uuid)
|
|
275
|
+
|
|
276
|
+
const file = this.uuidFileName(uuid)
|
|
277
|
+
try {
|
|
278
|
+
await fs.promises.unlink(file)
|
|
279
|
+
log.silly('UniformResourceNameRegistry', 'purge() %s', file)
|
|
280
|
+
} catch (e) {
|
|
281
|
+
log.warn('UniformResourceNameRegistry', 'purge() rejection:', (e as Error).message)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* destroy the urn registry.
|
|
287
|
+
*
|
|
288
|
+
* This function will be called automatically at `process.on(exit)`
|
|
289
|
+
* however, it till need to be called before the program ends
|
|
290
|
+
* because there have some timers in eventloop task list
|
|
291
|
+
*/
|
|
292
|
+
destroy () {
|
|
293
|
+
log.verbose('UniformResourceNameRegistry', 'destroy() %s UUIDs left',
|
|
294
|
+
[ ...this.uuidExpiringTable.values() ].flat().length,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
if (this.purgerTimer) {
|
|
298
|
+
log.verbose('UniformResourceNameRegistry', 'destroy() clearing purger timer ...')
|
|
299
|
+
clearInterval(this.purgerTimer)
|
|
300
|
+
this.purgerTimer = undefined
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const Klass = instanceToClass(this, UniformResourceNameRegistry)
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Remove process exit listener
|
|
307
|
+
*/
|
|
308
|
+
if (Klass.processExitMap.has(this)) {
|
|
309
|
+
log.verbose('UniformResourceNameRegistry', 'destroy() remove process `exit` listener ...')
|
|
310
|
+
const fn = Klass.processExitMap.get(this)
|
|
311
|
+
Klass.processExitMap.delete(this)
|
|
312
|
+
fn && fn()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Clean up all the files
|
|
317
|
+
*/
|
|
318
|
+
log.verbose('UniformResourceNameRegistry', 'destroy() fs.rmSync(%s) ...', this.storeDir)
|
|
319
|
+
try {
|
|
320
|
+
/**
|
|
321
|
+
* Huan(202110):
|
|
322
|
+
* Check for the `this.uuidDir` exist or not
|
|
323
|
+
* when we are running unit tests, we might instanciate multiple UniformResourceNameRegistry
|
|
324
|
+
* which will cause the `this.destroy()` to be registered multiple times
|
|
325
|
+
*/
|
|
326
|
+
fs.statSync(this.storeDir)
|
|
327
|
+
|
|
328
|
+
fs.rmSync(this.storeDir, { recursive: true })
|
|
329
|
+
log.verbose('UniformResourceNameRegistry', 'destroy() fs.rmSync(%s) done', this.storeDir)
|
|
330
|
+
|
|
331
|
+
} catch (e) {
|
|
332
|
+
if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
333
|
+
log.verbose('UniformResourceNameRegistry', 'destroy() %s not exist', this.storeDir)
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
log.warn('UniformResourceNameRegistry', 'destroy() fs.rmSync(%s) exception: %s', (e as Error).message)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export { UniformResourceNameRegistry }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
|
+
|
|
3
|
+
import { test } from 'tstest'
|
|
4
|
+
|
|
5
|
+
import { uuidToBigInt } from './uuid-to-big-int.js'
|
|
6
|
+
|
|
7
|
+
test('uuidToBigInt() Nil UUID', async t => {
|
|
8
|
+
const uuid = '00000000-0000-0000-0000-000000000000'
|
|
9
|
+
const bigInt = uuidToBigInt(uuid)
|
|
10
|
+
t.equal(bigInt.toString(), '0', 'should get zero for a Nil UUID')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('uuidToBigInt() Nil UUID', async t => {
|
|
14
|
+
const UUID = '00bd7c03-a690-48d3-a1fe-1314574e4fc1'
|
|
15
|
+
const EXPECTED = BigInt('0x00bd7c03a69048d3a1fe1314574e4fc1')
|
|
16
|
+
|
|
17
|
+
const bigInt = uuidToBigInt(UUID)
|
|
18
|
+
t.equal(bigInt, EXPECTED, 'should convert UUID right')
|
|
19
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert UUID to BigInt
|
|
3
|
+
* @param uuid
|
|
4
|
+
* @returns BigInt
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function uuidToBigInt (uuid: string): BigInt {
|
|
8
|
+
// credit: https://stackoverflow.com/a/58014300/1123955
|
|
9
|
+
const hexBytes = `0x${uuid.replace(/-/g, '')}`
|
|
10
|
+
const bigInteger = BigInt(hexBytes)
|
|
11
|
+
return bigInteger
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
uuidToBigInt,
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
|
+
|
|
3
|
+
import { test } from 'tstest'
|
|
4
|
+
|
|
5
|
+
import { VERSION } from './version.js'
|
|
6
|
+
|
|
7
|
+
test('Make sure the VERSION is fresh in source code', async t => {
|
|
8
|
+
t.equal(VERSION, '0.0.0', 'version should be 0.0.0 in source code, only updated before publish to NPM')
|
|
9
|
+
})
|
package/src/version.ts
ADDED