@causa/runtime-google 1.5.3 → 1.6.0
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.
- package/README.md +11 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/pubsub/interceptor.d.ts +4 -3
- package/dist/pubsub/interceptor.js +6 -5
- package/dist/pubsub/testing.d.ts +45 -7
- package/dist/pubsub/testing.js +56 -21
- package/dist/scheduler/cloud-scheduler-info.d.ts +13 -0
- package/dist/scheduler/cloud-scheduler-info.js +36 -0
- package/dist/scheduler/index.d.ts +3 -0
- package/dist/scheduler/index.js +3 -0
- package/dist/scheduler/interceptor.d.ts +34 -0
- package/dist/scheduler/interceptor.js +98 -0
- package/dist/scheduler/scheduler-event-info.decorator.d.ts +14 -0
- package/dist/scheduler/scheduler-event-info.decorator.js +10 -0
- package/dist/scheduler/testing.d.ts +41 -0
- package/dist/scheduler/testing.js +48 -0
- package/dist/spanner/entity-manager.d.ts +8 -1
- package/dist/spanner/entity-manager.js +9 -2
- package/dist/spanner/error-converter.js +10 -3
- package/dist/spanner/errors.d.ts +1 -1
- package/dist/spanner/errors.js +2 -2
- package/dist/spanner/types.d.ts +6 -0
- package/dist/tasks/cloud-tasks-info.d.ts +35 -0
- package/dist/tasks/cloud-tasks-info.js +90 -0
- package/dist/tasks/index.d.ts +3 -0
- package/dist/tasks/index.js +3 -0
- package/dist/tasks/interceptor.d.ts +30 -0
- package/dist/tasks/interceptor.js +90 -0
- package/dist/tasks/task-event-info.decorator.d.ts +14 -0
- package/dist/tasks/task-event-info.decorator.js +8 -0
- package/dist/tasks/testing.d.ts +41 -0
- package/dist/tasks/testing.js +59 -0
- package/dist/testing.js +4 -0
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -82,12 +82,22 @@ The `PubSubPublisherModule` provides the `PubSub` client, as well as the `PubSub
|
|
|
82
82
|
|
|
83
83
|
The `PubSubPublisher` requires the `PUBSUB_TOPIC_*` environment variables to be set for all the topics a service is expected to publish to. The name of the environment variables should be prefixed with `PUBSUB_TOPIC_`, followed by the topic full name in upper case, and using `_` as the only punctuation. For example, `my-domain.my-topic.v1` would become `PUBSUB_TOPIC_MY_DOMAIN_MY_TOPIC_V1`.
|
|
84
84
|
|
|
85
|
-
For services being triggered by Pub/Sub messages, the `
|
|
85
|
+
For services being triggered by Pub/Sub messages, the `PubSubEventHandlerInterceptor` automatically parses Pub/Sub messages coming from HTTP requests made by a Pub/Sub push subscription. If the interceptor is set up at the application level, any route with a parameter decorated with `@EventBody` will trigger the interceptor, and will receive the parsed event pushed by Pub/Sub. The `PubSubEventHandlerInterceptor.withSerializer` method should be used to create the interceptor with the desired serializer. It also allows defining whether the interceptor `isDefault`, i.e. whether `@UseEventHandler` is required on route handlers to apply the interceptor.
|
|
86
86
|
|
|
87
87
|
The `PubSubHealthIndicator` is a `HealthIndicator` which can be used in a health check controller, such as the `GoogleHealthcheckModule`. It attempts to list topics using the Pub/Sub client to check connectivity to the Pub/Sub API.
|
|
88
88
|
|
|
89
89
|
To test publishers, the `PubSubFixture` handles the creation and deletion of temporary topics, and provides `expect*` utilities to check for published messages. To test event handlers, it also provides the `makeRequester()` utility, which returns a function that can be used to make HTTP requests in the same way a Pub/Sub push subscription would.
|
|
90
90
|
|
|
91
|
+
### Cloud Tasks
|
|
92
|
+
|
|
93
|
+
The `CloudTasksEventHandlerInterceptor` handles HTTP requests triggered by Cloud Tasks. It parses task metadata from request headers into a `CloudTasksInfo` object, and validates the request body against the type decorated with `@EventBody`. The `@CloudTasksEventInfo` decorator can be used on a route handler parameter to retrieve the parsed `CloudTasksInfo`. The `CloudTasksEventHandlerInterceptor.withOptions` static method can be used to create the interceptor, defining whether it `isDefault`.
|
|
94
|
+
|
|
95
|
+
### Cloud Scheduler
|
|
96
|
+
|
|
97
|
+
The `CloudSchedulerEventHandlerInterceptor` handles HTTP requests triggered by Cloud Scheduler jobs. It parses scheduler job metadata from request headers into a `CloudSchedulerInfo` object. The `@CloudSchedulerEventInfo` decorator can be used on a route handler parameter to retrieve the parsed `CloudSchedulerInfo`. The `CloudSchedulerEventHandlerInterceptor.withOptions` static method can be used to create the interceptor, defining whether it `isDefault`.
|
|
98
|
+
|
|
99
|
+
Because Cloud Scheduler jobs are often configured to not send a body, the `@EventBody` parameter can be typed as a plain `object`. In that case, body parsing and validation are skipped, and an empty object is returned.
|
|
100
|
+
|
|
91
101
|
### Spanner
|
|
92
102
|
|
|
93
103
|
The `SpannerEntityManager` is an entity manager having some similarities with TypeORM, but with a much more limited feature set. It handles entity classes decorated using `@SpannerTable` and `@SpannerColumn`.
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './firestore/index.js';
|
|
|
4
4
|
export * from './identity-platform/index.js';
|
|
5
5
|
export * from './logging/index.js';
|
|
6
6
|
export * from './pubsub/index.js';
|
|
7
|
+
export * from './scheduler/index.js';
|
|
7
8
|
export * from './spanner/index.js';
|
|
8
9
|
export * from './tasks/index.js';
|
|
9
10
|
export * from './transaction/index.js';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from './firestore/index.js';
|
|
|
4
4
|
export * from './identity-platform/index.js';
|
|
5
5
|
export * from './logging/index.js';
|
|
6
6
|
export * from './pubsub/index.js';
|
|
7
|
+
export * from './scheduler/index.js';
|
|
7
8
|
export * from './spanner/index.js';
|
|
8
9
|
export * from './tasks/index.js';
|
|
9
10
|
export * from './transaction/index.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ObjectSerializer } from '@causa/runtime';
|
|
2
|
-
import { BaseEventHandlerInterceptor, Logger, type ParsedEventRequest } from '@causa/runtime/nestjs';
|
|
2
|
+
import { BaseEventHandlerInterceptor, type EventHandlerInterceptorOptions, Logger, type ParsedEventRequest } from '@causa/runtime/nestjs';
|
|
3
3
|
import { type ExecutionContext, type Type } from '@nestjs/common';
|
|
4
4
|
import { Reflector } from '@nestjs/core';
|
|
5
5
|
import type { Request } from 'express';
|
|
@@ -34,7 +34,7 @@ declare class PubSubMessage {
|
|
|
34
34
|
*/
|
|
35
35
|
export declare class PubSubEventHandlerInterceptor extends BaseEventHandlerInterceptor {
|
|
36
36
|
protected readonly serializer: ObjectSerializer;
|
|
37
|
-
constructor(serializer: ObjectSerializer, reflector: Reflector, logger: Logger);
|
|
37
|
+
constructor(serializer: ObjectSerializer, reflector: Reflector, logger: Logger, options?: EventHandlerInterceptorOptions);
|
|
38
38
|
/**
|
|
39
39
|
* Parses the given request as the payload of a Pub/Sub push request.
|
|
40
40
|
*
|
|
@@ -53,8 +53,9 @@ export declare class PubSubEventHandlerInterceptor extends BaseEventHandlerInter
|
|
|
53
53
|
* This can be used with the `UseInterceptors` decorator.
|
|
54
54
|
*
|
|
55
55
|
* @param serializer The {@link ObjectSerializer} to use to deserialize the event data.
|
|
56
|
+
* @param options Options for the interceptor.
|
|
56
57
|
* @returns A class that can be used as an interceptor for Pub/Sub event handlers.
|
|
57
58
|
*/
|
|
58
|
-
static withSerializer(serializer: ObjectSerializer): Type<PubSubEventHandlerInterceptor>;
|
|
59
|
+
static withSerializer(serializer: ObjectSerializer, options?: EventHandlerInterceptorOptions): Type<PubSubEventHandlerInterceptor>;
|
|
59
60
|
}
|
|
60
61
|
export {};
|
|
@@ -82,8 +82,8 @@ __decorate([
|
|
|
82
82
|
*/
|
|
83
83
|
let PubSubEventHandlerInterceptor = PubSubEventHandlerInterceptor_1 = class PubSubEventHandlerInterceptor extends BaseEventHandlerInterceptor {
|
|
84
84
|
serializer;
|
|
85
|
-
constructor(serializer, reflector, logger) {
|
|
86
|
-
super(PUBSUB_EVENT_HANDLER_ID, reflector, logger);
|
|
85
|
+
constructor(serializer, reflector, logger, options = {}) {
|
|
86
|
+
super(PUBSUB_EVENT_HANDLER_ID, reflector, logger, options);
|
|
87
87
|
this.serializer = serializer;
|
|
88
88
|
this.logger.setContext(PubSubEventHandlerInterceptor_1.name);
|
|
89
89
|
}
|
|
@@ -138,12 +138,13 @@ let PubSubEventHandlerInterceptor = PubSubEventHandlerInterceptor_1 = class PubS
|
|
|
138
138
|
* This can be used with the `UseInterceptors` decorator.
|
|
139
139
|
*
|
|
140
140
|
* @param serializer The {@link ObjectSerializer} to use to deserialize the event data.
|
|
141
|
+
* @param options Options for the interceptor.
|
|
141
142
|
* @returns A class that can be used as an interceptor for Pub/Sub event handlers.
|
|
142
143
|
*/
|
|
143
|
-
static withSerializer(serializer) {
|
|
144
|
+
static withSerializer(serializer, options = {}) {
|
|
144
145
|
let PubSubEventHandlerInterceptorWithSerializer = class PubSubEventHandlerInterceptorWithSerializer extends PubSubEventHandlerInterceptor_1 {
|
|
145
146
|
constructor(reflector, logger) {
|
|
146
|
-
super(serializer, reflector, logger);
|
|
147
|
+
super(serializer, reflector, logger, options);
|
|
147
148
|
}
|
|
148
149
|
};
|
|
149
150
|
PubSubEventHandlerInterceptorWithSerializer = __decorate([
|
|
@@ -156,6 +157,6 @@ let PubSubEventHandlerInterceptor = PubSubEventHandlerInterceptor_1 = class PubS
|
|
|
156
157
|
PubSubEventHandlerInterceptor = PubSubEventHandlerInterceptor_1 = __decorate([
|
|
157
158
|
Injectable(),
|
|
158
159
|
__metadata("design:paramtypes", [Object, Reflector,
|
|
159
|
-
Logger])
|
|
160
|
+
Logger, Object])
|
|
160
161
|
], PubSubEventHandlerInterceptor);
|
|
161
162
|
export { PubSubEventHandlerInterceptor };
|
package/dist/pubsub/testing.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type ObjectSerializer } from '@causa/runtime';
|
|
|
2
2
|
import type { AppFixture, EventFixture, Fixture, NestJsModuleOverrider } from '@causa/runtime/nestjs/testing';
|
|
3
3
|
import { PubSub, Subscription, Topic } from '@google-cloud/pubsub';
|
|
4
4
|
import { type Type } from '@nestjs/common';
|
|
5
|
+
import 'jest-extended';
|
|
5
6
|
/**
|
|
6
7
|
* A received Pub/Sub message that was deserialized.
|
|
7
8
|
*/
|
|
@@ -28,6 +29,22 @@ export type ExpectMessageOptions = {
|
|
|
28
29
|
* Defaults to `2000`.
|
|
29
30
|
*/
|
|
30
31
|
timeout?: number;
|
|
32
|
+
/**
|
|
33
|
+
* When `true`, the received messages must exactly match the expected messages (same members), rather than merely
|
|
34
|
+
* including all of them.
|
|
35
|
+
* Defaults to `false`.
|
|
36
|
+
*/
|
|
37
|
+
exact?: boolean;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Options for the {@link PubSubFixture.expectEvent} method.
|
|
41
|
+
*/
|
|
42
|
+
export type ExpectEventOptions = ExpectMessageOptions & {
|
|
43
|
+
/**
|
|
44
|
+
* The attributes expected to have been published with the event.
|
|
45
|
+
* This may contain only a subset of the attributes.
|
|
46
|
+
*/
|
|
47
|
+
attributes?: Record<string, string>;
|
|
31
48
|
};
|
|
32
49
|
/**
|
|
33
50
|
* Options when making a request to an endpoint handling Pub/Sub events using an {@link EventRequester}.
|
|
@@ -124,6 +141,23 @@ export declare class PubSubFixture implements Fixture, EventFixture {
|
|
|
124
141
|
*/
|
|
125
142
|
expectedStatus?: number;
|
|
126
143
|
}): EventRequester;
|
|
144
|
+
/**
|
|
145
|
+
* Gets the received messages for the specified topic.
|
|
146
|
+
*
|
|
147
|
+
* @param topic The original name of the event topic.
|
|
148
|
+
* @returns The received messages.
|
|
149
|
+
*/
|
|
150
|
+
getReceivedMessages(topic: string): ReceivedPubSubEvent[];
|
|
151
|
+
/**
|
|
152
|
+
* Checks that the given messages have been published to the specified topic.
|
|
153
|
+
* Each expected message must match a distinct received message.
|
|
154
|
+
*
|
|
155
|
+
* @param topic The original name of the event topic.
|
|
156
|
+
* @param expectedMessages The messages expected to have been published.
|
|
157
|
+
* Each can be an `expect` expression, e.g. `expect.objectContaining({})`.
|
|
158
|
+
* @param options Options for the expectation.
|
|
159
|
+
*/
|
|
160
|
+
expectMessages(topic: string, expectedMessages: Partial<ReceivedPubSubEvent>[], options?: ExpectMessageOptions): Promise<void>;
|
|
127
161
|
/**
|
|
128
162
|
* Checks that the given message has been published to the specified topic.
|
|
129
163
|
*
|
|
@@ -133,6 +167,16 @@ export declare class PubSubFixture implements Fixture, EventFixture {
|
|
|
133
167
|
* @param options Options for the expectation.
|
|
134
168
|
*/
|
|
135
169
|
expectMessage(topic: string, expectedMessage: any, options?: ExpectMessageOptions): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* Uses {@link PubSubFixture.expectMessages} to check that the given events have been published to the specified
|
|
172
|
+
* topic. Each element in `expectedEvents` is the payload of a message, i.e. the `event` property.
|
|
173
|
+
* If `attributes` are provided, all events are expected to share those attributes.
|
|
174
|
+
*
|
|
175
|
+
* @param topic The original name of the event topic.
|
|
176
|
+
* @param expectedEvents The events expected to have been published.
|
|
177
|
+
* @param options Options for the expectation.
|
|
178
|
+
*/
|
|
179
|
+
expectEvents(topic: string, expectedEvents: any[], options?: ExpectEventOptions): Promise<void>;
|
|
136
180
|
/**
|
|
137
181
|
* Uses {@link PubSubFixture.expectMessage} to check that the given event has been published to the specified
|
|
138
182
|
* topic. The `expectedEvent` is the payload of the message, i.e. the `event` property.
|
|
@@ -141,13 +185,7 @@ export declare class PubSubFixture implements Fixture, EventFixture {
|
|
|
141
185
|
* @param expectedEvent The event expected to have been published.
|
|
142
186
|
* @param options Options for the expectation.
|
|
143
187
|
*/
|
|
144
|
-
expectEvent(topic: string, expectedEvent: any, options?:
|
|
145
|
-
/**
|
|
146
|
-
* The attributes expected to have been published with the event.
|
|
147
|
-
* This may contain only a subset of the attributes.
|
|
148
|
-
*/
|
|
149
|
-
attributes?: Record<string, string>;
|
|
150
|
-
}): Promise<void>;
|
|
188
|
+
expectEvent(topic: string, expectedEvent: any, options?: ExpectEventOptions): Promise<void>;
|
|
151
189
|
/**
|
|
152
190
|
* Checks that no message has been published to the given topic.
|
|
153
191
|
* By default, because publishing (and receiving) the messages is asynchronous, a small delay is added before checking
|
package/dist/pubsub/testing.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { JsonObjectSerializer } from '@causa/runtime';
|
|
2
2
|
import { Message, PubSub, Subscription, Topic } from '@google-cloud/pubsub';
|
|
3
3
|
import { HttpStatus } from '@nestjs/common';
|
|
4
|
+
import 'jest-extended';
|
|
4
5
|
import { setTimeout } from 'timers/promises';
|
|
5
6
|
import * as uuid from 'uuid';
|
|
6
7
|
import { getConfigurationKeyForTopic } from './configuration.js';
|
|
@@ -143,30 +144,46 @@ export class PubSubFixture {
|
|
|
143
144
|
};
|
|
144
145
|
}
|
|
145
146
|
/**
|
|
146
|
-
*
|
|
147
|
+
* Gets the received messages for the specified topic.
|
|
147
148
|
*
|
|
148
149
|
* @param topic The original name of the event topic.
|
|
149
|
-
* @
|
|
150
|
-
* This can be an `expect` expression, e.g. `expect.objectContaining({})`.
|
|
151
|
-
* @param options Options for the expectation.
|
|
150
|
+
* @returns The received messages.
|
|
152
151
|
*/
|
|
153
|
-
|
|
152
|
+
getReceivedMessages(topic) {
|
|
154
153
|
const fixture = this.topics[topic];
|
|
155
154
|
if (!fixture) {
|
|
156
155
|
throw new Error(`Fixture for topic '${topic}' does not exist.`);
|
|
157
156
|
}
|
|
158
|
-
|
|
157
|
+
return fixture.messages;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Checks that the given messages have been published to the specified topic.
|
|
161
|
+
* Each expected message must match a distinct received message.
|
|
162
|
+
*
|
|
163
|
+
* @param topic The original name of the event topic.
|
|
164
|
+
* @param expectedMessages The messages expected to have been published.
|
|
165
|
+
* Each can be an `expect` expression, e.g. `expect.objectContaining({})`.
|
|
166
|
+
* @param options Options for the expectation.
|
|
167
|
+
*/
|
|
168
|
+
async expectMessages(topic, expectedMessages, options = {}) {
|
|
169
|
+
const timeoutTime = Date.now() + (options.timeout ?? DEFAULT_EXPECT_TIMEOUT);
|
|
159
170
|
while (true) {
|
|
171
|
+
const actualMessages = this.getReceivedMessages(topic);
|
|
160
172
|
try {
|
|
161
|
-
|
|
173
|
+
if (options.exact) {
|
|
174
|
+
expect(actualMessages).toIncludeSameMembers(expectedMessages);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
expect(actualMessages).toIncludeAllMembers(expectedMessages);
|
|
178
|
+
}
|
|
162
179
|
return;
|
|
163
180
|
}
|
|
164
181
|
catch (e) {
|
|
165
|
-
if (
|
|
166
|
-
if (
|
|
167
|
-
// This throws with a clearer message
|
|
168
|
-
//
|
|
169
|
-
expect(
|
|
182
|
+
if (Date.now() >= timeoutTime) {
|
|
183
|
+
if (expectedMessages.length === 1 && actualMessages.length === 1) {
|
|
184
|
+
// This throws with a clearer message because the single received message is actually compared to the
|
|
185
|
+
// expected message.
|
|
186
|
+
expect(actualMessages[0]).toEqual(expectedMessages[0]);
|
|
170
187
|
}
|
|
171
188
|
throw e;
|
|
172
189
|
}
|
|
@@ -174,6 +191,30 @@ export class PubSubFixture {
|
|
|
174
191
|
}
|
|
175
192
|
}
|
|
176
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Checks that the given message has been published to the specified topic.
|
|
196
|
+
*
|
|
197
|
+
* @param topic The original name of the event topic.
|
|
198
|
+
* @param expectedMessage The message expected to have been published.
|
|
199
|
+
* This can be an `expect` expression, e.g. `expect.objectContaining({})`.
|
|
200
|
+
* @param options Options for the expectation.
|
|
201
|
+
*/
|
|
202
|
+
async expectMessage(topic, expectedMessage, options = {}) {
|
|
203
|
+
await this.expectMessages(topic, [expectedMessage], options);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Uses {@link PubSubFixture.expectMessages} to check that the given events have been published to the specified
|
|
207
|
+
* topic. Each element in `expectedEvents` is the payload of a message, i.e. the `event` property.
|
|
208
|
+
* If `attributes` are provided, all events are expected to share those attributes.
|
|
209
|
+
*
|
|
210
|
+
* @param topic The original name of the event topic.
|
|
211
|
+
* @param expectedEvents The events expected to have been published.
|
|
212
|
+
* @param options Options for the expectation.
|
|
213
|
+
*/
|
|
214
|
+
async expectEvents(topic, expectedEvents, options = {}) {
|
|
215
|
+
const attributes = expect.objectContaining(options.attributes ?? {});
|
|
216
|
+
await this.expectMessages(topic, expectedEvents.map((event) => expect.objectContaining({ event, attributes })), options);
|
|
217
|
+
}
|
|
177
218
|
/**
|
|
178
219
|
* Uses {@link PubSubFixture.expectMessage} to check that the given event has been published to the specified
|
|
179
220
|
* topic. The `expectedEvent` is the payload of the message, i.e. the `event` property.
|
|
@@ -183,10 +224,7 @@ export class PubSubFixture {
|
|
|
183
224
|
* @param options Options for the expectation.
|
|
184
225
|
*/
|
|
185
226
|
async expectEvent(topic, expectedEvent, options = {}) {
|
|
186
|
-
await this.
|
|
187
|
-
event: expectedEvent,
|
|
188
|
-
attributes: expect.objectContaining(options.attributes ?? {}),
|
|
189
|
-
}), options);
|
|
227
|
+
await this.expectEvents(topic, [expectedEvent], options);
|
|
190
228
|
}
|
|
191
229
|
/**
|
|
192
230
|
* Checks that no message has been published to the given topic.
|
|
@@ -197,15 +235,11 @@ export class PubSubFixture {
|
|
|
197
235
|
* @param options Options for the expectation.
|
|
198
236
|
*/
|
|
199
237
|
async expectNoMessage(topic, options = {}) {
|
|
200
|
-
const fixture = this.topics[topic];
|
|
201
|
-
if (!fixture) {
|
|
202
|
-
throw new Error(`Fixture for topic '${topic}' does not exist.`);
|
|
203
|
-
}
|
|
204
238
|
const delay = options.delay ?? DEFAULT_EXPECT_NO_MESSAGE_DELAY;
|
|
205
239
|
if (delay > 0) {
|
|
206
240
|
await setTimeout(delay);
|
|
207
241
|
}
|
|
208
|
-
const numMessages =
|
|
242
|
+
const numMessages = this.getReceivedMessages(topic).length;
|
|
209
243
|
if (numMessages > 0) {
|
|
210
244
|
throw new Error(`Expected 0 messages in '${topic}' but found ${numMessages}.`);
|
|
211
245
|
}
|
|
@@ -236,5 +270,6 @@ export class PubSubFixture {
|
|
|
236
270
|
async delete() {
|
|
237
271
|
await Promise.all(Object.keys(this.topics).map((t) => this.deleteTopic(t)));
|
|
238
272
|
await this.pubSub.close();
|
|
273
|
+
this.appFixture = undefined;
|
|
239
274
|
}
|
|
240
275
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information about a Cloud Scheduler job, extracted from HTTP headers.
|
|
3
|
+
*/
|
|
4
|
+
export declare class CloudSchedulerInfo {
|
|
5
|
+
/**
|
|
6
|
+
* The name of the Cloud Scheduler job.
|
|
7
|
+
*/
|
|
8
|
+
readonly jobName: string;
|
|
9
|
+
/**
|
|
10
|
+
* The scheduled time for the job execution.
|
|
11
|
+
*/
|
|
12
|
+
readonly scheduleTime?: Date;
|
|
13
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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, IsDateType } from '@causa/runtime';
|
|
11
|
+
import { Expose } from 'class-transformer';
|
|
12
|
+
import { IsString } from 'class-validator';
|
|
13
|
+
/**
|
|
14
|
+
* Information about a Cloud Scheduler job, extracted from HTTP headers.
|
|
15
|
+
*/
|
|
16
|
+
export class CloudSchedulerInfo {
|
|
17
|
+
/**
|
|
18
|
+
* The name of the Cloud Scheduler job.
|
|
19
|
+
*/
|
|
20
|
+
jobName;
|
|
21
|
+
/**
|
|
22
|
+
* The scheduled time for the job execution.
|
|
23
|
+
*/
|
|
24
|
+
scheduleTime;
|
|
25
|
+
}
|
|
26
|
+
__decorate([
|
|
27
|
+
Expose({ name: 'x-cloudscheduler-jobname' }),
|
|
28
|
+
IsString(),
|
|
29
|
+
__metadata("design:type", String)
|
|
30
|
+
], CloudSchedulerInfo.prototype, "jobName", void 0);
|
|
31
|
+
__decorate([
|
|
32
|
+
Expose({ name: 'x-cloudscheduler-scheduletime' }),
|
|
33
|
+
IsDateType(),
|
|
34
|
+
AllowMissing(),
|
|
35
|
+
__metadata("design:type", Date)
|
|
36
|
+
], CloudSchedulerInfo.prototype, "scheduleTime", void 0);
|
|
@@ -0,0 +1,34 @@
|
|
|
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 { CloudSchedulerInfo } from './cloud-scheduler-info.js';
|
|
6
|
+
/**
|
|
7
|
+
* The ID of the Cloud Scheduler event handler interceptor, that can be passed to the `UseEventHandler` decorator.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CLOUD_SCHEDULER_EVENT_HANDLER_ID = "google.cloudScheduler";
|
|
10
|
+
/**
|
|
11
|
+
* The interceptor that should be added to controllers handling Cloud Scheduler events.
|
|
12
|
+
*
|
|
13
|
+
* Because Cloud Scheduler jobs are often configured to not send a body, the `@EventBody` parameter of route handlers
|
|
14
|
+
* can be typed as a plain `object`. In that case, body parsing and validation are skipped, and an empty object is
|
|
15
|
+
* returned.
|
|
16
|
+
*/
|
|
17
|
+
export declare class CloudSchedulerEventHandlerInterceptor extends BaseEventHandlerInterceptor {
|
|
18
|
+
constructor(reflector: Reflector, logger: Logger, options?: EventHandlerInterceptorOptions);
|
|
19
|
+
/**
|
|
20
|
+
* Parses the Cloud Scheduler request, extracting job information from headers.
|
|
21
|
+
*
|
|
22
|
+
* @param request The express request object.
|
|
23
|
+
* @returns The parsed Cloud Scheduler information.
|
|
24
|
+
*/
|
|
25
|
+
protected parseCloudSchedulerRequest(request: Request): Promise<CloudSchedulerInfo>;
|
|
26
|
+
protected parseEventFromContext(context: ExecutionContext, dataType: Type): Promise<ParsedEventRequest>;
|
|
27
|
+
/**
|
|
28
|
+
* Creates a Cloud Scheduler event handler interceptor class with the given options.
|
|
29
|
+
*
|
|
30
|
+
* @param options Options for the interceptor.
|
|
31
|
+
* @returns The Cloud Scheduler event handler interceptor class.
|
|
32
|
+
*/
|
|
33
|
+
static withOptions(options: EventHandlerInterceptorOptions): Type<CloudSchedulerEventHandlerInterceptor>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
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 CloudSchedulerEventHandlerInterceptor_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 { CloudSchedulerInfo } from './cloud-scheduler-info.js';
|
|
16
|
+
/**
|
|
17
|
+
* The ID of the Cloud Scheduler event handler interceptor, that can be passed to the `UseEventHandler` decorator.
|
|
18
|
+
*/
|
|
19
|
+
export const CLOUD_SCHEDULER_EVENT_HANDLER_ID = 'google.cloudScheduler';
|
|
20
|
+
/**
|
|
21
|
+
* The interceptor that should be added to controllers handling Cloud Scheduler events.
|
|
22
|
+
*
|
|
23
|
+
* Because Cloud Scheduler jobs are often configured to not send a body, the `@EventBody` parameter of route handlers
|
|
24
|
+
* can be typed as a plain `object`. In that case, body parsing and validation are skipped, and an empty object is
|
|
25
|
+
* returned.
|
|
26
|
+
*/
|
|
27
|
+
let CloudSchedulerEventHandlerInterceptor = CloudSchedulerEventHandlerInterceptor_1 = class CloudSchedulerEventHandlerInterceptor extends BaseEventHandlerInterceptor {
|
|
28
|
+
constructor(reflector, logger, options = {}) {
|
|
29
|
+
super(CLOUD_SCHEDULER_EVENT_HANDLER_ID, reflector, logger, options);
|
|
30
|
+
this.logger.setContext(CloudSchedulerEventHandlerInterceptor_1.name);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parses the Cloud Scheduler request, extracting job information from headers.
|
|
34
|
+
*
|
|
35
|
+
* @param request The express request object.
|
|
36
|
+
* @returns The parsed Cloud Scheduler information.
|
|
37
|
+
*/
|
|
38
|
+
async parseCloudSchedulerRequest(request) {
|
|
39
|
+
try {
|
|
40
|
+
const info = await parseObject(CloudSchedulerInfo, request.headers, {
|
|
41
|
+
...validatorOptions,
|
|
42
|
+
forbidNonWhitelisted: false,
|
|
43
|
+
});
|
|
44
|
+
this.logger.info('Successfully parsed Cloud Scheduler request.');
|
|
45
|
+
return info;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
this.logger.error({
|
|
49
|
+
error: error.stack,
|
|
50
|
+
...(error instanceof ValidationError
|
|
51
|
+
? { validationMessages: error.validationMessages }
|
|
52
|
+
: {}),
|
|
53
|
+
}, 'Received invalid Cloud Scheduler request.');
|
|
54
|
+
throwHttpErrorResponse(new BadRequestErrorDto());
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async parseEventFromContext(context, dataType) {
|
|
58
|
+
const request = context
|
|
59
|
+
.switchToHttp()
|
|
60
|
+
.getRequest();
|
|
61
|
+
request.cloudSchedulerInfo = await this.parseCloudSchedulerRequest(request);
|
|
62
|
+
// This supports `@EventBody()` decorating a plain object type. This can be useful as bodies are often not needed
|
|
63
|
+
// for Cloud Scheduler jobs.
|
|
64
|
+
if (dataType === Object) {
|
|
65
|
+
return { attributes: {}, body: {} };
|
|
66
|
+
}
|
|
67
|
+
return await this.wrapParsing(async () => {
|
|
68
|
+
const body = await parseObject(dataType, request.body, {
|
|
69
|
+
forbidNonWhitelisted: false,
|
|
70
|
+
});
|
|
71
|
+
return { attributes: {}, body };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates a Cloud Scheduler event handler interceptor class with the given options.
|
|
76
|
+
*
|
|
77
|
+
* @param options Options for the interceptor.
|
|
78
|
+
* @returns The Cloud Scheduler event handler interceptor class.
|
|
79
|
+
*/
|
|
80
|
+
static withOptions(options) {
|
|
81
|
+
let CloudSchedulerEventHandlerInterceptorWithOptions = class CloudSchedulerEventHandlerInterceptorWithOptions extends CloudSchedulerEventHandlerInterceptor_1 {
|
|
82
|
+
constructor(reflector, logger) {
|
|
83
|
+
super(reflector, logger, options);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
CloudSchedulerEventHandlerInterceptorWithOptions = __decorate([
|
|
87
|
+
Injectable(),
|
|
88
|
+
__metadata("design:paramtypes", [Reflector, Logger])
|
|
89
|
+
], CloudSchedulerEventHandlerInterceptorWithOptions);
|
|
90
|
+
return CloudSchedulerEventHandlerInterceptorWithOptions;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
CloudSchedulerEventHandlerInterceptor = CloudSchedulerEventHandlerInterceptor_1 = __decorate([
|
|
94
|
+
Injectable(),
|
|
95
|
+
__metadata("design:paramtypes", [Reflector,
|
|
96
|
+
Logger, Object])
|
|
97
|
+
], CloudSchedulerEventHandlerInterceptor);
|
|
98
|
+
export { CloudSchedulerEventHandlerInterceptor };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CloudSchedulerInfo } from './cloud-scheduler-info.js';
|
|
2
|
+
/**
|
|
3
|
+
* Additional information expected to be present on an express request that was parsed as a Cloud Scheduler request.
|
|
4
|
+
*/
|
|
5
|
+
export type RequestWithCloudSchedulerInfo = {
|
|
6
|
+
/**
|
|
7
|
+
* Information about the Cloud Scheduler job.
|
|
8
|
+
*/
|
|
9
|
+
cloudSchedulerInfo: CloudSchedulerInfo;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Decorates a route handler's parameter to populate it with information about the Cloud Scheduler job.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CloudSchedulerEventInfo: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createParamDecorator } from '@nestjs/common';
|
|
2
|
+
/**
|
|
3
|
+
* Decorates a route handler's parameter to populate it with information about the Cloud Scheduler job.
|
|
4
|
+
*/
|
|
5
|
+
export const CloudSchedulerEventInfo = createParamDecorator((_data, ctx) => {
|
|
6
|
+
const request = ctx
|
|
7
|
+
.switchToHttp()
|
|
8
|
+
.getRequest();
|
|
9
|
+
return request.cloudSchedulerInfo;
|
|
10
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AppFixture, Fixture } from '@causa/runtime/nestjs/testing';
|
|
2
|
+
import type { CloudSchedulerInfo } from './cloud-scheduler-info.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options when making a request to an endpoint handling Cloud Scheduler events using a {@link CloudSchedulerEventRequester}.
|
|
5
|
+
*/
|
|
6
|
+
export type CloudSchedulerEventRequesterOptions = {
|
|
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 Scheduler job to include in the request headers.
|
|
14
|
+
* If not provided, default values will be used.
|
|
15
|
+
*/
|
|
16
|
+
readonly jobInfo?: Partial<CloudSchedulerInfo>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A function that makes a query to an endpoint handling Cloud Scheduler events and tests the response.
|
|
20
|
+
*/
|
|
21
|
+
export type CloudSchedulerEventRequester = (event?: object, options?: CloudSchedulerEventRequesterOptions) => Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* A utility class for testing Cloud Scheduler event handlers.
|
|
24
|
+
*/
|
|
25
|
+
export declare class CloudSchedulerFixture implements Fixture {
|
|
26
|
+
/**
|
|
27
|
+
* The parent {@link AppFixture}.
|
|
28
|
+
*/
|
|
29
|
+
private appFixture;
|
|
30
|
+
init(appFixture: AppFixture): Promise<undefined>;
|
|
31
|
+
/**
|
|
32
|
+
* Creates a {@link CloudSchedulerEventRequester} for an endpoint handling Cloud Scheduler events.
|
|
33
|
+
*
|
|
34
|
+
* @param endpoint The endpoint to query.
|
|
35
|
+
* @param options Options when creating the requester.
|
|
36
|
+
* @returns The {@link CloudSchedulerEventRequester}.
|
|
37
|
+
*/
|
|
38
|
+
makeRequester(endpoint: string, options?: CloudSchedulerEventRequesterOptions): CloudSchedulerEventRequester;
|
|
39
|
+
clear(): Promise<void>;
|
|
40
|
+
delete(): Promise<void>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { HttpStatus } from '@nestjs/common';
|
|
2
|
+
/**
|
|
3
|
+
* A utility class for testing Cloud Scheduler event handlers.
|
|
4
|
+
*/
|
|
5
|
+
export class CloudSchedulerFixture {
|
|
6
|
+
/**
|
|
7
|
+
* The parent {@link AppFixture}.
|
|
8
|
+
*/
|
|
9
|
+
appFixture;
|
|
10
|
+
async init(appFixture) {
|
|
11
|
+
this.appFixture = appFixture;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Creates a {@link CloudSchedulerEventRequester} for an endpoint handling Cloud Scheduler events.
|
|
15
|
+
*
|
|
16
|
+
* @param endpoint The endpoint to query.
|
|
17
|
+
* @param options Options when creating the requester.
|
|
18
|
+
* @returns The {@link CloudSchedulerEventRequester}.
|
|
19
|
+
*/
|
|
20
|
+
makeRequester(endpoint, options = {}) {
|
|
21
|
+
return async (event, requestOptions) => {
|
|
22
|
+
const jobInfo = {
|
|
23
|
+
jobName: 'jobName',
|
|
24
|
+
...options.jobInfo,
|
|
25
|
+
...requestOptions?.jobInfo,
|
|
26
|
+
};
|
|
27
|
+
const headers = {
|
|
28
|
+
'x-cloudscheduler-jobname': jobInfo.jobName,
|
|
29
|
+
};
|
|
30
|
+
if (jobInfo.scheduleTime !== undefined) {
|
|
31
|
+
headers['x-cloudscheduler-scheduletime'] =
|
|
32
|
+
jobInfo.scheduleTime.toISOString();
|
|
33
|
+
}
|
|
34
|
+
const expectedStatus = requestOptions?.expectedStatus ??
|
|
35
|
+
options.expectedStatus ??
|
|
36
|
+
HttpStatus.OK;
|
|
37
|
+
await this.appFixture.request
|
|
38
|
+
.post(endpoint)
|
|
39
|
+
.set(headers)
|
|
40
|
+
.send(event)
|
|
41
|
+
.expect(expectedStatus);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async clear() { }
|
|
45
|
+
async delete() {
|
|
46
|
+
this.appFixture = undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/spanner/errors.d.ts
CHANGED
|
@@ -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.
|
package/dist/spanner/errors.js
CHANGED
|
@@ -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
|
/**
|
package/dist/spanner/types.d.ts
CHANGED
|
@@ -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);
|
package/dist/tasks/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/tasks/index.js
CHANGED
|
@@ -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.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';
|
|
@@ -34,5 +36,7 @@ export function createGoogleFixtures(options = {}) {
|
|
|
34
36
|
new PubSubFixture(options.pubSubTopics ?? {}),
|
|
35
37
|
...(disableAppCheck ? [new AppCheckFixture()] : []),
|
|
36
38
|
...versionedEntityFixture,
|
|
39
|
+
new CloudTasksFixture(),
|
|
40
|
+
new CloudSchedulerFixture(),
|
|
37
41
|
];
|
|
38
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@causa/runtime-google",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
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.
|
|
35
|
+
"@causa/runtime": "^1.6.0",
|
|
36
36
|
"@google-cloud/precise-date": "^5.0.0",
|
|
37
|
-
"@google-cloud/pubsub": "^5.2.
|
|
38
|
-
"@google-cloud/spanner": "^8.
|
|
37
|
+
"@google-cloud/pubsub": "^5.2.2",
|
|
38
|
+
"@google-cloud/spanner": "^8.5.0",
|
|
39
39
|
"@google-cloud/tasks": "^6.2.1",
|
|
40
40
|
"@grpc/grpc-js": "^1.14.3",
|
|
41
|
-
"@nestjs/common": "^11.1.
|
|
41
|
+
"@nestjs/common": "^11.1.12",
|
|
42
42
|
"@nestjs/config": "^4.0.2",
|
|
43
|
-
"@nestjs/core": "^11.1.
|
|
43
|
+
"@nestjs/core": "^11.1.12",
|
|
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.
|
|
52
|
+
"pino": "^10.3.0",
|
|
53
53
|
"reflect-metadata": "^0.2.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@nestjs/testing": "^11.1.
|
|
57
|
-
"@swc/core": "^1.15.
|
|
56
|
+
"@nestjs/testing": "^11.1.12",
|
|
57
|
+
"@swc/core": "^1.15.10",
|
|
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.
|
|
62
|
+
"@types/node": "^20.19.30",
|
|
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.
|
|
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.
|
|
76
|
+
"typescript-eslint": "^8.53.1",
|
|
77
77
|
"uuid": "^13.0.0"
|
|
78
78
|
}
|
|
79
79
|
}
|