@e280/mammoth 0.1.0-6 → 0.1.0-8

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 CHANGED
@@ -4,7 +4,7 @@
4
4
  # 🦣 mammoth
5
5
  > *big files. small api.*
6
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.
7
+ **mammoth is a file storage typescript library.** we use it in our web apps to store files like user uploads. it's content-addressed. files are streamed into a bucket, identified by their blake3 hash, and 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.
8
8
 
9
9
  ```bash
10
10
  npm install @e280/mammoth
@@ -17,7 +17,7 @@ const mammoth = new Mammoth()
17
17
  ```
18
18
 
19
19
  ### 🦣 mammoth stores files.
20
- - **write a file,** and you get back its blake3 hash.
20
+ - **write a file,** and you get back its blake3 `hash` and `size` in bytes.
21
21
  ```ts
22
22
  const hash = await mammoth.write(blob.stream())
23
23
  ```
@@ -37,11 +37,15 @@ const mammoth = new Mammoth()
37
37
  import {Kv} from "@e280/kv"
38
38
 
39
39
  const mammoth = new Mammoth(
40
+
41
+ // bucket for blob storage
40
42
  new MemoryBucket(),
43
+
44
+ // kv for metadata bookkeeping
41
45
  new Kv(),
42
46
  )
43
47
  ```
44
- defaults shown, so, this is equivalent:
48
+ defaults shown, this is equivalent:
45
49
  ```ts
46
50
  const mammoth = new Mammoth()
47
51
  ```
@@ -74,7 +78,7 @@ const mammoth = new Mammoth()
74
78
  ```ts
75
79
  const exists = await mammoth.has(hash)
76
80
  ```
77
- - **get file info,** including size in bytes, added timestamp, and bucket id.
81
+ - **get file info,** including `size` in bytes, `added` timestamp, and bucket `id`.
78
82
  ```ts
79
83
  const {size, added, id} = await mammoth.info(hash)
80
84
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e280/mammoth",
3
- "version": "0.1.0-6",
3
+ "version": "0.1.0-8",
4
4
  "description": "big file storage",
5
5
  "license": "MIT",
6
6
  "author": "Chase Moskal <chasemoskal@gmail.com>",
@@ -20,6 +20,6 @@ export async function example() {
20
20
 
21
21
  // write a file as an example
22
22
  const hash = await mammoth.write(myFile.stream())
23
- console.log(`mammoth stored file by hash "${hash}"`)
23
+ console.log(`mammoth stored file "${hash}"`)
24
24
  }
25
25
 
@@ -1,19 +1,23 @@
1
1
 
2
2
  import {hex, nap} from "@e280/stz"
3
3
  import {blake3} from "@noble/hashes/blake3.js"
4
+ import {Analysis} from "./types.js"
4
5
  import {relaxer} from "./utils/relaxer.js"
5
6
 
6
- export async function hashStream(readable: ReadableStream<Uint8Array>) {
7
+ export async function analyze(readable: ReadableStream<Uint8Array>): Promise<Analysis> {
8
+ let size = 0
7
9
  const hasher = blake3.create()
8
10
  const relax = relaxer()
9
11
 
10
12
  for await (const chunk of readable) {
11
13
  hasher.update(chunk)
14
+ size += chunk.byteLength
12
15
 
13
16
  if (relax())
14
17
  await nap()
15
18
  }
16
19
 
17
- return hex.fromBytes(hasher.digest())
20
+ const hash = hex.fromBytes(hasher.digest())
21
+ return {hash, size}
18
22
  }
19
23
 
package/s/core/consts.ts CHANGED
@@ -3,7 +3,6 @@ import {time} from "@e280/stz"
3
3
 
4
4
  export const consts = {
5
5
  max_jobs: 64,
6
- self_clean_limit: 64,
7
6
  expiry_time: time.days(7),
8
7
  }
9
8
 
package/s/core/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
- export * from "./hash-stream.js"
3
- export * from "./memory-bucket.js"
2
+ export * from "./analyze.js"
4
3
  export * from "./mammoth.js"
4
+ export * from "./memory-bucket.js"
5
5
  export * from "./types.js"
6
6
 
package/s/core/mammoth.ts CHANGED
@@ -3,11 +3,11 @@ import {Kv} from "@e280/kv"
3
3
  import {lane, queue} from "@e280/stz"
4
4
 
5
5
  import {consts} from "./consts.js"
6
- import {Hash, Bucket} from "./types.js"
6
+ import {save} from "./utils/save.js"
7
7
  import {Manifest} from "./utils/manifest.js"
8
8
  import {randomId} from "./utils/random-id.js"
9
9
  import {MemoryBucket} from "./memory-bucket.js"
10
- import {saveAndHash} from "./utils/save-and-hash.js"
10
+ import {Hash, Bucket, Analysis} from "./types.js"
11
11
 
12
12
  /** file storage datalake, content-addressed with blake3 hashes. */
13
13
  export class Mammoth {
@@ -20,6 +20,10 @@ export class Mammoth {
20
20
  this.#manifest = new Manifest(kv)
21
21
  }
22
22
 
23
+ async* hashes() {
24
+ yield* this.#manifest.hashes()
25
+ }
26
+
23
27
  async has(hash: Hash) {
24
28
  return this.#manifest.hasHash(hash)
25
29
  }
@@ -28,6 +32,10 @@ export class Mammoth {
28
32
  return this.#manifest.needInfo(hash)
29
33
  }
30
34
 
35
+ async stats() {
36
+ return this.#manifest.getStats()
37
+ }
38
+
31
39
  async read(hash: Hash) {
32
40
  const {id} = await this.#manifest.needInfo(hash)
33
41
  return this.#bucket.read(id)
@@ -36,47 +44,39 @@ export class Mammoth {
36
44
  async delete(hash: Hash) {
37
45
  await this.#wholesome(async() => {
38
46
  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
- }
47
+ if (info)
48
+ await this.#manifest.scheduleDeletion(hash, info)
44
49
  })
50
+ await this.#cleanup()
45
51
  }
46
52
 
47
53
  async write(readable: ReadableStream<Uint8Array>): Promise<Hash> {
48
54
  const id = randomId()
49
-
50
55
  await this.#manifest.addWip(id)
51
- const {hash, size} = await saveAndHash(this.#bucket, id, readable)
52
-
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
- })
62
-
63
- await this.#manifest.deleteWip(id)
64
- void this.#selfClean().catch(() => {})
65
- return hash
66
- }
67
-
68
- async* hashes() {
69
- yield* this.#manifest.hashes()
56
+ try {
57
+ const analysis = await save(this.#bucket, id, readable)
58
+ const {hash, size} = analysis
59
+ const info = {id, size, added: Date.now()}
60
+ await this.#wholesome(() => this.#manifest.commit(hash, info))
61
+ return analysis.hash
62
+ }
63
+ catch (error) {
64
+ await this.#manifest.moveWipToTrash(id)
65
+ throw error
66
+ }
67
+ finally {
68
+ void this.#cleanup().catch(e => console.error("cleanup failed", e))
69
+ }
70
70
  }
71
71
 
72
- async stats() {
73
- return this.#manifest.getStats()
74
- }
72
+ #cleanup = queue(async() => {
73
+ for await (const id of this.#manifest.getExpiredWipIds())
74
+ await this.#manifest.moveWipToTrash(id)
75
75
 
76
- #selfClean = queue(async() => {
77
- const expiredIds = await this.#manifest.getExpiredIds()
78
- await Promise.all(expiredIds.map(id => this.#bucket.delete(id)))
79
- await this.#manifest.deleteWip(...expiredIds)
80
- })
76
+ for await (const id of this.#manifest.listTrashIds()) {
77
+ await this.#bucket.delete(id)
78
+ await this.#manifest.dropTrashRecord(id)
79
+ }
80
+ }, consts.max_jobs)
81
81
  }
82
82
 
package/s/core/types.ts CHANGED
@@ -10,13 +10,23 @@ export type Bucket = {
10
10
  write(id: Id, readable: ReadableStream<Uint8Array>): Promise<void>
11
11
  }
12
12
 
13
+ /** report about a file */
14
+ export type Analysis = {
15
+
16
+ /** blake3 hash of the file's contents */
17
+ hash: Hash
18
+
19
+ /** filesize in bytes */
20
+ size: number
21
+ }
22
+
13
23
  /** metadata for a single file. */
14
24
  export type Info = {
15
25
 
16
26
  /** bucket id for this file's data. */
17
27
  id: Id
18
28
 
19
- /** file size in bytes. */
29
+ /** filesize in bytes. */
20
30
  size: number
21
31
 
22
32
  /** when this file was added to the datalake. */
@@ -1,12 +1,15 @@
1
1
 
2
2
  import {Kv} from "@e280/kv"
3
- import {collect, got} from "@e280/stz"
4
- import {consts} from "../consts.js"
3
+ import {got} from "@e280/stz"
5
4
  import {notFound} from "./not-found.js"
6
5
  import {isExpired} from "./is-expired.js"
7
6
  import {Hash, Id, Info, Stats, Wip} from "../types.js"
8
7
 
9
8
  export class Manifest {
9
+ #kv
10
+
11
+ /** statistics about the whole datalake */
12
+ #stats
10
13
 
11
14
  /** associates file hashes with bucket ids */
12
15
  #info
@@ -15,12 +18,18 @@ export class Manifest {
15
18
  #wip
16
19
 
17
20
  /** statistics about the whole datalake */
18
- #stats
21
+ #trash
19
22
 
20
23
  constructor(kv = new Kv()) {
24
+ this.#kv = kv
25
+ this.#stats = kv.store<Stats>("stats")
21
26
  this.#info = kv.scope<Info>("info")
22
27
  this.#wip = kv.scope<Wip>("wip")
23
- this.#stats = kv.store<Stats>("stats")
28
+ this.#trash = kv.scope<true>("trash")
29
+ }
30
+
31
+ async getStats() {
32
+ return structuredClone((await this.#stats.get()) ?? {count: 0, size: 0})
24
33
  }
25
34
 
26
35
  async* hashes() {
@@ -31,10 +40,6 @@ export class Manifest {
31
40
  return this.#info.has(hash)
32
41
  }
33
42
 
34
- async saveInfo(hash: Hash, info: Info) {
35
- await this.#info.set(hash, info)
36
- }
37
-
38
43
  async getInfo(hash: Hash) {
39
44
  return await this.#info.get(hash)
40
45
  }
@@ -43,10 +48,6 @@ export class Manifest {
43
48
  return got(await this.getInfo(hash), notFound(hash))
44
49
  }
45
50
 
46
- async deleteInfo(hash: Hash) {
47
- await this.#info.del(hash)
48
- }
49
-
50
51
  async addWip(id: Id) {
51
52
  await this.#wip.set(id, {created: Date.now()})
52
53
  }
@@ -55,35 +56,58 @@ export class Manifest {
55
56
  await this.#wip.del(...ids)
56
57
  }
57
58
 
58
- async statsAddFile(size: number) {
59
- await this.#updateStats(stats => {
60
- stats.count += 1
61
- stats.size += size
62
- })
59
+ async* getExpiredWipIds() {
60
+ for await (const [id, wip] of this.#wip.entries()) {
61
+ if (isExpired(wip))
62
+ yield id
63
+ }
63
64
  }
64
65
 
65
- async statsRemoveFile(size: number) {
66
- await this.#updateStats(stats => {
67
- stats.count -= 1
68
- stats.size -= size
69
- })
66
+ async moveWipToTrash(id: Id) {
67
+ await this.#kv.transaction(() => [
68
+ this.#wip.write.del(id),
69
+ this.#trash.write.set(id, true),
70
+ ])
70
71
  }
71
72
 
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)
73
+ async dropTrashRecord(id: Id) {
74
+ await this.#trash.del(id)
77
75
  }
78
76
 
79
- async getStats() {
80
- return structuredClone((await this.#stats.get()) ?? {count: 0, size: 0})
77
+ async* listTrashIds() {
78
+ yield* this.#trash.keys()
81
79
  }
82
80
 
83
- async #updateStats(fn: (stats: Stats) => void) {
81
+ async scheduleDeletion(hash: Hash, info: Info) {
84
82
  const stats = await this.getStats()
85
- fn(stats)
86
- await this.#stats.set(stats)
83
+ stats.count -= 1
84
+ stats.size -= info.size
85
+ await this.#kv.transaction(() => [
86
+ this.#info.write.del(hash),
87
+ this.#trash.write.set(info.id, true),
88
+ this.#stats.write.set(stats),
89
+ ])
90
+ }
91
+
92
+ async commit(hash: Hash, info: Info) {
93
+ const isNewFile = !await this.hasHash(hash)
94
+
95
+ if (isNewFile) {
96
+ const stats = await this.getStats()
97
+ stats.count += 1
98
+ stats.size += info.size
99
+ await this.#kv.transaction(() => [
100
+ this.#wip.write.del(info.id),
101
+ this.#info.write.set(hash, info),
102
+ this.#stats.write.set(stats)
103
+ ])
104
+ }
105
+ else {
106
+ await this.#kv.transaction(() => [
107
+ this.#wip.write.del(info.id),
108
+ this.#trash.write.set(info.id, true),
109
+ ])
110
+ }
87
111
  }
88
112
  }
89
113
 
@@ -1,9 +1,14 @@
1
1
 
2
2
  import {hex} from "@e280/stz"
3
3
  import {blake3} from "@noble/hashes/blake3.js"
4
- import {Bucket, Id} from "../types.js"
4
+ import {Analysis, Bucket, Id} from "../types.js"
5
+
6
+ export async function save(
7
+ bucket: Bucket,
8
+ id: Id,
9
+ readable: ReadableStream<Uint8Array>,
10
+ ): Promise<Analysis> {
5
11
 
6
- export async function saveAndHash(bucket: Bucket, id: Id, readable: ReadableStream<Uint8Array>) {
7
12
  let size = 0
8
13
  const pipe = new TransformStream()
9
14
  const done = bucket.write(id, pipe.readable)
@@ -14,6 +14,6 @@ export async function example() {
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());
17
- console.log(`mammoth stored file by hash "${hash}"`);
17
+ console.log(`mammoth stored file "${hash}"`);
18
18
  }
19
19
  //# sourceMappingURL=example.js.map
@@ -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,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"}
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,wBAAwB,IAAI,GAAG,CAAC,CAAA;AAC7C,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { Analysis } from "./types.js";
2
+ export declare function analyze(readable: ReadableStream<Uint8Array>): Promise<Analysis>;
@@ -1,14 +1,17 @@
1
1
  import { hex, nap } from "@e280/stz";
2
2
  import { blake3 } from "@noble/hashes/blake3.js";
3
3
  import { relaxer } from "./utils/relaxer.js";
4
- export async function hashStream(readable) {
4
+ export async function analyze(readable) {
5
+ let size = 0;
5
6
  const hasher = blake3.create();
6
7
  const relax = relaxer();
7
8
  for await (const chunk of readable) {
8
9
  hasher.update(chunk);
10
+ size += chunk.byteLength;
9
11
  if (relax())
10
12
  await nap();
11
13
  }
12
- return hex.fromBytes(hasher.digest());
14
+ const hash = hex.fromBytes(hasher.digest());
15
+ return { hash, size };
13
16
  }
14
- //# sourceMappingURL=hash-stream.js.map
17
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../s/core/analyze.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAE,GAAG,EAAC,MAAM,WAAW,CAAA;AAClC,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAA;AAE9C,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAE1C,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,QAAoC;IACjE,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAA;IAC9B,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;IAEvB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACpB,IAAI,IAAI,KAAK,CAAC,UAAU,CAAA;QAExB,IAAI,KAAK,EAAE;YACV,MAAM,GAAG,EAAE,CAAA;IACb,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;IAC3C,OAAO,EAAC,IAAI,EAAE,IAAI,EAAC,CAAA;AACpB,CAAC"}
@@ -1,5 +1,4 @@
1
1
  export declare const consts: {
2
2
  max_jobs: number;
3
- self_clean_limit: number;
4
3
  expiry_time: number;
5
4
  };
package/x/core/consts.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { time } from "@e280/stz";
2
2
  export const consts = {
3
3
  max_jobs: 64,
4
- self_clean_limit: 64,
5
4
  expiry_time: time.days(7),
6
5
  };
7
6
  //# sourceMappingURL=consts.js.map
@@ -1 +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"}
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,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;CACzB,CAAA"}
package/x/core/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from "./hash-stream.js";
2
- export * from "./memory-bucket.js";
1
+ export * from "./analyze.js";
3
2
  export * from "./mammoth.js";
3
+ export * from "./memory-bucket.js";
4
4
  export * from "./types.js";
package/x/core/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export * from "./hash-stream.js";
2
- export * from "./memory-bucket.js";
1
+ export * from "./analyze.js";
3
2
  export * from "./mammoth.js";
3
+ export * from "./memory-bucket.js";
4
4
  export * from "./types.js";
5
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/core/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../s/core/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA"}
@@ -4,11 +4,11 @@ import { Hash, Bucket } from "./types.js";
4
4
  export declare class Mammoth {
5
5
  #private;
6
6
  constructor(bucket?: Bucket, kv?: Kv<any>);
7
+ hashes(): AsyncGenerator<string, void, unknown>;
7
8
  has(hash: Hash): Promise<boolean>;
8
9
  info(hash: Hash): Promise<import("./types.js").Info>;
10
+ stats(): Promise<import("./types.js").Stats>;
9
11
  read(hash: Hash): Promise<Blob>;
10
12
  delete(hash: Hash): Promise<void>;
11
13
  write(readable: ReadableStream<Uint8Array>): Promise<Hash>;
12
- hashes(): AsyncGenerator<string, void, unknown>;
13
- stats(): Promise<import("./types.js").Stats>;
14
14
  }
package/x/core/mammoth.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Kv } from "@e280/kv";
2
2
  import { lane, queue } from "@e280/stz";
3
3
  import { consts } from "./consts.js";
4
+ import { save } from "./utils/save.js";
4
5
  import { Manifest } from "./utils/manifest.js";
5
6
  import { randomId } from "./utils/random-id.js";
6
7
  import { MemoryBucket } from "./memory-bucket.js";
7
- import { saveAndHash } from "./utils/save-and-hash.js";
8
8
  /** file storage datalake, content-addressed with blake3 hashes. */
9
9
  export class Mammoth {
10
10
  #bucket;
@@ -14,12 +14,18 @@ export class Mammoth {
14
14
  this.#bucket = bucket;
15
15
  this.#manifest = new Manifest(kv);
16
16
  }
17
+ async *hashes() {
18
+ yield* this.#manifest.hashes();
19
+ }
17
20
  async has(hash) {
18
21
  return this.#manifest.hasHash(hash);
19
22
  }
20
23
  async info(hash) {
21
24
  return this.#manifest.needInfo(hash);
22
25
  }
26
+ async stats() {
27
+ return this.#manifest.getStats();
28
+ }
23
29
  async read(hash) {
24
30
  const { id } = await this.#manifest.needInfo(hash);
25
31
  return this.#bucket.read(id);
@@ -27,40 +33,36 @@ export class Mammoth {
27
33
  async delete(hash) {
28
34
  await this.#wholesome(async () => {
29
35
  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
- }
36
+ if (info)
37
+ await this.#manifest.scheduleDeletion(hash, info);
35
38
  });
39
+ await this.#cleanup();
36
40
  }
37
41
  async write(readable) {
38
42
  const id = randomId();
39
43
  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);
51
- void this.#selfClean().catch(() => { });
52
- return hash;
53
- }
54
- async *hashes() {
55
- yield* this.#manifest.hashes();
56
- }
57
- async stats() {
58
- return this.#manifest.getStats();
44
+ try {
45
+ const analysis = await save(this.#bucket, id, readable);
46
+ const { hash, size } = analysis;
47
+ const info = { id, size, added: Date.now() };
48
+ await this.#wholesome(() => this.#manifest.commit(hash, info));
49
+ return analysis.hash;
50
+ }
51
+ catch (error) {
52
+ await this.#manifest.moveWipToTrash(id);
53
+ throw error;
54
+ }
55
+ finally {
56
+ void this.#cleanup().catch(e => console.error("cleanup failed", e));
57
+ }
59
58
  }
60
- #selfClean = queue(async () => {
61
- const expiredIds = await this.#manifest.getExpiredIds();
62
- await Promise.all(expiredIds.map(id => this.#bucket.delete(id)));
63
- await this.#manifest.deleteWip(...expiredIds);
64
- });
59
+ #cleanup = queue(async () => {
60
+ for await (const id of this.#manifest.getExpiredWipIds())
61
+ await this.#manifest.moveWipToTrash(id);
62
+ for await (const id of this.#manifest.listTrashIds()) {
63
+ await this.#bucket.delete(id);
64
+ await this.#manifest.dropTrashRecord(id);
65
+ }
66
+ }, consts.max_jobs);
65
67
  }
66
68
  //# sourceMappingURL=mammoth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mammoth.js","sourceRoot":"","sources":["../../s/core/mammoth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mammoth.js","sourceRoot":"","sources":["../../s/core/mammoth.ts"],"names":[],"mappings":"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"}
package/x/core/types.d.ts CHANGED
@@ -7,11 +7,18 @@ export type Bucket = {
7
7
  read(id: Id): Promise<Blob>;
8
8
  write(id: Id, readable: ReadableStream<Uint8Array>): Promise<void>;
9
9
  };
10
+ /** report about a file */
11
+ export type Analysis = {
12
+ /** blake3 hash of the file's contents */
13
+ hash: Hash;
14
+ /** filesize in bytes */
15
+ size: number;
16
+ };
10
17
  /** metadata for a single file. */
11
18
  export type Info = {
12
19
  /** bucket id for this file's data. */
13
20
  id: Id;
14
- /** file size in bytes. */
21
+ /** filesize in bytes. */
15
22
  size: number;
16
23
  /** when this file was added to the datalake. */
17
24
  added: number;
@@ -3,16 +3,17 @@ import { Hash, Id, Info, Stats } from "../types.js";
3
3
  export declare class Manifest {
4
4
  #private;
5
5
  constructor(kv?: Kv<any>);
6
+ getStats(): Promise<Stats>;
6
7
  hashes(): AsyncGenerator<string, void, unknown>;
7
8
  hasHash(hash: Hash): Promise<boolean>;
8
- saveInfo(hash: Hash, info: Info): Promise<void>;
9
9
  getInfo(hash: Hash): Promise<Info | undefined>;
10
10
  needInfo(hash: Hash): Promise<Info>;
11
- deleteInfo(hash: Hash): Promise<void>;
12
11
  addWip(id: Id): Promise<void>;
13
12
  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>;
13
+ getExpiredWipIds(): AsyncGenerator<string, void, unknown>;
14
+ moveWipToTrash(id: Id): Promise<void>;
15
+ dropTrashRecord(id: Id): Promise<void>;
16
+ listTrashIds(): AsyncGenerator<string, void, unknown>;
17
+ scheduleDeletion(hash: Hash, info: Info): Promise<void>;
18
+ commit(hash: Hash, info: Info): Promise<void>;
18
19
  }
@@ -1,19 +1,26 @@
1
1
  import { Kv } from "@e280/kv";
2
- import { collect, got } from "@e280/stz";
3
- import { consts } from "../consts.js";
2
+ import { got } from "@e280/stz";
4
3
  import { notFound } from "./not-found.js";
5
4
  import { isExpired } from "./is-expired.js";
6
5
  export class Manifest {
6
+ #kv;
7
+ /** statistics about the whole datalake */
8
+ #stats;
7
9
  /** associates file hashes with bucket ids */
8
10
  #info;
9
11
  /** temporary record of writes in-progress */
10
12
  #wip;
11
13
  /** statistics about the whole datalake */
12
- #stats;
14
+ #trash;
13
15
  constructor(kv = new Kv()) {
16
+ this.#kv = kv;
17
+ this.#stats = kv.store("stats");
14
18
  this.#info = kv.scope("info");
15
19
  this.#wip = kv.scope("wip");
16
- this.#stats = kv.store("stats");
20
+ this.#trash = kv.scope("trash");
21
+ }
22
+ async getStats() {
23
+ return structuredClone((await this.#stats.get()) ?? { count: 0, size: 0 });
17
24
  }
18
25
  async *hashes() {
19
26
  yield* this.#info.keys();
@@ -21,49 +28,64 @@ export class Manifest {
21
28
  async hasHash(hash) {
22
29
  return this.#info.has(hash);
23
30
  }
24
- async saveInfo(hash, info) {
25
- await this.#info.set(hash, info);
26
- }
27
31
  async getInfo(hash) {
28
32
  return await this.#info.get(hash);
29
33
  }
30
34
  async needInfo(hash) {
31
35
  return got(await this.getInfo(hash), notFound(hash));
32
36
  }
33
- async deleteInfo(hash) {
34
- await this.#info.del(hash);
35
- }
36
37
  async addWip(id) {
37
38
  await this.#wip.set(id, { created: Date.now() });
38
39
  }
39
40
  async deleteWip(...ids) {
40
41
  await this.#wip.del(...ids);
41
42
  }
42
- async statsAddFile(size) {
43
- await this.#updateStats(stats => {
44
- stats.count += 1;
45
- stats.size += size;
46
- });
43
+ async *getExpiredWipIds() {
44
+ for await (const [id, wip] of this.#wip.entries()) {
45
+ if (isExpired(wip))
46
+ yield id;
47
+ }
47
48
  }
48
- async statsRemoveFile(size) {
49
- await this.#updateStats(stats => {
50
- stats.count -= 1;
51
- stats.size -= size;
52
- });
49
+ async moveWipToTrash(id) {
50
+ await this.#kv.transaction(() => [
51
+ this.#wip.write.del(id),
52
+ this.#trash.write.set(id, true),
53
+ ]);
53
54
  }
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);
55
+ async dropTrashRecord(id) {
56
+ await this.#trash.del(id);
59
57
  }
60
- async getStats() {
61
- return structuredClone((await this.#stats.get()) ?? { count: 0, size: 0 });
58
+ async *listTrashIds() {
59
+ yield* this.#trash.keys();
62
60
  }
63
- async #updateStats(fn) {
61
+ async scheduleDeletion(hash, info) {
64
62
  const stats = await this.getStats();
65
- fn(stats);
66
- await this.#stats.set(stats);
63
+ stats.count -= 1;
64
+ stats.size -= info.size;
65
+ await this.#kv.transaction(() => [
66
+ this.#info.write.del(hash),
67
+ this.#trash.write.set(info.id, true),
68
+ this.#stats.write.set(stats),
69
+ ]);
70
+ }
71
+ async commit(hash, info) {
72
+ const isNewFile = !await this.hasHash(hash);
73
+ if (isNewFile) {
74
+ const stats = await this.getStats();
75
+ stats.count += 1;
76
+ stats.size += info.size;
77
+ await this.#kv.transaction(() => [
78
+ this.#wip.write.del(info.id),
79
+ this.#info.write.set(hash, info),
80
+ this.#stats.write.set(stats)
81
+ ]);
82
+ }
83
+ else {
84
+ await this.#kv.transaction(() => [
85
+ this.#wip.write.del(info.id),
86
+ this.#trash.write.set(info.id, true),
87
+ ]);
88
+ }
67
89
  }
68
90
  }
69
91
  //# sourceMappingURL=manifest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../s/core/utils/manifest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../s/core/utils/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Analysis, Bucket, Id } from "../types.js";
2
+ export declare function save(bucket: Bucket, id: Id, readable: ReadableStream<Uint8Array>): Promise<Analysis>;
@@ -1,6 +1,6 @@
1
1
  import { hex } from "@e280/stz";
2
2
  import { blake3 } from "@noble/hashes/blake3.js";
3
- export async function saveAndHash(bucket, id, readable) {
3
+ export async function save(bucket, id, readable) {
4
4
  let size = 0;
5
5
  const pipe = new TransformStream();
6
6
  const done = bucket.write(id, pipe.readable);
@@ -16,4 +16,4 @@ export async function saveAndHash(bucket, id, readable) {
16
16
  const hash = hex.fromBytes(hasher.digest());
17
17
  return { hash, size };
18
18
  }
19
- //# sourceMappingURL=save-and-hash.js.map
19
+ //# sourceMappingURL=save.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save.js","sourceRoot":"","sources":["../../../s/core/utils/save.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAC,MAAM,WAAW,CAAA;AAC7B,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAA;AAG9C,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,MAAc,EACd,EAAM,EACN,QAAoC;IAGrC,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"}
@@ -1 +0,0 @@
1
- export declare function hashStream(readable: ReadableStream<Uint8Array>): Promise<string>;
@@ -1 +0,0 @@
1
- {"version":3,"file":"hash-stream.js","sourceRoot":"","sources":["../../s/core/hash-stream.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAE,GAAG,EAAC,MAAM,WAAW,CAAA;AAClC,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAA;AAC9C,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAE1C,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAoC;IACpE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAA;IAC9B,MAAM,KAAK,GAAG,OAAO,EAAE,CAAA;IAEvB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAEpB,IAAI,KAAK,EAAE;YACV,MAAM,GAAG,EAAE,CAAA;IACb,CAAC;IAED,OAAO,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;AACtC,CAAC"}
@@ -1,5 +0,0 @@
1
- import { Bucket, Id } from "../types.js";
2
- export declare function saveAndHash(bucket: Bucket, id: Id, readable: ReadableStream<Uint8Array>): Promise<{
3
- hash: string;
4
- size: number;
5
- }>;
@@ -1 +0,0 @@
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"}