@causa/workspace-google 0.18.0-beta.2 → 0.18.0-beta.3

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 (35) hide show
  1. package/dist/configurations/generated.d.ts +30 -0
  2. package/dist/configurations/generated.js +60 -0
  3. package/dist/configurations/schemas/google.yaml +43 -0
  4. package/dist/emulators/firebase-storage.d.ts +9 -1
  5. package/dist/emulators/firebase-storage.js +13 -1
  6. package/dist/emulators/firestore.d.ts +9 -1
  7. package/dist/emulators/firestore.js +13 -1
  8. package/dist/emulators/identity-platform.d.ts +9 -1
  9. package/dist/emulators/identity-platform.js +13 -1
  10. package/dist/emulators/pubsub.d.ts +9 -1
  11. package/dist/emulators/pubsub.js +13 -1
  12. package/dist/emulators/spanner.d.ts +18 -2
  13. package/dist/emulators/spanner.js +26 -2
  14. package/dist/functions/emulator/start-firebase-storage.js +4 -3
  15. package/dist/functions/emulator/start-firestore.js +5 -10
  16. package/dist/functions/emulator/start-identity-platform.js +4 -7
  17. package/dist/functions/emulator/start-pubsub.call.js +10 -9
  18. package/dist/functions/emulator/start-spanner.call.js +14 -13
  19. package/dist/functions/event-topic/broker-delete-trigger-resource-pubsub-subscription.js +2 -1
  20. package/dist/functions/event-topic/broker-wait-for-processing-pubsub.call.d.ts +2 -0
  21. package/dist/functions/event-topic/broker-wait-for-processing-pubsub.call.js +119 -0
  22. package/dist/functions/event-topic/broker-wait-for-processing-pubsub.d.ts +16 -0
  23. package/dist/functions/event-topic/broker-wait-for-processing-pubsub.js +23 -0
  24. package/dist/functions/event-topic/index.d.ts +1 -0
  25. package/dist/functions/event-topic/index.js +1 -0
  26. package/dist/functions/index.js +2 -2
  27. package/dist/services/cloud-monitoring.d.ts +44 -0
  28. package/dist/services/cloud-monitoring.js +61 -0
  29. package/dist/services/index.d.ts +2 -1
  30. package/dist/services/index.js +2 -1
  31. package/dist/services/pubsub.d.ts +1 -0
  32. package/dist/services/pubsub.js +1 -0
  33. package/dist/services/pubsub.utils.d.ts +9 -0
  34. package/dist/services/pubsub.utils.js +11 -0
  35. package/package.json +6 -5
@@ -173,6 +173,11 @@ export declare class GoogleFirebaseStorageEmulator {
173
173
  * The name of the local Docker container running the emulator.
174
174
  */
175
175
  readonly containerName?: string;
176
+ /**
177
+ * The host port to which the Firebase Storage emulator is bound.
178
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `9199`.
179
+ */
180
+ readonly port?: number;
176
181
  [property: string]: any;
177
182
  }
178
183
  /**
@@ -203,6 +208,11 @@ export declare class GoogleFirestoreEmulator {
203
208
  * The name of the local Docker container running the emulator.
204
209
  */
205
210
  readonly containerName?: string;
211
+ /**
212
+ * The host port to which the Firestore emulator is bound.
213
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `8080`.
214
+ */
215
+ readonly port?: number;
206
216
  [property: string]: any;
207
217
  }
208
218
  /**
@@ -245,6 +255,11 @@ export declare class GoogleIdentityPlatformEmulator {
245
255
  * The name of the local Docker container running the emulator.
246
256
  */
247
257
  readonly containerName?: string;
258
+ /**
259
+ * The host port to which the Identity Platform (Firebase Auth) emulator is bound.
260
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `9099`.
261
+ */
262
+ readonly port?: number;
248
263
  [property: string]: any;
249
264
  }
250
265
  /**
@@ -294,6 +309,11 @@ export declare class GooglePubSubEmulator {
294
309
  * The name of the local Docker container running the emulator.
295
310
  */
296
311
  readonly containerName?: string;
312
+ /**
313
+ * The host port to which the Pub/Sub emulator is bound.
314
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `8085`.
315
+ */
316
+ readonly port?: number;
297
317
  [property: string]: any;
298
318
  }
299
319
  /**
@@ -393,6 +413,16 @@ export declare class GoogleSpannerEmulator {
393
413
  * The name of the local Docker container running the emulator.
394
414
  */
395
415
  readonly containerName?: string;
416
+ /**
417
+ * The host port to which the Spanner emulator gRPC API is bound.
418
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `9010`.
419
+ */
420
+ readonly grpcPort?: number;
421
+ /**
422
+ * The host port to which the Spanner emulator HTTP API is bound.
423
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `9020`.
424
+ */
425
+ readonly httpPort?: number;
396
426
  /**
397
427
  * The name of the Spanner instance that will automatically be created in the emulator.
398
428
  */
@@ -301,12 +301,22 @@ export class GoogleFirebaseStorageEmulator {
301
301
  * The name of the local Docker container running the emulator.
302
302
  */
303
303
  containerName;
304
+ /**
305
+ * The host port to which the Firebase Storage emulator is bound.
306
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `9199`.
307
+ */
308
+ port;
304
309
  }
305
310
  __decorate([
306
311
  AllowMissing(),
307
312
  IsString(),
308
313
  __metadata("design:type", String)
309
314
  ], GoogleFirebaseStorageEmulator.prototype, "containerName", void 0);
315
+ __decorate([
316
+ AllowMissing(),
317
+ IsNumber(),
318
+ __metadata("design:type", Number)
319
+ ], GoogleFirebaseStorageEmulator.prototype, "port", void 0);
310
320
  /**
311
321
  * Configuration for the Firebase Storage service.
312
322
  */
@@ -353,12 +363,22 @@ export class GoogleFirestoreEmulator {
353
363
  * The name of the local Docker container running the emulator.
354
364
  */
355
365
  containerName;
366
+ /**
367
+ * The host port to which the Firestore emulator is bound.
368
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `8080`.
369
+ */
370
+ port;
356
371
  }
357
372
  __decorate([
358
373
  AllowMissing(),
359
374
  IsString(),
360
375
  __metadata("design:type", String)
361
376
  ], GoogleFirestoreEmulator.prototype, "containerName", void 0);
377
+ __decorate([
378
+ AllowMissing(),
379
+ IsNumber(),
380
+ __metadata("design:type", Number)
381
+ ], GoogleFirestoreEmulator.prototype, "port", void 0);
362
382
  /**
363
383
  * Configuration for the Firestore service.
364
384
  */
@@ -423,12 +443,22 @@ export class GoogleIdentityPlatformEmulator {
423
443
  * The name of the local Docker container running the emulator.
424
444
  */
425
445
  containerName;
446
+ /**
447
+ * The host port to which the Identity Platform (Firebase Auth) emulator is bound.
448
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `9099`.
449
+ */
450
+ port;
426
451
  }
427
452
  __decorate([
428
453
  AllowMissing(),
429
454
  IsString(),
430
455
  __metadata("design:type", String)
431
456
  ], GoogleIdentityPlatformEmulator.prototype, "containerName", void 0);
457
+ __decorate([
458
+ AllowMissing(),
459
+ IsNumber(),
460
+ __metadata("design:type", Number)
461
+ ], GoogleIdentityPlatformEmulator.prototype, "port", void 0);
432
462
  /**
433
463
  * Configuration for the Identity Platform (formerly Firebase Auth) service.
434
464
  */
@@ -501,12 +531,22 @@ export class GooglePubSubEmulator {
501
531
  * The name of the local Docker container running the emulator.
502
532
  */
503
533
  containerName;
534
+ /**
535
+ * The host port to which the Pub/Sub emulator is bound.
536
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `8085`.
537
+ */
538
+ port;
504
539
  }
505
540
  __decorate([
506
541
  AllowMissing(),
507
542
  IsString(),
508
543
  __metadata("design:type", String)
509
544
  ], GooglePubSubEmulator.prototype, "containerName", void 0);
545
+ __decorate([
546
+ AllowMissing(),
547
+ IsNumber(),
548
+ __metadata("design:type", Number)
549
+ ], GooglePubSubEmulator.prototype, "port", void 0);
510
550
  /**
511
551
  * Configuration for the Pub/Sub service.
512
552
  */
@@ -684,6 +724,16 @@ export class GoogleSpannerEmulator {
684
724
  * The name of the local Docker container running the emulator.
685
725
  */
686
726
  containerName;
727
+ /**
728
+ * The host port to which the Spanner emulator gRPC API is bound.
729
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `9010`.
730
+ */
731
+ grpcPort;
732
+ /**
733
+ * The host port to which the Spanner emulator HTTP API is bound.
734
+ * Override this to run several workspaces in parallel on the same machine. Defaults to `9020`.
735
+ */
736
+ httpPort;
687
737
  /**
688
738
  * The name of the Spanner instance that will automatically be created in the emulator.
689
739
  */
@@ -698,6 +748,16 @@ __decorate([
698
748
  IsString(),
699
749
  __metadata("design:type", String)
700
750
  ], GoogleSpannerEmulator.prototype, "containerName", void 0);
751
+ __decorate([
752
+ AllowMissing(),
753
+ IsNumber(),
754
+ __metadata("design:type", Number)
755
+ ], GoogleSpannerEmulator.prototype, "grpcPort", void 0);
756
+ __decorate([
757
+ AllowMissing(),
758
+ IsNumber(),
759
+ __metadata("design:type", Number)
760
+ ], GoogleSpannerEmulator.prototype, "httpPort", void 0);
701
761
  __decorate([
702
762
  AllowMissing(),
703
763
  IsString(),
@@ -125,6 +125,13 @@ properties:
125
125
  containerName:
126
126
  type: string
127
127
  description: The name of the local Docker container running the emulator.
128
+ port:
129
+ type: number
130
+ description: >-
131
+ The host port to which the Firebase Storage emulator is bound.
132
+
133
+ Override this to run several workspaces in parallel on the
134
+ same machine. Defaults to `9199`.
128
135
  securityRuleFiles:
129
136
  type: array
130
137
  items:
@@ -149,6 +156,14 @@ properties:
149
156
  containerName:
150
157
  type: string
151
158
  description: The name of the local Docker container running the emulator.
159
+ port:
160
+ type: number
161
+ description: >-
162
+ The host port to which the Identity Platform (Firebase Auth)
163
+ emulator is bound.
164
+
165
+ Override this to run several workspaces in parallel on the
166
+ same machine. Defaults to `9099`.
152
167
  firestore:
153
168
  title: GoogleFirestore
154
169
  type: object
@@ -162,6 +177,13 @@ properties:
162
177
  containerName:
163
178
  type: string
164
179
  description: The name of the local Docker container running the emulator.
180
+ port:
181
+ type: number
182
+ description: >-
183
+ The host port to which the Firestore emulator is bound.
184
+
185
+ Override this to run several workspaces in parallel on the
186
+ same machine. Defaults to `8080`.
165
187
  securityRuleFiles:
166
188
  type: array
167
189
  items:
@@ -185,6 +207,13 @@ properties:
185
207
  containerName:
186
208
  type: string
187
209
  description: The name of the local Docker container running the emulator.
210
+ port:
211
+ type: number
212
+ description: >-
213
+ The host port to which the Pub/Sub emulator is bound.
214
+
215
+ Override this to run several workspaces in parallel on the
216
+ same machine. Defaults to `8085`.
188
217
  topicConfigurationsDirectory:
189
218
  type: string
190
219
  description: The directory where the topic configuration files are written by
@@ -233,6 +262,20 @@ properties:
233
262
  type: string
234
263
  description: The name of the Spanner instance that will automatically be created
235
264
  in the emulator.
265
+ grpcPort:
266
+ type: number
267
+ description: >-
268
+ The host port to which the Spanner emulator gRPC API is bound.
269
+
270
+ Override this to run several workspaces in parallel on the
271
+ same machine. Defaults to `9010`.
272
+ httpPort:
273
+ type: number
274
+ description: >-
275
+ The host port to which the Spanner emulator HTTP API is bound.
276
+
277
+ Override this to run several workspaces in parallel on the
278
+ same machine. Defaults to `9020`.
236
279
  instance:
237
280
  title: GoogleSpannerInstance
238
281
  type: object
@@ -11,9 +11,17 @@ export declare const FIREBASE_STORAGE_EMULATOR_NAME = "google.firebaseStorage";
11
11
  */
12
12
  export declare function getFirebaseStorageContainerName(context: WorkspaceContext): string;
13
13
  /**
14
- * The exposed port for Firebase Storage.
14
+ * The default exposed host port for Firebase Storage.
15
15
  */
16
16
  export declare const FIREBASE_STORAGE_PORT = 9199;
17
+ /**
18
+ * Returns the host port to which the Firebase Storage emulator should be bound.
19
+ * Reads `google.firebaseStorage.emulator.port`, falling back to {@link FIREBASE_STORAGE_PORT}.
20
+ *
21
+ * @param context The {@link WorkspaceContext}.
22
+ * @returns The host port for the Firebase Storage emulator.
23
+ */
24
+ export declare function getFirebaseStorageEmulatorPort(context: WorkspaceContext): number;
17
25
  /**
18
26
  * The location of the Firebase Storage security rules file within the container.
19
27
  */
@@ -19,9 +19,21 @@ export function getFirebaseStorageContainerName(context) {
19
19
  return `${workspaceName}-firebase-storage`;
20
20
  }
21
21
  /**
22
- * The exposed port for Firebase Storage.
22
+ * The default exposed host port for Firebase Storage.
23
23
  */
24
24
  export const FIREBASE_STORAGE_PORT = 9199;
25
+ /**
26
+ * Returns the host port to which the Firebase Storage emulator should be bound.
27
+ * Reads `google.firebaseStorage.emulator.port`, falling back to {@link FIREBASE_STORAGE_PORT}.
28
+ *
29
+ * @param context The {@link WorkspaceContext}.
30
+ * @returns The host port for the Firebase Storage emulator.
31
+ */
32
+ export function getFirebaseStorageEmulatorPort(context) {
33
+ return (context
34
+ .asConfiguration()
35
+ .get('google.firebaseStorage.emulator.port') ?? FIREBASE_STORAGE_PORT);
36
+ }
25
37
  /**
26
38
  * The location of the Firebase Storage security rules file within the container.
27
39
  */
@@ -15,6 +15,14 @@ export declare function getFirestoreContainerName(context: WorkspaceContext): st
15
15
  */
16
16
  export declare const FIRESTORE_CONTAINER_RULES_FILE = "/firestore.rules";
17
17
  /**
18
- * The port on which the Firestore emulator listens.
18
+ * The default host port on which the Firestore emulator listens.
19
19
  */
20
20
  export declare const FIRESTORE_PORT = 8080;
21
+ /**
22
+ * Returns the host port to which the Firestore emulator should be bound.
23
+ * Reads `google.firestore.emulator.port`, falling back to {@link FIRESTORE_PORT}.
24
+ *
25
+ * @param context The {@link WorkspaceContext}.
26
+ * @returns The host port for the Firestore emulator.
27
+ */
28
+ export declare function getFirestoreEmulatorPort(context: WorkspaceContext): number;
@@ -23,6 +23,18 @@ export function getFirestoreContainerName(context) {
23
23
  */
24
24
  export const FIRESTORE_CONTAINER_RULES_FILE = '/firestore.rules';
25
25
  /**
26
- * The port on which the Firestore emulator listens.
26
+ * The default host port on which the Firestore emulator listens.
27
27
  */
28
28
  export const FIRESTORE_PORT = 8080;
29
+ /**
30
+ * Returns the host port to which the Firestore emulator should be bound.
31
+ * Reads `google.firestore.emulator.port`, falling back to {@link FIRESTORE_PORT}.
32
+ *
33
+ * @param context The {@link WorkspaceContext}.
34
+ * @returns The host port for the Firestore emulator.
35
+ */
36
+ export function getFirestoreEmulatorPort(context) {
37
+ return (context
38
+ .asConfiguration()
39
+ .get('google.firestore.emulator.port') ?? FIRESTORE_PORT);
40
+ }
@@ -11,6 +11,14 @@ export declare const IDENTITY_PLATFORM_EMULATOR_NAME = "google.identityPlatform"
11
11
  */
12
12
  export declare function getIdentityPlatformContainerName(context: WorkspaceContext): string;
13
13
  /**
14
- * The port on which the Firebase Auth emulator is listening.
14
+ * The default host port on which the Firebase Auth emulator is listening.
15
15
  */
16
16
  export declare const FIREBASE_AUTH_PORT = 9099;
17
+ /**
18
+ * Returns the host port to which the Identity Platform (Firebase Auth) emulator should be bound.
19
+ * Reads `google.identityPlatform.emulator.port`, falling back to {@link FIREBASE_AUTH_PORT}.
20
+ *
21
+ * @param context The {@link WorkspaceContext}.
22
+ * @returns The host port for the Identity Platform emulator.
23
+ */
24
+ export declare function getIdentityPlatformEmulatorPort(context: WorkspaceContext): number;
@@ -19,6 +19,18 @@ export function getIdentityPlatformContainerName(context) {
19
19
  return `${workspaceName}-identity-platform`;
20
20
  }
21
21
  /**
22
- * The port on which the Firebase Auth emulator is listening.
22
+ * The default host port on which the Firebase Auth emulator is listening.
23
23
  */
24
24
  export const FIREBASE_AUTH_PORT = 9099;
25
+ /**
26
+ * Returns the host port to which the Identity Platform (Firebase Auth) emulator should be bound.
27
+ * Reads `google.identityPlatform.emulator.port`, falling back to {@link FIREBASE_AUTH_PORT}.
28
+ *
29
+ * @param context The {@link WorkspaceContext}.
30
+ * @returns The host port for the Identity Platform emulator.
31
+ */
32
+ export function getIdentityPlatformEmulatorPort(context) {
33
+ return (context
34
+ .asConfiguration()
35
+ .get('google.identityPlatform.emulator.port') ?? FIREBASE_AUTH_PORT);
36
+ }
@@ -11,9 +11,17 @@ export declare const PUBSUB_EMULATOR_NAME = "google.pubSub";
11
11
  */
12
12
  export declare function getPubSubContainerName(context: WorkspaceContext): string;
13
13
  /**
14
- * The port on which the Pub/Sub emulator is listening.
14
+ * The default host port on which the Pub/Sub emulator is listening.
15
15
  */
16
16
  export declare const PUBSUB_PORT = 8085;
17
+ /**
18
+ * Returns the host port to which the Pub/Sub emulator should be bound.
19
+ * Reads `google.pubSub.emulator.port`, falling back to {@link PUBSUB_PORT}.
20
+ *
21
+ * @param context The {@link WorkspaceContext}.
22
+ * @returns The host port for the Pub/Sub emulator.
23
+ */
24
+ export declare function getPubSubEmulatorPort(context: WorkspaceContext): number;
17
25
  /**
18
26
  * Formats the name of a Pub/Sub topic as an environment variable.
19
27
  * The value of the corresponding environment variable is expected to be the Pub/Sub topic ID, e.g.
@@ -19,9 +19,21 @@ export function getPubSubContainerName(context) {
19
19
  return `${workspaceName}-pubsub`;
20
20
  }
21
21
  /**
22
- * The port on which the Pub/Sub emulator is listening.
22
+ * The default host port on which the Pub/Sub emulator is listening.
23
23
  */
24
24
  export const PUBSUB_PORT = 8085;
25
+ /**
26
+ * Returns the host port to which the Pub/Sub emulator should be bound.
27
+ * Reads `google.pubSub.emulator.port`, falling back to {@link PUBSUB_PORT}.
28
+ *
29
+ * @param context The {@link WorkspaceContext}.
30
+ * @returns The host port for the Pub/Sub emulator.
31
+ */
32
+ export function getPubSubEmulatorPort(context) {
33
+ return (context
34
+ .asConfiguration()
35
+ .get('google.pubSub.emulator.port') ?? PUBSUB_PORT);
36
+ }
25
37
  /**
26
38
  * Formats the name of a Pub/Sub topic as an environment variable.
27
39
  * The value of the corresponding environment variable is expected to be the Pub/Sub topic ID, e.g.
@@ -15,10 +15,26 @@ export declare function getSpannerContainerName(context: WorkspaceContext): stri
15
15
  */
16
16
  export declare const SPANNER_IMAGE = "gcr.io/cloud-spanner-emulator/emulator";
17
17
  /**
18
- * The port on which the Spanner emulator exposes its gRPC API.
18
+ * The default host port on which the Spanner emulator exposes its gRPC API.
19
19
  */
20
20
  export declare const SPANNER_GRPC_PORT = 9010;
21
21
  /**
22
- * The port on which the Spanner emulator exposes its HTTP API.
22
+ * The default host port on which the Spanner emulator exposes its HTTP API.
23
23
  */
24
24
  export declare const SPANNER_HTTP_PORT = 9020;
25
+ /**
26
+ * Returns the host port to which the Spanner emulator gRPC API should be bound.
27
+ * Reads `google.spanner.emulator.grpcPort`, falling back to {@link SPANNER_GRPC_PORT}.
28
+ *
29
+ * @param context The {@link WorkspaceContext}.
30
+ * @returns The host gRPC port for the Spanner emulator.
31
+ */
32
+ export declare function getSpannerEmulatorGrpcPort(context: WorkspaceContext): number;
33
+ /**
34
+ * Returns the host port to which the Spanner emulator HTTP API should be bound.
35
+ * Reads `google.spanner.emulator.httpPort`, falling back to {@link SPANNER_HTTP_PORT}.
36
+ *
37
+ * @param context The {@link WorkspaceContext}.
38
+ * @returns The host HTTP port for the Spanner emulator.
39
+ */
40
+ export declare function getSpannerEmulatorHttpPort(context: WorkspaceContext): number;
@@ -23,10 +23,34 @@ export function getSpannerContainerName(context) {
23
23
  */
24
24
  export const SPANNER_IMAGE = `gcr.io/cloud-spanner-emulator/emulator`;
25
25
  /**
26
- * The port on which the Spanner emulator exposes its gRPC API.
26
+ * The default host port on which the Spanner emulator exposes its gRPC API.
27
27
  */
28
28
  export const SPANNER_GRPC_PORT = 9010;
29
29
  /**
30
- * The port on which the Spanner emulator exposes its HTTP API.
30
+ * The default host port on which the Spanner emulator exposes its HTTP API.
31
31
  */
32
32
  export const SPANNER_HTTP_PORT = 9020;
33
+ /**
34
+ * Returns the host port to which the Spanner emulator gRPC API should be bound.
35
+ * Reads `google.spanner.emulator.grpcPort`, falling back to {@link SPANNER_GRPC_PORT}.
36
+ *
37
+ * @param context The {@link WorkspaceContext}.
38
+ * @returns The host gRPC port for the Spanner emulator.
39
+ */
40
+ export function getSpannerEmulatorGrpcPort(context) {
41
+ return (context
42
+ .asConfiguration()
43
+ .get('google.spanner.emulator.grpcPort') ?? SPANNER_GRPC_PORT);
44
+ }
45
+ /**
46
+ * Returns the host port to which the Spanner emulator HTTP API should be bound.
47
+ * Reads `google.spanner.emulator.httpPort`, falling back to {@link SPANNER_HTTP_PORT}.
48
+ *
49
+ * @param context The {@link WorkspaceContext}.
50
+ * @returns The host HTTP port for the Spanner emulator.
51
+ */
52
+ export function getSpannerEmulatorHttpPort(context) {
53
+ return (context
54
+ .asConfiguration()
55
+ .get('google.spanner.emulator.httpPort') ?? SPANNER_HTTP_PORT);
56
+ }
@@ -1,6 +1,6 @@
1
1
  import { EmulatorStart } from '@causa/workspace-core';
2
2
  import { fileURLToPath } from 'url';
3
- import { FIREBASE_CONTAINER_STORAGE_RULES_FILE, FIREBASE_STORAGE_EMULATOR_NAME, FIREBASE_STORAGE_PORT, getFirebaseStorageContainerName, } from '../../emulators/index.js';
3
+ import { FIREBASE_CONTAINER_STORAGE_RULES_FILE, FIREBASE_STORAGE_EMULATOR_NAME, FIREBASE_STORAGE_PORT, getFirebaseStorageContainerName, getFirebaseStorageEmulatorPort, } from '../../emulators/index.js';
4
4
  import { FirebaseEmulatorService } from '../../services/firebase-emulator.js';
5
5
  import { GoogleFirebaseStorageMergeRules } from '../google-firebase-storage/index.js';
6
6
  /**
@@ -30,12 +30,13 @@ export class EmulatorStartForFirebaseStorage extends EmulatorStart {
30
30
  const { securityRuleFile } = await this._context.call(GoogleFirebaseStorageMergeRules, {});
31
31
  this._context.logger.info('️🍱 Starting Firebase Storage emulator.');
32
32
  const containerName = getFirebaseStorageContainerName(this._context);
33
+ const hostPort = getFirebaseStorageEmulatorPort(this._context);
33
34
  const firebaseEmulatorService = this._context.service(FirebaseEmulatorService);
34
35
  await firebaseEmulatorService.start(containerName, FIREBASE_CONF_FILE, [
35
36
  {
36
37
  host: '127.0.0.1',
37
38
  container: FIREBASE_STORAGE_PORT,
38
- local: FIREBASE_STORAGE_PORT,
39
+ local: hostPort,
39
40
  },
40
41
  ], {
41
42
  mounts: [
@@ -51,7 +52,7 @@ export class EmulatorStartForFirebaseStorage extends EmulatorStart {
51
52
  });
52
53
  this._context.logger.info('️🍱 Successfully initialized Firebase Storage emulator.');
53
54
  return {
54
- FIREBASE_STORAGE_EMULATOR_HOST: `127.0.0.1:${FIREBASE_STORAGE_PORT}`,
55
+ FIREBASE_STORAGE_EMULATOR_HOST: `127.0.0.1:${hostPort}`,
55
56
  GOOGLE_CLOUD_PROJECT: firebaseEmulatorService.localGcpProject,
56
57
  GCP_PROJECT: firebaseEmulatorService.localGcpProject,
57
58
  GCLOUD_PROJECT: firebaseEmulatorService.localGcpProject,
@@ -1,5 +1,5 @@
1
1
  import { EmulatorStart } from '@causa/workspace-core';
2
- import { FIRESTORE_CONTAINER_RULES_FILE, FIRESTORE_EMULATOR_NAME, FIRESTORE_PORT, getFirestoreContainerName, } from '../../emulators/index.js';
2
+ import { FIRESTORE_CONTAINER_RULES_FILE, FIRESTORE_EMULATOR_NAME, FIRESTORE_PORT, getFirestoreContainerName, getFirestoreEmulatorPort, } from '../../emulators/index.js';
3
3
  import { GcloudEmulatorService } from '../../services/gcloud-emulator.js';
4
4
  import { GoogleFirestoreMergeRules } from '../google-firestore/index.js';
5
5
  /**
@@ -26,14 +26,9 @@ export class EmulatorStartForFirestore extends EmulatorStart {
26
26
  const { securityRuleFile } = await this._context.call(GoogleFirestoreMergeRules, {});
27
27
  this._context.logger.info('🗃️ Starting Firestore emulator.');
28
28
  const containerName = getFirestoreContainerName(this._context);
29
+ const hostPort = getFirestoreEmulatorPort(this._context);
29
30
  const gcloudEmulatorService = this._context.service(GcloudEmulatorService);
30
- await gcloudEmulatorService.start('firestore', containerName, [
31
- {
32
- host: '127.0.0.1',
33
- local: FIRESTORE_PORT,
34
- container: FIRESTORE_PORT,
35
- },
36
- ], {
31
+ await gcloudEmulatorService.start('firestore', containerName, [{ host: '127.0.0.1', local: hostPort, container: FIRESTORE_PORT }], {
37
32
  mounts: [
38
33
  {
39
34
  type: 'bind',
@@ -43,11 +38,11 @@ export class EmulatorStartForFirestore extends EmulatorStart {
43
38
  },
44
39
  ],
45
40
  additionalArguments: ['--rules', FIRESTORE_CONTAINER_RULES_FILE],
46
- availabilityEndpoint: `http://127.0.0.1:${FIRESTORE_PORT}/`,
41
+ availabilityEndpoint: `http://127.0.0.1:${hostPort}/`,
47
42
  });
48
43
  this._context.logger.info('🗃️ Successfully initialized Firestore emulator.');
49
44
  return {
50
- FIRESTORE_EMULATOR_HOST: `127.0.0.1:${FIRESTORE_PORT}`,
45
+ FIRESTORE_EMULATOR_HOST: `127.0.0.1:${hostPort}`,
51
46
  GOOGLE_CLOUD_PROJECT: gcloudEmulatorService.localGcpProject,
52
47
  GCP_PROJECT: gcloudEmulatorService.localGcpProject,
53
48
  GCLOUD_PROJECT: gcloudEmulatorService.localGcpProject,
@@ -1,6 +1,6 @@
1
1
  import { EmulatorStart } from '@causa/workspace-core';
2
2
  import { fileURLToPath } from 'url';
3
- import { FIREBASE_AUTH_PORT, IDENTITY_PLATFORM_EMULATOR_NAME, getIdentityPlatformContainerName, } from '../../emulators/index.js';
3
+ import { FIREBASE_AUTH_PORT, IDENTITY_PLATFORM_EMULATOR_NAME, getIdentityPlatformContainerName, getIdentityPlatformEmulatorPort, } from '../../emulators/index.js';
4
4
  import { FirebaseEmulatorService } from '../../services/firebase-emulator.js';
5
5
  /**
6
6
  * The Firebase configuration file enabling only the Auth emulator.
@@ -27,17 +27,14 @@ export class EmulatorStartForIdentityPlatform extends EmulatorStart {
27
27
  }
28
28
  this._context.logger.info('🛂 Starting the Identity Platform emulator.');
29
29
  const containerName = getIdentityPlatformContainerName(this._context);
30
+ const hostPort = getIdentityPlatformEmulatorPort(this._context);
30
31
  const firebaseEmulatorService = this._context.service(FirebaseEmulatorService);
31
32
  await firebaseEmulatorService.start(containerName, FIREBASE_CONF_FILE, [
32
- {
33
- host: '127.0.0.1',
34
- local: FIREBASE_AUTH_PORT,
35
- container: FIREBASE_AUTH_PORT,
36
- },
33
+ { host: '127.0.0.1', local: hostPort, container: FIREBASE_AUTH_PORT },
37
34
  ]);
38
35
  this._context.logger.info('🛂 Successfully initialized the Identity Platform emulator.');
39
36
  return {
40
- FIREBASE_AUTH_EMULATOR_HOST: `127.0.0.1:${FIREBASE_AUTH_PORT}`,
37
+ FIREBASE_AUTH_EMULATOR_HOST: `127.0.0.1:${hostPort}`,
41
38
  GOOGLE_CLOUD_PROJECT: firebaseEmulatorService.localGcpProject,
42
39
  GCP_PROJECT: firebaseEmulatorService.localGcpProject,
43
40
  GCLOUD_PROJECT: firebaseEmulatorService.localGcpProject,
@@ -1,7 +1,7 @@
1
1
  import { EventTopicList, } from '@causa/workspace-core';
2
2
  import { PubSub } from '@google-cloud/pubsub';
3
3
  import { getLocalGcpProject } from '../../configurations/utils.js';
4
- import { PUBSUB_EMULATOR_NAME, PUBSUB_PORT, formatPubSubTopicAsEnvironmentVariable, getPubSubContainerName, } from '../../emulators/index.js';
4
+ import { PUBSUB_EMULATOR_NAME, PUBSUB_PORT, formatPubSubTopicAsEnvironmentVariable, getPubSubContainerName, getPubSubEmulatorPort, } from '../../emulators/index.js';
5
5
  import { GcloudEmulatorService } from '../../services/gcloud-emulator.js';
6
6
  export default async function call() {
7
7
  const configuration = await startPubSub(this, this._context);
@@ -11,27 +11,28 @@ async function startPubSub(self, context) {
11
11
  if (self.dryRun) {
12
12
  return {};
13
13
  }
14
- const emulatorConf = await startPubSubEmulator(context);
15
- const topicsConf = await createTopicsIfNeeded(context);
14
+ const hostPort = getPubSubEmulatorPort(context);
15
+ const emulatorConf = await startPubSubEmulator(context, hostPort);
16
+ const topicsConf = await createTopicsIfNeeded(context, hostPort);
16
17
  context.logger.info('📫 Successfully initialized Pub/Sub emulator.');
17
18
  return { ...emulatorConf, ...topicsConf };
18
19
  }
19
- async function startPubSubEmulator(context) {
20
+ async function startPubSubEmulator(context, hostPort) {
20
21
  context.logger.info('📫 Starting Pub/Sub emulator.');
21
22
  const gcpProject = getLocalGcpProject(context);
22
23
  const containerName = getPubSubContainerName(context);
23
24
  const gcloudEmulatorService = context.service(GcloudEmulatorService);
24
- await gcloudEmulatorService.start('pubsub', containerName, [{ host: '127.0.0.1', local: PUBSUB_PORT, container: PUBSUB_PORT }], {
25
- availabilityEndpoint: `http://127.0.0.1:${PUBSUB_PORT}/v1/projects/${gcpProject}/topics`,
25
+ await gcloudEmulatorService.start('pubsub', containerName, [{ host: '127.0.0.1', local: hostPort, container: PUBSUB_PORT }], {
26
+ availabilityEndpoint: `http://127.0.0.1:${hostPort}/v1/projects/${gcpProject}/topics`,
26
27
  });
27
28
  return {
28
- PUBSUB_EMULATOR_HOST: `127.0.0.1:${PUBSUB_PORT}`,
29
+ PUBSUB_EMULATOR_HOST: `127.0.0.1:${hostPort}`,
29
30
  GOOGLE_CLOUD_PROJECT: gcloudEmulatorService.localGcpProject,
30
31
  GCP_PROJECT: gcloudEmulatorService.localGcpProject,
31
32
  GCLOUD_PROJECT: gcloudEmulatorService.localGcpProject,
32
33
  };
33
34
  }
34
- async function createTopicsIfNeeded(context) {
35
+ async function createTopicsIfNeeded(context, hostPort) {
35
36
  if (context.get('events.broker') !== 'google.pubSub') {
36
37
  context.logger.warn('📫️ Pub/Sub is not set as the event broker. Skipping event topics setup.');
37
38
  return {};
@@ -40,7 +41,7 @@ async function createTopicsIfNeeded(context) {
40
41
  const gcpProject = getLocalGcpProject(context);
41
42
  const pubSub = new PubSub({
42
43
  projectId: gcpProject,
43
- apiEndpoint: `127.0.0.1:${PUBSUB_PORT}`,
44
+ apiEndpoint: `127.0.0.1:${hostPort}`,
44
45
  });
45
46
  const envVars = await Promise.all(topics.map(async ({ id }) => {
46
47
  context.logger.info(`📫 Creating Pub/Sub emulator topic '${id}'.`);
@@ -5,7 +5,7 @@ import { Instance, Spanner } from '@google-cloud/spanner';
5
5
  // version can be loaded.
6
6
  import { grpc } from 'google-gax';
7
7
  import { getLocalGcpProject, } from '../../configurations/index.js';
8
- import { SPANNER_EMULATOR_NAME, SPANNER_GRPC_PORT, SPANNER_HTTP_PORT, SPANNER_IMAGE, getSpannerContainerName, } from '../../emulators/index.js';
8
+ import { SPANNER_EMULATOR_NAME, SPANNER_GRPC_PORT, SPANNER_HTTP_PORT, SPANNER_IMAGE, getSpannerContainerName, getSpannerEmulatorGrpcPort, getSpannerEmulatorHttpPort, } from '../../emulators/index.js';
9
9
  import { GoogleSpannerListDatabases } from '../google-spanner/index.js';
10
10
  export default async function call() {
11
11
  const configuration = await startSpanner(this, this._context);
@@ -15,12 +15,14 @@ async function startSpanner(self, context) {
15
15
  if (self.dryRun) {
16
16
  return {};
17
17
  }
18
- const emulatorConf = await startSpannerEmulator(context);
19
- const instanceAndDatabaseConf = await initializeEmulator(context);
18
+ const grpcHostPort = getSpannerEmulatorGrpcPort(context);
19
+ const httpHostPort = getSpannerEmulatorHttpPort(context);
20
+ const emulatorConf = await startSpannerEmulator(context, grpcHostPort, httpHostPort);
21
+ const instanceAndDatabaseConf = await initializeEmulator(context, grpcHostPort);
20
22
  context.logger.info('🗃️ Successfully initialized Spanner emulator.');
21
23
  return { ...emulatorConf, ...instanceAndDatabaseConf };
22
24
  }
23
- async function startSpannerEmulator(context) {
25
+ async function startSpannerEmulator(context, grpcHostPort, httpHostPort) {
24
26
  context.logger.info('🗃️ Starting Spanner emulator.');
25
27
  const gcpProject = getLocalGcpProject(context);
26
28
  const containerName = getSpannerContainerName(context);
@@ -28,23 +30,22 @@ async function startSpannerEmulator(context) {
28
30
  .asConfiguration()
29
31
  .get('google.spanner.emulator.version') ?? 'latest';
30
32
  const dockerEmulatorService = context.service(DockerEmulatorService);
31
- await dockerEmulatorService.start(`${SPANNER_IMAGE}:${imageVersion}`, containerName, [SPANNER_GRPC_PORT, SPANNER_HTTP_PORT].map((p) => ({
32
- host: '127.0.0.1',
33
- local: p,
34
- container: p,
35
- })));
36
- await dockerEmulatorService.waitForAvailability(containerName, `http://127.0.0.1:${SPANNER_HTTP_PORT}/v1/projects/${gcpProject}/instances`);
33
+ await dockerEmulatorService.start(`${SPANNER_IMAGE}:${imageVersion}`, containerName, [
34
+ { host: '127.0.0.1', local: grpcHostPort, container: SPANNER_GRPC_PORT },
35
+ { host: '127.0.0.1', local: httpHostPort, container: SPANNER_HTTP_PORT },
36
+ ]);
37
+ await dockerEmulatorService.waitForAvailability(containerName, `http://127.0.0.1:${httpHostPort}/v1/projects/${gcpProject}/instances`);
37
38
  return {
38
- SPANNER_EMULATOR_HOST: `127.0.0.1:${SPANNER_GRPC_PORT}`,
39
+ SPANNER_EMULATOR_HOST: `127.0.0.1:${grpcHostPort}`,
39
40
  GOOGLE_CLOUD_PROJECT: gcpProject,
40
41
  GCP_PROJECT: gcpProject,
41
42
  GCLOUD_PROJECT: gcpProject,
42
43
  };
43
44
  }
44
- async function initializeEmulator(context) {
45
+ async function initializeEmulator(context, port) {
45
46
  const spanner = new Spanner({
46
47
  servicePath: '127.0.0.1',
47
- port: SPANNER_GRPC_PORT,
48
+ port,
48
49
  projectId: getLocalGcpProject(context),
49
50
  sslCreds: grpc.credentials.createInsecure(),
50
51
  });
@@ -1,5 +1,6 @@
1
1
  import { callDeferred } from '@causa/workspace';
2
2
  import { EventTopicBrokerDeleteTriggerResource, } from '@causa/workspace-core';
3
+ import { PUBSUB_SUBSCRIPTION_ID_REGEX } from '../../services/pubsub.utils.js';
3
4
  /**
4
5
  * Implements {@link EventTopicBrokerDeleteTriggerResource} for Pub/Sub subscriptions.
5
6
  * Pub/Sub subscriptions are for example used when creating Cloud Run triggers.
@@ -9,7 +10,7 @@ export class EventTopicBrokerDeleteTriggerResourceForPubSubSubscription extends
9
10
  return await callDeferred(this, import.meta.url);
10
11
  }
11
12
  _supports() {
12
- return (this.id.match(/^projects\/[\w-]+\/subscriptions\/[\w-]+$/) != null &&
13
+ return (PUBSUB_SUBSCRIPTION_ID_REGEX.test(this.id) &&
13
14
  this._context
14
15
  .asConfiguration()
15
16
  .get('events.broker') === 'google.pubSub');
@@ -0,0 +1,2 @@
1
+ import type { EventTopicBrokerWaitForProcessingForPubSub } from './broker-wait-for-processing-pubsub.js';
2
+ export default function call(this: EventTopicBrokerWaitForProcessingForPubSub): Promise<void>;
@@ -0,0 +1,119 @@
1
+ import { setTimeout } from 'timers/promises';
2
+ import { CloudMonitoringService, } from '../../services/cloud-monitoring.js';
3
+ import { PUBSUB_SUBSCRIPTION_ID_REGEX, shortSubscriptionId, } from '../../services/index.js';
4
+ /**
5
+ * The interval between two consecutive polls of the Cloud Monitoring API, in milliseconds.
6
+ */
7
+ const POLL_INTERVAL = 30 * 1000;
8
+ /**
9
+ * The duration of the trailing window of points fetched on each poll, in milliseconds.
10
+ * It must be greater than `QUIET_WINDOW` plus a margin for the Cloud Monitoring export lag.
11
+ */
12
+ const TRAILING_WINDOW = 10 * 60 * 1000;
13
+ /**
14
+ * The minimum duration (server-time, derived from point timestamps) over which all three monitored metrics must
15
+ * continuously report zero before a subscription is considered drained, in milliseconds.
16
+ */
17
+ const QUIET_WINDOW = 3 * 60 * 1000;
18
+ /**
19
+ * The alignment period used when querying the cumulative `ack_message_count` metric so that points represent the
20
+ * number of acks within each bucket (delta), rather than the absolute cumulative value, in seconds.
21
+ */
22
+ const ALIGNMENT_PERIOD = 60;
23
+ /**
24
+ * The overall timeout for waiting on a single subscription, in milliseconds.
25
+ * On timeout the function throws so that the backfill file is preserved for manual cleanup.
26
+ */
27
+ const TIMEOUT = 60 * 60 * 1000;
28
+ /**
29
+ * Returns the Cloud Monitoring filter for the given metric on the given subscription.
30
+ */
31
+ function buildFilter(metricType, subscriptionId) {
32
+ return [
33
+ `metric.type = "${metricType}"`,
34
+ `resource.type = "pubsub_subscription"`,
35
+ `resource.labels.subscription_id = "${subscriptionId}"`,
36
+ ].join(' AND ');
37
+ }
38
+ /**
39
+ * Checks whether a gauge metric has been quiet (zero-valued) for at least {@link QUIET_WINDOW}.
40
+ *
41
+ * The check is purely server-side: the latest zero-valued point's timestamp must be at least `QUIET_WINDOW` after the
42
+ * latest non-zero point's timestamp. When no non-zero point exists in the trailing window, the metric is considered
43
+ * quiet (the outer liftoff gate is responsible for distinguishing "cold start" from "already drained").
44
+ */
45
+ function isGaugeQuiet(points) {
46
+ const lastNonZeroTimestamp = points.filter(({ value }) => value > 0).at(-1)?.timestamp ?? -Infinity;
47
+ const latestZeroTimestamp = points.filter(({ value }) => value === 0).at(-1)?.timestamp ?? -Infinity;
48
+ return latestZeroTimestamp - lastNonZeroTimestamp >= QUIET_WINDOW;
49
+ }
50
+ /**
51
+ * Checks whether a cumulative metric (queried with `ALIGN_DELTA`) has had no non-zero activity in the trailing
52
+ * `QUIET_WINDOW` before the given reference time.
53
+ *
54
+ * Unlike a gauge, Pub/Sub stops writing samples for a cumulative metric while there is no activity. The server-side
55
+ * "still has zero points" anchor used by {@link isGaugeQuiet} therefore does not apply: the absence of points must be
56
+ * interpreted against an external time reference (the query end).
57
+ */
58
+ function isCumulativeQuiet(points, referenceTime) {
59
+ const lastNonZeroTimestamp = points.filter(({ value }) => value > 0).at(-1)?.timestamp ?? -Infinity;
60
+ return referenceTime.getTime() - lastNonZeroTimestamp >= QUIET_WINDOW;
61
+ }
62
+ /**
63
+ * Observes a single subscription by fetching its three metrics over the configured trailing window and evaluating the
64
+ * liftoff and quiet conditions.
65
+ */
66
+ async function observeSubscription(context, subscriptionId) {
67
+ const cms = context.service(CloudMonitoringService);
68
+ const end = new Date();
69
+ const start = new Date(end.getTime() - TRAILING_WINDOW);
70
+ const ackFilter = buildFilter('pubsub.googleapis.com/subscription/ack_message_count', subscriptionId);
71
+ const backlogFilter = buildFilter('pubsub.googleapis.com/subscription/num_undelivered_messages', subscriptionId);
72
+ const ageFilter = buildFilter('pubsub.googleapis.com/subscription/oldest_unacked_message_age', subscriptionId);
73
+ const [ackPoints, backlogPoints, agePoints] = await Promise.all([
74
+ cms.listPoints(ackFilter, start, end, {
75
+ aligner: 'ALIGN_DELTA',
76
+ alignmentPeriod: ALIGNMENT_PERIOD,
77
+ }),
78
+ cms.listPoints(backlogFilter, start, end),
79
+ cms.listPoints(ageFilter, start, end),
80
+ ]);
81
+ const liftoff = ackPoints.some(({ value }) => value > 0);
82
+ const backlog = backlogPoints.at(-1)?.value ?? null;
83
+ const drained = liftoff &&
84
+ isCumulativeQuiet(ackPoints, end) &&
85
+ isGaugeQuiet(backlogPoints) &&
86
+ isGaugeQuiet(agePoints);
87
+ return { liftoff, backlog, drained };
88
+ }
89
+ /**
90
+ * Waits for a single subscription to be drained, polling Cloud Monitoring until either the drained state is reached
91
+ * or the overall timeout elapses.
92
+ */
93
+ async function waitForSubscription(context, subscriptionId) {
94
+ const deadline = Date.now() + TIMEOUT;
95
+ while (true) {
96
+ const { drained, backlog, liftoff } = await observeSubscription(context, subscriptionId);
97
+ if (drained) {
98
+ context.logger.debug(`✅ Subscription '${subscriptionId}' drained: backlog is empty and metrics have been quiet for at least ${QUIET_WINDOW / 1000}s.`);
99
+ return;
100
+ }
101
+ const backlogLabel = backlog === null ? 'no data yet' : `backlog=${backlog}`;
102
+ context.logger.debug(`⏳ Subscription '${subscriptionId}' not drained yet (liftoff=${liftoff}, ${backlogLabel}).`);
103
+ if (Date.now() >= deadline) {
104
+ throw new Error(`Timed out after ${Math.round(TIMEOUT / 60_000)} minute(s) waiting for subscription '${subscriptionId}' to drain.`);
105
+ }
106
+ await setTimeout(POLL_INTERVAL);
107
+ }
108
+ }
109
+ export default async function call() {
110
+ const subscriptionIds = this.temporaryData.temporaryTriggerResourceIds
111
+ .filter((id) => PUBSUB_SUBSCRIPTION_ID_REGEX.test(id))
112
+ .map(shortSubscriptionId);
113
+ if (subscriptionIds.length === 0) {
114
+ throw new Error(`Cannot wait for processing of backfill on event topic '${this.eventTopic}': no temporary Pub/Sub subscription was created. Only backfills with temporary triggers are supported.`);
115
+ }
116
+ this._context.logger.info(`⏳ Waiting for processing of ${subscriptionIds.length} temporary Pub/Sub subscription(s) for event topic '${this.eventTopic}'.`);
117
+ await Promise.all(subscriptionIds.map((id) => waitForSubscription(this._context, id)));
118
+ this._context.logger.info(`✅ All temporary Pub/Sub subscriptions for event topic '${this.eventTopic}' have been drained.`);
119
+ }
@@ -0,0 +1,16 @@
1
+ import { EventTopicBrokerWaitForProcessing } from '@causa/workspace-core';
2
+ /**
3
+ * Implements {@link EventTopicBrokerWaitForProcessing} for Pub/Sub temporary subscriptions.
4
+ *
5
+ * Only backfills that created temporary push subscriptions are supported: at least one entry of
6
+ * {@link BackfillTemporaryData.temporaryTriggerResourceIds} must match a subscription ID. If none does, the function
7
+ * throws — there is no observable signal for backfills that publish into existing topics with existing subscriptions.
8
+ *
9
+ * Completion is decided per-subscription from Cloud Monitoring time series (`subscription/ack_message_count`,
10
+ * `subscription/num_undelivered_messages`, `subscription/oldest_unacked_message_age`). See the call file for the exact
11
+ * decision logic.
12
+ */
13
+ export declare class EventTopicBrokerWaitForProcessingForPubSub extends EventTopicBrokerWaitForProcessing {
14
+ _call(): Promise<void>;
15
+ _supports(): boolean;
16
+ }
@@ -0,0 +1,23 @@
1
+ import { callDeferred } from '@causa/workspace';
2
+ import { EventTopicBrokerWaitForProcessing, } from '@causa/workspace-core';
3
+ /**
4
+ * Implements {@link EventTopicBrokerWaitForProcessing} for Pub/Sub temporary subscriptions.
5
+ *
6
+ * Only backfills that created temporary push subscriptions are supported: at least one entry of
7
+ * {@link BackfillTemporaryData.temporaryTriggerResourceIds} must match a subscription ID. If none does, the function
8
+ * throws — there is no observable signal for backfills that publish into existing topics with existing subscriptions.
9
+ *
10
+ * Completion is decided per-subscription from Cloud Monitoring time series (`subscription/ack_message_count`,
11
+ * `subscription/num_undelivered_messages`, `subscription/oldest_unacked_message_age`). See the call file for the exact
12
+ * decision logic.
13
+ */
14
+ export class EventTopicBrokerWaitForProcessingForPubSub extends EventTopicBrokerWaitForProcessing {
15
+ async _call() {
16
+ return await callDeferred(this, import.meta.url);
17
+ }
18
+ _supports() {
19
+ return (this._context
20
+ .asConfiguration()
21
+ .get('events.broker') === 'google.pubSub');
22
+ }
23
+ }
@@ -6,5 +6,6 @@ export { EventTopicBrokerDeleteTriggerResourceForPubSubSubscription } from './br
6
6
  export { EventTopicBrokerDeleteTriggerResourceForServiceAccount } from './broker-delete-trigger-resource-service-account.js';
7
7
  export { EventTopicBrokerGetTopicIdForPubSub } from './broker-get-topic-id-pubsub.js';
8
8
  export { EventTopicBrokerPublishEventsForGoogle } from './broker-publish-events-google.js';
9
+ export { EventTopicBrokerWaitForProcessingForPubSub } from './broker-wait-for-processing-pubsub.js';
9
10
  export { EventTopicCreateBackfillSourceForBigQuery } from './create-backfill-source-bigquery.js';
10
11
  export { EventTopicQueryEventsForBigQuery } from './query-events-bigquery.js';
@@ -6,5 +6,6 @@ export { EventTopicBrokerDeleteTriggerResourceForPubSubSubscription } from './br
6
6
  export { EventTopicBrokerDeleteTriggerResourceForServiceAccount } from './broker-delete-trigger-resource-service-account.js';
7
7
  export { EventTopicBrokerGetTopicIdForPubSub } from './broker-get-topic-id-pubsub.js';
8
8
  export { EventTopicBrokerPublishEventsForGoogle } from './broker-publish-events-google.js';
9
+ export { EventTopicBrokerWaitForProcessingForPubSub } from './broker-wait-for-processing-pubsub.js';
9
10
  export { EventTopicCreateBackfillSourceForBigQuery } from './create-backfill-source-bigquery.js';
10
11
  export { EventTopicQueryEventsForBigQuery } from './query-events-bigquery.js';
@@ -1,6 +1,6 @@
1
1
  import { CausaListConfigurationSchemasForGoogle } from './causa/index.js';
2
2
  import { EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, } from './emulator/index.js';
3
- import { EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, } from './event-topic/index.js';
3
+ import { EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicBrokerWaitForProcessingForPubSub, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, } from './event-topic/index.js';
4
4
  import { GoogleAppCheckGenerateToken } from './google-app-check/index.js';
5
5
  import { GoogleFirebaseStorageMergeRules } from './google-firebase-storage/index.js';
6
6
  import { DatabaseQueryRecordsForFirestore, GoogleFirestoreMergeRules, ModelGenerateTypeScriptDecoratorsForGoogleFirestore, ModelSchemaExtractDatabaseForGoogleFirestore, } from './google-firestore/index.js';
@@ -12,5 +12,5 @@ import { ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDesti
12
12
  import { SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, } from './secret/index.js';
13
13
  import { ServiceContainerQueryLogsForCloudRun } from './service-container/index.js';
14
14
  export function registerFunctions(context) {
15
- context.registerFunctionImplementations(CausaListConfigurationSchemasForGoogle, DatabaseQueryRecordsForFirestore, DatabaseQueryRecordsForSpanner, EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GooglePubSubWriteTopics, GoogleServicesEnable, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, ModelGenerateTypeScriptDecoratorsForGoogleFirestore, ModelGenerateTypeScriptDecoratorsForGoogleSpanner, ModelSchemaExtractDatabaseForGoogleFirestore, ModelSchemaExtractDatabaseForGoogleSpanner, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, ServiceContainerQueryLogsForCloudRun);
15
+ context.registerFunctionImplementations(CausaListConfigurationSchemasForGoogle, DatabaseQueryRecordsForFirestore, DatabaseQueryRecordsForSpanner, EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicBrokerWaitForProcessingForPubSub, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GooglePubSubWriteTopics, GoogleServicesEnable, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, ModelGenerateTypeScriptDecoratorsForGoogleFirestore, ModelGenerateTypeScriptDecoratorsForGoogleSpanner, ModelSchemaExtractDatabaseForGoogleFirestore, ModelSchemaExtractDatabaseForGoogleSpanner, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, ServiceContainerQueryLogsForCloudRun);
16
16
  }
@@ -0,0 +1,44 @@
1
+ import { WorkspaceContext } from '@causa/workspace';
2
+ import { MetricServiceClient } from '@google-cloud/monitoring';
3
+ /**
4
+ * A single metric point with its server-side end timestamp (as milliseconds since epoch) and numeric value.
5
+ */
6
+ export type MetricPoint = {
7
+ /**
8
+ * The end timestamp of the point's interval, as milliseconds since epoch.
9
+ */
10
+ readonly timestamp: number;
11
+ /**
12
+ * The numeric value of the point.
13
+ */
14
+ readonly value: number;
15
+ };
16
+ /**
17
+ * A service exposing a thin wrapper around the Cloud Monitoring API.
18
+ */
19
+ export declare class CloudMonitoringService {
20
+ /**
21
+ * The underlying Cloud Monitoring client.
22
+ */
23
+ readonly client: MetricServiceClient;
24
+ /**
25
+ * The ID of the GCP project from which metrics are read.
26
+ */
27
+ readonly projectId: string;
28
+ constructor(context: WorkspaceContext);
29
+ /**
30
+ * Fetches the data points for a single time series over the given time interval.
31
+ * Returns the points sorted in ascending timestamp order. Each point's timestamp is the end of the corresponding
32
+ * interval, in milliseconds since epoch. An empty array is returned when no series matches the filter.
33
+ *
34
+ * @param filter The Cloud Monitoring metric filter expression (e.g. `metric.type = "..."`).
35
+ * @param start The inclusive lower bound of the interval to fetch.
36
+ * @param end The exclusive upper bound of the interval to fetch.
37
+ * @param options Optional aggregation options.
38
+ * @returns The list of points, ordered by ascending timestamp.
39
+ */
40
+ listPoints(filter: string, start: Date, end: Date, options?: {
41
+ readonly alignmentPeriod?: number;
42
+ readonly aligner?: 'ALIGN_NONE' | 'ALIGN_DELTA' | 'ALIGN_RATE' | 'ALIGN_MEAN' | 'ALIGN_MAX';
43
+ }): Promise<MetricPoint[]>;
44
+ }
@@ -0,0 +1,61 @@
1
+ import { WorkspaceContext } from '@causa/workspace';
2
+ import { MetricServiceClient } from '@google-cloud/monitoring';
3
+ /**
4
+ * A service exposing a thin wrapper around the Cloud Monitoring API.
5
+ */
6
+ export class CloudMonitoringService {
7
+ /**
8
+ * The underlying Cloud Monitoring client.
9
+ */
10
+ client;
11
+ /**
12
+ * The ID of the GCP project from which metrics are read.
13
+ */
14
+ projectId;
15
+ constructor(context) {
16
+ this.projectId = context
17
+ .asConfiguration()
18
+ .getOrThrow('google.project');
19
+ this.client = new MetricServiceClient();
20
+ }
21
+ /**
22
+ * Fetches the data points for a single time series over the given time interval.
23
+ * Returns the points sorted in ascending timestamp order. Each point's timestamp is the end of the corresponding
24
+ * interval, in milliseconds since epoch. An empty array is returned when no series matches the filter.
25
+ *
26
+ * @param filter The Cloud Monitoring metric filter expression (e.g. `metric.type = "..."`).
27
+ * @param start The inclusive lower bound of the interval to fetch.
28
+ * @param end The exclusive upper bound of the interval to fetch.
29
+ * @param options Optional aggregation options.
30
+ * @returns The list of points, ordered by ascending timestamp.
31
+ */
32
+ async listPoints(filter, start, end, options = {}) {
33
+ const aggregation = options.aligner && options.alignmentPeriod
34
+ ? {
35
+ alignmentPeriod: { seconds: options.alignmentPeriod },
36
+ perSeriesAligner: options.aligner,
37
+ }
38
+ : undefined;
39
+ const [series] = await this.client.listTimeSeries({
40
+ name: `projects/${this.projectId}`,
41
+ filter,
42
+ interval: {
43
+ startTime: { seconds: Math.floor(start.getTime() / 1000) },
44
+ endTime: { seconds: Math.floor(end.getTime() / 1000) },
45
+ },
46
+ aggregation,
47
+ });
48
+ return (series.at(0)?.points ?? [])
49
+ .map(({ interval, value: rawValue }) => {
50
+ const endSeconds = Number(interval?.endTime?.seconds ?? 0);
51
+ const endNanos = interval?.endTime?.nanos ?? 0;
52
+ const timestamp = endSeconds * 1000 + Math.floor(endNanos / 1e6);
53
+ const value = Number(rawValue?.int64Value ??
54
+ rawValue?.doubleValue ??
55
+ rawValue?.distributionValue?.count ??
56
+ 0);
57
+ return { timestamp, value };
58
+ })
59
+ .sort((a, b) => a.timestamp - b.timestamp);
60
+ }
61
+ }
@@ -1,4 +1,5 @@
1
1
  export { BigQueryService } from './bigquery.js';
2
+ export { CloudMonitoringService, type MetricPoint, } from './cloud-monitoring.js';
2
3
  export { CloudRunPubSubTriggerService } from './cloud-run-pubsub-trigger.js';
3
4
  export { CloudRunService } from './cloud-run.js';
4
5
  export * from './firebase-app.errors.js';
@@ -9,7 +10,7 @@ export { GcloudEmulatorService } from './gcloud-emulator.js';
9
10
  export { GoogleApisService } from './google-apis.js';
10
11
  export { IamService } from './iam.js';
11
12
  export { LoggingService } from './logging.js';
12
- export { PubSubService } from './pubsub.js';
13
+ export { PUBSUB_SUBSCRIPTION_ID_REGEX, PubSubService, shortSubscriptionId, } from './pubsub.js';
13
14
  export { ResourceManagerService } from './resource-manager.js';
14
15
  export { GoogleSecretManagerService } from './secret-manager.js';
15
16
  export * from './storage.errors.js';
@@ -1,4 +1,5 @@
1
1
  export { BigQueryService } from './bigquery.js';
2
+ export { CloudMonitoringService, } from './cloud-monitoring.js';
2
3
  export { CloudRunPubSubTriggerService } from './cloud-run-pubsub-trigger.js';
3
4
  export { CloudRunService } from './cloud-run.js';
4
5
  export * from './firebase-app.errors.js';
@@ -8,7 +9,7 @@ export { GcloudEmulatorService } from './gcloud-emulator.js';
8
9
  export { GoogleApisService } from './google-apis.js';
9
10
  export { IamService } from './iam.js';
10
11
  export { LoggingService } from './logging.js';
11
- export { PubSubService } from './pubsub.js';
12
+ export { PUBSUB_SUBSCRIPTION_ID_REGEX, PubSubService, shortSubscriptionId, } from './pubsub.js';
12
13
  export { ResourceManagerService } from './resource-manager.js';
13
14
  export { GoogleSecretManagerService } from './secret-manager.js';
14
15
  export * from './storage.errors.js';
@@ -1,6 +1,7 @@
1
1
  import { WorkspaceContext } from '@causa/workspace';
2
2
  import { PubSub } from '@google-cloud/pubsub';
3
3
  import { iam_v1 } from 'googleapis';
4
+ export { PUBSUB_SUBSCRIPTION_ID_REGEX, shortSubscriptionId, } from './pubsub.utils.js';
4
5
  /**
5
6
  * A service for managing Pub/Sub resources
6
7
  */
@@ -2,6 +2,7 @@ import { WorkspaceContext } from '@causa/workspace';
2
2
  import { PubSub } from '@google-cloud/pubsub';
3
3
  import { iam_v1 } from 'googleapis';
4
4
  import { ResourceManagerService } from './resource-manager.js';
5
+ export { PUBSUB_SUBSCRIPTION_ID_REGEX, shortSubscriptionId, } from './pubsub.utils.js';
5
6
  /**
6
7
  * A service for managing Pub/Sub resources
7
8
  */
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The regular expression matching a Pub/Sub subscription resource ID
3
+ * (e.g. `projects/<projectId>/subscriptions/<name>`).
4
+ */
5
+ export declare const PUBSUB_SUBSCRIPTION_ID_REGEX: RegExp;
6
+ /**
7
+ * Returns the short subscription ID (the last path segment) for a full subscription resource name.
8
+ */
9
+ export declare function shortSubscriptionId(id: string): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The regular expression matching a Pub/Sub subscription resource ID
3
+ * (e.g. `projects/<projectId>/subscriptions/<name>`).
4
+ */
5
+ export const PUBSUB_SUBSCRIPTION_ID_REGEX = /^projects\/[\w-]+\/subscriptions\/[\w-]+$/;
6
+ /**
7
+ * Returns the short subscription ID (the last path segment) for a full subscription resource name.
8
+ */
9
+ export function shortSubscriptionId(id) {
10
+ return id.split('/').at(-1) ?? id;
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@causa/workspace-google",
3
- "version": "0.18.0-beta.2",
3
+ "version": "0.18.0-beta.3",
4
4
  "description": "The Causa workspace module providing many functionalities related to GCP and its services.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,12 +37,13 @@
37
37
  "dependencies": {
38
38
  "@causa/cli": ">= 0.8.0-beta.1 < 1.0.0",
39
39
  "@causa/workspace": ">= 0.25.0-beta.3 < 1.0.0",
40
- "@causa/workspace-core": ">= 0.35.0-beta.2 < 1.0.0",
41
- "@causa/workspace-typescript": ">= 0.23.0-beta.2 < 1.0.0",
40
+ "@causa/workspace-core": ">= 0.35.0-beta.6 < 1.0.0",
41
+ "@causa/workspace-typescript": ">= 0.23.0-beta.4 < 1.0.0",
42
42
  "@google-cloud/apikeys": "^2.2.2",
43
43
  "@google-cloud/bigquery": "^8.3.1",
44
44
  "@google-cloud/iam-credentials": "^4.2.2",
45
45
  "@google-cloud/logging": "^11.2.1",
46
+ "@google-cloud/monitoring": "^5.3.2",
46
47
  "@google-cloud/pubsub": "^5.3.0",
47
48
  "@google-cloud/resource-manager": "^6.2.2",
48
49
  "@google-cloud/run": "^3.2.1",
@@ -61,7 +62,7 @@
61
62
  "pino": "^10.3.1"
62
63
  },
63
64
  "devDependencies": {
64
- "@swc/core": "^1.15.33",
65
+ "@swc/core": "^1.15.40",
65
66
  "@swc/jest": "^0.2.39",
66
67
  "@tsconfig/node22": "^22.0.5",
67
68
  "@types/jest": "^30.0.0",
@@ -75,7 +76,7 @@
75
76
  "jest-extended": "^7.0.0",
76
77
  "rimraf": "^6.1.3",
77
78
  "typescript": "^6.0.3",
78
- "typescript-eslint": "^8.59.4",
79
+ "typescript-eslint": "^8.60.0",
79
80
  "yaml": "^2.9.0"
80
81
  }
81
82
  }