@undefineds.co/xpod 0.1.7 → 0.2.0-preview.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 +141 -2
- package/config/cli.json +9 -71
- package/config/cloud.json +34 -7
- package/config/local.json +6 -2
- package/config/resolver.json +11 -49
- package/config/runtime-open.json +22 -0
- package/config/xpod.base.json +32 -0
- package/config/xpod.cluster.json +2 -44
- package/config/xpod.json +5 -2
- package/dist/api/auth/AuthContext.d.ts +12 -1
- package/dist/api/auth/AuthContext.js +18 -1
- package/dist/api/auth/AuthContext.js.map +1 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +0 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
- package/dist/api/auth/ServiceTokenAuthenticator.d.ts +18 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js +50 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -0
- package/dist/api/auth/index.d.ts +1 -0
- package/dist/api/auth/index.js +1 -0
- package/dist/api/auth/index.js.map +1 -1
- package/dist/api/chatkit/ai-provider.d.ts +0 -10
- package/dist/api/chatkit/ai-provider.js +11 -120
- package/dist/api/chatkit/ai-provider.js.map +1 -1
- package/dist/api/chatkit/default-agent.js +11 -8
- package/dist/api/chatkit/default-agent.js.map +1 -1
- package/dist/api/chatkit/pod-store.js +19 -3
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/chatkit/schema.d.ts +9 -3
- package/dist/api/chatkit/schema.js +14 -6
- package/dist/api/chatkit/schema.js.map +1 -1
- package/dist/api/container/business-token.d.ts +9 -0
- package/dist/api/container/business-token.js +32 -0
- package/dist/api/container/business-token.js.map +1 -0
- package/dist/api/container/cloud.js +36 -12
- package/dist/api/container/cloud.js.map +1 -1
- package/dist/api/container/common.js +12 -5
- package/dist/api/container/common.js.map +1 -1
- package/dist/api/container/index.js +94 -14
- package/dist/api/container/index.js.map +1 -1
- package/dist/api/container/local.js +2 -1
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/container/routes.js +81 -9
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +8 -6
- package/dist/api/container/types.js.map +1 -1
- package/dist/api/handlers/AdminHandler.js +9 -9
- package/dist/api/handlers/AdminHandler.js.map +1 -1
- package/dist/api/handlers/ApiKeyHandler.js +0 -6
- package/dist/api/handlers/ApiKeyHandler.js.map +1 -1
- package/dist/api/handlers/EdgeNodeSignalHandler.d.ts +17 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js +171 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -0
- package/dist/api/handlers/PodManagementHandler.d.ts +5 -4
- package/dist/api/handlers/PodManagementHandler.js +11 -10
- package/dist/api/handlers/PodManagementHandler.js.map +1 -1
- package/dist/api/handlers/ProvisionHandler.d.ts +42 -0
- package/dist/api/handlers/ProvisionHandler.js +161 -0
- package/dist/api/handlers/ProvisionHandler.js.map +1 -0
- package/dist/api/handlers/QuotaHandler.d.ts +7 -7
- package/dist/api/handlers/QuotaHandler.js +143 -73
- package/dist/api/handlers/QuotaHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainClientHandler.js +2 -2
- package/dist/api/handlers/SubdomainClientHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainHandler.js +13 -8
- package/dist/api/handlers/SubdomainHandler.js.map +1 -1
- package/dist/api/handlers/UsageHandler.d.ts +14 -0
- package/dist/api/handlers/UsageHandler.js +123 -0
- package/dist/api/handlers/UsageHandler.js.map +1 -0
- package/dist/api/handlers/index.d.ts +3 -1
- package/dist/api/handlers/index.js +3 -1
- package/dist/api/handlers/index.js.map +1 -1
- package/dist/api/main.js +18 -0
- package/dist/api/main.js.map +1 -1
- package/dist/api/middleware/OpenAuthMiddleware.d.ts +12 -0
- package/dist/api/middleware/OpenAuthMiddleware.js +27 -0
- package/dist/api/middleware/OpenAuthMiddleware.js.map +1 -0
- package/dist/api/runtime.d.ts +15 -0
- package/dist/api/runtime.js +104 -0
- package/dist/api/runtime.js.map +1 -0
- package/dist/api/service/VercelChatService.d.ts +16 -7
- package/dist/api/service/VercelChatService.js +98 -178
- package/dist/api/service/VercelChatService.js.map +1 -1
- package/dist/api/store/DrizzleClientCredentialsStore.d.ts +6 -11
- package/dist/api/store/DrizzleClientCredentialsStore.js +9 -39
- package/dist/api/store/DrizzleClientCredentialsStore.js.map +1 -1
- package/dist/authorization/AuthModeSelector.d.ts +10 -0
- package/dist/authorization/AuthModeSelector.js +27 -0
- package/dist/authorization/AuthModeSelector.js.map +1 -0
- package/dist/authorization/AuthModeSelector.jsonld +81 -0
- package/dist/cli/commands/account.d.ts +6 -0
- package/dist/cli/commands/account.js +119 -0
- package/dist/cli/commands/account.js.map +1 -0
- package/dist/cli/commands/auth.js +20 -29
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/backup.d.ts +15 -0
- package/dist/cli/commands/backup.js +286 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/config.d.ts +34 -3
- package/dist/cli/commands/config.js +195 -258
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.js +94 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/pod.d.ts +6 -0
- package/dist/cli/commands/pod.js +124 -0
- package/dist/cli/commands/pod.js.map +1 -0
- package/dist/cli/commands/start.js +28 -5
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/index.js +9 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/lib/credentials-store.d.ts +17 -0
- package/dist/cli/lib/credentials-store.js +73 -0
- package/dist/cli/lib/credentials-store.js.map +1 -0
- package/dist/cli/lib/css-account.d.ts +17 -0
- package/dist/cli/lib/css-account.js +56 -0
- package/dist/cli/lib/css-account.js.map +1 -1
- package/dist/cli/lib/pod-thread-store.d.ts +57 -0
- package/dist/cli/lib/pod-thread-store.js +310 -0
- package/dist/cli/lib/pod-thread-store.js.map +1 -0
- package/dist/cli/lib/solid-auth.d.ts +20 -0
- package/dist/cli/lib/solid-auth.js +70 -0
- package/dist/cli/lib/solid-auth.js.map +1 -0
- package/dist/components/components.jsonld +5 -8
- package/dist/components/context.jsonld +114 -244
- package/dist/edge/EdgeNodeAgent.js +2 -2
- package/dist/edge/EdgeNodeAgent.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -7
- package/dist/edge/EdgeNodeDnsCoordinator.js +31 -41
- package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.jsonld +1 -27
- package/dist/edge/EdgeNodeModeDetector.d.ts +1 -1
- package/dist/edge/EdgeNodeModeDetector.js +9 -11
- package/dist/edge/EdgeNodeModeDetector.js.map +1 -1
- package/dist/http/ClusterIngressRouter.js +3 -3
- package/dist/http/ClusterIngressRouter.js.map +1 -1
- package/dist/http/ClusterWebSocketConfigurator.js +2 -2
- package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
- package/dist/http/PodRoutingHttpHandler.js +2 -2
- package/dist/http/PodRoutingHttpHandler.js.map +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.d.ts +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
- package/dist/identity/drizzle/EdgeNodeRepository.d.ts +37 -4
- package/dist/identity/drizzle/EdgeNodeRepository.js +120 -128
- package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
- package/dist/identity/drizzle/ServiceTokenRepository.d.ts +52 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js +143 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -0
- package/dist/identity/drizzle/db.d.ts +9 -0
- package/dist/identity/drizzle/db.js +208 -1
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.d.ts +5 -0
- package/dist/identity/drizzle/schema.pg.js +49 -20
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +332 -57
- package/dist/identity/drizzle/schema.sqlite.js +48 -18
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +6 -4
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.js +12 -14
- package/dist/index.js.map +1 -1
- package/dist/main.js +25 -8
- package/dist/main.js.map +1 -1
- package/dist/provision/ProvisionCodeCodec.d.ts +39 -0
- package/dist/provision/ProvisionCodeCodec.js +65 -0
- package/dist/provision/ProvisionCodeCodec.js.map +1 -0
- package/dist/provision/ProvisionCodeCodec.jsonld +47 -0
- package/dist/provision/ProvisionPodCreator.d.ts +20 -0
- package/dist/provision/ProvisionPodCreator.js +84 -0
- package/dist/provision/ProvisionPodCreator.js.map +1 -0
- package/dist/provision/ProvisionPodCreator.jsonld +118 -0
- package/dist/quota/DrizzleQuotaService.d.ts +17 -3
- package/dist/quota/DrizzleQuotaService.js +108 -8
- package/dist/quota/DrizzleQuotaService.js.map +1 -1
- package/dist/quota/DrizzleQuotaService.jsonld +33 -22
- package/dist/quota/NoopQuotaService.d.ts +7 -1
- package/dist/quota/NoopQuotaService.js +12 -0
- package/dist/quota/NoopQuotaService.js.map +1 -1
- package/dist/quota/NoopQuotaService.jsonld +24 -0
- package/dist/quota/QuotaService.d.ts +17 -0
- package/dist/quota/QuotaService.js +5 -0
- package/dist/quota/QuotaService.js.map +1 -1
- package/dist/quota/QuotaService.jsonld +50 -0
- package/dist/runtime/Proxy.d.ts +22 -4
- package/dist/runtime/Proxy.js +154 -35
- package/dist/runtime/Proxy.js.map +1 -1
- package/dist/runtime/XpodRuntime.d.ts +49 -0
- package/dist/runtime/XpodRuntime.js +374 -0
- package/dist/runtime/XpodRuntime.js.map +1 -0
- package/dist/runtime/env-utils.d.ts +2 -0
- package/dist/runtime/env-utils.js +55 -0
- package/dist/runtime/env-utils.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +8 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/socket-fetch.d.ts +1 -0
- package/dist/runtime/socket-fetch.js +72 -0
- package/dist/runtime/socket-fetch.js.map +1 -0
- package/dist/runtime/socket-http.d.ts +1 -0
- package/dist/runtime/socket-http.js +142 -0
- package/dist/runtime/socket-http.js.map +1 -0
- package/dist/runtime/socket-utils.d.ts +2 -0
- package/dist/runtime/socket-utils.js +34 -0
- package/dist/runtime/socket-utils.js.map +1 -0
- package/dist/service/{EdgeNodeHeartbeatService.d.ts → EdgeNodeSignalClient.d.ts} +3 -3
- package/dist/service/{EdgeNodeHeartbeatService.js → EdgeNodeSignalClient.js} +4 -4
- package/dist/service/EdgeNodeSignalClient.js.map +1 -0
- package/dist/service/PodMigrationService.d.ts +1 -2
- package/dist/service/PodMigrationService.js +1 -2
- package/dist/service/PodMigrationService.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.d.ts +6 -0
- package/dist/storage/accessors/MinioDataAccessor.js +10 -0
- package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
- package/dist/storage/accessors/MixDataAccessor.d.ts +2 -1
- package/dist/storage/accessors/MixDataAccessor.js +12 -1
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +19 -0
- package/dist/storage/locking/UrlAwareRedisLocker.d.ts +18 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js +60 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js.map +1 -0
- package/dist/storage/locking/UrlAwareRedisLocker.jsonld +123 -0
- package/dist/storage/quota/UsageRepository.d.ts +41 -8
- package/dist/storage/quota/UsageRepository.js +252 -50
- package/dist/storage/quota/UsageRepository.js.map +1 -1
- package/dist/storage/sparql/ComunicaQuintEngine.d.ts +9 -0
- package/dist/storage/sparql/ComunicaQuintEngine.js +50 -9
- package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
- package/dist/storage/sparql/QueryOptimizer.js +13 -1
- package/dist/storage/sparql/QueryOptimizer.js.map +1 -1
- package/dist/storage/sparql/QuintQuerySource.d.ts +14 -0
- package/dist/storage/sparql/QuintQuerySource.js +152 -1
- package/dist/storage/sparql/QuintQuerySource.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -0
- package/dist/storage/sparql/SubgraphQueryEngine.js +6 -2
- package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.jsonld +4 -0
- package/dist/subdomain/SubdomainClient.d.ts +3 -3
- package/dist/subdomain/SubdomainClient.js +1 -1
- package/dist/subdomain/SubdomainClient.js.map +1 -1
- package/dist/subdomain/SubdomainService.d.ts +15 -16
- package/dist/subdomain/SubdomainService.js +80 -54
- package/dist/subdomain/SubdomainService.js.map +1 -1
- package/dist/subdomain/SubdomainService.jsonld +22 -26
- package/dist/supervisor/Supervisor.d.ts +7 -2
- package/dist/supervisor/Supervisor.js +33 -1
- package/dist/supervisor/Supervisor.js.map +1 -1
- package/dist/test-utils/index.d.ts +4 -0
- package/dist/test-utils/index.js +8 -0
- package/dist/test-utils/index.js.map +1 -0
- package/dist/test-utils/no-auth-xpod.d.ts +11 -0
- package/dist/test-utils/no-auth-xpod.js +25 -0
- package/dist/test-utils/no-auth-xpod.js.map +1 -0
- package/dist/test-utils/seed-pod.d.ts +5 -0
- package/dist/test-utils/seed-pod.js +61 -0
- package/dist/test-utils/seed-pod.js.map +1 -0
- package/package.json +23 -5
- package/templates/identity/account/create-pod.html.ejs +110 -0
- package/templates/main.html.ejs +10 -0
- package/dist/api/handlers/DevHandler.d.ts +0 -18
- package/dist/api/handlers/DevHandler.js +0 -276
- package/dist/api/handlers/DevHandler.js.map +0 -1
- package/dist/api/handlers/SignalHandler.d.ts +0 -13
- package/dist/api/handlers/SignalHandler.js +0 -122
- package/dist/api/handlers/SignalHandler.js.map +0 -1
- package/dist/gateway/Proxy.d.ts +0 -24
- package/dist/gateway/Proxy.js +0 -209
- package/dist/gateway/Proxy.js.map +0 -1
- package/dist/gateway/Supervisor.d.ts +0 -2
- package/dist/gateway/Supervisor.js +0 -7
- package/dist/gateway/Supervisor.js.map +0 -1
- package/dist/gateway/port-finder.d.ts +0 -4
- package/dist/gateway/port-finder.js +0 -15
- package/dist/gateway/port-finder.js.map +0 -1
- package/dist/gateway/types.d.ts +0 -1
- package/dist/gateway/types.js +0 -3
- package/dist/gateway/types.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.d.ts +0 -24
- package/dist/http/SignalInterceptHttpHandler.js +0 -47
- package/dist/http/SignalInterceptHttpHandler.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.jsonld +0 -103
- package/dist/http/admin/EdgeNodeSignalHttpHandler.d.ts +0 -71
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js +0 -674
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js.map +0 -1
- package/dist/http/admin/EdgeNodeSignalHttpHandler.jsonld +0 -406
- package/dist/http/cluster/PodMigrationHttpHandler.jsonld +0 -169
- package/dist/quota/DefaultQuotaService.d.ts +0 -16
- package/dist/quota/DefaultQuotaService.js +0 -37
- package/dist/quota/DefaultQuotaService.js.map +0 -1
- package/dist/quota/DefaultQuotaService.jsonld +0 -85
- package/dist/service/EdgeNodeHeartbeatService.js.map +0 -1
- package/dist/service/PodMigrationService.jsonld +0 -76
- package/dist/storage/MigratableDataAccessor.d.ts +0 -63
- package/dist/storage/MigratableDataAccessor.js +0 -11
- package/dist/storage/MigratableDataAccessor.js.map +0 -1
- package/dist/storage/MigratableDataAccessor.jsonld +0 -60
- package/dist/storage/accessors/TieredMinioDataAccessor.d.ts +0 -150
- package/dist/storage/accessors/TieredMinioDataAccessor.js +0 -582
- package/dist/storage/accessors/TieredMinioDataAccessor.js.map +0 -1
- package/dist/storage/accessors/TieredMinioDataAccessor.jsonld +0 -333
- package/static/app/assets/index.css +0 -1
- package/static/app/assets/main.js +0 -11
|
@@ -1,582 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TieredMinioDataAccessor = void 0;
|
|
4
|
-
const node_fs_1 = require("node:fs");
|
|
5
|
-
const global_logger_factory_1 = require("global-logger-factory");
|
|
6
|
-
const node_path_1 = require("node:path");
|
|
7
|
-
const minio_1 = require("minio");
|
|
8
|
-
const community_server_1 = require("@solid/community-server");
|
|
9
|
-
/**
|
|
10
|
-
* TieredMinioDataAccessor extends MinioDataAccessor with a local cache layer.
|
|
11
|
-
*
|
|
12
|
-
* Read: Check local cache first, if miss then fetch from COS and cache locally.
|
|
13
|
-
* Write: Write to COS first (ensure durability), then cache locally.
|
|
14
|
-
*
|
|
15
|
-
* Uses LRU eviction when cache exceeds maxSize.
|
|
16
|
-
*
|
|
17
|
-
* Supports cross-region migration via MigratableDataAccessor interface.
|
|
18
|
-
*/
|
|
19
|
-
class TieredMinioDataAccessor {
|
|
20
|
-
constructor(config) {
|
|
21
|
-
this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
|
|
22
|
-
// LRU tracking: Map<cacheFilePath, lastAccessTime>
|
|
23
|
-
this.cacheEntries = new Map();
|
|
24
|
-
this.currentCacheSize = 0;
|
|
25
|
-
// Active sync subscriptions for migration
|
|
26
|
-
this.activeSyncs = new Map();
|
|
27
|
-
this.resourceMapper = config.resourceMapper;
|
|
28
|
-
this.client = new minio_1.Client({
|
|
29
|
-
accessKey: config.accessKey,
|
|
30
|
-
secretKey: config.secretKey,
|
|
31
|
-
endPoint: config.endpoint,
|
|
32
|
-
useSSL: true,
|
|
33
|
-
});
|
|
34
|
-
this.bucketName = config.bucketName;
|
|
35
|
-
this.cachePath = config.cachePath;
|
|
36
|
-
this.cacheMaxSize = config.cacheMaxSize;
|
|
37
|
-
this.region = config.region;
|
|
38
|
-
this.regionBuckets = config.regionBuckets ?? {};
|
|
39
|
-
// Ensure cache directory exists
|
|
40
|
-
if (!(0, node_fs_1.existsSync)(this.cachePath)) {
|
|
41
|
-
(0, node_fs_1.mkdirSync)(this.cachePath, { recursive: true });
|
|
42
|
-
}
|
|
43
|
-
// Initialize cache tracking from existing files
|
|
44
|
-
this.initializeCacheTracking();
|
|
45
|
-
this.logger.info(`TieredMinioDataAccessor initialized with endpoint: ${config.endpoint}, cache: ${config.cachePath}, maxSize: ${this.formatBytes(config.cacheMaxSize)}`);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Scan existing cache directory and populate cache entries map.
|
|
49
|
-
*/
|
|
50
|
-
initializeCacheTracking() {
|
|
51
|
-
try {
|
|
52
|
-
this.scanCacheDir(this.cachePath);
|
|
53
|
-
this.logger.info(`Cache initialized: ${this.cacheEntries.size} files, ${this.formatBytes(this.currentCacheSize)}`);
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
this.logger.warn(`Failed to scan cache directory: ${error}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
scanCacheDir(dir) {
|
|
60
|
-
if (!(0, node_fs_1.existsSync)(dir))
|
|
61
|
-
return;
|
|
62
|
-
const entries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
63
|
-
for (const entry of entries) {
|
|
64
|
-
const fullPath = (0, node_path_1.join)(dir, entry.name);
|
|
65
|
-
if (entry.isDirectory()) {
|
|
66
|
-
this.scanCacheDir(fullPath);
|
|
67
|
-
}
|
|
68
|
-
else if (entry.isFile()) {
|
|
69
|
-
try {
|
|
70
|
-
const stats = (0, node_fs_1.statSync)(fullPath);
|
|
71
|
-
this.cacheEntries.set(fullPath, {
|
|
72
|
-
path: fullPath,
|
|
73
|
-
size: stats.size,
|
|
74
|
-
lastAccess: stats.atimeMs,
|
|
75
|
-
});
|
|
76
|
-
this.currentCacheSize += stats.size;
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
// File may have been deleted
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Convert URL path to local cache file path.
|
|
86
|
-
*/
|
|
87
|
-
getCacheFilePath(identifier) {
|
|
88
|
-
const url = new URL(identifier.path);
|
|
89
|
-
// Remove leading slash and encode special chars
|
|
90
|
-
const relativePath = url.pathname.slice(1).replace(/[<>:"|?*]/g, '_');
|
|
91
|
-
return (0, node_path_1.join)(this.cachePath, relativePath);
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Check if file exists in local cache.
|
|
95
|
-
*/
|
|
96
|
-
isCached(cacheFilePath) {
|
|
97
|
-
return (0, node_fs_1.existsSync)(cacheFilePath);
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Update LRU access time for a cache entry.
|
|
101
|
-
*/
|
|
102
|
-
touchCache(cacheFilePath) {
|
|
103
|
-
const entry = this.cacheEntries.get(cacheFilePath);
|
|
104
|
-
if (entry) {
|
|
105
|
-
entry.lastAccess = Date.now();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Add file to cache tracking.
|
|
110
|
-
*/
|
|
111
|
-
addToCacheTracking(cacheFilePath, size) {
|
|
112
|
-
this.cacheEntries.set(cacheFilePath, {
|
|
113
|
-
path: cacheFilePath,
|
|
114
|
-
size,
|
|
115
|
-
lastAccess: Date.now(),
|
|
116
|
-
});
|
|
117
|
-
this.currentCacheSize += size;
|
|
118
|
-
// Trigger eviction if needed
|
|
119
|
-
this.evictIfNeeded();
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Remove file from cache tracking.
|
|
123
|
-
*/
|
|
124
|
-
removeFromCacheTracking(cacheFilePath) {
|
|
125
|
-
const entry = this.cacheEntries.get(cacheFilePath);
|
|
126
|
-
if (entry) {
|
|
127
|
-
this.currentCacheSize -= entry.size;
|
|
128
|
-
this.cacheEntries.delete(cacheFilePath);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Evict least recently used files until cache is under maxSize.
|
|
133
|
-
*/
|
|
134
|
-
evictIfNeeded() {
|
|
135
|
-
if (this.currentCacheSize <= this.cacheMaxSize) {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
// Sort by lastAccess (oldest first)
|
|
139
|
-
const sortedEntries = [...this.cacheEntries.entries()]
|
|
140
|
-
.sort((a, b) => a[1].lastAccess - b[1].lastAccess);
|
|
141
|
-
for (const [path, entry] of sortedEntries) {
|
|
142
|
-
if (this.currentCacheSize <= this.cacheMaxSize * 0.8) {
|
|
143
|
-
// Evict until 80% of max to avoid frequent evictions
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
try {
|
|
147
|
-
(0, node_fs_1.unlinkSync)(path);
|
|
148
|
-
this.currentCacheSize -= entry.size;
|
|
149
|
-
this.cacheEntries.delete(path);
|
|
150
|
-
this.logger.debug(`Evicted from cache: ${path}`);
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
this.logger.warn(`Failed to evict cache file ${path}: ${error}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
formatBytes(bytes) {
|
|
158
|
-
if (bytes >= 1024 * 1024 * 1024) {
|
|
159
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
160
|
-
}
|
|
161
|
-
if (bytes >= 1024 * 1024) {
|
|
162
|
-
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
163
|
-
}
|
|
164
|
-
if (bytes >= 1024) {
|
|
165
|
-
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
166
|
-
}
|
|
167
|
-
return `${bytes} B`;
|
|
168
|
-
}
|
|
169
|
-
// ============== DataAccessor Interface ==============
|
|
170
|
-
async canHandle(representation) {
|
|
171
|
-
if (!representation.binary) {
|
|
172
|
-
throw new community_server_1.UnsupportedMediaTypeHttpError('Only binary data is supported.');
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Get data with cache-first strategy and cross-region fallback.
|
|
177
|
-
*
|
|
178
|
-
* Read order:
|
|
179
|
-
* 1. Local cache (fastest)
|
|
180
|
-
* 2. Local bucket (current region)
|
|
181
|
-
* 3. Fallback buckets (other regions) - enables instant migration
|
|
182
|
-
*
|
|
183
|
-
* When reading from fallback bucket, optionally copy to local bucket (lazy migration).
|
|
184
|
-
*/
|
|
185
|
-
async getData(identifier) {
|
|
186
|
-
const cacheFilePath = this.getCacheFilePath(identifier);
|
|
187
|
-
const url = new URL(identifier.path);
|
|
188
|
-
// 1. Check local cache first
|
|
189
|
-
if (this.isCached(cacheFilePath)) {
|
|
190
|
-
this.logger.debug(`Cache hit: ${identifier.path}`);
|
|
191
|
-
this.touchCache(cacheFilePath);
|
|
192
|
-
const stream = (0, node_fs_1.createReadStream)(cacheFilePath);
|
|
193
|
-
return (0, community_server_1.guardStream)(stream);
|
|
194
|
-
}
|
|
195
|
-
// 2. Try local bucket first
|
|
196
|
-
this.logger.debug(`Cache miss: ${identifier.path}`);
|
|
197
|
-
let data = null;
|
|
198
|
-
let sourceLocation = 'local';
|
|
199
|
-
try {
|
|
200
|
-
data = await this.fetchFromBucket(this.bucketName, url.pathname);
|
|
201
|
-
}
|
|
202
|
-
catch (error) {
|
|
203
|
-
// Local bucket failed, try fallback buckets
|
|
204
|
-
if (this.supportsMigration()) {
|
|
205
|
-
const fallbackResult = await this.fetchFromFallbackBuckets(url.pathname);
|
|
206
|
-
if (fallbackResult) {
|
|
207
|
-
data = fallbackResult.data;
|
|
208
|
-
sourceLocation = fallbackResult.bucket;
|
|
209
|
-
this.logger.debug(`Fallback read from ${sourceLocation}: ${identifier.path}`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (!data) {
|
|
214
|
-
throw new community_server_1.NotFoundHttpError(`Resource not found: ${identifier.path}`);
|
|
215
|
-
}
|
|
216
|
-
// 3. Write to local cache
|
|
217
|
-
await this.writeToCache(cacheFilePath, data, identifier.path);
|
|
218
|
-
// 4. Lazy copy to local bucket if read from fallback
|
|
219
|
-
if (sourceLocation !== 'local' && sourceLocation !== this.bucketName) {
|
|
220
|
-
this.lazyCopyToLocalBucket(url.pathname, data).catch(err => {
|
|
221
|
-
this.logger.warn(`Lazy copy failed for ${identifier.path}: ${err.message}`);
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
// Return data as stream
|
|
225
|
-
const { Readable } = require('node:stream');
|
|
226
|
-
const readable = Readable.from(data);
|
|
227
|
-
return (0, community_server_1.guardStream)(readable);
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Fetch data from a specific bucket.
|
|
231
|
-
*/
|
|
232
|
-
async fetchFromBucket(bucket, path) {
|
|
233
|
-
const stream = await this.client.getObject(bucket, path);
|
|
234
|
-
const chunks = [];
|
|
235
|
-
for await (const chunk of stream) {
|
|
236
|
-
chunks.push(chunk);
|
|
237
|
-
}
|
|
238
|
-
return Buffer.concat(chunks);
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Try to fetch data from fallback buckets (other regions).
|
|
242
|
-
* Returns the data and source bucket name, or null if not found.
|
|
243
|
-
*/
|
|
244
|
-
async fetchFromFallbackBuckets(path) {
|
|
245
|
-
// Try each region bucket except the current one
|
|
246
|
-
for (const [region, bucket] of Object.entries(this.regionBuckets)) {
|
|
247
|
-
if (bucket === this.bucketName) {
|
|
248
|
-
continue; // Skip local bucket
|
|
249
|
-
}
|
|
250
|
-
try {
|
|
251
|
-
this.logger.debug(`Trying fallback bucket: ${bucket} (region: ${region})`);
|
|
252
|
-
const data = await this.fetchFromBucket(bucket, path);
|
|
253
|
-
return { data, bucket };
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
// Not found in this bucket, try next
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Write data to local cache.
|
|
264
|
-
*/
|
|
265
|
-
async writeToCache(cacheFilePath, data, path) {
|
|
266
|
-
try {
|
|
267
|
-
const cacheDir = (0, node_path_1.dirname)(cacheFilePath);
|
|
268
|
-
if (!(0, node_fs_1.existsSync)(cacheDir)) {
|
|
269
|
-
(0, node_fs_1.mkdirSync)(cacheDir, { recursive: true });
|
|
270
|
-
}
|
|
271
|
-
const writeStream = (0, node_fs_1.createWriteStream)(cacheFilePath);
|
|
272
|
-
await new Promise((resolve, reject) => {
|
|
273
|
-
writeStream.write(data, (err) => {
|
|
274
|
-
if (err)
|
|
275
|
-
reject(err);
|
|
276
|
-
else {
|
|
277
|
-
writeStream.end();
|
|
278
|
-
resolve();
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
this.addToCacheTracking(cacheFilePath, data.length);
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
this.logger.warn(`Failed to cache ${path}: ${error}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Lazily copy data to local bucket for future reads.
|
|
290
|
-
* This runs in the background and doesn't block the read.
|
|
291
|
-
*/
|
|
292
|
-
async lazyCopyToLocalBucket(path, data) {
|
|
293
|
-
const { Readable } = require('node:stream');
|
|
294
|
-
const stream = Readable.from(data);
|
|
295
|
-
await this.client.putObject(this.bucketName, path, stream, data.length);
|
|
296
|
-
this.logger.debug(`Lazy copied to local bucket: ${path}`);
|
|
297
|
-
}
|
|
298
|
-
async getMetadata(identifier) {
|
|
299
|
-
const url = new URL(identifier.path);
|
|
300
|
-
const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
|
|
301
|
-
const isDirectory = identifier.path.endsWith('/');
|
|
302
|
-
const objectName = isDirectory ? `${url.pathname}/.container` : url.pathname;
|
|
303
|
-
let stats;
|
|
304
|
-
try {
|
|
305
|
-
stats = await this.client.statObject(this.bucketName, objectName);
|
|
306
|
-
}
|
|
307
|
-
catch (error) {
|
|
308
|
-
throw new community_server_1.NotFoundHttpError();
|
|
309
|
-
}
|
|
310
|
-
if (!(0, community_server_1.isContainerIdentifier)(identifier) && !isDirectory) {
|
|
311
|
-
return this.getFileMetadata(link, stats);
|
|
312
|
-
}
|
|
313
|
-
if ((0, community_server_1.isContainerIdentifier)(identifier) && isDirectory) {
|
|
314
|
-
return this.getDirectoryMetadata(link, stats);
|
|
315
|
-
}
|
|
316
|
-
throw new community_server_1.NotFoundHttpError();
|
|
317
|
-
}
|
|
318
|
-
async *getChildren(identifier) {
|
|
319
|
-
const url = new URL(identifier.path);
|
|
320
|
-
const objects = this.client.listObjectsV2(this.bucketName, url.pathname);
|
|
321
|
-
for await (const object of objects) {
|
|
322
|
-
const metadata = await this.getMetadata(object);
|
|
323
|
-
yield metadata;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Write document: COS first (durability), then cache.
|
|
328
|
-
*/
|
|
329
|
-
async writeDocument(identifier, data, metadata) {
|
|
330
|
-
const url = new URL(identifier.path);
|
|
331
|
-
const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
|
|
332
|
-
const itemMetadata = this.encodeMetadata(link, metadata);
|
|
333
|
-
// Collect data for both COS and cache
|
|
334
|
-
const chunks = [];
|
|
335
|
-
for await (const chunk of data) {
|
|
336
|
-
chunks.push(chunk);
|
|
337
|
-
}
|
|
338
|
-
const buffer = Buffer.concat(chunks);
|
|
339
|
-
// Write to COS first (ensure durability)
|
|
340
|
-
try {
|
|
341
|
-
const { Readable } = require('node:stream');
|
|
342
|
-
const cosStream = Readable.from(buffer);
|
|
343
|
-
await this.client.putObject(this.bucketName, url.pathname, cosStream, buffer.length, itemMetadata || undefined);
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
this.logger.error(`Error writing to COS: ${identifier.path} ${error}`);
|
|
347
|
-
throw error;
|
|
348
|
-
}
|
|
349
|
-
// Write to local cache
|
|
350
|
-
const cacheFilePath = this.getCacheFilePath(identifier);
|
|
351
|
-
try {
|
|
352
|
-
const cacheDir = (0, node_path_1.dirname)(cacheFilePath);
|
|
353
|
-
if (!(0, node_fs_1.existsSync)(cacheDir)) {
|
|
354
|
-
(0, node_fs_1.mkdirSync)(cacheDir, { recursive: true });
|
|
355
|
-
}
|
|
356
|
-
// Remove old cache entry if exists
|
|
357
|
-
if (this.isCached(cacheFilePath)) {
|
|
358
|
-
this.removeFromCacheTracking(cacheFilePath);
|
|
359
|
-
}
|
|
360
|
-
const writeStream = (0, node_fs_1.createWriteStream)(cacheFilePath);
|
|
361
|
-
await new Promise((resolve, reject) => {
|
|
362
|
-
writeStream.write(buffer, (err) => {
|
|
363
|
-
if (err)
|
|
364
|
-
reject(err);
|
|
365
|
-
else {
|
|
366
|
-
writeStream.end();
|
|
367
|
-
resolve();
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
this.addToCacheTracking(cacheFilePath, buffer.length);
|
|
372
|
-
}
|
|
373
|
-
catch (error) {
|
|
374
|
-
this.logger.warn(`Failed to write cache ${identifier.path}: ${error}`);
|
|
375
|
-
// Cache failure is non-fatal
|
|
376
|
-
}
|
|
377
|
-
// Replicate to sync targets (for active migrations)
|
|
378
|
-
const syncTargets = this.getActiveSyncTargets(url.pathname);
|
|
379
|
-
for (const targetBucket of syncTargets) {
|
|
380
|
-
try {
|
|
381
|
-
const { Readable } = require('node:stream');
|
|
382
|
-
const syncStream = Readable.from(buffer);
|
|
383
|
-
await this.client.putObject(targetBucket, url.pathname, syncStream, buffer.length, itemMetadata || undefined);
|
|
384
|
-
this.logger.debug(`Synced to ${targetBucket}: ${url.pathname}`);
|
|
385
|
-
}
|
|
386
|
-
catch (error) {
|
|
387
|
-
this.logger.warn(`Failed to sync ${url.pathname} to ${targetBucket}: ${error}`);
|
|
388
|
-
// Sync failure is non-fatal, migration will catch up
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
async writeContainer(identifier, metadata) {
|
|
393
|
-
const url = new URL(identifier.path);
|
|
394
|
-
const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
|
|
395
|
-
await this.client.putObject(this.bucketName, `${url.pathname}/.container`, Buffer.from(''), 0, this.encodeMetadata(link, metadata) || undefined);
|
|
396
|
-
}
|
|
397
|
-
async writeMetadata(identifier, metadata) {
|
|
398
|
-
throw new Error('TieredMinioDataAccessor does not support writing metadata for a resource.');
|
|
399
|
-
}
|
|
400
|
-
async deleteResource(identifier) {
|
|
401
|
-
const url = new URL(identifier.path);
|
|
402
|
-
// Delete from COS
|
|
403
|
-
await this.client.removeObject(this.bucketName, url.pathname);
|
|
404
|
-
// Delete from cache
|
|
405
|
-
const cacheFilePath = this.getCacheFilePath(identifier);
|
|
406
|
-
if (this.isCached(cacheFilePath)) {
|
|
407
|
-
try {
|
|
408
|
-
(0, node_fs_1.unlinkSync)(cacheFilePath);
|
|
409
|
-
this.removeFromCacheTracking(cacheFilePath);
|
|
410
|
-
}
|
|
411
|
-
catch (error) {
|
|
412
|
-
this.logger.warn(`Failed to delete cache ${identifier.path}: ${error}`);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
// Sync delete to migration targets
|
|
416
|
-
const syncTargets = this.getActiveSyncTargets(url.pathname);
|
|
417
|
-
for (const targetBucket of syncTargets) {
|
|
418
|
-
try {
|
|
419
|
-
await this.client.removeObject(targetBucket, url.pathname);
|
|
420
|
-
this.logger.debug(`Synced delete to ${targetBucket}: ${url.pathname}`);
|
|
421
|
-
}
|
|
422
|
-
catch (error) {
|
|
423
|
-
this.logger.warn(`Failed to sync delete ${url.pathname} to ${targetBucket}: ${error}`);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
// ============== Metadata Helpers ==============
|
|
428
|
-
async getFileMetadata(link, stats) {
|
|
429
|
-
const metadata = await this.getBaseMetadata(link, stats, false);
|
|
430
|
-
if (typeof metadata.contentType === 'undefined') {
|
|
431
|
-
metadata.set(community_server_1.CONTENT_TYPE_TERM, link.contentType);
|
|
432
|
-
}
|
|
433
|
-
return metadata;
|
|
434
|
-
}
|
|
435
|
-
async getDirectoryMetadata(link, stats) {
|
|
436
|
-
return this.getBaseMetadata(link, stats, true);
|
|
437
|
-
}
|
|
438
|
-
async getBaseMetadata(link, stats, isContainer) {
|
|
439
|
-
const metadata = this.decodeMetadata(link, stats.metaData);
|
|
440
|
-
(0, community_server_1.addResourceMetadata)(metadata, isContainer);
|
|
441
|
-
this.addPosixMetadata(metadata, stats, isContainer);
|
|
442
|
-
return metadata;
|
|
443
|
-
}
|
|
444
|
-
addPosixMetadata(metadata, stats, isDirectory) {
|
|
445
|
-
(0, community_server_1.updateModifiedDate)(metadata, stats.lastModified);
|
|
446
|
-
metadata.add(community_server_1.POSIX.terms.mtime, (0, community_server_1.toLiteral)(Math.floor(stats.lastModified.getTime() / 1000), community_server_1.XSD.terms.integer), community_server_1.SOLID_META.terms.ResponseMetadata);
|
|
447
|
-
if (!isDirectory) {
|
|
448
|
-
metadata.add(community_server_1.POSIX.terms.size, (0, community_server_1.toLiteral)(stats.size, community_server_1.XSD.terms.integer), community_server_1.SOLID_META.terms.ResponseMetadata);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
encodeMetadata(link, metadata) {
|
|
452
|
-
metadata.remove(community_server_1.RDF.terms.type, community_server_1.LDP.terms.Resource);
|
|
453
|
-
metadata.remove(community_server_1.RDF.terms.type, community_server_1.LDP.terms.Container);
|
|
454
|
-
metadata.remove(community_server_1.RDF.terms.type, community_server_1.LDP.terms.BasicContainer);
|
|
455
|
-
metadata.removeAll(community_server_1.DC.terms.modified);
|
|
456
|
-
if ((0, community_server_1.isContainerPath)(link.filePath) || typeof metadata.contentType !== 'undefined') {
|
|
457
|
-
metadata.removeAll(community_server_1.CONTENT_TYPE_TERM);
|
|
458
|
-
}
|
|
459
|
-
const contentTypeObject = metadata.contentTypeObject;
|
|
460
|
-
if (contentTypeObject === undefined || Object.keys(contentTypeObject.parameters).length === 0) {
|
|
461
|
-
return null;
|
|
462
|
-
}
|
|
463
|
-
return contentTypeObject.parameters;
|
|
464
|
-
}
|
|
465
|
-
decodeMetadata(link, metadata) {
|
|
466
|
-
return new community_server_1.RepresentationMetadata(link.identifier, metadata);
|
|
467
|
-
}
|
|
468
|
-
// ============== Cache Stats (for monitoring) ==============
|
|
469
|
-
getCacheStats() {
|
|
470
|
-
return {
|
|
471
|
-
entries: this.cacheEntries.size,
|
|
472
|
-
size: this.currentCacheSize,
|
|
473
|
-
maxSize: this.cacheMaxSize,
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
// ============== MigratableDataAccessor Implementation ==============
|
|
477
|
-
/**
|
|
478
|
-
* Check if migration is supported.
|
|
479
|
-
* Migration requires region configuration and region-to-bucket mapping.
|
|
480
|
-
*/
|
|
481
|
-
supportsMigration() {
|
|
482
|
-
return this.region !== undefined && Object.keys(this.regionBuckets).length > 0;
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Migrate all objects under the given prefix to a target region's bucket.
|
|
486
|
-
* Uses Minio server-side copy for efficiency (data doesn't pass through this node).
|
|
487
|
-
*/
|
|
488
|
-
async migrateToRegion(prefix, targetRegion, onProgress) {
|
|
489
|
-
if (!this.supportsMigration()) {
|
|
490
|
-
throw new Error('Migration not supported: region configuration missing');
|
|
491
|
-
}
|
|
492
|
-
const targetBucket = this.regionBuckets[targetRegion];
|
|
493
|
-
if (!targetBucket) {
|
|
494
|
-
throw new Error(`Unknown target region: ${targetRegion}. Available regions: ${Object.keys(this.regionBuckets).join(', ')}`);
|
|
495
|
-
}
|
|
496
|
-
if (targetBucket === this.bucketName) {
|
|
497
|
-
this.logger.info(`Source and target bucket are the same (${this.bucketName}), skipping migration`);
|
|
498
|
-
onProgress?.({ copied: 0, total: 0, bytesTransferred: 0 });
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
this.logger.info(`Starting migration: prefix=${prefix}, source=${this.bucketName}, target=${targetBucket}`);
|
|
502
|
-
// Normalize prefix (remove leading slash for Minio)
|
|
503
|
-
const objectPrefix = prefix.startsWith('/') ? prefix.slice(1) : prefix;
|
|
504
|
-
// 1. List all objects to migrate
|
|
505
|
-
const objects = [];
|
|
506
|
-
const stream = this.client.listObjectsV2(this.bucketName, objectPrefix, true);
|
|
507
|
-
for await (const obj of stream) {
|
|
508
|
-
if (obj.name) {
|
|
509
|
-
objects.push({ name: obj.name, size: obj.size ?? 0 });
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
this.logger.info(`Found ${objects.length} objects to migrate`);
|
|
513
|
-
if (objects.length === 0) {
|
|
514
|
-
onProgress?.({ copied: 0, total: 0, bytesTransferred: 0 });
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
// 2. Copy each object using server-side copy
|
|
518
|
-
let copied = 0;
|
|
519
|
-
let bytesTransferred = 0;
|
|
520
|
-
for (const obj of objects) {
|
|
521
|
-
try {
|
|
522
|
-
// Minio copyObject uses server-side copy when source and target are on same cluster
|
|
523
|
-
const copySource = `/${this.bucketName}/${obj.name}`;
|
|
524
|
-
await this.client.copyObject(targetBucket, obj.name, copySource, new minio_1.CopyConditions());
|
|
525
|
-
copied++;
|
|
526
|
-
bytesTransferred += obj.size;
|
|
527
|
-
onProgress?.({
|
|
528
|
-
copied,
|
|
529
|
-
total: objects.length,
|
|
530
|
-
bytesTransferred,
|
|
531
|
-
});
|
|
532
|
-
this.logger.debug(`Copied: ${obj.name} (${copied}/${objects.length})`);
|
|
533
|
-
}
|
|
534
|
-
catch (error) {
|
|
535
|
-
this.logger.error(`Failed to copy object ${obj.name}: ${error.message}`);
|
|
536
|
-
throw error;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
this.logger.info(`Migration completed: ${copied} objects, ${this.formatBytes(bytesTransferred)}`);
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Set up real-time sync during migration.
|
|
543
|
-
*
|
|
544
|
-
* Note: Full implementation would use Minio Bucket Notifications to replicate
|
|
545
|
-
* new writes to the target bucket. For now, we track active syncs and replicate
|
|
546
|
-
* writes in writeDocument().
|
|
547
|
-
*/
|
|
548
|
-
async setupRealtimeSync(prefix, targetRegion) {
|
|
549
|
-
if (!this.supportsMigration()) {
|
|
550
|
-
throw new Error('Migration not supported: region configuration missing');
|
|
551
|
-
}
|
|
552
|
-
const targetBucket = this.regionBuckets[targetRegion];
|
|
553
|
-
if (!targetBucket) {
|
|
554
|
-
throw new Error(`Unknown target region: ${targetRegion}`);
|
|
555
|
-
}
|
|
556
|
-
const syncKey = `${prefix}:${targetRegion}`;
|
|
557
|
-
this.activeSyncs.set(syncKey, { prefix, targetBucket });
|
|
558
|
-
this.logger.info(`Real-time sync enabled: prefix=${prefix}, target=${targetBucket}`);
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* Stop real-time sync after migration completes.
|
|
562
|
-
*/
|
|
563
|
-
async stopRealtimeSync(prefix, targetRegion) {
|
|
564
|
-
const syncKey = `${prefix}:${targetRegion}`;
|
|
565
|
-
this.activeSyncs.delete(syncKey);
|
|
566
|
-
this.logger.info(`Real-time sync disabled: prefix=${prefix}, targetRegion=${targetRegion}`);
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Check if a write should be replicated to sync targets.
|
|
570
|
-
*/
|
|
571
|
-
getActiveSyncTargets(path) {
|
|
572
|
-
const targets = [];
|
|
573
|
-
for (const [, sync] of this.activeSyncs) {
|
|
574
|
-
if (path.startsWith(sync.prefix)) {
|
|
575
|
-
targets.push(sync.targetBucket);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
return targets;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
exports.TieredMinioDataAccessor = TieredMinioDataAccessor;
|
|
582
|
-
//# sourceMappingURL=TieredMinioDataAccessor.js.map
|