gitx.do 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/durable-object/object-store.d.ts +113 -0
- package/dist/durable-object/object-store.d.ts.map +1 -0
- package/dist/durable-object/object-store.js +387 -0
- package/dist/durable-object/object-store.js.map +1 -0
- package/dist/durable-object/schema.d.ts +17 -0
- package/dist/durable-object/schema.d.ts.map +1 -0
- package/dist/durable-object/schema.js +43 -0
- package/dist/durable-object/schema.js.map +1 -0
- package/dist/durable-object/wal.d.ts +111 -0
- package/dist/durable-object/wal.d.ts.map +1 -0
- package/dist/durable-object/wal.js +200 -0
- package/dist/durable-object/wal.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/adapter.d.ts +231 -0
- package/dist/mcp/adapter.d.ts.map +1 -0
- package/dist/mcp/adapter.js +502 -0
- package/dist/mcp/adapter.js.map +1 -0
- package/dist/mcp/sandbox.d.ts +261 -0
- package/dist/mcp/sandbox.d.ts.map +1 -0
- package/dist/mcp/sandbox.js +983 -0
- package/dist/mcp/sandbox.js.map +1 -0
- package/dist/mcp/sdk-adapter.d.ts +413 -0
- package/dist/mcp/sdk-adapter.d.ts.map +1 -0
- package/dist/mcp/sdk-adapter.js +672 -0
- package/dist/mcp/sdk-adapter.js.map +1 -0
- package/dist/mcp/tools.d.ts +133 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +1604 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/ops/blame.d.ts +148 -0
- package/dist/ops/blame.d.ts.map +1 -0
- package/dist/ops/blame.js +754 -0
- package/dist/ops/blame.js.map +1 -0
- package/dist/ops/branch.d.ts +215 -0
- package/dist/ops/branch.d.ts.map +1 -0
- package/dist/ops/branch.js +608 -0
- package/dist/ops/branch.js.map +1 -0
- package/dist/ops/commit-traversal.d.ts +209 -0
- package/dist/ops/commit-traversal.d.ts.map +1 -0
- package/dist/ops/commit-traversal.js +755 -0
- package/dist/ops/commit-traversal.js.map +1 -0
- package/dist/ops/commit.d.ts +221 -0
- package/dist/ops/commit.d.ts.map +1 -0
- package/dist/ops/commit.js +606 -0
- package/dist/ops/commit.js.map +1 -0
- package/dist/ops/merge-base.d.ts +223 -0
- package/dist/ops/merge-base.d.ts.map +1 -0
- package/dist/ops/merge-base.js +581 -0
- package/dist/ops/merge-base.js.map +1 -0
- package/dist/ops/merge.d.ts +385 -0
- package/dist/ops/merge.d.ts.map +1 -0
- package/dist/ops/merge.js +1203 -0
- package/dist/ops/merge.js.map +1 -0
- package/dist/ops/tag.d.ts +182 -0
- package/dist/ops/tag.d.ts.map +1 -0
- package/dist/ops/tag.js +608 -0
- package/dist/ops/tag.js.map +1 -0
- package/dist/ops/tree-builder.d.ts +82 -0
- package/dist/ops/tree-builder.d.ts.map +1 -0
- package/dist/ops/tree-builder.js +246 -0
- package/dist/ops/tree-builder.js.map +1 -0
- package/dist/ops/tree-diff.d.ts +243 -0
- package/dist/ops/tree-diff.d.ts.map +1 -0
- package/dist/ops/tree-diff.js +657 -0
- package/dist/ops/tree-diff.js.map +1 -0
- package/dist/pack/delta.d.ts +68 -0
- package/dist/pack/delta.d.ts.map +1 -0
- package/dist/pack/delta.js +343 -0
- package/dist/pack/delta.js.map +1 -0
- package/dist/pack/format.d.ts +84 -0
- package/dist/pack/format.d.ts.map +1 -0
- package/dist/pack/format.js +261 -0
- package/dist/pack/format.js.map +1 -0
- package/dist/pack/full-generation.d.ts +327 -0
- package/dist/pack/full-generation.d.ts.map +1 -0
- package/dist/pack/full-generation.js +1159 -0
- package/dist/pack/full-generation.js.map +1 -0
- package/dist/pack/generation.d.ts +118 -0
- package/dist/pack/generation.d.ts.map +1 -0
- package/dist/pack/generation.js +459 -0
- package/dist/pack/generation.js.map +1 -0
- package/dist/pack/index.d.ts +181 -0
- package/dist/pack/index.d.ts.map +1 -0
- package/dist/pack/index.js +552 -0
- package/dist/pack/index.js.map +1 -0
- package/dist/refs/branch.d.ts +224 -0
- package/dist/refs/branch.d.ts.map +1 -0
- package/dist/refs/branch.js +170 -0
- package/dist/refs/branch.js.map +1 -0
- package/dist/refs/storage.d.ts +208 -0
- package/dist/refs/storage.d.ts.map +1 -0
- package/dist/refs/storage.js +421 -0
- package/dist/refs/storage.js.map +1 -0
- package/dist/refs/tag.d.ts +230 -0
- package/dist/refs/tag.d.ts.map +1 -0
- package/dist/refs/tag.js +188 -0
- package/dist/refs/tag.js.map +1 -0
- package/dist/storage/lru-cache.d.ts +188 -0
- package/dist/storage/lru-cache.d.ts.map +1 -0
- package/dist/storage/lru-cache.js +410 -0
- package/dist/storage/lru-cache.js.map +1 -0
- package/dist/storage/object-index.d.ts +140 -0
- package/dist/storage/object-index.d.ts.map +1 -0
- package/dist/storage/object-index.js +166 -0
- package/dist/storage/object-index.js.map +1 -0
- package/dist/storage/r2-pack.d.ts +394 -0
- package/dist/storage/r2-pack.d.ts.map +1 -0
- package/dist/storage/r2-pack.js +1062 -0
- package/dist/storage/r2-pack.js.map +1 -0
- package/dist/tiered/cdc-pipeline.d.ts +316 -0
- package/dist/tiered/cdc-pipeline.d.ts.map +1 -0
- package/dist/tiered/cdc-pipeline.js +771 -0
- package/dist/tiered/cdc-pipeline.js.map +1 -0
- package/dist/tiered/migration.d.ts +242 -0
- package/dist/tiered/migration.d.ts.map +1 -0
- package/dist/tiered/migration.js +592 -0
- package/dist/tiered/migration.js.map +1 -0
- package/dist/tiered/parquet-writer.d.ts +248 -0
- package/dist/tiered/parquet-writer.d.ts.map +1 -0
- package/dist/tiered/parquet-writer.js +555 -0
- package/dist/tiered/parquet-writer.js.map +1 -0
- package/dist/tiered/read-path.d.ts +141 -0
- package/dist/tiered/read-path.d.ts.map +1 -0
- package/dist/tiered/read-path.js +204 -0
- package/dist/tiered/read-path.js.map +1 -0
- package/dist/types/objects.d.ts +53 -0
- package/dist/types/objects.d.ts.map +1 -0
- package/dist/types/objects.js +291 -0
- package/dist/types/objects.js.map +1 -0
- package/dist/types/storage.d.ts +117 -0
- package/dist/types/storage.d.ts.map +1 -0
- package/dist/types/storage.js +8 -0
- package/dist/types/storage.js.map +1 -0
- package/dist/utils/hash.d.ts +31 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +60 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/sha1.d.ts +26 -0
- package/dist/utils/sha1.d.ts.map +1 -0
- package/dist/utils/sha1.js +127 -0
- package/dist/utils/sha1.js.map +1 -0
- package/dist/wire/capabilities.d.ts +236 -0
- package/dist/wire/capabilities.d.ts.map +1 -0
- package/dist/wire/capabilities.js +437 -0
- package/dist/wire/capabilities.js.map +1 -0
- package/dist/wire/pkt-line.d.ts +67 -0
- package/dist/wire/pkt-line.d.ts.map +1 -0
- package/dist/wire/pkt-line.js +145 -0
- package/dist/wire/pkt-line.js.map +1 -0
- package/dist/wire/receive-pack.d.ts +302 -0
- package/dist/wire/receive-pack.d.ts.map +1 -0
- package/dist/wire/receive-pack.js +885 -0
- package/dist/wire/receive-pack.js.map +1 -0
- package/dist/wire/smart-http.d.ts +321 -0
- package/dist/wire/smart-http.d.ts.map +1 -0
- package/dist/wire/smart-http.js +654 -0
- package/dist/wire/smart-http.js.map +1 -0
- package/dist/wire/upload-pack.d.ts +333 -0
- package/dist/wire/upload-pack.d.ts.map +1 -0
- package/dist/wire/upload-pack.js +850 -0
- package/dist/wire/upload-pack.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# gitx.do
|
|
2
|
+
|
|
3
|
+
Git on Cloudflare Durable Objects - A complete Git reimplementation for the edge.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Pack Files** - Full Git packfile v2/v3 support with delta compression
|
|
8
|
+
- **Wire Protocol** - Smart HTTP protocol implementation for fetch/push operations
|
|
9
|
+
- **MCP Tools** - Model Context Protocol integration for AI-assisted git operations
|
|
10
|
+
- **Tiered Storage** - Hot/warm/cold storage tiers with automatic promotion
|
|
11
|
+
- Hot: Durable Object SQLite (low latency)
|
|
12
|
+
- Warm: R2 object storage (packed objects)
|
|
13
|
+
- Cold: Analytics/Parquet (cold storage)
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install gitx.do
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Pack Index Operations
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { parsePackIndex, createPackIndex, lookupObject } from 'gitx.do/pack'
|
|
27
|
+
|
|
28
|
+
// Parse an existing pack index
|
|
29
|
+
const index = parsePackIndex(indexData)
|
|
30
|
+
console.log(`Pack contains ${index.objectCount} objects`)
|
|
31
|
+
|
|
32
|
+
// Look up an object by SHA
|
|
33
|
+
const entry = lookupObject(index, 'abc123...')
|
|
34
|
+
if (entry) {
|
|
35
|
+
console.log(`Object at offset ${entry.offset}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create a new pack index
|
|
39
|
+
const newIndex = createPackIndex({ packData })
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Smart HTTP Protocol
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { handleInfoRefs, handleUploadPack, handleReceivePack } from 'gitx.do/wire/smart-http'
|
|
46
|
+
|
|
47
|
+
// Handle ref discovery
|
|
48
|
+
const response = await handleInfoRefs(request, repository, {
|
|
49
|
+
sideBand64k: true,
|
|
50
|
+
ofsDelta: true
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Handle fetch
|
|
54
|
+
const packResponse = await handleUploadPack(request, repository)
|
|
55
|
+
|
|
56
|
+
// Handle push
|
|
57
|
+
const pushResponse = await handleReceivePack(request, repository)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### MCP Tools
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { gitTools, invokeTool, registerTool } from 'gitx.do/mcp/tools'
|
|
64
|
+
|
|
65
|
+
// List available git tools
|
|
66
|
+
console.log(gitTools.map(t => t.name))
|
|
67
|
+
// ['git_status', 'git_log', 'git_diff', 'git_commit', ...]
|
|
68
|
+
|
|
69
|
+
// Invoke a tool
|
|
70
|
+
const result = await invokeTool('git_status', { path: '/repo', short: true })
|
|
71
|
+
|
|
72
|
+
// Register a custom tool
|
|
73
|
+
registerTool({
|
|
74
|
+
name: 'my_tool',
|
|
75
|
+
description: 'Custom git operation',
|
|
76
|
+
inputSchema: { type: 'object', properties: {} },
|
|
77
|
+
handler: async (params) => ({ content: [{ type: 'text', text: 'Done' }] })
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Tiered Storage
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { TieredReader } from 'gitx.do/tiered/read-path'
|
|
85
|
+
|
|
86
|
+
const reader = new TieredReader(hotBackend, warmBackend, coldBackend, {
|
|
87
|
+
hot: { enabled: true, maxSize: 1024 * 1024 },
|
|
88
|
+
warm: { enabled: true },
|
|
89
|
+
cold: { enabled: true },
|
|
90
|
+
promotionPolicy: 'aggressive'
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Read with automatic tier fallback and promotion
|
|
94
|
+
const result = await reader.read(sha)
|
|
95
|
+
console.log(`Found in ${result.tier} tier, latency: ${result.latencyMs}ms`)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### R2 Pack Storage
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { R2PackStorage } from 'gitx.do/storage/r2-pack'
|
|
102
|
+
|
|
103
|
+
const storage = new R2PackStorage({ bucket, prefix: 'repos/my-repo/' })
|
|
104
|
+
|
|
105
|
+
// Upload a packfile
|
|
106
|
+
const result = await storage.uploadPackfile(packData, indexData)
|
|
107
|
+
|
|
108
|
+
// Download with verification
|
|
109
|
+
const pack = await storage.downloadPackfile(packId, { verify: true })
|
|
110
|
+
|
|
111
|
+
// Use multi-pack index for cross-pack lookups
|
|
112
|
+
await storage.rebuildMultiPackIndex()
|
|
113
|
+
const midx = await storage.getMultiPackIndex()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## API Overview
|
|
117
|
+
|
|
118
|
+
### Pack Module (`gitx.do/pack`)
|
|
119
|
+
|
|
120
|
+
- `parsePackIndex(data)` - Parse a pack index file
|
|
121
|
+
- `createPackIndex(options)` - Create a new pack index
|
|
122
|
+
- `lookupObject(index, sha)` - Find object by SHA in index
|
|
123
|
+
- `verifyPackIndex(data)` - Verify index integrity
|
|
124
|
+
- `calculateCRC32(data)` - Compute CRC32 checksum
|
|
125
|
+
|
|
126
|
+
### Wire Protocol (`gitx.do/wire`)
|
|
127
|
+
|
|
128
|
+
- `handleInfoRefs()` - Ref discovery endpoint
|
|
129
|
+
- `handleUploadPack()` - Fetch data transfer
|
|
130
|
+
- `handleReceivePack()` - Push data transfer
|
|
131
|
+
- `formatRefAdvertisement()` - Format ref list
|
|
132
|
+
- `parseCapabilities()` - Parse protocol capabilities
|
|
133
|
+
|
|
134
|
+
### MCP Tools (`gitx.do/mcp`)
|
|
135
|
+
|
|
136
|
+
- `gitTools` - Array of available git tool definitions
|
|
137
|
+
- `invokeTool(name, params)` - Execute a tool by name
|
|
138
|
+
- `registerTool(tool)` - Add a custom tool
|
|
139
|
+
- `validateToolInput(tool, params)` - Validate parameters
|
|
140
|
+
|
|
141
|
+
### Tiered Storage (`gitx.do/tiered`)
|
|
142
|
+
|
|
143
|
+
- `TieredReader` - Multi-tier read path with promotion
|
|
144
|
+
- `StoredObject` - Object representation
|
|
145
|
+
- `TieredStorageConfig` - Configuration options
|
|
146
|
+
|
|
147
|
+
### R2 Storage (`gitx.do/storage`)
|
|
148
|
+
|
|
149
|
+
- `R2PackStorage` - Packfile management for R2
|
|
150
|
+
- `uploadPackfile()` - Store pack and index
|
|
151
|
+
- `downloadPackfile()` - Retrieve with optional verification
|
|
152
|
+
- `createMultiPackIndex()` - Build cross-pack index
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectStore - Git object storage implementation
|
|
3
|
+
*
|
|
4
|
+
* Handles CRUD operations for git objects (blob, tree, commit, tag)
|
|
5
|
+
* with SHA-1 hash computation and proper git object format.
|
|
6
|
+
*/
|
|
7
|
+
import { DurableObjectStorage } from './schema';
|
|
8
|
+
import { ObjectType, BlobObject, TreeObject, CommitObject, TagObject, TreeEntry, Author } from '../types/objects';
|
|
9
|
+
/**
|
|
10
|
+
* Stored object record in SQLite
|
|
11
|
+
*/
|
|
12
|
+
export interface StoredObject {
|
|
13
|
+
sha: string;
|
|
14
|
+
type: ObjectType;
|
|
15
|
+
size: number;
|
|
16
|
+
data: Uint8Array;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* ObjectStore class for managing git objects in SQLite storage
|
|
21
|
+
*/
|
|
22
|
+
export declare class ObjectStore {
|
|
23
|
+
private storage;
|
|
24
|
+
constructor(storage: DurableObjectStorage);
|
|
25
|
+
/**
|
|
26
|
+
* Store a raw object and return its SHA
|
|
27
|
+
*/
|
|
28
|
+
putObject(type: ObjectType, data: Uint8Array): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Store a tree object with entries
|
|
31
|
+
*/
|
|
32
|
+
putTreeObject(entries: TreeEntry[]): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Store a commit object
|
|
35
|
+
*/
|
|
36
|
+
putCommitObject(commit: {
|
|
37
|
+
tree: string;
|
|
38
|
+
parents: string[];
|
|
39
|
+
author: Author;
|
|
40
|
+
committer: Author;
|
|
41
|
+
message: string;
|
|
42
|
+
}): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Store a tag object
|
|
45
|
+
*/
|
|
46
|
+
putTagObject(tag: {
|
|
47
|
+
object: string;
|
|
48
|
+
objectType: ObjectType;
|
|
49
|
+
tagger: Author;
|
|
50
|
+
message: string;
|
|
51
|
+
name: string;
|
|
52
|
+
}): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Retrieve an object by SHA
|
|
55
|
+
*/
|
|
56
|
+
getObject(sha: string): Promise<StoredObject | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Delete an object by SHA
|
|
59
|
+
*/
|
|
60
|
+
deleteObject(sha: string): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Check if an object exists
|
|
63
|
+
*/
|
|
64
|
+
hasObject(sha: string): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Verify an object's integrity by recomputing its hash
|
|
67
|
+
*/
|
|
68
|
+
verifyObject(sha: string): Promise<boolean>;
|
|
69
|
+
/**
|
|
70
|
+
* Get object type by SHA
|
|
71
|
+
*/
|
|
72
|
+
getObjectType(sha: string): Promise<ObjectType | null>;
|
|
73
|
+
/**
|
|
74
|
+
* Get object size by SHA
|
|
75
|
+
*/
|
|
76
|
+
getObjectSize(sha: string): Promise<number | null>;
|
|
77
|
+
/**
|
|
78
|
+
* Store multiple objects in a batch
|
|
79
|
+
*/
|
|
80
|
+
putObjects(objects: {
|
|
81
|
+
type: ObjectType;
|
|
82
|
+
data: Uint8Array;
|
|
83
|
+
}[]): Promise<string[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Retrieve multiple objects by SHA
|
|
86
|
+
*/
|
|
87
|
+
getObjects(shas: string[]): Promise<(StoredObject | null)[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Get a blob object with parsed content
|
|
90
|
+
*/
|
|
91
|
+
getBlobObject(sha: string): Promise<BlobObject | null>;
|
|
92
|
+
/**
|
|
93
|
+
* Get a tree object with parsed entries
|
|
94
|
+
*/
|
|
95
|
+
getTreeObject(sha: string): Promise<TreeObject | null>;
|
|
96
|
+
/**
|
|
97
|
+
* Get a commit object with parsed fields
|
|
98
|
+
*/
|
|
99
|
+
getCommitObject(sha: string): Promise<CommitObject | null>;
|
|
100
|
+
/**
|
|
101
|
+
* Get a tag object with parsed fields
|
|
102
|
+
*/
|
|
103
|
+
getTagObject(sha: string): Promise<TagObject | null>;
|
|
104
|
+
/**
|
|
105
|
+
* Get raw serialized object with git header
|
|
106
|
+
*/
|
|
107
|
+
getRawObject(sha: string): Promise<Uint8Array | null>;
|
|
108
|
+
/**
|
|
109
|
+
* Log operation to WAL
|
|
110
|
+
*/
|
|
111
|
+
private logToWAL;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=object-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-store.d.ts","sourceRoot":"","sources":["../../src/durable-object/object-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectStore - Git object storage implementation
|
|
3
|
+
*
|
|
4
|
+
* Handles CRUD operations for git objects (blob, tree, commit, tag)
|
|
5
|
+
* with SHA-1 hash computation and proper git object format.
|
|
6
|
+
*/
|
|
7
|
+
import { hashObject } from '../utils/hash';
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const decoder = new TextDecoder();
|
|
10
|
+
/**
|
|
11
|
+
* ObjectStore class for managing git objects in SQLite storage
|
|
12
|
+
*/
|
|
13
|
+
export class ObjectStore {
|
|
14
|
+
storage;
|
|
15
|
+
constructor(storage) {
|
|
16
|
+
this.storage = storage;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Store a raw object and return its SHA
|
|
20
|
+
*/
|
|
21
|
+
async putObject(type, data) {
|
|
22
|
+
// Compute SHA-1 hash using git object format: "type size\0content"
|
|
23
|
+
const sha = await hashObject(type, data);
|
|
24
|
+
// Log to WAL first
|
|
25
|
+
await this.logToWAL('PUT', sha, type, data);
|
|
26
|
+
// Store the object
|
|
27
|
+
this.storage.sql.exec('INSERT OR REPLACE INTO objects (sha, type, size, data, created_at) VALUES (?, ?, ?, ?, ?)', sha, type, data.length, data, Date.now());
|
|
28
|
+
// Update object index
|
|
29
|
+
this.storage.sql.exec('INSERT OR REPLACE INTO object_index (sha, tier, location, size, type) VALUES (?, ?, ?, ?, ?)', sha, 'hot', 'local', data.length, type);
|
|
30
|
+
return sha;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Store a tree object with entries
|
|
34
|
+
*/
|
|
35
|
+
async putTreeObject(entries) {
|
|
36
|
+
// Sort entries by name (directories get trailing / for sorting)
|
|
37
|
+
const sortedEntries = [...entries].sort((a, b) => {
|
|
38
|
+
const aName = a.mode === '040000' ? a.name + '/' : a.name;
|
|
39
|
+
const bName = b.mode === '040000' ? b.name + '/' : b.name;
|
|
40
|
+
return aName.localeCompare(bName);
|
|
41
|
+
});
|
|
42
|
+
// Build tree content (without header)
|
|
43
|
+
const entryParts = [];
|
|
44
|
+
for (const entry of sortedEntries) {
|
|
45
|
+
const modeName = encoder.encode(`${entry.mode} ${entry.name}\0`);
|
|
46
|
+
const sha20 = hexToBytes(entry.sha);
|
|
47
|
+
const entryData = new Uint8Array(modeName.length + 20);
|
|
48
|
+
entryData.set(modeName);
|
|
49
|
+
entryData.set(sha20, modeName.length);
|
|
50
|
+
entryParts.push(entryData);
|
|
51
|
+
}
|
|
52
|
+
// Combine all entry parts
|
|
53
|
+
const contentLength = entryParts.reduce((sum, part) => sum + part.length, 0);
|
|
54
|
+
const content = new Uint8Array(contentLength);
|
|
55
|
+
let offset = 0;
|
|
56
|
+
for (const part of entryParts) {
|
|
57
|
+
content.set(part, offset);
|
|
58
|
+
offset += part.length;
|
|
59
|
+
}
|
|
60
|
+
return this.putObject('tree', content);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Store a commit object
|
|
64
|
+
*/
|
|
65
|
+
async putCommitObject(commit) {
|
|
66
|
+
// Build commit content (without header)
|
|
67
|
+
const lines = [];
|
|
68
|
+
lines.push(`tree ${commit.tree}`);
|
|
69
|
+
for (const parent of commit.parents) {
|
|
70
|
+
lines.push(`parent ${parent}`);
|
|
71
|
+
}
|
|
72
|
+
lines.push(`author ${commit.author.name} <${commit.author.email}> ${commit.author.timestamp} ${commit.author.timezone}`);
|
|
73
|
+
lines.push(`committer ${commit.committer.name} <${commit.committer.email}> ${commit.committer.timestamp} ${commit.committer.timezone}`);
|
|
74
|
+
lines.push('');
|
|
75
|
+
lines.push(commit.message);
|
|
76
|
+
const content = encoder.encode(lines.join('\n'));
|
|
77
|
+
return this.putObject('commit', content);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Store a tag object
|
|
81
|
+
*/
|
|
82
|
+
async putTagObject(tag) {
|
|
83
|
+
// Build tag content (without header)
|
|
84
|
+
const lines = [];
|
|
85
|
+
lines.push(`object ${tag.object}`);
|
|
86
|
+
lines.push(`type ${tag.objectType}`);
|
|
87
|
+
lines.push(`tag ${tag.name}`);
|
|
88
|
+
lines.push(`tagger ${tag.tagger.name} <${tag.tagger.email}> ${tag.tagger.timestamp} ${tag.tagger.timezone}`);
|
|
89
|
+
lines.push('');
|
|
90
|
+
lines.push(tag.message);
|
|
91
|
+
const content = encoder.encode(lines.join('\n'));
|
|
92
|
+
return this.putObject('tag', content);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Retrieve an object by SHA
|
|
96
|
+
*/
|
|
97
|
+
async getObject(sha) {
|
|
98
|
+
if (!sha || sha.length < 4) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const result = this.storage.sql.exec('SELECT sha, type, size, data, created_at as createdAt FROM objects WHERE sha = ?', sha);
|
|
102
|
+
const rows = result.toArray();
|
|
103
|
+
if (rows.length === 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return rows[0];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Delete an object by SHA
|
|
110
|
+
*/
|
|
111
|
+
async deleteObject(sha) {
|
|
112
|
+
// Check if object exists first
|
|
113
|
+
const exists = await this.hasObject(sha);
|
|
114
|
+
if (!exists) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
// Log to WAL
|
|
118
|
+
await this.logToWAL('DELETE', sha, 'blob', new Uint8Array(0));
|
|
119
|
+
// Delete from objects table
|
|
120
|
+
this.storage.sql.exec('DELETE FROM objects WHERE sha = ?', sha);
|
|
121
|
+
// Delete from object index
|
|
122
|
+
this.storage.sql.exec('DELETE FROM object_index WHERE sha = ?', sha);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if an object exists
|
|
127
|
+
*/
|
|
128
|
+
async hasObject(sha) {
|
|
129
|
+
if (!sha || sha.length < 4) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
// Use getObject and check for null - this works better with the mock
|
|
133
|
+
const obj = await this.getObject(sha);
|
|
134
|
+
return obj !== null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Verify an object's integrity by recomputing its hash
|
|
138
|
+
*/
|
|
139
|
+
async verifyObject(sha) {
|
|
140
|
+
const obj = await this.getObject(sha);
|
|
141
|
+
if (!obj) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const computedSha = await hashObject(obj.type, obj.data);
|
|
145
|
+
return computedSha === sha;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get object type by SHA
|
|
149
|
+
*/
|
|
150
|
+
async getObjectType(sha) {
|
|
151
|
+
const obj = await this.getObject(sha);
|
|
152
|
+
return obj?.type ?? null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get object size by SHA
|
|
156
|
+
*/
|
|
157
|
+
async getObjectSize(sha) {
|
|
158
|
+
const obj = await this.getObject(sha);
|
|
159
|
+
return obj?.size ?? null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Store multiple objects in a batch
|
|
163
|
+
*/
|
|
164
|
+
async putObjects(objects) {
|
|
165
|
+
const shas = [];
|
|
166
|
+
for (const obj of objects) {
|
|
167
|
+
const sha = await this.putObject(obj.type, obj.data);
|
|
168
|
+
shas.push(sha);
|
|
169
|
+
}
|
|
170
|
+
return shas;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Retrieve multiple objects by SHA
|
|
174
|
+
*/
|
|
175
|
+
async getObjects(shas) {
|
|
176
|
+
const results = [];
|
|
177
|
+
for (const sha of shas) {
|
|
178
|
+
const obj = await this.getObject(sha);
|
|
179
|
+
results.push(obj);
|
|
180
|
+
}
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get a blob object with parsed content
|
|
185
|
+
*/
|
|
186
|
+
async getBlobObject(sha) {
|
|
187
|
+
const obj = await this.getObject(sha);
|
|
188
|
+
if (!obj || obj.type !== 'blob') {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
type: 'blob',
|
|
193
|
+
data: obj.data
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get a tree object with parsed entries
|
|
198
|
+
*/
|
|
199
|
+
async getTreeObject(sha) {
|
|
200
|
+
const obj = await this.getObject(sha);
|
|
201
|
+
if (!obj || obj.type !== 'tree') {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
// Parse tree entries from raw data
|
|
205
|
+
const entries = [];
|
|
206
|
+
let offset = 0;
|
|
207
|
+
const data = obj.data;
|
|
208
|
+
while (offset < data.length) {
|
|
209
|
+
// Find the null byte after mode+name
|
|
210
|
+
let nullIndex = offset;
|
|
211
|
+
while (nullIndex < data.length && data[nullIndex] !== 0) {
|
|
212
|
+
nullIndex++;
|
|
213
|
+
}
|
|
214
|
+
const modeNameStr = decoder.decode(data.slice(offset, nullIndex));
|
|
215
|
+
const spaceIndex = modeNameStr.indexOf(' ');
|
|
216
|
+
const mode = modeNameStr.slice(0, spaceIndex);
|
|
217
|
+
const name = modeNameStr.slice(spaceIndex + 1);
|
|
218
|
+
// Read 20-byte SHA
|
|
219
|
+
const sha20 = data.slice(nullIndex + 1, nullIndex + 21);
|
|
220
|
+
const entrySha = bytesToHex(sha20);
|
|
221
|
+
entries.push({ mode, name, sha: entrySha });
|
|
222
|
+
offset = nullIndex + 21;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
type: 'tree',
|
|
226
|
+
data: obj.data,
|
|
227
|
+
entries
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get a commit object with parsed fields
|
|
232
|
+
*/
|
|
233
|
+
async getCommitObject(sha) {
|
|
234
|
+
const obj = await this.getObject(sha);
|
|
235
|
+
if (!obj || obj.type !== 'commit') {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
const content = decoder.decode(obj.data);
|
|
239
|
+
const lines = content.split('\n');
|
|
240
|
+
let tree = '';
|
|
241
|
+
const parents = [];
|
|
242
|
+
let author = null;
|
|
243
|
+
let committer = null;
|
|
244
|
+
let messageStartIndex = 0;
|
|
245
|
+
for (let i = 0; i < lines.length; i++) {
|
|
246
|
+
const line = lines[i];
|
|
247
|
+
if (line === '') {
|
|
248
|
+
messageStartIndex = i + 1;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (line.startsWith('tree ')) {
|
|
252
|
+
tree = line.slice(5);
|
|
253
|
+
}
|
|
254
|
+
else if (line.startsWith('parent ')) {
|
|
255
|
+
parents.push(line.slice(7));
|
|
256
|
+
}
|
|
257
|
+
else if (line.startsWith('author ')) {
|
|
258
|
+
author = parseAuthorLine(line);
|
|
259
|
+
}
|
|
260
|
+
else if (line.startsWith('committer ')) {
|
|
261
|
+
committer = parseAuthorLine(line);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!author || !committer) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const message = lines.slice(messageStartIndex).join('\n');
|
|
268
|
+
return {
|
|
269
|
+
type: 'commit',
|
|
270
|
+
data: obj.data,
|
|
271
|
+
tree,
|
|
272
|
+
parents,
|
|
273
|
+
author,
|
|
274
|
+
committer,
|
|
275
|
+
message
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get a tag object with parsed fields
|
|
280
|
+
*/
|
|
281
|
+
async getTagObject(sha) {
|
|
282
|
+
const obj = await this.getObject(sha);
|
|
283
|
+
if (!obj || obj.type !== 'tag') {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const content = decoder.decode(obj.data);
|
|
287
|
+
const lines = content.split('\n');
|
|
288
|
+
let object = '';
|
|
289
|
+
let objectType = 'commit';
|
|
290
|
+
let name = '';
|
|
291
|
+
let tagger = null;
|
|
292
|
+
let messageStartIndex = 0;
|
|
293
|
+
for (let i = 0; i < lines.length; i++) {
|
|
294
|
+
const line = lines[i];
|
|
295
|
+
if (line === '') {
|
|
296
|
+
messageStartIndex = i + 1;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
if (line.startsWith('object ')) {
|
|
300
|
+
object = line.slice(7);
|
|
301
|
+
}
|
|
302
|
+
else if (line.startsWith('type ')) {
|
|
303
|
+
objectType = line.slice(5);
|
|
304
|
+
}
|
|
305
|
+
else if (line.startsWith('tag ')) {
|
|
306
|
+
name = line.slice(4);
|
|
307
|
+
}
|
|
308
|
+
else if (line.startsWith('tagger ')) {
|
|
309
|
+
tagger = parseAuthorLine(line);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (!tagger) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
const message = lines.slice(messageStartIndex).join('\n');
|
|
316
|
+
return {
|
|
317
|
+
type: 'tag',
|
|
318
|
+
data: obj.data,
|
|
319
|
+
object,
|
|
320
|
+
objectType,
|
|
321
|
+
name,
|
|
322
|
+
tagger,
|
|
323
|
+
message
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get raw serialized object with git header
|
|
328
|
+
*/
|
|
329
|
+
async getRawObject(sha) {
|
|
330
|
+
const obj = await this.getObject(sha);
|
|
331
|
+
if (!obj) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
// Build git object format: "type size\0content"
|
|
335
|
+
const header = encoder.encode(`${obj.type} ${obj.data.length}\0`);
|
|
336
|
+
const result = new Uint8Array(header.length + obj.data.length);
|
|
337
|
+
result.set(header);
|
|
338
|
+
result.set(obj.data, header.length);
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Log operation to WAL
|
|
343
|
+
*/
|
|
344
|
+
async logToWAL(operation, sha, type, _data) {
|
|
345
|
+
// Create payload with operation details
|
|
346
|
+
const payload = encoder.encode(JSON.stringify({
|
|
347
|
+
sha,
|
|
348
|
+
type,
|
|
349
|
+
timestamp: Date.now()
|
|
350
|
+
}));
|
|
351
|
+
this.storage.sql.exec('INSERT INTO wal (operation, payload, created_at, flushed) VALUES (?, ?, ?, 0)', operation, payload, Date.now());
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Helper: Convert hex string to bytes
|
|
356
|
+
*/
|
|
357
|
+
function hexToBytes(hex) {
|
|
358
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
359
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
360
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
361
|
+
}
|
|
362
|
+
return bytes;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Helper: Convert bytes to hex string
|
|
366
|
+
*/
|
|
367
|
+
function bytesToHex(bytes) {
|
|
368
|
+
return Array.from(bytes)
|
|
369
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
370
|
+
.join('');
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Helper: Parse author/committer/tagger line
|
|
374
|
+
*/
|
|
375
|
+
function parseAuthorLine(line) {
|
|
376
|
+
const match = line.match(/^(?:author|committer|tagger) (.+) <(.+)> (\d+) ([+-]\d{4})$/);
|
|
377
|
+
if (!match) {
|
|
378
|
+
throw new Error(`Invalid author line: ${line}`);
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
name: match[1],
|
|
382
|
+
email: match[2],
|
|
383
|
+
timestamp: parseInt(match[3], 10),
|
|
384
|
+
timezone: match[4]
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=object-store.js.map
|