@push.rocks/smartregistry 2.2.2 → 2.3.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 (91) 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.js +8 -8
  9. package/dist_ts/composer/classes.composerregistry.d.ts +7 -1
  10. package/dist_ts/composer/classes.composerregistry.js +34 -3
  11. package/dist_ts/composer/classes.composerupstream.d.ts +40 -0
  12. package/dist_ts/composer/classes.composerupstream.js +159 -0
  13. package/dist_ts/composer/index.d.ts +1 -0
  14. package/dist_ts/composer/index.js +2 -1
  15. package/dist_ts/core/interfaces.core.d.ts +3 -0
  16. package/dist_ts/index.d.ts +1 -0
  17. package/dist_ts/index.js +3 -1
  18. package/dist_ts/maven/classes.mavenregistry.d.ts +12 -1
  19. package/dist_ts/maven/classes.mavenregistry.js +69 -4
  20. package/dist_ts/maven/classes.mavenupstream.d.ts +45 -0
  21. package/dist_ts/maven/classes.mavenupstream.js +153 -0
  22. package/dist_ts/maven/index.d.ts +1 -0
  23. package/dist_ts/maven/index.js +2 -1
  24. package/dist_ts/npm/classes.npmregistry.d.ts +3 -1
  25. package/dist_ts/npm/classes.npmregistry.js +55 -6
  26. package/dist_ts/npm/classes.npmupstream.d.ts +51 -0
  27. package/dist_ts/npm/classes.npmupstream.js +206 -0
  28. package/dist_ts/npm/index.d.ts +1 -0
  29. package/dist_ts/npm/index.js +2 -1
  30. package/dist_ts/oci/classes.ociregistry.d.ts +4 -1
  31. package/dist_ts/oci/classes.ociregistry.js +78 -17
  32. package/dist_ts/oci/classes.ociupstream.d.ts +62 -0
  33. package/dist_ts/oci/classes.ociupstream.js +206 -0
  34. package/dist_ts/oci/index.d.ts +1 -0
  35. package/dist_ts/oci/index.js +2 -1
  36. package/dist_ts/plugins.d.ts +4 -1
  37. package/dist_ts/plugins.js +6 -2
  38. package/dist_ts/pypi/classes.pypiregistry.d.ts +7 -1
  39. package/dist_ts/pypi/classes.pypiregistry.js +60 -4
  40. package/dist_ts/pypi/classes.pypiupstream.d.ts +48 -0
  41. package/dist_ts/pypi/classes.pypiupstream.js +165 -0
  42. package/dist_ts/pypi/index.d.ts +1 -0
  43. package/dist_ts/pypi/index.js +2 -1
  44. package/dist_ts/rubygems/classes.rubygemsregistry.d.ts +7 -1
  45. package/dist_ts/rubygems/classes.rubygemsregistry.js +35 -4
  46. package/dist_ts/rubygems/classes.rubygemsupstream.d.ts +47 -0
  47. package/dist_ts/rubygems/classes.rubygemsupstream.js +184 -0
  48. package/dist_ts/rubygems/index.d.ts +1 -0
  49. package/dist_ts/rubygems/index.js +2 -1
  50. package/dist_ts/upstream/classes.baseupstream.d.ts +112 -0
  51. package/dist_ts/upstream/classes.baseupstream.js +409 -0
  52. package/dist_ts/upstream/classes.circuitbreaker.d.ts +111 -0
  53. package/dist_ts/upstream/classes.circuitbreaker.js +192 -0
  54. package/dist_ts/upstream/classes.upstreamcache.d.ts +123 -0
  55. package/dist_ts/upstream/classes.upstreamcache.js +328 -0
  56. package/dist_ts/upstream/index.d.ts +6 -0
  57. package/dist_ts/upstream/index.js +7 -0
  58. package/dist_ts/upstream/interfaces.upstream.d.ts +169 -0
  59. package/dist_ts/upstream/interfaces.upstream.js +23 -0
  60. package/package.json +4 -2
  61. package/ts/00_commitinfo_data.ts +1 -1
  62. package/ts/cargo/classes.cargoregistry.ts +48 -3
  63. package/ts/cargo/classes.cargoupstream.ts +159 -0
  64. package/ts/cargo/index.ts +1 -0
  65. package/ts/classes.smartregistry.ts +49 -7
  66. package/ts/composer/classes.composerregistry.ts +39 -2
  67. package/ts/composer/classes.composerupstream.ts +200 -0
  68. package/ts/composer/index.ts +1 -0
  69. package/ts/core/interfaces.core.ts +3 -0
  70. package/ts/index.ts +3 -0
  71. package/ts/maven/classes.mavenregistry.ts +84 -3
  72. package/ts/maven/classes.mavenupstream.ts +220 -0
  73. package/ts/maven/index.ts +1 -0
  74. package/ts/npm/classes.npmregistry.ts +61 -5
  75. package/ts/npm/classes.npmupstream.ts +260 -0
  76. package/ts/npm/index.ts +1 -0
  77. package/ts/oci/classes.ociregistry.ts +89 -17
  78. package/ts/oci/classes.ociupstream.ts +263 -0
  79. package/ts/oci/index.ts +1 -0
  80. package/ts/plugins.ts +7 -1
  81. package/ts/pypi/classes.pypiregistry.ts +68 -3
  82. package/ts/pypi/classes.pypiupstream.ts +211 -0
  83. package/ts/pypi/index.ts +1 -0
  84. package/ts/rubygems/classes.rubygemsregistry.ts +40 -3
  85. package/ts/rubygems/classes.rubygemsupstream.ts +230 -0
  86. package/ts/rubygems/index.ts +1 -0
  87. package/ts/upstream/classes.baseupstream.ts +521 -0
  88. package/ts/upstream/classes.circuitbreaker.ts +238 -0
  89. package/ts/upstream/classes.upstreamcache.ts +423 -0
  90. package/ts/upstream/index.ts +11 -0
  91. package/ts/upstream/interfaces.upstream.ts +195 -0
@@ -3,6 +3,7 @@ import { BaseRegistry } from '../core/classes.baseregistry.js';
3
3
  import { RegistryStorage } from '../core/classes.registrystorage.js';
4
4
  import { AuthManager } from '../core/classes.authmanager.js';
5
5
  import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
6
+ import type { IProtocolUpstreamConfig } from '../upstream/interfaces.upstream.js';
6
7
  import type {
7
8
  IRubyGemsMetadata,
8
9
  IRubyGemsVersionMetadata,
@@ -12,6 +13,7 @@ import type {
12
13
  ICompactIndexInfoEntry,
13
14
  } from './interfaces.rubygems.js';
14
15
  import * as helpers from './helpers.rubygems.js';
16
+ import { RubygemsUpstream } from './classes.rubygemsupstream.js';
15
17
 
16
18
  /**
17
19
  * RubyGems registry implementation
@@ -23,12 +25,14 @@ export class RubyGemsRegistry extends BaseRegistry {
23
25
  private basePath: string = '/rubygems';
24
26
  private registryUrl: string;
25
27
  private logger: Smartlog;
28
+ private upstream: RubygemsUpstream | null = null;
26
29
 
27
30
  constructor(
28
31
  storage: RegistryStorage,
29
32
  authManager: AuthManager,
30
33
  basePath: string = '/rubygems',
31
- registryUrl: string = 'http://localhost:5000/rubygems'
34
+ registryUrl: string = 'http://localhost:5000/rubygems',
35
+ upstreamConfig?: IProtocolUpstreamConfig
32
36
  ) {
33
37
  super();
34
38
  this.storage = storage;
@@ -48,6 +52,20 @@ export class RubyGemsRegistry extends BaseRegistry {
48
52
  }
49
53
  });
50
54
  this.logger.enableConsole();
55
+
56
+ // Initialize upstream if configured
57
+ if (upstreamConfig?.enabled) {
58
+ this.upstream = new RubygemsUpstream(upstreamConfig, this.logger);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Clean up resources (timers, connections, etc.)
64
+ */
65
+ public destroy(): void {
66
+ if (this.upstream) {
67
+ this.upstream.stop();
68
+ }
51
69
  }
52
70
 
53
71
  public async init(): Promise<void> {
@@ -215,7 +233,17 @@ export class RubyGemsRegistry extends BaseRegistry {
215
233
  * Handle /info/{gem} endpoint (Compact Index)
216
234
  */
217
235
  private async handleInfoFile(gemName: string): Promise<IResponse> {
218
- const content = await this.storage.getRubyGemsInfo(gemName);
236
+ let content = await this.storage.getRubyGemsInfo(gemName);
237
+
238
+ // Try upstream if not found locally
239
+ if (!content && this.upstream) {
240
+ const upstreamInfo = await this.upstream.fetchInfo(gemName);
241
+ if (upstreamInfo) {
242
+ // Cache locally
243
+ await this.storage.putRubyGemsInfo(gemName, upstreamInfo);
244
+ content = upstreamInfo;
245
+ }
246
+ }
219
247
 
220
248
  if (!content) {
221
249
  return {
@@ -245,12 +273,21 @@ export class RubyGemsRegistry extends BaseRegistry {
245
273
  return this.errorResponse(400, 'Invalid gem filename');
246
274
  }
247
275
 
248
- const gemData = await this.storage.getRubyGemsGem(
276
+ let gemData = await this.storage.getRubyGemsGem(
249
277
  parsed.name,
250
278
  parsed.version,
251
279
  parsed.platform
252
280
  );
253
281
 
282
+ // Try upstream if not found locally
283
+ if (!gemData && this.upstream) {
284
+ gemData = await this.upstream.fetchGem(parsed.name, parsed.version);
285
+ if (gemData) {
286
+ // Cache locally
287
+ await this.storage.putRubyGemsGem(parsed.name, parsed.version, gemData, parsed.platform);
288
+ }
289
+ }
290
+
254
291
  if (!gemData) {
255
292
  return this.errorResponse(404, 'Gem not found');
256
293
  }
@@ -0,0 +1,230 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { BaseUpstream } from '../upstream/classes.baseupstream.js';
3
+ import type {
4
+ IProtocolUpstreamConfig,
5
+ IUpstreamFetchContext,
6
+ IUpstreamRegistryConfig,
7
+ } from '../upstream/interfaces.upstream.js';
8
+
9
+ /**
10
+ * RubyGems-specific upstream implementation.
11
+ *
12
+ * Handles:
13
+ * - Compact Index format (/versions, /info/{gem}, /names)
14
+ * - Gem file (.gem) downloading
15
+ * - Gem spec fetching
16
+ * - HTTP Range requests for incremental updates
17
+ */
18
+ export class RubygemsUpstream extends BaseUpstream {
19
+ protected readonly protocolName = 'rubygems';
20
+
21
+ constructor(
22
+ config: IProtocolUpstreamConfig,
23
+ logger?: plugins.smartlog.Smartlog,
24
+ ) {
25
+ super(config, logger);
26
+ }
27
+
28
+ /**
29
+ * Fetch the /versions file (master list of all gems).
30
+ */
31
+ public async fetchVersions(etag?: string): Promise<{ data: string; etag?: string } | null> {
32
+ const headers: Record<string, string> = {
33
+ 'accept': 'text/plain',
34
+ };
35
+
36
+ if (etag) {
37
+ headers['if-none-match'] = etag;
38
+ }
39
+
40
+ const context: IUpstreamFetchContext = {
41
+ protocol: 'rubygems',
42
+ resource: '*',
43
+ resourceType: 'versions',
44
+ path: '/versions',
45
+ method: 'GET',
46
+ headers,
47
+ query: {},
48
+ };
49
+
50
+ const result = await this.fetch(context);
51
+
52
+ if (!result || !result.success) {
53
+ return null;
54
+ }
55
+
56
+ let data: string;
57
+ if (Buffer.isBuffer(result.body)) {
58
+ data = result.body.toString('utf8');
59
+ } else if (typeof result.body === 'string') {
60
+ data = result.body;
61
+ } else {
62
+ return null;
63
+ }
64
+
65
+ return {
66
+ data,
67
+ etag: result.headers['etag'],
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Fetch gem info file (/info/{gemname}).
73
+ */
74
+ public async fetchInfo(gemName: string): Promise<string | null> {
75
+ const context: IUpstreamFetchContext = {
76
+ protocol: 'rubygems',
77
+ resource: gemName,
78
+ resourceType: 'info',
79
+ path: `/info/${gemName}`,
80
+ method: 'GET',
81
+ headers: {
82
+ 'accept': 'text/plain',
83
+ },
84
+ query: {},
85
+ };
86
+
87
+ const result = await this.fetch(context);
88
+
89
+ if (!result || !result.success) {
90
+ return null;
91
+ }
92
+
93
+ if (Buffer.isBuffer(result.body)) {
94
+ return result.body.toString('utf8');
95
+ }
96
+
97
+ return typeof result.body === 'string' ? result.body : null;
98
+ }
99
+
100
+ /**
101
+ * Fetch the /names file (list of all gem names).
102
+ */
103
+ public async fetchNames(): Promise<string | null> {
104
+ const context: IUpstreamFetchContext = {
105
+ protocol: 'rubygems',
106
+ resource: '*',
107
+ resourceType: 'names',
108
+ path: '/names',
109
+ method: 'GET',
110
+ headers: {
111
+ 'accept': 'text/plain',
112
+ },
113
+ query: {},
114
+ };
115
+
116
+ const result = await this.fetch(context);
117
+
118
+ if (!result || !result.success) {
119
+ return null;
120
+ }
121
+
122
+ if (Buffer.isBuffer(result.body)) {
123
+ return result.body.toString('utf8');
124
+ }
125
+
126
+ return typeof result.body === 'string' ? result.body : null;
127
+ }
128
+
129
+ /**
130
+ * Fetch a gem file.
131
+ */
132
+ public async fetchGem(gemName: string, version: string): Promise<Buffer | null> {
133
+ const path = `/gems/${gemName}-${version}.gem`;
134
+
135
+ const context: IUpstreamFetchContext = {
136
+ protocol: 'rubygems',
137
+ resource: gemName,
138
+ resourceType: 'gem',
139
+ path,
140
+ method: 'GET',
141
+ headers: {
142
+ 'accept': 'application/octet-stream',
143
+ },
144
+ query: {},
145
+ };
146
+
147
+ const result = await this.fetch(context);
148
+
149
+ if (!result || !result.success) {
150
+ return null;
151
+ }
152
+
153
+ return Buffer.isBuffer(result.body) ? result.body : Buffer.from(result.body);
154
+ }
155
+
156
+ /**
157
+ * Fetch gem spec (quick spec).
158
+ */
159
+ public async fetchQuickSpec(gemName: string, version: string): Promise<Buffer | null> {
160
+ const path = `/quick/Marshal.4.8/${gemName}-${version}.gemspec.rz`;
161
+
162
+ const context: IUpstreamFetchContext = {
163
+ protocol: 'rubygems',
164
+ resource: gemName,
165
+ resourceType: 'spec',
166
+ path,
167
+ method: 'GET',
168
+ headers: {
169
+ 'accept': 'application/octet-stream',
170
+ },
171
+ query: {},
172
+ };
173
+
174
+ const result = await this.fetch(context);
175
+
176
+ if (!result || !result.success) {
177
+ return null;
178
+ }
179
+
180
+ return Buffer.isBuffer(result.body) ? result.body : Buffer.from(result.body);
181
+ }
182
+
183
+ /**
184
+ * Fetch gem versions JSON from API.
185
+ */
186
+ public async fetchVersionsJson(gemName: string): Promise<any[] | null> {
187
+ const path = `/api/v1/versions/${gemName}.json`;
188
+
189
+ const context: IUpstreamFetchContext = {
190
+ protocol: 'rubygems',
191
+ resource: gemName,
192
+ resourceType: 'versions-json',
193
+ path,
194
+ method: 'GET',
195
+ headers: {
196
+ 'accept': 'application/json',
197
+ },
198
+ query: {},
199
+ };
200
+
201
+ const result = await this.fetch(context);
202
+
203
+ if (!result || !result.success) {
204
+ return null;
205
+ }
206
+
207
+ if (Buffer.isBuffer(result.body)) {
208
+ return JSON.parse(result.body.toString('utf8'));
209
+ }
210
+
211
+ return Array.isArray(result.body) ? result.body : null;
212
+ }
213
+
214
+ /**
215
+ * Override URL building for RubyGems-specific handling.
216
+ */
217
+ protected buildUpstreamUrl(
218
+ upstream: IUpstreamRegistryConfig,
219
+ context: IUpstreamFetchContext,
220
+ ): string {
221
+ let baseUrl = upstream.url;
222
+
223
+ // Remove trailing slash
224
+ if (baseUrl.endsWith('/')) {
225
+ baseUrl = baseUrl.slice(0, -1);
226
+ }
227
+
228
+ return `${baseUrl}${context.path}`;
229
+ }
230
+ }
@@ -5,4 +5,5 @@
5
5
 
6
6
  export * from './interfaces.rubygems.js';
7
7
  export * from './classes.rubygemsregistry.js';
8
+ export { RubygemsUpstream } from './classes.rubygemsupstream.js';
8
9
  export * as rubygemsHelpers from './helpers.rubygems.js';