@useairfoil/producer-google-analytics 0.0.1 → 0.0.2

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/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { a as DeviceSchema, c as LandingPageSchema, d as TrafficSourceSchema, f as UserAcquisitionSchema, i as DailyOverviewSchema, l as LocationSchema, m as auth_exports, n as connector_exports, o as EcommerceItemSchema, p as client_exports, s as EventSchema, u as PageSchema } from "./src-Cu0LuOTi.mjs";
2
+ import { manifest } from "./manifest.mjs";
3
+ export { DailyOverviewSchema, DeviceSchema, EcommerceItemSchema, EventSchema, auth_exports as GoogleAnalyticsAuth, client_exports as GoogleAnalyticsClient, connector_exports as GoogleAnalyticsConnector, LandingPageSchema, LocationSchema, PageSchema, TrafficSourceSchema, UserAcquisitionSchema, manifest };
@@ -0,0 +1 @@
1
+ export {}
package/dist/main.mjs ADDED
@@ -0,0 +1,662 @@
1
+ #!/usr/bin/env node
2
+ import { a as DeviceSchema, c as LandingPageSchema, d as TrafficSourceSchema, f as UserAcquisitionSchema, i as DailyOverviewSchema, l as LocationSchema, o as EcommerceItemSchema, r as layerConfig, s as EventSchema, t as GoogleAnalyticsConnector, u as PageSchema } from "./src-Cu0LuOTi.mjs";
3
+ import { GoogleAnalyticsConfigDef } from "./manifest.mjs";
4
+ import { ConnectorApp, Demo, Ingestor, RuntimeConfig, StateStore, Telemetry } from "@useairfoil/connector-kit";
5
+ import { Config, DateTime, Duration, Effect, Layer, Logger, Schema } from "effect";
6
+ import { FetchHttpClient } from "effect/http";
7
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
8
+ import { Command } from "effect/cli";
9
+ import { PgClient } from "@effect/sql-pg";
10
+ //#region package.json
11
+ var version = "0.0.1";
12
+ //#endregion
13
+ //#region src/tables.ts
14
+ const tableSchemas = {
15
+ daily_overview: DailyOverviewSchema,
16
+ traffic_sources: TrafficSourceSchema,
17
+ user_acquisition: UserAcquisitionSchema,
18
+ pages: PageSchema,
19
+ landing_pages: LandingPageSchema,
20
+ events: EventSchema,
21
+ locations: LocationSchema,
22
+ devices: DeviceSchema,
23
+ ecommerce_items: EcommerceItemSchema
24
+ };
25
+ //#endregion
26
+ //#region src/create-table.ts
27
+ const createTableCommand = ConnectorApp.makeCreateTableCommand(tableSchemas);
28
+ //#endregion
29
+ //#region src/demo/shared.ts
30
+ const day = Duration.toMillis(Duration.days(1));
31
+ const propertyId = "123456789";
32
+ const host = "shop.acme.example.com";
33
+ const sources = [
34
+ [
35
+ "google",
36
+ "organic",
37
+ "(organic)",
38
+ "Organic Search",
39
+ 34
40
+ ],
41
+ [
42
+ "(direct)",
43
+ "(none)",
44
+ "(direct)",
45
+ "Direct",
46
+ 22
47
+ ],
48
+ [
49
+ "google",
50
+ "cpc",
51
+ "brand_search",
52
+ "Paid Search",
53
+ 14
54
+ ],
55
+ [
56
+ "facebook",
57
+ "paid_social",
58
+ "fall_sale",
59
+ "Paid Social",
60
+ 9
61
+ ],
62
+ [
63
+ "newsletter",
64
+ "email",
65
+ "weekly_digest",
66
+ "Email",
67
+ 8
68
+ ],
69
+ [
70
+ "bing",
71
+ "organic",
72
+ "(organic)",
73
+ "Organic Search",
74
+ 5
75
+ ],
76
+ [
77
+ "instagram.com",
78
+ "referral",
79
+ "(referral)",
80
+ "Organic Social",
81
+ 5
82
+ ],
83
+ [
84
+ "partner.example.com",
85
+ "referral",
86
+ "(referral)",
87
+ "Referral",
88
+ 3
89
+ ]
90
+ ];
91
+ const pages = [
92
+ [
93
+ "/",
94
+ "Acme - Everyday goods",
95
+ 30
96
+ ],
97
+ [
98
+ "/collections/new",
99
+ "New arrivals - Acme",
100
+ 14
101
+ ],
102
+ [
103
+ "/collections/sale",
104
+ "Sale - Acme",
105
+ 12
106
+ ],
107
+ [
108
+ "/products/classic-tee",
109
+ "Classic Tee - Acme",
110
+ 9
111
+ ],
112
+ [
113
+ "/products/everyday-hoodie",
114
+ "Everyday Hoodie - Acme",
115
+ 8
116
+ ],
117
+ [
118
+ "/products/water-bottle",
119
+ "Insulated Water Bottle - Acme",
120
+ 6
121
+ ],
122
+ [
123
+ "/cart",
124
+ "Your cart - Acme",
125
+ 8
126
+ ],
127
+ [
128
+ "/checkout",
129
+ "Checkout - Acme",
130
+ 5
131
+ ],
132
+ [
133
+ "/blog/how-we-source-cotton",
134
+ "How we source our cotton - Acme",
135
+ 4
136
+ ],
137
+ [
138
+ "/pages/shipping",
139
+ "Shipping - Acme",
140
+ 4
141
+ ]
142
+ ];
143
+ const locations = [
144
+ [
145
+ "United States",
146
+ "California",
147
+ "Los Angeles",
148
+ 14
149
+ ],
150
+ [
151
+ "United States",
152
+ "New York",
153
+ "New York",
154
+ 12
155
+ ],
156
+ [
157
+ "United States",
158
+ "Texas",
159
+ "Austin",
160
+ 7
161
+ ],
162
+ [
163
+ "United States",
164
+ "Illinois",
165
+ "Chicago",
166
+ 6
167
+ ],
168
+ [
169
+ "United Kingdom",
170
+ "England",
171
+ "London",
172
+ 9
173
+ ],
174
+ [
175
+ "Canada",
176
+ "Ontario",
177
+ "Toronto",
178
+ 6
179
+ ],
180
+ [
181
+ "Germany",
182
+ "Berlin",
183
+ "Berlin",
184
+ 5
185
+ ],
186
+ [
187
+ "Australia",
188
+ "New South Wales",
189
+ "Sydney",
190
+ 4
191
+ ],
192
+ [
193
+ "United States",
194
+ "(not set)",
195
+ "(not set)",
196
+ 3
197
+ ]
198
+ ];
199
+ const devices = [
200
+ [
201
+ "mobile",
202
+ "web",
203
+ "iOS",
204
+ "Safari",
205
+ 30
206
+ ],
207
+ [
208
+ "mobile",
209
+ "web",
210
+ "Android",
211
+ "Chrome",
212
+ 22
213
+ ],
214
+ [
215
+ "desktop",
216
+ "web",
217
+ "Windows",
218
+ "Chrome",
219
+ 18
220
+ ],
221
+ [
222
+ "desktop",
223
+ "web",
224
+ "Macintosh",
225
+ "Chrome",
226
+ 12
227
+ ],
228
+ [
229
+ "desktop",
230
+ "web",
231
+ "Macintosh",
232
+ "Safari",
233
+ 10
234
+ ],
235
+ [
236
+ "tablet",
237
+ "web",
238
+ "iOS",
239
+ "Safari",
240
+ 5
241
+ ],
242
+ [
243
+ "desktop",
244
+ "web",
245
+ "Windows",
246
+ "Edge",
247
+ 3
248
+ ]
249
+ ];
250
+ const items = [
251
+ [
252
+ "SKU-TEE",
253
+ "Classic Cotton Tee",
254
+ "Apparel",
255
+ 28
256
+ ],
257
+ [
258
+ "SKU-HOODIE",
259
+ "Everyday Hoodie",
260
+ "Apparel",
261
+ 64
262
+ ],
263
+ [
264
+ "SKU-SHIRT",
265
+ "Linen Button Shirt",
266
+ "Apparel",
267
+ 72
268
+ ],
269
+ [
270
+ "SKU-BEANIE",
271
+ "Wool Beanie",
272
+ "Accessories",
273
+ 26
274
+ ],
275
+ [
276
+ "SKU-TOTE",
277
+ "Canvas Tote Bag",
278
+ "Accessories",
279
+ 22
280
+ ],
281
+ [
282
+ "SKU-MUG",
283
+ "Stoneware Mug",
284
+ "Home",
285
+ 18
286
+ ],
287
+ [
288
+ "SKU-BOTTLE",
289
+ "Insulated Water Bottle",
290
+ "Home",
291
+ 32
292
+ ]
293
+ ];
294
+ /** Splits a total by weights into whole numbers that add up to it. */
295
+ const split = (total, weights) => {
296
+ const sum = weights.reduce((a, b) => a + b, 0);
297
+ const parts = weights.map((weight) => Math.floor(total * weight / sum));
298
+ parts[0] = (parts[0] ?? 0) + total - parts.reduce((a, b) => a + b, 0);
299
+ return parts;
300
+ };
301
+ const money = (value) => Math.round(value * 100) / 100;
302
+ //#endregion
303
+ //#region src/demo/reports.ts
304
+ /** One group is one day of reports, counting back from yesterday. */
305
+ const dayRows = (index, { faker, now }) => {
306
+ const date = DateTime.formatIsoDateUtc(DateTime.fromDateUnsafe(new Date(now.getTime() - (index + 1) * day)));
307
+ const id = (...dimensions) => JSON.stringify([
308
+ propertyId,
309
+ date,
310
+ ...dimensions
311
+ ]);
312
+ const base = {
313
+ version: now,
314
+ property_id: propertyId,
315
+ date
316
+ };
317
+ const weekday = (/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay();
318
+ const weekend = weekday === 0 || weekday === 6 ? .75 : 1;
319
+ const growth = Math.max(.4, 1 - index / 900);
320
+ const sessions = Math.round(4200 * weekend * growth * faker.number.float({
321
+ min: .85,
322
+ max: 1.15
323
+ }));
324
+ const engaged = Math.round(sessions * faker.number.float({
325
+ min: .55,
326
+ max: .68
327
+ }));
328
+ const activeUsers = Math.round(sessions * .78);
329
+ const newUsers = Math.round(activeUsers * faker.number.float({
330
+ min: .42,
331
+ max: .55
332
+ }));
333
+ const views = Math.round(sessions * faker.number.float({
334
+ min: 3.2,
335
+ max: 4.4
336
+ }));
337
+ const purchases = Math.round(sessions * faker.number.float({
338
+ min: .018,
339
+ max: .028
340
+ }));
341
+ const revenue = money(purchases * faker.number.float({
342
+ min: 58,
343
+ max: 74
344
+ }));
345
+ const engagement = money(engaged * faker.number.float({
346
+ min: 95,
347
+ max: 140
348
+ }));
349
+ const bySource = split(sessions, sources.map((source) => source[4]));
350
+ const byNewSource = split(newUsers, sources.map((source) => source[4]));
351
+ const purchasesBySource = split(purchases, sources.map((source) => source[4]));
352
+ const share = (part, whole) => whole === 0 ? 0 : part / whole;
353
+ const eventCounts = [
354
+ [
355
+ "page_view",
356
+ views,
357
+ activeUsers
358
+ ],
359
+ [
360
+ "session_start",
361
+ sessions,
362
+ activeUsers
363
+ ],
364
+ [
365
+ "first_visit",
366
+ newUsers,
367
+ newUsers
368
+ ],
369
+ [
370
+ "user_engagement",
371
+ engaged * 3,
372
+ Math.round(activeUsers * .7)
373
+ ],
374
+ [
375
+ "scroll",
376
+ Math.round(views * .45),
377
+ Math.round(activeUsers * .5)
378
+ ],
379
+ [
380
+ "view_item",
381
+ Math.round(views * .3),
382
+ Math.round(activeUsers * .4)
383
+ ],
384
+ [
385
+ "add_to_cart",
386
+ Math.round(sessions * .08),
387
+ Math.round(activeUsers * .07)
388
+ ],
389
+ [
390
+ "begin_checkout",
391
+ Math.round(sessions * .04),
392
+ Math.round(activeUsers * .035)
393
+ ],
394
+ [
395
+ "purchase",
396
+ purchases,
397
+ purchases
398
+ ]
399
+ ];
400
+ const itemUnits = split(purchases + Math.round(purchases * .4), items.map((item) => 10 - item[3] / 10));
401
+ const viewsByPage = split(views, pages.map((page) => page[2]));
402
+ const sessionsByLanding = split(sessions, [
403
+ 42,
404
+ 16,
405
+ 14,
406
+ 10,
407
+ 10,
408
+ 8
409
+ ]);
410
+ const sessionsByLocation = split(sessions, locations.map((location) => location[3]));
411
+ const sessionsByDevice = split(sessions, devices.map((device) => device[4]));
412
+ return {
413
+ daily_overview: [{
414
+ id: id(),
415
+ ...base,
416
+ active_users: BigInt(activeUsers),
417
+ new_users: BigInt(newUsers),
418
+ total_users: BigInt(Math.round(activeUsers * 1.08)),
419
+ sessions: BigInt(sessions),
420
+ engaged_sessions: BigInt(engaged),
421
+ screen_page_views: BigInt(views),
422
+ event_count: BigInt(eventCounts.reduce((sum, [, count]) => sum + count, 0)),
423
+ key_events: purchases,
424
+ total_revenue: revenue,
425
+ user_engagement_duration: engagement,
426
+ currency_code: "USD"
427
+ }],
428
+ traffic_sources: sources.map(([source, medium, campaign, channel], s) => {
429
+ const part = bySource[s] ?? 0;
430
+ return {
431
+ id: id(source, medium, campaign, channel),
432
+ ...base,
433
+ session_source: source,
434
+ session_medium: medium,
435
+ session_campaign_name: campaign,
436
+ session_default_channel_group: channel,
437
+ sessions: BigInt(part),
438
+ engaged_sessions: BigInt(Math.round(part * share(engaged, sessions))),
439
+ active_users: BigInt(Math.round(part * share(activeUsers, sessions))),
440
+ new_users: BigInt(Math.round(part * share(newUsers, sessions))),
441
+ screen_page_views: BigInt(Math.round(part * share(views, sessions))),
442
+ event_count: BigInt(Math.round(part * share(views, sessions) * 2.6)),
443
+ key_events: purchasesBySource[s] ?? 0,
444
+ total_revenue: money(revenue * share(purchasesBySource[s] ?? 0, purchases)),
445
+ user_engagement_duration: money(engagement * share(part, sessions)),
446
+ currency_code: "USD"
447
+ };
448
+ }),
449
+ user_acquisition: sources.map(([source, medium, campaign, channel], s) => ({
450
+ id: id(source, medium, campaign, channel),
451
+ ...base,
452
+ first_user_source: source,
453
+ first_user_medium: medium,
454
+ first_user_campaign_name: campaign,
455
+ first_user_default_channel_group: channel,
456
+ new_users: BigInt(byNewSource[s] ?? 0),
457
+ active_users: BigInt(Math.round((byNewSource[s] ?? 0) * 1.6)),
458
+ sessions: BigInt(Math.round((byNewSource[s] ?? 0) * 1.9)),
459
+ engaged_sessions: BigInt(Math.round((byNewSource[s] ?? 0) * 1.1)),
460
+ key_events: Math.round((purchasesBySource[s] ?? 0) * .4),
461
+ total_revenue: money(revenue * .4 * share(purchasesBySource[s] ?? 0, purchases)),
462
+ currency_code: "USD"
463
+ })),
464
+ pages: pages.map(([path, title, weight], p) => {
465
+ const part = viewsByPage[p] ?? 0;
466
+ return {
467
+ id: id(host, path, title),
468
+ ...base,
469
+ host_name: host,
470
+ page_path: path,
471
+ page_title: title,
472
+ screen_page_views: BigInt(part),
473
+ active_users: BigInt(Math.round(part * .62)),
474
+ event_count: BigInt(Math.round(part * 2.4)),
475
+ key_events: path === "/checkout" ? purchases : 0,
476
+ user_engagement_duration: money(engagement * (weight / 100))
477
+ };
478
+ }),
479
+ landing_pages: pages.slice(0, 6).map(([path], l) => {
480
+ const part = sessionsByLanding[l] ?? 0;
481
+ return {
482
+ id: id(path),
483
+ ...base,
484
+ landing_page: path,
485
+ sessions: BigInt(part),
486
+ engaged_sessions: BigInt(Math.round(part * share(engaged, sessions))),
487
+ active_users: BigInt(Math.round(part * .8)),
488
+ new_users: BigInt(Math.round(part * share(newUsers, sessions))),
489
+ key_events: Math.round(purchases * share(part, sessions)),
490
+ total_revenue: money(revenue * share(part, sessions)),
491
+ currency_code: "USD"
492
+ };
493
+ }),
494
+ events: eventCounts.map(([name, count, users]) => ({
495
+ id: id(name),
496
+ ...base,
497
+ event_name: name,
498
+ event_count: BigInt(count),
499
+ total_users: BigInt(users),
500
+ event_value: name === "purchase" ? revenue : 0,
501
+ key_events: name === "purchase" ? purchases : 0
502
+ })),
503
+ locations: locations.map(([country, region, city], l) => {
504
+ const part = sessionsByLocation[l] ?? 0;
505
+ return {
506
+ id: id(country, region, city),
507
+ ...base,
508
+ country,
509
+ region,
510
+ city,
511
+ active_users: BigInt(Math.round(part * .78)),
512
+ new_users: BigInt(Math.round(part * share(newUsers, sessions))),
513
+ sessions: BigInt(part),
514
+ engaged_sessions: BigInt(Math.round(part * share(engaged, sessions))),
515
+ key_events: Math.round(purchases * share(part, sessions)),
516
+ total_revenue: money(revenue * share(part, sessions)),
517
+ currency_code: "USD"
518
+ };
519
+ }),
520
+ devices: devices.map(([category, platform, os, browser], d) => {
521
+ const part = sessionsByDevice[d] ?? 0;
522
+ return {
523
+ id: id(category, platform, os, browser),
524
+ ...base,
525
+ device_category: category,
526
+ platform,
527
+ operating_system: os,
528
+ browser,
529
+ active_users: BigInt(Math.round(part * .78)),
530
+ new_users: BigInt(Math.round(part * share(newUsers, sessions))),
531
+ sessions: BigInt(part),
532
+ engaged_sessions: BigInt(Math.round(part * share(engaged, sessions))),
533
+ screen_page_views: BigInt(Math.round(part * share(views, sessions))),
534
+ key_events: Math.round(purchases * share(part, sessions))
535
+ };
536
+ }),
537
+ ecommerce_items: items.map(([itemId, name, category, price], i) => {
538
+ const bought = itemUnits[i] ?? 0;
539
+ const added = Math.round(bought * faker.number.float({
540
+ min: 3,
541
+ max: 5
542
+ }));
543
+ return {
544
+ id: id(itemId, name, "Acme", category),
545
+ ...base,
546
+ item_id: itemId,
547
+ item_name: name,
548
+ item_brand: "Acme",
549
+ item_category: category,
550
+ items_viewed: BigInt(Math.round(added * faker.number.float({
551
+ min: 6,
552
+ max: 9
553
+ }))),
554
+ items_added_to_cart: BigInt(added),
555
+ items_purchased: BigInt(bought),
556
+ item_revenue: money(bought * price),
557
+ currency_code: "USD"
558
+ };
559
+ })
560
+ };
561
+ };
562
+ //#endregion
563
+ //#region src/demo/index.ts
564
+ const demo = Demo.define({
565
+ name: "producer-google-analytics",
566
+ setup: {
567
+ tables: [],
568
+ rows: () => ({})
569
+ },
570
+ group: {
571
+ tables: [
572
+ Demo.table({
573
+ name: "daily_overview",
574
+ rowSchema: DailyOverviewSchema,
575
+ version: "version"
576
+ }),
577
+ Demo.table({
578
+ name: "traffic_sources",
579
+ rowSchema: TrafficSourceSchema,
580
+ version: "version"
581
+ }),
582
+ Demo.table({
583
+ name: "user_acquisition",
584
+ rowSchema: UserAcquisitionSchema,
585
+ version: "version"
586
+ }),
587
+ Demo.table({
588
+ name: "pages",
589
+ rowSchema: PageSchema,
590
+ version: "version"
591
+ }),
592
+ Demo.table({
593
+ name: "landing_pages",
594
+ rowSchema: LandingPageSchema,
595
+ version: "version"
596
+ }),
597
+ Demo.table({
598
+ name: "events",
599
+ rowSchema: EventSchema,
600
+ version: "version"
601
+ }),
602
+ Demo.table({
603
+ name: "locations",
604
+ rowSchema: LocationSchema,
605
+ version: "version"
606
+ }),
607
+ Demo.table({
608
+ name: "devices",
609
+ rowSchema: DeviceSchema,
610
+ version: "version"
611
+ }),
612
+ Demo.table({
613
+ name: "ecommerce_items",
614
+ rowSchema: EcommerceItemSchema,
615
+ version: "version"
616
+ })
617
+ ],
618
+ rows: dayRows,
619
+ count: 730
620
+ }
621
+ });
622
+ const demoCommand = ConnectorApp.makeDemoCommand(demo).pipe(Command.provide(RuntimeConfig.layerHosted()));
623
+ //#endregion
624
+ //#region src/sandbox.ts
625
+ const ConnectorLayer$1 = layerConfig(GoogleAnalyticsConfigDef.config);
626
+ const TelemetryLayer$1 = Layer.mergeAll(Telemetry.layerOtlp(), Telemetry.layerMetricsConsoleDump());
627
+ const sandboxCommand = Command.make("sandbox", {}, () => Effect.gen(function* () {
628
+ const port = yield* RuntimeConfig.httpPort;
629
+ const entrypoint = yield* GoogleAnalyticsConnector;
630
+ return yield* ConnectorApp.start(entrypoint, { port });
631
+ }).pipe(Effect.annotateLogs({ component: "google-analytics" }), Effect.provide(Layer.mergeAll(StateStore.layerMemory, Ingestor.layerConsole, ConnectorLayer$1, Logger.layer([Logger.consolePretty()]), TelemetryLayer$1)))).pipe(Command.withDescription("Run the connector locally and log ingested data"));
632
+ //#endregion
633
+ //#region src/start.ts
634
+ const ConnectorLayer = layerConfig(GoogleAnalyticsConfigDef.config);
635
+ const TelemetryLayer = Telemetry.layerOtlp();
636
+ const PostgresLayer = PgClient.layerConfig({
637
+ url: Config.schema(Schema.Redacted(Schema.NonEmptyString), RuntimeConfig.PlatformRuntimeKey.postgresConnectionString),
638
+ maxConnections: Config.succeed(2),
639
+ connectTimeout: Config.succeed("5 seconds"),
640
+ applicationName: Config.succeed("airfoil-producer-google-analytics")
641
+ });
642
+ const StateStoreLayer = StateStore.layerSql().pipe(Layer.provide(PostgresLayer));
643
+ const startCommand = Command.make("start", {}, () => Effect.gen(function* () {
644
+ const port = yield* RuntimeConfig.httpPort;
645
+ const entrypoint = yield* GoogleAnalyticsConnector;
646
+ return yield* ConnectorApp.start(entrypoint, { port }).pipe(Effect.provide(Ingestor.layerWingsConfig(entrypoint)));
647
+ }).pipe(Effect.annotateLogs({ component: "google-analytics" }), Effect.provide(Layer.mergeAll(StateStoreLayer, ConnectorLayer, Logger.layer([Logger.consolePretty()]), TelemetryLayer)), Effect.provide(RuntimeConfig.layerHosted()))).pipe(Command.withDescription("Run the production connector against Wings"));
648
+ //#endregion
649
+ //#region src/main.ts
650
+ const BootstrapLayer = Layer.mergeAll(FetchHttpClient.layer, NodeServices.layer);
651
+ const program = Command.make("producer-google-analytics", {}, () => Effect.void).pipe(Command.withSubcommands([
652
+ startCommand,
653
+ sandboxCommand,
654
+ createTableCommand,
655
+ demoCommand
656
+ ]));
657
+ const cli = Command.run(program, { version });
658
+ NodeRuntime.runMain(cli.pipe(Effect.provide(BootstrapLayer), Effect.scoped));
659
+ //#endregion
660
+ export {};
661
+
662
+ //# sourceMappingURL=main.mjs.map