@push.rocks/smartregistry 1.1.1 → 1.3.1

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 (51) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/cargo/classes.cargoregistry.d.ts +79 -0
  3. package/dist_ts/cargo/classes.cargoregistry.js +490 -0
  4. package/dist_ts/cargo/index.d.ts +5 -0
  5. package/dist_ts/cargo/index.js +6 -0
  6. package/dist_ts/cargo/interfaces.cargo.d.ts +160 -0
  7. package/dist_ts/cargo/interfaces.cargo.js +6 -0
  8. package/dist_ts/classes.smartregistry.d.ts +2 -2
  9. package/dist_ts/classes.smartregistry.js +50 -2
  10. package/dist_ts/composer/classes.composerregistry.d.ts +26 -0
  11. package/dist_ts/composer/classes.composerregistry.js +366 -0
  12. package/dist_ts/composer/helpers.composer.d.ts +35 -0
  13. package/dist_ts/composer/helpers.composer.js +120 -0
  14. package/dist_ts/composer/index.d.ts +7 -0
  15. package/dist_ts/composer/index.js +8 -0
  16. package/dist_ts/composer/interfaces.composer.d.ts +102 -0
  17. package/dist_ts/composer/interfaces.composer.js +6 -0
  18. package/dist_ts/core/classes.authmanager.d.ts +46 -1
  19. package/dist_ts/core/classes.authmanager.js +121 -12
  20. package/dist_ts/core/classes.registrystorage.d.ts +99 -0
  21. package/dist_ts/core/classes.registrystorage.js +246 -1
  22. package/dist_ts/core/interfaces.core.d.ts +4 -1
  23. package/dist_ts/index.d.ts +4 -1
  24. package/dist_ts/index.js +8 -2
  25. package/dist_ts/maven/classes.mavenregistry.d.ts +35 -0
  26. package/dist_ts/maven/classes.mavenregistry.js +407 -0
  27. package/dist_ts/maven/helpers.maven.d.ts +68 -0
  28. package/dist_ts/maven/helpers.maven.js +286 -0
  29. package/dist_ts/maven/index.d.ts +6 -0
  30. package/dist_ts/maven/index.js +7 -0
  31. package/dist_ts/maven/interfaces.maven.d.ts +116 -0
  32. package/dist_ts/maven/interfaces.maven.js +6 -0
  33. package/package.json +3 -2
  34. package/readme.md +288 -14
  35. package/ts/00_commitinfo_data.ts +1 -1
  36. package/ts/cargo/classes.cargoregistry.ts +604 -0
  37. package/ts/cargo/index.ts +6 -0
  38. package/ts/cargo/interfaces.cargo.ts +169 -0
  39. package/ts/classes.smartregistry.ts +56 -2
  40. package/ts/composer/classes.composerregistry.ts +475 -0
  41. package/ts/composer/helpers.composer.ts +139 -0
  42. package/ts/composer/index.ts +8 -0
  43. package/ts/composer/interfaces.composer.ts +111 -0
  44. package/ts/core/classes.authmanager.ts +145 -12
  45. package/ts/core/classes.registrystorage.ts +323 -0
  46. package/ts/core/interfaces.core.ts +4 -1
  47. package/ts/index.ts +10 -1
  48. package/ts/maven/classes.mavenregistry.ts +580 -0
  49. package/ts/maven/helpers.maven.ts +346 -0
  50. package/ts/maven/index.ts +7 -0
  51. package/ts/maven/interfaces.maven.ts +127 -0
@@ -0,0 +1,604 @@
1
+ import { Smartlog } from '@push.rocks/smartlog';
2
+ import { BaseRegistry } from '../core/classes.baseregistry.js';
3
+ import { RegistryStorage } from '../core/classes.registrystorage.js';
4
+ import { AuthManager } from '../core/classes.authmanager.js';
5
+ import type { IRequestContext, IResponse, IAuthToken } from '../core/interfaces.core.js';
6
+ import type {
7
+ ICargoIndexEntry,
8
+ ICargoPublishMetadata,
9
+ ICargoConfig,
10
+ ICargoError,
11
+ ICargoPublishResponse,
12
+ ICargoYankResponse,
13
+ ICargoSearchResponse,
14
+ ICargoSearchResult,
15
+ } from './interfaces.cargo.js';
16
+
17
+ /**
18
+ * Cargo/crates.io registry implementation
19
+ * Implements the sparse HTTP-based protocol
20
+ * Spec: https://doc.rust-lang.org/cargo/reference/registry-index.html
21
+ */
22
+ export class CargoRegistry extends BaseRegistry {
23
+ private storage: RegistryStorage;
24
+ private authManager: AuthManager;
25
+ private basePath: string = '/cargo';
26
+ private registryUrl: string;
27
+ private logger: Smartlog;
28
+
29
+ constructor(
30
+ storage: RegistryStorage,
31
+ authManager: AuthManager,
32
+ basePath: string = '/cargo',
33
+ registryUrl: string = 'http://localhost:5000/cargo'
34
+ ) {
35
+ super();
36
+ this.storage = storage;
37
+ this.authManager = authManager;
38
+ this.basePath = basePath;
39
+ this.registryUrl = registryUrl;
40
+
41
+ // Initialize logger
42
+ this.logger = new Smartlog({
43
+ logContext: {
44
+ company: 'push.rocks',
45
+ companyunit: 'smartregistry',
46
+ containerName: 'cargo-registry',
47
+ environment: (process.env.NODE_ENV as any) || 'development',
48
+ runtime: 'node',
49
+ zone: 'cargo'
50
+ }
51
+ });
52
+ this.logger.enableConsole();
53
+ }
54
+
55
+ public async init(): Promise<void> {
56
+ // Initialize config.json if not exists
57
+ const existingConfig = await this.storage.getCargoConfig();
58
+ if (!existingConfig) {
59
+ const config: ICargoConfig = {
60
+ dl: `${this.registryUrl}/api/v1/crates/{crate}/{version}/download`,
61
+ api: this.registryUrl,
62
+ };
63
+ await this.storage.putCargoConfig(config);
64
+ this.logger.log('info', 'Initialized Cargo registry config', { config });
65
+ }
66
+ }
67
+
68
+ public getBasePath(): string {
69
+ return this.basePath;
70
+ }
71
+
72
+ public async handleRequest(context: IRequestContext): Promise<IResponse> {
73
+ const path = context.path.replace(this.basePath, '');
74
+
75
+ // Extract token (Cargo uses Authorization header WITHOUT "Bearer" prefix)
76
+ const authHeader = context.headers['authorization'] || context.headers['Authorization'];
77
+ const token = authHeader ? await this.authManager.validateToken(authHeader, 'cargo') : null;
78
+
79
+ this.logger.log('debug', `handleRequest: ${context.method} ${path}`, {
80
+ method: context.method,
81
+ path,
82
+ hasAuth: !!token
83
+ });
84
+
85
+ // Config endpoint (required for sparse protocol)
86
+ if (path === '/config.json') {
87
+ return this.handleConfigJson();
88
+ }
89
+
90
+ // API endpoints
91
+ if (path.startsWith('/api/v1/')) {
92
+ return this.handleApiRequest(path, context, token);
93
+ }
94
+
95
+ // Index files (sparse protocol)
96
+ return this.handleIndexRequest(path);
97
+ }
98
+
99
+ /**
100
+ * Check if token has permission for resource
101
+ */
102
+ protected async checkPermission(
103
+ token: IAuthToken | null,
104
+ resource: string,
105
+ action: string
106
+ ): Promise<boolean> {
107
+ if (!token) return false;
108
+ return this.authManager.authorize(token, `cargo:crate:${resource}`, action);
109
+ }
110
+
111
+ /**
112
+ * Handle API requests (/api/v1/*)
113
+ */
114
+ private async handleApiRequest(
115
+ path: string,
116
+ context: IRequestContext,
117
+ token: IAuthToken | null
118
+ ): Promise<IResponse> {
119
+ // Publish: PUT /api/v1/crates/new
120
+ if (path === '/api/v1/crates/new' && context.method === 'PUT') {
121
+ return this.handlePublish(context.body as Buffer, token);
122
+ }
123
+
124
+ // Download: GET /api/v1/crates/{crate}/{version}/download
125
+ const downloadMatch = path.match(/^\/api\/v1\/crates\/([^\/]+)\/([^\/]+)\/download$/);
126
+ if (downloadMatch && context.method === 'GET') {
127
+ return this.handleDownload(downloadMatch[1], downloadMatch[2]);
128
+ }
129
+
130
+ // Yank: DELETE /api/v1/crates/{crate}/{version}/yank
131
+ const yankMatch = path.match(/^\/api\/v1\/crates\/([^\/]+)\/([^\/]+)\/yank$/);
132
+ if (yankMatch && context.method === 'DELETE') {
133
+ return this.handleYank(yankMatch[1], yankMatch[2], token);
134
+ }
135
+
136
+ // Unyank: PUT /api/v1/crates/{crate}/{version}/unyank
137
+ const unyankMatch = path.match(/^\/api\/v1\/crates\/([^\/]+)\/([^\/]+)\/unyank$/);
138
+ if (unyankMatch && context.method === 'PUT') {
139
+ return this.handleUnyank(unyankMatch[1], unyankMatch[2], token);
140
+ }
141
+
142
+ // Search: GET /api/v1/crates?q={query}
143
+ if (path.startsWith('/api/v1/crates') && context.method === 'GET') {
144
+ const query = context.query?.q || '';
145
+ const perPage = parseInt(context.query?.per_page || '10', 10);
146
+ return this.handleSearch(query, perPage);
147
+ }
148
+
149
+ return {
150
+ status: 404,
151
+ headers: { 'Content-Type': 'application/json' },
152
+ body: this.createError('API endpoint not found'),
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Handle index file requests
158
+ * Paths: /1/{name}, /2/{name}, /3/{c}/{name}, /{p1}/{p2}/{name}
159
+ */
160
+ private async handleIndexRequest(path: string): Promise<IResponse> {
161
+ // Parse index paths to extract crate name
162
+ const pathParts = path.split('/').filter(p => p);
163
+ let crateName: string | null = null;
164
+
165
+ if (pathParts.length === 2 && pathParts[0] === '1') {
166
+ // 1-character names: /1/{name}
167
+ crateName = pathParts[1];
168
+ } else if (pathParts.length === 2 && pathParts[0] === '2') {
169
+ // 2-character names: /2/{name}
170
+ crateName = pathParts[1];
171
+ } else if (pathParts.length === 3 && pathParts[0] === '3') {
172
+ // 3-character names: /3/{c}/{name}
173
+ crateName = pathParts[2];
174
+ } else if (pathParts.length === 3) {
175
+ // 4+ character names: /{p1}/{p2}/{name}
176
+ crateName = pathParts[2];
177
+ }
178
+
179
+ if (!crateName) {
180
+ return {
181
+ status: 404,
182
+ headers: { 'Content-Type': 'text/plain' },
183
+ body: Buffer.from(''),
184
+ };
185
+ }
186
+
187
+ return this.handleIndexFile(crateName);
188
+ }
189
+
190
+ /**
191
+ * Serve config.json
192
+ */
193
+ private async handleConfigJson(): Promise<IResponse> {
194
+ const config = await this.storage.getCargoConfig();
195
+
196
+ return {
197
+ status: 200,
198
+ headers: { 'Content-Type': 'application/json' },
199
+ body: config || {
200
+ dl: `${this.registryUrl}/api/v1/crates/{crate}/{version}/download`,
201
+ api: this.registryUrl,
202
+ },
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Serve index file for a crate
208
+ */
209
+ private async handleIndexFile(crateName: string): Promise<IResponse> {
210
+ const index = await this.storage.getCargoIndex(crateName);
211
+
212
+ if (!index || index.length === 0) {
213
+ return {
214
+ status: 404,
215
+ headers: { 'Content-Type': 'text/plain' },
216
+ body: Buffer.from(''),
217
+ };
218
+ }
219
+
220
+ // Return newline-delimited JSON
221
+ const data = index.map(e => JSON.stringify(e)).join('\n') + '\n';
222
+
223
+ // Calculate ETag for caching
224
+ const crypto = await import('crypto');
225
+ const etag = `"${crypto.createHash('sha256').update(data).digest('hex')}"`;
226
+
227
+ return {
228
+ status: 200,
229
+ headers: {
230
+ 'Content-Type': 'text/plain',
231
+ 'ETag': etag,
232
+ },
233
+ body: Buffer.from(data, 'utf-8'),
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Parse binary publish request
239
+ * Format: [4 bytes JSON len][JSON][4 bytes crate len][.crate file]
240
+ */
241
+ private parsePublishRequest(body: Buffer): {
242
+ metadata: ICargoPublishMetadata;
243
+ crateFile: Buffer;
244
+ } {
245
+ let offset = 0;
246
+
247
+ // Read JSON length (4 bytes, u32 little-endian)
248
+ if (body.length < 4) {
249
+ throw new Error('Invalid publish request: body too short');
250
+ }
251
+ const jsonLength = body.readUInt32LE(offset);
252
+ offset += 4;
253
+
254
+ // Read JSON metadata
255
+ if (body.length < offset + jsonLength) {
256
+ throw new Error('Invalid publish request: JSON data incomplete');
257
+ }
258
+ const jsonBuffer = body.slice(offset, offset + jsonLength);
259
+ const metadata = JSON.parse(jsonBuffer.toString('utf-8'));
260
+ offset += jsonLength;
261
+
262
+ // Read crate file length (4 bytes, u32 little-endian)
263
+ if (body.length < offset + 4) {
264
+ throw new Error('Invalid publish request: crate length missing');
265
+ }
266
+ const crateLength = body.readUInt32LE(offset);
267
+ offset += 4;
268
+
269
+ // Read crate file
270
+ if (body.length < offset + crateLength) {
271
+ throw new Error('Invalid publish request: crate data incomplete');
272
+ }
273
+ const crateFile = body.slice(offset, offset + crateLength);
274
+
275
+ return { metadata, crateFile };
276
+ }
277
+
278
+ /**
279
+ * Handle crate publish
280
+ */
281
+ private async handlePublish(
282
+ body: Buffer,
283
+ token: IAuthToken | null
284
+ ): Promise<IResponse> {
285
+ this.logger.log('info', 'handlePublish: received publish request', {
286
+ bodyLength: body?.length || 0,
287
+ hasAuth: !!token
288
+ });
289
+
290
+ // Check authorization
291
+ if (!token) {
292
+ return {
293
+ status: 403,
294
+ headers: { 'Content-Type': 'application/json' },
295
+ body: this.createError('Authentication required'),
296
+ };
297
+ }
298
+
299
+ // Parse binary request
300
+ let metadata: ICargoPublishMetadata;
301
+ let crateFile: Buffer;
302
+ try {
303
+ const parsed = this.parsePublishRequest(body);
304
+ metadata = parsed.metadata;
305
+ crateFile = parsed.crateFile;
306
+ } catch (error) {
307
+ this.logger.log('error', 'handlePublish: parse error', { error: error.message });
308
+ return {
309
+ status: 400,
310
+ headers: { 'Content-Type': 'application/json' },
311
+ body: this.createError(`Invalid request format: ${error.message}`),
312
+ };
313
+ }
314
+
315
+ // Validate crate name
316
+ if (!this.validateCrateName(metadata.name)) {
317
+ return {
318
+ status: 400,
319
+ headers: { 'Content-Type': 'application/json' },
320
+ body: this.createError('Invalid crate name'),
321
+ };
322
+ }
323
+
324
+ // Check permission
325
+ const hasPermission = await this.checkPermission(token, metadata.name, 'write');
326
+ if (!hasPermission) {
327
+ this.logger.log('warn', 'handlePublish: unauthorized', {
328
+ crateName: metadata.name,
329
+ userId: token.userId
330
+ });
331
+ return {
332
+ status: 403,
333
+ headers: { 'Content-Type': 'application/json' },
334
+ body: this.createError('Insufficient permissions'),
335
+ };
336
+ }
337
+
338
+ // Calculate SHA256 checksum
339
+ const crypto = await import('crypto');
340
+ const cksum = crypto.createHash('sha256').update(crateFile).digest('hex');
341
+
342
+ // Create index entry
343
+ const indexEntry: ICargoIndexEntry = {
344
+ name: metadata.name,
345
+ vers: metadata.vers,
346
+ deps: metadata.deps,
347
+ cksum,
348
+ features: metadata.features,
349
+ yanked: false,
350
+ links: metadata.links || null,
351
+ v: 2,
352
+ rust_version: metadata.rust_version,
353
+ };
354
+
355
+ // Check for duplicate version
356
+ const existingIndex = await this.storage.getCargoIndex(metadata.name) || [];
357
+ if (existingIndex.some(e => e.vers === metadata.vers)) {
358
+ return {
359
+ status: 400,
360
+ headers: { 'Content-Type': 'application/json' },
361
+ body: this.createError(`Version ${metadata.vers} already exists`),
362
+ };
363
+ }
364
+
365
+ // Store crate file
366
+ await this.storage.putCargoCrate(metadata.name, metadata.vers, crateFile);
367
+
368
+ // Update index (append new version)
369
+ existingIndex.push(indexEntry);
370
+ await this.storage.putCargoIndex(metadata.name, existingIndex);
371
+
372
+ this.logger.log('success', 'handlePublish: published crate', {
373
+ name: metadata.name,
374
+ version: metadata.vers,
375
+ checksum: cksum
376
+ });
377
+
378
+ const response: ICargoPublishResponse = {
379
+ warnings: {
380
+ invalid_categories: [],
381
+ invalid_badges: [],
382
+ other: [],
383
+ },
384
+ };
385
+
386
+ return {
387
+ status: 200,
388
+ headers: { 'Content-Type': 'application/json' },
389
+ body: response,
390
+ };
391
+ }
392
+
393
+ /**
394
+ * Handle crate download
395
+ */
396
+ private async handleDownload(
397
+ crateName: string,
398
+ version: string
399
+ ): Promise<IResponse> {
400
+ this.logger.log('debug', 'handleDownload', { crate: crateName, version });
401
+
402
+ const crateFile = await this.storage.getCargoCrate(crateName, version);
403
+
404
+ if (!crateFile) {
405
+ return {
406
+ status: 404,
407
+ headers: { 'Content-Type': 'application/json' },
408
+ body: this.createError('Crate not found'),
409
+ };
410
+ }
411
+
412
+ return {
413
+ status: 200,
414
+ headers: {
415
+ 'Content-Type': 'application/gzip',
416
+ 'Content-Length': crateFile.length.toString(),
417
+ 'Content-Disposition': `attachment; filename="${crateName}-${version}.crate"`,
418
+ },
419
+ body: crateFile,
420
+ };
421
+ }
422
+
423
+ /**
424
+ * Handle yank operation
425
+ */
426
+ private async handleYank(
427
+ crateName: string,
428
+ version: string,
429
+ token: IAuthToken | null
430
+ ): Promise<IResponse> {
431
+ return this.handleYankOperation(crateName, version, token, true);
432
+ }
433
+
434
+ /**
435
+ * Handle unyank operation
436
+ */
437
+ private async handleUnyank(
438
+ crateName: string,
439
+ version: string,
440
+ token: IAuthToken | null
441
+ ): Promise<IResponse> {
442
+ return this.handleYankOperation(crateName, version, token, false);
443
+ }
444
+
445
+ /**
446
+ * Handle yank/unyank operation
447
+ */
448
+ private async handleYankOperation(
449
+ crateName: string,
450
+ version: string,
451
+ token: IAuthToken | null,
452
+ yank: boolean
453
+ ): Promise<IResponse> {
454
+ this.logger.log('info', `handle${yank ? 'Yank' : 'Unyank'}`, {
455
+ crate: crateName,
456
+ version,
457
+ hasAuth: !!token
458
+ });
459
+
460
+ // Check authorization
461
+ if (!token) {
462
+ return {
463
+ status: 403,
464
+ headers: { 'Content-Type': 'application/json' },
465
+ body: this.createError('Authentication required'),
466
+ };
467
+ }
468
+
469
+ // Check permission
470
+ const hasPermission = await this.checkPermission(token, crateName, 'write');
471
+ if (!hasPermission) {
472
+ return {
473
+ status: 403,
474
+ headers: { 'Content-Type': 'application/json' },
475
+ body: this.createError('Insufficient permissions'),
476
+ };
477
+ }
478
+
479
+ // Load index
480
+ const index = await this.storage.getCargoIndex(crateName);
481
+ if (!index) {
482
+ return {
483
+ status: 404,
484
+ headers: { 'Content-Type': 'application/json' },
485
+ body: this.createError('Crate not found'),
486
+ };
487
+ }
488
+
489
+ // Find version
490
+ const entry = index.find(e => e.vers === version);
491
+ if (!entry) {
492
+ return {
493
+ status: 404,
494
+ headers: { 'Content-Type': 'application/json' },
495
+ body: this.createError('Version not found'),
496
+ };
497
+ }
498
+
499
+ // Update yank status
500
+ entry.yanked = yank;
501
+
502
+ // Save index (NOTE: do NOT delete .crate file)
503
+ await this.storage.putCargoIndex(crateName, index);
504
+
505
+ this.logger.log('success', `${yank ? 'Yanked' : 'Unyanked'} version`, {
506
+ crate: crateName,
507
+ version
508
+ });
509
+
510
+ const response: ICargoYankResponse = { ok: true };
511
+
512
+ return {
513
+ status: 200,
514
+ headers: { 'Content-Type': 'application/json' },
515
+ body: response,
516
+ };
517
+ }
518
+
519
+ /**
520
+ * Handle search
521
+ */
522
+ private async handleSearch(query: string, perPage: number): Promise<IResponse> {
523
+ this.logger.log('debug', 'handleSearch', { query, perPage });
524
+
525
+ const results: ICargoSearchResult[] = [];
526
+
527
+ try {
528
+ // List all index paths
529
+ const indexPaths = await this.storage.listObjects('cargo/index/');
530
+
531
+ // Extract unique crate names
532
+ const crateNames = new Set<string>();
533
+ for (const path of indexPaths) {
534
+ // Parse path to extract crate name
535
+ const parts = path.split('/');
536
+ if (parts.length >= 3) {
537
+ const name = parts[parts.length - 1];
538
+ if (name && !name.includes('.')) {
539
+ crateNames.add(name);
540
+ }
541
+ }
542
+ }
543
+
544
+ this.logger.log('debug', `handleSearch: found ${crateNames.size} crates`, {
545
+ totalCrates: crateNames.size
546
+ });
547
+
548
+ // Filter and process matching crates
549
+ for (const name of crateNames) {
550
+ if (!query || name.toLowerCase().includes(query.toLowerCase())) {
551
+ const index = await this.storage.getCargoIndex(name);
552
+ if (index && index.length > 0) {
553
+ // Find latest non-yanked version
554
+ const nonYanked = index.filter(e => !e.yanked);
555
+ if (nonYanked.length > 0) {
556
+ // Sort by version (simplified - should use semver)
557
+ const sorted = [...nonYanked].sort((a, b) => b.vers.localeCompare(a.vers));
558
+
559
+ results.push({
560
+ name: sorted[0].name,
561
+ max_version: sorted[0].vers,
562
+ description: '', // Would need to store separately
563
+ });
564
+
565
+ if (results.length >= perPage) break;
566
+ }
567
+ }
568
+ }
569
+ }
570
+ } catch (error) {
571
+ this.logger.log('error', 'handleSearch: error', { error: error.message });
572
+ }
573
+
574
+ const response: ICargoSearchResponse = {
575
+ crates: results,
576
+ meta: {
577
+ total: results.length,
578
+ },
579
+ };
580
+
581
+ return {
582
+ status: 200,
583
+ headers: { 'Content-Type': 'application/json' },
584
+ body: response,
585
+ };
586
+ }
587
+
588
+ /**
589
+ * Validate crate name
590
+ * Rules: lowercase alphanumeric + _ and -, length 1-64
591
+ */
592
+ private validateCrateName(name: string): boolean {
593
+ return /^[a-z0-9_-]+$/.test(name) && name.length >= 1 && name.length <= 64;
594
+ }
595
+
596
+ /**
597
+ * Create error response
598
+ */
599
+ private createError(detail: string): ICargoError {
600
+ return {
601
+ errors: [{ detail }],
602
+ };
603
+ }
604
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Cargo/crates.io Registry module exports
3
+ */
4
+
5
+ export { CargoRegistry } from './classes.cargoregistry.js';
6
+ export * from './interfaces.cargo.js';