@causa/workspace-google 0.15.0 → 0.17.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.
Files changed (49) hide show
  1. package/README.md +12 -1
  2. package/dist/configurations/generated.d.ts +6 -0
  3. package/dist/configurations/generated.js +11 -0
  4. package/dist/configurations/schemas/google.yaml +10 -0
  5. package/dist/functions/event-topic/broker-create-trigger-cloud-run.call.d.ts +1 -1
  6. package/dist/functions/event-topic/broker-create-trigger-cloud-run.call.js +41 -11
  7. package/dist/functions/event-topic/broker-create-trigger-cloud-run.d.ts +11 -2
  8. package/dist/functions/event-topic/broker-create-trigger-cloud-run.js +18 -6
  9. package/dist/functions/event-topic/broker-publish-events-google.call.js +42 -26
  10. package/dist/functions/event-topic/create-backfill-source-bigquery.call.d.ts +4 -0
  11. package/dist/functions/event-topic/create-backfill-source-bigquery.call.js +48 -0
  12. package/dist/functions/event-topic/create-backfill-source-bigquery.d.ts +20 -0
  13. package/dist/functions/event-topic/create-backfill-source-bigquery.js +32 -0
  14. package/dist/functions/event-topic/index.d.ts +2 -0
  15. package/dist/functions/event-topic/index.js +2 -0
  16. package/dist/functions/event-topic/query-events-bigquery.call.d.ts +4 -0
  17. package/dist/functions/event-topic/query-events-bigquery.call.js +41 -0
  18. package/dist/functions/event-topic/query-events-bigquery.d.ts +10 -0
  19. package/dist/functions/event-topic/query-events-bigquery.js +17 -0
  20. package/dist/functions/google-firestore/index.d.ts +1 -0
  21. package/dist/functions/google-firestore/index.js +1 -0
  22. package/dist/functions/google-firestore/query-records.call.d.ts +3 -0
  23. package/dist/functions/google-firestore/query-records.call.js +50 -0
  24. package/dist/functions/google-firestore/query-records.d.ts +18 -0
  25. package/dist/functions/google-firestore/query-records.js +22 -0
  26. package/dist/functions/google-spanner/index.d.ts +1 -0
  27. package/dist/functions/google-spanner/index.js +1 -0
  28. package/dist/functions/google-spanner/query-records.call.d.ts +3 -0
  29. package/dist/functions/google-spanner/query-records.call.js +66 -0
  30. package/dist/functions/google-spanner/query-records.d.ts +23 -0
  31. package/dist/functions/google-spanner/query-records.js +27 -0
  32. package/dist/functions/index.js +5 -4
  33. package/dist/functions/service-container/index.d.ts +1 -0
  34. package/dist/functions/service-container/index.js +1 -0
  35. package/dist/functions/service-container/query-logs-cloud-run.call.d.ts +4 -0
  36. package/dist/functions/service-container/query-logs-cloud-run.call.js +36 -0
  37. package/dist/functions/service-container/query-logs-cloud-run.d.ts +12 -0
  38. package/dist/functions/service-container/query-logs-cloud-run.js +18 -0
  39. package/dist/services/index.d.ts +1 -0
  40. package/dist/services/index.js +1 -0
  41. package/dist/services/logging.d.ts +16 -0
  42. package/dist/services/logging.js +20 -0
  43. package/package.json +18 -17
  44. package/dist/backfilling/bigquery.d.ts +0 -53
  45. package/dist/backfilling/bigquery.js +0 -101
  46. package/dist/backfilling/index.d.ts +0 -2
  47. package/dist/backfilling/index.js +0 -2
  48. package/dist/backfilling/pubsub.d.ts +0 -34
  49. package/dist/backfilling/pubsub.js +0 -65
@@ -0,0 +1,66 @@
1
+ import { Numeric, PGNumeric, Spanner, SpannerDate, } from '@google-cloud/spanner';
2
+ import { SPANNER_QUERY_RECORDS_LIMIT, } from './query-records.js';
3
+ export default async function call(context) {
4
+ if (!this.database) {
5
+ throw new Error(`The 'database' input is required for the 'google.spanner' engine.`);
6
+ }
7
+ if (!this.query) {
8
+ throw new Error(`The 'query' input is required for the 'google.spanner' engine.`);
9
+ }
10
+ const googleConf = context.asConfiguration();
11
+ const projectId = googleConf.getOrThrow('google.project');
12
+ const instanceName = googleConf.getOrThrow('google.spanner.instance.name');
13
+ context.logger.debug(`🗃️ Querying Spanner database '${this.database}' in instance '${instanceName}' (project '${projectId}').`);
14
+ const spanner = new Spanner({ projectId });
15
+ let database;
16
+ try {
17
+ database = spanner.instance(instanceName).database(this.database);
18
+ const rows = [];
19
+ const stream = database.runStream({
20
+ sql: this.query,
21
+ json: true,
22
+ jsonOptions: { wrapNumbers: false, wrapStructs: false },
23
+ });
24
+ for await (const row of stream) {
25
+ if (rows.length >= SPANNER_QUERY_RECORDS_LIMIT) {
26
+ context.logger.warn(`⚠️ Spanner query exceeded the hard limit of ${SPANNER_QUERY_RECORDS_LIMIT} rows. Results were truncated.`);
27
+ break;
28
+ }
29
+ rows.push(toJson(row));
30
+ }
31
+ return rows;
32
+ }
33
+ finally {
34
+ await database?.close();
35
+ spanner.close();
36
+ }
37
+ }
38
+ /**
39
+ * Recursively converts Spanner-specific types in a row to JSON-friendly equivalents:
40
+ * - {@link Numeric} / {@link PGNumeric} (NUMERIC / PG_NUMERIC) → their string value.
41
+ * - {@link SpannerDate} (DATE) → `YYYY-MM-DD` string.
42
+ * - Other `Date` subclasses such as `PreciseDate` (TIMESTAMP) → plain {@link Date} instances.
43
+ *
44
+ * `Buffer` values are returned as-is.
45
+ */
46
+ function toJson(value) {
47
+ if (value === null || typeof value !== 'object') {
48
+ return value;
49
+ }
50
+ if (value instanceof Numeric || value instanceof PGNumeric) {
51
+ return value.value;
52
+ }
53
+ if (value instanceof SpannerDate) {
54
+ return value.toJSON();
55
+ }
56
+ if (value instanceof Date) {
57
+ return new Date(value.getTime());
58
+ }
59
+ if (Buffer.isBuffer(value)) {
60
+ return value;
61
+ }
62
+ if (Array.isArray(value)) {
63
+ return value.map(toJson);
64
+ }
65
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toJson(v)]));
66
+ }
@@ -0,0 +1,23 @@
1
+ import { WorkspaceContext } from '@causa/workspace';
2
+ import { DatabaseQueryRecords } from '@causa/workspace-core';
3
+ /**
4
+ * The engine identifier handled by this implementation.
5
+ */
6
+ export declare const SPANNER_ENGINE = "google.spanner";
7
+ /**
8
+ * The maximum number of rows that will be returned by a single query.
9
+ * Acts as a safety mechanism to avoid loading huge result sets in memory.
10
+ */
11
+ export declare const SPANNER_QUERY_RECORDS_LIMIT = 100000;
12
+ /**
13
+ * Implements {@link DatabaseQueryRecords} for Spanner databases.
14
+ *
15
+ * The Spanner instance and GCP project are read from the workspace configuration. The `database` input is the name of
16
+ * the database within the instance, and `query` is a regular SQL statement.
17
+ *
18
+ * Results are streamed and capped to {@link SPANNER_QUERY_RECORDS_LIMIT} rows.
19
+ */
20
+ export declare class DatabaseQueryRecordsForSpanner extends DatabaseQueryRecords {
21
+ _call(context: WorkspaceContext): Promise<any[]>;
22
+ _supports(): boolean;
23
+ }
@@ -0,0 +1,27 @@
1
+ import { callDeferred, WorkspaceContext } from '@causa/workspace';
2
+ import { DatabaseQueryRecords } from '@causa/workspace-core';
3
+ /**
4
+ * The engine identifier handled by this implementation.
5
+ */
6
+ export const SPANNER_ENGINE = 'google.spanner';
7
+ /**
8
+ * The maximum number of rows that will be returned by a single query.
9
+ * Acts as a safety mechanism to avoid loading huge result sets in memory.
10
+ */
11
+ export const SPANNER_QUERY_RECORDS_LIMIT = 100_000;
12
+ /**
13
+ * Implements {@link DatabaseQueryRecords} for Spanner databases.
14
+ *
15
+ * The Spanner instance and GCP project are read from the workspace configuration. The `database` input is the name of
16
+ * the database within the instance, and `query` is a regular SQL statement.
17
+ *
18
+ * Results are streamed and capped to {@link SPANNER_QUERY_RECORDS_LIMIT} rows.
19
+ */
20
+ export class DatabaseQueryRecordsForSpanner extends DatabaseQueryRecords {
21
+ async _call(context) {
22
+ return await callDeferred(this, context, import.meta.url);
23
+ }
24
+ _supports() {
25
+ return this.engine === SPANNER_ENGINE;
26
+ }
27
+ }
@@ -1,16 +1,17 @@
1
1
  import { CausaListConfigurationSchemasForGoogle } from './causa/index.js';
2
2
  import { EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, } from './emulator/index.js';
3
- import { EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, } from './event-topic/index.js';
3
+ import { EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, } from './event-topic/index.js';
4
4
  import { GoogleAppCheckGenerateToken } from './google-app-check/index.js';
5
5
  import { GoogleFirebaseStorageMergeRules } from './google-firebase-storage/index.js';
6
- import { GoogleFirestoreMergeRules } from './google-firestore/index.js';
6
+ import { DatabaseQueryRecordsForFirestore, GoogleFirestoreMergeRules, } from './google-firestore/index.js';
7
7
  import { GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, } from './google-identity-platform/index.js';
8
8
  import { GooglePubSubWriteTopics } from './google-pubsub/index.js';
9
9
  import { GoogleServicesEnable } from './google-services/index.js';
10
- import { GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, } from './google-spanner/index.js';
10
+ import { DatabaseQueryRecordsForSpanner, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, } from './google-spanner/index.js';
11
11
  import { ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, } from './project/index.js';
12
12
  import { SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, } from './secret/index.js';
13
+ import { ServiceContainerQueryLogsForCloudRun } from './service-container/index.js';
13
14
  import { TypeScriptGetDecoratorRendererForGoogleFirestore, TypeScriptGetDecoratorRendererForGoogleSpanner, } from './typescript/index.js';
14
15
  export function registerFunctions(context) {
15
- context.registerFunctionImplementations(CausaListConfigurationSchemasForGoogle, 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, TypeScriptGetDecoratorRendererForGoogleFirestore, TypeScriptGetDecoratorRendererForGoogleSpanner);
16
+ context.registerFunctionImplementations(CausaListConfigurationSchemasForGoogle, DatabaseQueryRecordsForFirestore, DatabaseQueryRecordsForSpanner, EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GooglePubSubWriteTopics, GoogleServicesEnable, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, ServiceContainerQueryLogsForCloudRun, TypeScriptGetDecoratorRendererForGoogleFirestore, TypeScriptGetDecoratorRendererForGoogleSpanner);
16
17
  }
@@ -0,0 +1 @@
1
+ export { ServiceContainerQueryLogsForCloudRun } from './query-logs-cloud-run.js';
@@ -0,0 +1 @@
1
+ export { ServiceContainerQueryLogsForCloudRun } from './query-logs-cloud-run.js';
@@ -0,0 +1,4 @@
1
+ import type { WorkspaceContext } from '@causa/workspace';
2
+ import type { QueriedLogEntry } from '@causa/workspace-core';
3
+ import type { ServiceContainerQueryLogsForCloudRun } from './query-logs-cloud-run.js';
4
+ export default function call(this: ServiceContainerQueryLogsForCloudRun, context: WorkspaceContext): Promise<QueriedLogEntry[]>;
@@ -0,0 +1,36 @@
1
+ import { LoggingService } from '../../services/index.js';
2
+ /**
3
+ * The default duration applied to `from` when it is not set, in milliseconds.
4
+ */
5
+ const DEFAULT_FROM_OFFSET = 60 * 60 * 1000;
6
+ /**
7
+ * The default value for `limit` when it is not set.
8
+ */
9
+ const DEFAULT_LIMIT = 1000;
10
+ export default async function call(context) {
11
+ const from = this.from ?? new Date(Date.now() - DEFAULT_FROM_OFFSET);
12
+ const limit = this.limit ?? DEFAULT_LIMIT;
13
+ const filterParts = [
14
+ `resource.type="cloud_run_revision"`,
15
+ `resource.labels.service_name=~"${this.service}"`,
16
+ `timestamp>="${from.toISOString()}"`,
17
+ ];
18
+ if (this.to) {
19
+ filterParts.push(`timestamp<"${this.to.toISOString()}"`);
20
+ }
21
+ if (this.filter) {
22
+ filterParts.push(this.filter);
23
+ }
24
+ const filter = filterParts.join('\n');
25
+ const loggingService = context.service(LoggingService);
26
+ context.logger.debug(`Querying Cloud Logging for service '${this.service}' in project '${loggingService.projectId}' with filter:\n${filter}`);
27
+ const [pageEntries] = await loggingService.logging.getEntries({
28
+ filter,
29
+ orderBy: 'timestamp asc',
30
+ maxResults: limit,
31
+ });
32
+ return pageEntries.map((entry) => ({
33
+ timestamp: entry.metadata.timestamp,
34
+ message: entry.toStructuredJSON(),
35
+ }));
36
+ }
@@ -0,0 +1,12 @@
1
+ import { WorkspaceContext } from '@causa/workspace';
2
+ import { type QueriedLogEntry, ServiceContainerQueryLogs } from '@causa/workspace-core';
3
+ /**
4
+ * Implements {@link ServiceContainerQueryLogs} for services deployed on Cloud Run.
5
+ *
6
+ * Queries the Cloud Logging API for entries emitted by `cloud_run_revision` resources whose `service_name` label
7
+ * matches the input `service`. The optional `from` / `to` time range, `filter`, and `limit` are forwarded to the API.
8
+ */
9
+ export declare class ServiceContainerQueryLogsForCloudRun extends ServiceContainerQueryLogs {
10
+ _call(context: WorkspaceContext): Promise<QueriedLogEntry[]>;
11
+ _supports(context: WorkspaceContext): boolean;
12
+ }
@@ -0,0 +1,18 @@
1
+ import { callDeferred, WorkspaceContext } from '@causa/workspace';
2
+ import { ServiceContainerQueryLogs, } from '@causa/workspace-core';
3
+ /**
4
+ * Implements {@link ServiceContainerQueryLogs} for services deployed on Cloud Run.
5
+ *
6
+ * Queries the Cloud Logging API for entries emitted by `cloud_run_revision` resources whose `service_name` label
7
+ * matches the input `service`. The optional `from` / `to` time range, `filter`, and `limit` are forwarded to the API.
8
+ */
9
+ export class ServiceContainerQueryLogsForCloudRun extends ServiceContainerQueryLogs {
10
+ async _call(context) {
11
+ return await callDeferred(this, context, import.meta.url);
12
+ }
13
+ _supports(context) {
14
+ return (context
15
+ .asConfiguration()
16
+ .get('serviceContainer.platform') === 'google.cloudRun');
17
+ }
18
+ }
@@ -8,6 +8,7 @@ export { FirebaseEmulatorService } from './firebase-emulator.js';
8
8
  export { GcloudEmulatorService } from './gcloud-emulator.js';
9
9
  export { GoogleApisService } from './google-apis.js';
10
10
  export { IamService } from './iam.js';
11
+ export { LoggingService } from './logging.js';
11
12
  export { PubSubService } from './pubsub.js';
12
13
  export { ResourceManagerService } from './resource-manager.js';
13
14
  export { GoogleSecretManagerService } from './secret-manager.js';
@@ -7,6 +7,7 @@ export { FirebaseEmulatorService } from './firebase-emulator.js';
7
7
  export { GcloudEmulatorService } from './gcloud-emulator.js';
8
8
  export { GoogleApisService } from './google-apis.js';
9
9
  export { IamService } from './iam.js';
10
+ export { LoggingService } from './logging.js';
10
11
  export { PubSubService } from './pubsub.js';
11
12
  export { ResourceManagerService } from './resource-manager.js';
12
13
  export { GoogleSecretManagerService } from './secret-manager.js';
@@ -0,0 +1,16 @@
1
+ import { WorkspaceContext } from '@causa/workspace';
2
+ import { Logging } from '@google-cloud/logging';
3
+ /**
4
+ * A service that provides access to Cloud Logging.
5
+ */
6
+ export declare class LoggingService {
7
+ /**
8
+ * The Cloud Logging client configured with the GCP project ID.
9
+ */
10
+ readonly logging: Logging;
11
+ /**
12
+ * The GCP project ID.
13
+ */
14
+ readonly projectId: string;
15
+ constructor(context: WorkspaceContext);
16
+ }
@@ -0,0 +1,20 @@
1
+ import { WorkspaceContext } from '@causa/workspace';
2
+ import { Logging } from '@google-cloud/logging';
3
+ /**
4
+ * A service that provides access to Cloud Logging.
5
+ */
6
+ export class LoggingService {
7
+ /**
8
+ * The Cloud Logging client configured with the GCP project ID.
9
+ */
10
+ logging;
11
+ /**
12
+ * The GCP project ID.
13
+ */
14
+ projectId;
15
+ constructor(context) {
16
+ const googleConf = context.asConfiguration();
17
+ this.projectId = googleConf.getOrThrow('google.project');
18
+ this.logging = new Logging({ projectId: this.projectId });
19
+ }
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@causa/workspace-google",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "The Causa workspace module providing many functionalities related to GCP and its services.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,21 +36,22 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@causa/cli": ">= 0.7.0 < 1.0.0",
39
- "@causa/workspace": ">= 0.23.0 < 1.0.0",
40
- "@causa/workspace-core": ">= 0.30.0 < 1.0.0",
41
- "@causa/workspace-typescript": ">= 0.20.0 < 1.0.0",
42
- "@google-cloud/apikeys": "^2.2.1",
43
- "@google-cloud/bigquery": "^8.1.1",
44
- "@google-cloud/iam-credentials": "^4.2.1",
39
+ "@causa/workspace": ">= 0.24.1 < 1.0.0",
40
+ "@causa/workspace-core": ">= 0.34.0 < 1.0.0",
41
+ "@causa/workspace-typescript": ">= 0.22.0 < 1.0.0",
42
+ "@google-cloud/apikeys": "^2.2.2",
43
+ "@google-cloud/bigquery": "^8.3.0",
44
+ "@google-cloud/iam-credentials": "^4.2.2",
45
+ "@google-cloud/logging": "^11.2.1",
45
46
  "@google-cloud/pubsub": "^5.3.0",
46
- "@google-cloud/resource-manager": "^6.2.1",
47
- "@google-cloud/run": "^3.2.0",
48
- "@google-cloud/secret-manager": "^6.1.1",
49
- "@google-cloud/service-usage": "^4.2.1",
50
- "@google-cloud/spanner": "^8.6.0",
47
+ "@google-cloud/resource-manager": "^6.2.2",
48
+ "@google-cloud/run": "^3.2.1",
49
+ "@google-cloud/secret-manager": "^6.1.2",
50
+ "@google-cloud/service-usage": "^4.2.2",
51
+ "@google-cloud/spanner": "^8.7.0",
51
52
  "@google-cloud/storage": "^7.19.0",
52
53
  "class-validator": "^0.15.1",
53
- "firebase": "^12.12.0",
54
+ "firebase": "^12.12.1",
54
55
  "firebase-admin": "^13.8.0",
55
56
  "globby": "^16.2.0",
56
57
  "google-auth-library": "^10.6.2",
@@ -61,20 +62,20 @@
61
62
  "quicktype-core": "^23.2.6"
62
63
  },
63
64
  "devDependencies": {
64
- "@swc/core": "^1.15.24",
65
+ "@swc/core": "^1.15.33",
65
66
  "@swc/jest": "^0.2.39",
66
67
  "@tsconfig/node22": "^22.0.5",
67
68
  "@types/jest": "^30.0.0",
68
69
  "@types/micromatch": "^4.0.10",
69
70
  "@types/node": "^20.19.39",
70
71
  "copyfiles": "^2.4.1",
71
- "eslint": "^10.2.0",
72
+ "eslint": "^10.3.0",
72
73
  "eslint-config-prettier": "^10.1.8",
73
74
  "eslint-plugin-prettier": "^5.5.5",
74
75
  "jest": "^30.3.0",
75
76
  "jest-extended": "^7.0.0",
76
77
  "rimraf": "^6.1.3",
77
- "typescript": "^6.0.2",
78
- "typescript-eslint": "^8.58.1"
78
+ "typescript": "^6.0.3",
79
+ "typescript-eslint": "^8.59.1"
79
80
  }
80
81
  }
@@ -1,53 +0,0 @@
1
- import { WorkspaceContext } from '@causa/workspace';
2
- import type { BackfillEvent, BackfillEventsSource } from '@causa/workspace-core/backfill';
3
- /**
4
- * An events source that reads events from a BigQuery table.
5
- */
6
- export declare class BigQueryEventsSource implements BackfillEventsSource {
7
- readonly tableId: string;
8
- readonly filter?: string | undefined;
9
- /**
10
- * The BigQuery client.
11
- */
12
- private readonly bigQuery;
13
- /**
14
- * The job from which the events are read.
15
- */
16
- private job;
17
- /**
18
- * The request for the next page of results.
19
- * If this is `null` after initializing the job, there are no more results.
20
- */
21
- private nextPageRequest;
22
- /**
23
- * The logger.
24
- */
25
- private readonly logger;
26
- /**
27
- * Creates a new events source that reads events from a BigQuery table.
28
- *
29
- * @param context The {@link WorkspaceContext}.
30
- * @param tableId The ID of the BigQuery table from which the events should be read.
31
- * @param filter A SQL boolean expression that filters the events from the table.
32
- */
33
- constructor(context: WorkspaceContext, tableId: string, filter?: string | undefined);
34
- /**
35
- * Creates a BigQuery job that reads the events from the table.
36
- * The `data` and `attributes` columns are read to get the corresponding event properties.
37
- * If a filter was specified, it is applied to the query as a where clause.
38
- *
39
- * @returns The BigQuery job that reads the events from the table.
40
- */
41
- private createJob;
42
- getBatch(): Promise<BackfillEvent[] | null>;
43
- dispose(): Promise<void>;
44
- /**
45
- * Tries to create a new {@link BigQueryEventsSource} from the given source string.
46
- *
47
- * @param context The {@link WorkspaceContext}.
48
- * @param source The string representing the source, in the format `bq://<projectId>.<datasetId>.<tableId>`.
49
- * @param filter A SQL boolean expression that filters the events from the table.
50
- * @returns A new {@link BigQueryEventsSource} if the source string is valid, otherwise `null`.
51
- */
52
- static fromSourceAndFilter(context: WorkspaceContext, source: string, filter?: string): Promise<BigQueryEventsSource | null>;
53
- }
@@ -1,101 +0,0 @@
1
- import { WorkspaceContext } from '@causa/workspace';
2
- import { BigQuery, Job } from '@google-cloud/bigquery';
3
- import { BigQueryService } from '../services/index.js';
4
- /**
5
- * The number of rows (events) in a single batch.
6
- */
7
- const BIGQUERY_MAX_RESULTS = 10000;
8
- /**
9
- * An events source that reads events from a BigQuery table.
10
- */
11
- export class BigQueryEventsSource {
12
- tableId;
13
- filter;
14
- /**
15
- * The BigQuery client.
16
- */
17
- bigQuery;
18
- /**
19
- * The job from which the events are read.
20
- */
21
- job = null;
22
- /**
23
- * The request for the next page of results.
24
- * If this is `null` after initializing the job, there are no more results.
25
- */
26
- nextPageRequest = null;
27
- /**
28
- * The logger.
29
- */
30
- logger;
31
- /**
32
- * Creates a new events source that reads events from a BigQuery table.
33
- *
34
- * @param context The {@link WorkspaceContext}.
35
- * @param tableId The ID of the BigQuery table from which the events should be read.
36
- * @param filter A SQL boolean expression that filters the events from the table.
37
- */
38
- constructor(context, tableId, filter) {
39
- this.tableId = tableId;
40
- this.filter = filter;
41
- this.bigQuery = context.service(BigQueryService).bigQuery;
42
- this.logger = context.logger;
43
- }
44
- /**
45
- * Creates a BigQuery job that reads the events from the table.
46
- * The `data` and `attributes` columns are read to get the corresponding event properties.
47
- * If a filter was specified, it is applied to the query as a where clause.
48
- *
49
- * @returns The BigQuery job that reads the events from the table.
50
- */
51
- async createJob() {
52
- let query = `
53
- SELECT
54
- data,
55
- attributes,
56
- FROM
57
- \`${this.tableId}\``;
58
- if (this.filter) {
59
- query += `
60
- WHERE
61
- (${this.filter})`;
62
- }
63
- this.logger.debug(`Creating BigQuery job from query '${query}'.`);
64
- const [job] = await this.bigQuery.createQueryJob(query);
65
- return job;
66
- }
67
- async getBatch() {
68
- if (!this.job) {
69
- this.job = await this.createJob();
70
- }
71
- else if (!this.nextPageRequest) {
72
- return null;
73
- }
74
- const [rows, nextPageRequest] = await this.job.getQueryResults(this.nextPageRequest ?? {
75
- autoPaginate: false,
76
- maxResults: BIGQUERY_MAX_RESULTS,
77
- });
78
- this.nextPageRequest = nextPageRequest ?? null;
79
- return rows.map((row) => ({
80
- data: Buffer.from(row.data),
81
- attributes: JSON.parse(row.attributes),
82
- }));
83
- }
84
- async dispose() { }
85
- /**
86
- * Tries to create a new {@link BigQueryEventsSource} from the given source string.
87
- *
88
- * @param context The {@link WorkspaceContext}.
89
- * @param source The string representing the source, in the format `bq://<projectId>.<datasetId>.<tableId>`.
90
- * @param filter A SQL boolean expression that filters the events from the table.
91
- * @returns A new {@link BigQueryEventsSource} if the source string is valid, otherwise `null`.
92
- */
93
- static async fromSourceAndFilter(context, source, filter) {
94
- const match = source.match(/^bq:\/\/(?<tableId>.+)$/);
95
- if (!match?.groups?.tableId) {
96
- return null;
97
- }
98
- const tableId = match.groups.tableId;
99
- return new BigQueryEventsSource(context, tableId, filter);
100
- }
101
- }
@@ -1,2 +0,0 @@
1
- export { BigQueryEventsSource } from './bigquery.js';
2
- export { PubSubBackfillEventPublisher } from './pubsub.js';
@@ -1,2 +0,0 @@
1
- export { BigQueryEventsSource } from './bigquery.js';
2
- export { PubSubBackfillEventPublisher } from './pubsub.js';
@@ -1,34 +0,0 @@
1
- import { WorkspaceContext } from '@causa/workspace';
2
- import { type BackfillEvent, BackfillEventPublisher } from '@causa/workspace-core/backfill';
3
- /**
4
- * A publisher that publishes events to backfill to a Pub/Sub topic.
5
- */
6
- export declare class PubSubBackfillEventPublisher extends BackfillEventPublisher {
7
- readonly topicId: string;
8
- /**
9
- * The actual publisher, which handles batching and flow control.
10
- */
11
- private readonly publisher;
12
- /**
13
- * Creates a new {@link PubSubBackfillEventPublisher}.
14
- *
15
- * @param context The {@link WorkspaceContext}.
16
- * @param topicId The ID of the Pub/Sub topic to publish to.
17
- * This should be a full Pub/Sub topic ID, e.g. `projects/<projectId>/topics/<topicId>`.
18
- */
19
- constructor(context: WorkspaceContext, topicId: string);
20
- /**
21
- * Publishes an event to the Pub/Sub topic.
22
- * Publishing actually occurs asynchronously. {@link PubSubBackfillEventPublisher.flush} must be called to wait for
23
- * all events to be published.
24
- *
25
- * @param event The event to publish.
26
- * @returns A promise that resolves when the publisher has caught up with publishing, or `null` if other events can be
27
- * published immediately.
28
- */
29
- protected publishEvent(event: BackfillEvent): Promise<void> | null;
30
- /**
31
- * Waits for all events to be published.
32
- */
33
- protected flush(): Promise<void>;
34
- }
@@ -1,65 +0,0 @@
1
- import { WorkspaceContext } from '@causa/workspace';
2
- import { BackfillEventPublisher, } from '@causa/workspace-core/backfill';
3
- import { FlowControlledPublisher } from '@google-cloud/pubsub/build/src/publisher/flow-publisher.js';
4
- import { PubSubService } from '../services/index.js';
5
- /**
6
- * Options when publishing events in batches to Pub/Sub.
7
- */
8
- const PUBLISH_OPTIONS = {
9
- flowControlOptions: {
10
- maxOutstandingBytes: 10 * 1024 * 1024,
11
- maxOutstandingMessages: 1000,
12
- },
13
- batching: {
14
- maxBytes: 10 * 1024 * 1024,
15
- maxMessages: 1000,
16
- maxMilliseconds: 1000,
17
- },
18
- };
19
- /**
20
- * A publisher that publishes events to backfill to a Pub/Sub topic.
21
- */
22
- export class PubSubBackfillEventPublisher extends BackfillEventPublisher {
23
- topicId;
24
- /**
25
- * The actual publisher, which handles batching and flow control.
26
- */
27
- publisher;
28
- /**
29
- * Creates a new {@link PubSubBackfillEventPublisher}.
30
- *
31
- * @param context The {@link WorkspaceContext}.
32
- * @param topicId The ID of the Pub/Sub topic to publish to.
33
- * This should be a full Pub/Sub topic ID, e.g. `projects/<projectId>/topics/<topicId>`.
34
- */
35
- constructor(context, topicId) {
36
- super(context);
37
- this.topicId = topicId;
38
- this.publisher = context
39
- .service(PubSubService)
40
- .pubSub.topic(topicId, PUBLISH_OPTIONS)
41
- .flowControlled();
42
- }
43
- /**
44
- * Publishes an event to the Pub/Sub topic.
45
- * Publishing actually occurs asynchronously. {@link PubSubBackfillEventPublisher.flush} must be called to wait for
46
- * all events to be published.
47
- *
48
- * @param event The event to publish.
49
- * @returns A promise that resolves when the publisher has caught up with publishing, or `null` if other events can be
50
- * published immediately.
51
- */
52
- publishEvent(event) {
53
- return this.publisher.publish({
54
- data: event.data,
55
- attributes: event.attributes,
56
- orderingKey: event.key,
57
- });
58
- }
59
- /**
60
- * Waits for all events to be published.
61
- */
62
- async flush() {
63
- await this.publisher.all();
64
- }
65
- }