@causa/runtime-google 0.37.1 → 0.38.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/dist/transaction/spanner-outbox/event.d.ts +17 -2
- package/dist/transaction/spanner-outbox/event.js +21 -2
- package/dist/transaction/spanner-outbox/module.d.ts +2 -2
- package/dist/transaction/spanner-outbox/module.js +2 -0
- package/dist/transaction/spanner-outbox/runner.d.ts +3 -2
- package/dist/transaction/spanner-outbox/sender.d.ts +9 -0
- package/dist/transaction/spanner-outbox/sender.js +20 -3
- package/package.json +8 -8
|
@@ -11,11 +11,22 @@ import type { EventAttributes, OutboxEvent } from '@causa/runtime';
|
|
|
11
11
|
* data BYTES(MAX) NOT NULL,
|
|
12
12
|
* attributes JSON NOT NULL,
|
|
13
13
|
* leaseExpiration TIMESTAMP,
|
|
14
|
+
* publishedAt TIMESTAMP,
|
|
14
15
|
* -- 20 is the number of shards.
|
|
15
16
|
* shard INT64 AS (MOD(ABS(FARM_FINGERPRINT(id)), 20)),
|
|
16
|
-
* ) PRIMARY KEY (id)
|
|
17
|
-
*
|
|
17
|
+
* ) PRIMARY KEY (id)
|
|
18
|
+
* ROW DELETION POLICY (OLDER_THAN(publishedAt, INTERVAL 0 DAY));
|
|
19
|
+
* CREATE INDEX OutboxEventsByShardAndLeaseExpiration ON OutboxEvent(shard, leaseExpiration) STORING (publishedAt);
|
|
18
20
|
* ```
|
|
21
|
+
*
|
|
22
|
+
* Compared to the {@link OutboxEvent} interface, this requires a `publishedAt` column to be defined, on which the row
|
|
23
|
+
* deletion policy should be set. This allows updating published events rather than deleting them directly.
|
|
24
|
+
* Updating published events allows setting the `leaseExpiration` column to a distant date, such that those events are
|
|
25
|
+
* not scanned when fetching events to publish.
|
|
26
|
+
* Because Spanner allows for version retention, recently deleted rows are still scanned (even as part of an efficient
|
|
27
|
+
* index scan). This can affect performances if the rows are not updated before being deleted, because the
|
|
28
|
+
* `leaseExpiration` date of published events would end up back in the scanned range - in the past (although rows are
|
|
29
|
+
* deleted and not returned).
|
|
19
30
|
*/
|
|
20
31
|
export declare class SpannerOutboxEvent implements OutboxEvent {
|
|
21
32
|
constructor(init: SpannerOutboxEvent);
|
|
@@ -24,4 +35,8 @@ export declare class SpannerOutboxEvent implements OutboxEvent {
|
|
|
24
35
|
readonly data: Buffer;
|
|
25
36
|
readonly attributes: EventAttributes;
|
|
26
37
|
readonly leaseExpiration: Date | null;
|
|
38
|
+
/**
|
|
39
|
+
* The date at which the event was successfully published.
|
|
40
|
+
*/
|
|
41
|
+
readonly publishedAt: Date | null;
|
|
27
42
|
}
|
|
@@ -20,11 +20,22 @@ import { SpannerColumn, SpannerTable } from '../../spanner/index.js';
|
|
|
20
20
|
* data BYTES(MAX) NOT NULL,
|
|
21
21
|
* attributes JSON NOT NULL,
|
|
22
22
|
* leaseExpiration TIMESTAMP,
|
|
23
|
+
* publishedAt TIMESTAMP,
|
|
23
24
|
* -- 20 is the number of shards.
|
|
24
25
|
* shard INT64 AS (MOD(ABS(FARM_FINGERPRINT(id)), 20)),
|
|
25
|
-
* ) PRIMARY KEY (id)
|
|
26
|
-
*
|
|
26
|
+
* ) PRIMARY KEY (id)
|
|
27
|
+
* ROW DELETION POLICY (OLDER_THAN(publishedAt, INTERVAL 0 DAY));
|
|
28
|
+
* CREATE INDEX OutboxEventsByShardAndLeaseExpiration ON OutboxEvent(shard, leaseExpiration) STORING (publishedAt);
|
|
27
29
|
* ```
|
|
30
|
+
*
|
|
31
|
+
* Compared to the {@link OutboxEvent} interface, this requires a `publishedAt` column to be defined, on which the row
|
|
32
|
+
* deletion policy should be set. This allows updating published events rather than deleting them directly.
|
|
33
|
+
* Updating published events allows setting the `leaseExpiration` column to a distant date, such that those events are
|
|
34
|
+
* not scanned when fetching events to publish.
|
|
35
|
+
* Because Spanner allows for version retention, recently deleted rows are still scanned (even as part of an efficient
|
|
36
|
+
* index scan). This can affect performances if the rows are not updated before being deleted, because the
|
|
37
|
+
* `leaseExpiration` date of published events would end up back in the scanned range - in the past (although rows are
|
|
38
|
+
* deleted and not returned).
|
|
28
39
|
*/
|
|
29
40
|
let SpannerOutboxEvent = class SpannerOutboxEvent {
|
|
30
41
|
constructor(init) {
|
|
@@ -35,6 +46,10 @@ let SpannerOutboxEvent = class SpannerOutboxEvent {
|
|
|
35
46
|
data;
|
|
36
47
|
attributes;
|
|
37
48
|
leaseExpiration;
|
|
49
|
+
/**
|
|
50
|
+
* The date at which the event was successfully published.
|
|
51
|
+
*/
|
|
52
|
+
publishedAt;
|
|
38
53
|
};
|
|
39
54
|
__decorate([
|
|
40
55
|
SpannerColumn(),
|
|
@@ -56,6 +71,10 @@ __decorate([
|
|
|
56
71
|
SpannerColumn(),
|
|
57
72
|
__metadata("design:type", Object)
|
|
58
73
|
], SpannerOutboxEvent.prototype, "leaseExpiration", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
SpannerColumn(),
|
|
76
|
+
__metadata("design:type", Object)
|
|
77
|
+
], SpannerOutboxEvent.prototype, "publishedAt", void 0);
|
|
59
78
|
SpannerOutboxEvent = __decorate([
|
|
60
79
|
SpannerTable({ name: 'OutboxEvent', primaryKey: ['id'] }),
|
|
61
80
|
__metadata("design:paramtypes", [SpannerOutboxEvent])
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { OutboxEvent } from '@causa/runtime';
|
|
2
1
|
import type { DynamicModule, Type } from '@nestjs/common';
|
|
2
|
+
import { SpannerOutboxEvent } from './event.js';
|
|
3
3
|
import { type SpannerOutboxSenderOptions } from './sender.js';
|
|
4
4
|
/**
|
|
5
5
|
* Options for the {@link SpannerOutboxTransactionModule}.
|
|
@@ -10,7 +10,7 @@ export type SpannerOutboxTransactionModuleOptions = SpannerOutboxSenderOptions &
|
|
|
10
10
|
* This should be a valid class decorated with `@SpannerTable`.
|
|
11
11
|
* Defaults to {@link SpannerOutboxEvent}.
|
|
12
12
|
*/
|
|
13
|
-
outboxEventType?: Type<
|
|
13
|
+
outboxEventType?: Type<SpannerOutboxEvent>;
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
16
|
* The module providing the {@link SpannerOutboxTransactionRunner}.
|
|
@@ -29,6 +29,7 @@ function parseSenderOptions(defaultOptions, customOptions, config) {
|
|
|
29
29
|
const pollingInterval = validateIntOrUndefined('SPANNER_OUTBOX_POLLING_INTERVAL');
|
|
30
30
|
const idColumn = config.get('SPANNER_OUTBOX_ID_COLUMN');
|
|
31
31
|
const leaseExpirationColumn = config.get('SPANNER_OUTBOX_LEASE_EXPIRATION_COLUMN');
|
|
32
|
+
const publishedAtColumn = config.get('SPANNER_OUTBOX_PUBLISHED_AT_COLUMN');
|
|
32
33
|
const index = config.get('SPANNER_OUTBOX_INDEX');
|
|
33
34
|
const shardingColumn = config.get('SPANNER_OUTBOX_SHARDING_COLUMN');
|
|
34
35
|
const shardingCount = validateIntOrUndefined('SPANNER_OUTBOX_SHARDING_COUNT');
|
|
@@ -41,6 +42,7 @@ function parseSenderOptions(defaultOptions, customOptions, config) {
|
|
|
41
42
|
pollingInterval,
|
|
42
43
|
idColumn,
|
|
43
44
|
leaseExpirationColumn,
|
|
45
|
+
publishedAtColumn,
|
|
44
46
|
index,
|
|
45
47
|
sharding,
|
|
46
48
|
leaseDuration,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { OutboxTransactionRunner, type
|
|
1
|
+
import { OutboxTransactionRunner, type OutboxEventTransaction } from '@causa/runtime';
|
|
2
2
|
import { Logger } from '@causa/runtime/nestjs';
|
|
3
3
|
import type { Type } from '@nestjs/common';
|
|
4
4
|
import { SpannerEntityManager } from '../../spanner/index.js';
|
|
5
5
|
import { SpannerTransaction } from '../spanner-transaction.js';
|
|
6
|
+
import type { SpannerOutboxEvent } from './event.js';
|
|
6
7
|
import { SpannerOutboxSender } from './sender.js';
|
|
7
8
|
/**
|
|
8
9
|
* A {@link SpannerTransaction} that uses an {@link OutboxEventTransaction}.
|
|
@@ -23,6 +24,6 @@ export type SpannerOutboxTransactionOption = {
|
|
|
23
24
|
*/
|
|
24
25
|
export declare class SpannerOutboxTransactionRunner extends OutboxTransactionRunner<SpannerOutboxTransaction> {
|
|
25
26
|
readonly entityManager: SpannerEntityManager;
|
|
26
|
-
constructor(entityManager: SpannerEntityManager, outboxEventType: Type<
|
|
27
|
+
constructor(entityManager: SpannerEntityManager, outboxEventType: Type<SpannerOutboxEvent>, sender: SpannerOutboxSender, logger: Logger);
|
|
27
28
|
protected runStateTransaction<RT>(eventTransactionFactory: () => OutboxEventTransaction, runFn: (transaction: SpannerOutboxTransaction) => Promise<RT>): Promise<RT>;
|
|
28
29
|
}
|
|
@@ -34,6 +34,11 @@ export type SpannerOutboxSenderOptions = OutboxEventSenderOptions & {
|
|
|
34
34
|
* Defaults to `leaseExpiration`.
|
|
35
35
|
*/
|
|
36
36
|
readonly leaseExpirationColumn?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The name of the column used to store the timestamp at which the event was published.
|
|
39
|
+
* Defaults to `publishedAt`.
|
|
40
|
+
*/
|
|
41
|
+
readonly publishedAtColumn?: string;
|
|
37
42
|
/**
|
|
38
43
|
* The index used to fetch events.
|
|
39
44
|
*/
|
|
@@ -58,6 +63,10 @@ export declare class SpannerOutboxSender extends OutboxEventSender {
|
|
|
58
63
|
* The name of the column used for the {@link OutboxEvent.leaseExpiration} property.
|
|
59
64
|
*/
|
|
60
65
|
readonly leaseExpirationColumn: string;
|
|
66
|
+
/**
|
|
67
|
+
* The name of the column used for the {@link OutboxEvent.publishedAt} property.
|
|
68
|
+
*/
|
|
69
|
+
readonly publishedAtColumn: string;
|
|
61
70
|
/**
|
|
62
71
|
* The index used to fetch events.
|
|
63
72
|
*/
|
|
@@ -9,6 +9,10 @@ const DEFAULT_ID_COLUMN = 'id';
|
|
|
9
9
|
* The default name for the {@link OutboxEvent.leaseExpiration} column.
|
|
10
10
|
*/
|
|
11
11
|
const DEFAULT_LEASE_EXPIRATION_COLUMN = 'leaseExpiration';
|
|
12
|
+
/**
|
|
13
|
+
* The default name for the {@link OutboxEvent.publishedAt} column.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_PUBLISHED_AT_COLUMN = 'publishedAt';
|
|
12
16
|
/**
|
|
13
17
|
* An {@link OutboxEventSender} that uses a Spanner table to store events.
|
|
14
18
|
*/
|
|
@@ -28,6 +32,10 @@ export class SpannerOutboxSender extends OutboxEventSender {
|
|
|
28
32
|
* The name of the column used for the {@link OutboxEvent.leaseExpiration} property.
|
|
29
33
|
*/
|
|
30
34
|
leaseExpirationColumn;
|
|
35
|
+
/**
|
|
36
|
+
* The name of the column used for the {@link OutboxEvent.publishedAt} property.
|
|
37
|
+
*/
|
|
38
|
+
publishedAtColumn;
|
|
31
39
|
/**
|
|
32
40
|
* The index used to fetch events.
|
|
33
41
|
*/
|
|
@@ -67,6 +75,8 @@ export class SpannerOutboxSender extends OutboxEventSender {
|
|
|
67
75
|
this.idColumn = options.idColumn ?? DEFAULT_ID_COLUMN;
|
|
68
76
|
this.leaseExpirationColumn =
|
|
69
77
|
options.leaseExpirationColumn ?? DEFAULT_LEASE_EXPIRATION_COLUMN;
|
|
78
|
+
this.publishedAtColumn =
|
|
79
|
+
options.publishedAtColumn ?? DEFAULT_PUBLISHED_AT_COLUMN;
|
|
70
80
|
this.index = options.index;
|
|
71
81
|
({
|
|
72
82
|
fetchEventsSql: this.fetchEventsSql,
|
|
@@ -83,7 +93,11 @@ export class SpannerOutboxSender extends OutboxEventSender {
|
|
|
83
93
|
buildSql() {
|
|
84
94
|
const table = this.entityManager.sqlTableName(this.outboxEventType);
|
|
85
95
|
const tableWithIndex = this.entityManager.sqlTableName(this.outboxEventType, { index: this.index });
|
|
86
|
-
const noLeaseFilter =
|
|
96
|
+
const noLeaseFilter = `(
|
|
97
|
+
\`${this.leaseExpirationColumn}\` IS NULL OR
|
|
98
|
+
\`${this.leaseExpirationColumn}\` < @currentTime
|
|
99
|
+
) AND
|
|
100
|
+
\`${this.publishedAtColumn}\` IS NULL`;
|
|
87
101
|
let fetchFilter = noLeaseFilter;
|
|
88
102
|
if (this.sharding) {
|
|
89
103
|
const { column, count } = this.sharding;
|
|
@@ -112,8 +126,11 @@ export class SpannerOutboxSender extends OutboxEventSender {
|
|
|
112
126
|
THEN RETURN
|
|
113
127
|
${this.entityManager.sqlColumns(this.outboxEventType)}`;
|
|
114
128
|
const successfulUpdateSql = `
|
|
115
|
-
|
|
129
|
+
UPDATE
|
|
116
130
|
${table}
|
|
131
|
+
SET
|
|
132
|
+
\`${this.leaseExpirationColumn}\` = '9999-12-31T00:00:00.000Z',
|
|
133
|
+
\`${this.publishedAtColumn}\` = @currentTime
|
|
117
134
|
WHERE
|
|
118
135
|
\`${this.idColumn}\` IN UNNEST(@ids)`;
|
|
119
136
|
const failedUpdateSql = `
|
|
@@ -156,7 +173,7 @@ export class SpannerOutboxSender extends OutboxEventSender {
|
|
|
156
173
|
if (successfulSends.length > 0) {
|
|
157
174
|
batchUpdates.push({
|
|
158
175
|
sql: this.successfulUpdateSql,
|
|
159
|
-
params: { ids: successfulSends },
|
|
176
|
+
params: { ids: successfulSends, currentTime: new Date() },
|
|
160
177
|
});
|
|
161
178
|
}
|
|
162
179
|
if (failedSends.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@causa/runtime-google",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "An extension to the Causa runtime SDK (`@causa/runtime`), providing Google-specific features.",
|
|
5
5
|
"repository": "github:causa-io/runtime-typescript-google",
|
|
6
6
|
"license": "ISC",
|
|
@@ -51,27 +51,27 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@nestjs/testing": "^11.0.10",
|
|
54
|
-
"@swc/core": "^1.
|
|
54
|
+
"@swc/core": "^1.11.1",
|
|
55
55
|
"@swc/jest": "^0.2.37",
|
|
56
56
|
"@tsconfig/node22": "^22.0.0",
|
|
57
57
|
"@types/jest": "^29.5.14",
|
|
58
|
-
"@types/jsonwebtoken": "^9.0.
|
|
59
|
-
"@types/node": "^22.13.
|
|
58
|
+
"@types/jsonwebtoken": "^9.0.9",
|
|
59
|
+
"@types/node": "^22.13.5",
|
|
60
60
|
"@types/passport-http-bearer": "^1.0.41",
|
|
61
61
|
"@types/supertest": "^6.0.2",
|
|
62
62
|
"@types/uuid": "^10.0.0",
|
|
63
63
|
"dotenv": "^16.4.7",
|
|
64
|
-
"eslint": "^9.
|
|
64
|
+
"eslint": "^9.21.0",
|
|
65
65
|
"eslint-config-prettier": "^10.0.1",
|
|
66
66
|
"eslint-plugin-prettier": "^5.2.3",
|
|
67
67
|
"jest": "^29.7.0",
|
|
68
68
|
"jest-extended": "^4.0.2",
|
|
69
69
|
"rimraf": "^6.0.1",
|
|
70
70
|
"supertest": "^7.0.0",
|
|
71
|
-
"ts-jest": "^29.2.
|
|
71
|
+
"ts-jest": "^29.2.6",
|
|
72
72
|
"ts-node": "^10.9.2",
|
|
73
73
|
"typescript": "^5.7.3",
|
|
74
|
-
"typescript-eslint": "^8.
|
|
75
|
-
"uuid": "^11.0
|
|
74
|
+
"typescript-eslint": "^8.25.0",
|
|
75
|
+
"uuid": "^11.1.0"
|
|
76
76
|
}
|
|
77
77
|
}
|