Skip to content

Commit b14b3dc

Browse files
committed
feat(chrono): commit use-case
1 parent 8e59161 commit b14b3dc

File tree

10 files changed

+140
-13
lines changed

10 files changed

+140
-13
lines changed

packages/chrono/playground/PlayApp.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ async function run() {
3232
name: 'remove',
3333
method: 'removeEntry',
3434
},
35+
{
36+
name: 'commit',
37+
method: 'commit',
38+
},
3539
]
3640

3741
const command = options.find((o) => o.name === commandName)

packages/chrono/src/ChronoApp.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import CatFileUseCase from './use-cases/CatFileUseCase'
1414
import HashFileUseCase from './use-cases/HashFileUseCase'
1515
import RemoveStageItemUseCase from './use-cases/RemoveStageItemUseCase'
1616
import AddStateItemUseCase from './use-cases/AddStageItemUseCase'
17+
import CommitUseCase from './use-cases/CommitUseCase'
1718

1819
export default class ChronoApp {
1920
private readonly objectRepository: IObjectRepository
@@ -59,7 +60,7 @@ export default class ChronoApp {
5960
}
6061

6162
public async catEntry(objectHash: string) {
62-
const useCase = new CatFileUseCase(this.objectTemporaryRepository)
63+
const useCase = new CatFileUseCase(this.objectRepository, this.objectTemporaryRepository)
6364

6465
return useCase.execute({ objectHash })
6566
}
@@ -80,4 +81,16 @@ export default class ChronoApp {
8081

8182
return useCase.execute({ path })
8283
}
84+
85+
public async commit(message: string, body?: string) {
86+
const useCase = new CommitUseCase(
87+
this.objectRepository,
88+
this.objectTemporaryRepository,
89+
this.blobRepository,
90+
this.blobTemporaryRepository,
91+
this.stageItemRepository
92+
)
93+
94+
return useCase.execute({ message, body })
95+
}
8396
}

packages/chrono/src/entities/ChronoObject.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export default class ChronoObject {
4545
}
4646
}
4747

48-
public static from(object: Record<string, any>) {
48+
public static from(object: Record<string, any>, body?: string) {
4949
let content = ''
5050

5151
Object.entries(object).forEach(([key, value]) => {
@@ -54,6 +54,10 @@ export default class ChronoObject {
5454

5555
content += ChronoObject.HEAD_SEPARATOR
5656

57+
if (body) {
58+
content += body
59+
}
60+
5761
return new ChronoObject(content)
5862
}
5963

packages/chrono/src/entities/ChronoObjectTree.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import ChronoObject from './ChronoObject'
22

33
export interface ChronoObjectTreeEntry {
4-
name: string
4+
path: string
55
hash: string
6-
type: 'blob' | 'tree'
6+
type: string
77
}
88

99
export default class ChronoObjectTree extends ChronoObject {
1010
public get entries() {
1111
const lines = this.body.split('\n').filter(Boolean)
1212

1313
return lines.map((line) => {
14-
const [type, hash, name] = line.split(' ')
14+
const [type, hash, path] = line.split(' ')
1515

1616
return {
1717
type,
1818
hash,
19-
name,
19+
path,
2020
}
2121
})
2222
}
@@ -32,7 +32,7 @@ export default class ChronoObjectTree extends ChronoObject {
3232
let content = 'type: tree' + ChronoObject.HEAD_SEPARATOR
3333

3434
entries.forEach((entry) => {
35-
content += `${entry.type} ${entry.hash} ${entry.name}\n`
35+
content += `${entry.type} ${entry.hash} ${entry.path}\n`
3636
})
3737

3838
return new ChronoObjectTree(content)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export default interface IBlobRepository {
22
save(content: Uint8Array): Promise<{ blobHash: string }>
33
find(blobHash: string): Promise<Uint8Array | null>
4+
findOrFail(blobHash: string): Promise<Uint8Array>
5+
copyFrom(source: IBlobRepository, blobHash: string): Promise<void>
46
}

packages/chrono/src/repositories/IObjectRepository.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@ import ChronoObject from '../entities/ChronoObject'
22

33
export default interface IObjectRepository {
44
find(objectHash: string): Promise<ChronoObject | null>
5+
findOrFail(objectHash: string): Promise<ChronoObject>
56
save(object: ChronoObject): Promise<{ objectHash: string }>
7+
copyFrom(repository: IObjectRepository, objectHash: string): Promise<void>
68
}

packages/chrono/src/repositories/implementations/LocalBlobRepository.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,20 @@ export default class LocalBlobRepository implements IBlobRepository {
4646

4747
return bytes
4848
}
49+
50+
public findOrFail: IBlobRepository['findOrFail'] = async (blobHash) => {
51+
const bytes = await this.find(blobHash)
52+
53+
if (!bytes) {
54+
throw new Error(`Blob ${blobHash} not found`)
55+
}
56+
57+
return bytes
58+
}
59+
60+
public copyFrom: IBlobRepository['copyFrom'] = async (repository, blobHash) => {
61+
const bytes = await repository.findOrFail(blobHash)
62+
63+
await this.save(bytes)
64+
}
4965
}

packages/chrono/src/repositories/implementations/LocalObjectRepository.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export default class LocalObjectRepository implements IObjectRepository {
3636

3737
const folderPath = this.drive.resolve(this.directory, startHash)
3838

39+
if (!(await this.drive.exists(folderPath))) {
40+
return null
41+
}
42+
3943
const files = await this.drive.readdir(folderPath)
4044

4145
const search = files.find((file) => file.startsWith(endHash))
@@ -60,4 +64,20 @@ export default class LocalObjectRepository implements IObjectRepository {
6064

6165
return chronoObject
6266
}
67+
68+
public findOrFail: IObjectRepository['findOrFail'] = async (objectHash) => {
69+
const chronoObject = await this.find(objectHash)
70+
71+
if (!chronoObject) {
72+
throw new Error(`Object ${objectHash} not found`)
73+
}
74+
75+
return chronoObject
76+
}
77+
78+
public copyFrom: IObjectRepository['copyFrom'] = async (repository, objectHash) => {
79+
const chronoObject = await repository.findOrFail(objectHash)
80+
81+
await this.save(chronoObject)
82+
}
6383
}
Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1-
import BaseException from '../exceptions/BaseException'
21
import IObjectRepository from '../repositories/IObjectRepository'
32

43
interface CatUseCaseParams {
54
objectHash: string
65
}
76

87
export default class CatFileUseCase {
9-
constructor(private readonly objectRepository: IObjectRepository) {}
8+
constructor(
9+
private readonly objectRepository: IObjectRepository,
10+
private readonly objectTemporaryRepository: IObjectRepository
11+
) {}
1012

1113
async execute({ objectHash }: CatUseCaseParams) {
12-
const object = await this.objectRepository.find(objectHash)
14+
let result = await this.objectRepository.find(objectHash)
15+
let isTemporary = false
1316

14-
if (!object) {
15-
throw new BaseException(`Object ${objectHash} not found`)
17+
if (!result) {
18+
result = await this.objectTemporaryRepository.find(objectHash)
19+
isTemporary = !!result
1620
}
1721

18-
return object.serialize()
22+
return {
23+
isTemporary,
24+
object: result?.serialize() ?? null,
25+
}
1926
}
2027
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import ChronoObject from '../entities/ChronoObject'
2+
import ChronoObjectTree from '../entities/ChronoObjectTree'
3+
import IBlobRepository from '../repositories/IBlobRepository'
4+
import IObjectRepository from '../repositories/IObjectRepository'
5+
import IStageItemRepository from '../repositories/IStageItemRepository'
6+
import HashFileUseCase from './HashFileUseCase'
7+
8+
interface Params {
9+
message: string
10+
body?: string
11+
}
12+
13+
export default class CommitUseCase {
14+
public hashFileUseCase: HashFileUseCase
15+
16+
constructor(
17+
private readonly objectRepository: IObjectRepository,
18+
private readonly objectTemporaryRepository: IObjectRepository,
19+
private readonly blobRepository: IBlobRepository,
20+
private readonly blobTemporaryRepository: IBlobRepository,
21+
private readonly stageItemRepository: IStageItemRepository
22+
) {}
23+
24+
async execute({ message, body }: Params) {
25+
// get stage
26+
const items = await this.stageItemRepository.findAll()
27+
28+
// copy objects & blobs from .chrono/tmp to .chrono/objects & .chrono/blobs
29+
for await (const item of items) {
30+
const object = await this.objectTemporaryRepository.findOrFail(item.hash)
31+
await this.objectRepository.copyFrom(this.objectTemporaryRepository, item.hash)
32+
33+
if (object.type === 'blob') {
34+
await this.blobRepository.copyFrom(
35+
this.blobTemporaryRepository,
36+
object.head.blobHash
37+
)
38+
}
39+
}
40+
41+
// create tree
42+
const tree = ChronoObjectTree.fromEntries(items)
43+
44+
const { objectHash: treeHash } = await this.objectRepository.save(tree)
45+
// create commit
46+
47+
const commit = ChronoObject.from(
48+
{
49+
type: 'commit',
50+
tree: treeHash,
51+
date: new Date().toISOString(),
52+
message: message || '',
53+
},
54+
body
55+
)
56+
57+
return this.objectRepository.save(commit)
58+
}
59+
}

0 commit comments

Comments
 (0)