@causa/runtime-google 1.5.3 → 1.6.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 (36) hide show
  1. package/README.md +11 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/pubsub/interceptor.d.ts +4 -3
  5. package/dist/pubsub/interceptor.js +6 -5
  6. package/dist/pubsub/testing.d.ts +45 -7
  7. package/dist/pubsub/testing.js +56 -21
  8. package/dist/scheduler/cloud-scheduler-info.d.ts +13 -0
  9. package/dist/scheduler/cloud-scheduler-info.js +36 -0
  10. package/dist/scheduler/index.d.ts +3 -0
  11. package/dist/scheduler/index.js +3 -0
  12. package/dist/scheduler/interceptor.d.ts +34 -0
  13. package/dist/scheduler/interceptor.js +98 -0
  14. package/dist/scheduler/scheduler-event-info.decorator.d.ts +14 -0
  15. package/dist/scheduler/scheduler-event-info.decorator.js +10 -0
  16. package/dist/scheduler/testing.d.ts +41 -0
  17. package/dist/scheduler/testing.js +48 -0
  18. package/dist/spanner/entity-manager.d.ts +8 -1
  19. package/dist/spanner/entity-manager.js +9 -2
  20. package/dist/spanner/error-converter.js +10 -3
  21. package/dist/spanner/errors.d.ts +1 -1
  22. package/dist/spanner/errors.js +2 -2
  23. package/dist/spanner/types.d.ts +6 -0
  24. package/dist/tasks/cloud-tasks-info.d.ts +35 -0
  25. package/dist/tasks/cloud-tasks-info.js +90 -0
  26. package/dist/tasks/index.d.ts +3 -0
  27. package/dist/tasks/index.js +3 -0
  28. package/dist/tasks/interceptor.d.ts +30 -0
  29. package/dist/tasks/interceptor.js +90 -0
  30. package/dist/tasks/task-event-info.decorator.d.ts +14 -0
  31. package/dist/tasks/task-event-info.decorator.js +8 -0
  32. package/dist/tasks/testing.d.ts +41 -0
  33. package/dist/tasks/testing.js +59 -0
  34. package/dist/testing.d.ts +2 -0
  35. package/dist/testing.js +6 -0
  36. package/package.json +14 -14
@@ -2,7 +2,7 @@ import { Database } from '@google-cloud/spanner';
2
2
  import type { ExecuteSqlRequest, TimestampBounds } from '@google-cloud/spanner/build/src/transaction.js';
3
3
  import { type Type } from '@nestjs/common';
4
4
  import { SpannerTableCache } from './table-cache.js';
5
- import type { SpannerKey, SpannerReadOnlyTransaction, SpannerReadOnlyTransactionOption, SpannerReadWriteTransaction, SpannerReadWriteTransactionOption, SqlParamType, SqlStatement } from './types.js';
5
+ import type { SpannerKey, SpannerReadOnlyTransaction, SpannerReadOnlyTransactionOption, SpannerReadWriteTransaction, SpannerReadWriteTransactionOption, SqlParamFieldType, SqlParamType, SqlStatement } from './types.js';
6
6
  /**
7
7
  * Options for {@link SpannerEntityManager.snapshot}.
8
8
  */
@@ -291,6 +291,13 @@ export declare class SpannerEntityManager {
291
291
  static readonly ParamTypeJsonArray: SqlParamType;
292
292
  static readonly ParamTypeTimestampArray: SqlParamType;
293
293
  static readonly ParamTypeDateArray: SqlParamType;
294
+ static ParamTypeStructArray(...fields: SqlParamFieldType[]): {
295
+ type: string;
296
+ child: {
297
+ type: string;
298
+ fields: SqlParamFieldType[];
299
+ };
300
+ };
294
301
  /**
295
302
  * Converts the given entity or array of entities to Spanner objects, grouping them by table name.
296
303
  *
@@ -12,7 +12,7 @@ import { Database, Snapshot } from '@google-cloud/spanner';
12
12
  import { Injectable } from '@nestjs/common';
13
13
  import { copyInstanceWithMissingColumnsToNull, instanceToSpannerObject, spannerObjectToInstance, updateInstanceByColumn, } from './conversion.js';
14
14
  import { convertSpannerToEntityError } from './error-converter.js';
15
- import { InvalidArgumentError, TransactionFinishedError } from './errors.js';
15
+ import { InvalidArgumentError, TemporarySpannerError, TransactionFinishedError, } from './errors.js';
16
16
  import { SpannerTableCache } from './table-cache.js';
17
17
  /**
18
18
  * A class that manages access to entities stored in a Cloud Spanner database.
@@ -241,7 +241,11 @@ let SpannerEntityManager = class SpannerEntityManager {
241
241
  transaction.end();
242
242
  }
243
243
  }
244
- throw error;
244
+ // If the error was already converted and detected as temporary, the original Spanner error is thrown such
245
+ // that the Spanner client can apply its retry logic. If it decides not to retry, the error will be
246
+ // converted again outside the transaction.
247
+ const isTemporaryWithCause = error instanceof TemporarySpannerError && error.cause;
248
+ throw isTemporaryWithCause ? error.cause : error;
245
249
  }
246
250
  });
247
251
  }
@@ -383,6 +387,9 @@ let SpannerEntityManager = class SpannerEntityManager {
383
387
  type: 'array',
384
388
  child: { type: 'date' },
385
389
  };
390
+ static ParamTypeStructArray(...fields) {
391
+ return { type: 'array', child: { type: 'struct', fields } };
392
+ }
386
393
  /**
387
394
  * Converts the given entity or array of entities to Spanner objects, grouping them by table name.
388
395
  *
@@ -1,7 +1,7 @@
1
- import { EntityAlreadyExistsError } from '@causa/runtime';
1
+ import { EntityAlreadyExistsError, RetryableError } from '@causa/runtime';
2
2
  import { SessionPoolExhaustedError } from '@google-cloud/spanner/build/src/session-pool.js';
3
3
  import { status } from '@grpc/grpc-js';
4
- import { InvalidArgumentError, InvalidQueryError, TemporarySpannerError, } from './errors.js';
4
+ import { InvalidArgumentError, InvalidQueryError, TemporarySpannerError, UnexpectedSpannerError, } from './errors.js';
5
5
  /**
6
6
  * Converts an error thrown by Spanner to an entity error or a Spanner error subclass.
7
7
  *
@@ -9,6 +9,11 @@ import { InvalidArgumentError, InvalidQueryError, TemporarySpannerError, } from
9
9
  * @returns The specific error, or undefined if it could not be converted.
10
10
  */
11
11
  export function convertSpannerToEntityError(error) {
12
+ // Those are errors that have already been converted (or that don't come from Spanner).
13
+ if (error instanceof RetryableError ||
14
+ error instanceof UnexpectedSpannerError) {
15
+ return;
16
+ }
12
17
  // Those are not gRPC errors and are thrown by the session pool.
13
18
  if (error instanceof SessionPoolExhaustedError ||
14
19
  error.message == 'Timeout occurred while acquiring session.') {
@@ -31,7 +36,9 @@ export function convertSpannerToEntityError(error) {
31
36
  case status.UNAVAILABLE:
32
37
  case status.ABORTED:
33
38
  case status.RESOURCE_EXHAUSTED:
34
- return new TemporarySpannerError(error.message, error.code);
39
+ return new TemporarySpannerError(error.message, error.code, {
40
+ cause: error,
41
+ });
35
42
  default:
36
43
  return;
37
44
  }
@@ -45,7 +45,7 @@ export declare class InvalidArgumentError extends UnexpectedSpannerError {
45
45
  */
46
46
  export declare class TemporarySpannerError extends RetryableError {
47
47
  readonly code?: status | undefined;
48
- constructor(message: string, code?: status | undefined);
48
+ constructor(message: string, code?: status | undefined, options?: ErrorOptions);
49
49
  /**
50
50
  * Creates a new {@link TemporarySpannerError} that can be thrown to retry a transaction using the Spanner client
51
51
  * retry mechanism.
@@ -57,8 +57,8 @@ export class InvalidArgumentError extends UnexpectedSpannerError {
57
57
  */
58
58
  export class TemporarySpannerError extends RetryableError {
59
59
  code;
60
- constructor(message, code) {
61
- super(message);
60
+ constructor(message, code, options) {
61
+ super(message, undefined, options);
62
62
  this.code = code;
63
63
  }
64
64
  /**
@@ -1,6 +1,12 @@
1
1
  import { protos, Snapshot, Transaction } from '@google-cloud/spanner';
2
2
  import type { Type } from '@google-cloud/spanner/build/src/codec.js';
3
3
  export type SqlParamType = Type;
4
+ /**
5
+ * The type of a single `STRUCT` field in a Spanner SQL parameter.
6
+ */
7
+ export type SqlParamFieldType = Type & {
8
+ name: string;
9
+ };
4
10
  export declare const SpannerRequestPriority: typeof protos.google.spanner.v1.RequestOptions.Priority;
5
11
  /**
6
12
  * A key for a Spanner row.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Information about a Cloud Tasks task, extracted from HTTP headers.
3
+ */
4
+ export declare class CloudTasksInfo {
5
+ /**
6
+ * The name of the queue.
7
+ */
8
+ readonly queueName: string;
9
+ /**
10
+ * The short name of the task, or a unique system-generated ID if no name was specified at creation.
11
+ */
12
+ readonly taskName: string;
13
+ /**
14
+ * The number of times this task has been retried. For the first attempt, this value is `0`.
15
+ */
16
+ readonly retryCount: number;
17
+ /**
18
+ * The total number of times that the task has received a response from the handler.
19
+ */
20
+ readonly executionCount: number;
21
+ /**
22
+ * The schedule time of the task.
23
+ */
24
+ readonly eta: Date;
25
+ /**
26
+ * The HTTP response code from the previous retry.
27
+ * Only present if this is a retry attempt.
28
+ */
29
+ readonly previousResponse?: number;
30
+ /**
31
+ * The reason for retrying the task.
32
+ * Only present if this is a retry attempt.
33
+ */
34
+ readonly retryReason?: string;
35
+ }
@@ -0,0 +1,90 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { AllowMissing } from '@causa/runtime';
11
+ import { Expose, Transform } from 'class-transformer';
12
+ import { IsDate, IsInt, IsString, Min } from 'class-validator';
13
+ /**
14
+ * Information about a Cloud Tasks task, extracted from HTTP headers.
15
+ */
16
+ export class CloudTasksInfo {
17
+ /**
18
+ * The name of the queue.
19
+ */
20
+ queueName;
21
+ /**
22
+ * The short name of the task, or a unique system-generated ID if no name was specified at creation.
23
+ */
24
+ taskName;
25
+ /**
26
+ * The number of times this task has been retried. For the first attempt, this value is `0`.
27
+ */
28
+ retryCount;
29
+ /**
30
+ * The total number of times that the task has received a response from the handler.
31
+ */
32
+ executionCount;
33
+ /**
34
+ * The schedule time of the task.
35
+ */
36
+ eta;
37
+ /**
38
+ * The HTTP response code from the previous retry.
39
+ * Only present if this is a retry attempt.
40
+ */
41
+ previousResponse;
42
+ /**
43
+ * The reason for retrying the task.
44
+ * Only present if this is a retry attempt.
45
+ */
46
+ retryReason;
47
+ }
48
+ __decorate([
49
+ Expose({ name: 'x-cloudtasks-queuename' }),
50
+ IsString(),
51
+ __metadata("design:type", String)
52
+ ], CloudTasksInfo.prototype, "queueName", void 0);
53
+ __decorate([
54
+ Expose({ name: 'x-cloudtasks-taskname' }),
55
+ IsString(),
56
+ __metadata("design:type", String)
57
+ ], CloudTasksInfo.prototype, "taskName", void 0);
58
+ __decorate([
59
+ Expose({ name: 'x-cloudtasks-taskretrycount' }),
60
+ Transform(({ value }) => parseInt(value)),
61
+ IsInt(),
62
+ Min(0),
63
+ __metadata("design:type", Number)
64
+ ], CloudTasksInfo.prototype, "retryCount", void 0);
65
+ __decorate([
66
+ Expose({ name: 'x-cloudtasks-taskexecutioncount' }),
67
+ Transform(({ value }) => parseInt(value)),
68
+ IsInt(),
69
+ Min(0),
70
+ __metadata("design:type", Number)
71
+ ], CloudTasksInfo.prototype, "executionCount", void 0);
72
+ __decorate([
73
+ Expose({ name: 'x-cloudtasks-tasketa' }),
74
+ Transform(({ value }) => new Date(parseFloat(value) * 1000)),
75
+ IsDate(),
76
+ __metadata("design:type", Date)
77
+ ], CloudTasksInfo.prototype, "eta", void 0);
78
+ __decorate([
79
+ Expose({ name: 'x-cloudtasks-taskpreviousresponse' }),
80
+ Transform(({ value }) => (value !== undefined ? parseInt(value) : undefined)),
81
+ IsInt(),
82
+ AllowMissing(),
83
+ __metadata("design:type", Number)
84
+ ], CloudTasksInfo.prototype, "previousResponse", void 0);
85
+ __decorate([
86
+ Expose({ name: 'x-cloudtasks-taskretryreason' }),
87
+ IsString(),
88
+ AllowMissing(),
89
+ __metadata("design:type", String)
90
+ ], CloudTasksInfo.prototype, "retryReason", void 0);
@@ -1,4 +1,7 @@
1
+ export { CloudTasksInfo } from './cloud-tasks-info.js';
1
2
  export * from './errors.js';
3
+ export { CLOUD_TASKS_EVENT_HANDLER_ID, CloudTasksEventHandlerInterceptor, } from './interceptor.js';
2
4
  export { CloudTasksModule } from './module.js';
3
5
  export { CloudTasksScheduler, HttpMethod } from './scheduler.js';
4
6
  export type { HttpRequest, Task } from './scheduler.js';
7
+ export { CloudTasksEventInfo } from './task-event-info.decorator.js';
@@ -1,3 +1,6 @@
1
+ export { CloudTasksInfo } from './cloud-tasks-info.js';
1
2
  export * from './errors.js';
3
+ export { CLOUD_TASKS_EVENT_HANDLER_ID, CloudTasksEventHandlerInterceptor, } from './interceptor.js';
2
4
  export { CloudTasksModule } from './module.js';
3
5
  export { CloudTasksScheduler, HttpMethod } from './scheduler.js';
6
+ export { CloudTasksEventInfo } from './task-event-info.decorator.js';
@@ -0,0 +1,30 @@
1
+ import { BaseEventHandlerInterceptor, type EventHandlerInterceptorOptions, Logger, type ParsedEventRequest } from '@causa/runtime/nestjs';
2
+ import { type ExecutionContext, type Type } from '@nestjs/common';
3
+ import { Reflector } from '@nestjs/core';
4
+ import type { Request } from 'express';
5
+ import { CloudTasksInfo } from './cloud-tasks-info.js';
6
+ /**
7
+ * The ID of the Cloud Tasks event handler interceptor, that can passed to the `UseEventHandler` decorator.
8
+ */
9
+ export declare const CLOUD_TASKS_EVENT_HANDLER_ID = "google.cloudTasks";
10
+ /**
11
+ * The interceptor that should be added to controllers handling Cloud Tasks events.
12
+ */
13
+ export declare class CloudTasksEventHandlerInterceptor extends BaseEventHandlerInterceptor {
14
+ constructor(reflector: Reflector, logger: Logger, options?: EventHandlerInterceptorOptions);
15
+ /**
16
+ * Parses the Cloud Tasks request, extracting task information from headers.
17
+ *
18
+ * @param request The express request object.
19
+ * @returns The parsed Cloud Tasks information.
20
+ */
21
+ protected parseCloudTasksRequest(request: Request): Promise<CloudTasksInfo>;
22
+ protected parseEventFromContext(context: ExecutionContext, dataType: Type): Promise<ParsedEventRequest>;
23
+ /**
24
+ * Creates a Cloud Tasks event handler interceptor class with the given options.
25
+ *
26
+ * @param options Options for the interceptor.
27
+ * @returns The Cloud Tasks event handler interceptor class.
28
+ */
29
+ static withOptions(options: EventHandlerInterceptorOptions): Type<CloudTasksEventHandlerInterceptor>;
30
+ }
@@ -0,0 +1,90 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var CloudTasksEventHandlerInterceptor_1;
11
+ import { ValidationError, parseObject, validatorOptions } from '@causa/runtime';
12
+ import { BadRequestErrorDto, BaseEventHandlerInterceptor, Logger, throwHttpErrorResponse, } from '@causa/runtime/nestjs';
13
+ import { Injectable } from '@nestjs/common';
14
+ import { Reflector } from '@nestjs/core';
15
+ import { CloudTasksInfo } from './cloud-tasks-info.js';
16
+ /**
17
+ * The ID of the Cloud Tasks event handler interceptor, that can passed to the `UseEventHandler` decorator.
18
+ */
19
+ export const CLOUD_TASKS_EVENT_HANDLER_ID = 'google.cloudTasks';
20
+ /**
21
+ * The interceptor that should be added to controllers handling Cloud Tasks events.
22
+ */
23
+ let CloudTasksEventHandlerInterceptor = CloudTasksEventHandlerInterceptor_1 = class CloudTasksEventHandlerInterceptor extends BaseEventHandlerInterceptor {
24
+ constructor(reflector, logger, options = {}) {
25
+ super(CLOUD_TASKS_EVENT_HANDLER_ID, reflector, logger, options);
26
+ this.logger.setContext(CloudTasksEventHandlerInterceptor_1.name);
27
+ }
28
+ /**
29
+ * Parses the Cloud Tasks request, extracting task information from headers.
30
+ *
31
+ * @param request The express request object.
32
+ * @returns The parsed Cloud Tasks information.
33
+ */
34
+ async parseCloudTasksRequest(request) {
35
+ try {
36
+ const info = await parseObject(CloudTasksInfo, request.headers, {
37
+ ...validatorOptions,
38
+ forbidNonWhitelisted: false,
39
+ });
40
+ this.assignEventId(info.taskName);
41
+ this.logger.info('Successfully parsed Cloud Tasks request.');
42
+ return info;
43
+ }
44
+ catch (error) {
45
+ this.logger.error({
46
+ error: error.stack,
47
+ ...(error instanceof ValidationError
48
+ ? { validationMessages: error.validationMessages }
49
+ : {}),
50
+ }, 'Received invalid Cloud Tasks request.');
51
+ throwHttpErrorResponse(new BadRequestErrorDto());
52
+ }
53
+ }
54
+ async parseEventFromContext(context, dataType) {
55
+ const request = context
56
+ .switchToHttp()
57
+ .getRequest();
58
+ request.cloudTasksInfo = await this.parseCloudTasksRequest(request);
59
+ return await this.wrapParsing(async () => {
60
+ const body = await parseObject(dataType, request.body, {
61
+ forbidNonWhitelisted: false,
62
+ });
63
+ return { attributes: {}, body };
64
+ });
65
+ }
66
+ /**
67
+ * Creates a Cloud Tasks event handler interceptor class with the given options.
68
+ *
69
+ * @param options Options for the interceptor.
70
+ * @returns The Cloud Tasks event handler interceptor class.
71
+ */
72
+ static withOptions(options) {
73
+ let CloudTasksEventHandlerInterceptorWithOptions = class CloudTasksEventHandlerInterceptorWithOptions extends CloudTasksEventHandlerInterceptor_1 {
74
+ constructor(reflector, logger) {
75
+ super(reflector, logger, options);
76
+ }
77
+ };
78
+ CloudTasksEventHandlerInterceptorWithOptions = __decorate([
79
+ Injectable(),
80
+ __metadata("design:paramtypes", [Reflector, Logger])
81
+ ], CloudTasksEventHandlerInterceptorWithOptions);
82
+ return CloudTasksEventHandlerInterceptorWithOptions;
83
+ }
84
+ };
85
+ CloudTasksEventHandlerInterceptor = CloudTasksEventHandlerInterceptor_1 = __decorate([
86
+ Injectable(),
87
+ __metadata("design:paramtypes", [Reflector,
88
+ Logger, Object])
89
+ ], CloudTasksEventHandlerInterceptor);
90
+ export { CloudTasksEventHandlerInterceptor };
@@ -0,0 +1,14 @@
1
+ import type { CloudTasksInfo } from './cloud-tasks-info.js';
2
+ /**
3
+ * Additional information expected to be present on an express request that was parsed as a Cloud Tasks request.
4
+ */
5
+ export type RequestWithCloudTasksInfo = {
6
+ /**
7
+ * Information about the Cloud Tasks task.
8
+ */
9
+ cloudTasksInfo: CloudTasksInfo;
10
+ };
11
+ /**
12
+ * Decorates a route handler's parameter to populate it with information about the Cloud Tasks task.
13
+ */
14
+ export declare const CloudTasksEventInfo: (...dataOrPipes: unknown[]) => ParameterDecorator;
@@ -0,0 +1,8 @@
1
+ import { createParamDecorator } from '@nestjs/common';
2
+ /**
3
+ * Decorates a route handler's parameter to populate it with information about the Cloud Tasks task.
4
+ */
5
+ export const CloudTasksEventInfo = createParamDecorator((_data, ctx) => {
6
+ const request = ctx.switchToHttp().getRequest();
7
+ return request.cloudTasksInfo;
8
+ });
@@ -0,0 +1,41 @@
1
+ import type { AppFixture, Fixture } from '@causa/runtime/nestjs/testing';
2
+ import type { CloudTasksInfo } from './cloud-tasks-info.js';
3
+ /**
4
+ * Options when making a request to an endpoint handling Cloud Tasks events using a {@link CloudTasksEventRequester}.
5
+ */
6
+ export type CloudTasksEventRequesterOptions = {
7
+ /**
8
+ * The expected status code when making the request.
9
+ * Default is `200`.
10
+ */
11
+ readonly expectedStatus?: number;
12
+ /**
13
+ * The information about the Cloud Tasks task to include in the request headers.
14
+ * If not provided, default values will be used.
15
+ */
16
+ readonly taskInfo?: Partial<CloudTasksInfo>;
17
+ };
18
+ /**
19
+ * A function that makes a query to an endpoint handling Cloud Tasks events and tests the response.
20
+ */
21
+ export type CloudTasksEventRequester = (event: object, options?: CloudTasksEventRequesterOptions) => Promise<void>;
22
+ /**
23
+ * A utility class for testing Cloud Tasks event handlers.
24
+ */
25
+ export declare class CloudTasksFixture implements Fixture {
26
+ /**
27
+ * The parent {@link AppFixture}.
28
+ */
29
+ private appFixture;
30
+ init(appFixture: AppFixture): Promise<undefined>;
31
+ /**
32
+ * Creates a {@link CloudTasksEventRequester} for an endpoint handling Cloud Tasks events.
33
+ *
34
+ * @param endpoint The endpoint to query.
35
+ * @param options Options when creating the requester.
36
+ * @returns The {@link CloudTasksEventRequester}.
37
+ */
38
+ makeRequester(endpoint: string, options?: CloudTasksEventRequesterOptions): CloudTasksEventRequester;
39
+ clear(): Promise<void>;
40
+ delete(): Promise<void>;
41
+ }
@@ -0,0 +1,59 @@
1
+ import { HttpStatus } from '@nestjs/common';
2
+ import { randomUUID } from 'node:crypto';
3
+ /**
4
+ * A utility class for testing Cloud Tasks event handlers.
5
+ */
6
+ export class CloudTasksFixture {
7
+ /**
8
+ * The parent {@link AppFixture}.
9
+ */
10
+ appFixture;
11
+ async init(appFixture) {
12
+ this.appFixture = appFixture;
13
+ }
14
+ /**
15
+ * Creates a {@link CloudTasksEventRequester} for an endpoint handling Cloud Tasks events.
16
+ *
17
+ * @param endpoint The endpoint to query.
18
+ * @param options Options when creating the requester.
19
+ * @returns The {@link CloudTasksEventRequester}.
20
+ */
21
+ makeRequester(endpoint, options = {}) {
22
+ return async (event, requestOptions) => {
23
+ const taskInfo = {
24
+ queueName: 'queueName',
25
+ taskName: randomUUID(),
26
+ retryCount: 0,
27
+ executionCount: 0,
28
+ eta: new Date(),
29
+ ...options.taskInfo,
30
+ ...requestOptions?.taskInfo,
31
+ };
32
+ const headers = {
33
+ 'x-cloudtasks-queuename': taskInfo.queueName,
34
+ 'x-cloudtasks-taskname': taskInfo.taskName,
35
+ 'x-cloudtasks-taskretrycount': String(taskInfo.retryCount),
36
+ 'x-cloudtasks-taskexecutioncount': String(taskInfo.executionCount),
37
+ 'x-cloudtasks-tasketa': (taskInfo.eta.getTime() / 1000).toFixed(3),
38
+ };
39
+ if (taskInfo.previousResponse !== undefined) {
40
+ headers['x-cloudtasks-taskpreviousresponse'] = String(taskInfo.previousResponse);
41
+ }
42
+ if (taskInfo.retryReason !== undefined) {
43
+ headers['x-cloudtasks-taskretryreason'] = taskInfo.retryReason;
44
+ }
45
+ const expectedStatus = requestOptions?.expectedStatus ??
46
+ options.expectedStatus ??
47
+ HttpStatus.OK;
48
+ await this.appFixture.request
49
+ .post(endpoint)
50
+ .set(headers)
51
+ .send(event)
52
+ .expect(expectedStatus);
53
+ };
54
+ }
55
+ async clear() { }
56
+ async delete() {
57
+ this.appFixture = undefined;
58
+ }
59
+ }
package/dist/testing.d.ts CHANGED
@@ -6,7 +6,9 @@ export * from './firebase/testing.js';
6
6
  export * from './firestore/testing.js';
7
7
  export * from './identity-platform/testing.js';
8
8
  export * from './pubsub/testing.js';
9
+ export * from './scheduler/testing.js';
9
10
  export * from './spanner/testing.js';
11
+ export * from './tasks/testing.js';
10
12
  /**
11
13
  * Creates a NestJS application using the specified module, and sets up the fixture.
12
14
  *
package/dist/testing.js CHANGED
@@ -3,7 +3,9 @@ import { VersionedEntityFixture } from '@causa/runtime/testing';
3
3
  import { FirestoreFixture } from './firestore/testing.js';
4
4
  import { AuthUsersFixture } from './identity-platform/testing.js';
5
5
  import { PubSubFixture } from './pubsub/testing.js';
6
+ import { CloudSchedulerFixture } from './scheduler/testing.js';
6
7
  import { SpannerFixture } from './spanner/testing.js';
8
+ import { CloudTasksFixture } from './tasks/testing.js';
7
9
  import { AppCheckFixture, FirebaseFixture } from './testing.js';
8
10
  import { FirestorePubSubTransactionRunner, SpannerOutboxTransactionRunner, } from './transaction/index.js';
9
11
  export * from './app-check/testing.js';
@@ -11,7 +13,9 @@ export * from './firebase/testing.js';
11
13
  export * from './firestore/testing.js';
12
14
  export * from './identity-platform/testing.js';
13
15
  export * from './pubsub/testing.js';
16
+ export * from './scheduler/testing.js';
14
17
  export * from './spanner/testing.js';
18
+ export * from './tasks/testing.js';
15
19
  /**
16
20
  * Creates a NestJS application using the specified module, and sets up the fixture.
17
21
  *
@@ -34,5 +38,7 @@ export function createGoogleFixtures(options = {}) {
34
38
  new PubSubFixture(options.pubSubTopics ?? {}),
35
39
  ...(disableAppCheck ? [new AppCheckFixture()] : []),
36
40
  ...versionedEntityFixture,
41
+ new CloudTasksFixture(),
42
+ new CloudSchedulerFixture(),
37
43
  ];
38
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@causa/runtime-google",
3
- "version": "1.5.3",
3
+ "version": "1.6.1",
4
4
  "description": "An extension to the Causa runtime SDK (`@causa/runtime`), providing Google-specific features.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,15 +32,15 @@
32
32
  "test:cov": "npm run test -- --coverage"
33
33
  },
34
34
  "dependencies": {
35
- "@causa/runtime": "^1.5.0",
35
+ "@causa/runtime": "^1.6.0",
36
36
  "@google-cloud/precise-date": "^5.0.0",
37
- "@google-cloud/pubsub": "^5.2.1",
38
- "@google-cloud/spanner": "^8.4.0",
37
+ "@google-cloud/pubsub": "^5.2.2",
38
+ "@google-cloud/spanner": "^8.6.0",
39
39
  "@google-cloud/tasks": "^6.2.1",
40
40
  "@grpc/grpc-js": "^1.14.3",
41
- "@nestjs/common": "^11.1.11",
42
- "@nestjs/config": "^4.0.2",
43
- "@nestjs/core": "^11.1.11",
41
+ "@nestjs/common": "^11.1.13",
42
+ "@nestjs/config": "^4.0.3",
43
+ "@nestjs/core": "^11.1.13",
44
44
  "@nestjs/passport": "^11.0.5",
45
45
  "@nestjs/terminus": "^11.0.0",
46
46
  "class-transformer": "^0.5.1",
@@ -49,31 +49,31 @@
49
49
  "firebase-admin": "^13.6.0",
50
50
  "jsonwebtoken": "^9.0.3",
51
51
  "passport-http-bearer": "^1.0.1",
52
- "pino": "^10.1.1",
52
+ "pino": "^10.3.0",
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "devDependencies": {
56
- "@nestjs/testing": "^11.1.11",
57
- "@swc/core": "^1.15.8",
56
+ "@nestjs/testing": "^11.1.13",
57
+ "@swc/core": "^1.15.11",
58
58
  "@swc/jest": "^0.2.39",
59
- "@tsconfig/node20": "^20.1.8",
59
+ "@tsconfig/node20": "^20.1.9",
60
60
  "@types/jest": "^30.0.0",
61
61
  "@types/jsonwebtoken": "^9.0.10",
62
- "@types/node": "^20.19.28",
62
+ "@types/node": "^20.19.31",
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
67
  "eslint": "^9.39.2",
68
68
  "eslint-config-prettier": "^10.1.8",
69
- "eslint-plugin-prettier": "^5.5.4",
69
+ "eslint-plugin-prettier": "^5.5.5",
70
70
  "jest": "^30.2.0",
71
71
  "jest-extended": "^7.0.0",
72
72
  "pino-pretty": "^13.1.3",
73
73
  "rimraf": "^6.1.2",
74
74
  "supertest": "^7.2.2",
75
75
  "typescript": "^5.9.3",
76
- "typescript-eslint": "^8.52.0",
76
+ "typescript-eslint": "^8.54.0",
77
77
  "uuid": "^13.0.0"
78
78
  }
79
79
  }