@causa/workspace-google 0.6.0 → 0.7.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 CHANGED
@@ -65,6 +65,32 @@ secrets:
65
65
  backend: google.accessToken
66
66
  ```
67
67
 
68
+ ### Code generation
69
+
70
+ This module implements the `google.spanner` TypeScript decorator renderer, which can be used to add `@SpannerTable` and `@SpannerColumn` decorators to classes generated from events. Below is an example of how to enable it for a JSONSchema object:
71
+
72
+ ```yaml
73
+ title: MyClass
74
+ type: object
75
+ additionalProperties: false
76
+ causa:
77
+ # This must be set for the decorators to be added to both the class and its properties.
78
+ # The content of the object will be passed as the argument to the `@SpannerTable` decorator.
79
+ tsGoogleSpannerTable:
80
+ primaryKey: [id]
81
+ properties:
82
+ id:
83
+ type: string
84
+ format: uuid
85
+ # In most cases, the property-level `tsGoogleSpannerColumn` attribute does not need to be set. The decorator configuration will be automatically inferred.
86
+ # If needed, the content of `tsGoogleSpannerColumn` will be passed as the argument to the `@SpannerColumn` decorator.
87
+ # causa:
88
+ # tsGoogleSpannerColumn:
89
+ # isJson: false
90
+ myProperty:
91
+ type: string
92
+ ```
93
+
68
94
  ## 🔨 Custom `google` commands
69
95
 
70
96
  This modules adds a new command to the CLI: `cs google`. Here is the list of subcommands that are exposed.
@@ -136,3 +162,5 @@ Although infrastructure as code tools usually expose this feature as well (e.g.
136
162
  ### `GooglePubSubWriteTopics`
137
163
 
138
164
  [GooglePubSubWriteTopics](./src/functions/google-pubsub-write-topics.ts) writes a configuration file for each event topic, such that it can be picked up by the Causa Pub/Sub Terraform module. This allows automatic setup of Pub/Sub topics, and optionally of the corresponding BigQuery tables.
165
+
166
+ Documentation of Firebase account + Firebase API key.
@@ -0,0 +1,11 @@
1
+ import { ClassContext, ClassPropertyContext, TypeScriptDecorator, TypeScriptDecoratorsRenderer } from '@causa/workspace-typescript';
2
+ /**
3
+ * A {@link TypeScriptDecoratorsRenderer} that adds Google Spanner decorators from the Causa Google runtime.
4
+ *
5
+ * If an object schema is marked with the `tsGoogleSpannerTable` attribute, the `@SpannerTable` decorator is added to
6
+ * the class, and `@SpannerColumn` decorators are added to all its properties.
7
+ */
8
+ export declare class GoogleSpannerRenderer extends TypeScriptDecoratorsRenderer {
9
+ decoratorsForClass(context: ClassContext): TypeScriptDecorator[];
10
+ decoratorsForProperty(context: ClassPropertyContext): TypeScriptDecorator[];
11
+ }
@@ -0,0 +1,94 @@
1
+ import { TypeScriptDecoratorsRenderer, getSingleType, typeScriptSourceForObject, } from '@causa/workspace-typescript';
2
+ import { panic } from 'quicktype-core';
3
+ /**
4
+ * The name of the Causa attribute that should be present for a class to be decorated with Google Spanner decorators.
5
+ */
6
+ const GOOGLE_SPANNER_TABLE_ATTRIBUTE = 'tsGoogleSpannerTable';
7
+ /**
8
+ * The name of the Causa attribute that can be present on a class to only generate `@SpannerColumn` decorators for its
9
+ * properties, but not a `@SpannerTable` decorator for the class itself.
10
+ */
11
+ const GOOGLE_SPANNER_NESTED_TYPE_ATTRIBUTE = 'tsGoogleSpannerNestedType';
12
+ /**
13
+ * The name of the optional Causa attribute that can be present on an object property schema to specify options for the
14
+ * `@SpannerColumn` decorator.
15
+ */
16
+ const GOOGLE_SPANNER_COLUMN_ATTRIBUTE = 'tsGoogleSpannerColumn';
17
+ /**
18
+ * The name of the `decoratorOptions` key that can be used to specify the name of the property that should be used as
19
+ * the soft deletion column.
20
+ */
21
+ const GOOGLE_SPANNER_SOFT_DELETION_COLUMN_OPTION = 'googleSpannerSoftDeletionColumn';
22
+ /**
23
+ * The name of the Causa module for the TypeScript Google runtime.
24
+ */
25
+ const CAUSA_GOOGLE_MODULE = '@causa/runtime-google';
26
+ /**
27
+ * The list of options that can be passed to the `@SpannerColumn` decorator and that indicates that the JSONSchema
28
+ * specifies the type of the column. In this case, the renderer should not infer any type information.
29
+ */
30
+ const TYPE_INFO_COLUMN_ATTRIBUTE_NAMES = [
31
+ 'nestedType',
32
+ 'nullifyNested',
33
+ 'isBigInt',
34
+ 'isInt',
35
+ 'isPreciseDate',
36
+ 'isJson',
37
+ ];
38
+ /**
39
+ * A {@link TypeScriptDecoratorsRenderer} that adds Google Spanner decorators from the Causa Google runtime.
40
+ *
41
+ * If an object schema is marked with the `tsGoogleSpannerTable` attribute, the `@SpannerTable` decorator is added to
42
+ * the class, and `@SpannerColumn` decorators are added to all its properties.
43
+ */
44
+ export class GoogleSpannerRenderer extends TypeScriptDecoratorsRenderer {
45
+ decoratorsForClass(context) {
46
+ const tableAttribute = context.objectAttributes[GOOGLE_SPANNER_TABLE_ATTRIBUTE];
47
+ if (!tableAttribute) {
48
+ return [];
49
+ }
50
+ if (typeof tableAttribute !== 'object' ||
51
+ !('primaryKey' in tableAttribute) ||
52
+ !Array.isArray(tableAttribute.primaryKey)) {
53
+ panic(`Invalid ${GOOGLE_SPANNER_TABLE_ATTRIBUTE} attribute on ${context.classType.getNames()}`);
54
+ }
55
+ const optionsSource = typeScriptSourceForObject(tableAttribute);
56
+ const decorators = [];
57
+ this.addDecoratorToList(decorators, context, 'SpannerTable', CAUSA_GOOGLE_MODULE, ['@SpannerTable(', optionsSource, ')']);
58
+ return decorators;
59
+ }
60
+ decoratorsForProperty(context) {
61
+ if (!context.objectAttributes[GOOGLE_SPANNER_TABLE_ATTRIBUTE] &&
62
+ !context.objectAttributes[GOOGLE_SPANNER_NESTED_TYPE_ATTRIBUTE]) {
63
+ return [];
64
+ }
65
+ const rawColumnAttributes = context.propertyAttributes[GOOGLE_SPANNER_COLUMN_ATTRIBUTE] ?? {};
66
+ const columnAttributes = typeof rawColumnAttributes === 'object' ? rawColumnAttributes : {};
67
+ const softDeletionColumn = this.decoratorOptions[GOOGLE_SPANNER_SOFT_DELETION_COLUMN_OPTION];
68
+ if (softDeletionColumn && context.jsonName === softDeletionColumn) {
69
+ columnAttributes.softDelete = true;
70
+ }
71
+ if (!context.propertyAttributes.tsType) {
72
+ const singleTypeInfo = getSingleType(context.property.type);
73
+ const schemaOverridesTypeInfo = TYPE_INFO_COLUMN_ATTRIBUTE_NAMES.some((name) => name in columnAttributes);
74
+ if (!schemaOverridesTypeInfo && singleTypeInfo) {
75
+ switch (singleTypeInfo.type.kind) {
76
+ case 'class':
77
+ case 'object':
78
+ case 'map':
79
+ columnAttributes.isJson = true;
80
+ break;
81
+ case 'integer':
82
+ columnAttributes.isInt = true;
83
+ break;
84
+ }
85
+ }
86
+ }
87
+ const optionsSource = typeScriptSourceForObject(columnAttributes, {
88
+ encoder: (key, value) => key === 'nestedType' ? value : JSON.stringify(value),
89
+ });
90
+ const decorators = [];
91
+ this.addDecoratorToList(decorators, context, 'SpannerColumn', CAUSA_GOOGLE_MODULE, ['@SpannerColumn(', optionsSource, ')']);
92
+ return decorators;
93
+ }
94
+ }
@@ -0,0 +1 @@
1
+ export { GoogleSpannerRenderer } from './google-spanner-renderer.js';
@@ -0,0 +1 @@
1
+ export { GoogleSpannerRenderer } from './google-spanner-renderer.js';
@@ -1,6 +1,9 @@
1
1
  import { DockerEmulatorService, EmulatorStart, } from '@causa/workspace-core';
2
2
  import { Spanner } from '@google-cloud/spanner';
3
- import { credentials } from '@grpc/grpc-js';
3
+ // The Spanner client depends on `google-gax` rather than `@grpc/grpc-js` directly. By ensuring `google-gax` is kept in
4
+ // sync with the version required by Spanner, and importing `grpc` from `google-gax`, the correct `grpc.credentials`
5
+ // version can be loaded.
6
+ import { grpc } from 'google-gax';
4
7
  import { getLocalGcpProject, } from '../../configurations/index.js';
5
8
  import { SPANNER_EMULATOR_NAME, SPANNER_GRPC_PORT, SPANNER_HTTP_PORT, SPANNER_IMAGE, getSpannerContainerName, } from '../../emulators/index.js';
6
9
  import { GoogleSpannerListDatabases } from '../google-spanner/index.js';
@@ -70,7 +73,7 @@ export class EmulatorStartForSpanner extends EmulatorStart {
70
73
  servicePath: '127.0.0.1',
71
74
  port: SPANNER_GRPC_PORT,
72
75
  projectId: getLocalGcpProject(context),
73
- sslCreds: credentials.createInsecure(),
76
+ sslCreds: grpc.credentials.createInsecure(),
74
77
  });
75
78
  const { instance, instanceConf } = await this.createInstance(spanner, context);
76
79
  const databaseConf = await this.createDatabases(instance, context);
@@ -1,5 +1,5 @@
1
1
  import { EventTopicBrokerDeleteTopic, } from '@causa/workspace-core';
2
- import { status } from '@grpc/grpc-js';
2
+ import { grpc } from 'google-gax';
3
3
  import { PubSubService } from '../../services/index.js';
4
4
  /**
5
5
  * Implements {@link EventTopicBrokerDeleteTopic} for Pub/Sub.
@@ -12,7 +12,7 @@ export class EventTopicBrokerDeleteTopicForPubSub extends EventTopicBrokerDelete
12
12
  await context.service(PubSubService).pubSub.topic(this.id).delete();
13
13
  }
14
14
  catch (error) {
15
- if (error.code === status.NOT_FOUND) {
15
+ if (error.code === grpc.status.NOT_FOUND) {
16
16
  context.logger.warn(`⚠️ Pub/Sub topic to delete '${this.id}' does not exist. It might have already been deleted.`);
17
17
  return;
18
18
  }
@@ -1,5 +1,5 @@
1
1
  import { EventTopicBrokerDeleteTriggerResource, } from '@causa/workspace-core';
2
- import { status } from '@grpc/grpc-js';
2
+ import { grpc } from 'google-gax';
3
3
  import { PubSubService } from '../../services/index.js';
4
4
  /**
5
5
  * Implements {@link EventTopicBrokerDeleteTriggerResource} for Pub/Sub subscriptions.
@@ -15,7 +15,7 @@ export class EventTopicBrokerDeleteTriggerResourceForPubSubSubscription extends
15
15
  .delete();
16
16
  }
17
17
  catch (error) {
18
- if (error.code === status.NOT_FOUND) {
18
+ if (error.code === grpc.status.NOT_FOUND) {
19
19
  context.logger.warn(`⚠️ Pub/Sub subscription '${this.id}' does not exist. It might have already been deleted.`);
20
20
  }
21
21
  else {
@@ -9,6 +9,7 @@ import { GoogleServicesEnable } from './google-services/index.js';
9
9
  import { GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, } from './google-spanner/index.js';
10
10
  import { ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, } from './project/index.js';
11
11
  import { SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, } from './secret/index.js';
12
+ import { TypeScriptGetDecoratorRendererForGoogleSpanner } from './typescript/index.js';
12
13
  export function registerFunctions(context) {
13
- context.registerFunctionImplementations(EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GooglePubSubWriteTopics, GoogleServicesEnable, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager);
14
+ context.registerFunctionImplementations(EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GooglePubSubWriteTopics, GoogleServicesEnable, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, TypeScriptGetDecoratorRendererForGoogleSpanner);
14
15
  }
@@ -0,0 +1,10 @@
1
+ import { WorkspaceContext } from '@causa/workspace';
2
+ import { TypeScriptDecoratorsRenderer, TypeScriptGetDecoratorRenderer } from '@causa/workspace-typescript';
3
+ /**
4
+ * Implements {@link TypeScriptGetDecoratorRenderer} for the {@link GoogleSpannerRenderer}.
5
+ * The configuration name for the renderer is `google.spanner`.
6
+ */
7
+ export declare class TypeScriptGetDecoratorRendererForGoogleSpanner extends TypeScriptGetDecoratorRenderer {
8
+ _call(): new (...args: any[]) => TypeScriptDecoratorsRenderer;
9
+ _supports(context: WorkspaceContext): boolean;
10
+ }
@@ -0,0 +1,21 @@
1
+ import { TypeScriptGetDecoratorRenderer, } from '@causa/workspace-typescript';
2
+ import { GoogleSpannerRenderer } from '../../code-generation/index.js';
3
+ /**
4
+ * Implements {@link TypeScriptGetDecoratorRenderer} for the {@link GoogleSpannerRenderer}.
5
+ * The configuration name for the renderer is `google.spanner`.
6
+ */
7
+ export class TypeScriptGetDecoratorRendererForGoogleSpanner extends TypeScriptGetDecoratorRenderer {
8
+ _call() {
9
+ return GoogleSpannerRenderer;
10
+ }
11
+ _supports(context) {
12
+ if (context.get('project.language') !== 'typescript') {
13
+ return false;
14
+ }
15
+ const decoratorRenderers = context
16
+ .asConfiguration()
17
+ .get('typescript.codeGeneration.decoratorRenderers') ?? [];
18
+ return (decoratorRenderers.length === 0 ||
19
+ decoratorRenderers.includes('google.spanner'));
20
+ }
21
+ }
@@ -0,0 +1 @@
1
+ export { TypeScriptGetDecoratorRendererForGoogleSpanner } from './get-decorator-renderer-google-spanner.js';
@@ -0,0 +1 @@
1
+ export { TypeScriptGetDecoratorRendererForGoogleSpanner } from './get-decorator-renderer-google-spanner.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@causa/workspace-google",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "The Causa workspace module providing many functionalities related to GCP and its services.",
5
5
  "repository": "github:causa-io/workspace-module-google",
6
6
  "license": "ISC",
@@ -29,44 +29,46 @@
29
29
  "test:cov": "npm run test -- --coverage"
30
30
  },
31
31
  "dependencies": {
32
- "@causa/cli": ">= 0.4.0 < 1.0.0",
33
- "@causa/workspace": ">= 0.12.0 < 1.0.0",
34
- "@causa/workspace-core": ">= 0.16.0 < 1.0.0",
35
- "@google-cloud/apikeys": "^1.0.1",
36
- "@google-cloud/bigquery": "^7.3.0",
37
- "@google-cloud/iam-credentials": "^3.0.1",
38
- "@google-cloud/pubsub": "^4.0.6",
39
- "@google-cloud/resource-manager": "^5.0.1",
40
- "@google-cloud/run": "^1.0.1",
41
- "@google-cloud/secret-manager": "^5.0.1",
42
- "@google-cloud/service-usage": "^3.1.0",
43
- "@google-cloud/spanner": "^7.0.0",
44
- "@google-cloud/storage": "^7.1.0",
45
- "@grpc/grpc-js": "^1.9.4",
46
- "class-validator": "^0.14.0",
47
- "firebase": "^10.4.0",
48
- "firebase-admin": "^11.11.0",
49
- "globby": "^13.2.2",
50
- "google-auth-library": "^9.0.0",
51
- "googleapis": "^126.0.1",
52
- "pino": "^8.15.1",
32
+ "@causa/cli": ">= 0.4.1 < 1.0.0",
33
+ "@causa/workspace": ">= 0.12.1 < 1.0.0",
34
+ "@causa/workspace-core": ">= 0.19.1 < 1.0.0",
35
+ "@causa/workspace-typescript": ">=0.8.2",
36
+ "@google-cloud/apikeys": "^1.1.0",
37
+ "@google-cloud/bigquery": "^7.4.0",
38
+ "@google-cloud/iam-credentials": "^3.1.0",
39
+ "@google-cloud/pubsub": "^4.3.2",
40
+ "@google-cloud/resource-manager": "^5.1.0",
41
+ "@google-cloud/run": "^1.1.0",
42
+ "@google-cloud/secret-manager": "^5.1.0",
43
+ "@google-cloud/service-usage": "^3.2.0",
44
+ "@google-cloud/spanner": "7.3.0",
45
+ "@google-cloud/storage": "^7.7.0",
46
+ "class-validator": "^0.14.1",
47
+ "firebase": "^10.8.0",
48
+ "firebase-admin": "^12.0.0",
49
+ "globby": "^14.0.1",
50
+ "google-auth-library": "^9.6.3",
51
+ "google-gax": "4.3.0",
52
+ "googleapis": "^133.0.0",
53
+ "pino": "^8.19.0",
54
+ "quicktype-core": "^23.0.104",
53
55
  "uuid": "^9.0.1"
54
56
  },
55
57
  "devDependencies": {
56
58
  "@tsconfig/node18": "^18.2.2",
57
- "@types/jest": "^29.5.5",
58
- "@types/node": "^18.18.0",
59
- "@types/uuid": "^9.0.4",
60
- "@typescript-eslint/eslint-plugin": "^6.7.3",
59
+ "@types/jest": "^29.5.12",
60
+ "@types/node": "^18.19.17",
61
+ "@types/uuid": "^9.0.8",
62
+ "@typescript-eslint/eslint-plugin": "^7.0.2",
61
63
  "copyfiles": "^2.4.1",
62
- "eslint": "^8.50.0",
63
- "eslint-config-prettier": "^9.0.0",
64
- "eslint-plugin-prettier": "^5.0.0",
64
+ "eslint": "^8.56.0",
65
+ "eslint-config-prettier": "^9.1.0",
66
+ "eslint-plugin-prettier": "^5.1.3",
65
67
  "jest": "^29.7.0",
66
- "jest-extended": "^4.0.1",
68
+ "jest-extended": "^4.0.2",
67
69
  "rimraf": "^5.0.5",
68
- "ts-jest": "^29.1.1",
69
- "ts-node": "^10.9.1",
70
- "typescript": "^5.2.2"
70
+ "ts-jest": "^29.1.2",
71
+ "ts-node": "^10.9.2",
72
+ "typescript": "^5.3.3"
71
73
  }
72
74
  }