@push.rocks/smartregistry 2.2.3 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/cargo/classes.cargoregistry.d.ts +7 -1
  3. package/dist_ts/cargo/classes.cargoregistry.js +42 -4
  4. package/dist_ts/cargo/classes.cargoupstream.d.ts +44 -0
  5. package/dist_ts/cargo/classes.cargoupstream.js +129 -0
  6. package/dist_ts/cargo/index.d.ts +1 -0
  7. package/dist_ts/cargo/index.js +2 -1
  8. package/dist_ts/classes.smartregistry.d.ts +33 -2
  9. package/dist_ts/classes.smartregistry.js +45 -12
  10. package/dist_ts/composer/classes.composerregistry.d.ts +7 -1
  11. package/dist_ts/composer/classes.composerregistry.js +34 -3
  12. package/dist_ts/composer/classes.composerupstream.d.ts +40 -0
  13. package/dist_ts/composer/classes.composerupstream.js +159 -0
  14. package/dist_ts/composer/index.d.ts +1 -0
  15. package/dist_ts/composer/index.js +2 -1
  16. package/dist_ts/core/classes.authmanager.d.ts +30 -80
  17. package/dist_ts/core/classes.authmanager.js +63 -337
  18. package/dist_ts/core/classes.defaultauthprovider.d.ts +78 -0
  19. package/dist_ts/core/classes.defaultauthprovider.js +311 -0
  20. package/dist_ts/core/classes.registrystorage.d.ts +70 -4
  21. package/dist_ts/core/classes.registrystorage.js +165 -5
  22. package/dist_ts/core/index.d.ts +3 -0
  23. package/dist_ts/core/index.js +7 -2
  24. package/dist_ts/core/interfaces.auth.d.ts +83 -0
  25. package/dist_ts/core/interfaces.auth.js +2 -0
  26. package/dist_ts/core/interfaces.core.d.ts +38 -0
  27. package/dist_ts/core/interfaces.storage.d.ts +120 -0
  28. package/dist_ts/core/interfaces.storage.js +2 -0
  29. package/dist_ts/index.d.ts +1 -0
  30. package/dist_ts/index.js +3 -1
  31. package/dist_ts/maven/classes.mavenregistry.d.ts +12 -1
  32. package/dist_ts/maven/classes.mavenregistry.js +69 -4
  33. package/dist_ts/maven/classes.mavenupstream.d.ts +45 -0
  34. package/dist_ts/maven/classes.mavenupstream.js +153 -0
  35. package/dist_ts/maven/index.d.ts +1 -0
  36. package/dist_ts/maven/index.js +2 -1
  37. package/dist_ts/npm/classes.npmregistry.d.ts +3 -1
  38. package/dist_ts/npm/classes.npmregistry.js +55 -6
  39. package/dist_ts/npm/classes.npmupstream.d.ts +51 -0
  40. package/dist_ts/npm/classes.npmupstream.js +206 -0
  41. package/dist_ts/npm/index.d.ts +1 -0
  42. package/dist_ts/npm/index.js +2 -1
  43. package/dist_ts/oci/classes.ociregistry.d.ts +4 -1
  44. package/dist_ts/oci/classes.ociregistry.js +78 -17
  45. package/dist_ts/oci/classes.ociupstream.d.ts +62 -0
  46. package/dist_ts/oci/classes.ociupstream.js +206 -0
  47. package/dist_ts/oci/index.d.ts +1 -0
  48. package/dist_ts/oci/index.js +2 -1
  49. package/dist_ts/plugins.d.ts +4 -1
  50. package/dist_ts/plugins.js +6 -2
  51. package/dist_ts/pypi/classes.pypiregistry.d.ts +7 -1
  52. package/dist_ts/pypi/classes.pypiregistry.js +60 -4
  53. package/dist_ts/pypi/classes.pypiupstream.d.ts +48 -0
  54. package/dist_ts/pypi/classes.pypiupstream.js +165 -0
  55. package/dist_ts/pypi/index.d.ts +1 -0
  56. package/dist_ts/pypi/index.js +2 -1
  57. package/dist_ts/rubygems/classes.rubygemsregistry.d.ts +7 -1
  58. package/dist_ts/rubygems/classes.rubygemsregistry.js +35 -4
  59. package/dist_ts/rubygems/classes.rubygemsupstream.d.ts +47 -0
  60. package/dist_ts/rubygems/classes.rubygemsupstream.js +184 -0
  61. package/dist_ts/rubygems/index.d.ts +1 -0
  62. package/dist_ts/rubygems/index.js +2 -1
  63. package/dist_ts/upstream/classes.baseupstream.d.ts +112 -0
  64. package/dist_ts/upstream/classes.baseupstream.js +411 -0
  65. package/dist_ts/upstream/classes.circuitbreaker.d.ts +111 -0
  66. package/dist_ts/upstream/classes.circuitbreaker.js +192 -0
  67. package/dist_ts/upstream/classes.upstreamcache.d.ts +170 -0
  68. package/dist_ts/upstream/classes.upstreamcache.js +485 -0
  69. package/dist_ts/upstream/index.d.ts +6 -0
  70. package/dist_ts/upstream/index.js +7 -0
  71. package/dist_ts/upstream/interfaces.upstream.d.ts +169 -0
  72. package/dist_ts/upstream/interfaces.upstream.js +23 -0
  73. package/package.json +4 -2
  74. package/ts/00_commitinfo_data.ts +1 -1
  75. package/ts/cargo/classes.cargoregistry.ts +48 -3
  76. package/ts/cargo/classes.cargoupstream.ts +159 -0
  77. package/ts/cargo/index.ts +1 -0
  78. package/ts/classes.smartregistry.ts +88 -11
  79. package/ts/composer/classes.composerregistry.ts +39 -2
  80. package/ts/composer/classes.composerupstream.ts +200 -0
  81. package/ts/composer/index.ts +1 -0
  82. package/ts/core/classes.authmanager.ts +74 -412
  83. package/ts/core/classes.defaultauthprovider.ts +393 -0
  84. package/ts/core/classes.registrystorage.ts +199 -5
  85. package/ts/core/index.ts +8 -1
  86. package/ts/core/interfaces.auth.ts +91 -0
  87. package/ts/core/interfaces.core.ts +42 -0
  88. package/ts/core/interfaces.storage.ts +130 -0
  89. package/ts/index.ts +3 -0
  90. package/ts/maven/classes.mavenregistry.ts +84 -3
  91. package/ts/maven/classes.mavenupstream.ts +220 -0
  92. package/ts/maven/index.ts +1 -0
  93. package/ts/npm/classes.npmregistry.ts +61 -5
  94. package/ts/npm/classes.npmupstream.ts +260 -0
  95. package/ts/npm/index.ts +1 -0
  96. package/ts/oci/classes.ociregistry.ts +89 -17
  97. package/ts/oci/classes.ociupstream.ts +263 -0
  98. package/ts/oci/index.ts +1 -0
  99. package/ts/plugins.ts +7 -1
  100. package/ts/pypi/classes.pypiregistry.ts +68 -3
  101. package/ts/pypi/classes.pypiupstream.ts +211 -0
  102. package/ts/pypi/index.ts +1 -0
  103. package/ts/rubygems/classes.rubygemsregistry.ts +40 -3
  104. package/ts/rubygems/classes.rubygemsupstream.ts +230 -0
  105. package/ts/rubygems/index.ts +1 -0
  106. package/ts/upstream/classes.baseupstream.ts +526 -0
  107. package/ts/upstream/classes.circuitbreaker.ts +238 -0
  108. package/ts/upstream/classes.upstreamcache.ts +626 -0
  109. package/ts/upstream/index.ts +11 -0
  110. package/ts/upstream/interfaces.upstream.ts +195 -0
@@ -0,0 +1,192 @@
1
+ import { DEFAULT_RESILIENCE_CONFIG } from './interfaces.upstream.js';
2
+ /**
3
+ * Circuit breaker implementation for upstream resilience.
4
+ *
5
+ * States:
6
+ * - CLOSED: Normal operation, requests pass through
7
+ * - OPEN: Circuit is tripped, requests fail fast
8
+ * - HALF_OPEN: Testing if upstream has recovered
9
+ *
10
+ * Transitions:
11
+ * - CLOSED → OPEN: When failure count exceeds threshold
12
+ * - OPEN → HALF_OPEN: After reset timeout expires
13
+ * - HALF_OPEN → CLOSED: On successful request
14
+ * - HALF_OPEN → OPEN: On failed request
15
+ */
16
+ export class CircuitBreaker {
17
+ /** Unique identifier for logging and metrics */
18
+ id;
19
+ /** Current circuit state */
20
+ state = 'CLOSED';
21
+ /** Count of consecutive failures */
22
+ failureCount = 0;
23
+ /** Timestamp when circuit was opened */
24
+ openedAt = 0;
25
+ /** Number of successful requests in half-open state */
26
+ halfOpenSuccesses = 0;
27
+ /** Configuration */
28
+ config;
29
+ /** Number of successes required to close circuit from half-open */
30
+ halfOpenThreshold = 2;
31
+ constructor(id, config) {
32
+ this.id = id;
33
+ this.config = { ...DEFAULT_RESILIENCE_CONFIG, ...config };
34
+ }
35
+ /**
36
+ * Get current circuit state.
37
+ */
38
+ getState() {
39
+ // Check if we should transition from OPEN to HALF_OPEN
40
+ if (this.state === 'OPEN') {
41
+ const elapsed = Date.now() - this.openedAt;
42
+ if (elapsed >= this.config.circuitBreakerResetMs) {
43
+ this.transitionTo('HALF_OPEN');
44
+ }
45
+ }
46
+ return this.state;
47
+ }
48
+ /**
49
+ * Check if circuit allows requests.
50
+ * Returns true if requests should be allowed.
51
+ */
52
+ canRequest() {
53
+ const currentState = this.getState();
54
+ return currentState !== 'OPEN';
55
+ }
56
+ /**
57
+ * Record a successful request.
58
+ * May transition circuit from HALF_OPEN to CLOSED.
59
+ */
60
+ recordSuccess() {
61
+ if (this.state === 'HALF_OPEN') {
62
+ this.halfOpenSuccesses++;
63
+ if (this.halfOpenSuccesses >= this.halfOpenThreshold) {
64
+ this.transitionTo('CLOSED');
65
+ }
66
+ }
67
+ else if (this.state === 'CLOSED') {
68
+ // Reset failure count on success
69
+ this.failureCount = 0;
70
+ }
71
+ }
72
+ /**
73
+ * Record a failed request.
74
+ * May transition circuit from CLOSED/HALF_OPEN to OPEN.
75
+ */
76
+ recordFailure() {
77
+ if (this.state === 'HALF_OPEN') {
78
+ // Any failure in half-open immediately opens circuit
79
+ this.transitionTo('OPEN');
80
+ }
81
+ else if (this.state === 'CLOSED') {
82
+ this.failureCount++;
83
+ if (this.failureCount >= this.config.circuitBreakerThreshold) {
84
+ this.transitionTo('OPEN');
85
+ }
86
+ }
87
+ }
88
+ /**
89
+ * Force circuit to open state.
90
+ * Useful for manual intervention or external health checks.
91
+ */
92
+ forceOpen() {
93
+ this.transitionTo('OPEN');
94
+ }
95
+ /**
96
+ * Force circuit to closed state.
97
+ * Useful for manual intervention after fixing upstream issues.
98
+ */
99
+ forceClose() {
100
+ this.transitionTo('CLOSED');
101
+ }
102
+ /**
103
+ * Reset circuit to initial state.
104
+ */
105
+ reset() {
106
+ this.state = 'CLOSED';
107
+ this.failureCount = 0;
108
+ this.openedAt = 0;
109
+ this.halfOpenSuccesses = 0;
110
+ }
111
+ /**
112
+ * Get circuit metrics for monitoring.
113
+ */
114
+ getMetrics() {
115
+ return {
116
+ id: this.id,
117
+ state: this.getState(),
118
+ failureCount: this.failureCount,
119
+ openedAt: this.openedAt > 0 ? new Date(this.openedAt) : null,
120
+ timeUntilHalfOpen: this.state === 'OPEN'
121
+ ? Math.max(0, this.config.circuitBreakerResetMs - (Date.now() - this.openedAt))
122
+ : 0,
123
+ halfOpenSuccesses: this.halfOpenSuccesses,
124
+ threshold: this.config.circuitBreakerThreshold,
125
+ resetMs: this.config.circuitBreakerResetMs,
126
+ };
127
+ }
128
+ /**
129
+ * Transition to a new state with proper cleanup.
130
+ */
131
+ transitionTo(newState) {
132
+ const previousState = this.state;
133
+ this.state = newState;
134
+ switch (newState) {
135
+ case 'OPEN':
136
+ this.openedAt = Date.now();
137
+ this.halfOpenSuccesses = 0;
138
+ break;
139
+ case 'HALF_OPEN':
140
+ this.halfOpenSuccesses = 0;
141
+ break;
142
+ case 'CLOSED':
143
+ this.failureCount = 0;
144
+ this.openedAt = 0;
145
+ this.halfOpenSuccesses = 0;
146
+ break;
147
+ }
148
+ // Log state transition (useful for debugging and monitoring)
149
+ // In production, this would emit events or metrics
150
+ if (previousState !== newState) {
151
+ // State changed - could emit event here
152
+ }
153
+ }
154
+ }
155
+ /**
156
+ * Execute a function with circuit breaker protection.
157
+ *
158
+ * @param breaker The circuit breaker to use
159
+ * @param fn The async function to execute
160
+ * @param fallback Optional fallback function when circuit is open
161
+ * @returns The result of fn or fallback
162
+ * @throws CircuitOpenError if circuit is open and no fallback provided
163
+ */
164
+ export async function withCircuitBreaker(breaker, fn, fallback) {
165
+ if (!breaker.canRequest()) {
166
+ if (fallback) {
167
+ return fallback();
168
+ }
169
+ throw new CircuitOpenError(breaker.id);
170
+ }
171
+ try {
172
+ const result = await fn();
173
+ breaker.recordSuccess();
174
+ return result;
175
+ }
176
+ catch (error) {
177
+ breaker.recordFailure();
178
+ throw error;
179
+ }
180
+ }
181
+ /**
182
+ * Error thrown when circuit is open and no fallback is provided.
183
+ */
184
+ export class CircuitOpenError extends Error {
185
+ circuitId;
186
+ constructor(circuitId) {
187
+ super(`Circuit breaker '${circuitId}' is open`);
188
+ this.name = 'CircuitOpenError';
189
+ this.circuitId = circuitId;
190
+ }
191
+ }
192
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jaXJjdWl0YnJlYWtlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3Vwc3RyZWFtL2NsYXNzZXMuY2lyY3VpdGJyZWFrZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFckU7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILE1BQU0sT0FBTyxjQUFjO0lBQ3pCLGdEQUFnRDtJQUNoQyxFQUFFLENBQVM7SUFFM0IsNEJBQTRCO0lBQ3BCLEtBQUssR0FBa0IsUUFBUSxDQUFDO0lBRXhDLG9DQUFvQztJQUM1QixZQUFZLEdBQVcsQ0FBQyxDQUFDO0lBRWpDLHdDQUF3QztJQUNoQyxRQUFRLEdBQVcsQ0FBQyxDQUFDO0lBRTdCLHVEQUF1RDtJQUMvQyxpQkFBaUIsR0FBVyxDQUFDLENBQUM7SUFFdEMsb0JBQW9CO0lBQ0gsTUFBTSxDQUE0QjtJQUVuRCxtRUFBbUU7SUFDbEQsaUJBQWlCLEdBQVcsQ0FBQyxDQUFDO0lBRS9DLFlBQVksRUFBVSxFQUFFLE1BQTJDO1FBQ2pFLElBQUksQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDO1FBQ2IsSUFBSSxDQUFDLE1BQU0sR0FBRyxFQUFFLEdBQUcseUJBQXlCLEVBQUUsR0FBRyxNQUFNLEVBQUUsQ0FBQztJQUM1RCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxRQUFRO1FBQ2IsdURBQXVEO1FBQ3ZELElBQUksSUFBSSxDQUFDLEtBQUssS0FBSyxNQUFNLEVBQUUsQ0FBQztZQUMxQixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQztZQUMzQyxJQUFJLE9BQU8sSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLHFCQUFxQixFQUFFLENBQUM7Z0JBQ2pELElBQUksQ0FBQyxZQUFZLENBQUMsV0FBVyxDQUFDLENBQUM7WUFDakMsQ0FBQztRQUNILENBQUM7UUFD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
@@ -0,0 +1,170 @@
1
+ import type { ICacheEntry, IUpstreamCacheConfig, IUpstreamFetchContext } from './interfaces.upstream.js';
2
+ import type { IStorageBackend } from '../core/interfaces.core.js';
3
+ /**
4
+ * S3-backed upstream cache with in-memory hot layer.
5
+ *
6
+ * Features:
7
+ * - TTL-based expiration
8
+ * - Stale-while-revalidate support
9
+ * - Negative caching (404s)
10
+ * - Content-type aware caching
11
+ * - ETag support for conditional requests
12
+ * - Multi-upstream support via URL-based cache paths
13
+ * - Persistent S3 storage with in-memory hot layer
14
+ *
15
+ * Cache paths are structured as:
16
+ * cache/{escaped-upstream-url}/{protocol}:{method}:{path}
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // In-memory only (default)
21
+ * const cache = new UpstreamCache(config);
22
+ *
23
+ * // With S3 persistence
24
+ * const cache = new UpstreamCache(config, 10000, storage);
25
+ * ```
26
+ */
27
+ export declare class UpstreamCache {
28
+ /** In-memory hot cache */
29
+ private readonly memoryCache;
30
+ /** Configuration */
31
+ private readonly config;
32
+ /** Maximum in-memory cache entries */
33
+ private readonly maxMemoryEntries;
34
+ /** S3 storage backend (optional) */
35
+ private readonly storage?;
36
+ /** Cleanup interval handle */
37
+ private cleanupInterval;
38
+ constructor(config?: Partial<IUpstreamCacheConfig>, maxMemoryEntries?: number, storage?: IStorageBackend);
39
+ /**
40
+ * Check if caching is enabled.
41
+ */
42
+ isEnabled(): boolean;
43
+ /**
44
+ * Check if S3 storage is configured.
45
+ */
46
+ hasStorage(): boolean;
47
+ /**
48
+ * Get cached entry for a request context.
49
+ * Checks memory first, then falls back to S3.
50
+ * Returns null if not found or expired (unless stale-while-revalidate).
51
+ */
52
+ get(context: IUpstreamFetchContext, upstreamUrl?: string): Promise<ICacheEntry | null>;
53
+ /**
54
+ * Store a response in the cache (memory and optionally S3).
55
+ */
56
+ set(context: IUpstreamFetchContext, data: Buffer, contentType: string, headers: Record<string, string>, upstreamId: string, upstreamUrl: string, options?: ICacheSetOptions): Promise<void>;
57
+ /**
58
+ * Store a negative cache entry (404 response).
59
+ */
60
+ setNegative(context: IUpstreamFetchContext, upstreamId: string, upstreamUrl: string): Promise<void>;
61
+ /**
62
+ * Check if there's a negative cache entry for this context.
63
+ */
64
+ hasNegative(context: IUpstreamFetchContext, upstreamUrl?: string): Promise<boolean>;
65
+ /**
66
+ * Invalidate a specific cache entry.
67
+ */
68
+ invalidate(context: IUpstreamFetchContext, upstreamUrl?: string): Promise<boolean>;
69
+ /**
70
+ * Invalidate all entries matching a pattern.
71
+ * Useful for invalidating all versions of a package.
72
+ */
73
+ invalidatePattern(pattern: RegExp): Promise<number>;
74
+ /**
75
+ * Invalidate all entries from a specific upstream.
76
+ */
77
+ invalidateUpstream(upstreamId: string): Promise<number>;
78
+ /**
79
+ * Clear all cache entries (memory and S3).
80
+ */
81
+ clear(): Promise<void>;
82
+ /**
83
+ * Get cache statistics.
84
+ */
85
+ getStats(): ICacheStats;
86
+ /**
87
+ * Stop the cache and cleanup.
88
+ */
89
+ stop(): void;
90
+ /**
91
+ * Build storage path for a cache key.
92
+ * Escapes upstream URL for safe use in S3 paths.
93
+ */
94
+ private buildStoragePath;
95
+ /**
96
+ * Build storage path for cache metadata.
97
+ */
98
+ private buildMetadataPath;
99
+ /**
100
+ * Load a cache entry from S3 storage.
101
+ */
102
+ private loadFromStorage;
103
+ /**
104
+ * Save a cache entry to S3 storage.
105
+ */
106
+ private saveToStorage;
107
+ /**
108
+ * Delete a cache entry from S3 storage.
109
+ */
110
+ private deleteFromStorage;
111
+ /**
112
+ * Escape a URL for safe use in storage paths.
113
+ */
114
+ private escapeUrl;
115
+ /**
116
+ * Build a unique cache key for a request context.
117
+ * Includes escaped upstream URL for multi-upstream support.
118
+ */
119
+ private buildCacheKey;
120
+ /**
121
+ * Determine TTL based on content characteristics.
122
+ */
123
+ private determineTtl;
124
+ /**
125
+ * Check if content is immutable (content-addressable).
126
+ */
127
+ private isImmutableContent;
128
+ /**
129
+ * Evict oldest entries to make room for new ones.
130
+ */
131
+ private evictOldest;
132
+ /**
133
+ * Start periodic cleanup of expired entries.
134
+ */
135
+ private startCleanup;
136
+ /**
137
+ * Remove all expired entries from memory cache.
138
+ */
139
+ private cleanup;
140
+ }
141
+ /**
142
+ * Options for cache set operation.
143
+ */
144
+ export interface ICacheSetOptions {
145
+ /** Override TTL in seconds */
146
+ ttlSeconds?: number;
147
+ /** ETag for conditional requests */
148
+ etag?: string;
149
+ }
150
+ /**
151
+ * Cache statistics.
152
+ */
153
+ export interface ICacheStats {
154
+ /** Total number of cached entries in memory */
155
+ totalEntries: number;
156
+ /** Number of fresh (non-expired) entries */
157
+ freshEntries: number;
158
+ /** Number of stale entries (expired but still usable) */
159
+ staleEntries: number;
160
+ /** Number of negative cache entries */
161
+ negativeEntries: number;
162
+ /** Total size of cached data in bytes (memory only) */
163
+ totalSizeBytes: number;
164
+ /** Maximum allowed memory entries */
165
+ maxEntries: number;
166
+ /** Whether caching is enabled */
167
+ enabled: boolean;
168
+ /** Whether S3 storage is configured */
169
+ hasStorage: boolean;
170
+ }