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.
- package/README.md +40 -353
- package/dist/do/logger.d.ts +50 -0
- package/dist/do/logger.d.ts.map +1 -0
- package/dist/do/logger.js +122 -0
- package/dist/do/logger.js.map +1 -0
- package/dist/{durable-object → do}/schema.d.ts +3 -3
- package/dist/do/schema.d.ts.map +1 -0
- package/dist/{durable-object → do}/schema.js +4 -3
- package/dist/do/schema.js.map +1 -0
- package/dist/do/types.d.ts +267 -0
- package/dist/do/types.d.ts.map +1 -0
- package/dist/do/types.js +62 -0
- package/dist/do/types.js.map +1 -0
- package/dist/index.d.ts +15 -415
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -483
- package/dist/index.js.map +1 -1
- package/package.json +13 -21
- package/dist/cli/commands/add.d.ts +0 -174
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -131
- package/dist/cli/commands/add.js.map +0 -1
- package/dist/cli/commands/blame.d.ts +0 -259
- package/dist/cli/commands/blame.d.ts.map +0 -1
- package/dist/cli/commands/blame.js +0 -609
- package/dist/cli/commands/blame.js.map +0 -1
- package/dist/cli/commands/branch.d.ts +0 -249
- package/dist/cli/commands/branch.d.ts.map +0 -1
- package/dist/cli/commands/branch.js +0 -693
- package/dist/cli/commands/branch.js.map +0 -1
- package/dist/cli/commands/commit.d.ts +0 -182
- package/dist/cli/commands/commit.d.ts.map +0 -1
- package/dist/cli/commands/commit.js +0 -437
- package/dist/cli/commands/commit.js.map +0 -1
- package/dist/cli/commands/diff.d.ts +0 -464
- package/dist/cli/commands/diff.d.ts.map +0 -1
- package/dist/cli/commands/diff.js +0 -958
- package/dist/cli/commands/diff.js.map +0 -1
- package/dist/cli/commands/log.d.ts +0 -239
- package/dist/cli/commands/log.d.ts.map +0 -1
- package/dist/cli/commands/log.js +0 -535
- package/dist/cli/commands/log.js.map +0 -1
- package/dist/cli/commands/merge.d.ts +0 -106
- package/dist/cli/commands/merge.d.ts.map +0 -1
- package/dist/cli/commands/merge.js +0 -55
- package/dist/cli/commands/merge.js.map +0 -1
- package/dist/cli/commands/review.d.ts +0 -457
- package/dist/cli/commands/review.d.ts.map +0 -1
- package/dist/cli/commands/review.js +0 -533
- package/dist/cli/commands/review.js.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -269
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/status.js +0 -493
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/web.d.ts +0 -199
- package/dist/cli/commands/web.d.ts.map +0 -1
- package/dist/cli/commands/web.js +0 -696
- package/dist/cli/commands/web.js.map +0 -1
- package/dist/cli/fs-adapter.d.ts +0 -656
- package/dist/cli/fs-adapter.d.ts.map +0 -1
- package/dist/cli/fs-adapter.js +0 -1179
- package/dist/cli/fs-adapter.js.map +0 -1
- package/dist/cli/fsx-cli-adapter.d.ts +0 -359
- package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
- package/dist/cli/fsx-cli-adapter.js +0 -619
- package/dist/cli/fsx-cli-adapter.js.map +0 -1
- package/dist/cli/index.d.ts +0 -387
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -523
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/ui/components/DiffView.d.ts +0 -7
- package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
- package/dist/cli/ui/components/DiffView.js +0 -11
- package/dist/cli/ui/components/DiffView.js.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
- package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.js +0 -11
- package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
- package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.js +0 -12
- package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
- package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.js +0 -10
- package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
- package/dist/cli/ui/components/NavigationList.d.ts +0 -9
- package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
- package/dist/cli/ui/components/NavigationList.js +0 -11
- package/dist/cli/ui/components/NavigationList.js.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
- package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.js +0 -11
- package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
- package/dist/cli/ui/components/index.d.ts +0 -7
- package/dist/cli/ui/components/index.d.ts.map +0 -1
- package/dist/cli/ui/components/index.js +0 -9
- package/dist/cli/ui/components/index.js.map +0 -1
- package/dist/cli/ui/terminal-ui.d.ts +0 -52
- package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
- package/dist/cli/ui/terminal-ui.js +0 -121
- package/dist/cli/ui/terminal-ui.js.map +0 -1
- package/dist/do/BashModule.d.ts +0 -871
- package/dist/do/BashModule.d.ts.map +0 -1
- package/dist/do/BashModule.js +0 -1143
- package/dist/do/BashModule.js.map +0 -1
- package/dist/do/FsModule.d.ts +0 -601
- package/dist/do/FsModule.d.ts.map +0 -1
- package/dist/do/FsModule.js +0 -1120
- package/dist/do/FsModule.js.map +0 -1
- package/dist/do/GitModule.d.ts +0 -635
- package/dist/do/GitModule.d.ts.map +0 -1
- package/dist/do/GitModule.js +0 -781
- package/dist/do/GitModule.js.map +0 -1
- package/dist/do/GitRepoDO.d.ts +0 -281
- package/dist/do/GitRepoDO.d.ts.map +0 -1
- package/dist/do/GitRepoDO.js +0 -479
- package/dist/do/GitRepoDO.js.map +0 -1
- package/dist/do/bash-ast.d.ts +0 -246
- package/dist/do/bash-ast.d.ts.map +0 -1
- package/dist/do/bash-ast.js +0 -888
- package/dist/do/bash-ast.js.map +0 -1
- package/dist/do/container-executor.d.ts +0 -491
- package/dist/do/container-executor.d.ts.map +0 -1
- package/dist/do/container-executor.js +0 -730
- package/dist/do/container-executor.js.map +0 -1
- package/dist/do/index.d.ts +0 -53
- package/dist/do/index.d.ts.map +0 -1
- package/dist/do/index.js +0 -91
- package/dist/do/index.js.map +0 -1
- package/dist/do/tiered-storage.d.ts +0 -403
- package/dist/do/tiered-storage.d.ts.map +0 -1
- package/dist/do/tiered-storage.js +0 -689
- package/dist/do/tiered-storage.js.map +0 -1
- package/dist/do/withBash.d.ts +0 -231
- package/dist/do/withBash.d.ts.map +0 -1
- package/dist/do/withBash.js +0 -244
- package/dist/do/withBash.js.map +0 -1
- package/dist/do/withFs.d.ts +0 -237
- package/dist/do/withFs.d.ts.map +0 -1
- package/dist/do/withFs.js +0 -387
- package/dist/do/withFs.js.map +0 -1
- package/dist/do/withGit.d.ts +0 -180
- package/dist/do/withGit.d.ts.map +0 -1
- package/dist/do/withGit.js +0 -271
- package/dist/do/withGit.js.map +0 -1
- package/dist/durable-object/object-store.d.ts +0 -633
- package/dist/durable-object/object-store.d.ts.map +0 -1
- package/dist/durable-object/object-store.js +0 -1161
- package/dist/durable-object/object-store.js.map +0 -1
- package/dist/durable-object/schema.d.ts.map +0 -1
- package/dist/durable-object/schema.js.map +0 -1
- package/dist/durable-object/wal.d.ts +0 -416
- package/dist/durable-object/wal.d.ts.map +0 -1
- package/dist/durable-object/wal.js +0 -445
- package/dist/durable-object/wal.js.map +0 -1
- package/dist/mcp/adapter.d.ts +0 -772
- package/dist/mcp/adapter.d.ts.map +0 -1
- package/dist/mcp/adapter.js +0 -895
- package/dist/mcp/adapter.js.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
- package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.js +0 -30
- package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
- package/dist/mcp/sandbox/template.d.ts +0 -17
- package/dist/mcp/sandbox/template.d.ts.map +0 -1
- package/dist/mcp/sandbox/template.js +0 -71
- package/dist/mcp/sandbox/template.js.map +0 -1
- package/dist/mcp/sandbox.d.ts +0 -764
- package/dist/mcp/sandbox.d.ts.map +0 -1
- package/dist/mcp/sandbox.js +0 -1362
- package/dist/mcp/sandbox.js.map +0 -1
- package/dist/mcp/sdk-adapter.d.ts +0 -835
- package/dist/mcp/sdk-adapter.d.ts.map +0 -1
- package/dist/mcp/sdk-adapter.js +0 -974
- package/dist/mcp/sdk-adapter.js.map +0 -1
- package/dist/mcp/tools/do.d.ts +0 -32
- package/dist/mcp/tools/do.d.ts.map +0 -1
- package/dist/mcp/tools/do.js +0 -115
- package/dist/mcp/tools/do.js.map +0 -1
- package/dist/mcp/tools.d.ts +0 -548
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/tools.js +0 -1934
- package/dist/mcp/tools.js.map +0 -1
- package/dist/ops/blame.d.ts +0 -551
- package/dist/ops/blame.d.ts.map +0 -1
- package/dist/ops/blame.js +0 -1037
- package/dist/ops/blame.js.map +0 -1
- package/dist/ops/branch.d.ts +0 -766
- package/dist/ops/branch.d.ts.map +0 -1
- package/dist/ops/branch.js +0 -950
- package/dist/ops/branch.js.map +0 -1
- package/dist/ops/commit-traversal.d.ts +0 -349
- package/dist/ops/commit-traversal.d.ts.map +0 -1
- package/dist/ops/commit-traversal.js +0 -821
- package/dist/ops/commit-traversal.js.map +0 -1
- package/dist/ops/commit.d.ts +0 -555
- package/dist/ops/commit.d.ts.map +0 -1
- package/dist/ops/commit.js +0 -826
- package/dist/ops/commit.js.map +0 -1
- package/dist/ops/merge-base.d.ts +0 -397
- package/dist/ops/merge-base.d.ts.map +0 -1
- package/dist/ops/merge-base.js +0 -691
- package/dist/ops/merge-base.js.map +0 -1
- package/dist/ops/merge.d.ts +0 -855
- package/dist/ops/merge.d.ts.map +0 -1
- package/dist/ops/merge.js +0 -1551
- package/dist/ops/merge.js.map +0 -1
- package/dist/ops/tag.d.ts +0 -247
- package/dist/ops/tag.d.ts.map +0 -1
- package/dist/ops/tag.js +0 -649
- package/dist/ops/tag.js.map +0 -1
- package/dist/ops/tree-builder.d.ts +0 -178
- package/dist/ops/tree-builder.d.ts.map +0 -1
- package/dist/ops/tree-builder.js +0 -271
- package/dist/ops/tree-builder.js.map +0 -1
- package/dist/ops/tree-diff.d.ts +0 -291
- package/dist/ops/tree-diff.d.ts.map +0 -1
- package/dist/ops/tree-diff.js +0 -705
- package/dist/ops/tree-diff.js.map +0 -1
- package/dist/pack/delta.d.ts +0 -248
- package/dist/pack/delta.d.ts.map +0 -1
- package/dist/pack/delta.js +0 -736
- package/dist/pack/delta.js.map +0 -1
- package/dist/pack/format.d.ts +0 -446
- package/dist/pack/format.d.ts.map +0 -1
- package/dist/pack/format.js +0 -572
- package/dist/pack/format.js.map +0 -1
- package/dist/pack/full-generation.d.ts +0 -612
- package/dist/pack/full-generation.d.ts.map +0 -1
- package/dist/pack/full-generation.js +0 -1378
- package/dist/pack/full-generation.js.map +0 -1
- package/dist/pack/generation.d.ts +0 -441
- package/dist/pack/generation.d.ts.map +0 -1
- package/dist/pack/generation.js +0 -707
- package/dist/pack/generation.js.map +0 -1
- package/dist/pack/index.d.ts +0 -502
- package/dist/pack/index.d.ts.map +0 -1
- package/dist/pack/index.js +0 -833
- package/dist/pack/index.js.map +0 -1
- package/dist/refs/branch.d.ts +0 -668
- package/dist/refs/branch.d.ts.map +0 -1
- package/dist/refs/branch.js +0 -897
- package/dist/refs/branch.js.map +0 -1
- package/dist/refs/storage.d.ts +0 -833
- package/dist/refs/storage.d.ts.map +0 -1
- package/dist/refs/storage.js +0 -1023
- package/dist/refs/storage.js.map +0 -1
- package/dist/refs/tag.d.ts +0 -860
- package/dist/refs/tag.d.ts.map +0 -1
- package/dist/refs/tag.js +0 -996
- package/dist/refs/tag.js.map +0 -1
- package/dist/storage/backend.d.ts +0 -425
- package/dist/storage/backend.d.ts.map +0 -1
- package/dist/storage/backend.js +0 -41
- package/dist/storage/backend.js.map +0 -1
- package/dist/storage/fsx-adapter.d.ts +0 -204
- package/dist/storage/fsx-adapter.d.ts.map +0 -1
- package/dist/storage/fsx-adapter.js +0 -470
- package/dist/storage/fsx-adapter.js.map +0 -1
- package/dist/storage/lru-cache.d.ts +0 -691
- package/dist/storage/lru-cache.d.ts.map +0 -1
- package/dist/storage/lru-cache.js +0 -813
- package/dist/storage/lru-cache.js.map +0 -1
- package/dist/storage/object-index.d.ts +0 -585
- package/dist/storage/object-index.d.ts.map +0 -1
- package/dist/storage/object-index.js +0 -532
- package/dist/storage/object-index.js.map +0 -1
- package/dist/storage/r2-pack.d.ts +0 -1257
- package/dist/storage/r2-pack.d.ts.map +0 -1
- package/dist/storage/r2-pack.js +0 -1770
- package/dist/storage/r2-pack.js.map +0 -1
- package/dist/tiered/cdc-pipeline.d.ts +0 -1888
- package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
- package/dist/tiered/cdc-pipeline.js +0 -1880
- package/dist/tiered/cdc-pipeline.js.map +0 -1
- package/dist/tiered/migration.d.ts +0 -1104
- package/dist/tiered/migration.d.ts.map +0 -1
- package/dist/tiered/migration.js +0 -1214
- package/dist/tiered/migration.js.map +0 -1
- package/dist/tiered/parquet-writer.d.ts +0 -1145
- package/dist/tiered/parquet-writer.d.ts.map +0 -1
- package/dist/tiered/parquet-writer.js +0 -1183
- package/dist/tiered/parquet-writer.js.map +0 -1
- package/dist/tiered/read-path.d.ts +0 -835
- package/dist/tiered/read-path.d.ts.map +0 -1
- package/dist/tiered/read-path.js +0 -487
- package/dist/tiered/read-path.js.map +0 -1
- package/dist/types/capability.d.ts +0 -1385
- package/dist/types/capability.d.ts.map +0 -1
- package/dist/types/capability.js +0 -36
- package/dist/types/capability.js.map +0 -1
- package/dist/types/index.d.ts +0 -13
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -18
- package/dist/types/index.js.map +0 -1
- package/dist/types/objects.d.ts +0 -692
- package/dist/types/objects.d.ts.map +0 -1
- package/dist/types/objects.js +0 -837
- package/dist/types/objects.js.map +0 -1
- package/dist/types/storage.d.ts +0 -603
- package/dist/types/storage.d.ts.map +0 -1
- package/dist/types/storage.js +0 -191
- package/dist/types/storage.js.map +0 -1
- package/dist/types/worker-loader.d.ts +0 -60
- package/dist/types/worker-loader.d.ts.map +0 -1
- package/dist/types/worker-loader.js +0 -62
- package/dist/types/worker-loader.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -197
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -268
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/sha1.d.ts +0 -290
- package/dist/utils/sha1.d.ts.map +0 -1
- package/dist/utils/sha1.js +0 -582
- package/dist/utils/sha1.js.map +0 -1
- package/dist/wire/capabilities.d.ts +0 -1044
- package/dist/wire/capabilities.d.ts.map +0 -1
- package/dist/wire/capabilities.js +0 -941
- package/dist/wire/capabilities.js.map +0 -1
- package/dist/wire/path-security.d.ts +0 -157
- package/dist/wire/path-security.d.ts.map +0 -1
- package/dist/wire/path-security.js +0 -307
- package/dist/wire/path-security.js.map +0 -1
- package/dist/wire/pkt-line.d.ts +0 -345
- package/dist/wire/pkt-line.d.ts.map +0 -1
- package/dist/wire/pkt-line.js +0 -381
- package/dist/wire/pkt-line.js.map +0 -1
- package/dist/wire/receive-pack.d.ts +0 -1059
- package/dist/wire/receive-pack.d.ts.map +0 -1
- package/dist/wire/receive-pack.js +0 -1414
- package/dist/wire/receive-pack.js.map +0 -1
- package/dist/wire/smart-http.d.ts +0 -799
- package/dist/wire/smart-http.d.ts.map +0 -1
- package/dist/wire/smart-http.js +0 -945
- package/dist/wire/smart-http.js.map +0 -1
- package/dist/wire/upload-pack.d.ts +0 -727
- package/dist/wire/upload-pack.d.ts.map +0 -1
- package/dist/wire/upload-pack.js +0 -1138
- package/dist/wire/upload-pack.js.map +0 -1
package/dist/tiered/migration.js
DELETED
|
@@ -1,1214 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Tier Migration Module (Hot -> Warm)
|
|
3
|
-
*
|
|
4
|
-
* This module handles the migration of Git objects between storage tiers in the
|
|
5
|
-
* gitdo tiered storage architecture. It provides comprehensive functionality for:
|
|
6
|
-
*
|
|
7
|
-
* ## Storage Tiers
|
|
8
|
-
*
|
|
9
|
-
* - **Hot**: SQLite in Durable Object storage - fastest access, limited capacity
|
|
10
|
-
* - **Warm/R2**: Packed objects in R2 object storage - medium latency, larger capacity
|
|
11
|
-
*
|
|
12
|
-
* ## Key Features
|
|
13
|
-
*
|
|
14
|
-
* - **Policy-based Migration**: Configurable policies based on age, access frequency, and size
|
|
15
|
-
* - **Access Tracking**: Monitors object access patterns to inform migration decisions
|
|
16
|
-
* - **Atomic Operations**: Ensures data integrity during migration with rollback support
|
|
17
|
-
* - **Concurrent Access Handling**: Safe reads/writes during in-progress migrations
|
|
18
|
-
* - **Checksum Verification**: Optional integrity verification after migration
|
|
19
|
-
* - **Batch Migration**: Efficient bulk migration with configurable concurrency
|
|
20
|
-
*
|
|
21
|
-
* ## Migration Process
|
|
22
|
-
*
|
|
23
|
-
* 1. Acquire distributed lock on the object
|
|
24
|
-
* 2. Copy data from hot tier to warm tier
|
|
25
|
-
* 3. Verify data integrity (optional checksum verification)
|
|
26
|
-
* 4. Update object location index
|
|
27
|
-
* 5. Delete from hot tier
|
|
28
|
-
* 6. Release lock
|
|
29
|
-
*
|
|
30
|
-
* If any step fails, the migration is rolled back automatically.
|
|
31
|
-
*
|
|
32
|
-
* @module tiered/migration
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```typescript
|
|
36
|
-
* // Create a migrator
|
|
37
|
-
* const migrator = new TierMigrator(storage);
|
|
38
|
-
*
|
|
39
|
-
* // Define migration policy
|
|
40
|
-
* const policy: MigrationPolicy = {
|
|
41
|
-
* maxAgeInHot: 24 * 60 * 60 * 1000, // 24 hours
|
|
42
|
-
* minAccessCount: 5,
|
|
43
|
-
* maxHotSize: 100 * 1024 * 1024 // 100MB
|
|
44
|
-
* };
|
|
45
|
-
*
|
|
46
|
-
* // Find candidates and migrate
|
|
47
|
-
* const candidates = await migrator.findMigrationCandidates(policy);
|
|
48
|
-
* for (const sha of candidates) {
|
|
49
|
-
* await migrator.migrate(sha, 'hot', 'r2', { verifyChecksum: true });
|
|
50
|
-
* }
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
/**
|
|
54
|
-
* Error thrown during migration operations.
|
|
55
|
-
*
|
|
56
|
-
* @description
|
|
57
|
-
* Custom error class for migration failures with detailed information
|
|
58
|
-
* about the failure context. Also implements MigrationResult-like properties
|
|
59
|
-
* for compatibility with result handling code.
|
|
60
|
-
*
|
|
61
|
-
* Error codes:
|
|
62
|
-
* - `NOT_FOUND`: Object does not exist in source tier
|
|
63
|
-
* - `ALREADY_IN_TARGET`: Object is already in the target tier
|
|
64
|
-
* - `LOCK_TIMEOUT`: Could not acquire lock within timeout
|
|
65
|
-
* - `WRITE_FAILED`: Failed to write to target tier
|
|
66
|
-
* - `CHECKSUM_MISMATCH`: Data verification failed after migration
|
|
67
|
-
* - `UPDATE_FAILED`: Failed to update object index
|
|
68
|
-
*
|
|
69
|
-
* @example
|
|
70
|
-
* ```typescript
|
|
71
|
-
* try {
|
|
72
|
-
* await migrator.migrate(sha, 'hot', 'r2');
|
|
73
|
-
* } catch (error) {
|
|
74
|
-
* if (error instanceof MigrationError) {
|
|
75
|
-
* console.log(`Migration failed: ${error.code}`);
|
|
76
|
-
* console.log(`Object: ${error.sha}`);
|
|
77
|
-
* console.log(`${error.sourceTier} -> ${error.targetTier}`);
|
|
78
|
-
* }
|
|
79
|
-
* }
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
export class MigrationError extends Error {
|
|
83
|
-
code;
|
|
84
|
-
sha;
|
|
85
|
-
sourceTier;
|
|
86
|
-
targetTier;
|
|
87
|
-
cause;
|
|
88
|
-
/** Always false for error objects */
|
|
89
|
-
success = false;
|
|
90
|
-
/** Whether rollback was performed */
|
|
91
|
-
rolledBack = true;
|
|
92
|
-
/** Reason for rollback (from cause error) */
|
|
93
|
-
rollbackReason;
|
|
94
|
-
/**
|
|
95
|
-
* Creates a new MigrationError.
|
|
96
|
-
*
|
|
97
|
-
* @param message - Human-readable error message
|
|
98
|
-
* @param code - Error code for programmatic handling
|
|
99
|
-
* @param sha - SHA of the object being migrated
|
|
100
|
-
* @param sourceTier - Source storage tier
|
|
101
|
-
* @param targetTier - Target storage tier
|
|
102
|
-
* @param cause - Underlying error that caused this failure
|
|
103
|
-
*/
|
|
104
|
-
constructor(message, code, sha, sourceTier, targetTier, cause) {
|
|
105
|
-
super(message);
|
|
106
|
-
this.code = code;
|
|
107
|
-
this.sha = sha;
|
|
108
|
-
this.sourceTier = sourceTier;
|
|
109
|
-
this.targetTier = targetTier;
|
|
110
|
-
this.cause = cause;
|
|
111
|
-
this.name = 'MigrationError';
|
|
112
|
-
this.rollbackReason = cause?.message;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Returns this error as a MigrationError reference.
|
|
116
|
-
*
|
|
117
|
-
* @description
|
|
118
|
-
* Provides compatibility with MigrationResult.error property access.
|
|
119
|
-
*
|
|
120
|
-
* @returns This MigrationError instance
|
|
121
|
-
*/
|
|
122
|
-
get error() {
|
|
123
|
-
return this;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Rollback handler for failed migrations.
|
|
128
|
-
*
|
|
129
|
-
* @description
|
|
130
|
-
* Handles cleanup operations when a migration fails, ensuring
|
|
131
|
-
* that partial migrations don't leave the system in an inconsistent state.
|
|
132
|
-
*
|
|
133
|
-
* @example
|
|
134
|
-
* ```typescript
|
|
135
|
-
* const rollback = new MigrationRollback(storage);
|
|
136
|
-
* if (migrationFailed) {
|
|
137
|
-
* await rollback.rollback(job);
|
|
138
|
-
* }
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
export class MigrationRollback {
|
|
142
|
-
storage;
|
|
143
|
-
/**
|
|
144
|
-
* Creates a new MigrationRollback handler.
|
|
145
|
-
*
|
|
146
|
-
* @param storage - The tier storage implementation
|
|
147
|
-
*/
|
|
148
|
-
constructor(storage) {
|
|
149
|
-
this.storage = storage;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Rolls back a failed migration job.
|
|
153
|
-
*
|
|
154
|
-
* @description
|
|
155
|
-
* Cleans up any partial data in the warm tier and releases the lock.
|
|
156
|
-
* Updates the job state to 'rolled_back'.
|
|
157
|
-
*
|
|
158
|
-
* @param job - The migration job to roll back
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* ```typescript
|
|
162
|
-
* await rollback.rollback(failedJob);
|
|
163
|
-
* console.log(failedJob.state); // 'rolled_back'
|
|
164
|
-
* ```
|
|
165
|
-
*/
|
|
166
|
-
async rollback(job) {
|
|
167
|
-
// Clean up warm tier if data was written there
|
|
168
|
-
await this.storage.deleteFromWarm(job.sha);
|
|
169
|
-
// Release lock if held
|
|
170
|
-
if (job.lockAcquired) {
|
|
171
|
-
await this.storage.releaseLock(job.sha);
|
|
172
|
-
}
|
|
173
|
-
job.state = 'rolled_back';
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Handler for concurrent access during migration.
|
|
178
|
-
*
|
|
179
|
-
* @description
|
|
180
|
-
* Manages read and write operations that occur while an object
|
|
181
|
-
* is being migrated, ensuring data consistency.
|
|
182
|
-
*
|
|
183
|
-
* During migration:
|
|
184
|
-
* - Reads check hot tier first (data still there), then warm tier
|
|
185
|
-
* - Writes go to hot tier and may be queued for replay
|
|
186
|
-
*
|
|
187
|
-
* @example
|
|
188
|
-
* ```typescript
|
|
189
|
-
* const handler = new ConcurrentAccessHandler(storage);
|
|
190
|
-
*
|
|
191
|
-
* // Safe read during migration
|
|
192
|
-
* const data = await handler.handleRead(sha);
|
|
193
|
-
*
|
|
194
|
-
* // Safe write during migration
|
|
195
|
-
* await handler.handleWrite(sha, newData);
|
|
196
|
-
* ```
|
|
197
|
-
*/
|
|
198
|
-
export class ConcurrentAccessHandler {
|
|
199
|
-
storage;
|
|
200
|
-
/**
|
|
201
|
-
* Creates a new ConcurrentAccessHandler.
|
|
202
|
-
*
|
|
203
|
-
* @param storage - The tier storage implementation
|
|
204
|
-
*/
|
|
205
|
-
constructor(storage) {
|
|
206
|
-
this.storage = storage;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Handles a read operation during migration.
|
|
210
|
-
*
|
|
211
|
-
* @description
|
|
212
|
-
* Reads from hot tier first (data is still there during migration),
|
|
213
|
-
* then falls back to warm tier if not found.
|
|
214
|
-
*
|
|
215
|
-
* @param sha - The object SHA to read
|
|
216
|
-
*
|
|
217
|
-
* @returns Object data or null if not found
|
|
218
|
-
*
|
|
219
|
-
* @example
|
|
220
|
-
* ```typescript
|
|
221
|
-
* const data = await handler.handleRead(sha);
|
|
222
|
-
* if (data) {
|
|
223
|
-
* // Process the data
|
|
224
|
-
* }
|
|
225
|
-
* ```
|
|
226
|
-
*/
|
|
227
|
-
async handleRead(sha) {
|
|
228
|
-
// During migration, read from hot tier first (data is still there)
|
|
229
|
-
const data = await this.storage.getFromHot(sha);
|
|
230
|
-
if (data)
|
|
231
|
-
return data;
|
|
232
|
-
// Fall back to warm tier
|
|
233
|
-
return this.storage.getFromWarm(sha);
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Handles a write operation during migration.
|
|
237
|
-
*
|
|
238
|
-
* @description
|
|
239
|
-
* Writes to the hot tier. The TierMigrator will handle replaying
|
|
240
|
-
* pending writes after migration completes.
|
|
241
|
-
*
|
|
242
|
-
* @param sha - The object SHA to write
|
|
243
|
-
* @param data - The data to write
|
|
244
|
-
*
|
|
245
|
-
* @example
|
|
246
|
-
* ```typescript
|
|
247
|
-
* await handler.handleWrite(sha, newData);
|
|
248
|
-
* ```
|
|
249
|
-
*/
|
|
250
|
-
async handleWrite(sha, data) {
|
|
251
|
-
// Queue write - for now just write to hot tier
|
|
252
|
-
await this.storage.putToHot(sha, data);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Tracks access patterns for objects to inform migration decisions.
|
|
257
|
-
*
|
|
258
|
-
* @description
|
|
259
|
-
* Records and analyzes access patterns for objects in the storage system.
|
|
260
|
-
* This information is used to make intelligent decisions about which
|
|
261
|
-
* objects should be migrated between tiers.
|
|
262
|
-
*
|
|
263
|
-
* ## Features
|
|
264
|
-
*
|
|
265
|
-
* - Records read/write operations with optional metrics
|
|
266
|
-
* - Calculates access frequency over time
|
|
267
|
-
* - Identifies hot objects (frequently accessed)
|
|
268
|
-
* - Identifies cold objects (rarely accessed)
|
|
269
|
-
* - Supports access count decay for temporal relevance
|
|
270
|
-
* - Persists patterns to storage for durability
|
|
271
|
-
*
|
|
272
|
-
* @example
|
|
273
|
-
* ```typescript
|
|
274
|
-
* const tracker = new AccessTracker(storage);
|
|
275
|
-
*
|
|
276
|
-
* // Record accesses
|
|
277
|
-
* await tracker.recordAccess(sha, 'read', { bytesRead: 1024 });
|
|
278
|
-
* await tracker.recordAccess(sha, 'write');
|
|
279
|
-
*
|
|
280
|
-
* // Get access pattern for an object
|
|
281
|
-
* const pattern = await tracker.getAccessPattern(sha);
|
|
282
|
-
* console.log(`Frequency: ${pattern.accessFrequency}/sec`);
|
|
283
|
-
*
|
|
284
|
-
* // Find hot and cold objects
|
|
285
|
-
* const hotObjects = await tracker.identifyHotObjects({ minAccessCount: 50 });
|
|
286
|
-
* const coldObjects = await tracker.identifyColdObjects({ maxAccessCount: 2 });
|
|
287
|
-
*
|
|
288
|
-
* // Apply decay to gradually forget old patterns
|
|
289
|
-
* await tracker.applyDecay({ decayFactor: 0.5, minAgeForDecayMs: 86400000 });
|
|
290
|
-
* ```
|
|
291
|
-
*/
|
|
292
|
-
export class AccessTracker {
|
|
293
|
-
storage;
|
|
294
|
-
accessPatterns;
|
|
295
|
-
/**
|
|
296
|
-
* Creates a new AccessTracker.
|
|
297
|
-
*
|
|
298
|
-
* @param storage - The tier storage implementation
|
|
299
|
-
*
|
|
300
|
-
* @example
|
|
301
|
-
* ```typescript
|
|
302
|
-
* const tracker = new AccessTracker(storage);
|
|
303
|
-
* ```
|
|
304
|
-
*/
|
|
305
|
-
constructor(storage) {
|
|
306
|
-
this.storage = storage;
|
|
307
|
-
this.accessPatterns = new Map();
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Records an access operation for an object.
|
|
311
|
-
*
|
|
312
|
-
* @description
|
|
313
|
-
* Tracks a read or write operation, updating the access pattern
|
|
314
|
-
* for the object. Can include optional metrics like bytes read
|
|
315
|
-
* and latency.
|
|
316
|
-
*
|
|
317
|
-
* @param sha - The object SHA being accessed
|
|
318
|
-
* @param type - Type of access ('read' or 'write')
|
|
319
|
-
* @param metrics - Optional additional metrics
|
|
320
|
-
*
|
|
321
|
-
* @example
|
|
322
|
-
* ```typescript
|
|
323
|
-
* // Basic access recording
|
|
324
|
-
* await tracker.recordAccess(sha, 'read');
|
|
325
|
-
*
|
|
326
|
-
* // With metrics
|
|
327
|
-
* await tracker.recordAccess(sha, 'read', {
|
|
328
|
-
* bytesRead: 2048,
|
|
329
|
-
* latencyMs: 5
|
|
330
|
-
* });
|
|
331
|
-
* ```
|
|
332
|
-
*/
|
|
333
|
-
async recordAccess(sha, type, metrics) {
|
|
334
|
-
let pattern = this.accessPatterns.get(sha);
|
|
335
|
-
if (!pattern) {
|
|
336
|
-
pattern = {
|
|
337
|
-
readCount: 0,
|
|
338
|
-
writeCount: 0,
|
|
339
|
-
lastAccessedAt: Date.now(),
|
|
340
|
-
createdAt: Date.now(),
|
|
341
|
-
totalBytesRead: 0,
|
|
342
|
-
totalLatencyMs: 0,
|
|
343
|
-
accessCount: 0
|
|
344
|
-
};
|
|
345
|
-
this.accessPatterns.set(sha, pattern);
|
|
346
|
-
}
|
|
347
|
-
pattern.lastAccessedAt = Date.now();
|
|
348
|
-
pattern.accessCount++;
|
|
349
|
-
if (type === 'read') {
|
|
350
|
-
pattern.readCount++;
|
|
351
|
-
if (metrics?.bytesRead) {
|
|
352
|
-
pattern.totalBytesRead += metrics.bytesRead;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
pattern.writeCount++;
|
|
357
|
-
}
|
|
358
|
-
if (metrics?.latencyMs) {
|
|
359
|
-
pattern.totalLatencyMs += metrics.latencyMs;
|
|
360
|
-
}
|
|
361
|
-
// Also persist to storage for loadFromStorage to work
|
|
362
|
-
this.persistPattern(sha, pattern);
|
|
363
|
-
}
|
|
364
|
-
persistPattern(sha, pattern) {
|
|
365
|
-
// Store in a special key in the storage for persistence
|
|
366
|
-
void `access_pattern:${sha}` // Key reserved for future persistence implementation
|
|
367
|
-
;
|
|
368
|
-
this.storage.accessPatterns =
|
|
369
|
-
this.storage.accessPatterns || new Map();
|
|
370
|
-
(this.storage.accessPatterns).set(sha, pattern);
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Gets the access pattern for a specific object.
|
|
374
|
-
*
|
|
375
|
-
* @description
|
|
376
|
-
* Returns detailed access statistics for an object including
|
|
377
|
-
* read/write counts, access frequency, and average latency.
|
|
378
|
-
*
|
|
379
|
-
* @param sha - The object SHA to query
|
|
380
|
-
*
|
|
381
|
-
* @returns Access pattern for the object
|
|
382
|
-
*
|
|
383
|
-
* @example
|
|
384
|
-
* ```typescript
|
|
385
|
-
* const pattern = await tracker.getAccessPattern(sha);
|
|
386
|
-
* console.log(`Reads: ${pattern.readCount}`);
|
|
387
|
-
* console.log(`Writes: ${pattern.writeCount}`);
|
|
388
|
-
* console.log(`Frequency: ${pattern.accessFrequency.toFixed(2)}/sec`);
|
|
389
|
-
* ```
|
|
390
|
-
*/
|
|
391
|
-
async getAccessPattern(sha) {
|
|
392
|
-
const pattern = this.accessPatterns.get(sha);
|
|
393
|
-
const now = Date.now();
|
|
394
|
-
if (!pattern) {
|
|
395
|
-
return {
|
|
396
|
-
sha,
|
|
397
|
-
readCount: 0,
|
|
398
|
-
writeCount: 0,
|
|
399
|
-
lastAccessedAt: now,
|
|
400
|
-
accessFrequency: 0,
|
|
401
|
-
totalBytesRead: 0,
|
|
402
|
-
avgLatencyMs: 0
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
const durationMs = now - pattern.createdAt;
|
|
406
|
-
const accessFrequency = durationMs > 0
|
|
407
|
-
? (pattern.readCount + pattern.writeCount) / (durationMs / 1000)
|
|
408
|
-
: pattern.readCount + pattern.writeCount;
|
|
409
|
-
return {
|
|
410
|
-
sha,
|
|
411
|
-
readCount: pattern.readCount,
|
|
412
|
-
writeCount: pattern.writeCount,
|
|
413
|
-
lastAccessedAt: pattern.lastAccessedAt,
|
|
414
|
-
accessFrequency,
|
|
415
|
-
totalBytesRead: pattern.totalBytesRead,
|
|
416
|
-
avgLatencyMs: pattern.accessCount > 0 ? pattern.totalLatencyMs / pattern.accessCount : 0
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Identifies frequently accessed (hot) objects.
|
|
421
|
-
*
|
|
422
|
-
* @description
|
|
423
|
-
* Returns SHAs of objects that meet the hot object criteria,
|
|
424
|
-
* typically objects with high access counts.
|
|
425
|
-
*
|
|
426
|
-
* @param criteria - Criteria for identifying hot objects
|
|
427
|
-
*
|
|
428
|
-
* @returns Array of SHAs for hot objects
|
|
429
|
-
*
|
|
430
|
-
* @example
|
|
431
|
-
* ```typescript
|
|
432
|
-
* const hotObjects = await tracker.identifyHotObjects({
|
|
433
|
-
* minAccessCount: 100
|
|
434
|
-
* });
|
|
435
|
-
* console.log(`Found ${hotObjects.length} hot objects`);
|
|
436
|
-
* ```
|
|
437
|
-
*/
|
|
438
|
-
async identifyHotObjects(criteria) {
|
|
439
|
-
const hotObjects = [];
|
|
440
|
-
const minAccessCount = criteria.minAccessCount ?? 0;
|
|
441
|
-
for (const [sha, pattern] of this.accessPatterns) {
|
|
442
|
-
const totalAccesses = pattern.readCount + pattern.writeCount;
|
|
443
|
-
if (totalAccesses >= minAccessCount) {
|
|
444
|
-
hotObjects.push(sha);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return hotObjects;
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Identifies rarely accessed (cold) objects.
|
|
451
|
-
*
|
|
452
|
-
* @description
|
|
453
|
-
* Returns SHAs of objects that meet the cold object criteria,
|
|
454
|
-
* typically objects with low access counts.
|
|
455
|
-
*
|
|
456
|
-
* @param criteria - Criteria for identifying cold objects
|
|
457
|
-
*
|
|
458
|
-
* @returns Array of SHAs for cold objects
|
|
459
|
-
*
|
|
460
|
-
* @example
|
|
461
|
-
* ```typescript
|
|
462
|
-
* const coldObjects = await tracker.identifyColdObjects({
|
|
463
|
-
* maxAccessCount: 2,
|
|
464
|
-
* minAgeMs: 7 * 24 * 60 * 60 * 1000 // 7 days
|
|
465
|
-
* });
|
|
466
|
-
* console.log(`Found ${coldObjects.length} cold objects for migration`);
|
|
467
|
-
* ```
|
|
468
|
-
*/
|
|
469
|
-
async identifyColdObjects(criteria) {
|
|
470
|
-
const coldObjects = [];
|
|
471
|
-
const maxAccessCount = criteria.maxAccessCount ?? Infinity;
|
|
472
|
-
void Date.now(); // Reserved for time-based cold object identification
|
|
473
|
-
// Get all objects in hot storage
|
|
474
|
-
for (const sha of this.storage.hotObjects.keys()) {
|
|
475
|
-
const pattern = this.accessPatterns.get(sha);
|
|
476
|
-
const totalAccesses = pattern ? pattern.readCount + pattern.writeCount : 0;
|
|
477
|
-
if (totalAccesses <= maxAccessCount) {
|
|
478
|
-
coldObjects.push(sha);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
return coldObjects;
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Applies decay to access counts.
|
|
485
|
-
*
|
|
486
|
-
* @description
|
|
487
|
-
* Reduces access counts by a factor to gradually "forget" old access
|
|
488
|
-
* patterns. This helps the system respond to changing usage patterns
|
|
489
|
-
* over time.
|
|
490
|
-
*
|
|
491
|
-
* @param options - Decay configuration options
|
|
492
|
-
*
|
|
493
|
-
* @example
|
|
494
|
-
* ```typescript
|
|
495
|
-
* // Run daily to decay access counts by 50%
|
|
496
|
-
* await tracker.applyDecay({
|
|
497
|
-
* decayFactor: 0.5,
|
|
498
|
-
* minAgeForDecayMs: 0 // Apply to all
|
|
499
|
-
* });
|
|
500
|
-
* ```
|
|
501
|
-
*/
|
|
502
|
-
async applyDecay(options) {
|
|
503
|
-
const { decayFactor } = options;
|
|
504
|
-
for (const [_sha, pattern] of this.accessPatterns) {
|
|
505
|
-
pattern.readCount = Math.floor(pattern.readCount * decayFactor);
|
|
506
|
-
pattern.writeCount = Math.floor(pattern.writeCount * decayFactor);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Gets aggregate access statistics.
|
|
511
|
-
*
|
|
512
|
-
* @description
|
|
513
|
-
* Returns summary statistics about access patterns across all
|
|
514
|
-
* tracked objects.
|
|
515
|
-
*
|
|
516
|
-
* @returns Aggregate access statistics
|
|
517
|
-
*
|
|
518
|
-
* @example
|
|
519
|
-
* ```typescript
|
|
520
|
-
* const stats = await tracker.getAccessStats();
|
|
521
|
-
* console.log(`Total reads: ${stats.totalReads}`);
|
|
522
|
-
* console.log(`Total writes: ${stats.totalWrites}`);
|
|
523
|
-
* console.log(`Unique objects: ${stats.uniqueObjectsAccessed}`);
|
|
524
|
-
* ```
|
|
525
|
-
*/
|
|
526
|
-
async getAccessStats() {
|
|
527
|
-
let totalReads = 0;
|
|
528
|
-
let totalWrites = 0;
|
|
529
|
-
const uniqueObjects = new Set();
|
|
530
|
-
for (const [sha, pattern] of this.accessPatterns) {
|
|
531
|
-
totalReads += pattern.readCount;
|
|
532
|
-
totalWrites += pattern.writeCount;
|
|
533
|
-
if (pattern.readCount > 0 || pattern.writeCount > 0) {
|
|
534
|
-
uniqueObjects.add(sha);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
return {
|
|
538
|
-
totalReads,
|
|
539
|
-
totalWrites,
|
|
540
|
-
uniqueObjectsAccessed: uniqueObjects.size
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* Loads persisted access patterns from storage.
|
|
545
|
-
*
|
|
546
|
-
* @description
|
|
547
|
-
* Restores access patterns that were previously persisted,
|
|
548
|
-
* useful for recovering state after a restart.
|
|
549
|
-
*
|
|
550
|
-
* @example
|
|
551
|
-
* ```typescript
|
|
552
|
-
* // On startup, restore access patterns
|
|
553
|
-
* await tracker.loadFromStorage();
|
|
554
|
-
* ```
|
|
555
|
-
*/
|
|
556
|
-
async loadFromStorage() {
|
|
557
|
-
// Load persisted access patterns from storage
|
|
558
|
-
const storedPatterns = this.storage.accessPatterns;
|
|
559
|
-
if (storedPatterns) {
|
|
560
|
-
for (const [sha, pattern] of storedPatterns) {
|
|
561
|
-
this.accessPatterns.set(sha, pattern);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Main tier migration service.
|
|
568
|
-
*
|
|
569
|
-
* @description
|
|
570
|
-
* Orchestrates the migration of Git objects between storage tiers.
|
|
571
|
-
* Provides both synchronous single-object migration and asynchronous
|
|
572
|
-
* job-based migration for long-running operations.
|
|
573
|
-
*
|
|
574
|
-
* ## Migration Process
|
|
575
|
-
*
|
|
576
|
-
* 1. Validate object exists and is not already in target tier
|
|
577
|
-
* 2. Acquire distributed lock with configurable timeout
|
|
578
|
-
* 3. Read data from source tier
|
|
579
|
-
* 4. Optionally compute source checksum
|
|
580
|
-
* 5. Write data to target tier
|
|
581
|
-
* 6. Optionally verify checksum matches
|
|
582
|
-
* 7. Update object index to point to new location
|
|
583
|
-
* 8. Delete from source tier
|
|
584
|
-
* 9. Release lock
|
|
585
|
-
*
|
|
586
|
-
* If any step fails, the migration is automatically rolled back.
|
|
587
|
-
*
|
|
588
|
-
* @example
|
|
589
|
-
* ```typescript
|
|
590
|
-
* const migrator = new TierMigrator(storage);
|
|
591
|
-
*
|
|
592
|
-
* // Simple migration
|
|
593
|
-
* const result = await migrator.migrate(sha, 'hot', 'r2');
|
|
594
|
-
* if (result.success) {
|
|
595
|
-
* console.log('Migration successful');
|
|
596
|
-
* }
|
|
597
|
-
*
|
|
598
|
-
* // Migration with verification
|
|
599
|
-
* const verifiedResult = await migrator.migrate(sha, 'hot', 'r2', {
|
|
600
|
-
* verifyChecksum: true,
|
|
601
|
-
* lockTimeout: 10000
|
|
602
|
-
* });
|
|
603
|
-
*
|
|
604
|
-
* // Batch migration
|
|
605
|
-
* const batchResult = await migrator.migrateBatch(shas, 'hot', 'r2', {
|
|
606
|
-
* concurrency: 5
|
|
607
|
-
* });
|
|
608
|
-
* console.log(`Migrated: ${batchResult.successful.length}`);
|
|
609
|
-
*
|
|
610
|
-
* // Long-running migration job
|
|
611
|
-
* const job = await migrator.startMigrationJob(largeSha, 'hot', 'r2');
|
|
612
|
-
* // ... later
|
|
613
|
-
* await migrator.completeMigrationJob(job);
|
|
614
|
-
* ```
|
|
615
|
-
*/
|
|
616
|
-
export class TierMigrator {
|
|
617
|
-
storage;
|
|
618
|
-
activeJobs;
|
|
619
|
-
migrationHistory;
|
|
620
|
-
migratingObjects;
|
|
621
|
-
pendingWrites;
|
|
622
|
-
/**
|
|
623
|
-
* Creates a new TierMigrator.
|
|
624
|
-
*
|
|
625
|
-
* @param storage - The tier storage implementation
|
|
626
|
-
*
|
|
627
|
-
* @example
|
|
628
|
-
* ```typescript
|
|
629
|
-
* const migrator = new TierMigrator(storage);
|
|
630
|
-
* ```
|
|
631
|
-
*/
|
|
632
|
-
constructor(storage) {
|
|
633
|
-
this.storage = storage;
|
|
634
|
-
this.activeJobs = new Map();
|
|
635
|
-
this.migrationHistory = new Map();
|
|
636
|
-
this.migratingObjects = new Set();
|
|
637
|
-
this.pendingWrites = new Map();
|
|
638
|
-
// checksumCache reserved for integrity verification during migration
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Finds objects that are candidates for migration based on policy.
|
|
642
|
-
*
|
|
643
|
-
* @description
|
|
644
|
-
* Analyzes objects in the hot tier and returns those that meet
|
|
645
|
-
* the migration criteria defined in the policy. Results are sorted
|
|
646
|
-
* by last access time (oldest first).
|
|
647
|
-
*
|
|
648
|
-
* @param policy - Migration policy defining criteria
|
|
649
|
-
*
|
|
650
|
-
* @returns Array of SHAs that are candidates for migration
|
|
651
|
-
*
|
|
652
|
-
* @example
|
|
653
|
-
* ```typescript
|
|
654
|
-
* const candidates = await migrator.findMigrationCandidates({
|
|
655
|
-
* maxAgeInHot: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
656
|
-
* minAccessCount: 5,
|
|
657
|
-
* maxHotSize: 100 * 1024 * 1024
|
|
658
|
-
* });
|
|
659
|
-
*
|
|
660
|
-
* console.log(`Found ${candidates.length} candidates for migration`);
|
|
661
|
-
* ```
|
|
662
|
-
*/
|
|
663
|
-
async findMigrationCandidates(policy) {
|
|
664
|
-
const now = Date.now();
|
|
665
|
-
const candidates = [];
|
|
666
|
-
let totalHotSize = 0;
|
|
667
|
-
// Calculate total hot size and gather candidate info
|
|
668
|
-
for (const [sha, obj] of this.storage.hotObjects) {
|
|
669
|
-
totalHotSize += obj.data.length;
|
|
670
|
-
candidates.push({ sha, accessedAt: obj.accessedAt, size: obj.data.length });
|
|
671
|
-
}
|
|
672
|
-
// Count accesses per object from access log
|
|
673
|
-
const accessCounts = new Map();
|
|
674
|
-
for (const entry of this.storage.getAccessLog()) {
|
|
675
|
-
const count = accessCounts.get(entry.sha) ?? 0;
|
|
676
|
-
accessCounts.set(entry.sha, count + 1);
|
|
677
|
-
}
|
|
678
|
-
// Filter candidates based on policy
|
|
679
|
-
const filtered = candidates.filter(({ sha, accessedAt }) => {
|
|
680
|
-
const age = now - accessedAt;
|
|
681
|
-
const accessCount = accessCounts.get(sha) ?? 0;
|
|
682
|
-
// Age-based check
|
|
683
|
-
const isOld = age > policy.maxAgeInHot;
|
|
684
|
-
// Access frequency check
|
|
685
|
-
const isInfrequent = accessCount < policy.minAccessCount;
|
|
686
|
-
// If maxAgeInHot is Infinity and minAccessCount is 0, only use size policy
|
|
687
|
-
if (policy.maxAgeInHot === Infinity && policy.minAccessCount === 0) {
|
|
688
|
-
return totalHotSize > policy.maxHotSize;
|
|
689
|
-
}
|
|
690
|
-
// If maxAgeInHot is Infinity, only use access count
|
|
691
|
-
if (policy.maxAgeInHot === Infinity) {
|
|
692
|
-
return isInfrequent;
|
|
693
|
-
}
|
|
694
|
-
// If minAccessCount is 0, only use age
|
|
695
|
-
if (policy.minAccessCount === 0) {
|
|
696
|
-
return isOld;
|
|
697
|
-
}
|
|
698
|
-
// Both criteria must be met
|
|
699
|
-
return isOld && isInfrequent;
|
|
700
|
-
});
|
|
701
|
-
// Sort by accessedAt (oldest first) for priority
|
|
702
|
-
filtered.sort((a, b) => a.accessedAt - b.accessedAt);
|
|
703
|
-
return filtered.map(c => c.sha);
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Migrates a single object between tiers.
|
|
707
|
-
*
|
|
708
|
-
* @description
|
|
709
|
-
* Performs a complete migration of an object from the source tier
|
|
710
|
-
* to the target tier. Handles locking, data transfer, verification,
|
|
711
|
-
* and cleanup.
|
|
712
|
-
*
|
|
713
|
-
* @param sha - The object SHA to migrate
|
|
714
|
-
* @param sourceTier - The source storage tier
|
|
715
|
-
* @param targetTier - The target storage tier
|
|
716
|
-
* @param options - Optional migration settings
|
|
717
|
-
*
|
|
718
|
-
* @returns Migration result with success/failure status
|
|
719
|
-
*
|
|
720
|
-
* @throws {MigrationError} If object not found or already in target tier
|
|
721
|
-
*
|
|
722
|
-
* @example
|
|
723
|
-
* ```typescript
|
|
724
|
-
* // Basic migration
|
|
725
|
-
* const result = await migrator.migrate(sha, 'hot', 'r2');
|
|
726
|
-
*
|
|
727
|
-
* // With checksum verification
|
|
728
|
-
* const verified = await migrator.migrate(sha, 'hot', 'r2', {
|
|
729
|
-
* verifyChecksum: true,
|
|
730
|
-
* lockTimeout: 10000
|
|
731
|
-
* });
|
|
732
|
-
*
|
|
733
|
-
* if (verified.success) {
|
|
734
|
-
* console.log('Migration successful');
|
|
735
|
-
* if (verified.checksumVerified) {
|
|
736
|
-
* console.log('Integrity verified');
|
|
737
|
-
* }
|
|
738
|
-
* } else if (verified.rolledBack) {
|
|
739
|
-
* console.log(`Rolled back: ${verified.rollbackReason}`);
|
|
740
|
-
* }
|
|
741
|
-
* ```
|
|
742
|
-
*/
|
|
743
|
-
async migrate(sha, sourceTier, targetTier, options) {
|
|
744
|
-
// Check if object exists
|
|
745
|
-
const location = await this.storage.getLocation(sha);
|
|
746
|
-
if (!location) {
|
|
747
|
-
throw new MigrationError(`Object ${sha} not found`, 'NOT_FOUND', sha, sourceTier, targetTier);
|
|
748
|
-
}
|
|
749
|
-
// Check if already in target tier
|
|
750
|
-
if (location.tier === targetTier) {
|
|
751
|
-
throw new MigrationError(`Object ${sha} already in ${targetTier} tier`, 'ALREADY_IN_TARGET', sha, sourceTier, targetTier);
|
|
752
|
-
}
|
|
753
|
-
// Check if already migrating
|
|
754
|
-
if (this.migratingObjects.has(sha)) {
|
|
755
|
-
return { success: false, skipped: true };
|
|
756
|
-
}
|
|
757
|
-
// Try to acquire lock with timeout
|
|
758
|
-
const lockTimeout = options?.lockTimeout ?? 5000;
|
|
759
|
-
const startTime = Date.now();
|
|
760
|
-
let lockAcquired = false;
|
|
761
|
-
while (!lockAcquired && (Date.now() - startTime) < lockTimeout) {
|
|
762
|
-
lockAcquired = await this.storage.acquireLock(sha);
|
|
763
|
-
if (!lockAcquired) {
|
|
764
|
-
await new Promise(r => setTimeout(r, 10));
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
if (!lockAcquired) {
|
|
768
|
-
return {
|
|
769
|
-
success: false,
|
|
770
|
-
error: new MigrationError(`Failed to acquire lock for ${sha}`, 'LOCK_TIMEOUT', sha, sourceTier, targetTier)
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
this.migratingObjects.add(sha);
|
|
774
|
-
try {
|
|
775
|
-
// Re-check if object was already migrated while we were waiting for the lock
|
|
776
|
-
const currentLocation = await this.storage.getLocation(sha);
|
|
777
|
-
if (currentLocation?.tier === targetTier) {
|
|
778
|
-
// Another migration completed while we waited - return skipped
|
|
779
|
-
return { success: false, skipped: true };
|
|
780
|
-
}
|
|
781
|
-
// Get data from hot tier
|
|
782
|
-
const data = await this.storage.getFromHot(sha);
|
|
783
|
-
if (!data) {
|
|
784
|
-
// Data was deleted by another migration that completed
|
|
785
|
-
return { success: false, skipped: true };
|
|
786
|
-
}
|
|
787
|
-
// Compute checksum before transfer if verification is requested
|
|
788
|
-
let sourceChecksum;
|
|
789
|
-
if (options?.verifyChecksum) {
|
|
790
|
-
sourceChecksum = await this.computeChecksum(data);
|
|
791
|
-
}
|
|
792
|
-
// Write to warm tier
|
|
793
|
-
const packId = `pack-${Date.now()}`;
|
|
794
|
-
const offset = 0;
|
|
795
|
-
try {
|
|
796
|
-
await this.storage.putToWarm(sha, packId, offset, data);
|
|
797
|
-
}
|
|
798
|
-
catch (error) {
|
|
799
|
-
// Rollback: ensure hot tier data is preserved and clean up any orphaned warm data
|
|
800
|
-
try {
|
|
801
|
-
await this.storage.deleteFromWarm(sha);
|
|
802
|
-
}
|
|
803
|
-
catch (cleanupError) {
|
|
804
|
-
// Ignore cleanup errors - hot data is still preserved
|
|
805
|
-
}
|
|
806
|
-
this.recordHistory(sha, sourceTier, targetTier, 'rolled_back');
|
|
807
|
-
const migrationError = new MigrationError(`Failed to write to warm tier: ${error.message}`, 'WRITE_FAILED', sha, sourceTier, targetTier, error);
|
|
808
|
-
return {
|
|
809
|
-
success: false,
|
|
810
|
-
rolledBack: true,
|
|
811
|
-
error: migrationError,
|
|
812
|
-
rollbackReason: error.message
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
// Verify checksum after transfer
|
|
816
|
-
if (options?.verifyChecksum) {
|
|
817
|
-
const migratedData = await this.storage.getFromWarm(sha);
|
|
818
|
-
if (migratedData) {
|
|
819
|
-
const targetChecksum = await this.computeChecksum(migratedData);
|
|
820
|
-
// Validate checksum format - should be valid hex
|
|
821
|
-
// If checksum is clearly invalid (like 'corrupted'), fail the verification
|
|
822
|
-
const isValidChecksum = /^-?[0-9a-f]+$/i.test(sourceChecksum || '') &&
|
|
823
|
-
/^-?[0-9a-f]+$/i.test(targetChecksum);
|
|
824
|
-
if (!isValidChecksum || sourceChecksum !== targetChecksum) {
|
|
825
|
-
// Cleanup warm tier
|
|
826
|
-
await this.storage.deleteFromWarm(sha);
|
|
827
|
-
return {
|
|
828
|
-
success: false,
|
|
829
|
-
checksumVerified: false,
|
|
830
|
-
error: new MigrationError('Checksum mismatch after migration', 'CHECKSUM_MISMATCH', sha, sourceTier, targetTier)
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
// Update location
|
|
836
|
-
try {
|
|
837
|
-
await this.storage.updateLocation(sha, { tier: targetTier, packId, offset });
|
|
838
|
-
}
|
|
839
|
-
catch (error) {
|
|
840
|
-
// Rollback: clean up warm tier
|
|
841
|
-
await this.storage.deleteFromWarm(sha).catch(() => { });
|
|
842
|
-
this.recordHistory(sha, sourceTier, targetTier, 'rolled_back');
|
|
843
|
-
return {
|
|
844
|
-
success: false,
|
|
845
|
-
rolledBack: true,
|
|
846
|
-
error: new MigrationError(`Failed to update location: ${error.message}`, 'UPDATE_FAILED', sha, sourceTier, targetTier, error),
|
|
847
|
-
rollbackReason: error.message
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
// Delete from hot tier
|
|
851
|
-
await this.storage.deleteFromHot(sha);
|
|
852
|
-
this.recordHistory(sha, sourceTier, targetTier, 'completed');
|
|
853
|
-
return {
|
|
854
|
-
success: true,
|
|
855
|
-
checksumVerified: options?.verifyChecksum ? true : undefined
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
catch (error) {
|
|
859
|
-
this.recordHistory(sha, sourceTier, targetTier, 'failed');
|
|
860
|
-
throw error;
|
|
861
|
-
}
|
|
862
|
-
finally {
|
|
863
|
-
this.migratingObjects.delete(sha);
|
|
864
|
-
await this.storage.releaseLock(sha);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
recordHistory(sha, sourceTier, targetTier, state) {
|
|
868
|
-
const history = this.migrationHistory.get(sha) ?? [];
|
|
869
|
-
history.push({
|
|
870
|
-
sha,
|
|
871
|
-
sourceTier,
|
|
872
|
-
targetTier,
|
|
873
|
-
state,
|
|
874
|
-
timestamp: Date.now()
|
|
875
|
-
});
|
|
876
|
-
this.migrationHistory.set(sha, history);
|
|
877
|
-
}
|
|
878
|
-
/**
|
|
879
|
-
* Starts a long-running migration job.
|
|
880
|
-
*
|
|
881
|
-
* @description
|
|
882
|
-
* Initiates a migration job that can be monitored and completed
|
|
883
|
-
* asynchronously. Useful for large objects where progress tracking
|
|
884
|
-
* is important.
|
|
885
|
-
*
|
|
886
|
-
* @param sha - The object SHA to migrate
|
|
887
|
-
* @param sourceTier - The source storage tier
|
|
888
|
-
* @param targetTier - The target storage tier
|
|
889
|
-
*
|
|
890
|
-
* @returns The migration job with tracking information
|
|
891
|
-
*
|
|
892
|
-
* @example
|
|
893
|
-
* ```typescript
|
|
894
|
-
* const job = await migrator.startMigrationJob(largeSha, 'hot', 'r2');
|
|
895
|
-
* console.log(`Job ${job.id} started`);
|
|
896
|
-
* console.log(`Progress: ${job.progress.bytesTransferred}/${job.progress.totalBytes}`);
|
|
897
|
-
*
|
|
898
|
-
* // Complete the job when ready
|
|
899
|
-
* await migrator.completeMigrationJob(job);
|
|
900
|
-
* ```
|
|
901
|
-
*/
|
|
902
|
-
async startMigrationJob(sha, sourceTier, targetTier) {
|
|
903
|
-
// Acquire lock
|
|
904
|
-
const lockAcquired = await this.storage.acquireLock(sha);
|
|
905
|
-
// Get object size
|
|
906
|
-
const hotObj = this.storage.hotObjects.get(sha);
|
|
907
|
-
const totalBytes = hotObj?.data.length ?? 0;
|
|
908
|
-
const job = {
|
|
909
|
-
id: `job-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
910
|
-
sha,
|
|
911
|
-
sourceTier,
|
|
912
|
-
targetTier,
|
|
913
|
-
state: 'in_progress',
|
|
914
|
-
lockAcquired,
|
|
915
|
-
progress: {
|
|
916
|
-
bytesTransferred: 0,
|
|
917
|
-
totalBytes
|
|
918
|
-
},
|
|
919
|
-
startedAt: Date.now()
|
|
920
|
-
};
|
|
921
|
-
this.activeJobs.set(job.id, job);
|
|
922
|
-
this.migratingObjects.add(sha);
|
|
923
|
-
// Start the actual data copy in the background
|
|
924
|
-
if (hotObj) {
|
|
925
|
-
const packId = `pack-${Date.now()}`;
|
|
926
|
-
const offset = 0;
|
|
927
|
-
// Copy data to warm tier but don't delete from hot yet
|
|
928
|
-
await this.storage.putToWarm(sha, packId, offset, hotObj.data);
|
|
929
|
-
job.progress.bytesTransferred = hotObj.data.length;
|
|
930
|
-
job.packId = packId;
|
|
931
|
-
job.offset = offset;
|
|
932
|
-
}
|
|
933
|
-
return job;
|
|
934
|
-
}
|
|
935
|
-
/**
|
|
936
|
-
* Completes a migration job.
|
|
937
|
-
*
|
|
938
|
-
* @description
|
|
939
|
-
* Finalizes a migration job by updating the index and cleaning up
|
|
940
|
-
* the source tier. Also processes any pending writes that occurred
|
|
941
|
-
* during the migration.
|
|
942
|
-
*
|
|
943
|
-
* @param job - The migration job to complete
|
|
944
|
-
*
|
|
945
|
-
* @example
|
|
946
|
-
* ```typescript
|
|
947
|
-
* const job = await migrator.startMigrationJob(sha, 'hot', 'r2');
|
|
948
|
-
* // ... wait for progress or do other work
|
|
949
|
-
* await migrator.completeMigrationJob(job);
|
|
950
|
-
* console.log(`Job completed at ${new Date(job.completedAt!)}`);
|
|
951
|
-
* ```
|
|
952
|
-
*/
|
|
953
|
-
async completeMigrationJob(job) {
|
|
954
|
-
const jobWithMeta = job;
|
|
955
|
-
// Update location
|
|
956
|
-
await this.storage.updateLocation(job.sha, {
|
|
957
|
-
tier: job.targetTier,
|
|
958
|
-
packId: jobWithMeta.packId,
|
|
959
|
-
offset: jobWithMeta.offset
|
|
960
|
-
});
|
|
961
|
-
// Delete from hot tier
|
|
962
|
-
await this.storage.deleteFromHot(job.sha);
|
|
963
|
-
// Release lock
|
|
964
|
-
if (job.lockAcquired) {
|
|
965
|
-
await this.storage.releaseLock(job.sha);
|
|
966
|
-
}
|
|
967
|
-
job.state = 'completed';
|
|
968
|
-
job.completedAt = Date.now();
|
|
969
|
-
this.migratingObjects.delete(job.sha);
|
|
970
|
-
this.activeJobs.delete(job.id);
|
|
971
|
-
// Process any pending writes
|
|
972
|
-
const pending = this.pendingWrites.get(job.sha);
|
|
973
|
-
if (pending) {
|
|
974
|
-
for (const p of pending) {
|
|
975
|
-
await this.storage.putToWarm(job.sha, jobWithMeta.packId, jobWithMeta.offset, p.data); // Actually write the data
|
|
976
|
-
p.resolve();
|
|
977
|
-
}
|
|
978
|
-
this.pendingWrites.delete(job.sha);
|
|
979
|
-
}
|
|
980
|
-
this.recordHistory(job.sha, job.sourceTier, job.targetTier, 'completed');
|
|
981
|
-
}
|
|
982
|
-
/**
|
|
983
|
-
* Rolls back a migration job.
|
|
984
|
-
*
|
|
985
|
-
* @description
|
|
986
|
-
* Cancels a migration job and cleans up any partial data in the
|
|
987
|
-
* target tier.
|
|
988
|
-
*
|
|
989
|
-
* @param job - The migration job to roll back
|
|
990
|
-
*
|
|
991
|
-
* @example
|
|
992
|
-
* ```typescript
|
|
993
|
-
* const job = await migrator.startMigrationJob(sha, 'hot', 'r2');
|
|
994
|
-
* if (someCondition) {
|
|
995
|
-
* await migrator.rollbackMigrationJob(job);
|
|
996
|
-
* console.log('Migration rolled back');
|
|
997
|
-
* }
|
|
998
|
-
* ```
|
|
999
|
-
*/
|
|
1000
|
-
async rollbackMigrationJob(job) {
|
|
1001
|
-
// Clean up warm tier
|
|
1002
|
-
await this.storage.deleteFromWarm(job.sha);
|
|
1003
|
-
// Release lock
|
|
1004
|
-
if (job.lockAcquired) {
|
|
1005
|
-
await this.storage.releaseLock(job.sha);
|
|
1006
|
-
}
|
|
1007
|
-
job.state = 'rolled_back';
|
|
1008
|
-
this.migratingObjects.delete(job.sha);
|
|
1009
|
-
this.activeJobs.delete(job.id);
|
|
1010
|
-
this.recordHistory(job.sha, job.sourceTier, job.targetTier, 'rolled_back');
|
|
1011
|
-
}
|
|
1012
|
-
/**
|
|
1013
|
-
* Cancels a migration job by ID.
|
|
1014
|
-
*
|
|
1015
|
-
* @description
|
|
1016
|
-
* Stops a migration job and cleans up resources.
|
|
1017
|
-
*
|
|
1018
|
-
* @param jobId - The job ID to cancel
|
|
1019
|
-
*
|
|
1020
|
-
* @example
|
|
1021
|
-
* ```typescript
|
|
1022
|
-
* const job = await migrator.startMigrationJob(sha, 'hot', 'r2');
|
|
1023
|
-
* // Later...
|
|
1024
|
-
* await migrator.cancelMigrationJob(job.id);
|
|
1025
|
-
* ```
|
|
1026
|
-
*/
|
|
1027
|
-
async cancelMigrationJob(jobId) {
|
|
1028
|
-
const job = this.activeJobs.get(jobId);
|
|
1029
|
-
if (!job)
|
|
1030
|
-
return;
|
|
1031
|
-
// Clean up warm tier
|
|
1032
|
-
await this.storage.deleteFromWarm(job.sha);
|
|
1033
|
-
// Release lock
|
|
1034
|
-
if (job.lockAcquired) {
|
|
1035
|
-
await this.storage.releaseLock(job.sha);
|
|
1036
|
-
}
|
|
1037
|
-
job.state = 'cancelled';
|
|
1038
|
-
this.migratingObjects.delete(job.sha);
|
|
1039
|
-
this.activeJobs.delete(jobId);
|
|
1040
|
-
}
|
|
1041
|
-
/**
|
|
1042
|
-
* Gets all active migration jobs.
|
|
1043
|
-
*
|
|
1044
|
-
* @description
|
|
1045
|
-
* Returns jobs that are currently in progress.
|
|
1046
|
-
*
|
|
1047
|
-
* @returns Array of active migration jobs
|
|
1048
|
-
*
|
|
1049
|
-
* @example
|
|
1050
|
-
* ```typescript
|
|
1051
|
-
* const activeJobs = await migrator.getActiveMigrationJobs();
|
|
1052
|
-
* for (const job of activeJobs) {
|
|
1053
|
-
* console.log(`${job.id}: ${job.sha} - ${job.progress.bytesTransferred}/${job.progress.totalBytes}`);
|
|
1054
|
-
* }
|
|
1055
|
-
* ```
|
|
1056
|
-
*/
|
|
1057
|
-
async getActiveMigrationJobs() {
|
|
1058
|
-
return Array.from(this.activeJobs.values()).filter(j => j.state === 'in_progress');
|
|
1059
|
-
}
|
|
1060
|
-
/**
|
|
1061
|
-
* Gets migration history for an object.
|
|
1062
|
-
*
|
|
1063
|
-
* @description
|
|
1064
|
-
* Returns the history of migration events for a specific object.
|
|
1065
|
-
*
|
|
1066
|
-
* @param sha - The object SHA to query
|
|
1067
|
-
*
|
|
1068
|
-
* @returns Array of migration history entries
|
|
1069
|
-
*
|
|
1070
|
-
* @example
|
|
1071
|
-
* ```typescript
|
|
1072
|
-
* const history = await migrator.getMigrationHistory(sha);
|
|
1073
|
-
* for (const entry of history) {
|
|
1074
|
-
* console.log(`${new Date(entry.timestamp)}: ${entry.state}`);
|
|
1075
|
-
* }
|
|
1076
|
-
* ```
|
|
1077
|
-
*/
|
|
1078
|
-
async getMigrationHistory(sha) {
|
|
1079
|
-
return this.migrationHistory.get(sha) ?? [];
|
|
1080
|
-
}
|
|
1081
|
-
/**
|
|
1082
|
-
* Migrates multiple objects in a batch.
|
|
1083
|
-
*
|
|
1084
|
-
* @description
|
|
1085
|
-
* Efficiently migrates multiple objects with configurable concurrency.
|
|
1086
|
-
* Failed migrations don't affect other objects in the batch.
|
|
1087
|
-
*
|
|
1088
|
-
* @param shas - Array of object SHAs to migrate
|
|
1089
|
-
* @param sourceTier - The source storage tier
|
|
1090
|
-
* @param targetTier - The target storage tier
|
|
1091
|
-
* @param options - Optional batch migration settings
|
|
1092
|
-
*
|
|
1093
|
-
* @returns Result with successful and failed SHAs
|
|
1094
|
-
*
|
|
1095
|
-
* @example
|
|
1096
|
-
* ```typescript
|
|
1097
|
-
* const result = await migrator.migrateBatch(
|
|
1098
|
-
* candidates,
|
|
1099
|
-
* 'hot',
|
|
1100
|
-
* 'r2',
|
|
1101
|
-
* { concurrency: 5 }
|
|
1102
|
-
* );
|
|
1103
|
-
*
|
|
1104
|
-
* console.log(`Migrated: ${result.successful.length}`);
|
|
1105
|
-
* console.log(`Failed: ${result.failed.length}`);
|
|
1106
|
-
* ```
|
|
1107
|
-
*/
|
|
1108
|
-
async migrateBatch(shas, sourceTier, targetTier, options) {
|
|
1109
|
-
const concurrency = options?.concurrency ?? shas.length;
|
|
1110
|
-
const successful = [];
|
|
1111
|
-
const failed = [];
|
|
1112
|
-
// Process in batches based on concurrency
|
|
1113
|
-
const batches = [];
|
|
1114
|
-
for (let i = 0; i < shas.length; i += concurrency) {
|
|
1115
|
-
batches.push(shas.slice(i, i + concurrency));
|
|
1116
|
-
}
|
|
1117
|
-
for (const batch of batches) {
|
|
1118
|
-
await Promise.allSettled(batch.map(async (sha) => {
|
|
1119
|
-
try {
|
|
1120
|
-
const result = await this.migrate(sha, sourceTier, targetTier);
|
|
1121
|
-
if (result.success) {
|
|
1122
|
-
successful.push(sha);
|
|
1123
|
-
}
|
|
1124
|
-
else {
|
|
1125
|
-
failed.push(sha);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
catch (error) {
|
|
1129
|
-
failed.push(sha);
|
|
1130
|
-
}
|
|
1131
|
-
}));
|
|
1132
|
-
}
|
|
1133
|
-
return { successful, failed };
|
|
1134
|
-
}
|
|
1135
|
-
/**
|
|
1136
|
-
* Reads object data during an in-progress migration.
|
|
1137
|
-
*
|
|
1138
|
-
* @description
|
|
1139
|
-
* Safely reads data for an object that may be in the process of
|
|
1140
|
-
* being migrated. Checks hot tier first, then warm tier.
|
|
1141
|
-
*
|
|
1142
|
-
* @param sha - The object SHA to read
|
|
1143
|
-
*
|
|
1144
|
-
* @returns Object data or null if not found
|
|
1145
|
-
*
|
|
1146
|
-
* @example
|
|
1147
|
-
* ```typescript
|
|
1148
|
-
* const data = await migrator.readDuringMigration(sha);
|
|
1149
|
-
* if (data) {
|
|
1150
|
-
* // Process the data regardless of which tier it's in
|
|
1151
|
-
* }
|
|
1152
|
-
* ```
|
|
1153
|
-
*/
|
|
1154
|
-
async readDuringMigration(sha) {
|
|
1155
|
-
// During migration, data should still be in hot tier
|
|
1156
|
-
const data = await this.storage.getFromHot(sha);
|
|
1157
|
-
if (data)
|
|
1158
|
-
return data;
|
|
1159
|
-
// Fall back to warm tier if already migrated
|
|
1160
|
-
return this.storage.getFromWarm(sha);
|
|
1161
|
-
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Writes object data during an in-progress migration.
|
|
1164
|
-
*
|
|
1165
|
-
* @description
|
|
1166
|
-
* Safely handles writes for an object that may be in the process
|
|
1167
|
-
* of being migrated. If the object is being migrated, the write
|
|
1168
|
-
* is queued and replayed after migration completes.
|
|
1169
|
-
*
|
|
1170
|
-
* @param sha - The object SHA to write
|
|
1171
|
-
* @param data - The data to write
|
|
1172
|
-
*
|
|
1173
|
-
* @example
|
|
1174
|
-
* ```typescript
|
|
1175
|
-
* // This is safe to call even during migration
|
|
1176
|
-
* await migrator.writeDuringMigration(sha, newData);
|
|
1177
|
-
* ```
|
|
1178
|
-
*/
|
|
1179
|
-
async writeDuringMigration(sha, data) {
|
|
1180
|
-
// If object is being migrated, queue the write
|
|
1181
|
-
if (this.migratingObjects.has(sha)) {
|
|
1182
|
-
return new Promise((resolve) => {
|
|
1183
|
-
const pending = this.pendingWrites.get(sha) ?? [];
|
|
1184
|
-
pending.push({ resolve, data });
|
|
1185
|
-
this.pendingWrites.set(sha, pending);
|
|
1186
|
-
});
|
|
1187
|
-
}
|
|
1188
|
-
// Otherwise write directly
|
|
1189
|
-
await this.storage.putToHot(sha, data);
|
|
1190
|
-
}
|
|
1191
|
-
/**
|
|
1192
|
-
* Computes SHA-256 checksum for data verification.
|
|
1193
|
-
*
|
|
1194
|
-
* @description
|
|
1195
|
-
* Calculates a SHA-256 hash of the data for integrity verification
|
|
1196
|
-
* during migration.
|
|
1197
|
-
*
|
|
1198
|
-
* @param data - The data to hash
|
|
1199
|
-
*
|
|
1200
|
-
* @returns Hex-encoded SHA-256 hash
|
|
1201
|
-
*
|
|
1202
|
-
* @example
|
|
1203
|
-
* ```typescript
|
|
1204
|
-
* const checksum = await migrator.computeChecksum(data);
|
|
1205
|
-
* console.log(`Checksum: ${checksum}`);
|
|
1206
|
-
* ```
|
|
1207
|
-
*/
|
|
1208
|
-
async computeChecksum(data) {
|
|
1209
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
1210
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1211
|
-
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
//# sourceMappingURL=migration.js.map
|