@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.
Files changed (71) hide show
  1. package/README.md +7 -14
  2. package/dist/app-check/testing.d.ts +7 -3
  3. package/dist/app-check/testing.js +9 -6
  4. package/dist/firebase/testing.d.ts +7 -4
  5. package/dist/firebase/testing.js +12 -7
  6. package/dist/firestore/testing.d.ts +32 -17
  7. package/dist/firestore/testing.js +45 -29
  8. package/dist/identity-platform/testing.d.ts +21 -6
  9. package/dist/identity-platform/testing.js +34 -9
  10. package/dist/pubsub/publisher.module.d.ts +1 -1
  11. package/dist/pubsub/{testing/fixture.d.ts → testing.d.ts} +64 -48
  12. package/dist/pubsub/{testing/fixture.js → testing.js} +106 -73
  13. package/dist/spanner/column.decorator.d.ts +1 -11
  14. package/dist/spanner/column.decorator.js +2 -7
  15. package/dist/spanner/conversion.d.ts +5 -18
  16. package/dist/spanner/conversion.js +45 -125
  17. package/dist/spanner/entity-manager.d.ts +19 -23
  18. package/dist/spanner/entity-manager.js +26 -60
  19. package/dist/spanner/table-cache.js +0 -3
  20. package/dist/spanner/testing.d.ts +37 -18
  21. package/dist/spanner/testing.js +75 -10
  22. package/dist/spanner/types.d.ts +0 -6
  23. package/dist/testing.d.ts +36 -2
  24. package/dist/testing.js +33 -2
  25. package/dist/transaction/firestore-pubsub/index.d.ts +3 -2
  26. package/dist/transaction/firestore-pubsub/index.js +2 -1
  27. package/dist/transaction/firestore-pubsub/nestjs-collection-resolver.d.ts +1 -1
  28. package/dist/transaction/firestore-pubsub/nestjs-collection-resolver.js +0 -1
  29. package/dist/transaction/firestore-pubsub/readonly-state-transaction.d.ts +37 -0
  30. package/dist/transaction/firestore-pubsub/readonly-state-transaction.js +46 -0
  31. package/dist/transaction/firestore-pubsub/runner.d.ts +6 -4
  32. package/dist/transaction/firestore-pubsub/runner.js +16 -7
  33. package/dist/transaction/firestore-pubsub/state-transaction.d.ts +11 -49
  34. package/dist/transaction/firestore-pubsub/state-transaction.js +19 -42
  35. package/dist/transaction/firestore-pubsub/transaction.d.ts +15 -3
  36. package/dist/transaction/firestore-pubsub/transaction.js +21 -3
  37. package/dist/transaction/firestore-pubsub/types.d.ts +36 -0
  38. package/dist/transaction/firestore-pubsub/types.js +1 -0
  39. package/dist/transaction/index.d.ts +0 -3
  40. package/dist/transaction/index.js +0 -3
  41. package/dist/transaction/spanner-outbox/index.d.ts +3 -1
  42. package/dist/transaction/spanner-outbox/index.js +3 -0
  43. package/dist/transaction/spanner-outbox/readonly-transaction.d.ts +22 -0
  44. package/dist/transaction/spanner-outbox/readonly-transaction.js +25 -0
  45. package/dist/transaction/spanner-outbox/runner.d.ts +7 -18
  46. package/dist/transaction/spanner-outbox/runner.js +13 -6
  47. package/dist/transaction/{spanner-utils.js → spanner-outbox/spanner-utils.js} +1 -1
  48. package/dist/transaction/spanner-outbox/state-transaction.d.ts +20 -0
  49. package/dist/transaction/spanner-outbox/state-transaction.js +34 -0
  50. package/dist/transaction/spanner-outbox/transaction.d.ts +28 -0
  51. package/dist/transaction/spanner-outbox/transaction.js +38 -0
  52. package/package.json +24 -21
  53. package/dist/pubsub/testing/index.d.ts +0 -4
  54. package/dist/pubsub/testing/index.js +0 -2
  55. package/dist/pubsub/testing/requester.d.ts +0 -50
  56. package/dist/pubsub/testing/requester.js +0 -53
  57. package/dist/testing/google-app-fixture.d.ts +0 -191
  58. package/dist/testing/google-app-fixture.js +0 -200
  59. package/dist/testing/index.d.ts +0 -1
  60. package/dist/testing/index.js +0 -1
  61. package/dist/transaction/spanner-pubsub/index.d.ts +0 -2
  62. package/dist/transaction/spanner-pubsub/index.js +0 -2
  63. package/dist/transaction/spanner-pubsub/module.d.ts +0 -14
  64. package/dist/transaction/spanner-pubsub/module.js +0 -21
  65. package/dist/transaction/spanner-pubsub/runner.d.ts +0 -23
  66. package/dist/transaction/spanner-pubsub/runner.js +0 -69
  67. package/dist/transaction/spanner-state-transaction.d.ts +0 -20
  68. package/dist/transaction/spanner-state-transaction.js +0 -35
  69. package/dist/transaction/spanner-transaction.d.ts +0 -16
  70. package/dist/transaction/spanner-transaction.js +0 -20
  71. /package/dist/transaction/{spanner-utils.d.ts → spanner-outbox/spanner-utils.d.ts} +0 -0
@@ -1,9 +1,10 @@
1
- import { TransactionRunner } from '@causa/runtime';
1
+ import { TransactionRunner, type ReadWriteTransactionOptions, type TransactionFn } from '@causa/runtime';
2
2
  import { Logger } from '@causa/runtime/nestjs';
3
3
  import { Firestore } from '@google-cloud/firestore';
4
4
  import { PubSubPublisher } from '../../pubsub/index.js';
5
- import { type FirestoreCollectionResolver } from './state-transaction.js';
5
+ import { FirestoreReadOnlyStateTransaction } from './readonly-state-transaction.js';
6
6
  import { FirestorePubSubTransaction } from './transaction.js';
7
+ import type { FirestoreCollectionResolver } from './types.js';
7
8
  /**
8
9
  * A {@link TransactionRunner} that uses Firestore for state and Pub/Sub for events.
9
10
  * A Firestore transaction is used as the main transaction. If it succeeds, events are published to Pub/Sub outside of
@@ -11,11 +12,12 @@ import { FirestorePubSubTransaction } from './transaction.js';
11
12
  * This runner and the transaction use the {@link FirestoreStateTransaction}, which handles soft-deleted documents. All
12
13
  * entities that are written to the state should be decorated with the `SoftDeletedFirestoreCollection` decorator.
13
14
  */
14
- export declare class FirestorePubSubTransactionRunner extends TransactionRunner<FirestorePubSubTransaction> {
15
+ export declare class FirestorePubSubTransactionRunner extends TransactionRunner<FirestorePubSubTransaction, FirestoreReadOnlyStateTransaction> {
15
16
  readonly firestore: Firestore;
16
17
  readonly pubSubPublisher: PubSubPublisher;
17
18
  readonly collectionResolver: FirestoreCollectionResolver;
18
19
  private readonly logger;
19
20
  constructor(firestore: Firestore, pubSubPublisher: PubSubPublisher, collectionResolver: FirestoreCollectionResolver, logger: Logger);
20
- run<T>(runFn: (transaction: FirestorePubSubTransaction) => Promise<T>): Promise<[T]>;
21
+ protected runReadWrite<RT>(options: ReadWriteTransactionOptions, runFn: TransactionFn<FirestorePubSubTransaction, RT>): Promise<RT>;
22
+ protected runReadOnly<RT>(runFn: TransactionFn<FirestoreReadOnlyStateTransaction, RT>): Promise<RT>;
21
23
  }
@@ -8,13 +8,14 @@ var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
10
  var FirestorePubSubTransactionRunner_1;
11
- import { BufferEventTransaction, TransactionRunner } from '@causa/runtime';
11
+ import { OutboxEventTransaction, TransactionRunner, } from '@causa/runtime';
12
12
  import { Logger } from '@causa/runtime/nestjs';
13
13
  import { Firestore } from '@google-cloud/firestore';
14
14
  import { Injectable } from '@nestjs/common';
15
15
  import { wrapFirestoreOperation } from '../../firestore/index.js';
16
16
  import { PubSubPublisher } from '../../pubsub/index.js';
17
- import { FirestoreStateTransaction, } from './state-transaction.js';
17
+ import { FirestoreReadOnlyStateTransaction } from './readonly-state-transaction.js';
18
+ import { FirestoreStateTransaction } from './state-transaction.js';
18
19
  import { FirestorePubSubTransaction } from './transaction.js';
19
20
  /**
20
21
  * A {@link TransactionRunner} that uses Firestore for state and Pub/Sub for events.
@@ -36,19 +37,27 @@ let FirestorePubSubTransactionRunner = FirestorePubSubTransactionRunner_1 = clas
36
37
  this.logger = logger;
37
38
  this.logger.setContext(FirestorePubSubTransactionRunner_1.name);
38
39
  }
39
- async run(runFn) {
40
+ async runReadWrite(options, runFn) {
40
41
  this.logger.info('Creating a Firestore Pub/Sub transaction.');
41
42
  const { result, eventTransaction } = await wrapFirestoreOperation(() => this.firestore.runTransaction(async (firestoreTransaction) => {
42
43
  const stateTransaction = new FirestoreStateTransaction(firestoreTransaction, this.collectionResolver);
43
- const eventTransaction = new BufferEventTransaction(this.pubSubPublisher);
44
+ const eventTransaction = new OutboxEventTransaction(this.pubSubPublisher, options.publishOptions);
44
45
  const transaction = new FirestorePubSubTransaction(stateTransaction, eventTransaction);
45
46
  const result = await runFn(transaction);
46
47
  this.logger.info('Committing the Firestore transaction.');
47
48
  return { result, eventTransaction };
48
49
  }));
49
- this.logger.info('Publishing Pub/Sub events.');
50
- await eventTransaction.commit();
51
- return [result];
50
+ if (eventTransaction.events.length > 0) {
51
+ this.logger.info('Publishing Pub/Sub events.');
52
+ await Promise.all(eventTransaction.events.map((e) => this.pubSubPublisher.publish(e)));
53
+ }
54
+ return result;
55
+ }
56
+ async runReadOnly(runFn) {
57
+ return await wrapFirestoreOperation(() => this.firestore.runTransaction(async (firestoreTransaction) => {
58
+ const transaction = new FirestoreReadOnlyStateTransaction(firestoreTransaction, this.collectionResolver);
59
+ return await runFn(transaction);
60
+ }, { readOnly: true }));
52
61
  }
53
62
  };
54
63
  FirestorePubSubTransactionRunner = FirestorePubSubTransactionRunner_1 = __decorate([
@@ -1,62 +1,24 @@
1
- import type { FindReplaceStateTransaction } from '@causa/runtime';
1
+ import type { StateTransaction } from '@causa/runtime';
2
2
  import type { Type } from '@nestjs/common';
3
- import { CollectionReference, Transaction } from 'firebase-admin/firestore';
4
- import type { SoftDeletedFirestoreCollectionMetadata } from './soft-deleted-collection.decorator.js';
3
+ import { FirestoreReadOnlyStateTransaction } from './readonly-state-transaction.js';
5
4
  /**
6
- * The Firestore collections that should be used to create document references for a given document type.
7
- */
8
- export type FirestoreCollectionsForDocumentType<T> = {
9
- /**
10
- * The regular collection, where documents are stored when they are not deleted.
11
- */
12
- readonly activeCollection: CollectionReference<T>;
13
- /**
14
- * Configuration about the soft-delete collection, where documents are stored when their `deletedAt` field is not
15
- * `null`. This can be `null` if the document type does not declare a soft-delete collection.
16
- */
17
- readonly softDelete: ({
18
- /**
19
- * The collection where soft-deleted documents are stored.
20
- */
21
- collection: CollectionReference<T>;
22
- } & Pick<SoftDeletedFirestoreCollectionMetadata, 'expirationDelay' | 'expirationField'>) | null;
23
- };
24
- /**
25
- * A resolver that returns the Firestore collections for a given document type.
26
- * This allows using the {@link FirestoreStateTransaction} in various contexts, such as in a NestJS module. It also
27
- * enables testing with temporary collections.
28
- */
29
- export interface FirestoreCollectionResolver {
30
- /**
31
- * Returns the Firestore collections for a given document type.
32
- *
33
- * @param documentType The type of document.
34
- * @returns The Firestore collections for the given document type.
35
- */
36
- getCollectionsForType<T>(documentType: Type<T>): FirestoreCollectionsForDocumentType<T>;
37
- }
38
- /**
39
- * A {@link FindReplaceStateTransaction} that uses Firestore for state storage.
5
+ * A {@link StateTransaction} that uses Firestore for state storage.
40
6
  *
41
7
  * This transaction handles soft-deleted documents if the class is decorated with `SoftDeletedFirestoreCollection`,
42
8
  * which means that documents with a `deletedAt` field set to a non-null value are considered deleted.
43
9
  * Soft-deleted documents are moved to a separate collection, where they are kept for a configurable amount of time
44
10
  * before being permanently deleted. See the `SoftDeletedFirestoreCollection` decorator for more information.
45
11
  *
46
- * {@link FirestoreStateTransaction.deleteWithSameKeyAs} will delete the document from any of the regular or soft-delete
47
- * collections. It does not throw an error if the document does not exist.
12
+ * {@link FirestoreStateTransaction.delete} will delete the document from any of the regular or soft-delete collections.
13
+ * It does not throw an error if the document does not exist.
48
14
  *
49
- * {@link FirestoreStateTransaction.findOneWithSameKeyAs} will return the document from either the regular or
50
- * soft-delete collection, as expected by {@link FindReplaceStateTransaction.findOneWithSameKeyAs}.
15
+ * {@link FirestoreStateTransaction.get} will return the document from either the regular or soft-delete collection, as
16
+ * expected by {@link StateTransaction.get}.
51
17
  *
52
- * {@link FirestoreStateTransaction.replace} will set the document in the relevant collection, either the regular or
18
+ * {@link FirestoreStateTransaction.set} will set the document in the relevant collection, either the regular or
53
19
  * soft-delete collection depending on the value of the `deletedAt` field.
54
20
  */
55
- export declare class FirestoreStateTransaction implements FindReplaceStateTransaction {
56
- readonly transaction: Transaction;
57
- readonly collectionResolver: FirestoreCollectionResolver;
58
- constructor(transaction: Transaction, collectionResolver: FirestoreCollectionResolver);
59
- deleteWithSameKeyAs<T extends object>(type: Type<T>, key: Partial<T>): Promise<void>;
60
- findOneWithSameKeyAs<T extends object>(type: Type<T>, entity: Partial<T>): Promise<T | undefined>;
61
- replace<T extends object>(entity: T): Promise<void>;
21
+ export declare class FirestoreStateTransaction extends FirestoreReadOnlyStateTransaction implements StateTransaction {
22
+ delete<T extends object>(typeOrEntity: Type<T> | T, key?: Partial<T>): Promise<void>;
23
+ set<T extends object>(entity: T): Promise<void>;
62
24
  }
@@ -1,65 +1,42 @@
1
1
  import { plainToInstance } from 'class-transformer';
2
- import { CollectionReference, Transaction } from 'firebase-admin/firestore';
3
2
  import { getReferenceForFirestoreDocument } from '../../firestore/index.js';
3
+ import { FirestoreReadOnlyStateTransaction } from './readonly-state-transaction.js';
4
4
  /**
5
- * A {@link FindReplaceStateTransaction} that uses Firestore for state storage.
5
+ * A {@link StateTransaction} that uses Firestore for state storage.
6
6
  *
7
7
  * This transaction handles soft-deleted documents if the class is decorated with `SoftDeletedFirestoreCollection`,
8
8
  * which means that documents with a `deletedAt` field set to a non-null value are considered deleted.
9
9
  * Soft-deleted documents are moved to a separate collection, where they are kept for a configurable amount of time
10
10
  * before being permanently deleted. See the `SoftDeletedFirestoreCollection` decorator for more information.
11
11
  *
12
- * {@link FirestoreStateTransaction.deleteWithSameKeyAs} will delete the document from any of the regular or soft-delete
13
- * collections. It does not throw an error if the document does not exist.
12
+ * {@link FirestoreStateTransaction.delete} will delete the document from any of the regular or soft-delete collections.
13
+ * It does not throw an error if the document does not exist.
14
14
  *
15
- * {@link FirestoreStateTransaction.findOneWithSameKeyAs} will return the document from either the regular or
16
- * soft-delete collection, as expected by {@link FindReplaceStateTransaction.findOneWithSameKeyAs}.
15
+ * {@link FirestoreStateTransaction.get} will return the document from either the regular or soft-delete collection, as
16
+ * expected by {@link StateTransaction.get}.
17
17
  *
18
- * {@link FirestoreStateTransaction.replace} will set the document in the relevant collection, either the regular or
18
+ * {@link FirestoreStateTransaction.set} will set the document in the relevant collection, either the regular or
19
19
  * soft-delete collection depending on the value of the `deletedAt` field.
20
20
  */
21
- export class FirestoreStateTransaction {
22
- transaction;
23
- collectionResolver;
24
- constructor(transaction, collectionResolver) {
25
- this.transaction = transaction;
26
- this.collectionResolver = collectionResolver;
27
- }
28
- async deleteWithSameKeyAs(type, key) {
21
+ export class FirestoreStateTransaction extends FirestoreReadOnlyStateTransaction {
22
+ async delete(typeOrEntity, key) {
23
+ const type = (key === undefined ? typeOrEntity.constructor : typeOrEntity);
24
+ key ??= typeOrEntity;
29
25
  const { activeCollection, softDelete } = this.collectionResolver.getCollectionsForType(type);
30
26
  const activeDocRef = getReferenceForFirestoreDocument(activeCollection, key, type);
31
- this.transaction.delete(activeDocRef);
27
+ this.firestoreTransaction.delete(activeDocRef);
32
28
  if (!softDelete) {
33
29
  return;
34
30
  }
35
31
  const deletedDocRef = getReferenceForFirestoreDocument(softDelete.collection, key, type);
36
- this.transaction.delete(deletedDocRef);
37
- }
38
- async findOneWithSameKeyAs(type, entity) {
39
- const { activeCollection, softDelete } = this.collectionResolver.getCollectionsForType(type);
40
- const activeDocRef = getReferenceForFirestoreDocument(activeCollection, entity, type);
41
- const activeSnapshot = await this.transaction.get(activeDocRef);
42
- if (activeSnapshot.exists) {
43
- return activeSnapshot.data();
44
- }
45
- if (!softDelete) {
46
- return undefined;
47
- }
48
- const deletedDocRef = getReferenceForFirestoreDocument(softDelete.collection, entity, type);
49
- const deletedSnapshot = await this.transaction.get(deletedDocRef);
50
- if (!deletedSnapshot.exists) {
51
- return undefined;
52
- }
53
- const deletedDocument = deletedSnapshot.data();
54
- delete deletedDocument[softDelete.expirationField];
55
- return deletedDocument;
32
+ this.firestoreTransaction.delete(deletedDocRef);
56
33
  }
57
- async replace(entity) {
34
+ async set(entity) {
58
35
  const documentType = entity.constructor;
59
36
  const { activeCollection, softDelete } = this.collectionResolver.getCollectionsForType(documentType);
60
37
  const activeDocRef = getReferenceForFirestoreDocument(activeCollection, entity);
61
38
  if (!softDelete) {
62
- this.transaction.set(activeDocRef, entity);
39
+ this.firestoreTransaction.set(activeDocRef, entity);
63
40
  return;
64
41
  }
65
42
  const deletedDocRef = getReferenceForFirestoreDocument(softDelete.collection, entity);
@@ -70,12 +47,12 @@ export class FirestoreStateTransaction {
70
47
  ...entity,
71
48
  [expirationField]: expiresAt,
72
49
  });
73
- this.transaction.delete(activeDocRef);
74
- this.transaction.set(deletedDocRef, deletedDoc);
50
+ this.firestoreTransaction.delete(activeDocRef);
51
+ this.firestoreTransaction.set(deletedDocRef, deletedDoc);
75
52
  }
76
53
  else {
77
- this.transaction.set(activeDocRef, entity);
78
- this.transaction.delete(deletedDocRef);
54
+ this.firestoreTransaction.set(activeDocRef, entity);
55
+ this.firestoreTransaction.delete(deletedDocRef);
79
56
  }
80
57
  }
81
58
  }
@@ -1,12 +1,24 @@
1
- import { BufferEventTransaction, Transaction } from '@causa/runtime';
1
+ import { OutboxEventTransaction, Transaction, type PublishOptions, type TransactionOption } from '@causa/runtime';
2
+ import type { Type } from '@nestjs/common';
2
3
  import { Transaction as FirestoreTransaction } from 'firebase-admin/firestore';
3
- import { FirestoreStateTransaction } from './state-transaction.js';
4
+ import type { FirestoreStateTransaction } from './state-transaction.js';
5
+ /**
6
+ * Option for a function that accepts a {@link FirestorePubSubTransaction}.
7
+ */
8
+ export type FirestoreOutboxTransactionOption = TransactionOption<FirestorePubSubTransaction>;
4
9
  /**
5
10
  * A {@link Transaction} that uses Firestore for state storage and Pub/Sub for event publishing.
6
11
  */
7
- export declare class FirestorePubSubTransaction extends Transaction<FirestoreStateTransaction, BufferEventTransaction> {
12
+ export declare class FirestorePubSubTransaction extends Transaction {
13
+ readonly stateTransaction: FirestoreStateTransaction;
14
+ private readonly eventTransaction;
15
+ constructor(stateTransaction: FirestoreStateTransaction, eventTransaction: OutboxEventTransaction, publishOptions?: PublishOptions);
8
16
  /**
9
17
  * The underlying {@link FirestoreTransaction} used by the state transaction.
10
18
  */
11
19
  get firestoreTransaction(): FirestoreTransaction;
20
+ set<T extends object>(entity: T): Promise<void>;
21
+ delete<T extends object>(type: Type<T> | T, key?: Partial<T>): Promise<void>;
22
+ get<T extends object>(type: Type<T>, entity: Partial<T>): Promise<T | undefined>;
23
+ publish(topic: string, event: object, options?: PublishOptions): Promise<void>;
12
24
  }
@@ -1,14 +1,32 @@
1
- import { BufferEventTransaction, Transaction } from '@causa/runtime';
1
+ import { OutboxEventTransaction, Transaction, } from '@causa/runtime';
2
2
  import { Transaction as FirestoreTransaction } from 'firebase-admin/firestore';
3
- import { FirestoreStateTransaction } from './state-transaction.js';
4
3
  /**
5
4
  * A {@link Transaction} that uses Firestore for state storage and Pub/Sub for event publishing.
6
5
  */
7
6
  export class FirestorePubSubTransaction extends Transaction {
7
+ stateTransaction;
8
+ eventTransaction;
9
+ constructor(stateTransaction, eventTransaction, publishOptions = {}) {
10
+ super(publishOptions);
11
+ this.stateTransaction = stateTransaction;
12
+ this.eventTransaction = eventTransaction;
13
+ }
8
14
  /**
9
15
  * The underlying {@link FirestoreTransaction} used by the state transaction.
10
16
  */
11
17
  get firestoreTransaction() {
12
- return this.stateTransaction.transaction;
18
+ return this.stateTransaction.firestoreTransaction;
19
+ }
20
+ set(entity) {
21
+ return this.stateTransaction.set(entity);
22
+ }
23
+ delete(type, key) {
24
+ return this.stateTransaction.delete(type, key);
25
+ }
26
+ get(type, entity) {
27
+ return this.stateTransaction.get(type, entity);
28
+ }
29
+ publish(topic, event, options) {
30
+ return this.eventTransaction.publish(topic, event, options);
13
31
  }
14
32
  }
@@ -0,0 +1,36 @@
1
+ import type { Type } from '@nestjs/common';
2
+ import type { CollectionReference } from 'firebase-admin/firestore';
3
+ import type { SoftDeletedFirestoreCollectionMetadata } from './soft-deleted-collection.decorator.js';
4
+ /**
5
+ * The Firestore collections that should be used to create document references for a given document type.
6
+ */
7
+ export type FirestoreCollectionsForDocumentType<T> = {
8
+ /**
9
+ * The regular collection, where documents are stored when they are not deleted.
10
+ */
11
+ readonly activeCollection: CollectionReference<T>;
12
+ /**
13
+ * Configuration about the soft-delete collection, where documents are stored when their `deletedAt` field is not
14
+ * `null`. This can be `null` if the document type does not declare a soft-delete collection.
15
+ */
16
+ readonly softDelete: ({
17
+ /**
18
+ * The collection where soft-deleted documents are stored.
19
+ */
20
+ collection: CollectionReference<T>;
21
+ } & Pick<SoftDeletedFirestoreCollectionMetadata, 'expirationDelay' | 'expirationField'>) | null;
22
+ };
23
+ /**
24
+ * A resolver that returns the Firestore collections for a given document type.
25
+ * This allows using the {@link FirestoreStateTransaction} in various contexts, such as in a NestJS module. It also
26
+ * enables testing with temporary collections.
27
+ */
28
+ export interface FirestoreCollectionResolver {
29
+ /**
30
+ * Returns the Firestore collections for a given document type.
31
+ *
32
+ * @param documentType The type of document.
33
+ * @returns The Firestore collections for the given document type.
34
+ */
35
+ getCollectionsForType<T>(documentType: Type<T>): FirestoreCollectionsForDocumentType<T>;
36
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,2 @@
1
1
  export * from './firestore-pubsub/index.js';
2
2
  export * from './spanner-outbox/index.js';
3
- export * from './spanner-pubsub/index.js';
4
- export { SpannerStateTransaction } from './spanner-state-transaction.js';
5
- export { SpannerTransaction } from './spanner-transaction.js';
@@ -1,5 +1,2 @@
1
1
  export * from './firestore-pubsub/index.js';
2
2
  export * from './spanner-outbox/index.js';
3
- export * from './spanner-pubsub/index.js';
4
- export { SpannerStateTransaction } from './spanner-state-transaction.js';
5
- export { SpannerTransaction } from './spanner-transaction.js';
@@ -1,5 +1,7 @@
1
1
  export { SpannerOutboxEvent } from './event.js';
2
2
  export { SpannerOutboxTransactionModule } from './module.js';
3
+ export * from './readonly-transaction.js';
3
4
  export { SpannerOutboxTransactionRunner } from './runner.js';
4
- export type { SpannerOutboxTransaction, SpannerOutboxTransactionOption, } from './runner.js';
5
5
  export { SpannerOutboxSender } from './sender.js';
6
+ export { SpannerStateTransaction } from './state-transaction.js';
7
+ export * from './transaction.js';
@@ -1,4 +1,7 @@
1
1
  export { SpannerOutboxEvent } from './event.js';
2
2
  export { SpannerOutboxTransactionModule } from './module.js';
3
+ export * from './readonly-transaction.js';
3
4
  export { SpannerOutboxTransactionRunner } from './runner.js';
4
5
  export { SpannerOutboxSender } from './sender.js';
6
+ export { SpannerStateTransaction } from './state-transaction.js';
7
+ export * from './transaction.js';
@@ -0,0 +1,22 @@
1
+ import type { ReadOnlyStateTransaction, ReadOnlyTransactionOption } from '@causa/runtime';
2
+ import type { Type } from '@nestjs/common';
3
+ import { SpannerEntityManager, type SpannerReadOnlyTransaction, type SpannerReadWriteTransaction } from '../../spanner/index.js';
4
+ /**
5
+ * Option for a function that accepts a {@link SpannerReadOnlyStateTransaction}.
6
+ */
7
+ export type SpannerReadOnlyStateTransactionOption = ReadOnlyTransactionOption<SpannerReadOnlyStateTransaction>;
8
+ /**
9
+ * A {@link ReadOnlyStateTransaction} that uses Spanner for state storage.
10
+ */
11
+ export declare class SpannerReadOnlyStateTransaction implements ReadOnlyStateTransaction {
12
+ readonly entityManager: SpannerEntityManager;
13
+ readonly spannerTransaction: SpannerReadWriteTransaction | SpannerReadOnlyTransaction;
14
+ /**
15
+ * Creates a new {@link SpannerReadOnlyStateTransaction}.
16
+ *
17
+ * @param entityManager The {@link SpannerEntityManager} to use to access entities in the state.
18
+ * @param spannerTransaction The {@link SpannerReadWriteTransaction} or {@link SpannerReadOnlyTransaction} to use.
19
+ */
20
+ constructor(entityManager: SpannerEntityManager, spannerTransaction: SpannerReadWriteTransaction | SpannerReadOnlyTransaction);
21
+ get<T extends object>(type: Type<T>, entity: Partial<T>): Promise<T | undefined>;
22
+ }
@@ -0,0 +1,25 @@
1
+ import { SpannerEntityManager, } from '../../spanner/index.js';
2
+ /**
3
+ * A {@link ReadOnlyStateTransaction} that uses Spanner for state storage.
4
+ */
5
+ export class SpannerReadOnlyStateTransaction {
6
+ entityManager;
7
+ spannerTransaction;
8
+ /**
9
+ * Creates a new {@link SpannerReadOnlyStateTransaction}.
10
+ *
11
+ * @param entityManager The {@link SpannerEntityManager} to use to access entities in the state.
12
+ * @param spannerTransaction The {@link SpannerReadWriteTransaction} or {@link SpannerReadOnlyTransaction} to use.
13
+ */
14
+ constructor(entityManager, spannerTransaction) {
15
+ this.entityManager = entityManager;
16
+ this.spannerTransaction = spannerTransaction;
17
+ }
18
+ async get(type, entity) {
19
+ const primaryKey = this.entityManager.getPrimaryKey(entity, type);
20
+ return await this.entityManager.findOneByKey(type, primaryKey, {
21
+ transaction: this.spannerTransaction,
22
+ includeSoftDeletes: true,
23
+ });
24
+ }
25
+ }
@@ -1,28 +1,17 @@
1
- import { OutboxTransactionRunner, type OutboxEvent, type OutboxEventTransaction } from '@causa/runtime';
1
+ import { OutboxTransactionRunner, type OutboxEvent, type OutboxEventTransaction, type ReadWriteTransactionOptions, type TransactionFn } 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
- import { SpannerTransaction } from '../spanner-transaction.js';
5
+ import { SpannerReadOnlyStateTransaction } from './readonly-transaction.js';
6
6
  import { SpannerOutboxSender } from './sender.js';
7
+ import { SpannerOutboxTransaction } from './transaction.js';
7
8
  /**
8
- * A {@link SpannerTransaction} that uses an {@link OutboxEventTransaction}.
9
- */
10
- export type SpannerOutboxTransaction = SpannerTransaction<OutboxEventTransaction>;
11
- /**
12
- * Option for a function that accepts a {@link SpannerOutboxTransaction}.
13
- */
14
- export type SpannerOutboxTransactionOption = {
15
- /**
16
- * The transaction to use.
17
- */
18
- readonly transaction?: SpannerOutboxTransaction;
19
- };
20
- /**
21
- * An {@link OutboxTransactionRunner} that uses a {@link SpannerTransaction} to run transactions.
9
+ * An {@link OutboxTransactionRunner} that uses a {@link SpannerOutboxTransaction} to run transactions.
22
10
  * Events are stored in a Spanner table before being published.
23
11
  */
24
- export declare class SpannerOutboxTransactionRunner extends OutboxTransactionRunner<SpannerOutboxTransaction> {
12
+ export declare class SpannerOutboxTransactionRunner extends OutboxTransactionRunner<SpannerOutboxTransaction, SpannerReadOnlyStateTransaction> {
25
13
  readonly entityManager: SpannerEntityManager;
26
14
  constructor(entityManager: SpannerEntityManager, outboxEventType: Type<OutboxEvent>, sender: SpannerOutboxSender, logger: Logger);
27
- protected runStateTransaction<RT>(eventTransactionFactory: () => OutboxEventTransaction, runFn: (transaction: SpannerOutboxTransaction) => Promise<RT>): Promise<RT>;
15
+ protected runReadOnly<RT>(runFn: TransactionFn<SpannerReadOnlyStateTransaction, RT>): Promise<RT>;
16
+ protected runStateTransaction<RT>(eventTransactionFactory: () => OutboxEventTransaction, options: ReadWriteTransactionOptions, runFn: TransactionFn<SpannerOutboxTransaction, RT>): Promise<RT>;
28
17
  }
@@ -1,12 +1,13 @@
1
1
  import { OutboxTransactionRunner, } from '@causa/runtime';
2
2
  import { Logger } from '@causa/runtime/nestjs';
3
3
  import { SpannerEntityManager } from '../../spanner/index.js';
4
- import { SpannerStateTransaction } from '../spanner-state-transaction.js';
5
- import { SpannerTransaction } from '../spanner-transaction.js';
6
- import { throwRetryableInTransactionIfNeeded } from '../spanner-utils.js';
4
+ import { SpannerReadOnlyStateTransaction } from './readonly-transaction.js';
7
5
  import { SpannerOutboxSender } from './sender.js';
6
+ import { throwRetryableInTransactionIfNeeded } from './spanner-utils.js';
7
+ import { SpannerStateTransaction } from './state-transaction.js';
8
+ import { SpannerOutboxTransaction } from './transaction.js';
8
9
  /**
9
- * An {@link OutboxTransactionRunner} that uses a {@link SpannerTransaction} to run transactions.
10
+ * An {@link OutboxTransactionRunner} that uses a {@link SpannerOutboxTransaction} to run transactions.
10
11
  * Events are stored in a Spanner table before being published.
11
12
  */
12
13
  export class SpannerOutboxTransactionRunner extends OutboxTransactionRunner {
@@ -15,11 +16,17 @@ export class SpannerOutboxTransactionRunner extends OutboxTransactionRunner {
15
16
  super(outboxEventType, sender, logger);
16
17
  this.entityManager = entityManager;
17
18
  }
18
- async runStateTransaction(eventTransactionFactory, runFn) {
19
+ async runReadOnly(runFn) {
20
+ return await this.entityManager.snapshot(async (dbTransaction) => {
21
+ const transaction = new SpannerReadOnlyStateTransaction(this.entityManager, dbTransaction);
22
+ return await runFn(transaction);
23
+ });
24
+ }
25
+ async runStateTransaction(eventTransactionFactory, options, runFn) {
19
26
  return await this.entityManager.transaction(async (dbTransaction) => {
20
27
  const stateTransaction = new SpannerStateTransaction(this.entityManager, dbTransaction);
21
28
  const eventTransaction = eventTransactionFactory();
22
- const transaction = new SpannerTransaction(stateTransaction, eventTransaction);
29
+ const transaction = new SpannerOutboxTransaction(stateTransaction, eventTransaction, options.publishOptions);
23
30
  try {
24
31
  return await runFn(transaction);
25
32
  }
@@ -1,6 +1,6 @@
1
1
  import { TransactionOldTimestampError } from '@causa/runtime';
2
2
  import { setTimeout } from 'timers/promises';
3
- import { TemporarySpannerError } from '../spanner/index.js';
3
+ import { TemporarySpannerError } from '../../spanner/index.js';
4
4
  /**
5
5
  * The delay, in milliseconds, over which a timestamp issue is deemed irrecoverable.
6
6
  */
@@ -0,0 +1,20 @@
1
+ import type { StateTransaction } from '@causa/runtime';
2
+ import type { Type } from '@nestjs/common';
3
+ import { SpannerEntityManager, type SpannerReadWriteTransaction } from '../../spanner/index.js';
4
+ import { SpannerReadOnlyStateTransaction } from './readonly-transaction.js';
5
+ /**
6
+ * A {@link StateTransaction} that uses Spanner for state storage.
7
+ */
8
+ export declare class SpannerStateTransaction extends SpannerReadOnlyStateTransaction implements StateTransaction {
9
+ readonly entityManager: SpannerEntityManager;
10
+ readonly spannerTransaction: SpannerReadWriteTransaction;
11
+ /**
12
+ * Creates a new {@link SpannerStateTransaction}.
13
+ *
14
+ * @param entityManager The {@link SpannerEntityManager} to use to access entities in the state.
15
+ * @param spannerTransaction The {@link SpannerReadWriteTransaction} to use for the transaction.
16
+ */
17
+ constructor(entityManager: SpannerEntityManager, spannerTransaction: SpannerReadWriteTransaction);
18
+ set<T extends object>(entity: T): Promise<void>;
19
+ delete<T extends object>(typeOrEntity: Type<T> | T, key?: Partial<T>): Promise<void>;
20
+ }
@@ -0,0 +1,34 @@
1
+ import { SpannerEntityManager, } from '../../spanner/index.js';
2
+ import { SpannerReadOnlyStateTransaction } from './readonly-transaction.js';
3
+ /**
4
+ * A {@link StateTransaction} that uses Spanner for state storage.
5
+ */
6
+ export class SpannerStateTransaction extends SpannerReadOnlyStateTransaction {
7
+ entityManager;
8
+ spannerTransaction;
9
+ /**
10
+ * Creates a new {@link SpannerStateTransaction}.
11
+ *
12
+ * @param entityManager The {@link SpannerEntityManager} to use to access entities in the state.
13
+ * @param spannerTransaction The {@link SpannerReadWriteTransaction} to use for the transaction.
14
+ */
15
+ constructor(entityManager, spannerTransaction) {
16
+ super(entityManager, spannerTransaction);
17
+ this.entityManager = entityManager;
18
+ this.spannerTransaction = spannerTransaction;
19
+ }
20
+ async set(entity) {
21
+ await this.entityManager.replace(entity, {
22
+ transaction: this.spannerTransaction,
23
+ });
24
+ }
25
+ async delete(typeOrEntity, key) {
26
+ const type = (key === undefined ? typeOrEntity.constructor : typeOrEntity);
27
+ key ??= typeOrEntity;
28
+ const primaryKey = this.entityManager.getPrimaryKey(key, type);
29
+ await this.entityManager.delete(type, primaryKey, {
30
+ transaction: this.spannerTransaction,
31
+ includeSoftDeletes: true,
32
+ });
33
+ }
34
+ }
@@ -0,0 +1,28 @@
1
+ import { OutboxEventTransaction, Transaction, type OutboxTransaction, type PublishOptions, type TransactionOption } from '@causa/runtime';
2
+ import type { Type } from '@nestjs/common';
3
+ import { SpannerEntityManager, type SpannerReadWriteTransaction } from '../../spanner/index.js';
4
+ import type { SpannerStateTransaction } from './state-transaction.js';
5
+ /**
6
+ * Option for a function that accepts a {@link SpannerOutboxTransaction}.
7
+ */
8
+ export type SpannerOutboxTransactionOption = TransactionOption<SpannerOutboxTransaction>;
9
+ /**
10
+ * A {@link Transaction} that uses Spanner for state (and outbox) storage, and Pub/Sub for event publishing.
11
+ */
12
+ export declare class SpannerOutboxTransaction extends Transaction implements OutboxTransaction {
13
+ readonly stateTransaction: SpannerStateTransaction;
14
+ readonly eventTransaction: OutboxEventTransaction;
15
+ constructor(stateTransaction: SpannerStateTransaction, eventTransaction: OutboxEventTransaction, publishOptions?: PublishOptions);
16
+ /**
17
+ * The underlying {@link SpannerTransaction} used by the state transaction.
18
+ */
19
+ get spannerTransaction(): SpannerReadWriteTransaction;
20
+ /**
21
+ * The underlying {@link SpannerEntityManager} used by the state transaction.
22
+ */
23
+ get entityManager(): SpannerEntityManager;
24
+ set<T extends object>(entity: T): Promise<void>;
25
+ delete<T extends object>(type: Type<T> | T, key?: Partial<T>): Promise<void>;
26
+ get<T extends object>(type: Type<T>, entity: Partial<T>): Promise<T | undefined>;
27
+ publish(topic: string, event: object, options?: PublishOptions): Promise<void>;
28
+ }