@useairfoil/producer-google-analytics 0.0.2 → 0.0.3
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/LICENSE.txt +201 -0
- package/dist/index.mjs +1 -1
- package/dist/main.mjs +6 -4
- package/dist/main.mjs.map +1 -1
- package/dist/{src-Cu0LuOTi.mjs → src-Cvr4Q9Xs.mjs} +89 -90
- package/dist/src-Cvr4Q9Xs.mjs.map +1 -0
- package/package.json +17 -17
- package/src/create-table.ts +6 -1
- package/src/main.ts +8 -2
- package/src/schemas/reports.ts +79 -83
- package/src/schemas/shared.ts +19 -15
- package/src/start.ts +2 -2
- package/dist/src-Cu0LuOTi.mjs.map +0 -1
|
@@ -189,7 +189,6 @@ const layer$1 = (config) => Layer.effect(GoogleAnalyticsClient)(make$1(config)).
|
|
|
189
189
|
const layerConfig$1 = (config) => Layer.unwrap(Config.unwrap(config).pipe(Effect.map(layer$1)));
|
|
190
190
|
//#endregion
|
|
191
191
|
//#region src/schemas/shared.ts
|
|
192
|
-
const field = (fieldId, description) => Iceberg.field(fieldId, { description });
|
|
193
192
|
/** The API's `YYYYMMDD` as `YYYY-MM-DD`. */
|
|
194
193
|
const isoDay = (date) => `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6)}`;
|
|
195
194
|
const ReportDate = Schema.String.check(Schema.isPattern(/^\d{8}$/)).pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
|
|
@@ -197,19 +196,19 @@ const ReportDate = Schema.String.check(Schema.isPattern(/^\d{8}$/)).pipe(Schema.
|
|
|
197
196
|
encode: (date) => date.replaceAll("-", "")
|
|
198
197
|
})));
|
|
199
198
|
/** `TYPE_INTEGER` metrics, as an Iceberg long. */
|
|
200
|
-
const count = (
|
|
199
|
+
const count = (description) => Schema.BigIntFromString.pipe(Schema.annotateKey({ description }));
|
|
201
200
|
/** Float, currency, and seconds metrics, as an Iceberg double. */
|
|
202
|
-
const amount = (
|
|
201
|
+
const amount = (description) => Schema.FiniteFromString.pipe(Schema.annotateKey({ description }));
|
|
203
202
|
/** Dimension values are never null. GA uses `(not set)` and `(other)` instead. */
|
|
204
|
-
const dimension = (
|
|
203
|
+
const dimension = (description) => Schema.String.pipe(Schema.annotateKey({ description }));
|
|
205
204
|
/** `version` is the fetch time, since report rows have no update time. */
|
|
206
205
|
const common = {
|
|
207
|
-
id: Schema.String.pipe(
|
|
208
|
-
version: Schema.Date.pipe(
|
|
209
|
-
property_id: Schema.String.pipe(
|
|
210
|
-
date: ReportDate.pipe(
|
|
206
|
+
id: Schema.String.pipe(Schema.annotateKey({ description: "JSON array of the property ID, date, and dimension values." })),
|
|
207
|
+
version: Schema.Date.pipe(Schema.annotateKey({ description: "Time the row was read from Google Analytics." })),
|
|
208
|
+
property_id: Schema.String.pipe(Schema.annotateKey({ description: "GA4 property ID." })),
|
|
209
|
+
date: ReportDate.pipe(Schema.annotateKey({ description: "Day in the property's time zone, as YYYY-MM-DD." }))
|
|
211
210
|
};
|
|
212
|
-
const currencyCode = Schema.String.pipe(
|
|
211
|
+
const currencyCode = Schema.String.pipe(Schema.annotateKey({ description: "Currency of the revenue fields, the property's currency." }));
|
|
213
212
|
//#endregion
|
|
214
213
|
//#region src/resources/reports/read.ts
|
|
215
214
|
const pageSize = 1e5;
|
|
@@ -414,115 +413,115 @@ const reportEntity = (client, config, spec) => ({
|
|
|
414
413
|
//#region src/schemas/reports.ts
|
|
415
414
|
const DailyOverviewSchema = Schema.Struct({
|
|
416
415
|
...common,
|
|
417
|
-
active_users: count(
|
|
418
|
-
new_users: count(
|
|
419
|
-
total_users: count(
|
|
420
|
-
sessions: count(
|
|
421
|
-
engaged_sessions: count(
|
|
422
|
-
screen_page_views: count(
|
|
423
|
-
event_count: count(
|
|
424
|
-
key_events: amount(
|
|
425
|
-
total_revenue: amount(
|
|
426
|
-
user_engagement_duration: amount(
|
|
416
|
+
active_users: count("Users who engaged on the day."),
|
|
417
|
+
new_users: count("Users who came for the first time."),
|
|
418
|
+
total_users: count("Users who logged any event."),
|
|
419
|
+
sessions: count("Sessions that started."),
|
|
420
|
+
engaged_sessions: count("Sessions that lasted 10 seconds or more, or had a key event or 2 page views."),
|
|
421
|
+
screen_page_views: count("Page and screen views."),
|
|
422
|
+
event_count: count("Events."),
|
|
423
|
+
key_events: amount("Key events. Can be fractional after attribution."),
|
|
424
|
+
total_revenue: amount("Revenue from purchases, subscriptions, and ads."),
|
|
425
|
+
user_engagement_duration: amount("Seconds the site or app was in the foreground."),
|
|
427
426
|
currency_code: currencyCode
|
|
428
427
|
}).annotate({ description: "Google Analytics totals per day." }).pipe(Iceberg.identifier("id"));
|
|
429
428
|
const TrafficSourceSchema = Schema.Struct({
|
|
430
429
|
...common,
|
|
431
|
-
session_source: dimension(
|
|
432
|
-
session_medium: dimension(
|
|
433
|
-
session_campaign_name: dimension(
|
|
434
|
-
session_default_channel_group: dimension(
|
|
435
|
-
sessions: count(
|
|
436
|
-
engaged_sessions: count(
|
|
437
|
-
active_users: count(
|
|
438
|
-
new_users: count(
|
|
439
|
-
screen_page_views: count(
|
|
440
|
-
event_count: count(
|
|
441
|
-
key_events: amount(
|
|
442
|
-
total_revenue: amount(
|
|
443
|
-
user_engagement_duration: amount(
|
|
430
|
+
session_source: dimension("Source of the session, such as google."),
|
|
431
|
+
session_medium: dimension("Medium of the session, such as organic."),
|
|
432
|
+
session_campaign_name: dimension("Campaign of the session."),
|
|
433
|
+
session_default_channel_group: dimension("Default channel group of the session."),
|
|
434
|
+
sessions: count("Sessions that started."),
|
|
435
|
+
engaged_sessions: count("Engaged sessions."),
|
|
436
|
+
active_users: count("Users who engaged."),
|
|
437
|
+
new_users: count("Users who came for the first time."),
|
|
438
|
+
screen_page_views: count("Page and screen views."),
|
|
439
|
+
event_count: count("Events."),
|
|
440
|
+
key_events: amount("Key events."),
|
|
441
|
+
total_revenue: amount("Revenue from purchases, subscriptions, and ads."),
|
|
442
|
+
user_engagement_duration: amount("Seconds the site or app was in the foreground."),
|
|
444
443
|
currency_code: currencyCode
|
|
445
444
|
}).annotate({ description: "Google Analytics sessions per day and traffic source." }).pipe(Iceberg.identifier("id"));
|
|
446
445
|
const UserAcquisitionSchema = Schema.Struct({
|
|
447
446
|
...common,
|
|
448
|
-
first_user_source: dimension(
|
|
449
|
-
first_user_medium: dimension(
|
|
450
|
-
first_user_campaign_name: dimension(
|
|
451
|
-
first_user_default_channel_group: dimension(
|
|
452
|
-
new_users: count(
|
|
453
|
-
active_users: count(
|
|
454
|
-
sessions: count(
|
|
455
|
-
engaged_sessions: count(
|
|
456
|
-
key_events: amount(
|
|
457
|
-
total_revenue: amount(
|
|
447
|
+
first_user_source: dimension("Source that first brought the user."),
|
|
448
|
+
first_user_medium: dimension("Medium that first brought the user."),
|
|
449
|
+
first_user_campaign_name: dimension("Campaign that first brought the user."),
|
|
450
|
+
first_user_default_channel_group: dimension("Default channel group that first brought the user."),
|
|
451
|
+
new_users: count("Users who came for the first time."),
|
|
452
|
+
active_users: count("Users who engaged."),
|
|
453
|
+
sessions: count("Sessions that started."),
|
|
454
|
+
engaged_sessions: count("Engaged sessions."),
|
|
455
|
+
key_events: amount("Key events."),
|
|
456
|
+
total_revenue: amount("Revenue from purchases, subscriptions, and ads."),
|
|
458
457
|
currency_code: currencyCode
|
|
459
458
|
}).annotate({ description: "Google Analytics users per day and first traffic source." }).pipe(Iceberg.identifier("id"));
|
|
460
459
|
const PageSchema = Schema.Struct({
|
|
461
460
|
...common,
|
|
462
|
-
host_name: dimension(
|
|
463
|
-
page_path: dimension(
|
|
464
|
-
page_title: dimension(
|
|
465
|
-
screen_page_views: count(
|
|
466
|
-
active_users: count(
|
|
467
|
-
event_count: count(
|
|
468
|
-
key_events: amount(
|
|
469
|
-
user_engagement_duration: amount(
|
|
461
|
+
host_name: dimension("Host name, such as www.example.com."),
|
|
462
|
+
page_path: dimension("Page path, without the query string."),
|
|
463
|
+
page_title: dimension("Page title."),
|
|
464
|
+
screen_page_views: count("Page and screen views."),
|
|
465
|
+
active_users: count("Users who engaged."),
|
|
466
|
+
event_count: count("Events."),
|
|
467
|
+
key_events: amount("Key events."),
|
|
468
|
+
user_engagement_duration: amount("Seconds the page was in the foreground.")
|
|
470
469
|
}).annotate({ description: "Google Analytics page views per day and page." }).pipe(Iceberg.identifier("id"));
|
|
471
470
|
const LandingPageSchema = Schema.Struct({
|
|
472
471
|
...common,
|
|
473
|
-
landing_page: dimension(
|
|
474
|
-
sessions: count(
|
|
475
|
-
engaged_sessions: count(
|
|
476
|
-
active_users: count(
|
|
477
|
-
new_users: count(
|
|
478
|
-
key_events: amount(
|
|
479
|
-
total_revenue: amount(
|
|
472
|
+
landing_page: dimension("First page path of the session, without the query string."),
|
|
473
|
+
sessions: count("Sessions that started."),
|
|
474
|
+
engaged_sessions: count("Engaged sessions."),
|
|
475
|
+
active_users: count("Users who engaged."),
|
|
476
|
+
new_users: count("Users who came for the first time."),
|
|
477
|
+
key_events: amount("Key events."),
|
|
478
|
+
total_revenue: amount("Revenue from purchases, subscriptions, and ads."),
|
|
480
479
|
currency_code: currencyCode
|
|
481
480
|
}).annotate({ description: "Google Analytics sessions per day and landing page." }).pipe(Iceberg.identifier("id"));
|
|
482
481
|
const EventSchema = Schema.Struct({
|
|
483
482
|
...common,
|
|
484
|
-
event_name: dimension(
|
|
485
|
-
event_count: count(
|
|
486
|
-
total_users: count(
|
|
487
|
-
event_value: amount(
|
|
488
|
-
key_events: amount(
|
|
483
|
+
event_name: dimension("Event name, such as page_view."),
|
|
484
|
+
event_count: count("Events."),
|
|
485
|
+
total_users: count("Users who logged the event."),
|
|
486
|
+
event_value: amount("Sum of the event's value parameter."),
|
|
487
|
+
key_events: amount("Key events.")
|
|
489
488
|
}).annotate({ description: "Google Analytics events per day and event name." }).pipe(Iceberg.identifier("id"));
|
|
490
489
|
const LocationSchema = Schema.Struct({
|
|
491
490
|
...common,
|
|
492
|
-
country: dimension(
|
|
493
|
-
region: dimension(
|
|
494
|
-
city: dimension(
|
|
495
|
-
active_users: count(
|
|
496
|
-
new_users: count(
|
|
497
|
-
sessions: count(
|
|
498
|
-
engaged_sessions: count(
|
|
499
|
-
key_events: amount(
|
|
500
|
-
total_revenue: amount(
|
|
491
|
+
country: dimension("Country of the user."),
|
|
492
|
+
region: dimension("Region of the user."),
|
|
493
|
+
city: dimension("City of the user."),
|
|
494
|
+
active_users: count("Users who engaged."),
|
|
495
|
+
new_users: count("Users who came for the first time."),
|
|
496
|
+
sessions: count("Sessions that started."),
|
|
497
|
+
engaged_sessions: count("Engaged sessions."),
|
|
498
|
+
key_events: amount("Key events."),
|
|
499
|
+
total_revenue: amount("Revenue from purchases, subscriptions, and ads."),
|
|
501
500
|
currency_code: currencyCode
|
|
502
501
|
}).annotate({ description: "Google Analytics users per day and location." }).pipe(Iceberg.identifier("id"));
|
|
503
502
|
const DeviceSchema = Schema.Struct({
|
|
504
503
|
...common,
|
|
505
|
-
device_category: dimension(
|
|
506
|
-
platform: dimension(
|
|
507
|
-
operating_system: dimension(
|
|
508
|
-
browser: dimension(
|
|
509
|
-
active_users: count(
|
|
510
|
-
new_users: count(
|
|
511
|
-
sessions: count(
|
|
512
|
-
engaged_sessions: count(
|
|
513
|
-
screen_page_views: count(
|
|
514
|
-
key_events: amount(
|
|
504
|
+
device_category: dimension("Device category: desktop, mobile, or tablet."),
|
|
505
|
+
platform: dimension("Platform: web, iOS, or Android."),
|
|
506
|
+
operating_system: dimension("Operating system."),
|
|
507
|
+
browser: dimension("Browser."),
|
|
508
|
+
active_users: count("Users who engaged."),
|
|
509
|
+
new_users: count("Users who came for the first time."),
|
|
510
|
+
sessions: count("Sessions that started."),
|
|
511
|
+
engaged_sessions: count("Engaged sessions."),
|
|
512
|
+
screen_page_views: count("Page and screen views."),
|
|
513
|
+
key_events: amount("Key events.")
|
|
515
514
|
}).annotate({ description: "Google Analytics users per day and device." }).pipe(Iceberg.identifier("id"));
|
|
516
515
|
const EcommerceItemSchema = Schema.Struct({
|
|
517
516
|
...common,
|
|
518
|
-
item_id: dimension(
|
|
519
|
-
item_name: dimension(
|
|
520
|
-
item_brand: dimension(
|
|
521
|
-
item_category: dimension(
|
|
522
|
-
items_viewed: count(
|
|
523
|
-
items_added_to_cart: count(
|
|
524
|
-
items_purchased: count(
|
|
525
|
-
item_revenue: amount(
|
|
517
|
+
item_id: dimension("Item ID."),
|
|
518
|
+
item_name: dimension("Item name."),
|
|
519
|
+
item_brand: dimension("Item brand."),
|
|
520
|
+
item_category: dimension("First item category."),
|
|
521
|
+
items_viewed: count("Units viewed."),
|
|
522
|
+
items_added_to_cart: count("Units added to carts."),
|
|
523
|
+
items_purchased: count("Units purchased."),
|
|
524
|
+
item_revenue: amount("Revenue from the item, without tax and shipping."),
|
|
526
525
|
currency_code: currencyCode
|
|
527
526
|
}).annotate({ description: "Google Analytics ecommerce items per day. Empty without ecommerce events." }).pipe(Iceberg.identifier("id"));
|
|
528
527
|
//#endregion
|
|
@@ -708,4 +707,4 @@ const layerConfig = (config) => Layer.unwrap(Config.unwrap(config).pipe(Effect.m
|
|
|
708
707
|
//#endregion
|
|
709
708
|
export { DeviceSchema as a, LandingPageSchema as c, TrafficSourceSchema as d, UserAcquisitionSchema as f, DailyOverviewSchema as i, LocationSchema as l, auth_exports as m, connector_exports as n, EcommerceItemSchema as o, client_exports as p, layerConfig as r, EventSchema as s, GoogleAnalyticsConnector as t, PageSchema as u };
|
|
710
709
|
|
|
711
|
-
//# sourceMappingURL=src-
|
|
710
|
+
//# sourceMappingURL=src-Cvr4Q9Xs.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src-Cvr4Q9Xs.mjs","names":["make","layer","make","GoogleAnalyticsAuth.GoogleAnalyticsAuth","layer","GoogleAnalyticsAuth.layer","layerConfig","GoogleAnalyticsClient.GoogleAnalyticsClient","GoogleAnalyticsClient.layer"],"sources":["../src/client/auth.ts","../src/schemas/api.ts","../src/client/client.ts","../src/schemas/shared.ts","../src/resources/reports/read.ts","../src/resources/reports/sources.ts","../src/resources/reports/resource.ts","../src/schemas/reports.ts","../src/resources/reports/specs.ts","../src/connector.ts"],"sourcesContent":["import { Auth, ConnectorError, Telemetry } from \"@useairfoil/connector-kit\";\nimport { Clock, Context, Duration, Effect, Layer, Redacted, Schedule, Schema } from \"effect\";\nimport { HttpClient, HttpClientRequest, HttpClientResponse } from \"effect/http\";\nimport { createPrivateKey, sign } from \"node:crypto\";\n\nimport type { GoogleAnalyticsConfig } from \"../manifest\";\n\ntype GoogleAnalyticsAuthConfig = Pick<GoogleAnalyticsConfig, \"clientEmail\" | \"privateKey\">;\n\nexport type GoogleAnalyticsAuthService = Auth.TokenCache<ConnectorError>;\n\nexport class GoogleAnalyticsAuth extends Context.Service<\n GoogleAnalyticsAuth,\n GoogleAnalyticsAuthService\n>()(\"@useairfoil/producer-google-analytics/GoogleAnalyticsAuth\") {}\n\nexport const tokenUrl = \"https://oauth2.googleapis.com/token\";\nexport const scope = \"https://www.googleapis.com/auth/analytics.readonly\";\n\nconst TokenResponseSchema = Schema.Struct({\n access_token: Schema.String,\n expires_in: Schema.Number,\n});\n\nconst TokenErrorSchema = Schema.Struct({\n error: Schema.optional(Schema.String),\n error_description: Schema.optional(Schema.String),\n});\n\nconst base64Url = (value: string | Buffer): string => Buffer.from(value).toString(\"base64url\");\n\n/** Signs the service account JWT with RS256. Google allows at most 1 hour. */\nexport const makeAssertion = (options: {\n readonly clientEmail: string;\n readonly privateKey: string;\n readonly nowMillis: number;\n}): Effect.Effect<string, ConnectorError> =>\n Effect.try({\n try: () => {\n const now = Math.floor(options.nowMillis / 1000);\n const header = base64Url(JSON.stringify({ alg: \"RS256\", typ: \"JWT\" }));\n const claims = base64Url(\n JSON.stringify({\n iss: options.clientEmail,\n scope,\n aud: tokenUrl,\n iat: now,\n exp: now + 3600,\n }),\n );\n const signingInput = `${header}.${claims}`;\n // Env files often keep the PEM on one line with escaped newlines.\n const key = createPrivateKey(options.privateKey.replace(/\\\\n/g, \"\\n\"));\n return `${signingInput}.${base64Url(sign(\"sha256\", Buffer.from(signingInput), key))}`;\n },\n catch: (cause) =>\n new ConnectorError({ message: \"Invalid Google service account private key\", cause }),\n });\n\nconst tokenError = (response: HttpClientResponse.HttpClientResponse) =>\n HttpClientResponse.schemaBodyJson(TokenErrorSchema)(response).pipe(\n Effect.map((body) => body.error),\n Effect.orElseSucceed(() => undefined),\n Effect.flatMap((error) =>\n Effect.fail(\n new ConnectorError({\n message:\n error === \"invalid_grant\" || error === \"invalid_client\"\n ? \"Google rejected the service account key\"\n : `Google returned ${response.status} for the OAuth token`,\n }),\n ),\n ),\n );\n\nexport const make = Effect.fnUntraced(function* (config: GoogleAnalyticsAuthConfig) {\n const client = (yield* HttpClient.HttpClient).pipe(\n HttpClient.retryTransient({\n schedule: Schedule.exponential(\"250 millis\").pipe(Schedule.jittered),\n times: 3,\n }),\n );\n\n return yield* Auth.makeTokenCache({\n acquire: Effect.gen(function* () {\n const assertion = yield* makeAssertion({\n clientEmail: config.clientEmail,\n privateKey: Redacted.value(config.privateKey),\n nowMillis: yield* Clock.currentTimeMillis,\n });\n const response = yield* client\n .execute(\n HttpClientRequest.post(tokenUrl).pipe(\n HttpClientRequest.acceptJson,\n HttpClientRequest.bodyUrlParams({\n grant_type: \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n assertion,\n }),\n ),\n )\n .pipe(\n Effect.mapError(\n (cause) => new ConnectorError({ message: \"Google OAuth token request failed\", cause }),\n ),\n );\n const body = yield* HttpClientResponse.matchStatus(response, {\n \"2xx\": (ok) =>\n HttpClientResponse.schemaBodyJson(TokenResponseSchema)(ok).pipe(\n Effect.mapError(\n (cause) =>\n new ConnectorError({ message: \"Invalid Google OAuth token response\", cause }),\n ),\n ),\n orElse: tokenError,\n });\n return {\n value: Redacted.make(body.access_token),\n expiresIn: Duration.seconds(body.expires_in),\n };\n }).pipe(\n Effect.tapError((error) => Telemetry.annotateError(\"api_auth\", error)),\n Effect.withSpan(Telemetry.SpanName.apiFetch, {\n kind: \"client\",\n attributes: { [Telemetry.Attr.apiPath]: \"/token\" },\n }),\n ),\n });\n});\n\nexport const layer = (\n config: GoogleAnalyticsAuthConfig,\n): Layer.Layer<GoogleAnalyticsAuth, never, HttpClient.HttpClient> =>\n Layer.effect(GoogleAnalyticsAuth)(make(config));\n","import { Schema } from \"effect\";\n\nconst ValueSchema = Schema.Struct({ value: Schema.String });\n\n/** `POST properties/{id}:runReport`. Every value comes as a string. */\nexport const RunReportResponseSchema = Schema.Struct({\n dimensionHeaders: Schema.Array(Schema.Struct({ name: Schema.String })),\n metricHeaders: Schema.Array(Schema.Struct({ name: Schema.String })),\n rows: Schema.optional(\n Schema.Array(\n Schema.Struct({\n dimensionValues: Schema.Array(ValueSchema),\n metricValues: Schema.Array(ValueSchema),\n }),\n ),\n ),\n // Left out when the report is empty.\n rowCount: Schema.optional(Schema.Int),\n metadata: Schema.Struct({\n timeZone: Schema.String,\n currencyCode: Schema.optional(Schema.String),\n dataLossFromOtherRow: Schema.optional(Schema.Boolean),\n subjectToThresholding: Schema.optional(Schema.Boolean),\n samplingMetadatas: Schema.optional(Schema.Array(Schema.Unknown)),\n dataTruncationReasons: Schema.optional(Schema.Array(Schema.Unknown)),\n }),\n});\n\nexport type RunReportResponse = Schema.Schema.Type<typeof RunReportResponseSchema>;\n\nexport const ErrorBodySchema = Schema.Struct({\n error: Schema.Struct({\n code: Schema.optional(Schema.Int),\n message: Schema.optional(Schema.String),\n status: Schema.optional(Schema.String),\n }),\n});\n","import { ConnectorError, Metrics, Telemetry } from \"@useairfoil/connector-kit\";\nimport {\n Config,\n Context,\n Data,\n DateTime,\n Duration,\n Effect,\n Exit,\n Layer,\n Redacted,\n Schedule,\n Semaphore,\n} from \"effect\";\nimport { HttpClient, HttpClientRequest, HttpClientResponse } from \"effect/http\";\n\nimport { type GoogleAnalyticsConfig, manifest } from \"../manifest\";\nimport { ErrorBodySchema, type RunReportResponse, RunReportResponseSchema } from \"../schemas/api\";\nimport * as GoogleAnalyticsAuth from \"./auth\";\n\nexport const DATA_API_VERSION = \"v1beta\";\n\nconst baseUrl = `https://analyticsdata.googleapis.com/${DATA_API_VERSION}`;\nconst maxRetries = 5;\nconst retryBaseDelay = Duration.seconds(1);\nconst requestTimeout = Duration.minutes(2);\n// Token quotas refill within the hour, or at midnight Pacific for the daily one.\n// Keep waiting; a failed source stays stopped until restart.\nconst limitRetry = Schedule.min([Schedule.exponential(\"1 minute\"), Schedule.spaced(\"10 minutes\")]);\n// A standard property allows 10 requests at once. Leave room for other tools.\nconst maxConcurrent = 5;\n\nexport type ReportRequest = {\n readonly dimensions: ReadonlyArray<string>;\n readonly metrics: ReadonlyArray<string>;\n /** `YYYY-MM-DD` in the property's time zone. */\n readonly startDate: string;\n readonly endDate: string;\n readonly limit: number;\n readonly offset: number;\n};\n\nexport type GoogleAnalyticsClientService = {\n /** `name` labels the span and errors, such as the resource name. */\n readonly runReport: (\n name: string,\n request: ReportRequest,\n ) => Effect.Effect<RunReportResponse, ConnectorError>;\n /** The property's time zone. Report dates are days in this zone. */\n readonly timeZone: Effect.Effect<DateTime.TimeZone, ConnectorError>;\n};\n\nexport class GoogleAnalyticsClient extends Context.Service<\n GoogleAnalyticsClient,\n GoogleAnalyticsClientService\n>()(\"@useairfoil/producer-google-analytics/GoogleAnalyticsClient\") {}\n\nclass QuotaExhausted extends Data.TaggedError(\"QuotaExhausted\")<{ readonly message: string }> {}\n\nconst statusError = (response: HttpClientResponse.HttpClientResponse, name: string) =>\n HttpClientResponse.schemaBodyJson(ErrorBodySchema)(response).pipe(\n Effect.map((body) => body.error),\n Effect.orElseSucceed(() => undefined),\n Effect.flatMap(\n (error): Effect.Effect<never, ConnectorError | QuotaExhausted> =>\n response.status === 429\n ? Effect.fail(\n new QuotaExhausted({\n message: `Google Analytics quota used up for ${name}${error?.message ? `: ${error.message}` : \"\"}`,\n }),\n )\n : Effect.fail(\n new ConnectorError({\n message: `Google Analytics API returned ${response.status}${error?.status ? ` ${error.status}` : \"\"} for ${name}${error?.message ? `: ${error.message}` : \"\"}`,\n }),\n ),\n ),\n Effect.tapError((error) => Telemetry.annotateError(\"api_status\", error)),\n );\n\nexport const make = Effect.fnUntraced(function* (\n config: Pick<GoogleAnalyticsConfig, \"propertyId\">,\n) {\n const auth = yield* GoogleAnalyticsAuth.GoogleAnalyticsAuth;\n const semaphore = yield* Semaphore.make(maxConcurrent);\n const client = (yield* HttpClient.HttpClient).pipe(\n HttpClient.mapRequest(HttpClientRequest.acceptJson),\n HttpClient.mapRequestEffect((request) =>\n auth.get.pipe(\n Effect.map((token) => HttpClientRequest.bearerToken(request, Redacted.value(token))),\n ),\n ),\n // Short retries cover 5xx and the concurrent request limit. 5xx count\n // against a small hourly server error quota, so keep the cap.\n HttpClient.retryTransient({\n schedule: Metrics.retrySchedule({\n connector: manifest.name,\n baseDelay: retryBaseDelay,\n times: maxRetries,\n }),\n }),\n );\n const url = `${baseUrl}/properties/${config.propertyId}:runReport`;\n\n const runReport = (name: string, request: ReportRequest) =>\n Effect.gen(function* () {\n const response = yield* client\n .execute(\n HttpClientRequest.post(url).pipe(\n HttpClientRequest.bodyJsonUnsafe({\n dateRanges: [{ startDate: request.startDate, endDate: request.endDate }],\n dimensions: request.dimensions.map((dimension) => ({ name: dimension })),\n metrics: request.metrics.map((metric) => ({ name: metric })),\n // Sort by every dimension so offset pages stay stable.\n orderBys: request.dimensions.map((dimension) => ({\n dimension: { dimensionName: dimension },\n })),\n limit: request.limit,\n offset: request.offset,\n }),\n ),\n )\n .pipe(\n Effect.tapError((error) => Telemetry.annotateError(\"api_http\", error)),\n Effect.mapError(\n (error) =>\n new ConnectorError({\n message: `Google Analytics API request failed for ${name}`,\n cause: error,\n }),\n ),\n );\n return yield* HttpClientResponse.matchStatus(response, {\n \"2xx\": (ok) =>\n HttpClientResponse.schemaBodyJson(RunReportResponseSchema)(ok).pipe(\n Effect.tapError((error) => Telemetry.annotateError(\"api_decode\", error)),\n Effect.mapError(\n (error) =>\n new ConnectorError({\n message: `Google Analytics API response for ${name} does not match the schema`,\n cause: error,\n }),\n ),\n ),\n orElse: (failed) => statusError(failed, name),\n });\n }).pipe(\n Effect.timeoutOrElse({\n duration: requestTimeout,\n orElse: () =>\n Effect.fail(\n new ConnectorError({ message: `Google Analytics API request timed out for ${name}` }),\n ),\n }),\n // The timeout starts once a permit is free, so queue time doesn't count.\n semaphore.withPermits(1),\n Effect.tapErrorTag(\"QuotaExhausted\", (error) =>\n Effect.logWarning(`${error.message}. Waiting to try again.`),\n ),\n Effect.retry({\n schedule: limitRetry,\n while: (error) => error._tag === \"QuotaExhausted\",\n }),\n Effect.catchTag(\"QuotaExhausted\", (error) =>\n Effect.fail(new ConnectorError({ message: error.message })),\n ),\n Effect.withSpan(Telemetry.SpanName.apiFetch, {\n kind: \"client\",\n attributes: { [Telemetry.Attr.apiPath]: `runReport:${name}` },\n }),\n );\n\n // Every report carries the zone; one small report reads it. Failures aren't\n // kept, and the hour picks up a changed zone.\n const timeZone = yield* Effect.cachedWithTTL(\n runReport(\"time_zone\", {\n dimensions: [\"date\"],\n metrics: [\"sessions\"],\n startDate: \"yesterday\",\n endDate: \"yesterday\",\n limit: 1,\n offset: 0,\n }).pipe(\n Effect.flatMap((response) =>\n DateTime.zoneMakeNamedEffect(response.metadata.timeZone).pipe(\n Effect.mapError(\n (cause) =>\n new ConnectorError({\n message: `Unknown Google Analytics time zone ${response.metadata.timeZone}`,\n cause,\n }),\n ),\n ),\n ),\n ),\n (exit) => (Exit.isSuccess(exit) ? Duration.hours(1) : Duration.zero),\n );\n\n return GoogleAnalyticsClient.of({ runReport, timeZone });\n});\n\nexport const layer = (\n config: GoogleAnalyticsConfig,\n): Layer.Layer<GoogleAnalyticsClient, never, HttpClient.HttpClient> =>\n Layer.effect(GoogleAnalyticsClient)(make(config)).pipe(\n Layer.provide(GoogleAnalyticsAuth.layer(config)),\n );\n\nexport const layerConfig = (\n config: Config.Wrap<GoogleAnalyticsConfig>,\n): Layer.Layer<GoogleAnalyticsClient, Config.ConfigError, HttpClient.HttpClient> =>\n Layer.unwrap(Config.unwrap(config).pipe(Effect.map(layer)));\n","import { Schema, SchemaTransformation } from \"effect\";\n\n/** The API's `YYYYMMDD` as `YYYY-MM-DD`. */\nexport const isoDay = (date: string) => `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6)}`;\n\nconst ReportDate = Schema.String.check(Schema.isPattern(/^\\d{8}$/)).pipe(\n Schema.decodeTo(\n Schema.String,\n SchemaTransformation.transform({\n decode: isoDay,\n encode: (date) => date.replaceAll(\"-\", \"\"),\n }),\n ),\n);\n\n/** `TYPE_INTEGER` metrics, as an Iceberg long. */\nexport const count = (description: string) =>\n Schema.BigIntFromString.pipe(Schema.annotateKey({ description }));\n\n/** Float, currency, and seconds metrics, as an Iceberg double. */\nexport const amount = (description: string) =>\n Schema.FiniteFromString.pipe(Schema.annotateKey({ description }));\n\n/** Dimension values are never null. GA uses `(not set)` and `(other)` instead. */\nexport const dimension = (description: string) =>\n Schema.String.pipe(Schema.annotateKey({ description }));\n\n/** `version` is the fetch time, since report rows have no update time. */\nexport const common = {\n id: Schema.String.pipe(\n Schema.annotateKey({\n description: \"JSON array of the property ID, date, and dimension values.\",\n }),\n ),\n version: Schema.Date.pipe(\n Schema.annotateKey({ description: \"Time the row was read from Google Analytics.\" }),\n ),\n property_id: Schema.String.pipe(Schema.annotateKey({ description: \"GA4 property ID.\" })),\n date: ReportDate.pipe(\n Schema.annotateKey({ description: \"Day in the property's time zone, as YYYY-MM-DD.\" }),\n ),\n};\n\nexport const currencyCode = Schema.String.pipe(\n Schema.annotateKey({ description: \"Currency of the revenue fields, the property's currency.\" }),\n);\n","import { ConnectorError } from \"@useairfoil/connector-kit\";\nimport { DateTime, Effect, Option, Schema } from \"effect\";\n\nimport type { GoogleAnalyticsClientService } from \"../../client/client\";\nimport type { RunReportResponse } from \"../../schemas/api\";\nimport type { ReportSpec } from \"./spec\";\n\nimport { isoDay } from \"../../schemas/shared\";\n\n// The API returns at most 250,000 rows a request. Smaller pages keep memory low.\nconst pageSize = 100_000;\n\nexport type ReportPage<Row> = {\n readonly rows: ReadonlyArray<Row>;\n /** Offset of the next page of the same day. */\n readonly next: Option.Option<number>;\n};\n\ntype ReportRow = NonNullable<RunReportResponse[\"rows\"]>[number];\n\n/** `[column, apiName]` pairs, in request order. */\nconst pairs = (columns: Readonly<Record<string, string | undefined>>) =>\n Object.entries(columns).flatMap(([column, name]) =>\n name === undefined ? [] : [[column, name] as const],\n );\n\nconst valuesByName = (\n headers: ReadonlyArray<{ readonly name: string }>,\n values: ReadonlyArray<{ readonly value: string }>,\n) => new Map(headers.map((header, index) => [header.name, values[index]?.value]));\n\n/** Logs the response flags that mean some data is hidden or estimated. */\nconst warnDataLoss = (name: string, day: string, metadata: RunReportResponse[\"metadata\"]) => {\n const flags = {\n otherRow: metadata.dataLossFromOtherRow === true,\n thresholded: metadata.subjectToThresholding === true,\n sampled: (metadata.samplingMetadatas?.length ?? 0) > 0,\n truncated: (metadata.dataTruncationReasons?.length ?? 0) > 0,\n };\n return Object.values(flags).some(Boolean)\n ? Effect.logWarning(\"Google Analytics report is incomplete\").pipe(\n Effect.annotateLogs({ report: name, day, ...flags }),\n )\n : Effect.void;\n};\n\nexport const readDay = <Row extends object>(\n client: GoogleAnalyticsClientService,\n propertyId: string,\n spec: ReportSpec<Row>,\n options: { readonly day: string; readonly offset: number; readonly limit?: number },\n): Effect.Effect<ReportPage<Row>, ConnectorError> =>\n Effect.gen(function* () {\n const dimensions = pairs(spec.dimensions);\n const metrics = pairs(spec.metrics);\n const columns = [...dimensions, ...metrics];\n const response = yield* client.runReport(spec.name, {\n dimensions: [\"date\", ...dimensions.map(([, name]) => name)],\n metrics: metrics.map(([, name]) => name),\n startDate: options.day,\n endDate: options.day,\n limit: options.limit ?? pageSize,\n offset: options.offset,\n });\n yield* warnDataLoss(spec.name, options.day, response.metadata);\n const version = yield* DateTime.nowAsDate;\n const decodeRow = Schema.decodeUnknownEffect(spec.rowSchema);\n\n const toRow = (row: ReportRow) => {\n const values = new Map([\n ...valuesByName(response.dimensionHeaders, row.dimensionValues),\n ...valuesByName(response.metricHeaders, row.metricValues),\n ]);\n const date = values.get(\"date\") ?? \"\";\n return decodeRow({\n id: JSON.stringify([\n propertyId,\n isoDay(date),\n ...dimensions.map(([, name]) => values.get(name)),\n ]),\n version,\n property_id: propertyId,\n date,\n // Tables without revenue leave it out.\n currency_code: response.metadata.currencyCode,\n ...Object.fromEntries(columns.map(([column, name]) => [column, values.get(name)])),\n }).pipe(\n Effect.mapError(\n (cause) =>\n new ConnectorError({\n message: `Unexpected Google Analytics row in ${spec.name} for ${options.day}`,\n cause,\n }),\n ),\n );\n };\n\n const rows = yield* Effect.forEach(response.rows ?? [], toRow);\n const read = options.offset + rows.length;\n return {\n rows,\n next: rows.length > 0 && read < (response.rowCount ?? 0) ? Option.some(read) : Option.none(),\n };\n });\n","import { ConnectorError, Cursor, Fetch } from \"@useairfoil/connector-kit\";\nimport { DateTime, Duration, Effect, Option, Schema } from \"effect\";\n\nimport type { GoogleAnalyticsClientService } from \"../../client/client\";\nimport type { GoogleAnalyticsConfig } from \"../../manifest\";\nimport type { ReportSpec } from \"./spec\";\n\nimport { readDay } from \"./read\";\n\n// Daily data is ready about 12 hours after the day ends, so polling more often\n// mostly reads the same numbers.\nconst changesInterval = Duration.hours(6);\n\ntype ReportConfig = Pick<GoogleAnalyticsConfig, \"propertyId\" | \"startDate\" | \"lookbackDays\">;\n\nconst Day = Schema.String.check(Schema.isPattern(/^\\d{4}-\\d{2}-\\d{2}$/));\n\nconst addDays = (day: string, days: number) =>\n DateTime.formatIsoDateUtc(DateTime.add(DateTime.makeUnsafe(day), { days }));\n\n/** The calendar day of `time` in the property's time zone. */\nconst dayIn = (zone: DateTime.TimeZone, time: DateTime.DateTime) =>\n DateTime.formatIsoDate(DateTime.setZone(time, zone));\n\nconst minDay = (a: string, b: string) => (a < b ? a : b);\n\n/** Reports cover whole days, so backfill ends the day before the cutoff. */\nconst lastBackfillDay = (zone: DateTime.TimeZone, cutoff: DateTime.Utc) =>\n addDays(dayIn(zone, cutoff), -1);\n\n// The API rejects earlier days.\nconst firstDay = \"2015-08-14\";\n\nconst startDay = (config: ReportConfig, end: string) =>\n Option.match(config.startDate, {\n onNone: () =>\n Effect.succeed(\n DateTime.formatIsoDateUtc(DateTime.add(DateTime.makeUnsafe(end), { years: -2 })),\n ),\n onSome: (startDate) =>\n Schema.decodeUnknownEffect(Day)(startDate).pipe(\n Effect.mapError(\n (cause) => new ConnectorError({ message: \"GA_START_DATE must be YYYY-MM-DD\", cause }),\n ),\n ),\n }).pipe(Effect.map((day) => (day < firstDay ? firstDay : day)));\n\nconst cursorError = (cause: Schema.SchemaError) =>\n new ConnectorError({ message: \"Invalid Google Analytics cursor\", cause });\n\nconst PageCursorSchema = Schema.fromJsonString(Schema.Struct({ day: Day, offset: Schema.Int }));\n\n/**\n * `through` is the last day fully read. `pending` is set while a run is split:\n * the day and offset to go on from, and the last day of that run.\n */\nconst ChangesCursorSchema = Schema.Struct({\n through: Day,\n pending: Schema.optional(Schema.Struct({ day: Day, end: Day, offset: Schema.Int })),\n});\n\ntype ChangesCursor = Schema.Schema.Type<typeof ChangesCursorSchema>;\n\n// The first cursor is the backfill cutoff, a plain ISO time.\nconst ChangesCursorCodec = Schema.Union([\n Schema.fromJsonString(ChangesCursorSchema),\n Schema.DateTimeUtcFromString,\n]);\n\nexport const reportCheck = <Row extends object>(\n client: GoogleAnalyticsClientService,\n config: ReportConfig,\n spec: ReportSpec<Row>,\n) =>\n Effect.gen(function* () {\n const zone = yield* client.timeZone;\n const yesterday = addDays(dayIn(zone, yield* DateTime.now), -1);\n yield* startDay(config, yesterday);\n yield* readDay(client, config.propertyId, spec, { day: yesterday, offset: 0, limit: 1 });\n });\n\nexport const reportBackfill = <Row extends object>(\n client: GoogleAnalyticsClientService,\n config: ReportConfig,\n spec: ReportSpec<Row>,\n) =>\n Fetch.page({\n pageCursor: Cursor.string(),\n cutoff: Cursor.isoDateTime(),\n fetch: ({ pageCursor, cutoff }) =>\n Effect.gen(function* () {\n const zone = yield* client.timeZone;\n const cutoffTime = yield* Schema.decodeUnknownEffect(Schema.DateTimeUtcFromString)(\n String(cutoff),\n ).pipe(Effect.mapError(cursorError));\n const end = lastBackfillDay(zone, cutoffTime);\n const at =\n pageCursor === undefined\n ? { day: yield* startDay(config, end), offset: 0 }\n : yield* Schema.decodeUnknownEffect(PageCursorSchema)(String(pageCursor)).pipe(\n Effect.mapError(cursorError),\n );\n if (at.day > end) return { rows: [], hasMore: false };\n\n const page = yield* readDay(client, config.propertyId, spec, at);\n const next = Option.match(page.next, {\n onSome: (offset) => ({ day: at.day, offset }),\n onNone: () => ({ day: addDays(at.day, 1), offset: 0 }),\n });\n if (next.day > end) return { rows: page.rows, hasMore: false };\n return {\n rows: page.rows,\n hasMore: true,\n nextPageCursor: yield* Schema.encodeEffect(PageCursorSchema)(next).pipe(\n Effect.mapError(cursorError),\n ),\n };\n }),\n });\n\n/**\n * Reads the last `lookbackDays` again on every run, since Google keeps\n * updating recent days. After downtime it also reads the days it missed.\n */\nexport const reportChanges = <Row extends object>(\n client: GoogleAnalyticsClientService,\n config: ReportConfig,\n spec: ReportSpec<Row>,\n) =>\n Fetch.changes({\n cursor: Cursor.string(),\n interval: changesInterval,\n fetch: ({ cursor }) =>\n Effect.gen(function* () {\n const zone = yield* client.timeZone;\n const decoded = yield* Schema.decodeUnknownEffect(ChangesCursorCodec)(String(cursor)).pipe(\n Effect.mapError(cursorError),\n );\n const state: ChangesCursor = DateTime.isDateTime(decoded)\n ? { through: lastBackfillDay(zone, decoded) }\n : decoded;\n const yesterday = addDays(dayIn(zone, yield* DateTime.now), -1);\n const window = state.pending ?? {\n day: minDay(addDays(state.through, 1), addDays(yesterday, 1 - config.lookbackDays)),\n end: yesterday,\n offset: 0,\n };\n\n const page = yield* readDay(client, config.propertyId, spec, window);\n const pending = Option.match(page.next, {\n onSome: (offset) => Option.some({ ...window, offset }),\n onNone: () =>\n window.day < window.end\n ? Option.some({ ...window, day: addDays(window.day, 1), offset: 0 })\n : Option.none(),\n });\n const next: ChangesCursor = Option.match(pending, {\n onSome: (pending) => ({ through: state.through, pending }),\n onNone: () => ({ through: window.end }),\n });\n return {\n rows: page.rows,\n cursor: yield* Schema.encodeEffect(Schema.fromJsonString(ChangesCursorSchema))(next).pipe(\n Effect.mapError(cursorError),\n ),\n hasMore: Option.isSome(pending),\n };\n }),\n });\n","import type { ConnectorError, Identified } from \"@useairfoil/connector-kit\";\nimport type { Effect, Schema } from \"effect\";\n\nimport type { GoogleAnalyticsClientService } from \"../../client/client\";\nimport type { GoogleAnalyticsConfig } from \"../../manifest\";\nimport type { ReportSpec } from \"./spec\";\n\nimport { reportBackfill, reportChanges, reportCheck } from \"./sources\";\n\n/** What `Resource.entity` takes for a report. */\nexport type ReportEntity<Row extends object> = {\n readonly name: string;\n readonly rowSchema: Schema.Decoder<Row> & Identified<\"id\">;\n readonly version: \"version\";\n readonly check: Effect.Effect<void, ConnectorError>;\n readonly backfill: ReturnType<typeof reportBackfill<Row>>;\n readonly changes: ReturnType<typeof reportChanges<Row>>;\n};\n\nexport const reportEntity = <Row extends object>(\n client: GoogleAnalyticsClientService,\n config: GoogleAnalyticsConfig,\n spec: ReportSpec<Row>,\n): ReportEntity<Row> => ({\n name: spec.name,\n rowSchema: spec.rowSchema,\n version: \"version\",\n check: reportCheck(client, config, spec),\n backfill: reportBackfill(client, config, spec),\n changes: reportChanges(client, config, spec),\n});\n","import { Iceberg } from \"@useairfoil/connector-kit\";\nimport { Schema } from \"effect\";\n\nimport { amount, common, count, currencyCode, dimension } from \"./shared\";\n\n// User counts are distinct users, so they don't add up across rows or days.\n\nexport const DailyOverviewSchema = Schema.Struct({\n ...common,\n active_users: count(\"Users who engaged on the day.\"),\n new_users: count(\"Users who came for the first time.\"),\n total_users: count(\"Users who logged any event.\"),\n sessions: count(\"Sessions that started.\"),\n engaged_sessions: count(\n \"Sessions that lasted 10 seconds or more, or had a key event or 2 page views.\",\n ),\n screen_page_views: count(\"Page and screen views.\"),\n event_count: count(\"Events.\"),\n key_events: amount(\"Key events. Can be fractional after attribution.\"),\n total_revenue: amount(\"Revenue from purchases, subscriptions, and ads.\"),\n user_engagement_duration: amount(\"Seconds the site or app was in the foreground.\"),\n currency_code: currencyCode,\n})\n .annotate({ description: \"Google Analytics totals per day.\" })\n .pipe(Iceberg.identifier(\"id\"));\n\nexport type DailyOverview = Schema.Schema.Type<typeof DailyOverviewSchema>;\n\nexport const TrafficSourceSchema = Schema.Struct({\n ...common,\n session_source: dimension(\"Source of the session, such as google.\"),\n session_medium: dimension(\"Medium of the session, such as organic.\"),\n session_campaign_name: dimension(\"Campaign of the session.\"),\n session_default_channel_group: dimension(\"Default channel group of the session.\"),\n sessions: count(\"Sessions that started.\"),\n engaged_sessions: count(\"Engaged sessions.\"),\n active_users: count(\"Users who engaged.\"),\n new_users: count(\"Users who came for the first time.\"),\n screen_page_views: count(\"Page and screen views.\"),\n event_count: count(\"Events.\"),\n key_events: amount(\"Key events.\"),\n total_revenue: amount(\"Revenue from purchases, subscriptions, and ads.\"),\n user_engagement_duration: amount(\"Seconds the site or app was in the foreground.\"),\n currency_code: currencyCode,\n})\n .annotate({ description: \"Google Analytics sessions per day and traffic source.\" })\n .pipe(Iceberg.identifier(\"id\"));\n\nexport type TrafficSource = Schema.Schema.Type<typeof TrafficSourceSchema>;\n\nexport const UserAcquisitionSchema = Schema.Struct({\n ...common,\n first_user_source: dimension(\"Source that first brought the user.\"),\n first_user_medium: dimension(\"Medium that first brought the user.\"),\n first_user_campaign_name: dimension(\"Campaign that first brought the user.\"),\n first_user_default_channel_group: dimension(\"Default channel group that first brought the user.\"),\n new_users: count(\"Users who came for the first time.\"),\n active_users: count(\"Users who engaged.\"),\n sessions: count(\"Sessions that started.\"),\n engaged_sessions: count(\"Engaged sessions.\"),\n key_events: amount(\"Key events.\"),\n total_revenue: amount(\"Revenue from purchases, subscriptions, and ads.\"),\n currency_code: currencyCode,\n})\n .annotate({ description: \"Google Analytics users per day and first traffic source.\" })\n .pipe(Iceberg.identifier(\"id\"));\n\nexport type UserAcquisition = Schema.Schema.Type<typeof UserAcquisitionSchema>;\n\nexport const PageSchema = Schema.Struct({\n ...common,\n host_name: dimension(\"Host name, such as www.example.com.\"),\n page_path: dimension(\"Page path, without the query string.\"),\n page_title: dimension(\"Page title.\"),\n screen_page_views: count(\"Page and screen views.\"),\n active_users: count(\"Users who engaged.\"),\n event_count: count(\"Events.\"),\n key_events: amount(\"Key events.\"),\n user_engagement_duration: amount(\"Seconds the page was in the foreground.\"),\n})\n .annotate({ description: \"Google Analytics page views per day and page.\" })\n .pipe(Iceberg.identifier(\"id\"));\n\nexport type Page = Schema.Schema.Type<typeof PageSchema>;\n\nexport const LandingPageSchema = Schema.Struct({\n ...common,\n landing_page: dimension(\"First page path of the session, without the query string.\"),\n sessions: count(\"Sessions that started.\"),\n engaged_sessions: count(\"Engaged sessions.\"),\n active_users: count(\"Users who engaged.\"),\n new_users: count(\"Users who came for the first time.\"),\n key_events: amount(\"Key events.\"),\n total_revenue: amount(\"Revenue from purchases, subscriptions, and ads.\"),\n currency_code: currencyCode,\n})\n .annotate({ description: \"Google Analytics sessions per day and landing page.\" })\n .pipe(Iceberg.identifier(\"id\"));\n\nexport type LandingPage = Schema.Schema.Type<typeof LandingPageSchema>;\n\nexport const EventSchema = Schema.Struct({\n ...common,\n event_name: dimension(\"Event name, such as page_view.\"),\n event_count: count(\"Events.\"),\n total_users: count(\"Users who logged the event.\"),\n event_value: amount(\"Sum of the event's value parameter.\"),\n key_events: amount(\"Key events.\"),\n})\n .annotate({ description: \"Google Analytics events per day and event name.\" })\n .pipe(Iceberg.identifier(\"id\"));\n\nexport type Event = Schema.Schema.Type<typeof EventSchema>;\n\nexport const LocationSchema = Schema.Struct({\n ...common,\n country: dimension(\"Country of the user.\"),\n region: dimension(\"Region of the user.\"),\n city: dimension(\"City of the user.\"),\n active_users: count(\"Users who engaged.\"),\n new_users: count(\"Users who came for the first time.\"),\n sessions: count(\"Sessions that started.\"),\n engaged_sessions: count(\"Engaged sessions.\"),\n key_events: amount(\"Key events.\"),\n total_revenue: amount(\"Revenue from purchases, subscriptions, and ads.\"),\n currency_code: currencyCode,\n})\n .annotate({ description: \"Google Analytics users per day and location.\" })\n .pipe(Iceberg.identifier(\"id\"));\n\nexport type Location = Schema.Schema.Type<typeof LocationSchema>;\n\nexport const DeviceSchema = Schema.Struct({\n ...common,\n device_category: dimension(\"Device category: desktop, mobile, or tablet.\"),\n platform: dimension(\"Platform: web, iOS, or Android.\"),\n operating_system: dimension(\"Operating system.\"),\n browser: dimension(\"Browser.\"),\n active_users: count(\"Users who engaged.\"),\n new_users: count(\"Users who came for the first time.\"),\n sessions: count(\"Sessions that started.\"),\n engaged_sessions: count(\"Engaged sessions.\"),\n screen_page_views: count(\"Page and screen views.\"),\n key_events: amount(\"Key events.\"),\n})\n .annotate({ description: \"Google Analytics users per day and device.\" })\n .pipe(Iceberg.identifier(\"id\"));\n\nexport type Device = Schema.Schema.Type<typeof DeviceSchema>;\n\nexport const EcommerceItemSchema = Schema.Struct({\n ...common,\n item_id: dimension(\"Item ID.\"),\n item_name: dimension(\"Item name.\"),\n item_brand: dimension(\"Item brand.\"),\n item_category: dimension(\"First item category.\"),\n items_viewed: count(\"Units viewed.\"),\n items_added_to_cart: count(\"Units added to carts.\"),\n items_purchased: count(\"Units purchased.\"),\n item_revenue: amount(\"Revenue from the item, without tax and shipping.\"),\n currency_code: currencyCode,\n})\n .annotate({\n description: \"Google Analytics ecommerce items per day. Empty without ecommerce events.\",\n })\n .pipe(Iceberg.identifier(\"id\"));\n\nexport type EcommerceItem = Schema.Schema.Type<typeof EcommerceItemSchema>;\n","import type { ReportSpec } from \"./spec\";\n\nimport {\n type DailyOverview,\n DailyOverviewSchema,\n type Device,\n DeviceSchema,\n type EcommerceItem,\n EcommerceItemSchema,\n type Event,\n EventSchema,\n type LandingPage,\n LandingPageSchema,\n type Location,\n LocationSchema,\n type Page,\n PageSchema,\n type TrafficSource,\n TrafficSourceSchema,\n type UserAcquisition,\n UserAcquisitionSchema,\n} from \"../../schemas/reports\";\n\n// Every pair here is one Google shows together in its own reports. Check new\n// ones with `checkCompatibility` first.\n\nexport const dailyOverview: ReportSpec<DailyOverview> = {\n name: \"daily_overview\",\n dimensions: {},\n metrics: {\n active_users: \"activeUsers\",\n new_users: \"newUsers\",\n total_users: \"totalUsers\",\n sessions: \"sessions\",\n engaged_sessions: \"engagedSessions\",\n screen_page_views: \"screenPageViews\",\n event_count: \"eventCount\",\n key_events: \"keyEvents\",\n total_revenue: \"totalRevenue\",\n user_engagement_duration: \"userEngagementDuration\",\n },\n rowSchema: DailyOverviewSchema,\n};\n\nexport const trafficSources: ReportSpec<TrafficSource> = {\n name: \"traffic_sources\",\n dimensions: {\n session_source: \"sessionSource\",\n session_medium: \"sessionMedium\",\n session_campaign_name: \"sessionCampaignName\",\n session_default_channel_group: \"sessionDefaultChannelGroup\",\n },\n metrics: {\n sessions: \"sessions\",\n engaged_sessions: \"engagedSessions\",\n active_users: \"activeUsers\",\n new_users: \"newUsers\",\n screen_page_views: \"screenPageViews\",\n event_count: \"eventCount\",\n key_events: \"keyEvents\",\n total_revenue: \"totalRevenue\",\n user_engagement_duration: \"userEngagementDuration\",\n },\n rowSchema: TrafficSourceSchema,\n};\n\nexport const userAcquisition: ReportSpec<UserAcquisition> = {\n name: \"user_acquisition\",\n dimensions: {\n first_user_source: \"firstUserSource\",\n first_user_medium: \"firstUserMedium\",\n first_user_campaign_name: \"firstUserCampaignName\",\n first_user_default_channel_group: \"firstUserDefaultChannelGroup\",\n },\n metrics: {\n new_users: \"newUsers\",\n active_users: \"activeUsers\",\n sessions: \"sessions\",\n engaged_sessions: \"engagedSessions\",\n key_events: \"keyEvents\",\n total_revenue: \"totalRevenue\",\n },\n rowSchema: UserAcquisitionSchema,\n};\n\nexport const pages: ReportSpec<Page> = {\n name: \"pages\",\n dimensions: { host_name: \"hostName\", page_path: \"pagePath\", page_title: \"pageTitle\" },\n metrics: {\n screen_page_views: \"screenPageViews\",\n active_users: \"activeUsers\",\n event_count: \"eventCount\",\n key_events: \"keyEvents\",\n user_engagement_duration: \"userEngagementDuration\",\n },\n rowSchema: PageSchema,\n};\n\nexport const landingPages: ReportSpec<LandingPage> = {\n name: \"landing_pages\",\n dimensions: { landing_page: \"landingPage\" },\n metrics: {\n sessions: \"sessions\",\n engaged_sessions: \"engagedSessions\",\n active_users: \"activeUsers\",\n new_users: \"newUsers\",\n key_events: \"keyEvents\",\n total_revenue: \"totalRevenue\",\n },\n rowSchema: LandingPageSchema,\n};\n\nexport const events: ReportSpec<Event> = {\n name: \"events\",\n dimensions: { event_name: \"eventName\" },\n metrics: {\n event_count: \"eventCount\",\n total_users: \"totalUsers\",\n event_value: \"eventValue\",\n key_events: \"keyEvents\",\n },\n rowSchema: EventSchema,\n};\n\nexport const locations: ReportSpec<Location> = {\n name: \"locations\",\n dimensions: { country: \"country\", region: \"region\", city: \"city\" },\n metrics: {\n active_users: \"activeUsers\",\n new_users: \"newUsers\",\n sessions: \"sessions\",\n engaged_sessions: \"engagedSessions\",\n key_events: \"keyEvents\",\n total_revenue: \"totalRevenue\",\n },\n rowSchema: LocationSchema,\n};\n\nexport const devices: ReportSpec<Device> = {\n name: \"devices\",\n dimensions: {\n device_category: \"deviceCategory\",\n platform: \"platform\",\n operating_system: \"operatingSystem\",\n browser: \"browser\",\n },\n metrics: {\n active_users: \"activeUsers\",\n new_users: \"newUsers\",\n sessions: \"sessions\",\n engaged_sessions: \"engagedSessions\",\n screen_page_views: \"screenPageViews\",\n key_events: \"keyEvents\",\n },\n rowSchema: DeviceSchema,\n};\n\nexport const ecommerceItems: ReportSpec<EcommerceItem> = {\n name: \"ecommerce_items\",\n dimensions: {\n item_id: \"itemId\",\n item_name: \"itemName\",\n item_brand: \"itemBrand\",\n item_category: \"itemCategory\",\n },\n metrics: {\n items_viewed: \"itemsViewed\",\n items_added_to_cart: \"itemsAddedToCart\",\n items_purchased: \"itemsPurchased\",\n item_revenue: \"itemRevenue\",\n },\n rowSchema: EcommerceItemSchema,\n};\n","import { Connector, Resource } from \"@useairfoil/connector-kit\";\nimport { Config, Context, Effect, Layer } from \"effect\";\n\nimport type { GoogleAnalyticsConfig } from \"./manifest\";\n\nimport * as GoogleAnalyticsClient from \"./client/client\";\nimport { reportEntity } from \"./resources/reports/resource\";\nimport {\n dailyOverview,\n devices,\n ecommerceItems,\n events,\n landingPages,\n locations,\n pages,\n trafficSources,\n userAcquisition,\n} from \"./resources/reports/specs\";\nexport { GoogleAnalyticsConfigDef, manifest } from \"./manifest\";\nexport type { GoogleAnalyticsConfig } from \"./manifest\";\n\nexport const make = Effect.fnUntraced(function* (config: GoogleAnalyticsConfig) {\n const client = yield* GoogleAnalyticsClient.GoogleAnalyticsClient;\n\n return Connector.define({\n name: \"producer-google-analytics\",\n title: \"Google Analytics\",\n resources: [\n Resource.entity(reportEntity(client, config, dailyOverview)),\n Resource.entity(reportEntity(client, config, trafficSources)),\n Resource.entity(reportEntity(client, config, userAcquisition)),\n Resource.entity(reportEntity(client, config, pages)),\n Resource.entity(reportEntity(client, config, landingPages)),\n Resource.entity(reportEntity(client, config, events)),\n Resource.entity(reportEntity(client, config, locations)),\n Resource.entity(reportEntity(client, config, devices)),\n Resource.entity(reportEntity(client, config, ecommerceItems)),\n ],\n });\n});\n\nexport type GoogleAnalyticsConnectorRuntime = Effect.Success<ReturnType<typeof make>>;\n\nexport class GoogleAnalyticsConnector extends Context.Service<\n GoogleAnalyticsConnector,\n GoogleAnalyticsConnectorRuntime\n>()(\"@useairfoil/producer-google-analytics/GoogleAnalyticsConnector\") {}\n\nexport const layer = (config: GoogleAnalyticsConfig) =>\n Layer.effect(GoogleAnalyticsConnector)(\n make(config).pipe(Effect.annotateLogs({ component: \"google-analytics\" })),\n ).pipe(Layer.provide(GoogleAnalyticsClient.layer(config)));\n\nexport const layerConfig = (config: Config.Wrap<GoogleAnalyticsConfig>) =>\n Layer.unwrap(Config.unwrap(config).pipe(Effect.map(layer)));\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useairfoil/producer-google-analytics",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"repository": "useairfoil/connector-kit",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -22,6 +22,21 @@
|
|
|
22
22
|
"default": "./dist/manifest.mjs"
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@effect/platform-node": "^4.1.0",
|
|
27
|
+
"@effect/sql-pg": "^4.1.0",
|
|
28
|
+
"effect": "^4.1.0",
|
|
29
|
+
"@useairfoil/connector-kit": "0.7.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@dotenvx/dotenvx": "^1.62.0",
|
|
33
|
+
"@effect/vitest": "^4.1.0",
|
|
34
|
+
"@types/node": "^24.2.0",
|
|
35
|
+
"tsdown": "^0.23.0",
|
|
36
|
+
"tsx": "^4.21.0",
|
|
37
|
+
"vitest": "^5.0.3",
|
|
38
|
+
"@useairfoil/effect-vcr": "0.4.9"
|
|
39
|
+
},
|
|
25
40
|
"scripts": {
|
|
26
41
|
"build": "tsdown",
|
|
27
42
|
"docker:build": "docker build --load --file Dockerfile --tag airfoil/producer-google-analytics:local ../..",
|
|
@@ -31,20 +46,5 @@
|
|
|
31
46
|
"test": "dotenvx run --ignore=MISSING_ENV_FILE --quiet -- vitest",
|
|
32
47
|
"test:ci": "dotenvx run --ignore=MISSING_ENV_FILE --quiet -- vitest run",
|
|
33
48
|
"typecheck": "tsc --noEmit"
|
|
34
|
-
},
|
|
35
|
-
"dependencies": {
|
|
36
|
-
"@effect/platform-node": "^4.0.7",
|
|
37
|
-
"@effect/sql-pg": "^4.0.7",
|
|
38
|
-
"@useairfoil/connector-kit": "0.6.1",
|
|
39
|
-
"effect": "^4.0.7"
|
|
40
|
-
},
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"@dotenvx/dotenvx": "^1.62.0",
|
|
43
|
-
"@effect/vitest": "^4.0.7",
|
|
44
|
-
"@types/node": "^24.2.0",
|
|
45
|
-
"@useairfoil/effect-vcr": "0.4.8",
|
|
46
|
-
"tsdown": "^0.23.0",
|
|
47
|
-
"tsx": "^4.21.0",
|
|
48
|
-
"vitest": "^5.0.3"
|
|
49
49
|
}
|
|
50
|
-
}
|
|
50
|
+
}
|
package/src/create-table.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { ConnectorApp } from "@useairfoil/connector-kit";
|
|
1
|
+
import { ConnectorApp, RuntimeConfig } from "@useairfoil/connector-kit";
|
|
2
|
+
import { Command } from "effect/cli";
|
|
2
3
|
|
|
3
4
|
import { tableSchemas } from "./tables";
|
|
4
5
|
|
|
5
6
|
export const createTableCommand = ConnectorApp.makeCreateTableCommand(tableSchemas);
|
|
7
|
+
|
|
8
|
+
export const setupCommand = ConnectorApp.makeSetupCommand(tableSchemas).pipe(
|
|
9
|
+
Command.provide(RuntimeConfig.layerHosted()),
|
|
10
|
+
);
|
package/src/main.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "effect/cli";
|
|
|
6
6
|
import { FetchHttpClient } from "effect/http";
|
|
7
7
|
|
|
8
8
|
import packageJson from "../package.json";
|
|
9
|
-
import { createTableCommand } from "./create-table";
|
|
9
|
+
import { createTableCommand, setupCommand } from "./create-table";
|
|
10
10
|
import { demoCommand } from "./demo";
|
|
11
11
|
import { sandboxCommand } from "./sandbox";
|
|
12
12
|
import { startCommand } from "./start";
|
|
@@ -14,7 +14,13 @@ import { startCommand } from "./start";
|
|
|
14
14
|
const BootstrapLayer = Layer.mergeAll(FetchHttpClient.layer, NodeServices.layer);
|
|
15
15
|
|
|
16
16
|
const program = Command.make("producer-google-analytics", {}, () => Effect.void).pipe(
|
|
17
|
-
Command.withSubcommands([
|
|
17
|
+
Command.withSubcommands([
|
|
18
|
+
setupCommand,
|
|
19
|
+
startCommand,
|
|
20
|
+
sandboxCommand,
|
|
21
|
+
createTableCommand,
|
|
22
|
+
demoCommand,
|
|
23
|
+
]),
|
|
18
24
|
);
|
|
19
25
|
|
|
20
26
|
const cli = Command.run(program, { version: packageJson.version });
|