@causa/runtime-google 0.40.0 → 1.0.0-rc.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.
- package/README.md +7 -14
- package/dist/app-check/testing.d.ts +7 -3
- package/dist/app-check/testing.js +9 -6
- package/dist/firebase/testing.d.ts +7 -4
- package/dist/firebase/testing.js +12 -7
- package/dist/firestore/testing.d.ts +32 -17
- package/dist/firestore/testing.js +45 -29
- package/dist/identity-platform/testing.d.ts +21 -6
- package/dist/identity-platform/testing.js +34 -9
- package/dist/pubsub/publisher.module.d.ts +1 -1
- package/dist/pubsub/{testing/fixture.d.ts → testing.d.ts} +64 -48
- package/dist/pubsub/{testing/fixture.js → testing.js} +106 -73
- package/dist/spanner/column.decorator.d.ts +1 -11
- package/dist/spanner/column.decorator.js +2 -7
- package/dist/spanner/conversion.d.ts +5 -18
- package/dist/spanner/conversion.js +45 -125
- package/dist/spanner/entity-manager.d.ts +19 -23
- package/dist/spanner/entity-manager.js +26 -60
- package/dist/spanner/table-cache.js +0 -3
- package/dist/spanner/testing.d.ts +37 -18
- package/dist/spanner/testing.js +75 -10
- package/dist/spanner/types.d.ts +0 -6
- package/dist/testing.d.ts +36 -2
- package/dist/testing.js +33 -2
- package/dist/transaction/firestore-pubsub/index.d.ts +3 -2
- package/dist/transaction/firestore-pubsub/index.js +2 -1
- package/dist/transaction/firestore-pubsub/nestjs-collection-resolver.d.ts +1 -1
- package/dist/transaction/firestore-pubsub/nestjs-collection-resolver.js +0 -1
- package/dist/transaction/firestore-pubsub/readonly-state-transaction.d.ts +37 -0
- package/dist/transaction/firestore-pubsub/readonly-state-transaction.js +46 -0
- package/dist/transaction/firestore-pubsub/runner.d.ts +6 -4
- package/dist/transaction/firestore-pubsub/runner.js +16 -7
- package/dist/transaction/firestore-pubsub/state-transaction.d.ts +11 -49
- package/dist/transaction/firestore-pubsub/state-transaction.js +19 -42
- package/dist/transaction/firestore-pubsub/transaction.d.ts +15 -3
- package/dist/transaction/firestore-pubsub/transaction.js +21 -3
- package/dist/transaction/firestore-pubsub/types.d.ts +36 -0
- package/dist/transaction/firestore-pubsub/types.js +1 -0
- package/dist/transaction/index.d.ts +0 -3
- package/dist/transaction/index.js +0 -3
- package/dist/transaction/spanner-outbox/index.d.ts +3 -1
- package/dist/transaction/spanner-outbox/index.js +3 -0
- package/dist/transaction/spanner-outbox/readonly-transaction.d.ts +22 -0
- package/dist/transaction/spanner-outbox/readonly-transaction.js +25 -0
- package/dist/transaction/spanner-outbox/runner.d.ts +7 -18
- package/dist/transaction/spanner-outbox/runner.js +13 -6
- package/dist/transaction/{spanner-utils.js → spanner-outbox/spanner-utils.js} +1 -1
- package/dist/transaction/spanner-outbox/state-transaction.d.ts +20 -0
- package/dist/transaction/spanner-outbox/state-transaction.js +34 -0
- package/dist/transaction/spanner-outbox/transaction.d.ts +28 -0
- package/dist/transaction/spanner-outbox/transaction.js +38 -0
- package/package.json +24 -21
- package/dist/pubsub/testing/index.d.ts +0 -4
- package/dist/pubsub/testing/index.js +0 -2
- package/dist/pubsub/testing/requester.d.ts +0 -50
- package/dist/pubsub/testing/requester.js +0 -53
- package/dist/testing/google-app-fixture.d.ts +0 -191
- package/dist/testing/google-app-fixture.js +0 -200
- package/dist/testing/index.d.ts +0 -1
- package/dist/testing/index.js +0 -1
- package/dist/transaction/spanner-pubsub/index.d.ts +0 -2
- package/dist/transaction/spanner-pubsub/index.js +0 -2
- package/dist/transaction/spanner-pubsub/module.d.ts +0 -14
- package/dist/transaction/spanner-pubsub/module.js +0 -21
- package/dist/transaction/spanner-pubsub/runner.d.ts +0 -23
- package/dist/transaction/spanner-pubsub/runner.js +0 -69
- package/dist/transaction/spanner-state-transaction.d.ts +0 -20
- package/dist/transaction/spanner-state-transaction.js +0 -35
- package/dist/transaction/spanner-transaction.d.ts +0 -16
- package/dist/transaction/spanner-transaction.js +0 -20
- /package/dist/transaction/{spanner-utils.d.ts → spanner-outbox/spanner-utils.d.ts} +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { JsonObjectSerializer } from '@causa/runtime';
|
|
2
2
|
import { Message, PubSub, Subscription, Topic } from '@google-cloud/pubsub';
|
|
3
|
+
import { HttpStatus } from '@nestjs/common';
|
|
3
4
|
import { setTimeout } from 'timers/promises';
|
|
4
5
|
import * as uuid from 'uuid';
|
|
5
|
-
import { getConfigurationKeyForTopic } from '
|
|
6
|
-
import { PUBSUB_PUBLISHER_CONFIGURATION_GETTER_INJECTION_NAME } from '
|
|
6
|
+
import { getConfigurationKeyForTopic } from './configuration.js';
|
|
7
|
+
import { PUBSUB_PUBLISHER_CONFIGURATION_GETTER_INJECTION_NAME } from './publisher.module.js';
|
|
7
8
|
/**
|
|
8
9
|
* The default duration (in milliseconds) after which `expectMessageInTopic` times out.
|
|
9
10
|
*/
|
|
@@ -20,14 +21,15 @@ const DURATION_BETWEEN_EXPECT_ATTEMPTS = 50;
|
|
|
20
21
|
* A utility class managing temporary Pub/Sub topics and listening to messages published to them.
|
|
21
22
|
*/
|
|
22
23
|
export class PubSubFixture {
|
|
24
|
+
topicsAndTypes;
|
|
23
25
|
/**
|
|
24
|
-
* The
|
|
26
|
+
* The parent {@link AppFixture}.
|
|
25
27
|
*/
|
|
26
|
-
|
|
28
|
+
appFixture;
|
|
27
29
|
/**
|
|
28
|
-
*
|
|
30
|
+
* The Pub/Sub client to use.
|
|
29
31
|
*/
|
|
30
|
-
|
|
32
|
+
pubSub;
|
|
31
33
|
/**
|
|
32
34
|
* The (de)serializer to use for Pub/Sub messages.
|
|
33
35
|
*/
|
|
@@ -36,85 +38,122 @@ export class PubSubFixture {
|
|
|
36
38
|
* The dictionary of monitored temporary topics.
|
|
37
39
|
* The key is the name of the event topic (not broker-specific).
|
|
38
40
|
*/
|
|
39
|
-
|
|
41
|
+
topics = {};
|
|
40
42
|
/**
|
|
41
43
|
* Creates a new {@link PubSubFixture}.
|
|
42
44
|
*
|
|
45
|
+
* @param topicsAndTypes The dictionary of topics to test and their event types.
|
|
43
46
|
* @param options Options for the fixture.
|
|
44
47
|
*/
|
|
45
|
-
constructor(options = {}) {
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
+
constructor(topicsAndTypes, options = {}) {
|
|
49
|
+
this.topicsAndTypes = topicsAndTypes;
|
|
50
|
+
this.pubSub = new PubSub();
|
|
48
51
|
this.serializer = options.serializer ?? new JsonObjectSerializer();
|
|
49
52
|
}
|
|
50
53
|
/**
|
|
51
54
|
* Creates a new temporary topic and starts listening to messages published to it.
|
|
52
55
|
*
|
|
53
|
-
* @param
|
|
56
|
+
* @param sourceTopic The original name of the topic, i.e. the one used in production.
|
|
54
57
|
* @param eventType The type of the event published to the topic, used for deserialization.
|
|
55
58
|
* @returns The configuration key and value for the created temporary topic.
|
|
56
59
|
*/
|
|
57
|
-
async create(
|
|
58
|
-
await this.
|
|
60
|
+
async create(sourceTopic, eventType) {
|
|
61
|
+
await this.deleteTopic(sourceTopic);
|
|
59
62
|
const suffix = uuid.v4().slice(-10);
|
|
60
|
-
const topicName = `${
|
|
63
|
+
const topicName = `${sourceTopic}-${suffix}`;
|
|
61
64
|
const subscriptionName = `fixture-${suffix}`;
|
|
62
65
|
// This ensures the project ID is populated in the Pub/Sub client.
|
|
63
66
|
// Because the configuration is cached, it should be okay to call this multiple times.
|
|
64
67
|
await this.pubSub.getClientConfig();
|
|
65
68
|
const [topic] = await this.pubSub.createTopic(topicName);
|
|
66
69
|
const [subscription] = await topic.createSubscription(subscriptionName);
|
|
67
|
-
this.
|
|
70
|
+
this.topics[sourceTopic] = { topic, subscription, messages: [] };
|
|
68
71
|
subscription.on('message', async (message) => {
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
72
|
+
const topicFixture = this.topics[sourceTopic];
|
|
73
|
+
if (topicFixture?.topic !== topic) {
|
|
71
74
|
return;
|
|
72
75
|
}
|
|
73
76
|
const event = await this.serializer.deserialize(eventType, message.data);
|
|
74
|
-
|
|
77
|
+
topicFixture.messages.push({
|
|
75
78
|
attributes: message.attributes,
|
|
76
79
|
orderingKey: message.orderingKey ? message.orderingKey : undefined,
|
|
77
80
|
event,
|
|
78
81
|
});
|
|
79
82
|
message.ack();
|
|
80
83
|
});
|
|
81
|
-
return { [getConfigurationKeyForTopic(
|
|
84
|
+
return { [getConfigurationKeyForTopic(sourceTopic)]: topic.name };
|
|
82
85
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const configurations = await Promise.all(Object.entries(topicsAndTypes).map(async ([topicName, eventType]) => this.create(topicName, eventType)));
|
|
91
|
-
return Object.assign({}, ...configurations);
|
|
86
|
+
async init(appFixture) {
|
|
87
|
+
this.appFixture = appFixture;
|
|
88
|
+
const configurations = await Promise.all(Object.entries(this.topicsAndTypes).map(([topic, eventType]) => this.create(topic, eventType)));
|
|
89
|
+
const configuration = Object.assign({}, ...configurations);
|
|
90
|
+
return (builder) => builder
|
|
91
|
+
.overrideProvider(PUBSUB_PUBLISHER_CONFIGURATION_GETTER_INJECTION_NAME)
|
|
92
|
+
.useValue((key) => configuration[key]);
|
|
92
93
|
}
|
|
93
94
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
95
|
+
* Creates an {@link EventRequester} for an endpoint handling Pub/Sub messages.
|
|
96
|
+
* If the `event` passed to the {@link EventRequester} conforms to the `Event` interface (if it has `producedAt`,
|
|
97
|
+
* `name` and / or `id` properties), the default attributes are set in the Pub/Sub message. Default attributes can be
|
|
98
|
+
* overridden (or removed by passing `undefined`) using {@link EventRequesterOptions.attributes}.
|
|
96
99
|
*
|
|
97
|
-
* @param
|
|
98
|
-
* @
|
|
100
|
+
* @param endpoint The endpoint to query.
|
|
101
|
+
* @param options Options when creating the requester.
|
|
102
|
+
* @returns The {@link EventRequester}.
|
|
99
103
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
.
|
|
104
|
+
makeRequester(endpoint, options = {}) {
|
|
105
|
+
return async (event, requestOptions) => {
|
|
106
|
+
const messageId = uuid.v4();
|
|
107
|
+
const publishTime = (requestOptions?.publishTime ?? new Date()).toISOString();
|
|
108
|
+
const buffer = await this.serializer.serialize(event);
|
|
109
|
+
const data = buffer.toString('base64');
|
|
110
|
+
// Default attributes if the event conforms to the `Event` interface.
|
|
111
|
+
const defaultAttributes = {};
|
|
112
|
+
if ('producedAt' in event && event.producedAt instanceof Date) {
|
|
113
|
+
defaultAttributes.producedAt = event.producedAt.toISOString();
|
|
114
|
+
}
|
|
115
|
+
if ('name' in event && typeof event.name === 'string') {
|
|
116
|
+
defaultAttributes.eventName = event.name;
|
|
117
|
+
}
|
|
118
|
+
if ('id' in event && typeof event.id === 'string') {
|
|
119
|
+
defaultAttributes.eventId = event.id;
|
|
120
|
+
}
|
|
121
|
+
const attributes = {
|
|
122
|
+
...defaultAttributes,
|
|
123
|
+
...requestOptions?.attributes,
|
|
124
|
+
};
|
|
125
|
+
const payload = {
|
|
126
|
+
message: {
|
|
127
|
+
messageId,
|
|
128
|
+
message_id: messageId,
|
|
129
|
+
publishTime,
|
|
130
|
+
publish_time: publishTime,
|
|
131
|
+
attributes,
|
|
132
|
+
data,
|
|
133
|
+
},
|
|
134
|
+
subscription: 'subscription',
|
|
135
|
+
};
|
|
136
|
+
const expectedStatus = requestOptions?.expectedStatus ??
|
|
137
|
+
options.expectedStatus ??
|
|
138
|
+
HttpStatus.OK;
|
|
139
|
+
await this.appFixture.request
|
|
140
|
+
.post(endpoint)
|
|
141
|
+
.send(payload)
|
|
142
|
+
.expect(expectedStatus);
|
|
143
|
+
};
|
|
105
144
|
}
|
|
106
145
|
/**
|
|
107
146
|
* Checks that the given message has been published to the specified topic.
|
|
108
147
|
*
|
|
109
|
-
* @param
|
|
148
|
+
* @param topic The original name of the event topic.
|
|
110
149
|
* @param expectedMessage The message expected to have been published.
|
|
111
150
|
* This can be an `expect` expression, e.g. `expect.objectContaining({})`.
|
|
112
151
|
* @param options Options for the expectation.
|
|
113
152
|
*/
|
|
114
|
-
async
|
|
115
|
-
const fixture = this.
|
|
153
|
+
async expectMessage(topic, expectedMessage, options = {}) {
|
|
154
|
+
const fixture = this.topics[topic];
|
|
116
155
|
if (!fixture) {
|
|
117
|
-
throw new Error(`Fixture for topic '${
|
|
156
|
+
throw new Error(`Fixture for topic '${topic}' does not exist.`);
|
|
118
157
|
}
|
|
119
158
|
const timeoutTime = new Date().getTime() + (options.timeout ?? DEFAULT_EXPECT_TIMEOUT);
|
|
120
159
|
while (true) {
|
|
@@ -136,15 +175,15 @@ export class PubSubFixture {
|
|
|
136
175
|
}
|
|
137
176
|
}
|
|
138
177
|
/**
|
|
139
|
-
* Uses {@link PubSubFixture.
|
|
178
|
+
* Uses {@link PubSubFixture.expectMessage} to check that the given event has been published to the specified
|
|
140
179
|
* topic. The `expectedEvent` is the payload of the message, i.e. the `event` property.
|
|
141
180
|
*
|
|
142
|
-
* @param
|
|
181
|
+
* @param topic The original name of the event topic.
|
|
143
182
|
* @param expectedEvent The event expected to have been published.
|
|
144
183
|
* @param options Options for the expectation.
|
|
145
184
|
*/
|
|
146
|
-
async
|
|
147
|
-
await this.
|
|
185
|
+
async expectEvent(topic, expectedEvent, options = {}) {
|
|
186
|
+
await this.expectMessage(topic, expect.objectContaining({
|
|
148
187
|
event: expectedEvent,
|
|
149
188
|
attributes: expect.objectContaining(options.attributes ?? {}),
|
|
150
189
|
}), options);
|
|
@@ -154,13 +193,13 @@ export class PubSubFixture {
|
|
|
154
193
|
* By default, because publishing (and receiving) the messages is asynchronous, a small delay is added before checking
|
|
155
194
|
* that no message has been received. This delay can be removed or increased by passing the `delay` option.
|
|
156
195
|
*
|
|
157
|
-
* @param
|
|
196
|
+
* @param topic The original name of the topic, i.e. the one used in production.
|
|
158
197
|
* @param options Options for the expectation.
|
|
159
198
|
*/
|
|
160
|
-
async
|
|
161
|
-
const fixture = this.
|
|
199
|
+
async expectNoMessage(topic, options = {}) {
|
|
200
|
+
const fixture = this.topics[topic];
|
|
162
201
|
if (!fixture) {
|
|
163
|
-
throw new Error(`Fixture for topic '${
|
|
202
|
+
throw new Error(`Fixture for topic '${topic}' does not exist.`);
|
|
164
203
|
}
|
|
165
204
|
const delay = options.delay ?? DEFAULT_EXPECT_NO_MESSAGE_DELAY;
|
|
166
205
|
if (delay > 0) {
|
|
@@ -168,40 +207,34 @@ export class PubSubFixture {
|
|
|
168
207
|
}
|
|
169
208
|
const numMessages = fixture.messages.length;
|
|
170
209
|
if (numMessages > 0) {
|
|
171
|
-
throw new Error(`Expected 0 messages in '${
|
|
210
|
+
throw new Error(`Expected 0 messages in '${topic}' but found ${numMessages}.`);
|
|
172
211
|
}
|
|
173
212
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
clear() {
|
|
178
|
-
Object.values(this.
|
|
179
|
-
|
|
213
|
+
async expectNoEvent(topic) {
|
|
214
|
+
await this.expectNoMessage(topic);
|
|
215
|
+
}
|
|
216
|
+
async clear() {
|
|
217
|
+
Object.values(this.topics).forEach((t) => {
|
|
218
|
+
t.messages = [];
|
|
180
219
|
});
|
|
181
220
|
}
|
|
182
221
|
/**
|
|
183
222
|
* Deletes the temporary topic for the corresponding "production" topic.
|
|
184
223
|
*
|
|
185
|
-
* @param
|
|
224
|
+
* @param topic The original name of the topic, i.e. the one used in production.
|
|
186
225
|
*/
|
|
187
|
-
async
|
|
188
|
-
const
|
|
189
|
-
if (!
|
|
226
|
+
async deleteTopic(topic) {
|
|
227
|
+
const topicFixture = this.topics[topic];
|
|
228
|
+
if (!topicFixture) {
|
|
190
229
|
return;
|
|
191
230
|
}
|
|
192
|
-
delete this.
|
|
193
|
-
|
|
194
|
-
await
|
|
195
|
-
await
|
|
231
|
+
delete this.topics[topic];
|
|
232
|
+
topicFixture.subscription.removeAllListeners();
|
|
233
|
+
await topicFixture.subscription.delete();
|
|
234
|
+
await topicFixture.topic.delete();
|
|
196
235
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
*/
|
|
201
|
-
async deleteAll() {
|
|
202
|
-
await Promise.all(Object.keys(this.fixtures).map((sourceTopicName) => this.delete(sourceTopicName)));
|
|
203
|
-
if (this.isManagedClient) {
|
|
204
|
-
await this.pubSub.close();
|
|
205
|
-
}
|
|
236
|
+
async delete() {
|
|
237
|
+
await Promise.all(Object.keys(this.topics).map((t) => this.deleteTopic(t)));
|
|
238
|
+
await this.pubSub.close();
|
|
206
239
|
}
|
|
207
240
|
}
|
|
@@ -8,15 +8,6 @@ export type SpannerColumnMetadata = {
|
|
|
8
8
|
* The name of the Spanner column for this property.
|
|
9
9
|
*/
|
|
10
10
|
name: string;
|
|
11
|
-
/**
|
|
12
|
-
* If the type of the property is a nested type, this is the class for this property.
|
|
13
|
-
*/
|
|
14
|
-
nestedType?: Type;
|
|
15
|
-
/**
|
|
16
|
-
* When `true`, sets the property to null if all properties is the nested object are null.
|
|
17
|
-
* Defaults to `false`.
|
|
18
|
-
*/
|
|
19
|
-
nullifyNested: boolean;
|
|
20
11
|
/**
|
|
21
12
|
* When `true`, the column is assumed to be of type `INT64` and the value will be safely stored in a `bigint`.
|
|
22
13
|
*/
|
|
@@ -60,7 +51,6 @@ export declare function getSpannerColumnsMetadata(classType: Type): SpannerColum
|
|
|
60
51
|
* Lists all the columns in the given class, based on {@link SpannerColumn} decorators.
|
|
61
52
|
*
|
|
62
53
|
* @param classType The type for the table.
|
|
63
|
-
* @param namePrefix Used for recursion with nested types, do not use directly.
|
|
64
54
|
* @returns The list of columns for the given class.
|
|
65
55
|
*/
|
|
66
|
-
export declare function getSpannerColumns(classType: Type
|
|
56
|
+
export declare function getSpannerColumns(classType: Type): string[];
|
|
@@ -16,8 +16,6 @@ export function SpannerColumn(options = {}) {
|
|
|
16
16
|
const isPreciseDate = options.isPreciseDate ?? false;
|
|
17
17
|
metadata[propertyKey] = {
|
|
18
18
|
name: options.name ?? propertyKey,
|
|
19
|
-
nestedType: options.nestedType,
|
|
20
|
-
nullifyNested: options.nullifyNested ?? false,
|
|
21
19
|
isInt: options.isInt ?? false,
|
|
22
20
|
isBigInt: options.isBigInt ?? false,
|
|
23
21
|
isPreciseDate,
|
|
@@ -55,12 +53,9 @@ export function getSpannerColumnsMetadata(classType) {
|
|
|
55
53
|
* Lists all the columns in the given class, based on {@link SpannerColumn} decorators.
|
|
56
54
|
*
|
|
57
55
|
* @param classType The type for the table.
|
|
58
|
-
* @param namePrefix Used for recursion with nested types, do not use directly.
|
|
59
56
|
* @returns The list of columns for the given class.
|
|
60
57
|
*/
|
|
61
|
-
export function getSpannerColumns(classType
|
|
58
|
+
export function getSpannerColumns(classType) {
|
|
62
59
|
const columnsMetadata = getSpannerColumnsMetadata(classType);
|
|
63
|
-
return Object.values(columnsMetadata).flatMap((c) => c.
|
|
64
|
-
? getSpannerColumns(c.nestedType, `${c.name}_`)
|
|
65
|
-
: `${namePrefix}${c.name}`);
|
|
60
|
+
return Object.values(columnsMetadata).flatMap((c) => c.name);
|
|
66
61
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Type } from '@nestjs/common';
|
|
2
|
-
import type { RecursivePartialEntity } from './types.js';
|
|
3
2
|
/**
|
|
4
3
|
* Creates a typed class instance from an object returned by the Spanner API.
|
|
5
4
|
*
|
|
@@ -13,35 +12,23 @@ export declare function spannerObjectToInstance<T>(spannerObject: Record<string,
|
|
|
13
12
|
*
|
|
14
13
|
* @param instance The object to convert to a Spanner object.
|
|
15
14
|
* @param type The type of the object to convert.
|
|
16
|
-
* @param columnNamePrefix The prefix to add to column names. Used for recursion and should not be set directly.
|
|
17
15
|
* @returns A generic JavaScript object that can be passed to the Spanner API.
|
|
18
16
|
*/
|
|
19
|
-
export declare function
|
|
17
|
+
export declare function instanceToSpannerObject<T>(instance: T | Partial<T>, type: Type<T>): Record<string, any>;
|
|
20
18
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @param instance The object to convert to a Spanner object.
|
|
24
|
-
* @param type The type of the object to convert.
|
|
25
|
-
* @returns A generic JavaScript object that can be passed to the Spanner API.
|
|
26
|
-
*/
|
|
27
|
-
export declare function instanceToSpannerObject<T>(instance: T | RecursivePartialEntity<T>, type: Type<T>): Record<string, any>;
|
|
28
|
-
/**
|
|
29
|
-
* Copies an instance, recursively setting all columns that are not defined in the instance to `null`.
|
|
30
|
-
* Columns with the {@link SpannerColumnMetadata.nullifyNested} option set to `true` are also set to `null`.
|
|
19
|
+
* Copies an instance, setting all columns that are not defined in the instance to `null`.
|
|
31
20
|
*
|
|
32
21
|
* @param instance The instance to copy.
|
|
33
22
|
* @param type The type of the instance.
|
|
34
23
|
* @returns The copied instance.
|
|
35
24
|
*/
|
|
36
|
-
export declare function copyInstanceWithMissingColumnsToNull<T>(instance: T |
|
|
25
|
+
export declare function copyInstanceWithMissingColumnsToNull<T>(instance: T | Partial<T>, type: Type<T>): T;
|
|
37
26
|
/**
|
|
38
|
-
*
|
|
39
|
-
* Updates are applied "column-wise", which means that recursion stops at properties decorated as columns.
|
|
40
|
-
* For example, JSON values are not affected.
|
|
27
|
+
* Updates an instance with the values from the update.
|
|
41
28
|
*
|
|
42
29
|
* @param instance The instance to update. It should be a full, typed, instance, unless `type` is passed as well.
|
|
43
30
|
* @param update The update to apply to the instance.
|
|
44
31
|
* @param type The type of the instance. If not provided, it will be inferred from the instance.
|
|
45
32
|
* @returns The updated instance.
|
|
46
33
|
*/
|
|
47
|
-
export declare function updateInstanceByColumn<T>(instance: T, update:
|
|
34
|
+
export declare function updateInstanceByColumn<T>(instance: T, update: Partial<T>, type?: Type<T>): T;
|
|
@@ -7,38 +7,23 @@ import { getSpannerColumnsMetadata, } from './column.decorator.js';
|
|
|
7
7
|
*
|
|
8
8
|
* @param spannerObject The object returned by Spanner that should be converted back to a class instance.
|
|
9
9
|
* @param type The class for the object.
|
|
10
|
-
* @param options Used for recursion and should not be set directly.
|
|
11
10
|
* @returns The created object.
|
|
12
11
|
*/
|
|
13
|
-
function
|
|
12
|
+
export function spannerObjectToInstance(spannerObject, type) {
|
|
14
13
|
const columnsMetadata = getSpannerColumnsMetadata(type);
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
else if (Array.isArray(spannerObject[columnName])) {
|
|
30
|
-
plain[property] = spannerObject[columnName].map((v) => spannerValueToJavaScript(v, columnMetadata));
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
plain[property] = spannerValueToJavaScript(spannerObject[columnName], columnMetadata);
|
|
34
|
-
}
|
|
35
|
-
if (plain[property] != null) {
|
|
36
|
-
hasAtLeastOneNonNullValue = true;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
return hasAtLeastOneNonNullValue || !options.nullifyInstance
|
|
40
|
-
? plainToInstance(type, plain)
|
|
41
|
-
: null;
|
|
14
|
+
const plain = Object.fromEntries(Object.entries(columnsMetadata).map(([property, columnMetadata]) => {
|
|
15
|
+
const columnName = columnMetadata.name;
|
|
16
|
+
const value = spannerObject[columnName];
|
|
17
|
+
if (columnMetadata.isJson) {
|
|
18
|
+
return [property, value];
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
const values = value.map((v) => spannerValueToJavaScript(v, columnMetadata));
|
|
22
|
+
return [property, values];
|
|
23
|
+
}
|
|
24
|
+
return [property, spannerValueToJavaScript(value, columnMetadata)];
|
|
25
|
+
}));
|
|
26
|
+
return plainToInstance(type, plain);
|
|
42
27
|
}
|
|
43
28
|
/**
|
|
44
29
|
* Converts a scalar value returned by Spanner to a regular JavaScript value.
|
|
@@ -54,17 +39,15 @@ function spannerValueToJavaScript(value, columnMetadata) {
|
|
|
54
39
|
// Floats are always safe and can always be unwrapped.
|
|
55
40
|
return value.valueOf();
|
|
56
41
|
}
|
|
57
|
-
|
|
42
|
+
if (value instanceof Int) {
|
|
58
43
|
if (columnMetadata.isBigInt) {
|
|
59
44
|
return BigInt(value.value);
|
|
60
45
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return value.valueOf();
|
|
65
|
-
}
|
|
46
|
+
// This unwraps the string to an integer number, but might throw if the integer is above what can be represented
|
|
47
|
+
// safely as a float.
|
|
48
|
+
return value.valueOf();
|
|
66
49
|
}
|
|
67
|
-
|
|
50
|
+
if (value instanceof Date &&
|
|
68
51
|
// `PreciseDate` extends `Date`. This was previously used to handle conflicting versions of `PreciseDate`.
|
|
69
52
|
value.constructor.name === PreciseDate.name &&
|
|
70
53
|
!columnMetadata.isPreciseDate) {
|
|
@@ -74,17 +57,6 @@ function spannerValueToJavaScript(value, columnMetadata) {
|
|
|
74
57
|
}
|
|
75
58
|
return value;
|
|
76
59
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Creates a typed class instance from an object returned by the Spanner API.
|
|
79
|
-
*
|
|
80
|
-
* @param spannerObject The object returned by Spanner that should be converted back to a class instance.
|
|
81
|
-
* @param type The class for the object.
|
|
82
|
-
* @returns The created object.
|
|
83
|
-
*/
|
|
84
|
-
export function spannerObjectToInstance(spannerObject, type) {
|
|
85
|
-
// This is okay as `null` can only be returned when the internal option `nullifyInstance` is set.
|
|
86
|
-
return spannerObjectToInstanceWithOptions(spannerObject, type);
|
|
87
|
-
}
|
|
88
60
|
/**
|
|
89
61
|
* Converts a value such that it is safe to pass to the Spanner client.
|
|
90
62
|
* Numeric values and arrays of numeric values are wrapped using Spanner classes.
|
|
@@ -103,50 +75,19 @@ function makeSpannerValue(value, metadata) {
|
|
|
103
75
|
? value.map((v) => new Int(v.toString()))
|
|
104
76
|
: new Int(value.toString());
|
|
105
77
|
}
|
|
106
|
-
|
|
78
|
+
if (metadata.isJson) {
|
|
107
79
|
return JSON.stringify(value);
|
|
108
80
|
}
|
|
109
|
-
|
|
81
|
+
if (typeof value === 'number') {
|
|
110
82
|
return new Float(value);
|
|
111
83
|
}
|
|
112
|
-
|
|
84
|
+
if (Array.isArray(value) &&
|
|
113
85
|
value.length > 0 &&
|
|
114
86
|
typeof value[0] === 'number') {
|
|
115
87
|
return value.map((v) => new Float(v));
|
|
116
88
|
}
|
|
117
89
|
return value;
|
|
118
90
|
}
|
|
119
|
-
/**
|
|
120
|
-
* Converts a class object to a plain JavaScript object that can be passed to the Spanner API.
|
|
121
|
-
*
|
|
122
|
-
* @param instance The object to convert to a Spanner object.
|
|
123
|
-
* @param type The type of the object to convert.
|
|
124
|
-
* @param columnNamePrefix The prefix to add to column names. Used for recursion and should not be set directly.
|
|
125
|
-
* @returns A generic JavaScript object that can be passed to the Spanner API.
|
|
126
|
-
*/
|
|
127
|
-
export function instanceToSpannerObjectInternal(instance, type, columnNamePrefix = '') {
|
|
128
|
-
const columnsMetadata = getSpannerColumnsMetadata(type);
|
|
129
|
-
let spannerObject = {};
|
|
130
|
-
Object.entries(columnsMetadata).forEach(([property, columnMetadata]) => {
|
|
131
|
-
const columnName = `${columnNamePrefix}${columnMetadata.name}`;
|
|
132
|
-
// When the current property value is `undefined`, column(s) values should not be set.
|
|
133
|
-
if (instance !== null && instance[property] === undefined) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
// When the whole instance is `null`, all its child properties should be set to null.
|
|
137
|
-
const propertyValue = instance === null ? null : instance[property];
|
|
138
|
-
if (columnMetadata.nestedType) {
|
|
139
|
-
spannerObject = {
|
|
140
|
-
...spannerObject,
|
|
141
|
-
...instanceToSpannerObjectInternal(propertyValue, columnMetadata.nestedType, `${columnName}_`),
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
spannerObject[columnName] = makeSpannerValue(propertyValue, columnMetadata);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
return spannerObject;
|
|
149
|
-
}
|
|
150
91
|
/**
|
|
151
92
|
* Converts a class object to a plain JavaScript object that can be passed to the Spanner API.
|
|
152
93
|
*
|
|
@@ -155,11 +96,18 @@ export function instanceToSpannerObjectInternal(instance, type, columnNamePrefix
|
|
|
155
96
|
* @returns A generic JavaScript object that can be passed to the Spanner API.
|
|
156
97
|
*/
|
|
157
98
|
export function instanceToSpannerObject(instance, type) {
|
|
158
|
-
|
|
99
|
+
const columnsMetadata = getSpannerColumnsMetadata(type);
|
|
100
|
+
return Object.fromEntries(Object.entries(columnsMetadata)
|
|
101
|
+
.map(([p, m]) => [m, instance[p]])
|
|
102
|
+
// When the current property value is `undefined`, the column value should not be set.
|
|
103
|
+
.filter(([, v]) => v !== undefined)
|
|
104
|
+
.map(([metadata, value]) => [
|
|
105
|
+
metadata.name,
|
|
106
|
+
makeSpannerValue(value, metadata),
|
|
107
|
+
]));
|
|
159
108
|
}
|
|
160
109
|
/**
|
|
161
|
-
* Copies an instance,
|
|
162
|
-
* Columns with the {@link SpannerColumnMetadata.nullifyNested} option set to `true` are also set to `null`.
|
|
110
|
+
* Copies an instance, setting all columns that are not defined in the instance to `null`.
|
|
163
111
|
*
|
|
164
112
|
* @param instance The instance to copy.
|
|
165
113
|
* @param type The type of the instance.
|
|
@@ -167,30 +115,14 @@ export function instanceToSpannerObject(instance, type) {
|
|
|
167
115
|
*/
|
|
168
116
|
export function copyInstanceWithMissingColumnsToNull(instance, type) {
|
|
169
117
|
const columnsMetadata = getSpannerColumnsMetadata(type);
|
|
170
|
-
const plain = {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (columnMetadata.nullifyNested && instanceValue == null) {
|
|
175
|
-
plain[property] = null;
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
plain[property] = copyInstanceWithMissingColumnsToNull(instanceValue, columnMetadata.nestedType);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
else if (instanceValue !== undefined) {
|
|
182
|
-
plain[property] = instanceValue;
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
plain[property] = null;
|
|
186
|
-
}
|
|
187
|
-
});
|
|
118
|
+
const plain = Object.fromEntries(Object.keys(columnsMetadata).map((property) => {
|
|
119
|
+
const value = instance[property];
|
|
120
|
+
return [property, value === undefined ? null : value];
|
|
121
|
+
}));
|
|
188
122
|
return plainToInstance(type, plain);
|
|
189
123
|
}
|
|
190
124
|
/**
|
|
191
|
-
*
|
|
192
|
-
* Updates are applied "column-wise", which means that recursion stops at properties decorated as columns.
|
|
193
|
-
* For example, JSON values are not affected.
|
|
125
|
+
* Updates an instance with the values from the update.
|
|
194
126
|
*
|
|
195
127
|
* @param instance The instance to update. It should be a full, typed, instance, unless `type` is passed as well.
|
|
196
128
|
* @param update The update to apply to the instance.
|
|
@@ -200,25 +132,13 @@ export function copyInstanceWithMissingColumnsToNull(instance, type) {
|
|
|
200
132
|
export function updateInstanceByColumn(instance, update, type) {
|
|
201
133
|
type ??= instance.constructor;
|
|
202
134
|
const columnsMetadata = getSpannerColumnsMetadata(type);
|
|
203
|
-
const plain = {
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (columnMetadata.nestedType) {
|
|
212
|
-
if (columnMetadata.nullifyNested && updateValue === null) {
|
|
213
|
-
plain[property] = null;
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
plain[property] = updateInstanceByColumn(instanceValue, updateValue, columnMetadata.nestedType);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
else if (updateValue !== undefined) {
|
|
220
|
-
plain[property] = updateValue;
|
|
221
|
-
}
|
|
222
|
-
});
|
|
135
|
+
const plain = Object.fromEntries(Object.keys(columnsMetadata).map((property) => {
|
|
136
|
+
const instanceValue = instance[property];
|
|
137
|
+
const updateValue = update[property];
|
|
138
|
+
return [
|
|
139
|
+
property,
|
|
140
|
+
updateValue === undefined ? instanceValue : updateValue,
|
|
141
|
+
];
|
|
142
|
+
}));
|
|
223
143
|
return plainToInstance(type, plain);
|
|
224
144
|
}
|