@e280/mammoth 0.1.0-4 → 0.1.0-6
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/README.md +13 -11
- package/package.json +2 -2
- package/s/browser/example.ts +1 -1
- package/s/browser/opfs-bucket.ts +0 -6
- package/s/core/consts.ts +9 -0
- package/s/core/mammoth.ts +37 -93
- package/s/core/memory-bucket.ts +0 -4
- package/s/core/types.ts +24 -1
- package/s/core/utils/is-expired.ts +10 -0
- package/s/core/utils/manifest.ts +89 -0
- package/s/core/utils/not-found.ts +7 -0
- package/s/core/utils/save-and-hash.ts +25 -0
- package/s/node/disk-bucket.ts +0 -5
- package/s/test.ts +13 -14
- package/x/browser/example.js +1 -1
- package/x/browser/example.js.map +1 -1
- package/x/browser/opfs-bucket.d.ts +0 -1
- package/x/browser/opfs-bucket.js +0 -5
- package/x/browser/opfs-bucket.js.map +1 -1
- package/x/core/consts.d.ts +5 -0
- package/x/core/consts.js +7 -0
- package/x/core/consts.js.map +1 -0
- package/x/core/mammoth.d.ts +3 -9
- package/x/core/mammoth.js +36 -76
- package/x/core/mammoth.js.map +1 -1
- package/x/core/memory-bucket.d.ts +0 -1
- package/x/core/memory-bucket.js +0 -3
- package/x/core/memory-bucket.js.map +1 -1
- package/x/core/types.d.ts +17 -1
- package/x/core/utils/is-expired.d.ts +2 -0
- package/x/core/utils/is-expired.js +7 -0
- package/x/core/utils/is-expired.js.map +1 -0
- package/x/core/utils/manifest.d.ts +18 -0
- package/x/core/utils/manifest.js +69 -0
- package/x/core/utils/manifest.js.map +1 -0
- package/x/core/utils/not-found.d.ts +2 -0
- package/x/core/utils/not-found.js +4 -0
- package/x/core/utils/not-found.js.map +1 -0
- package/x/core/utils/save-and-hash.d.ts +5 -0
- package/x/core/utils/save-and-hash.js +19 -0
- package/x/core/utils/save-and-hash.js.map +1 -0
- package/x/node/disk-bucket.d.ts +0 -1
- package/x/node/disk-bucket.js +0 -4
- package/x/node/disk-bucket.js.map +1 -1
- package/x/test.js +13 -14
- package/x/test.js.map +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
|
|
2
|
-

|
|
3
3
|
|
|
4
|
-
# 🦣
|
|
5
|
-
|
|
4
|
+
# 🦣 mammoth
|
|
5
|
+
> *big files. small api.*
|
|
6
|
+
|
|
7
|
+
**mammoth is a content-addressed file store.** files are streamed into a bucket, identified by their blake3 hash, and automatically deduplicated. mammoth works the same whether it's backed by a bucket in memory, on disk, in the cloud, or in the browser's opfs.
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
npm install @e280/mammoth
|
|
@@ -35,8 +37,8 @@ const mammoth = new Mammoth()
|
|
|
35
37
|
import {Kv} from "@e280/kv"
|
|
36
38
|
|
|
37
39
|
const mammoth = new Mammoth(
|
|
38
|
-
new Kv(),
|
|
39
40
|
new MemoryBucket(),
|
|
41
|
+
new Kv(),
|
|
40
42
|
)
|
|
41
43
|
```
|
|
42
44
|
defaults shown, so, this is equivalent:
|
|
@@ -51,8 +53,8 @@ const mammoth = new Mammoth()
|
|
|
51
53
|
import {LevelDriver} from "@e280/kv/level"
|
|
52
54
|
|
|
53
55
|
const mammoth = new Mammoth(
|
|
54
|
-
new Kv(new LevelDriver("./data/kv")),
|
|
55
56
|
new DiskBucket("./data/bucket"),
|
|
57
|
+
new Kv(new LevelDriver("./data/kv")),
|
|
56
58
|
)
|
|
57
59
|
```
|
|
58
60
|
- **opfs bucket,** for local storage in the browser. *(note the import paths)*
|
|
@@ -62,24 +64,24 @@ const mammoth = new Mammoth()
|
|
|
62
64
|
import {Kv, StorageDriver} from "@e280/kv"
|
|
63
65
|
|
|
64
66
|
const mammoth = new Mammoth(
|
|
65
|
-
new Kv(new StorageDriver(localStorage)),
|
|
66
67
|
new OpfsBucket(await navigator.storage.getDirectory()),
|
|
68
|
+
new Kv(new StorageDriver(localStorage)),
|
|
67
69
|
)
|
|
68
70
|
```
|
|
69
71
|
|
|
70
|
-
### 🦣 mammoth
|
|
72
|
+
### 🦣 more mammoth methods.
|
|
71
73
|
- **check if a file exists,** get back a boolean.
|
|
72
74
|
```ts
|
|
73
75
|
const exists = await mammoth.has(hash)
|
|
74
76
|
```
|
|
75
|
-
- **get file
|
|
77
|
+
- **get file info,** including size in bytes, added timestamp, and bucket id.
|
|
76
78
|
```ts
|
|
77
|
-
const size = await mammoth.
|
|
79
|
+
const {size, added, id} = await mammoth.info(hash)
|
|
78
80
|
```
|
|
79
81
|
- **get stats,** for the whole datalake.
|
|
80
82
|
```ts
|
|
81
83
|
await mammoth.stats()
|
|
82
|
-
// {size:
|
|
84
|
+
// {count: 123, size: 123456789}
|
|
83
85
|
```
|
|
84
86
|
- **loop over all hashes,** for all stored files.
|
|
85
87
|
```ts
|
|
@@ -91,5 +93,5 @@ const mammoth = new Mammoth()
|
|
|
91
93
|
|
|
92
94
|
<br/><br/>
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
*https://e280.org/*
|
|
95
97
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e280/mammoth",
|
|
3
|
-
"version": "0.1.0-
|
|
3
|
+
"version": "0.1.0-6",
|
|
4
4
|
"description": "big file storage",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@e280/kv": "^0.1.5",
|
|
27
|
-
"@e280/stz": "^0.3.
|
|
27
|
+
"@e280/stz": "^0.3.5",
|
|
28
28
|
"@noble/hashes": "^2.2.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
package/s/browser/example.ts
CHANGED
|
@@ -15,7 +15,7 @@ export async function example() {
|
|
|
15
15
|
const bucket = new OpfsBucket(directory)
|
|
16
16
|
|
|
17
17
|
// setup the mammoth
|
|
18
|
-
const mammoth = new Mammoth(
|
|
18
|
+
const mammoth = new Mammoth(bucket, kv)
|
|
19
19
|
const myFile = new Blob([new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF])])
|
|
20
20
|
|
|
21
21
|
// write a file as an example
|
package/s/browser/opfs-bucket.ts
CHANGED
|
@@ -20,12 +20,6 @@ export class OpfsBucket implements Bucket {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
async size(id: Id) {
|
|
24
|
-
const handle = await this.#directory.getFileHandle(id)
|
|
25
|
-
const file = await handle.getFile()
|
|
26
|
-
return file.size
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
async delete(id: Id) {
|
|
30
24
|
try {
|
|
31
25
|
await this.#directory.removeEntry(id)
|
package/s/core/consts.ts
ADDED
package/s/core/mammoth.ts
CHANGED
|
@@ -1,138 +1,82 @@
|
|
|
1
1
|
|
|
2
2
|
import {Kv} from "@e280/kv"
|
|
3
|
-
import {
|
|
4
|
-
import {blake3} from "@noble/hashes/blake3.js"
|
|
3
|
+
import {lane, queue} from "@e280/stz"
|
|
5
4
|
|
|
6
|
-
import {
|
|
5
|
+
import {consts} from "./consts.js"
|
|
6
|
+
import {Hash, Bucket} from "./types.js"
|
|
7
|
+
import {Manifest} from "./utils/manifest.js"
|
|
7
8
|
import {randomId} from "./utils/random-id.js"
|
|
8
9
|
import {MemoryBucket} from "./memory-bucket.js"
|
|
10
|
+
import {saveAndHash} from "./utils/save-and-hash.js"
|
|
9
11
|
|
|
10
12
|
/** file storage datalake, content-addressed with blake3 hashes. */
|
|
11
13
|
export class Mammoth {
|
|
12
|
-
#ids
|
|
13
|
-
#wip
|
|
14
|
-
#stats
|
|
15
14
|
#bucket
|
|
15
|
+
#manifest
|
|
16
|
+
#wholesome = lane(consts.max_jobs)
|
|
16
17
|
|
|
17
|
-
constructor(
|
|
18
|
-
|
|
19
|
-
/** key-value metadata manifest. */
|
|
20
|
-
kv = new Kv(),
|
|
21
|
-
|
|
22
|
-
/** dumb raw file blob storage. */
|
|
23
|
-
bucket: Bucket = new MemoryBucket(),
|
|
24
|
-
|
|
25
|
-
) {
|
|
26
|
-
this.#ids = kv.scope<string>("ids")
|
|
27
|
-
this.#wip = kv.scope<{created: number}>("wip")
|
|
28
|
-
this.#stats = kv.store<{size: number}>("stats")
|
|
18
|
+
constructor(bucket: Bucket = new MemoryBucket(), kv = new Kv()) {
|
|
29
19
|
this.#bucket = bucket
|
|
20
|
+
this.#manifest = new Manifest(kv)
|
|
30
21
|
}
|
|
31
22
|
|
|
32
23
|
async has(hash: Hash) {
|
|
33
|
-
return this.#
|
|
24
|
+
return this.#manifest.hasHash(hash)
|
|
34
25
|
}
|
|
35
26
|
|
|
36
|
-
async
|
|
37
|
-
|
|
38
|
-
return this.#bucket.size(id)
|
|
27
|
+
async info(hash: Hash) {
|
|
28
|
+
return this.#manifest.needInfo(hash)
|
|
39
29
|
}
|
|
40
30
|
|
|
41
31
|
async read(hash: Hash) {
|
|
42
|
-
const id = await this.#
|
|
32
|
+
const {id} = await this.#manifest.needInfo(hash)
|
|
43
33
|
return this.#bucket.read(id)
|
|
44
34
|
}
|
|
45
35
|
|
|
46
36
|
async delete(hash: Hash) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
await this.#wholesome(async() => {
|
|
38
|
+
const info = await this.#manifest.getInfo(hash)
|
|
39
|
+
if (info) {
|
|
40
|
+
await this.#manifest.deleteInfo(hash)
|
|
41
|
+
await this.#bucket.delete(info.id)
|
|
42
|
+
await this.#manifest.statsRemoveFile(info.size)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
54
45
|
}
|
|
55
46
|
|
|
56
47
|
async write(readable: ReadableStream<Uint8Array>): Promise<Hash> {
|
|
57
48
|
const id = randomId()
|
|
58
|
-
await this.#wip.set(id, {created: Date.now()})
|
|
59
49
|
|
|
60
|
-
|
|
50
|
+
await this.#manifest.addWip(id)
|
|
51
|
+
const {hash, size} = await saveAndHash(this.#bucket, id, readable)
|
|
61
52
|
|
|
62
|
-
await this.#
|
|
63
|
-
|
|
53
|
+
await this.#wholesome(async() => {
|
|
54
|
+
if (await this.#manifest.hasHash(hash)) {
|
|
55
|
+
await this.#bucket.delete(id) // forget this new file (we already have it)
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
await this.#manifest.saveInfo(hash, {id, size, added: Date.now()})
|
|
59
|
+
await this.#manifest.statsAddFile(size)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
64
62
|
|
|
63
|
+
await this.#manifest.deleteWip(id)
|
|
65
64
|
void this.#selfClean().catch(() => {})
|
|
66
|
-
|
|
67
65
|
return hash
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
async* hashes() {
|
|
71
|
-
yield* this.#
|
|
69
|
+
yield* this.#manifest.hashes()
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
async stats() {
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async #getId(hash: Hash): Promise<Id | undefined> {
|
|
79
|
-
return this.#ids.get<string>(hash)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async #needId(hash: Hash): Promise<Id> {
|
|
83
|
-
return got(await this.#getId(hash), `file not found by hash "${hash}"`)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async #hashAndSave(id: Id, readable: ReadableStream<Uint8Array>) {
|
|
87
|
-
let size = 0
|
|
88
|
-
const pipe = new TransformStream()
|
|
89
|
-
const done = this.#bucket.write(id, pipe.readable)
|
|
90
|
-
const writer = pipe.writable.getWriter()
|
|
91
|
-
const hasher = blake3.create()
|
|
92
|
-
|
|
93
|
-
for await (const chunk of readable) {
|
|
94
|
-
await writer.write(chunk as Uint8Array<ArrayBuffer>)
|
|
95
|
-
hasher.update(chunk)
|
|
96
|
-
size += chunk.byteLength
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
await writer.close()
|
|
100
|
-
await done
|
|
101
|
-
|
|
102
|
-
const hash = hex.fromBytes(hasher.digest())
|
|
103
|
-
return {hash, size}
|
|
73
|
+
return this.#manifest.getStats()
|
|
104
74
|
}
|
|
105
75
|
|
|
106
|
-
#finalizeWrite = queue(async(hash: Hash, id: Id, size: number) => {
|
|
107
|
-
const existingId = await this.#getId(hash)
|
|
108
|
-
|
|
109
|
-
if (existingId) {
|
|
110
|
-
await this.#bucket.delete(id)
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
await this.#ids.set(hash, id)
|
|
114
|
-
await this.#addSize(size)
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
#addSize = queue(async(sizeChange: number) => {
|
|
119
|
-
const oldStats = await this.stats()
|
|
120
|
-
await this.#stats.set({...oldStats, size: oldStats.size + sizeChange})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
76
|
#selfClean = queue(async() => {
|
|
124
|
-
const
|
|
125
|
-
const expiredIds: Id[] = []
|
|
126
|
-
|
|
127
|
-
for await (const [id, {created}] of this.#wip.entries({limit: 64})) {
|
|
128
|
-
const since = now - created
|
|
129
|
-
const isExpired = since > time.days(7)
|
|
130
|
-
if (isExpired)
|
|
131
|
-
expiredIds.push(id)
|
|
132
|
-
}
|
|
133
|
-
|
|
77
|
+
const expiredIds = await this.#manifest.getExpiredIds()
|
|
134
78
|
await Promise.all(expiredIds.map(id => this.#bucket.delete(id)))
|
|
135
|
-
await this.#
|
|
79
|
+
await this.#manifest.deleteWip(...expiredIds)
|
|
136
80
|
})
|
|
137
81
|
}
|
|
138
82
|
|
package/s/core/memory-bucket.ts
CHANGED
package/s/core/types.ts
CHANGED
|
@@ -2,15 +2,38 @@
|
|
|
2
2
|
export type Hash = string
|
|
3
3
|
export type Id = string
|
|
4
4
|
|
|
5
|
+
/** file blob store. */
|
|
5
6
|
export type Bucket = {
|
|
6
7
|
has(id: Id): Promise<boolean>
|
|
7
|
-
size(id: Id): Promise<number>
|
|
8
8
|
delete(id: Id): Promise<void>
|
|
9
9
|
read(id: Id): Promise<Blob>
|
|
10
10
|
write(id: Id, readable: ReadableStream<Uint8Array>): Promise<void>
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** metadata for a single file. */
|
|
14
|
+
export type Info = {
|
|
15
|
+
|
|
16
|
+
/** bucket id for this file's data. */
|
|
17
|
+
id: Id
|
|
18
|
+
|
|
19
|
+
/** file size in bytes. */
|
|
20
|
+
size: number
|
|
21
|
+
|
|
22
|
+
/** when this file was added to the datalake. */
|
|
23
|
+
added: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** statistics for the whole datalake. */
|
|
13
27
|
export type Stats = {
|
|
28
|
+
|
|
29
|
+
/** total number of bytes in the whole datalake. */
|
|
14
30
|
size: number
|
|
31
|
+
|
|
32
|
+
/** total number of files in the datalake. */
|
|
33
|
+
count: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type Wip = {
|
|
37
|
+
created: number
|
|
15
38
|
}
|
|
16
39
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
|
|
2
|
+
import {Kv} from "@e280/kv"
|
|
3
|
+
import {collect, got} from "@e280/stz"
|
|
4
|
+
import {consts} from "../consts.js"
|
|
5
|
+
import {notFound} from "./not-found.js"
|
|
6
|
+
import {isExpired} from "./is-expired.js"
|
|
7
|
+
import {Hash, Id, Info, Stats, Wip} from "../types.js"
|
|
8
|
+
|
|
9
|
+
export class Manifest {
|
|
10
|
+
|
|
11
|
+
/** associates file hashes with bucket ids */
|
|
12
|
+
#info
|
|
13
|
+
|
|
14
|
+
/** temporary record of writes in-progress */
|
|
15
|
+
#wip
|
|
16
|
+
|
|
17
|
+
/** statistics about the whole datalake */
|
|
18
|
+
#stats
|
|
19
|
+
|
|
20
|
+
constructor(kv = new Kv()) {
|
|
21
|
+
this.#info = kv.scope<Info>("info")
|
|
22
|
+
this.#wip = kv.scope<Wip>("wip")
|
|
23
|
+
this.#stats = kv.store<Stats>("stats")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async* hashes() {
|
|
27
|
+
yield* this.#info.keys()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async hasHash(hash: Hash) {
|
|
31
|
+
return this.#info.has(hash)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async saveInfo(hash: Hash, info: Info) {
|
|
35
|
+
await this.#info.set(hash, info)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getInfo(hash: Hash) {
|
|
39
|
+
return await this.#info.get(hash)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async needInfo(hash: Hash) {
|
|
43
|
+
return got(await this.getInfo(hash), notFound(hash))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async deleteInfo(hash: Hash) {
|
|
47
|
+
await this.#info.del(hash)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async addWip(id: Id) {
|
|
51
|
+
await this.#wip.set(id, {created: Date.now()})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async deleteWip(...ids: Id[]) {
|
|
55
|
+
await this.#wip.del(...ids)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async statsAddFile(size: number) {
|
|
59
|
+
await this.#updateStats(stats => {
|
|
60
|
+
stats.count += 1
|
|
61
|
+
stats.size += size
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async statsRemoveFile(size: number) {
|
|
66
|
+
await this.#updateStats(stats => {
|
|
67
|
+
stats.count -= 1
|
|
68
|
+
stats.size -= size
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async getExpiredIds() {
|
|
73
|
+
const limit = consts.self_clean_limit
|
|
74
|
+
return (await collect(this.#wip.entries({limit})))
|
|
75
|
+
.filter(([,wip]) => isExpired(wip))
|
|
76
|
+
.map(([id]) => id)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getStats() {
|
|
80
|
+
return structuredClone((await this.#stats.get()) ?? {count: 0, size: 0})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async #updateStats(fn: (stats: Stats) => void) {
|
|
84
|
+
const stats = await this.getStats()
|
|
85
|
+
fn(stats)
|
|
86
|
+
await this.#stats.set(stats)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
import {hex} from "@e280/stz"
|
|
3
|
+
import {blake3} from "@noble/hashes/blake3.js"
|
|
4
|
+
import {Bucket, Id} from "../types.js"
|
|
5
|
+
|
|
6
|
+
export async function saveAndHash(bucket: Bucket, id: Id, readable: ReadableStream<Uint8Array>) {
|
|
7
|
+
let size = 0
|
|
8
|
+
const pipe = new TransformStream()
|
|
9
|
+
const done = bucket.write(id, pipe.readable)
|
|
10
|
+
const writer = pipe.writable.getWriter()
|
|
11
|
+
const hasher = blake3.create()
|
|
12
|
+
|
|
13
|
+
for await (const chunk of readable) {
|
|
14
|
+
await writer.write(chunk as Uint8Array<ArrayBuffer>)
|
|
15
|
+
hasher.update(chunk)
|
|
16
|
+
size += chunk.byteLength
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
await writer.close()
|
|
20
|
+
await done
|
|
21
|
+
|
|
22
|
+
const hash = hex.fromBytes(hasher.digest())
|
|
23
|
+
return {hash, size}
|
|
24
|
+
}
|
|
25
|
+
|
package/s/node/disk-bucket.ts
CHANGED
package/s/test.ts
CHANGED
|
@@ -13,9 +13,9 @@ const blob = () => new Blob([new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF])])
|
|
|
13
13
|
const quickstream = (b: number[]) => new Blob([new Uint8Array(b)]).stream()
|
|
14
14
|
|
|
15
15
|
function setup() {
|
|
16
|
-
const
|
|
16
|
+
const kv = new Kv()
|
|
17
17
|
const bucket = new MemoryBucket()
|
|
18
|
-
const mammoth = new Mammoth(
|
|
18
|
+
const mammoth = new Mammoth(bucket, kv)
|
|
19
19
|
return {mammoth}
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -23,15 +23,13 @@ await science.run({
|
|
|
23
23
|
"mammoth node": test(async() => {
|
|
24
24
|
const dataDir = "data"
|
|
25
25
|
await rm(dataDir, {recursive: true, force: true})
|
|
26
|
-
const
|
|
27
|
-
const bucket = new DiskBucket(dataDir)
|
|
28
|
-
const mammoth = new Mammoth(manifest, bucket)
|
|
26
|
+
const mammoth = new Mammoth(new DiskBucket(dataDir), new Kv())
|
|
29
27
|
const hash = await mammoth.write(blob().stream())
|
|
30
28
|
const second = await mammoth.read(hash)
|
|
31
29
|
expect(bytes.eq(await second.bytes(), await blob().bytes()))
|
|
32
30
|
}),
|
|
33
31
|
|
|
34
|
-
"mammoth": {
|
|
32
|
+
"mammoth memory": {
|
|
35
33
|
".write and .read": test(async() => {
|
|
36
34
|
const {mammoth} = setup()
|
|
37
35
|
const hash = await mammoth.write(blob().stream())
|
|
@@ -52,7 +50,7 @@ await science.run({
|
|
|
52
50
|
".write empty file": test(async() => {
|
|
53
51
|
const {mammoth} = setup()
|
|
54
52
|
const hash = await mammoth.write(new Blob().stream())
|
|
55
|
-
expect(await mammoth.
|
|
53
|
+
expect((await mammoth.info(hash)).size).is(0)
|
|
56
54
|
expect((await mammoth.read(hash)).size).is(0)
|
|
57
55
|
}),
|
|
58
56
|
|
|
@@ -66,15 +64,15 @@ await science.run({
|
|
|
66
64
|
|
|
67
65
|
".stats": test(async() => {
|
|
68
66
|
const {mammoth} = setup()
|
|
69
|
-
expect(await mammoth.stats()).deep({size: 0})
|
|
67
|
+
expect(await mammoth.stats()).deep({count: 0, size: 0})
|
|
70
68
|
const hashA = await mammoth.write(quickstream([0xC0, 0xFF, 0xEE]))
|
|
71
|
-
expect(await mammoth.stats()).deep({size: 3})
|
|
69
|
+
expect(await mammoth.stats()).deep({count: 1, size: 3})
|
|
72
70
|
const hashB = await mammoth.write(quickstream([0xB0, 0x0B, 0x1E, 0x5]))
|
|
73
|
-
expect(await mammoth.stats()).deep({size: 7})
|
|
71
|
+
expect(await mammoth.stats()).deep({count: 2, size: 7})
|
|
74
72
|
await mammoth.delete(hashA)
|
|
75
|
-
expect(await mammoth.stats()).deep({size: 4})
|
|
73
|
+
expect(await mammoth.stats()).deep({count: 1, size: 4})
|
|
76
74
|
await mammoth.delete(hashB)
|
|
77
|
-
expect(await mammoth.stats()).deep({size: 0})
|
|
75
|
+
expect(await mammoth.stats()).deep({count: 0, size: 0})
|
|
78
76
|
}),
|
|
79
77
|
|
|
80
78
|
".has": test(async() => {
|
|
@@ -86,8 +84,9 @@ await science.run({
|
|
|
86
84
|
|
|
87
85
|
".size": test(async() => {
|
|
88
86
|
const {mammoth} = setup()
|
|
89
|
-
const
|
|
90
|
-
|
|
87
|
+
const b = blob()
|
|
88
|
+
const hash = await mammoth.write(b.stream())
|
|
89
|
+
expect((await mammoth.info(hash)).size).is(b.size)
|
|
91
90
|
}),
|
|
92
91
|
|
|
93
92
|
".delete": test(async() => {
|
package/x/browser/example.js
CHANGED
|
@@ -10,7 +10,7 @@ export async function example() {
|
|
|
10
10
|
const directory = await root.getDirectoryHandle("mammoth", { create: true });
|
|
11
11
|
const bucket = new OpfsBucket(directory);
|
|
12
12
|
// setup the mammoth
|
|
13
|
-
const mammoth = new Mammoth(
|
|
13
|
+
const mammoth = new Mammoth(bucket, kv);
|
|
14
14
|
const myFile = new Blob([new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF])]);
|
|
15
15
|
// write a file as an example
|
|
16
16
|
const hash = await mammoth.write(myFile.stream());
|
package/x/browser/example.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"example.js","sourceRoot":"","sources":["../../s/browser/example.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,EAAE,EAAE,aAAa,EAAC,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAA;AAE3C,MAAM,CAAC,KAAK,UAAU,OAAO;IAE5B,4BAA4B;IAC5B,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IACrD,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAE1C,6BAA6B;IAC7B,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,CAAA;IACnD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAC,MAAM,EAAE,IAAI,EAAC,CAAC,CAAA;IAC1E,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAA;IAExC,oBAAoB;IACpB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"example.js","sourceRoot":"","sources":["../../s/browser/example.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,EAAE,EAAE,aAAa,EAAC,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAA;AAE3C,MAAM,CAAC,KAAK,UAAU,OAAO;IAE5B,4BAA4B;IAC5B,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IACrD,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAE1C,6BAA6B;IAC7B,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,CAAA;IACnD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAC,MAAM,EAAE,IAAI,EAAC,CAAC,CAAA;IAC1E,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAA;IAExC,oBAAoB;IACpB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACvC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAEnE,6BAA6B;IAC7B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;IACjD,OAAO,CAAC,GAAG,CAAC,gCAAgC,IAAI,GAAG,CAAC,CAAA;AACrD,CAAC"}
|
|
@@ -3,7 +3,6 @@ export declare class OpfsBucket implements Bucket {
|
|
|
3
3
|
#private;
|
|
4
4
|
constructor(directory: FileSystemDirectoryHandle);
|
|
5
5
|
has(id: Id): Promise<boolean>;
|
|
6
|
-
size(id: Id): Promise<number>;
|
|
7
6
|
delete(id: Id): Promise<void>;
|
|
8
7
|
read(id: Id): Promise<File>;
|
|
9
8
|
write(id: Id, readable: ReadableStream<Uint8Array>): Promise<void>;
|
package/x/browser/opfs-bucket.js
CHANGED
|
@@ -15,11 +15,6 @@ export class OpfsBucket {
|
|
|
15
15
|
throw error;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
async size(id) {
|
|
19
|
-
const handle = await this.#directory.getFileHandle(id);
|
|
20
|
-
const file = await handle.getFile();
|
|
21
|
-
return file.size;
|
|
22
|
-
}
|
|
23
18
|
async delete(id) {
|
|
24
19
|
try {
|
|
25
20
|
await this.#directory.removeEntry(id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opfs-bucket.js","sourceRoot":"","sources":["../../s/browser/opfs-bucket.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAA;AAElD,MAAM,OAAO,UAAU;IACtB,UAAU,CAAA;IAEV,YAAY,SAAoC;QAC/C,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAM;QACf,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA;YACvC,OAAO,IAAI,CAAA;QACZ,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACd,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YACnC,MAAM,KAAK,CAAA;QACZ,CAAC;IACF,CAAC;IAED,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"opfs-bucket.js","sourceRoot":"","sources":["../../s/browser/opfs-bucket.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAA;AAElD,MAAM,OAAO,UAAU;IACtB,UAAU,CAAA;IAEV,YAAY,SAAoC;QAC/C,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAM;QACf,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA;YACvC,OAAO,IAAI,CAAA;QACZ,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACd,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YACnC,MAAM,KAAK,CAAA;QACZ,CAAC;IACF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAM;QAClB,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;QACtC,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACd,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;gBACrB,MAAM,KAAK,CAAA;QACb,CAAC;IACF,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAM;QAChB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA;QACtD,OAAO,MAAM,CAAC,OAAO,EAAE,CAAA;IACxB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAM,EAAE,QAAoC;QACvD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,EAAE,EAAC,MAAM,EAAE,IAAI,EAAC,CAAC,CAAA;QACtE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,cAAc,EAAE,CAAA;QAC9C,MAAM,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAChC,CAAC;CACD"}
|
package/x/core/consts.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../../s/core/consts.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAE9B,MAAM,CAAC,MAAM,MAAM,GAAG;IACrB,QAAQ,EAAE,EAAE;IACZ,gBAAgB,EAAE,EAAE;IACpB,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;CACzB,CAAA"}
|
package/x/core/mammoth.d.ts
CHANGED
|
@@ -3,18 +3,12 @@ import { Hash, Bucket } from "./types.js";
|
|
|
3
3
|
/** file storage datalake, content-addressed with blake3 hashes. */
|
|
4
4
|
export declare class Mammoth {
|
|
5
5
|
#private;
|
|
6
|
-
constructor(
|
|
7
|
-
/** key-value metadata manifest. */
|
|
8
|
-
kv?: Kv<any>,
|
|
9
|
-
/** dumb raw file blob storage. */
|
|
10
|
-
bucket?: Bucket);
|
|
6
|
+
constructor(bucket?: Bucket, kv?: Kv<any>);
|
|
11
7
|
has(hash: Hash): Promise<boolean>;
|
|
12
|
-
|
|
8
|
+
info(hash: Hash): Promise<import("./types.js").Info>;
|
|
13
9
|
read(hash: Hash): Promise<Blob>;
|
|
14
10
|
delete(hash: Hash): Promise<void>;
|
|
15
11
|
write(readable: ReadableStream<Uint8Array>): Promise<Hash>;
|
|
16
12
|
hashes(): AsyncGenerator<string, void, unknown>;
|
|
17
|
-
stats(): Promise<
|
|
18
|
-
size: number;
|
|
19
|
-
}>;
|
|
13
|
+
stats(): Promise<import("./types.js").Stats>;
|
|
20
14
|
}
|
package/x/core/mammoth.js
CHANGED
|
@@ -1,106 +1,66 @@
|
|
|
1
1
|
import { Kv } from "@e280/kv";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { lane, queue } from "@e280/stz";
|
|
3
|
+
import { consts } from "./consts.js";
|
|
4
|
+
import { Manifest } from "./utils/manifest.js";
|
|
4
5
|
import { randomId } from "./utils/random-id.js";
|
|
5
6
|
import { MemoryBucket } from "./memory-bucket.js";
|
|
7
|
+
import { saveAndHash } from "./utils/save-and-hash.js";
|
|
6
8
|
/** file storage datalake, content-addressed with blake3 hashes. */
|
|
7
9
|
export class Mammoth {
|
|
8
|
-
#ids;
|
|
9
|
-
#wip;
|
|
10
|
-
#stats;
|
|
11
10
|
#bucket;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
kv = new Kv()
|
|
15
|
-
/** dumb raw file blob storage. */
|
|
16
|
-
bucket = new MemoryBucket()) {
|
|
17
|
-
this.#ids = kv.scope("ids");
|
|
18
|
-
this.#wip = kv.scope("wip");
|
|
19
|
-
this.#stats = kv.store("stats");
|
|
11
|
+
#manifest;
|
|
12
|
+
#wholesome = lane(consts.max_jobs);
|
|
13
|
+
constructor(bucket = new MemoryBucket(), kv = new Kv()) {
|
|
20
14
|
this.#bucket = bucket;
|
|
15
|
+
this.#manifest = new Manifest(kv);
|
|
21
16
|
}
|
|
22
17
|
async has(hash) {
|
|
23
|
-
return this.#
|
|
18
|
+
return this.#manifest.hasHash(hash);
|
|
24
19
|
}
|
|
25
|
-
async
|
|
26
|
-
|
|
27
|
-
return this.#bucket.size(id);
|
|
20
|
+
async info(hash) {
|
|
21
|
+
return this.#manifest.needInfo(hash);
|
|
28
22
|
}
|
|
29
23
|
async read(hash) {
|
|
30
|
-
const id = await this.#
|
|
24
|
+
const { id } = await this.#manifest.needInfo(hash);
|
|
31
25
|
return this.#bucket.read(id);
|
|
32
26
|
}
|
|
33
27
|
async delete(hash) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
await this.#wholesome(async () => {
|
|
29
|
+
const info = await this.#manifest.getInfo(hash);
|
|
30
|
+
if (info) {
|
|
31
|
+
await this.#manifest.deleteInfo(hash);
|
|
32
|
+
await this.#bucket.delete(info.id);
|
|
33
|
+
await this.#manifest.statsRemoveFile(info.size);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
41
36
|
}
|
|
42
37
|
async write(readable) {
|
|
43
38
|
const id = randomId();
|
|
44
|
-
await this.#
|
|
45
|
-
const { hash, size } = await this.#
|
|
46
|
-
await this.#
|
|
47
|
-
|
|
39
|
+
await this.#manifest.addWip(id);
|
|
40
|
+
const { hash, size } = await saveAndHash(this.#bucket, id, readable);
|
|
41
|
+
await this.#wholesome(async () => {
|
|
42
|
+
if (await this.#manifest.hasHash(hash)) {
|
|
43
|
+
await this.#bucket.delete(id); // forget this new file (we already have it)
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
await this.#manifest.saveInfo(hash, { id, size, added: Date.now() });
|
|
47
|
+
await this.#manifest.statsAddFile(size);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
await this.#manifest.deleteWip(id);
|
|
48
51
|
void this.#selfClean().catch(() => { });
|
|
49
52
|
return hash;
|
|
50
53
|
}
|
|
51
54
|
async *hashes() {
|
|
52
|
-
yield* this.#
|
|
55
|
+
yield* this.#manifest.hashes();
|
|
53
56
|
}
|
|
54
57
|
async stats() {
|
|
55
|
-
return
|
|
58
|
+
return this.#manifest.getStats();
|
|
56
59
|
}
|
|
57
|
-
async #getId(hash) {
|
|
58
|
-
return this.#ids.get(hash);
|
|
59
|
-
}
|
|
60
|
-
async #needId(hash) {
|
|
61
|
-
return got(await this.#getId(hash), `file not found by hash "${hash}"`);
|
|
62
|
-
}
|
|
63
|
-
async #hashAndSave(id, readable) {
|
|
64
|
-
let size = 0;
|
|
65
|
-
const pipe = new TransformStream();
|
|
66
|
-
const done = this.#bucket.write(id, pipe.readable);
|
|
67
|
-
const writer = pipe.writable.getWriter();
|
|
68
|
-
const hasher = blake3.create();
|
|
69
|
-
for await (const chunk of readable) {
|
|
70
|
-
await writer.write(chunk);
|
|
71
|
-
hasher.update(chunk);
|
|
72
|
-
size += chunk.byteLength;
|
|
73
|
-
}
|
|
74
|
-
await writer.close();
|
|
75
|
-
await done;
|
|
76
|
-
const hash = hex.fromBytes(hasher.digest());
|
|
77
|
-
return { hash, size };
|
|
78
|
-
}
|
|
79
|
-
#finalizeWrite = queue(async (hash, id, size) => {
|
|
80
|
-
const existingId = await this.#getId(hash);
|
|
81
|
-
if (existingId) {
|
|
82
|
-
await this.#bucket.delete(id);
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
await this.#ids.set(hash, id);
|
|
86
|
-
await this.#addSize(size);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
#addSize = queue(async (sizeChange) => {
|
|
90
|
-
const oldStats = await this.stats();
|
|
91
|
-
await this.#stats.set({ ...oldStats, size: oldStats.size + sizeChange });
|
|
92
|
-
});
|
|
93
60
|
#selfClean = queue(async () => {
|
|
94
|
-
const
|
|
95
|
-
const expiredIds = [];
|
|
96
|
-
for await (const [id, { created }] of this.#wip.entries({ limit: 64 })) {
|
|
97
|
-
const since = now - created;
|
|
98
|
-
const isExpired = since > time.days(7);
|
|
99
|
-
if (isExpired)
|
|
100
|
-
expiredIds.push(id);
|
|
101
|
-
}
|
|
61
|
+
const expiredIds = await this.#manifest.getExpiredIds();
|
|
102
62
|
await Promise.all(expiredIds.map(id => this.#bucket.delete(id)));
|
|
103
|
-
await this.#
|
|
63
|
+
await this.#manifest.deleteWip(...expiredIds);
|
|
104
64
|
});
|
|
105
65
|
}
|
|
106
66
|
//# sourceMappingURL=mammoth.js.map
|
package/x/core/mammoth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mammoth.js","sourceRoot":"","sources":["../../s/core/mammoth.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,EAAE,EAAC,MAAM,UAAU,CAAA;AAC3B,OAAO,EAAC,
|
|
1
|
+
{"version":3,"file":"mammoth.js","sourceRoot":"","sources":["../../s/core/mammoth.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,6 @@ import { Bucket, Id } from "./types.js";
|
|
|
2
2
|
export declare class MemoryBucket implements Bucket {
|
|
3
3
|
#private;
|
|
4
4
|
has(id: Id): Promise<boolean>;
|
|
5
|
-
size(id: Id): Promise<number>;
|
|
6
5
|
delete(id: Id): Promise<void>;
|
|
7
6
|
read(id: Id): Promise<Blob>;
|
|
8
7
|
write(id: Id, readable: ReadableStream<Uint8Array>): Promise<void>;
|
package/x/core/memory-bucket.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-bucket.js","sourceRoot":"","sources":["../../s/core/memory-bucket.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAC,MAAM,WAAW,CAAA;AAEzC,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAE1C,MAAM,OAAO,YAAY;IACxB,IAAI,GAAG,IAAI,GAAG,EAAsB,CAAA;IAEpC,KAAK,CAAC,GAAG,CAAC,EAAM;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"memory-bucket.js","sourceRoot":"","sources":["../../s/core/memory-bucket.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAC,MAAM,WAAW,CAAA;AAEzC,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAE1C,MAAM,OAAO,YAAY;IACxB,IAAI,GAAG,IAAI,GAAG,EAAsB,CAAA;IAEpC,KAAK,CAAC,GAAG,CAAC,EAAM;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAM;QAClB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IACrB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAM;QAChB,OAAO,IAAI,IAAI,CACd,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAA4B,CAAC,EACnD,EAAC,IAAI,EAAE,0BAA0B,EAAC,CAClC,CAAA;IACF,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAM,EAAE,QAAoC;QACvD,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;QACvB,MAAM,KAAK,GAAiB,EAAE,CAAA;QAE9B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YACpC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACjB,IAAI,KAAK,EAAE;gBAAE,MAAM,GAAG,EAAE,CAAA;QACzB,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACnC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IAC3B,CAAC;CACD"}
|
package/x/core/types.d.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
export type Hash = string;
|
|
2
2
|
export type Id = string;
|
|
3
|
+
/** file blob store. */
|
|
3
4
|
export type Bucket = {
|
|
4
5
|
has(id: Id): Promise<boolean>;
|
|
5
|
-
size(id: Id): Promise<number>;
|
|
6
6
|
delete(id: Id): Promise<void>;
|
|
7
7
|
read(id: Id): Promise<Blob>;
|
|
8
8
|
write(id: Id, readable: ReadableStream<Uint8Array>): Promise<void>;
|
|
9
9
|
};
|
|
10
|
+
/** metadata for a single file. */
|
|
11
|
+
export type Info = {
|
|
12
|
+
/** bucket id for this file's data. */
|
|
13
|
+
id: Id;
|
|
14
|
+
/** file size in bytes. */
|
|
15
|
+
size: number;
|
|
16
|
+
/** when this file was added to the datalake. */
|
|
17
|
+
added: number;
|
|
18
|
+
};
|
|
19
|
+
/** statistics for the whole datalake. */
|
|
10
20
|
export type Stats = {
|
|
21
|
+
/** total number of bytes in the whole datalake. */
|
|
11
22
|
size: number;
|
|
23
|
+
/** total number of files in the datalake. */
|
|
24
|
+
count: number;
|
|
25
|
+
};
|
|
26
|
+
export type Wip = {
|
|
27
|
+
created: number;
|
|
12
28
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-expired.js","sourceRoot":"","sources":["../../../s/core/utils/is-expired.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAE9B,OAAO,EAAC,MAAM,EAAC,MAAM,cAAc,CAAA;AAEnC,MAAM,UAAU,SAAS,CAAC,EAAC,OAAO,EAAM;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAA;IAClC,OAAO,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;AAC7C,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Kv } from "@e280/kv";
|
|
2
|
+
import { Hash, Id, Info, Stats } from "../types.js";
|
|
3
|
+
export declare class Manifest {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(kv?: Kv<any>);
|
|
6
|
+
hashes(): AsyncGenerator<string, void, unknown>;
|
|
7
|
+
hasHash(hash: Hash): Promise<boolean>;
|
|
8
|
+
saveInfo(hash: Hash, info: Info): Promise<void>;
|
|
9
|
+
getInfo(hash: Hash): Promise<Info | undefined>;
|
|
10
|
+
needInfo(hash: Hash): Promise<Info>;
|
|
11
|
+
deleteInfo(hash: Hash): Promise<void>;
|
|
12
|
+
addWip(id: Id): Promise<void>;
|
|
13
|
+
deleteWip(...ids: Id[]): Promise<void>;
|
|
14
|
+
statsAddFile(size: number): Promise<void>;
|
|
15
|
+
statsRemoveFile(size: number): Promise<void>;
|
|
16
|
+
getExpiredIds(): Promise<string[]>;
|
|
17
|
+
getStats(): Promise<Stats>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Kv } from "@e280/kv";
|
|
2
|
+
import { collect, got } from "@e280/stz";
|
|
3
|
+
import { consts } from "../consts.js";
|
|
4
|
+
import { notFound } from "./not-found.js";
|
|
5
|
+
import { isExpired } from "./is-expired.js";
|
|
6
|
+
export class Manifest {
|
|
7
|
+
/** associates file hashes with bucket ids */
|
|
8
|
+
#info;
|
|
9
|
+
/** temporary record of writes in-progress */
|
|
10
|
+
#wip;
|
|
11
|
+
/** statistics about the whole datalake */
|
|
12
|
+
#stats;
|
|
13
|
+
constructor(kv = new Kv()) {
|
|
14
|
+
this.#info = kv.scope("info");
|
|
15
|
+
this.#wip = kv.scope("wip");
|
|
16
|
+
this.#stats = kv.store("stats");
|
|
17
|
+
}
|
|
18
|
+
async *hashes() {
|
|
19
|
+
yield* this.#info.keys();
|
|
20
|
+
}
|
|
21
|
+
async hasHash(hash) {
|
|
22
|
+
return this.#info.has(hash);
|
|
23
|
+
}
|
|
24
|
+
async saveInfo(hash, info) {
|
|
25
|
+
await this.#info.set(hash, info);
|
|
26
|
+
}
|
|
27
|
+
async getInfo(hash) {
|
|
28
|
+
return await this.#info.get(hash);
|
|
29
|
+
}
|
|
30
|
+
async needInfo(hash) {
|
|
31
|
+
return got(await this.getInfo(hash), notFound(hash));
|
|
32
|
+
}
|
|
33
|
+
async deleteInfo(hash) {
|
|
34
|
+
await this.#info.del(hash);
|
|
35
|
+
}
|
|
36
|
+
async addWip(id) {
|
|
37
|
+
await this.#wip.set(id, { created: Date.now() });
|
|
38
|
+
}
|
|
39
|
+
async deleteWip(...ids) {
|
|
40
|
+
await this.#wip.del(...ids);
|
|
41
|
+
}
|
|
42
|
+
async statsAddFile(size) {
|
|
43
|
+
await this.#updateStats(stats => {
|
|
44
|
+
stats.count += 1;
|
|
45
|
+
stats.size += size;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async statsRemoveFile(size) {
|
|
49
|
+
await this.#updateStats(stats => {
|
|
50
|
+
stats.count -= 1;
|
|
51
|
+
stats.size -= size;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async getExpiredIds() {
|
|
55
|
+
const limit = consts.self_clean_limit;
|
|
56
|
+
return (await collect(this.#wip.entries({ limit })))
|
|
57
|
+
.filter(([, wip]) => isExpired(wip))
|
|
58
|
+
.map(([id]) => id);
|
|
59
|
+
}
|
|
60
|
+
async getStats() {
|
|
61
|
+
return structuredClone((await this.#stats.get()) ?? { count: 0, size: 0 });
|
|
62
|
+
}
|
|
63
|
+
async #updateStats(fn) {
|
|
64
|
+
const stats = await this.getStats();
|
|
65
|
+
fn(stats);
|
|
66
|
+
await this.#stats.set(stats);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../s/core/utils/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"not-found.js","sourceRoot":"","sources":["../../../s/core/utils/not-found.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,QAAQ,CAAC,IAAU;IAClC,OAAO,2BAA2B,IAAI,GAAG,CAAA;AAC1C,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { hex } from "@e280/stz";
|
|
2
|
+
import { blake3 } from "@noble/hashes/blake3.js";
|
|
3
|
+
export async function saveAndHash(bucket, id, readable) {
|
|
4
|
+
let size = 0;
|
|
5
|
+
const pipe = new TransformStream();
|
|
6
|
+
const done = bucket.write(id, pipe.readable);
|
|
7
|
+
const writer = pipe.writable.getWriter();
|
|
8
|
+
const hasher = blake3.create();
|
|
9
|
+
for await (const chunk of readable) {
|
|
10
|
+
await writer.write(chunk);
|
|
11
|
+
hasher.update(chunk);
|
|
12
|
+
size += chunk.byteLength;
|
|
13
|
+
}
|
|
14
|
+
await writer.close();
|
|
15
|
+
await done;
|
|
16
|
+
const hash = hex.fromBytes(hasher.digest());
|
|
17
|
+
return { hash, size };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=save-and-hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"save-and-hash.js","sourceRoot":"","sources":["../../../s/core/utils/save-and-hash.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAC,MAAM,WAAW,CAAA;AAC7B,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAA;AAG9C,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAc,EAAE,EAAM,EAAE,QAAoC;IAC7F,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAA;IACxC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAA;IAE9B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QACpC,MAAM,MAAM,CAAC,KAAK,CAAC,KAAgC,CAAC,CAAA;QACpD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACpB,IAAI,IAAI,KAAK,CAAC,UAAU,CAAA;IACzB,CAAC;IAED,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;IACpB,MAAM,IAAI,CAAA;IAEV,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;IAC3C,OAAO,EAAC,IAAI,EAAE,IAAI,EAAC,CAAA;AACpB,CAAC"}
|
package/x/node/disk-bucket.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ export declare class DiskBucket implements Bucket {
|
|
|
3
3
|
#private;
|
|
4
4
|
constructor(directory: string);
|
|
5
5
|
has(id: Id): Promise<boolean>;
|
|
6
|
-
size(id: Id): Promise<number>;
|
|
7
6
|
delete(id: Id): Promise<void>;
|
|
8
7
|
read(id: Id): Promise<Blob>;
|
|
9
8
|
write(id: Id, readable: ReadableStream<Uint8Array>): Promise<void>;
|
package/x/node/disk-bucket.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disk-bucket.js","sourceRoot":"","sources":["../../s/node/disk-bucket.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAC9B,OAAO,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAA;AACpC,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAC,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAC,iBAAiB,EAAE,UAAU,EAAC,MAAM,SAAS,CAAA;AAGrD,OAAO,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAA;AAElD,MAAM,OAAO,UAAU;IACtB,UAAU,CAAA;IAEV,YAAY,SAAiB;QAC5B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAM;QACf,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;YAC1B,OAAO,IAAI,CAAA;QACZ,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACd,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YACnC,MAAM,KAAK,CAAA;QACZ,CAAC;IACF,CAAC;IAED,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"disk-bucket.js","sourceRoot":"","sources":["../../s/node/disk-bucket.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAA;AAC9B,OAAO,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAA;AACpC,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAC,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAC,iBAAiB,EAAE,UAAU,EAAC,MAAM,SAAS,CAAA;AAGrD,OAAO,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAA;AAElD,MAAM,OAAO,UAAU;IACtB,UAAU,CAAA;IAEV,YAAY,SAAiB;QAC5B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAM;QACf,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;YAC1B,OAAO,IAAI,CAAA;QACZ,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACd,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YACnC,MAAM,KAAK,CAAA;QACZ,CAAC;IACF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAM;QAClB,IAAI,CAAC;YACJ,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;QAC7B,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACd,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;gBACrB,MAAM,KAAK,CAAA;QACb,CAAC;IACF,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAM;QAChB,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;IAClC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAM,EAAE,QAAoC;QACvD,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,EAAC,SAAS,EAAE,IAAI,EAAC,CAAC,CAAA;QAC/C,MAAM,QAAQ,CACb,QAAQ,CAAC,OAAO,CAAC,QAAe,CAAC,EACjC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CACjC,CAAA;IACF,CAAC;IAED,KAAK,CAAC,EAAM;QACX,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;IACjC,CAAC;CACD"}
|
package/x/test.js
CHANGED
|
@@ -9,23 +9,21 @@ import { MemoryBucket } from "./core/memory-bucket.js";
|
|
|
9
9
|
const blob = () => new Blob([new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF])]);
|
|
10
10
|
const quickstream = (b) => new Blob([new Uint8Array(b)]).stream();
|
|
11
11
|
function setup() {
|
|
12
|
-
const
|
|
12
|
+
const kv = new Kv();
|
|
13
13
|
const bucket = new MemoryBucket();
|
|
14
|
-
const mammoth = new Mammoth(
|
|
14
|
+
const mammoth = new Mammoth(bucket, kv);
|
|
15
15
|
return { mammoth };
|
|
16
16
|
}
|
|
17
17
|
await science.run({
|
|
18
18
|
"mammoth node": test(async () => {
|
|
19
19
|
const dataDir = "data";
|
|
20
20
|
await rm(dataDir, { recursive: true, force: true });
|
|
21
|
-
const
|
|
22
|
-
const bucket = new DiskBucket(dataDir);
|
|
23
|
-
const mammoth = new Mammoth(manifest, bucket);
|
|
21
|
+
const mammoth = new Mammoth(new DiskBucket(dataDir), new Kv());
|
|
24
22
|
const hash = await mammoth.write(blob().stream());
|
|
25
23
|
const second = await mammoth.read(hash);
|
|
26
24
|
expect(bytes.eq(await second.bytes(), await blob().bytes()));
|
|
27
25
|
}),
|
|
28
|
-
"mammoth": {
|
|
26
|
+
"mammoth memory": {
|
|
29
27
|
".write and .read": test(async () => {
|
|
30
28
|
const { mammoth } = setup();
|
|
31
29
|
const hash = await mammoth.write(blob().stream());
|
|
@@ -44,7 +42,7 @@ await science.run({
|
|
|
44
42
|
".write empty file": test(async () => {
|
|
45
43
|
const { mammoth } = setup();
|
|
46
44
|
const hash = await mammoth.write(new Blob().stream());
|
|
47
|
-
expect(await mammoth.
|
|
45
|
+
expect((await mammoth.info(hash)).size).is(0);
|
|
48
46
|
expect((await mammoth.read(hash)).size).is(0);
|
|
49
47
|
}),
|
|
50
48
|
".write distinct files": test(async () => {
|
|
@@ -56,15 +54,15 @@ await science.run({
|
|
|
56
54
|
}),
|
|
57
55
|
".stats": test(async () => {
|
|
58
56
|
const { mammoth } = setup();
|
|
59
|
-
expect(await mammoth.stats()).deep({ size: 0 });
|
|
57
|
+
expect(await mammoth.stats()).deep({ count: 0, size: 0 });
|
|
60
58
|
const hashA = await mammoth.write(quickstream([0xC0, 0xFF, 0xEE]));
|
|
61
|
-
expect(await mammoth.stats()).deep({ size: 3 });
|
|
59
|
+
expect(await mammoth.stats()).deep({ count: 1, size: 3 });
|
|
62
60
|
const hashB = await mammoth.write(quickstream([0xB0, 0x0B, 0x1E, 0x5]));
|
|
63
|
-
expect(await mammoth.stats()).deep({ size: 7 });
|
|
61
|
+
expect(await mammoth.stats()).deep({ count: 2, size: 7 });
|
|
64
62
|
await mammoth.delete(hashA);
|
|
65
|
-
expect(await mammoth.stats()).deep({ size: 4 });
|
|
63
|
+
expect(await mammoth.stats()).deep({ count: 1, size: 4 });
|
|
66
64
|
await mammoth.delete(hashB);
|
|
67
|
-
expect(await mammoth.stats()).deep({ size: 0 });
|
|
65
|
+
expect(await mammoth.stats()).deep({ count: 0, size: 0 });
|
|
68
66
|
}),
|
|
69
67
|
".has": test(async () => {
|
|
70
68
|
const { mammoth } = setup();
|
|
@@ -74,8 +72,9 @@ await science.run({
|
|
|
74
72
|
}),
|
|
75
73
|
".size": test(async () => {
|
|
76
74
|
const { mammoth } = setup();
|
|
77
|
-
const
|
|
78
|
-
|
|
75
|
+
const b = blob();
|
|
76
|
+
const hash = await mammoth.write(b.stream());
|
|
77
|
+
expect((await mammoth.info(hash)).size).is(b.size);
|
|
79
78
|
}),
|
|
80
79
|
".delete": test(async () => {
|
|
81
80
|
const { mammoth } = setup();
|
package/x/test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.js","sourceRoot":"","sources":["../s/test.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,EAAE,EAAC,MAAM,UAAU,CAAA;AAC3B,OAAO,EAAC,EAAE,EAAC,MAAM,aAAa,CAAA;AAC9B,OAAO,EAAC,KAAK,EAAE,OAAO,EAAC,MAAM,WAAW,CAAA;AACxC,OAAO,EAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAC,MAAM,eAAe,CAAA;AAEnD,OAAO,EAAC,OAAO,EAAC,MAAM,mBAAmB,CAAA;AACzC,OAAO,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAC,YAAY,EAAC,MAAM,yBAAyB,CAAA;AAEpD,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AACvE,MAAM,WAAW,GAAG,CAAC,CAAW,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAA;AAE3E,SAAS,KAAK;IACb,MAAM,
|
|
1
|
+
{"version":3,"file":"test.js","sourceRoot":"","sources":["../s/test.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,EAAE,EAAC,MAAM,UAAU,CAAA;AAC3B,OAAO,EAAC,EAAE,EAAC,MAAM,aAAa,CAAA;AAC9B,OAAO,EAAC,KAAK,EAAE,OAAO,EAAC,MAAM,WAAW,CAAA;AACxC,OAAO,EAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAC,MAAM,eAAe,CAAA;AAEnD,OAAO,EAAC,OAAO,EAAC,MAAM,mBAAmB,CAAA;AACzC,OAAO,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAA;AAClD,OAAO,EAAC,YAAY,EAAC,MAAM,yBAAyB,CAAA;AAEpD,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AACvE,MAAM,WAAW,GAAG,CAAC,CAAW,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAA;AAE3E,SAAS,KAAK;IACb,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,CAAA;IACnB,MAAM,MAAM,GAAG,IAAI,YAAY,EAAE,CAAA;IACjC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACvC,OAAO,EAAC,OAAO,EAAC,CAAA;AACjB,CAAC;AAED,MAAM,OAAO,CAAC,GAAG,CAAC;IACjB,cAAc,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;QAC9B,MAAM,OAAO,GAAG,MAAM,CAAA;QACtB,MAAM,EAAE,CAAC,OAAO,EAAE,EAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAC,CAAC,CAAA;QACjD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;QAC9D,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;QACjD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IAC7D,CAAC,CAAC;IAEF,gBAAgB,EAAE;QACjB,kBAAkB,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YAClC,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;YACjD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACvC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QAC7D,CAAC,CAAC;QAEF,sBAAsB,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YACtC,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;YAClD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;YAClD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;YAC5C,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;YACvB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;QAC1B,CAAC,CAAC;QAEF,mBAAmB,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YACnC,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;YACrD,MAAM,CAAC,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YAC7C,MAAM,CAAC,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAC9C,CAAC,CAAC;QAEF,uBAAuB,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YACvC,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;YAClE,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;YACvE,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;YAC3B,MAAM,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QACvD,CAAC,CAAC;QAEF,QAAQ,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YACxB,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAC,CAAC,CAAA;YACvD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;YAClE,MAAM,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAC,CAAC,CAAA;YACvD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;YACvE,MAAM,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAC,CAAC,CAAA;YACvD,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC3B,MAAM,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAC,CAAC,CAAA;YACvD,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC3B,MAAM,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,EAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAC,CAAC,CAAA;QACxD,CAAC,CAAC;QAEF,MAAM,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YACtB,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;YACjD,MAAM,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;YACxC,MAAM,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;QAChD,CAAC,CAAC;QAEF,OAAO,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YACvB,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,CAAC,GAAG,IAAI,EAAE,CAAA;YAChB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;YAC5C,MAAM,CAAC,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACnD,CAAC,CAAC;QAEF,SAAS,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YACzB,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;YACjD,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YAC1B,MAAM,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;YACzC,MAAM,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACjD,CAAC,CAAC;QAEF,oBAAoB,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YACpC,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;QACjC,CAAC,CAAC;QAEF,OAAO,EAAE,IAAI,CAAC,KAAK,IAAG,EAAE;YACvB,MAAM,EAAC,OAAO,EAAC,GAAG,KAAK,EAAE,CAAA;YACzB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;YACjD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;YAC5C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;QACzB,CAAC,CAAC;KACF;CACD,CAAC,CAAA"}
|