4
4
* SPDX-License-Identifier: Apache-2.0
5
5
*/
6
6
7
+ import {
8
+ brotliCompress as brotliCompressCallbackStyle ,
9
+ brotliDecompress as brotliDecompressCallbackStyle ,
10
+ } from 'node:zlib' ;
11
+ import { promisify } from 'node:util' ;
7
12
import {
8
13
FieldValue ,
9
14
Query ,
@@ -31,6 +36,7 @@ import {
31
36
ReadablePackageVersion ,
32
37
ReadablePackageInfo ,
33
38
UnreadablePackageStatus ,
39
+ UnreadablePackageVersion ,
34
40
} from '@webcomponents/catalog-api/lib/schema.js' ;
35
41
import {
36
42
Package ,
@@ -41,14 +47,22 @@ import {
41
47
packageInfoConverter ,
42
48
packageNameToId ,
43
49
} from './package-info-converter.js' ;
44
- import { packageVersionConverter } from './package-version-converter.js' ;
50
+ import {
51
+ CompressedPackageVersion ,
52
+ isReadableCompressedPackageVersion ,
53
+ packageVersionConverter ,
54
+ ReadableCompressedPackageVersion ,
55
+ } from './package-version-converter.js' ;
45
56
import { customElementConverter } from './custom-element-converter.js' ;
46
57
import { validationProblemConverter } from './validation-problem-converter.js' ;
47
58
48
59
const projectId = process . env [ 'GCP_PROJECT_ID' ] || 'wc-catalog' ;
49
60
firebase . initializeApp ( { projectId} ) ;
50
61
export const db = new Firestore ( { projectId} ) ;
51
62
63
+ const brotliCompress = promisify ( brotliCompressCallbackStyle ) ;
64
+ const brotliDecompress = promisify ( brotliDecompressCallbackStyle ) ;
65
+
52
66
export class FirestoreRepository implements Repository {
53
67
/**
54
68
* A namespace suffix to apply to the 'packages' collection to support
@@ -234,8 +248,13 @@ export class FirestoreRepository implements Repository {
234
248
const distTags = packageMetadata [ 'dist-tags' ] ;
235
249
const versionDistTags = getDistTagsForVersion ( distTags , version ) ;
236
250
251
+ const compressedManifest =
252
+ customElementsManifestSource &&
253
+ ( await brotliCompress ( customElementsManifestSource ) ) . toString ( 'base64' ) ;
254
+
237
255
// Store package data and mark version as ready
238
256
t . set ( versionRef , {
257
+ __typename : 'ReadableCompressedPackageVersion' ,
239
258
name : packageName ,
240
259
version,
241
260
status : VersionStatus . READY ,
@@ -246,14 +265,23 @@ export class FirestoreRepository implements Repository {
246
265
author,
247
266
time : new Date ( packageTime ) ,
248
267
homepage : packageVersionMetadata . homepage ?? null ,
249
- customElementsManifest : customElementsManifestSource ?? null ,
268
+ customElementsManifestCompressed : compressedManifest ,
250
269
} ) ;
251
270
} ) ;
252
271
const packageVersion = await db . runTransaction ( async ( t ) => {
253
272
// There doesn't seem to be a way to get a WriteResult and therefore
254
273
// a writeTime inside a transaction, so we read from the database to
255
274
// get the server timestamp.
256
- return ( await t . get ( versionRef ) ) . data ( ) as ReadablePackageVersion ;
275
+ const packageVersionCompressed = ( await t . get ( versionRef ) ) . data ( ) ! ;
276
+ if ( isReadableCompressedPackageVersion ( packageVersionCompressed ) ) {
277
+ return decompressPackageVersion (
278
+ packageVersionCompressed ,
279
+ versionRef . id
280
+ ) ;
281
+ }
282
+ throw new Error (
283
+ `Internal error: expected package version ${ versionRef . id } to be readable`
284
+ ) ;
257
285
} ) ;
258
286
return packageVersion ;
259
287
}
@@ -282,14 +310,14 @@ export class FirestoreRepository implements Repository {
282
310
t . update ( versionRef , {
283
311
status,
284
312
lastUpdate : FieldValue . serverTimestamp ( ) ,
285
- } as UpdateData < PackageVersion > as PackageVersion ) ;
313
+ } as UpdateData < UnreadablePackageVersion > as UnreadablePackageVersion ) ;
286
314
} ) ;
287
315
const packageVersion = await db . runTransaction ( async ( t ) => {
288
316
// There doesn't seem to be a way to get a WriteResult and therefore
289
317
// a writeTime inside a transaction, so we read from the database to
290
318
// get the server timestamp.
291
319
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
292
- return ( await t . get ( versionRef ) ) . data ( ) ! ;
320
+ return ( await t . get ( versionRef ) ) . data ( ) as UnreadablePackageVersion ;
293
321
} ) ;
294
322
return packageVersion ;
295
323
}
@@ -439,7 +467,14 @@ export class FirestoreRepository implements Repository {
439
467
// If version is valid semver we can build a document reference.
440
468
const versionRef = this . getPackageVersionRef ( packageName , versionNumber ) ;
441
469
const versionDoc = await versionRef . get ( ) ;
442
- return versionDoc . data ( ) ;
470
+ const packageVersion = versionDoc . data ( ) ;
471
+ if (
472
+ packageVersion &&
473
+ isReadableCompressedPackageVersion ( packageVersion )
474
+ ) {
475
+ return decompressPackageVersion ( packageVersion , versionRef . id ) ;
476
+ }
477
+ return packageVersion ;
443
478
} else {
444
479
// If version is not a valid semver it may be a dist-tag
445
480
@@ -450,7 +485,9 @@ export class FirestoreRepository implements Repository {
450
485
}
451
486
452
487
// Now query for a version that's assigned this dist-tag
453
- let query : CollectionReference < PackageVersion > | Query < PackageVersion > =
488
+ let query :
489
+ | CollectionReference < CompressedPackageVersion >
490
+ | Query < CompressedPackageVersion > =
454
491
this . getPackageVersionCollectionRef ( packageName ) ;
455
492
if ( versionOrTag === 'latest' ) {
456
493
query = query . where ( 'isLatest' , '==' , true ) ;
@@ -459,7 +496,16 @@ export class FirestoreRepository implements Repository {
459
496
}
460
497
const result = await query . limit ( 1 ) . get ( ) ;
461
498
if ( result . size !== 0 ) {
462
- return result . docs [ 0 ] ! . data ( ) ;
499
+ const doc = result . docs [ 0 ] ! ;
500
+ const packageVersion = doc . data ( ) ;
501
+ // Decompress the custom elements manifest.
502
+ // Note: We'd like to do this in the packageVersionConverter Firestore
503
+ // converter so that we don't have to remember to compress/decompress
504
+ // at every read and write operation, but we can't because we also want
505
+ // this to be an async operation and not block the main thread.
506
+ if ( isReadableCompressedPackageVersion ( packageVersion ) ) {
507
+ return decompressPackageVersion ( packageVersion , doc . id ) ;
508
+ }
463
509
}
464
510
return undefined ;
465
511
}
@@ -549,6 +595,28 @@ export class FirestoreRepository implements Repository {
549
595
}
550
596
}
551
597
598
+ export const decompressPackageVersion = async (
599
+ packageVersion : ReadableCompressedPackageVersion ,
600
+ id : string
601
+ ) : Promise < ReadablePackageVersion > => {
602
+ const manifestCompressed = packageVersion . customElementsManifestCompressed ;
603
+ try {
604
+ const customElementsManifest =
605
+ manifestCompressed &&
606
+ (
607
+ await brotliDecompress ( Buffer . from ( manifestCompressed , 'base64' ) )
608
+ ) . toString ( ) ;
609
+ return {
610
+ ...packageVersion ,
611
+ __typename : undefined ,
612
+ customElementsManifest,
613
+ } ;
614
+ } catch ( e ) {
615
+ console . error ( `Filed to decompress manifest for package version ${ id } ` ) ;
616
+ throw e ;
617
+ }
618
+ } ;
619
+
552
620
// /**
553
621
// * Generates a type representing a Firestore document from a GraphQL schema
554
622
// * type.
0 commit comments