gitx.do 0.1.0 → 0.1.2

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.
Files changed (344) hide show
  1. package/README.md +40 -353
  2. package/dist/do/logger.d.ts +50 -0
  3. package/dist/do/logger.d.ts.map +1 -0
  4. package/dist/do/logger.js +122 -0
  5. package/dist/do/logger.js.map +1 -0
  6. package/dist/{durable-object → do}/schema.d.ts +3 -3
  7. package/dist/do/schema.d.ts.map +1 -0
  8. package/dist/{durable-object → do}/schema.js +4 -3
  9. package/dist/do/schema.js.map +1 -0
  10. package/dist/do/types.d.ts +267 -0
  11. package/dist/do/types.d.ts.map +1 -0
  12. package/dist/do/types.js +62 -0
  13. package/dist/do/types.js.map +1 -0
  14. package/dist/index.d.ts +15 -415
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +31 -483
  17. package/dist/index.js.map +1 -1
  18. package/package.json +13 -21
  19. package/dist/cli/commands/add.d.ts +0 -174
  20. package/dist/cli/commands/add.d.ts.map +0 -1
  21. package/dist/cli/commands/add.js +0 -131
  22. package/dist/cli/commands/add.js.map +0 -1
  23. package/dist/cli/commands/blame.d.ts +0 -259
  24. package/dist/cli/commands/blame.d.ts.map +0 -1
  25. package/dist/cli/commands/blame.js +0 -609
  26. package/dist/cli/commands/blame.js.map +0 -1
  27. package/dist/cli/commands/branch.d.ts +0 -249
  28. package/dist/cli/commands/branch.d.ts.map +0 -1
  29. package/dist/cli/commands/branch.js +0 -693
  30. package/dist/cli/commands/branch.js.map +0 -1
  31. package/dist/cli/commands/commit.d.ts +0 -182
  32. package/dist/cli/commands/commit.d.ts.map +0 -1
  33. package/dist/cli/commands/commit.js +0 -437
  34. package/dist/cli/commands/commit.js.map +0 -1
  35. package/dist/cli/commands/diff.d.ts +0 -464
  36. package/dist/cli/commands/diff.d.ts.map +0 -1
  37. package/dist/cli/commands/diff.js +0 -958
  38. package/dist/cli/commands/diff.js.map +0 -1
  39. package/dist/cli/commands/log.d.ts +0 -239
  40. package/dist/cli/commands/log.d.ts.map +0 -1
  41. package/dist/cli/commands/log.js +0 -535
  42. package/dist/cli/commands/log.js.map +0 -1
  43. package/dist/cli/commands/merge.d.ts +0 -106
  44. package/dist/cli/commands/merge.d.ts.map +0 -1
  45. package/dist/cli/commands/merge.js +0 -55
  46. package/dist/cli/commands/merge.js.map +0 -1
  47. package/dist/cli/commands/review.d.ts +0 -457
  48. package/dist/cli/commands/review.d.ts.map +0 -1
  49. package/dist/cli/commands/review.js +0 -533
  50. package/dist/cli/commands/review.js.map +0 -1
  51. package/dist/cli/commands/status.d.ts +0 -269
  52. package/dist/cli/commands/status.d.ts.map +0 -1
  53. package/dist/cli/commands/status.js +0 -493
  54. package/dist/cli/commands/status.js.map +0 -1
  55. package/dist/cli/commands/web.d.ts +0 -199
  56. package/dist/cli/commands/web.d.ts.map +0 -1
  57. package/dist/cli/commands/web.js +0 -696
  58. package/dist/cli/commands/web.js.map +0 -1
  59. package/dist/cli/fs-adapter.d.ts +0 -656
  60. package/dist/cli/fs-adapter.d.ts.map +0 -1
  61. package/dist/cli/fs-adapter.js +0 -1179
  62. package/dist/cli/fs-adapter.js.map +0 -1
  63. package/dist/cli/fsx-cli-adapter.d.ts +0 -359
  64. package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
  65. package/dist/cli/fsx-cli-adapter.js +0 -619
  66. package/dist/cli/fsx-cli-adapter.js.map +0 -1
  67. package/dist/cli/index.d.ts +0 -387
  68. package/dist/cli/index.d.ts.map +0 -1
  69. package/dist/cli/index.js +0 -523
  70. package/dist/cli/index.js.map +0 -1
  71. package/dist/cli/ui/components/DiffView.d.ts +0 -7
  72. package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
  73. package/dist/cli/ui/components/DiffView.js +0 -11
  74. package/dist/cli/ui/components/DiffView.js.map +0 -1
  75. package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
  76. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
  77. package/dist/cli/ui/components/ErrorDisplay.js +0 -11
  78. package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
  79. package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
  80. package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
  81. package/dist/cli/ui/components/FuzzySearch.js +0 -12
  82. package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
  83. package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
  84. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
  85. package/dist/cli/ui/components/LoadingSpinner.js +0 -10
  86. package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
  87. package/dist/cli/ui/components/NavigationList.d.ts +0 -9
  88. package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
  89. package/dist/cli/ui/components/NavigationList.js +0 -11
  90. package/dist/cli/ui/components/NavigationList.js.map +0 -1
  91. package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
  92. package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
  93. package/dist/cli/ui/components/ScrollableContent.js +0 -11
  94. package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
  95. package/dist/cli/ui/components/index.d.ts +0 -7
  96. package/dist/cli/ui/components/index.d.ts.map +0 -1
  97. package/dist/cli/ui/components/index.js +0 -9
  98. package/dist/cli/ui/components/index.js.map +0 -1
  99. package/dist/cli/ui/terminal-ui.d.ts +0 -52
  100. package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
  101. package/dist/cli/ui/terminal-ui.js +0 -121
  102. package/dist/cli/ui/terminal-ui.js.map +0 -1
  103. package/dist/do/BashModule.d.ts +0 -871
  104. package/dist/do/BashModule.d.ts.map +0 -1
  105. package/dist/do/BashModule.js +0 -1143
  106. package/dist/do/BashModule.js.map +0 -1
  107. package/dist/do/FsModule.d.ts +0 -601
  108. package/dist/do/FsModule.d.ts.map +0 -1
  109. package/dist/do/FsModule.js +0 -1120
  110. package/dist/do/FsModule.js.map +0 -1
  111. package/dist/do/GitModule.d.ts +0 -635
  112. package/dist/do/GitModule.d.ts.map +0 -1
  113. package/dist/do/GitModule.js +0 -781
  114. package/dist/do/GitModule.js.map +0 -1
  115. package/dist/do/GitRepoDO.d.ts +0 -281
  116. package/dist/do/GitRepoDO.d.ts.map +0 -1
  117. package/dist/do/GitRepoDO.js +0 -479
  118. package/dist/do/GitRepoDO.js.map +0 -1
  119. package/dist/do/bash-ast.d.ts +0 -246
  120. package/dist/do/bash-ast.d.ts.map +0 -1
  121. package/dist/do/bash-ast.js +0 -888
  122. package/dist/do/bash-ast.js.map +0 -1
  123. package/dist/do/container-executor.d.ts +0 -491
  124. package/dist/do/container-executor.d.ts.map +0 -1
  125. package/dist/do/container-executor.js +0 -730
  126. package/dist/do/container-executor.js.map +0 -1
  127. package/dist/do/index.d.ts +0 -53
  128. package/dist/do/index.d.ts.map +0 -1
  129. package/dist/do/index.js +0 -91
  130. package/dist/do/index.js.map +0 -1
  131. package/dist/do/tiered-storage.d.ts +0 -403
  132. package/dist/do/tiered-storage.d.ts.map +0 -1
  133. package/dist/do/tiered-storage.js +0 -689
  134. package/dist/do/tiered-storage.js.map +0 -1
  135. package/dist/do/withBash.d.ts +0 -231
  136. package/dist/do/withBash.d.ts.map +0 -1
  137. package/dist/do/withBash.js +0 -244
  138. package/dist/do/withBash.js.map +0 -1
  139. package/dist/do/withFs.d.ts +0 -237
  140. package/dist/do/withFs.d.ts.map +0 -1
  141. package/dist/do/withFs.js +0 -387
  142. package/dist/do/withFs.js.map +0 -1
  143. package/dist/do/withGit.d.ts +0 -180
  144. package/dist/do/withGit.d.ts.map +0 -1
  145. package/dist/do/withGit.js +0 -271
  146. package/dist/do/withGit.js.map +0 -1
  147. package/dist/durable-object/object-store.d.ts +0 -633
  148. package/dist/durable-object/object-store.d.ts.map +0 -1
  149. package/dist/durable-object/object-store.js +0 -1161
  150. package/dist/durable-object/object-store.js.map +0 -1
  151. package/dist/durable-object/schema.d.ts.map +0 -1
  152. package/dist/durable-object/schema.js.map +0 -1
  153. package/dist/durable-object/wal.d.ts +0 -416
  154. package/dist/durable-object/wal.d.ts.map +0 -1
  155. package/dist/durable-object/wal.js +0 -445
  156. package/dist/durable-object/wal.js.map +0 -1
  157. package/dist/mcp/adapter.d.ts +0 -772
  158. package/dist/mcp/adapter.d.ts.map +0 -1
  159. package/dist/mcp/adapter.js +0 -895
  160. package/dist/mcp/adapter.js.map +0 -1
  161. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
  162. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
  163. package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
  164. package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
  165. package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
  166. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
  167. package/dist/mcp/sandbox/object-store-proxy.js +0 -30
  168. package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
  169. package/dist/mcp/sandbox/template.d.ts +0 -17
  170. package/dist/mcp/sandbox/template.d.ts.map +0 -1
  171. package/dist/mcp/sandbox/template.js +0 -71
  172. package/dist/mcp/sandbox/template.js.map +0 -1
  173. package/dist/mcp/sandbox.d.ts +0 -764
  174. package/dist/mcp/sandbox.d.ts.map +0 -1
  175. package/dist/mcp/sandbox.js +0 -1362
  176. package/dist/mcp/sandbox.js.map +0 -1
  177. package/dist/mcp/sdk-adapter.d.ts +0 -835
  178. package/dist/mcp/sdk-adapter.d.ts.map +0 -1
  179. package/dist/mcp/sdk-adapter.js +0 -974
  180. package/dist/mcp/sdk-adapter.js.map +0 -1
  181. package/dist/mcp/tools/do.d.ts +0 -32
  182. package/dist/mcp/tools/do.d.ts.map +0 -1
  183. package/dist/mcp/tools/do.js +0 -115
  184. package/dist/mcp/tools/do.js.map +0 -1
  185. package/dist/mcp/tools.d.ts +0 -548
  186. package/dist/mcp/tools.d.ts.map +0 -1
  187. package/dist/mcp/tools.js +0 -1934
  188. package/dist/mcp/tools.js.map +0 -1
  189. package/dist/ops/blame.d.ts +0 -551
  190. package/dist/ops/blame.d.ts.map +0 -1
  191. package/dist/ops/blame.js +0 -1037
  192. package/dist/ops/blame.js.map +0 -1
  193. package/dist/ops/branch.d.ts +0 -766
  194. package/dist/ops/branch.d.ts.map +0 -1
  195. package/dist/ops/branch.js +0 -950
  196. package/dist/ops/branch.js.map +0 -1
  197. package/dist/ops/commit-traversal.d.ts +0 -349
  198. package/dist/ops/commit-traversal.d.ts.map +0 -1
  199. package/dist/ops/commit-traversal.js +0 -821
  200. package/dist/ops/commit-traversal.js.map +0 -1
  201. package/dist/ops/commit.d.ts +0 -555
  202. package/dist/ops/commit.d.ts.map +0 -1
  203. package/dist/ops/commit.js +0 -826
  204. package/dist/ops/commit.js.map +0 -1
  205. package/dist/ops/merge-base.d.ts +0 -397
  206. package/dist/ops/merge-base.d.ts.map +0 -1
  207. package/dist/ops/merge-base.js +0 -691
  208. package/dist/ops/merge-base.js.map +0 -1
  209. package/dist/ops/merge.d.ts +0 -855
  210. package/dist/ops/merge.d.ts.map +0 -1
  211. package/dist/ops/merge.js +0 -1551
  212. package/dist/ops/merge.js.map +0 -1
  213. package/dist/ops/tag.d.ts +0 -247
  214. package/dist/ops/tag.d.ts.map +0 -1
  215. package/dist/ops/tag.js +0 -649
  216. package/dist/ops/tag.js.map +0 -1
  217. package/dist/ops/tree-builder.d.ts +0 -178
  218. package/dist/ops/tree-builder.d.ts.map +0 -1
  219. package/dist/ops/tree-builder.js +0 -271
  220. package/dist/ops/tree-builder.js.map +0 -1
  221. package/dist/ops/tree-diff.d.ts +0 -291
  222. package/dist/ops/tree-diff.d.ts.map +0 -1
  223. package/dist/ops/tree-diff.js +0 -705
  224. package/dist/ops/tree-diff.js.map +0 -1
  225. package/dist/pack/delta.d.ts +0 -248
  226. package/dist/pack/delta.d.ts.map +0 -1
  227. package/dist/pack/delta.js +0 -736
  228. package/dist/pack/delta.js.map +0 -1
  229. package/dist/pack/format.d.ts +0 -446
  230. package/dist/pack/format.d.ts.map +0 -1
  231. package/dist/pack/format.js +0 -572
  232. package/dist/pack/format.js.map +0 -1
  233. package/dist/pack/full-generation.d.ts +0 -612
  234. package/dist/pack/full-generation.d.ts.map +0 -1
  235. package/dist/pack/full-generation.js +0 -1378
  236. package/dist/pack/full-generation.js.map +0 -1
  237. package/dist/pack/generation.d.ts +0 -441
  238. package/dist/pack/generation.d.ts.map +0 -1
  239. package/dist/pack/generation.js +0 -707
  240. package/dist/pack/generation.js.map +0 -1
  241. package/dist/pack/index.d.ts +0 -502
  242. package/dist/pack/index.d.ts.map +0 -1
  243. package/dist/pack/index.js +0 -833
  244. package/dist/pack/index.js.map +0 -1
  245. package/dist/refs/branch.d.ts +0 -668
  246. package/dist/refs/branch.d.ts.map +0 -1
  247. package/dist/refs/branch.js +0 -897
  248. package/dist/refs/branch.js.map +0 -1
  249. package/dist/refs/storage.d.ts +0 -833
  250. package/dist/refs/storage.d.ts.map +0 -1
  251. package/dist/refs/storage.js +0 -1023
  252. package/dist/refs/storage.js.map +0 -1
  253. package/dist/refs/tag.d.ts +0 -860
  254. package/dist/refs/tag.d.ts.map +0 -1
  255. package/dist/refs/tag.js +0 -996
  256. package/dist/refs/tag.js.map +0 -1
  257. package/dist/storage/backend.d.ts +0 -425
  258. package/dist/storage/backend.d.ts.map +0 -1
  259. package/dist/storage/backend.js +0 -41
  260. package/dist/storage/backend.js.map +0 -1
  261. package/dist/storage/fsx-adapter.d.ts +0 -204
  262. package/dist/storage/fsx-adapter.d.ts.map +0 -1
  263. package/dist/storage/fsx-adapter.js +0 -470
  264. package/dist/storage/fsx-adapter.js.map +0 -1
  265. package/dist/storage/lru-cache.d.ts +0 -691
  266. package/dist/storage/lru-cache.d.ts.map +0 -1
  267. package/dist/storage/lru-cache.js +0 -813
  268. package/dist/storage/lru-cache.js.map +0 -1
  269. package/dist/storage/object-index.d.ts +0 -585
  270. package/dist/storage/object-index.d.ts.map +0 -1
  271. package/dist/storage/object-index.js +0 -532
  272. package/dist/storage/object-index.js.map +0 -1
  273. package/dist/storage/r2-pack.d.ts +0 -1257
  274. package/dist/storage/r2-pack.d.ts.map +0 -1
  275. package/dist/storage/r2-pack.js +0 -1770
  276. package/dist/storage/r2-pack.js.map +0 -1
  277. package/dist/tiered/cdc-pipeline.d.ts +0 -1888
  278. package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
  279. package/dist/tiered/cdc-pipeline.js +0 -1880
  280. package/dist/tiered/cdc-pipeline.js.map +0 -1
  281. package/dist/tiered/migration.d.ts +0 -1104
  282. package/dist/tiered/migration.d.ts.map +0 -1
  283. package/dist/tiered/migration.js +0 -1214
  284. package/dist/tiered/migration.js.map +0 -1
  285. package/dist/tiered/parquet-writer.d.ts +0 -1145
  286. package/dist/tiered/parquet-writer.d.ts.map +0 -1
  287. package/dist/tiered/parquet-writer.js +0 -1183
  288. package/dist/tiered/parquet-writer.js.map +0 -1
  289. package/dist/tiered/read-path.d.ts +0 -835
  290. package/dist/tiered/read-path.d.ts.map +0 -1
  291. package/dist/tiered/read-path.js +0 -487
  292. package/dist/tiered/read-path.js.map +0 -1
  293. package/dist/types/capability.d.ts +0 -1385
  294. package/dist/types/capability.d.ts.map +0 -1
  295. package/dist/types/capability.js +0 -36
  296. package/dist/types/capability.js.map +0 -1
  297. package/dist/types/index.d.ts +0 -13
  298. package/dist/types/index.d.ts.map +0 -1
  299. package/dist/types/index.js +0 -18
  300. package/dist/types/index.js.map +0 -1
  301. package/dist/types/objects.d.ts +0 -692
  302. package/dist/types/objects.d.ts.map +0 -1
  303. package/dist/types/objects.js +0 -837
  304. package/dist/types/objects.js.map +0 -1
  305. package/dist/types/storage.d.ts +0 -603
  306. package/dist/types/storage.d.ts.map +0 -1
  307. package/dist/types/storage.js +0 -191
  308. package/dist/types/storage.js.map +0 -1
  309. package/dist/types/worker-loader.d.ts +0 -60
  310. package/dist/types/worker-loader.d.ts.map +0 -1
  311. package/dist/types/worker-loader.js +0 -62
  312. package/dist/types/worker-loader.js.map +0 -1
  313. package/dist/utils/hash.d.ts +0 -197
  314. package/dist/utils/hash.d.ts.map +0 -1
  315. package/dist/utils/hash.js +0 -268
  316. package/dist/utils/hash.js.map +0 -1
  317. package/dist/utils/sha1.d.ts +0 -290
  318. package/dist/utils/sha1.d.ts.map +0 -1
  319. package/dist/utils/sha1.js +0 -582
  320. package/dist/utils/sha1.js.map +0 -1
  321. package/dist/wire/capabilities.d.ts +0 -1044
  322. package/dist/wire/capabilities.d.ts.map +0 -1
  323. package/dist/wire/capabilities.js +0 -941
  324. package/dist/wire/capabilities.js.map +0 -1
  325. package/dist/wire/path-security.d.ts +0 -157
  326. package/dist/wire/path-security.d.ts.map +0 -1
  327. package/dist/wire/path-security.js +0 -307
  328. package/dist/wire/path-security.js.map +0 -1
  329. package/dist/wire/pkt-line.d.ts +0 -345
  330. package/dist/wire/pkt-line.d.ts.map +0 -1
  331. package/dist/wire/pkt-line.js +0 -381
  332. package/dist/wire/pkt-line.js.map +0 -1
  333. package/dist/wire/receive-pack.d.ts +0 -1059
  334. package/dist/wire/receive-pack.d.ts.map +0 -1
  335. package/dist/wire/receive-pack.js +0 -1414
  336. package/dist/wire/receive-pack.js.map +0 -1
  337. package/dist/wire/smart-http.d.ts +0 -799
  338. package/dist/wire/smart-http.d.ts.map +0 -1
  339. package/dist/wire/smart-http.js +0 -945
  340. package/dist/wire/smart-http.js.map +0 -1
  341. package/dist/wire/upload-pack.d.ts +0 -727
  342. package/dist/wire/upload-pack.d.ts.map +0 -1
  343. package/dist/wire/upload-pack.js +0 -1138
  344. package/dist/wire/upload-pack.js.map +0 -1
@@ -1,1161 +0,0 @@
1
- /**
2
- * @fileoverview Git Object Store for Durable Objects
3
- *
4
- * This module provides a Git object storage implementation backed by SQLite
5
- * within Cloudflare Durable Objects. It handles CRUD operations for all four
6
- * Git object types (blob, tree, commit, tag) with proper SHA-1 hash computation.
7
- *
8
- * **Key Features**:
9
- * - Content-addressable storage using SHA-1 hashes
10
- * - Write-ahead logging (WAL) for durability
11
- * - Object index for tiered storage support
12
- * - Batch operations for efficiency with transaction support
13
- * - LRU caching for hot tier objects
14
- * - Metrics and logging infrastructure
15
- * - Typed accessors for each Git object type
16
- *
17
- * @module durable-object/object-store
18
- *
19
- * @example
20
- * ```typescript
21
- * import { ObjectStore } from './durable-object/object-store'
22
- *
23
- * const store = new ObjectStore(durableObjectStorage, {
24
- * cacheMaxCount: 1000,
25
- * cacheMaxBytes: 50 * 1024 * 1024, // 50MB
26
- * enableMetrics: true
27
- * })
28
- *
29
- * // Store a blob
30
- * const content = new TextEncoder().encode('Hello, World!')
31
- * const sha = await store.putObject('blob', content)
32
- *
33
- * // Retrieve it (cached on second access)
34
- * const obj = await store.getObject(sha)
35
- * console.log(obj?.type, obj?.size)
36
- *
37
- * // Get typed object
38
- * const blob = await store.getBlobObject(sha)
39
- *
40
- * // Get metrics
41
- * const metrics = store.getMetrics()
42
- * console.log(`Cache hit rate: ${metrics.cacheHitRate}%`)
43
- * ```
44
- */
45
- import { LRUCache } from '../storage/lru-cache';
46
- import { isValidMode, isValidSha } from '../types/objects';
47
- import { hashObject } from '../utils/hash';
48
- const encoder = new TextEncoder();
49
- const decoder = new TextDecoder();
50
- // Default cache configuration
51
- const DEFAULT_CACHE_MAX_COUNT = 500;
52
- const DEFAULT_CACHE_MAX_BYTES = 25 * 1024 * 1024; // 25MB
53
- // ============================================================================
54
- // ObjectStore Class
55
- // ============================================================================
56
- /**
57
- * ObjectStore class for managing Git objects in SQLite storage.
58
- *
59
- * @description
60
- * Provides a complete implementation of Git object storage operations.
61
- * All objects are stored in the `objects` table and indexed in `object_index`
62
- * for tiered storage support. Write operations are logged to WAL for durability.
63
- *
64
- * @example
65
- * ```typescript
66
- * const store = new ObjectStore(durableObjectStorage)
67
- *
68
- * // Create a commit
69
- * const commitSha = await store.putCommitObject({
70
- * tree: treeSha,
71
- * parents: [parentSha],
72
- * author: { name: 'Alice', email: 'alice@example.com', timestamp: 1704067200, timezone: '+0000' },
73
- * committer: { name: 'Alice', email: 'alice@example.com', timestamp: 1704067200, timezone: '+0000' },
74
- * message: 'Initial commit'
75
- * })
76
- *
77
- * // Read it back
78
- * const commit = await store.getCommitObject(commitSha)
79
- * console.log(commit?.message)
80
- * ```
81
- */
82
- export class ObjectStore {
83
- storage;
84
- cache;
85
- options;
86
- logger;
87
- backend;
88
- // Metrics tracking
89
- _reads = 0;
90
- _writes = 0;
91
- _deletes = 0;
92
- _bytesWritten = 0;
93
- _bytesRead = 0;
94
- _totalWriteLatency = 0;
95
- _totalReadLatency = 0;
96
- _batchOperations = 0;
97
- _batchObjectsTotal = 0;
98
- /**
99
- * Create a new ObjectStore.
100
- *
101
- * @param storage - Durable Object storage interface with SQL support
102
- * @param options - Configuration options for caching, metrics, logging, and backend
103
- *
104
- * @example
105
- * ```typescript
106
- * // Basic usage (SQLite backend)
107
- * const store = new ObjectStore(storage)
108
- *
109
- * // With caching and metrics
110
- * const store = new ObjectStore(storage, {
111
- * cacheMaxCount: 1000,
112
- * cacheMaxBytes: 50 * 1024 * 1024,
113
- * enableMetrics: true,
114
- * logger: console
115
- * })
116
- *
117
- * // With StorageBackend abstraction
118
- * const store = new ObjectStore(storage, {
119
- * backend: fsBackend
120
- * })
121
- * ```
122
- */
123
- constructor(storage, options) {
124
- this.storage = storage;
125
- this.options = options ?? {};
126
- this.logger = options?.logger;
127
- this.backend = options?.backend ?? null;
128
- // Initialize LRU cache for hot tier objects
129
- this.cache = new LRUCache({
130
- maxCount: options?.cacheMaxCount ?? DEFAULT_CACHE_MAX_COUNT,
131
- maxBytes: options?.cacheMaxBytes ?? DEFAULT_CACHE_MAX_BYTES,
132
- defaultTTL: options?.cacheTTL,
133
- sizeCalculator: (obj) => obj.data.byteLength + 100, // 100 bytes overhead for metadata
134
- onEvict: (key, _value, reason) => {
135
- this.log('debug', `Cache eviction: ${key} (reason: ${reason})`);
136
- }
137
- });
138
- }
139
- /**
140
- * Log a message if logger is configured.
141
- * @internal
142
- */
143
- log(level, message, ...args) {
144
- if (!this.logger)
145
- return;
146
- const logFn = this.logger[level];
147
- if (logFn) {
148
- logFn.call(this.logger, `[ObjectStore] ${message}`, ...args);
149
- }
150
- }
151
- /**
152
- * Store a raw object and return its SHA.
153
- *
154
- * @description
155
- * Computes the SHA-1 hash of the object in Git format (type + size + content),
156
- * logs the operation to WAL, stores the object, and updates the object index.
157
- * If an object with the same SHA already exists, it is replaced (idempotent).
158
- * The object is also added to the LRU cache for fast subsequent reads.
159
- *
160
- * @param type - Object type ('blob', 'tree', 'commit', 'tag')
161
- * @param data - Raw object content (without Git header)
162
- * @returns 40-character SHA-1 hash of the stored object
163
- *
164
- * @example
165
- * ```typescript
166
- * const content = new TextEncoder().encode('file content')
167
- * const sha = await store.putObject('blob', content)
168
- * console.log(`Stored blob: ${sha}`)
169
- * ```
170
- */
171
- async putObject(type, data) {
172
- const startTime = this.options.enableMetrics ? Date.now() : 0;
173
- // Delegate to backend if available
174
- if (this.backend) {
175
- const sha = await this.backend.putObject(type, data);
176
- // Add to cache for fast subsequent reads
177
- const storedObject = {
178
- sha,
179
- type,
180
- size: data.length,
181
- data,
182
- createdAt: Date.now()
183
- };
184
- this.cache.set(sha, storedObject);
185
- // Update metrics
186
- if (this.options.enableMetrics) {
187
- this._writes++;
188
- this._bytesWritten += data.length;
189
- this._totalWriteLatency += Date.now() - startTime;
190
- }
191
- return sha;
192
- }
193
- // Existing SQLite implementation as fallback
194
- // Compute SHA-1 hash using git object format: "type size\0content"
195
- const sha = await hashObject(type, data);
196
- this.log('debug', `Storing ${type} object: ${sha} (${data.length} bytes)`);
197
- // Log to WAL first
198
- await this.logToWAL('PUT', sha, type, data);
199
- const now = Date.now();
200
- // Store the object
201
- this.storage.sql.exec('INSERT OR REPLACE INTO objects (sha, type, size, data, created_at) VALUES (?, ?, ?, ?, ?)', sha, type, data.length, data, now);
202
- // Update object index
203
- this.storage.sql.exec('INSERT OR REPLACE INTO object_index (sha, tier, pack_id, offset, size, type, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)', sha, 'hot', null, // pack_id is null for hot tier
204
- null, // offset is null for hot tier
205
- data.length, type, now);
206
- // Add to cache for fast subsequent reads
207
- const storedObject = {
208
- sha,
209
- type,
210
- size: data.length,
211
- data,
212
- createdAt: now
213
- };
214
- this.cache.set(sha, storedObject);
215
- // Update metrics
216
- if (this.options.enableMetrics) {
217
- this._writes++;
218
- this._bytesWritten += data.length;
219
- this._totalWriteLatency += Date.now() - startTime;
220
- }
221
- return sha;
222
- }
223
- /**
224
- * Store a tree object with entries.
225
- *
226
- * @description
227
- * Creates a Git tree object from an array of entries. Entries are sorted
228
- * by name (with directories treated as having trailing slashes for sorting).
229
- * Each entry is serialized as: "{mode} {name}\0{20-byte-sha}"
230
- *
231
- * @param entries - Array of tree entries (files and subdirectories)
232
- * @returns 40-character SHA-1 hash of the stored tree
233
- *
234
- * @example
235
- * ```typescript
236
- * const treeSha = await store.putTreeObject([
237
- * { mode: '100644', name: 'README.md', sha: blobSha },
238
- * { mode: '040000', name: 'src', sha: subdirSha }
239
- * ])
240
- * ```
241
- */
242
- async putTreeObject(entries) {
243
- // Validate all entries first
244
- const seenNames = new Set();
245
- for (const entry of entries) {
246
- // Check for invalid names: empty, '.', '..', contains '/' or null byte
247
- if (!entry.name || entry.name === '.' || entry.name === '..') {
248
- throw new Error(`Invalid entry name: "${entry.name}". Entry names cannot be empty, ".", or ".."`);
249
- }
250
- if (entry.name.includes('/')) {
251
- throw new Error(`Invalid entry name: "${entry.name}". Entry names cannot contain path separators`);
252
- }
253
- if (entry.name.includes('\0')) {
254
- throw new Error(`Invalid entry name: "${entry.name}". Entry names cannot contain null bytes`);
255
- }
256
- // Check for duplicate names
257
- if (seenNames.has(entry.name)) {
258
- throw new Error(`Duplicate entry name: "${entry.name}". Tree entries must have unique names`);
259
- }
260
- seenNames.add(entry.name);
261
- // Validate mode
262
- if (!isValidMode(entry.mode)) {
263
- throw new Error(`Invalid mode: "${entry.mode}". Valid modes: 100644, 100755, 040000, 120000, 160000`);
264
- }
265
- // Validate SHA
266
- if (!isValidSha(entry.sha)) {
267
- throw new Error(`Invalid SHA: "${entry.sha}". Must be 40 lowercase hex characters`);
268
- }
269
- }
270
- // Sort entries by name using ASCII byte-order comparison
271
- // Git sorts directories as if they have trailing slashes for comparison
272
- const sortedEntries = [...entries].sort((a, b) => {
273
- const aName = a.mode === '040000' ? a.name + '/' : a.name;
274
- const bName = b.mode === '040000' ? b.name + '/' : b.name;
275
- // Use simple comparison for ASCII byte order
276
- if (aName < bName)
277
- return -1;
278
- if (aName > bName)
279
- return 1;
280
- return 0;
281
- });
282
- // Build tree content (without header)
283
- const entryParts = [];
284
- for (const entry of sortedEntries) {
285
- const modeName = encoder.encode(`${entry.mode} ${entry.name}\0`);
286
- const sha20 = hexToBytes(entry.sha);
287
- const entryData = new Uint8Array(modeName.length + 20);
288
- entryData.set(modeName);
289
- entryData.set(sha20, modeName.length);
290
- entryParts.push(entryData);
291
- }
292
- // Combine all entry parts
293
- const contentLength = entryParts.reduce((sum, part) => sum + part.length, 0);
294
- const content = new Uint8Array(contentLength);
295
- let offset = 0;
296
- for (const part of entryParts) {
297
- content.set(part, offset);
298
- offset += part.length;
299
- }
300
- return this.putObject('tree', content);
301
- }
302
- /**
303
- * Store a commit object.
304
- *
305
- * @description
306
- * Creates a Git commit object with the specified tree, parents, author,
307
- * committer, and message. The commit content is formatted according to
308
- * the Git commit format specification.
309
- *
310
- * @param commit - Commit data
311
- * @param commit.tree - SHA of the root tree object
312
- * @param commit.parents - Array of parent commit SHAs (empty for root commit)
313
- * @param commit.author - Author information
314
- * @param commit.committer - Committer information
315
- * @param commit.message - Commit message
316
- * @returns 40-character SHA-1 hash of the stored commit
317
- *
318
- * @example
319
- * ```typescript
320
- * const now = Math.floor(Date.now() / 1000)
321
- * const author = { name: 'Alice', email: 'alice@example.com', timestamp: now, timezone: '+0000' }
322
- *
323
- * const sha = await store.putCommitObject({
324
- * tree: treeSha,
325
- * parents: [],
326
- * author,
327
- * committer: author,
328
- * message: 'Initial commit\n\nThis is the first commit.'
329
- * })
330
- * ```
331
- */
332
- async putCommitObject(commit) {
333
- // Build commit content (without header)
334
- const lines = [];
335
- lines.push(`tree ${commit.tree}`);
336
- for (const parent of commit.parents) {
337
- lines.push(`parent ${parent}`);
338
- }
339
- lines.push(`author ${commit.author.name} <${commit.author.email}> ${commit.author.timestamp} ${commit.author.timezone}`);
340
- lines.push(`committer ${commit.committer.name} <${commit.committer.email}> ${commit.committer.timestamp} ${commit.committer.timezone}`);
341
- lines.push('');
342
- lines.push(commit.message);
343
- const content = encoder.encode(lines.join('\n'));
344
- return this.putObject('commit', content);
345
- }
346
- /**
347
- * Store a tag object (annotated tag).
348
- *
349
- * @description
350
- * Creates a Git tag object pointing to another object with tagger
351
- * information and a message. The tag content is formatted according
352
- * to the Git tag format specification.
353
- *
354
- * @param tag - Tag data
355
- * @param tag.object - SHA of the object being tagged
356
- * @param tag.objectType - Type of the object being tagged
357
- * @param tag.tagger - Tagger information
358
- * @param tag.message - Tag message
359
- * @param tag.name - Tag name
360
- * @returns 40-character SHA-1 hash of the stored tag object
361
- *
362
- * @example
363
- * ```typescript
364
- * const now = Math.floor(Date.now() / 1000)
365
- * const tagger = { name: 'Bob', email: 'bob@example.com', timestamp: now, timezone: '+0000' }
366
- *
367
- * const sha = await store.putTagObject({
368
- * object: commitSha,
369
- * objectType: 'commit',
370
- * tagger,
371
- * message: 'Release v1.0.0',
372
- * name: 'v1.0.0'
373
- * })
374
- * ```
375
- */
376
- async putTagObject(tag) {
377
- // Build tag content (without header)
378
- const lines = [];
379
- lines.push(`object ${tag.object}`);
380
- lines.push(`type ${tag.objectType}`);
381
- lines.push(`tag ${tag.name}`);
382
- if (tag.tagger) {
383
- lines.push(`tagger ${tag.tagger.name} <${tag.tagger.email}> ${tag.tagger.timestamp} ${tag.tagger.timezone}`);
384
- }
385
- lines.push('');
386
- lines.push(tag.message);
387
- const content = encoder.encode(lines.join('\n'));
388
- return this.putObject('tag', content);
389
- }
390
- /**
391
- * Retrieve an object by SHA.
392
- *
393
- * @description
394
- * Fetches an object from the LRU cache first, falling back to the database
395
- * if not cached. Returns null if the object doesn't exist or if the SHA is invalid.
396
- *
397
- * @param sha - 40-character SHA-1 hash
398
- * @returns The stored object or null if not found
399
- *
400
- * @example
401
- * ```typescript
402
- * const obj = await store.getObject(sha)
403
- * if (obj) {
404
- * console.log(`Found ${obj.type} of ${obj.size} bytes`)
405
- * }
406
- * ```
407
- */
408
- async getObject(sha) {
409
- const startTime = this.options.enableMetrics ? Date.now() : 0;
410
- if (!sha || sha.length < 4) {
411
- return null;
412
- }
413
- // Check cache first (fast path)
414
- const cached = this.cache.get(sha);
415
- if (cached) {
416
- this.log('debug', `Cache hit for object: ${sha}`);
417
- if (this.options.enableMetrics) {
418
- this._reads++;
419
- this._bytesRead += cached.size;
420
- this._totalReadLatency += Date.now() - startTime;
421
- }
422
- return cached;
423
- }
424
- // Delegate to backend if available
425
- if (this.backend) {
426
- const result = await this.backend.getObject(sha);
427
- if (!result) {
428
- this.log('debug', `Object not found: ${sha}`);
429
- if (this.options.enableMetrics) {
430
- this._reads++;
431
- this._totalReadLatency += Date.now() - startTime;
432
- }
433
- return null;
434
- }
435
- const obj = {
436
- sha,
437
- type: result.type,
438
- size: result.content.length,
439
- data: result.content,
440
- createdAt: Date.now()
441
- };
442
- // Add to cache for subsequent reads
443
- this.cache.set(sha, obj);
444
- if (this.options.enableMetrics) {
445
- this._reads++;
446
- this._bytesRead += obj.size;
447
- this._totalReadLatency += Date.now() - startTime;
448
- }
449
- return obj;
450
- }
451
- // Existing SQLite implementation as fallback
452
- // Fall back to database
453
- const result = this.storage.sql.exec('SELECT sha, type, size, data, created_at as createdAt FROM objects WHERE sha = ?', sha);
454
- const rows = result.toArray();
455
- if (rows.length === 0) {
456
- this.log('debug', `Object not found: ${sha}`);
457
- if (this.options.enableMetrics) {
458
- this._reads++;
459
- this._totalReadLatency += Date.now() - startTime;
460
- }
461
- return null;
462
- }
463
- const obj = rows[0];
464
- // Add to cache for subsequent reads
465
- this.cache.set(sha, obj);
466
- if (this.options.enableMetrics) {
467
- this._reads++;
468
- this._bytesRead += obj.size;
469
- this._totalReadLatency += Date.now() - startTime;
470
- }
471
- return obj;
472
- }
473
- /**
474
- * Delete an object by SHA.
475
- *
476
- * @description
477
- * Removes an object from the cache, objects table, and the object index.
478
- * The operation is logged to WAL. Returns false if the object doesn't exist.
479
- *
480
- * **Warning**: Deleting objects that are still referenced by other objects
481
- * (e.g., blobs referenced by trees) will corrupt the repository.
482
- *
483
- * @param sha - 40-character SHA-1 hash
484
- * @returns True if the object was deleted, false if it didn't exist
485
- *
486
- * @example
487
- * ```typescript
488
- * const deleted = await store.deleteObject(sha)
489
- * if (deleted) {
490
- * console.log('Object removed')
491
- * }
492
- * ```
493
- */
494
- async deleteObject(sha) {
495
- // Delegate to backend if available
496
- if (this.backend) {
497
- // Check if object exists first via backend
498
- const exists = await this.backend.hasObject(sha);
499
- if (!exists) {
500
- return false;
501
- }
502
- this.log('debug', `Deleting object via backend: ${sha}`);
503
- await this.backend.deleteObject(sha);
504
- // Remove from cache
505
- this.cache.delete(sha);
506
- // Update metrics
507
- if (this.options.enableMetrics) {
508
- this._deletes++;
509
- }
510
- return true;
511
- }
512
- // Existing SQLite implementation as fallback
513
- // Check if object exists first
514
- const exists = await this.hasObject(sha);
515
- if (!exists) {
516
- return false;
517
- }
518
- this.log('debug', `Deleting object: ${sha}`);
519
- // Log to WAL
520
- await this.logToWAL('DELETE', sha, 'blob', new Uint8Array(0));
521
- // Delete from objects table
522
- this.storage.sql.exec('DELETE FROM objects WHERE sha = ?', sha);
523
- // Delete from object index
524
- this.storage.sql.exec('DELETE FROM object_index WHERE sha = ?', sha);
525
- // Remove from cache
526
- this.cache.delete(sha);
527
- // Update metrics
528
- if (this.options.enableMetrics) {
529
- this._deletes++;
530
- }
531
- return true;
532
- }
533
- /**
534
- * Check if an object exists.
535
- *
536
- * @description
537
- * Efficiently checks for object existence without fetching the full content.
538
- *
539
- * @param sha - 40-character SHA-1 hash
540
- * @returns True if the object exists, false otherwise
541
- *
542
- * @example
543
- * ```typescript
544
- * if (await store.hasObject(sha)) {
545
- * console.log('Object exists')
546
- * }
547
- * ```
548
- */
549
- async hasObject(sha) {
550
- if (!sha || sha.length < 4) {
551
- return false;
552
- }
553
- // Check cache first (fast path)
554
- if (this.cache.has(sha)) {
555
- return true;
556
- }
557
- // Delegate to backend if available
558
- if (this.backend) {
559
- return this.backend.hasObject(sha);
560
- }
561
- // Existing SQLite implementation as fallback
562
- // Use getObject and check for null - this works better with the mock
563
- const obj = await this.getObject(sha);
564
- return obj !== null;
565
- }
566
- /**
567
- * Verify an object's integrity by recomputing its hash.
568
- *
569
- * @description
570
- * Computes the SHA-1 hash of the stored object and compares it
571
- * to the stored SHA. Returns false if the object is corrupted
572
- * or doesn't exist.
573
- *
574
- * @param sha - 40-character SHA-1 hash to verify
575
- * @returns True if the computed hash matches, false otherwise
576
- *
577
- * @example
578
- * ```typescript
579
- * if (await store.verifyObject(sha)) {
580
- * console.log('Object integrity verified')
581
- * } else {
582
- * console.log('Object is corrupted or missing')
583
- * }
584
- * ```
585
- */
586
- async verifyObject(sha) {
587
- // Read directly from storage (bypass cache) to verify actual stored data
588
- const result = this.storage.sql.exec('SELECT type, data FROM objects WHERE sha = ?', sha);
589
- const rows = result.toArray();
590
- if (rows.length === 0) {
591
- return false;
592
- }
593
- const obj = rows[0];
594
- const computedSha = await hashObject(obj.type, new Uint8Array(obj.data));
595
- return computedSha === sha;
596
- }
597
- /**
598
- * Get object type by SHA.
599
- *
600
- * @description
601
- * Returns just the type of an object without fetching its content.
602
- *
603
- * @param sha - 40-character SHA-1 hash
604
- * @returns Object type or null if not found
605
- *
606
- * @example
607
- * ```typescript
608
- * const type = await store.getObjectType(sha)
609
- * if (type === 'commit') {
610
- * // Handle commit
611
- * }
612
- * ```
613
- */
614
- async getObjectType(sha) {
615
- const obj = await this.getObject(sha);
616
- return obj?.type ?? null;
617
- }
618
- /**
619
- * Get object size by SHA.
620
- *
621
- * @description
622
- * Returns just the size of an object without fetching its content.
623
- *
624
- * @param sha - 40-character SHA-1 hash
625
- * @returns Object size in bytes or null if not found
626
- *
627
- * @example
628
- * ```typescript
629
- * const size = await store.getObjectSize(sha)
630
- * console.log(`Object is ${size} bytes`)
631
- * ```
632
- */
633
- async getObjectSize(sha) {
634
- const obj = await this.getObject(sha);
635
- return obj?.size ?? null;
636
- }
637
- /**
638
- * Store multiple objects in a batch using a single transaction.
639
- *
640
- * @description
641
- * Stores multiple objects atomically within a single SQLite transaction.
642
- * This is more efficient than individual puts for bulk operations as it:
643
- * - Reduces the number of disk flushes
644
- * - Ensures atomic writes (all-or-nothing)
645
- * - Batches WAL entries for better performance
646
- *
647
- * @param objects - Array of objects to store
648
- * @returns Array of SHA-1 hashes in the same order as input
649
- *
650
- * @example
651
- * ```typescript
652
- * const shas = await store.putObjects([
653
- * { type: 'blob', data: content1 },
654
- * { type: 'blob', data: content2 }
655
- * ])
656
- * ```
657
- */
658
- async putObjects(objects) {
659
- if (objects.length === 0) {
660
- return [];
661
- }
662
- // For single objects, delegate to putObject
663
- if (objects.length === 1) {
664
- const sha = await this.putObject(objects[0].type, objects[0].data);
665
- return [sha];
666
- }
667
- const startTime = this.options.enableMetrics ? Date.now() : 0;
668
- const shas = [];
669
- const now = Date.now();
670
- let totalBytes = 0;
671
- this.log('info', `Starting batch write of ${objects.length} objects`);
672
- // Pre-compute all SHA hashes (CPU-bound, before transaction)
673
- const objectsWithSha = [];
674
- for (const obj of objects) {
675
- const sha = await hashObject(obj.type, obj.data);
676
- objectsWithSha.push({ sha, type: obj.type, data: obj.data });
677
- shas.push(sha);
678
- totalBytes += obj.data.length;
679
- }
680
- // Begin transaction for atomic batch write
681
- this.storage.sql.exec('BEGIN TRANSACTION');
682
- try {
683
- for (const { sha, type, data } of objectsWithSha) {
684
- // Log batch operation to WAL (single entry for the batch)
685
- const payload = encoder.encode(JSON.stringify({
686
- sha,
687
- type,
688
- timestamp: now,
689
- batchSize: objects.length
690
- }));
691
- this.storage.sql.exec('INSERT INTO wal (operation, payload, created_at, flushed) VALUES (?, ?, ?, 0)', 'BATCH_PUT', payload, now);
692
- // Store the object
693
- this.storage.sql.exec('INSERT OR REPLACE INTO objects (sha, type, size, data, created_at) VALUES (?, ?, ?, ?, ?)', sha, type, data.length, data, now);
694
- // Update object index
695
- this.storage.sql.exec('INSERT OR REPLACE INTO object_index (sha, tier, pack_id, offset, size, type, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)', sha, 'hot', null, // pack_id is null for hot tier
696
- null, // offset is null for hot tier
697
- data.length, type, now);
698
- // Add to cache
699
- const storedObject = {
700
- sha,
701
- type,
702
- size: data.length,
703
- data,
704
- createdAt: now
705
- };
706
- this.cache.set(sha, storedObject);
707
- }
708
- // Commit transaction
709
- this.storage.sql.exec('COMMIT');
710
- this.log('info', `Batch write completed: ${objects.length} objects, ${totalBytes} bytes`);
711
- // Update metrics
712
- if (this.options.enableMetrics) {
713
- this._writes += objects.length;
714
- this._bytesWritten += totalBytes;
715
- this._totalWriteLatency += Date.now() - startTime;
716
- this._batchOperations++;
717
- this._batchObjectsTotal += objects.length;
718
- }
719
- return shas;
720
- }
721
- catch (error) {
722
- // Rollback on error
723
- this.storage.sql.exec('ROLLBACK');
724
- this.log('error', `Batch write failed, rolled back`, error);
725
- throw error;
726
- }
727
- }
728
- /**
729
- * Retrieve multiple objects by SHA using optimized batch queries.
730
- *
731
- * @description
732
- * Fetches multiple objects efficiently by:
733
- * 1. First checking the LRU cache for each SHA
734
- * 2. Batching uncached SHAs into a single SQL query with IN clause
735
- * 3. Returning results in the original order with null for missing objects
736
- *
737
- * @param shas - Array of 40-character SHA-1 hashes
738
- * @returns Array of objects (or null for missing) in the same order
739
- *
740
- * @example
741
- * ```typescript
742
- * const objects = await store.getObjects([sha1, sha2, sha3])
743
- * objects.forEach((obj, i) => {
744
- * if (obj) {
745
- * console.log(`${i}: ${obj.type}`)
746
- * }
747
- * })
748
- * ```
749
- */
750
- async getObjects(shas) {
751
- if (shas.length === 0) {
752
- return [];
753
- }
754
- const startTime = this.options.enableMetrics ? Date.now() : 0;
755
- const results = new Array(shas.length).fill(null);
756
- const uncachedIndices = [];
757
- const uncachedShas = [];
758
- let totalBytesRead = 0;
759
- // First pass: check cache for each SHA
760
- for (let i = 0; i < shas.length; i++) {
761
- const sha = shas[i];
762
- if (!sha || sha.length < 4) {
763
- results[i] = null;
764
- continue;
765
- }
766
- const cached = this.cache.get(sha);
767
- if (cached) {
768
- results[i] = cached;
769
- totalBytesRead += cached.size;
770
- }
771
- else {
772
- uncachedIndices.push(i);
773
- uncachedShas.push(sha);
774
- }
775
- }
776
- // Second pass: batch query for uncached objects
777
- if (uncachedShas.length > 0) {
778
- this.log('debug', `Batch fetching ${uncachedShas.length} uncached objects`);
779
- // Build optimized IN query
780
- const placeholders = uncachedShas.map(() => '?').join(', ');
781
- const result = this.storage.sql.exec(`SELECT sha, type, size, data, created_at as createdAt FROM objects WHERE sha IN (${placeholders})`, ...uncachedShas);
782
- const rows = result.toArray();
783
- // Build lookup map for O(1) access
784
- const rowMap = new Map();
785
- for (const row of rows) {
786
- rowMap.set(row.sha, row);
787
- // Add to cache for future reads
788
- this.cache.set(row.sha, row);
789
- totalBytesRead += row.size;
790
- }
791
- // Fill in results at original indices
792
- for (let i = 0; i < uncachedIndices.length; i++) {
793
- const originalIndex = uncachedIndices[i];
794
- const sha = uncachedShas[i];
795
- results[originalIndex] = rowMap.get(sha) ?? null;
796
- }
797
- }
798
- // Update metrics
799
- if (this.options.enableMetrics) {
800
- this._reads += shas.length;
801
- this._bytesRead += totalBytesRead;
802
- this._totalReadLatency += Date.now() - startTime;
803
- }
804
- return results;
805
- }
806
- /**
807
- * Get a blob object with typed result.
808
- *
809
- * @description
810
- * Fetches an object and returns it as a BlobObject if it's a blob.
811
- * Returns null if the object doesn't exist or isn't a blob.
812
- *
813
- * @param sha - 40-character SHA-1 hash
814
- * @returns Typed BlobObject or null
815
- *
816
- * @example
817
- * ```typescript
818
- * const blob = await store.getBlobObject(sha)
819
- * if (blob) {
820
- * const content = new TextDecoder().decode(blob.data)
821
- * console.log(content)
822
- * }
823
- * ```
824
- */
825
- async getBlobObject(sha) {
826
- const obj = await this.getObject(sha);
827
- if (!obj || obj.type !== 'blob') {
828
- return null;
829
- }
830
- return {
831
- type: 'blob',
832
- data: obj.data
833
- };
834
- }
835
- /**
836
- * Get a tree object with parsed entries.
837
- *
838
- * @description
839
- * Fetches and parses a tree object, extracting all entries
840
- * with their modes, names, and SHA references.
841
- *
842
- * @param sha - 40-character SHA-1 hash
843
- * @returns Parsed TreeObject with entries or null
844
- *
845
- * @example
846
- * ```typescript
847
- * const tree = await store.getTreeObject(sha)
848
- * if (tree) {
849
- * for (const entry of tree.entries) {
850
- * console.log(`${entry.mode} ${entry.name} ${entry.sha}`)
851
- * }
852
- * }
853
- * ```
854
- */
855
- async getTreeObject(sha) {
856
- const obj = await this.getObject(sha);
857
- if (!obj || obj.type !== 'tree') {
858
- return null;
859
- }
860
- // Parse tree entries from raw data
861
- const entries = [];
862
- let offset = 0;
863
- const data = obj.data;
864
- try {
865
- while (offset < data.length) {
866
- // Find the null byte after mode+name
867
- let nullIndex = offset;
868
- while (nullIndex < data.length && data[nullIndex] !== 0) {
869
- nullIndex++;
870
- }
871
- // Check if we found a null byte
872
- if (nullIndex >= data.length) {
873
- // No null byte found - malformed data, return empty entries
874
- return { type: 'tree', data: obj.data, entries: [] };
875
- }
876
- const modeNameStr = decoder.decode(data.slice(offset, nullIndex));
877
- const spaceIndex = modeNameStr.indexOf(' ');
878
- // Check for valid mode+name format
879
- if (spaceIndex === -1) {
880
- // No space found - malformed entry, return empty entries
881
- return { type: 'tree', data: obj.data, entries: [] };
882
- }
883
- const mode = modeNameStr.slice(0, spaceIndex);
884
- const name = modeNameStr.slice(spaceIndex + 1);
885
- // Check if we have enough bytes for the 20-byte SHA
886
- if (nullIndex + 21 > data.length) {
887
- // Not enough bytes for SHA - return what we have parsed so far as malformed
888
- return { type: 'tree', data: obj.data, entries: [] };
889
- }
890
- // Read 20-byte SHA
891
- const sha20 = data.slice(nullIndex + 1, nullIndex + 21);
892
- const entrySha = bytesToHex(sha20);
893
- entries.push({ mode, name, sha: entrySha });
894
- offset = nullIndex + 21;
895
- }
896
- }
897
- catch {
898
- // Any parsing error - return null or empty entries
899
- return { type: 'tree', data: obj.data, entries: [] };
900
- }
901
- return {
902
- type: 'tree',
903
- data: obj.data,
904
- entries
905
- };
906
- }
907
- /**
908
- * Get a commit object with parsed fields.
909
- *
910
- * @description
911
- * Fetches and parses a commit object, extracting tree SHA,
912
- * parent SHAs, author, committer, and message.
913
- *
914
- * @param sha - 40-character SHA-1 hash
915
- * @returns Parsed CommitObject or null
916
- *
917
- * @example
918
- * ```typescript
919
- * const commit = await store.getCommitObject(sha)
920
- * if (commit) {
921
- * console.log(`Author: ${commit.author.name}`)
922
- * console.log(`Message: ${commit.message}`)
923
- * console.log(`Parents: ${commit.parents.length}`)
924
- * }
925
- * ```
926
- */
927
- async getCommitObject(sha) {
928
- const obj = await this.getObject(sha);
929
- if (!obj || obj.type !== 'commit') {
930
- return null;
931
- }
932
- const content = decoder.decode(obj.data);
933
- const lines = content.split('\n');
934
- let tree = '';
935
- const parents = [];
936
- let author = null;
937
- let committer = null;
938
- let messageStartIndex = 0;
939
- for (let i = 0; i < lines.length; i++) {
940
- const line = lines[i];
941
- if (line === '') {
942
- messageStartIndex = i + 1;
943
- break;
944
- }
945
- if (line.startsWith('tree ')) {
946
- tree = line.slice(5);
947
- }
948
- else if (line.startsWith('parent ')) {
949
- parents.push(line.slice(7));
950
- }
951
- else if (line.startsWith('author ')) {
952
- author = parseAuthorLine(line);
953
- }
954
- else if (line.startsWith('committer ')) {
955
- committer = parseAuthorLine(line);
956
- }
957
- }
958
- if (!author || !committer) {
959
- return null;
960
- }
961
- const message = lines.slice(messageStartIndex).join('\n');
962
- return {
963
- type: 'commit',
964
- data: obj.data,
965
- tree,
966
- parents,
967
- author,
968
- committer,
969
- message
970
- };
971
- }
972
- /**
973
- * Get a tag object with parsed fields.
974
- *
975
- * @description
976
- * Fetches and parses an annotated tag object, extracting
977
- * the tagged object SHA, object type, tag name, tagger, and message.
978
- *
979
- * @param sha - 40-character SHA-1 hash
980
- * @returns Parsed TagObject or null
981
- *
982
- * @example
983
- * ```typescript
984
- * const tag = await store.getTagObject(sha)
985
- * if (tag) {
986
- * console.log(`Tag: ${tag.name}`)
987
- * console.log(`Points to: ${tag.object} (${tag.objectType})`)
988
- * console.log(`Tagger: ${tag.tagger?.name}`)
989
- * }
990
- * ```
991
- */
992
- async getTagObject(sha) {
993
- const obj = await this.getObject(sha);
994
- if (!obj || obj.type !== 'tag') {
995
- return null;
996
- }
997
- const content = decoder.decode(obj.data);
998
- const lines = content.split('\n');
999
- let object = '';
1000
- let objectType = 'commit';
1001
- let name = '';
1002
- let tagger = undefined;
1003
- let messageStartIndex = 0;
1004
- for (let i = 0; i < lines.length; i++) {
1005
- const line = lines[i];
1006
- if (line === '') {
1007
- messageStartIndex = i + 1;
1008
- break;
1009
- }
1010
- if (line.startsWith('object ')) {
1011
- object = line.slice(7);
1012
- }
1013
- else if (line.startsWith('type ')) {
1014
- objectType = line.slice(5);
1015
- }
1016
- else if (line.startsWith('tag ')) {
1017
- name = line.slice(4);
1018
- }
1019
- else if (line.startsWith('tagger ')) {
1020
- try {
1021
- tagger = parseAuthorLine(line);
1022
- }
1023
- catch {
1024
- // Malformed tagger line - leave tagger as undefined
1025
- return null;
1026
- }
1027
- }
1028
- }
1029
- // Validate required fields - object and name must be present
1030
- // tagger is optional (some older tags or special tags may not have it)
1031
- if (!object || !name) {
1032
- return null;
1033
- }
1034
- const message = lines.slice(messageStartIndex).join('\n');
1035
- return {
1036
- type: 'tag',
1037
- data: obj.data,
1038
- object,
1039
- objectType,
1040
- name,
1041
- tagger,
1042
- message
1043
- };
1044
- }
1045
- /**
1046
- * Get raw serialized object with Git header.
1047
- *
1048
- * @description
1049
- * Returns the complete Git object format including header:
1050
- * "{type} {size}\0{content}"
1051
- *
1052
- * This is the format used for hashing and storage in pack files.
1053
- *
1054
- * @param sha - 40-character SHA-1 hash
1055
- * @returns Complete object with Git header or null
1056
- *
1057
- * @example
1058
- * ```typescript
1059
- * const raw = await store.getRawObject(sha)
1060
- * if (raw) {
1061
- * // Can be written directly to a pack file or loose object
1062
- * }
1063
- * ```
1064
- */
1065
- async getRawObject(sha) {
1066
- const obj = await this.getObject(sha);
1067
- if (!obj) {
1068
- return null;
1069
- }
1070
- // Build git object format: "type size\0content"
1071
- const header = encoder.encode(`${obj.type} ${obj.data.length}\0`);
1072
- const result = new Uint8Array(header.length + obj.data.length);
1073
- result.set(header);
1074
- result.set(obj.data, header.length);
1075
- return result;
1076
- }
1077
- /**
1078
- * Log operation to WAL.
1079
- *
1080
- * @description
1081
- * Writes an operation entry to the write-ahead log for durability.
1082
- * The WAL ensures operations can be recovered after crashes.
1083
- *
1084
- * @param operation - Operation type ('PUT', 'DELETE', etc.)
1085
- * @param sha - Object SHA being operated on
1086
- * @param type - Object type
1087
- * @param _data - Object data (not stored in WAL, just for signature compatibility)
1088
- * @internal
1089
- */
1090
- async logToWAL(operation, sha, type, _data) {
1091
- // Create payload with operation details
1092
- const payload = encoder.encode(JSON.stringify({
1093
- sha,
1094
- type,
1095
- timestamp: Date.now()
1096
- }));
1097
- this.storage.sql.exec('INSERT INTO wal (operation, payload, created_at, flushed) VALUES (?, ?, ?, 0)', operation, payload, Date.now());
1098
- }
1099
- }
1100
- // ============================================================================
1101
- // Helper Functions
1102
- // ============================================================================
1103
- /**
1104
- * Convert hexadecimal string to bytes.
1105
- *
1106
- * @description
1107
- * Parses a hexadecimal string and returns the corresponding bytes.
1108
- * Used for converting SHA strings to 20-byte binary format.
1109
- *
1110
- * @param hex - Hexadecimal string
1111
- * @returns Binary data as Uint8Array
1112
- * @internal
1113
- */
1114
- function hexToBytes(hex) {
1115
- const bytes = new Uint8Array(hex.length / 2);
1116
- for (let i = 0; i < hex.length; i += 2) {
1117
- bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
1118
- }
1119
- return bytes;
1120
- }
1121
- /**
1122
- * Convert bytes to hexadecimal string.
1123
- *
1124
- * @description
1125
- * Converts binary data to a lowercase hexadecimal string.
1126
- * Used for converting 20-byte SHA to 40-character string.
1127
- *
1128
- * @param bytes - Binary data to convert
1129
- * @returns Lowercase hexadecimal string
1130
- * @internal
1131
- */
1132
- function bytesToHex(bytes) {
1133
- return Array.from(bytes)
1134
- .map(b => b.toString(16).padStart(2, '0'))
1135
- .join('');
1136
- }
1137
- /**
1138
- * Parse author/committer/tagger line.
1139
- *
1140
- * @description
1141
- * Parses a Git author/committer/tagger line in the format:
1142
- * "author Name <email> timestamp timezone"
1143
- *
1144
- * @param line - Full line including prefix
1145
- * @returns Parsed Author object
1146
- * @throws Error if line format is invalid
1147
- * @internal
1148
- */
1149
- function parseAuthorLine(line) {
1150
- const match = line.match(/^(?:author|committer|tagger) (.+) <(.+)> (\d+) ([+-]\d{4})$/);
1151
- if (!match) {
1152
- throw new Error(`Invalid author line: ${line}`);
1153
- }
1154
- return {
1155
- name: match[1],
1156
- email: match[2],
1157
- timestamp: parseInt(match[3], 10),
1158
- timezone: match[4]
1159
- };
1160
- }
1161
- //# sourceMappingURL=object-store.js.map