@causa/runtime-google 1.5.0 → 1.5.2

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.
@@ -1,11 +1,12 @@
1
- import { BaseHealthIndicatorService } from '@causa/runtime/nestjs';
2
- import { type HealthIndicatorResult } from '@nestjs/terminus';
1
+ import { BaseHealthIndicatorService, type HealthChecker } from '@causa/runtime/nestjs';
2
+ import { HealthIndicatorService, type HealthIndicatorResult } from '@nestjs/terminus';
3
3
  import { Firestore } from 'firebase-admin/firestore';
4
4
  /**
5
5
  * A service testing the availability of the Firestore service.
6
6
  */
7
- export declare class FirestoreHealthIndicator extends BaseHealthIndicatorService {
7
+ export declare class FirestoreHealthIndicator extends BaseHealthIndicatorService implements HealthChecker {
8
8
  private readonly firestore;
9
- constructor(firestore: Firestore);
9
+ private readonly healthIndicatorService;
10
+ constructor(firestore: Firestore, healthIndicatorService: HealthIndicatorService);
10
11
  check(): Promise<HealthIndicatorResult>;
11
12
  }
@@ -7,9 +7,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { BaseHealthIndicatorService } from '@causa/runtime/nestjs';
10
+ import { orFallbackFn, tryMap } from '@causa/runtime';
11
+ import { BaseHealthIndicatorService, } from '@causa/runtime/nestjs';
11
12
  import { Injectable } from '@nestjs/common';
12
- import { HealthCheckError } from '@nestjs/terminus';
13
+ import { HealthIndicatorService, } from '@nestjs/terminus';
13
14
  import { Firestore } from 'firebase-admin/firestore';
14
15
  /**
15
16
  * The key used to identify the Firestore health indicator.
@@ -20,24 +21,23 @@ const FIRESTORE_HEALTH_KEY = 'google.firestore';
20
21
  */
21
22
  let FirestoreHealthIndicator = class FirestoreHealthIndicator extends BaseHealthIndicatorService {
22
23
  firestore;
23
- constructor(firestore) {
24
+ healthIndicatorService;
25
+ constructor(firestore, healthIndicatorService) {
24
26
  super();
25
27
  this.firestore = firestore;
28
+ this.healthIndicatorService = healthIndicatorService;
26
29
  }
27
30
  async check() {
28
- try {
31
+ const check = this.healthIndicatorService.check(FIRESTORE_HEALTH_KEY);
32
+ return await tryMap(async () => {
29
33
  await this.firestore.listCollections();
30
- return this.getStatus(FIRESTORE_HEALTH_KEY, true);
31
- }
32
- catch (error) {
33
- throw new HealthCheckError('Failed to check health by listing Firestore collections.', this.getStatus(FIRESTORE_HEALTH_KEY, false, {
34
- error: error.message,
35
- }));
36
- }
34
+ return check.up();
35
+ }, orFallbackFn((error) => check.down({ error: error.message })));
37
36
  }
38
37
  };
39
38
  FirestoreHealthIndicator = __decorate([
40
39
  Injectable(),
41
- __metadata("design:paramtypes", [Firestore])
40
+ __metadata("design:paramtypes", [Firestore,
41
+ HealthIndicatorService])
42
42
  ], FirestoreHealthIndicator);
43
43
  export { FirestoreHealthIndicator };
@@ -1,11 +1,12 @@
1
- import { BaseHealthIndicatorService } from '@causa/runtime/nestjs';
1
+ import { BaseHealthIndicatorService, type HealthChecker } from '@causa/runtime/nestjs';
2
2
  import { PubSub } from '@google-cloud/pubsub';
3
- import { type HealthIndicatorResult } from '@nestjs/terminus';
3
+ import { HealthIndicatorService, type HealthIndicatorResult } from '@nestjs/terminus';
4
4
  /**
5
5
  * A service testing the availability of the Pub/Sub service.
6
6
  */
7
- export declare class PubSubHealthIndicator extends BaseHealthIndicatorService {
7
+ export declare class PubSubHealthIndicator extends BaseHealthIndicatorService implements HealthChecker {
8
8
  private readonly pubSub;
9
- constructor(pubSub: PubSub);
9
+ private readonly healthIndicatorService;
10
+ constructor(pubSub: PubSub, healthIndicatorService: HealthIndicatorService);
10
11
  check(): Promise<HealthIndicatorResult>;
11
12
  }
@@ -7,11 +7,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { BaseHealthIndicatorService } from '@causa/runtime/nestjs';
10
+ import { orFallbackFn, tryMap } from '@causa/runtime';
11
+ import { BaseHealthIndicatorService, } from '@causa/runtime/nestjs';
11
12
  import { PubSub } from '@google-cloud/pubsub';
12
13
  import { status } from '@grpc/grpc-js';
13
14
  import { Injectable } from '@nestjs/common';
14
- import { HealthCheckError } from '@nestjs/terminus';
15
+ import { HealthIndicatorService, } from '@nestjs/terminus';
15
16
  /**
16
17
  * The key used to identify the Pub/Sub health indicator.
17
18
  */
@@ -21,30 +22,30 @@ const PUBSUB_HEALTH_KEY = 'google.pubSub';
21
22
  */
22
23
  let PubSubHealthIndicator = class PubSubHealthIndicator extends BaseHealthIndicatorService {
23
24
  pubSub;
24
- constructor(pubSub) {
25
+ healthIndicatorService;
26
+ constructor(pubSub, healthIndicatorService) {
25
27
  super();
26
28
  this.pubSub = pubSub;
29
+ this.healthIndicatorService = healthIndicatorService;
27
30
  }
28
31
  async check() {
29
- try {
30
- await this.pubSub.getTopics({
31
- autoPaginate: false,
32
- pageSize: 1,
33
- });
34
- }
35
- catch (error) {
32
+ const check = this.healthIndicatorService.check(PUBSUB_HEALTH_KEY);
33
+ return await tryMap(async () => {
34
+ await this.pubSub.getTopics({ autoPaginate: false, pageSize: 1 });
35
+ return check.up();
36
+ }, orFallbackFn((error) => {
36
37
  // Permission denied errors are treated as healthy, as they indicate that the service could successfully connect
37
38
  // to the Pub/Sub service (which is probably healthy), but was not allowed to list topics (which is usually not
38
39
  // the case for publishers).
39
- if (error.code !== status.PERMISSION_DENIED) {
40
- throw new HealthCheckError('Failed to check health by retrieving Pub/Sub topics.', this.getStatus(PUBSUB_HEALTH_KEY, false, { error: error.message }));
41
- }
42
- }
43
- return this.getStatus(PUBSUB_HEALTH_KEY, true);
40
+ return error.code === status.PERMISSION_DENIED
41
+ ? check.up()
42
+ : check.down({ error: error.message });
43
+ }));
44
44
  }
45
45
  };
46
46
  PubSubHealthIndicator = __decorate([
47
47
  Injectable(),
48
- __metadata("design:paramtypes", [PubSub])
48
+ __metadata("design:paramtypes", [PubSub,
49
+ HealthIndicatorService])
49
50
  ], PubSubHealthIndicator);
50
51
  export { PubSubHealthIndicator };
@@ -57,6 +57,25 @@ function spannerValueToJavaScript(value, columnMetadata) {
57
57
  }
58
58
  return value;
59
59
  }
60
+ /**
61
+ * Converts a number, bigint, or string value to a Spanner `Int`, throwing if the value is not a safe integer.
62
+ *
63
+ * @param value The value to convert.
64
+ * @returns The Spanner `Int` value.
65
+ */
66
+ function toSafeSpannerInt(value) {
67
+ const valueType = typeof value;
68
+ if (valueType !== 'number' &&
69
+ valueType !== 'string' &&
70
+ valueType !== 'bigint') {
71
+ throw new TypeError(`Expected a number, bigint, or string, but received ${valueType}.`);
72
+ }
73
+ const num = valueType !== 'number' ? Number(value) : value;
74
+ if (!Number.isSafeInteger(num)) {
75
+ throw new RangeError('Value is not a safe integer for column marked as integer.');
76
+ }
77
+ return new Int(value.toString());
78
+ }
60
79
  /**
61
80
  * Converts a value such that it is safe to pass to the Spanner client.
62
81
  * Numeric values and arrays of numeric values are wrapped using Spanner classes.
@@ -70,11 +89,16 @@ function makeSpannerValue(value, metadata) {
70
89
  if (value === undefined || value === null) {
71
90
  return value;
72
91
  }
73
- if (metadata.isBigInt || metadata.isInt) {
92
+ if (metadata.isBigInt) {
74
93
  return Array.isArray(value)
75
94
  ? value.map((v) => new Int(v.toString()))
76
95
  : new Int(value.toString());
77
96
  }
97
+ if (metadata.isInt) {
98
+ return Array.isArray(value)
99
+ ? value.map(toSafeSpannerInt)
100
+ : toSafeSpannerInt(value);
101
+ }
78
102
  if (metadata.isJson) {
79
103
  return JSON.stringify(value);
80
104
  }
@@ -294,8 +294,14 @@ let SpannerEntityManager = class SpannerEntityManager {
294
294
  : {};
295
295
  const sqlStatement = statement ?? optionsOrStatement;
296
296
  const { entityType, requestOptions, transaction } = options;
297
+ let snapshot;
297
298
  try {
298
- const stream = (transaction ?? this.database).runStream({
299
+ let txn = transaction;
300
+ if (!txn) {
301
+ [snapshot] = await this.database.getSnapshot();
302
+ txn = snapshot;
303
+ }
304
+ const stream = txn.runStream({
299
305
  ...sqlStatement,
300
306
  requestOptions,
301
307
  json: true,
@@ -310,9 +316,13 @@ let SpannerEntityManager = class SpannerEntityManager {
310
316
  }
311
317
  }
312
318
  catch (error) {
313
- // If running in a transaction, the error will be caught by `snapshot()` or `transaction()`.
319
+ // If running in a provided transaction, the error will be caught by `snapshot()` or `transaction()`.
320
+ // Otherwise, the error should be converted.
314
321
  throw transaction ? error : (convertSpannerToEntityError(error) ?? error);
315
322
  }
323
+ finally {
324
+ snapshot?.end();
325
+ }
316
326
  }
317
327
  /**
318
328
  * Runs the given SQL statement in the database, returning an async iterable of batches of results.
@@ -1,11 +1,12 @@
1
- import { BaseHealthIndicatorService } from '@causa/runtime/nestjs';
1
+ import { BaseHealthIndicatorService, type HealthChecker } from '@causa/runtime/nestjs';
2
2
  import { Database } from '@google-cloud/spanner';
3
- import { type HealthIndicatorResult } from '@nestjs/terminus';
3
+ import { HealthIndicatorService, type HealthIndicatorResult } from '@nestjs/terminus';
4
4
  /**
5
5
  * A service testing the availability of the Spanner service.
6
6
  */
7
- export declare class SpannerHealthIndicator extends BaseHealthIndicatorService {
7
+ export declare class SpannerHealthIndicator extends BaseHealthIndicatorService implements HealthChecker {
8
8
  private readonly database;
9
- constructor(database: Database);
9
+ private readonly healthIndicatorService;
10
+ constructor(database: Database, healthIndicatorService: HealthIndicatorService);
10
11
  check(): Promise<HealthIndicatorResult>;
11
12
  }
@@ -7,10 +7,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { BaseHealthIndicatorService } from '@causa/runtime/nestjs';
10
+ import { orFallbackFn, tryMap } from '@causa/runtime';
11
+ import { BaseHealthIndicatorService, } from '@causa/runtime/nestjs';
11
12
  import { Database } from '@google-cloud/spanner';
12
13
  import { Injectable } from '@nestjs/common';
13
- import { HealthCheckError } from '@nestjs/terminus';
14
+ import { HealthIndicatorService, } from '@nestjs/terminus';
14
15
  /**
15
16
  * The key used to identify the Spanner health indicator.
16
17
  */
@@ -20,22 +21,23 @@ const SPANNER_HEALTH_KEY = 'google.spanner';
20
21
  */
21
22
  let SpannerHealthIndicator = class SpannerHealthIndicator extends BaseHealthIndicatorService {
22
23
  database;
23
- constructor(database) {
24
+ healthIndicatorService;
25
+ constructor(database, healthIndicatorService) {
24
26
  super();
25
27
  this.database = database;
28
+ this.healthIndicatorService = healthIndicatorService;
26
29
  }
27
30
  async check() {
28
- try {
31
+ const check = this.healthIndicatorService.check(SPANNER_HEALTH_KEY);
32
+ return await tryMap(async () => {
29
33
  await this.database.run('SELECT 1');
30
- return this.getStatus(SPANNER_HEALTH_KEY, true);
31
- }
32
- catch (error) {
33
- throw new HealthCheckError('Failed to check health by running Spanner query.', this.getStatus(SPANNER_HEALTH_KEY, false, { error: error.message }));
34
- }
34
+ return check.up();
35
+ }, orFallbackFn((error) => check.down({ error: error.message })));
35
36
  }
36
37
  };
37
38
  SpannerHealthIndicator = __decorate([
38
39
  Injectable(),
39
- __metadata("design:paramtypes", [Database])
40
+ __metadata("design:paramtypes", [Database,
41
+ HealthIndicatorService])
40
42
  ], SpannerHealthIndicator);
41
43
  export { SpannerHealthIndicator };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@causa/runtime-google",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "An extension to the Causa runtime SDK (`@causa/runtime`), providing Google-specific features.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,48 +32,48 @@
32
32
  "test:cov": "npm run test -- --coverage"
33
33
  },
34
34
  "dependencies": {
35
- "@causa/runtime": "^1.4.0",
35
+ "@causa/runtime": "^1.5.0",
36
36
  "@google-cloud/precise-date": "^5.0.0",
37
- "@google-cloud/pubsub": "^5.2.0",
38
- "@google-cloud/spanner": "^8.2.2",
37
+ "@google-cloud/pubsub": "^5.2.1",
38
+ "@google-cloud/spanner": "^8.4.0",
39
39
  "@google-cloud/tasks": "^6.2.1",
40
- "@grpc/grpc-js": "^1.14.1",
41
- "@nestjs/common": "^11.1.9",
40
+ "@grpc/grpc-js": "^1.14.3",
41
+ "@nestjs/common": "^11.1.11",
42
42
  "@nestjs/config": "^4.0.2",
43
- "@nestjs/core": "^11.1.9",
43
+ "@nestjs/core": "^11.1.11",
44
44
  "@nestjs/passport": "^11.0.5",
45
45
  "@nestjs/terminus": "^11.0.0",
46
46
  "class-transformer": "^0.5.1",
47
- "class-validator": "^0.14.2",
48
- "express": "^5.1.0",
47
+ "class-validator": "^0.14.3",
48
+ "express": "^5.2.1",
49
49
  "firebase-admin": "^13.6.0",
50
- "jsonwebtoken": "^9.0.2",
50
+ "jsonwebtoken": "^9.0.3",
51
51
  "passport-http-bearer": "^1.0.1",
52
- "pino": "^9.14.0",
52
+ "pino": "^10.1.1",
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "devDependencies": {
56
- "@nestjs/testing": "^11.1.9",
57
- "@swc/core": "^1.15.2",
56
+ "@nestjs/testing": "^11.1.11",
57
+ "@swc/core": "^1.15.8",
58
58
  "@swc/jest": "^0.2.39",
59
59
  "@tsconfig/node20": "^20.1.8",
60
60
  "@types/jest": "^30.0.0",
61
61
  "@types/jsonwebtoken": "^9.0.10",
62
- "@types/node": "^20.19.25",
62
+ "@types/node": "^20.19.28",
63
63
  "@types/passport-http-bearer": "^1.0.42",
64
64
  "@types/supertest": "^6.0.3",
65
65
  "@types/uuid": "^11.0.0",
66
66
  "dotenv": "^17.2.3",
67
- "eslint": "^9.39.1",
67
+ "eslint": "^9.39.2",
68
68
  "eslint-config-prettier": "^10.1.8",
69
69
  "eslint-plugin-prettier": "^5.5.4",
70
70
  "jest": "^30.2.0",
71
71
  "jest-extended": "^7.0.0",
72
- "pino-pretty": "^13.1.2",
72
+ "pino-pretty": "^13.1.3",
73
73
  "rimraf": "^6.1.2",
74
- "supertest": "^7.1.4",
74
+ "supertest": "^7.2.2",
75
75
  "typescript": "^5.9.3",
76
- "typescript-eslint": "^8.47.0",
76
+ "typescript-eslint": "^8.52.0",
77
77
  "uuid": "^13.0.0"
78
78
  }
79
79
  }