@squadbase/vite-server 0.1.17-dev.3b633bb → 0.1.17-dev.71a85cd

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 (77) hide show
  1. package/dist/cli/index.js +3310 -742
  2. package/dist/connectors/airtable-oauth.js +50 -8
  3. package/dist/connectors/airtable.js +46 -8
  4. package/dist/connectors/amplitude.js +10 -8
  5. package/dist/connectors/anthropic.js +4 -2
  6. package/dist/connectors/asana.js +39 -10
  7. package/dist/connectors/attio.js +32 -13
  8. package/dist/connectors/aws-billing.js +10 -8
  9. package/dist/connectors/azure-sql.js +33 -7
  10. package/dist/connectors/backlog-api-key.js +42 -15
  11. package/dist/connectors/clickup.js +52 -10
  12. package/dist/connectors/cosmosdb.js +14 -12
  13. package/dist/connectors/customerio.js +10 -8
  14. package/dist/connectors/dbt.js +688 -25
  15. package/dist/connectors/freshdesk.js +84 -8
  16. package/dist/connectors/freshsales.js +10 -8
  17. package/dist/connectors/freshservice.js +10 -8
  18. package/dist/connectors/gamma.js +17 -15
  19. package/dist/connectors/gemini.js +4 -2
  20. package/dist/connectors/github.js +14 -12
  21. package/dist/connectors/gmail-oauth.js +10 -10
  22. package/dist/connectors/gmail.js +6 -4
  23. package/dist/connectors/google-ads.js +10 -8
  24. package/dist/connectors/google-analytics-oauth.js +154 -25
  25. package/dist/connectors/google-analytics.js +536 -109
  26. package/dist/connectors/google-audit-log.js +6 -4
  27. package/dist/connectors/google-calendar-oauth.js +63 -15
  28. package/dist/connectors/google-calendar.js +63 -11
  29. package/dist/connectors/google-docs.js +10 -10
  30. package/dist/connectors/google-drive.js +32 -10
  31. package/dist/connectors/google-search-console-oauth.js +128 -17
  32. package/dist/connectors/google-sheets.js +8 -6
  33. package/dist/connectors/google-slides.js +10 -10
  34. package/dist/connectors/grafana.js +47 -10
  35. package/dist/connectors/hubspot-oauth.js +41 -9
  36. package/dist/connectors/hubspot.js +27 -9
  37. package/dist/connectors/influxdb.js +10 -8
  38. package/dist/connectors/intercom-oauth.js +72 -12
  39. package/dist/connectors/intercom.js +14 -12
  40. package/dist/connectors/jdbc.js +8 -6
  41. package/dist/connectors/jira-api-key.js +70 -11
  42. package/dist/connectors/kintone-api-token.js +68 -18
  43. package/dist/connectors/kintone.js +56 -11
  44. package/dist/connectors/linear.js +56 -12
  45. package/dist/connectors/linkedin-ads.js +43 -14
  46. package/dist/connectors/mailchimp-oauth.js +8 -6
  47. package/dist/connectors/mailchimp.js +8 -6
  48. package/dist/connectors/meta-ads-oauth.js +35 -14
  49. package/dist/connectors/meta-ads.js +37 -14
  50. package/dist/connectors/mixpanel.js +10 -8
  51. package/dist/connectors/monday.js +11 -9
  52. package/dist/connectors/mongodb.js +10 -8
  53. package/dist/connectors/notion-oauth.js +60 -11
  54. package/dist/connectors/notion.js +62 -11
  55. package/dist/connectors/openai.js +4 -2
  56. package/dist/connectors/oracle.js +25 -7
  57. package/dist/connectors/outlook-oauth.js +21 -21
  58. package/dist/connectors/powerbi-oauth.js +13 -13
  59. package/dist/connectors/salesforce.js +44 -9
  60. package/dist/connectors/semrush.js +8 -6
  61. package/dist/connectors/sentry.js +38 -10
  62. package/dist/connectors/shopify-oauth.js +43 -10
  63. package/dist/connectors/shopify.js +10 -8
  64. package/dist/connectors/sqlserver.js +33 -7
  65. package/dist/connectors/stripe-api-key.js +68 -15
  66. package/dist/connectors/stripe-oauth.js +70 -19
  67. package/dist/connectors/supabase.js +24 -5
  68. package/dist/connectors/tableau.js +17 -15
  69. package/dist/connectors/tiktok-ads.js +39 -16
  70. package/dist/connectors/wix-store.js +10 -8
  71. package/dist/connectors/zendesk-oauth.js +55 -12
  72. package/dist/connectors/zendesk.js +14 -12
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.js +3339 -746
  75. package/dist/main.js +3328 -740
  76. package/dist/vite-plugin.js +3308 -740
  77. package/package.json +1 -1
@@ -16,6 +16,7 @@ var init_parameter_definition = __esm({
16
16
  type;
17
17
  secret;
18
18
  required;
19
+ isDeprecated;
19
20
  constructor(config) {
20
21
  this.slug = config.slug;
21
22
  this.name = config.name;
@@ -24,6 +25,7 @@ var init_parameter_definition = __esm({
24
25
  this.type = config.type;
25
26
  this.secret = config.secret;
26
27
  this.required = config.required;
28
+ this.isDeprecated = config.isDeprecated ?? false;
27
29
  }
28
30
  /**
29
31
  * Get the parameter value from a ConnectorConnectionObject.
@@ -67,6 +69,16 @@ var parameters = {
67
69
  type: "base64EncodedJson",
68
70
  secret: true,
69
71
  required: true
72
+ }),
73
+ propertyId: new ParameterDefinition({
74
+ slug: "property-id",
75
+ name: "Google Analytics Property ID",
76
+ description: "The Google Analytics 4 property ID (e.g., 123456789). Automatically set during the setup flow.",
77
+ envVarBaseKey: "GA_PROPERTY_ID",
78
+ type: "text",
79
+ secret: false,
80
+ required: false,
81
+ isDeprecated: true
70
82
  })
71
83
  };
72
84
 
@@ -98,6 +110,7 @@ function buildJwt(clientEmail, privateKey, nowSec) {
98
110
  }
99
111
  function createClient(params) {
100
112
  const serviceAccountKeyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
113
+ const defaultPropertyId = params["property-id"] ?? "";
101
114
  if (!serviceAccountKeyJsonBase64) {
102
115
  throw new Error(
103
116
  `google-analytics: missing required parameters: ${parameters.serviceAccountKeyJsonBase64.slug}`
@@ -159,13 +172,18 @@ function createClient(params) {
159
172
  headers.set("Authorization", `Bearer ${accessToken}`);
160
173
  return fetch(url, { ...init, headers });
161
174
  },
162
- async runReport(propertyId, request) {
175
+ async runReport(request) {
176
+ const { propertyId: pid, ...body } = request;
177
+ const propertyId = pid || defaultPropertyId;
178
+ if (!propertyId) {
179
+ throw new Error("google-analytics: propertyId is required in runReport request");
180
+ }
163
181
  const response = await this.request(
164
182
  `properties/${propertyId}:runReport`,
165
183
  {
166
184
  method: "POST",
167
185
  headers: { "Content-Type": "application/json" },
168
- body: JSON.stringify(request)
186
+ body: JSON.stringify(body)
169
187
  }
170
188
  );
171
189
  if (!response.ok) {
@@ -180,7 +198,11 @@ function createClient(params) {
180
198
  rowCount: data.rowCount ?? 0
181
199
  };
182
200
  },
183
- async getMetadata(propertyId) {
201
+ async getMetadata(request) {
202
+ const propertyId = request.propertyId || defaultPropertyId;
203
+ if (!propertyId) {
204
+ throw new Error("google-analytics: propertyId is required in getMetadata request");
205
+ }
184
206
  const response = await this.request(
185
207
  `properties/${propertyId}/metadata`,
186
208
  { method: "GET" }
@@ -193,13 +215,18 @@ function createClient(params) {
193
215
  }
194
216
  return await response.json();
195
217
  },
196
- async runRealtimeReport(propertyId, request) {
218
+ async runRealtimeReport(request) {
219
+ const { propertyId: pid, ...body } = request;
220
+ const propertyId = pid || defaultPropertyId;
221
+ if (!propertyId) {
222
+ throw new Error("google-analytics: propertyId is required in runRealtimeReport request");
223
+ }
197
224
  const response = await this.request(
198
225
  `properties/${propertyId}:runRealtimeReport`,
199
226
  {
200
227
  method: "POST",
201
228
  headers: { "Content-Type": "application/json" },
202
- body: JSON.stringify(request)
229
+ body: JSON.stringify(body)
203
230
  }
204
231
  );
205
232
  if (!response.ok) {
@@ -323,7 +350,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
323
350
  /**
324
351
  * Create tools for connections that belong to this connector.
325
352
  * Filters connections by connectorKey internally.
326
- * Returns tools keyed as `${connectorKey}_${toolName}`.
353
+ * Returns tools keyed as `connector_${connectorKey}_${toolName}`.
327
354
  */
328
355
  createTools(connections, config, opts) {
329
356
  const myConnections = connections.filter(
@@ -333,7 +360,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
333
360
  for (const t of Object.values(this.tools)) {
334
361
  const tool = t.createTool(myConnections, config);
335
362
  const originalToModelOutput = tool.toModelOutput;
336
- result[`${this.connectorKey}_${t.name}`] = {
363
+ result[`connector_${this.connectorKey}_${t.name}`] = {
337
364
  ...tool,
338
365
  toModelOutput: async (options) => {
339
366
  if (!originalToModelOutput) {
@@ -463,10 +490,10 @@ var AUTH_TYPES = {
463
490
  // ../connectors/src/connectors/google-analytics/setup.ts
464
491
  var googleAnalyticsOnboarding = new ConnectorOnboarding({
465
492
  dataOverviewInstructions: {
466
- en: `1. Call google-analytics-service-account_request with GET properties/{propertyId}/metadata to list available dimensions and metrics
467
- 2. Call google-analytics-service-account_request with POST properties/{propertyId}:runReport using a small date range and basic metrics to verify data availability`,
468
- ja: `1. google-analytics-service-account_request \u3067 GET properties/{propertyId}/metadata \u3092\u547C\u3073\u51FA\u3057\u3001\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3\u3068\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7\u3092\u53D6\u5F97
469
- 2. google-analytics-service-account_request \u3067 POST properties/{propertyId}:runReport \u3092\u77ED\u3044\u671F\u9593\u3068\u57FA\u672C\u30E1\u30C8\u30EA\u30AF\u30B9\u3067\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u306E\u53EF\u7528\u6027\u3092\u78BA\u8A8D`
493
+ en: `1. Call connector_google-analytics-service-account_request with GET properties/{propertyId}/metadata to list available dimensions and metrics
494
+ 2. Call connector_google-analytics-service-account_request with POST properties/{propertyId}:runReport using a small date range and basic metrics to verify data availability`,
495
+ ja: `1. connector_google-analytics-service-account_request \u3067 GET properties/{propertyId}/metadata \u3092\u547C\u3073\u51FA\u3057\u3001\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3\u3068\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7\u3092\u53D6\u5F97
496
+ 2. connector_google-analytics-service-account_request \u3067 POST properties/{propertyId}:runReport \u3092\u77ED\u3044\u671F\u9593\u3068\u57FA\u672C\u30E1\u30C8\u30EA\u30AF\u30B9\u3067\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u306E\u53EF\u7528\u6027\u3092\u78BA\u8A8D`
470
497
  }
471
498
  });
472
499
 
@@ -574,7 +601,17 @@ async function dataFetch(params, path2, init) {
574
601
  // ../connectors/src/connectors/google-analytics/setup-flow.ts
575
602
  var ALL_PROPERTIES = "__ALL_PROPERTIES__";
576
603
  var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
577
- var METADATA_DISPLAY_LIMIT = 30;
604
+ var MAX_PROPERTIES_TO_EXPLORE = 2;
605
+ var TOP_N_SOURCES = 8;
606
+ var TOP_N_COUNTRIES = 8;
607
+ var TOP_N_BROWSERS = 5;
608
+ var TOP_N_PAGES = 10;
609
+ var TOP_N_EVENTS = 10;
610
+ var STANDARD_METADATA_LIMIT = 25;
611
+ var CUSTOM_METADATA_LIMIT = 15;
612
+ var DESCRIPTION_MAX = 140;
613
+ var PAGE_PATH_MAX = 80;
614
+ var EVENT_NAME_MAX = 60;
578
615
  async function listAccountSummaries(params) {
579
616
  const summaries = [];
580
617
  let pageToken;
@@ -597,9 +634,13 @@ function propertyIdFromResource(resource) {
597
634
  return (resource ?? "").replace(/^properties\//, "");
598
635
  }
599
636
  async function getProperty(params, propertyId) {
600
- const res = await adminFetch(params, `/properties/${propertyId}`);
601
- if (!res.ok) return null;
602
- return await res.json();
637
+ try {
638
+ const res = await adminFetch(params, `/properties/${propertyId}`);
639
+ if (!res.ok) return null;
640
+ return await res.json();
641
+ } catch {
642
+ return null;
643
+ }
603
644
  }
604
645
  async function getMetadata(params, propertyId) {
605
646
  try {
@@ -607,41 +648,387 @@ async function getMetadata(params, propertyId) {
607
648
  params,
608
649
  `/properties/${propertyId}/metadata`
609
650
  );
610
- if (!res.ok) return { dimensions: [], metrics: [] };
611
- const data = await res.json();
651
+ if (!res.ok) return null;
652
+ return await res.json();
653
+ } catch {
654
+ return null;
655
+ }
656
+ }
657
+ async function probePropertyAccess(params, propertyId) {
658
+ try {
659
+ const res = await dataFetch(
660
+ params,
661
+ `/properties/${propertyId}:runReport`,
662
+ {
663
+ method: "POST",
664
+ headers: { "Content-Type": "application/json" },
665
+ body: JSON.stringify({
666
+ dateRanges: [{ startDate: "7daysAgo", endDate: "today" }],
667
+ metrics: [{ name: "activeUsers" }],
668
+ limit: 1
669
+ })
670
+ }
671
+ );
672
+ if (res.ok) return { ok: true };
673
+ const body = await res.text().catch(() => res.statusText);
674
+ return { ok: false, status: res.status, body };
675
+ } catch (err) {
612
676
  return {
613
- dimensions: data.dimensions ?? [],
614
- metrics: data.metrics ?? []
677
+ ok: false,
678
+ status: "network",
679
+ body: err instanceof Error ? err.message : String(err)
615
680
  };
681
+ }
682
+ }
683
+ async function batchRunReports(params, propertyId, requests) {
684
+ try {
685
+ const res = await dataFetch(
686
+ params,
687
+ `/properties/${propertyId}:batchRunReports`,
688
+ {
689
+ method: "POST",
690
+ headers: { "Content-Type": "application/json" },
691
+ body: JSON.stringify({ requests })
692
+ }
693
+ );
694
+ if (!res.ok) return requests.map(() => ({}));
695
+ const data = await res.json();
696
+ const reports = data.reports ?? [];
697
+ return requests.map((_, i) => reports[i] ?? {});
616
698
  } catch {
617
- return { dimensions: [], metrics: [] };
699
+ return requests.map(() => ({}));
700
+ }
701
+ }
702
+ var LAST_30D = { startDate: "30daysAgo", endDate: "today" };
703
+ var PREV_30D = { startDate: "60daysAgo", endDate: "31daysAgo" };
704
+ var HEADLINE_METRICS = [
705
+ "activeUsers",
706
+ "newUsers",
707
+ "sessions",
708
+ "screenPageViews",
709
+ "engagementRate",
710
+ "averageSessionDuration",
711
+ "keyEvents",
712
+ "totalRevenue"
713
+ ];
714
+ function buildHeadlineRequest() {
715
+ return {
716
+ dateRanges: [
717
+ { ...LAST_30D, name: "current" },
718
+ { ...PREV_30D, name: "previous" }
719
+ ],
720
+ metrics: HEADLINE_METRICS.map((name) => ({ name }))
721
+ };
722
+ }
723
+ function buildTopDimensionRequest(dimension, metric, limit) {
724
+ return {
725
+ dateRanges: [LAST_30D],
726
+ dimensions: [{ name: dimension }],
727
+ metrics: [{ name: metric }],
728
+ limit,
729
+ orderBys: [{ desc: true, metric: { metricName: metric } }]
730
+ };
731
+ }
732
+ function buildTopPagesRequest() {
733
+ return {
734
+ dateRanges: [LAST_30D],
735
+ dimensions: [{ name: "pagePath" }],
736
+ metrics: [
737
+ { name: "screenPageViews" },
738
+ { name: "activeUsers" },
739
+ { name: "averageSessionDuration" }
740
+ ],
741
+ limit: TOP_N_PAGES,
742
+ orderBys: [{ desc: true, metric: { metricName: "screenPageViews" } }]
743
+ };
744
+ }
745
+ function buildTopEventsRequest() {
746
+ return {
747
+ dateRanges: [LAST_30D],
748
+ dimensions: [{ name: "eventName" }],
749
+ metrics: [{ name: "eventCount" }, { name: "totalUsers" }],
750
+ limit: TOP_N_EVENTS,
751
+ orderBys: [{ desc: true, metric: { metricName: "eventCount" } }]
752
+ };
753
+ }
754
+ function escapePipe(s) {
755
+ return s.replace(/\|/g, "\\|");
756
+ }
757
+ function truncate(s, max) {
758
+ if (!s) return "";
759
+ if (s.length <= max) return s;
760
+ return `${s.slice(0, Math.max(0, max - 1))}\u2026`;
761
+ }
762
+ function formatNumber(raw) {
763
+ if (raw == null || raw === "") return "-";
764
+ const n = Number(raw);
765
+ if (!Number.isFinite(n)) return raw;
766
+ if (Number.isInteger(n)) return n.toLocaleString("en-US");
767
+ return n.toLocaleString("en-US", { maximumFractionDigits: 2 });
768
+ }
769
+ function formatPercent(raw) {
770
+ if (raw == null || raw === "") return "-";
771
+ const n = Number(raw);
772
+ if (!Number.isFinite(n)) return raw;
773
+ return `${(n * 100).toFixed(1)}%`;
774
+ }
775
+ function formatDuration(rawSeconds) {
776
+ if (rawSeconds == null || rawSeconds === "") return "-";
777
+ const s = Math.round(Number(rawSeconds));
778
+ if (!Number.isFinite(s)) return rawSeconds;
779
+ if (s < 60) return `${s}s`;
780
+ const mins = Math.floor(s / 60);
781
+ const secs = s % 60;
782
+ return `${mins}m ${secs}s`;
783
+ }
784
+ function deltaArrow(curr, prev) {
785
+ if (!Number.isFinite(prev) || prev === 0) return "";
786
+ const pct = (curr - prev) / prev * 100;
787
+ if (Math.abs(pct) < 0.5) return " (\u2248 flat)";
788
+ const sign = pct > 0 ? "\u2191" : "\u2193";
789
+ return ` (${sign} ${Math.abs(pct).toFixed(1)}% vs prior 30d)`;
790
+ }
791
+ function pivotComparisonReport(report) {
792
+ const out = /* @__PURE__ */ new Map();
793
+ const headers = report.metricHeaders ?? [];
794
+ const rows = report.rows ?? [];
795
+ for (let i = 0; i < headers.length; i++) {
796
+ const name = headers[i]?.name;
797
+ if (!name) continue;
798
+ out.set(name, {
799
+ current: Number(rows[0]?.metricValues?.[i]?.value ?? "0"),
800
+ previous: Number(rows[1]?.metricValues?.[i]?.value ?? "0")
801
+ });
802
+ }
803
+ return out;
804
+ }
805
+ function renderHeadline(sections, report) {
806
+ const m = pivotComparisonReport(report);
807
+ if (m.size === 0) return;
808
+ sections.push("#### Headline metrics (last 30 days)", "");
809
+ const lines = [];
810
+ const push = (label, key, formatter) => {
811
+ const cell = m.get(key);
812
+ if (!cell) return;
813
+ const formatted = formatter(String(cell.current));
814
+ const delta = deltaArrow(cell.current, cell.previous);
815
+ lines.push([label, `${formatted}${delta}`]);
816
+ };
817
+ push("Active users", "activeUsers", formatNumber);
818
+ push("New users", "newUsers", formatNumber);
819
+ push("Sessions", "sessions", formatNumber);
820
+ push("Page / screen views", "screenPageViews", formatNumber);
821
+ push("Engagement rate", "engagementRate", formatPercent);
822
+ push("Avg session duration", "averageSessionDuration", formatDuration);
823
+ push("Key events (conversions)", "keyEvents", formatNumber);
824
+ const rev = m.get("totalRevenue");
825
+ if (rev && rev.current > 0) {
826
+ lines.push([
827
+ "Total revenue",
828
+ `${formatNumber(String(rev.current))}${deltaArrow(rev.current, rev.previous)}`
829
+ ]);
830
+ }
831
+ for (const [k, v] of lines) sections.push(`- **${k}**: ${v}`);
832
+ sections.push("");
833
+ }
834
+ function renderBreakdown(sections, title, blurb, report, topN, metricFormatter = formatNumber) {
835
+ const rows = report.rows ?? [];
836
+ if (rows.length === 0) return;
837
+ const dim = report.dimensionHeaders?.[0]?.name ?? "dimension";
838
+ const metricName = report.metricHeaders?.[0]?.name ?? "metric";
839
+ sections.push(`#### ${title}`, "", blurb, "");
840
+ for (const row of rows.slice(0, topN)) {
841
+ const label = row.dimensionValues?.[0]?.value || `(empty ${dim})`;
842
+ const value = metricFormatter(row.metricValues?.[0]?.value);
843
+ sections.push(`- ${label}: ${value} ${metricName}`);
844
+ }
845
+ sections.push("");
846
+ }
847
+ function renderTopPages(sections, report) {
848
+ const rows = report.rows ?? [];
849
+ if (rows.length === 0) return;
850
+ sections.push(
851
+ `#### Top pages by views (last 30 days)`,
852
+ "",
853
+ "_The most-visited pages \u2014 strong candidates for content-performance dashboards._",
854
+ ""
855
+ );
856
+ sections.push("| Path | Page views | Active users | Avg session duration |");
857
+ sections.push("|------|------------|--------------|----------------------|");
858
+ for (const row of rows.slice(0, TOP_N_PAGES)) {
859
+ const path2 = truncate(row.dimensionValues?.[0]?.value, PAGE_PATH_MAX) || "-";
860
+ sections.push(
861
+ `| ${escapePipe(path2)} | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
862
+ row.metricValues?.[1]?.value
863
+ )} | ${formatDuration(row.metricValues?.[2]?.value)} |`
864
+ );
865
+ }
866
+ sections.push("");
867
+ }
868
+ function renderTopEvents(sections, report) {
869
+ const rows = report.rows ?? [];
870
+ if (rows.length === 0) return;
871
+ sections.push(
872
+ `#### Top events (last 30 days)`,
873
+ "",
874
+ "_Standard auto-collected events (`page_view`, `session_start`, \u2026) plus any custom events the team has set up. The custom ones are usually the most interesting for funnel / conversion dashboards._",
875
+ ""
876
+ );
877
+ sections.push("| Event | Event count | Unique users |");
878
+ sections.push("|-------|-------------|--------------|");
879
+ for (const row of rows.slice(0, TOP_N_EVENTS)) {
880
+ const name = truncate(row.dimensionValues?.[0]?.value, EVENT_NAME_MAX) || "-";
881
+ sections.push(
882
+ `| \`${escapePipe(name)}\` | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
883
+ row.metricValues?.[1]?.value
884
+ )} |`
885
+ );
618
886
  }
887
+ sections.push("");
619
888
  }
620
- function appendMetadataSection(sections, dimensions, metrics) {
621
- if (dimensions.length > 0) {
622
- sections.push(`#### Dimensions (${dimensions.length})`, "");
623
- for (const d of dimensions.slice(0, METADATA_DISPLAY_LIMIT)) {
624
- sections.push(`- ${d.apiName ?? d.uiName ?? "(unknown)"}`);
889
+ function renderMetadata(sections, metadata) {
890
+ const dims = metadata.dimensions ?? [];
891
+ const mets = metadata.metrics ?? [];
892
+ if (dims.length === 0 && mets.length === 0) return;
893
+ const customDims = dims.filter((d) => d.customDefinition);
894
+ const customMets = mets.filter((m) => m.customDefinition);
895
+ const standardDims = dims.filter((d) => !d.customDefinition);
896
+ const standardMets = mets.filter((m) => !m.customDefinition);
897
+ sections.push("#### Available query surface", "");
898
+ sections.push(
899
+ "_Use these in custom `runReport` calls. Custom definitions reveal what the team specifically tracks beyond GA4's defaults._",
900
+ ""
901
+ );
902
+ if (customDims.length > 0 || customMets.length > 0) {
903
+ sections.push(`##### Custom definitions`, "");
904
+ for (const d of customDims.slice(0, CUSTOM_METADATA_LIMIT)) {
905
+ const desc = d.description ? ` \u2014 ${truncate(d.description, DESCRIPTION_MAX)}` : "";
906
+ sections.push(`- dimension \`${d.apiName ?? d.uiName}\`${desc}`);
625
907
  }
626
- if (dimensions.length > METADATA_DISPLAY_LIMIT) {
908
+ if (customDims.length > CUSTOM_METADATA_LIMIT) {
627
909
  sections.push(
628
- `- \u2026and ${dimensions.length - METADATA_DISPLAY_LIMIT} more`
910
+ `- _\u2026and ${customDims.length - CUSTOM_METADATA_LIMIT} more custom dimensions._`
629
911
  );
630
912
  }
631
- sections.push("");
632
- }
633
- if (metrics.length > 0) {
634
- sections.push(`#### Metrics (${metrics.length})`, "");
635
- for (const m of metrics.slice(0, METADATA_DISPLAY_LIMIT)) {
636
- sections.push(`- ${m.apiName ?? m.uiName ?? "(unknown)"}`);
913
+ for (const m of customMets.slice(0, CUSTOM_METADATA_LIMIT)) {
914
+ const desc = m.description ? ` \u2014 ${truncate(m.description, DESCRIPTION_MAX)}` : "";
915
+ sections.push(`- metric \`${m.apiName ?? m.uiName}\`${desc}`);
637
916
  }
638
- if (metrics.length > METADATA_DISPLAY_LIMIT) {
917
+ if (customMets.length > CUSTOM_METADATA_LIMIT) {
639
918
  sections.push(
640
- `- \u2026and ${metrics.length - METADATA_DISPLAY_LIMIT} more`
919
+ `- _\u2026and ${customMets.length - CUSTOM_METADATA_LIMIT} more custom metrics._`
641
920
  );
642
921
  }
643
922
  sections.push("");
644
923
  }
924
+ if (standardDims.length > 0) {
925
+ sections.push(`##### Standard dimensions (${standardDims.length})`);
926
+ const names = standardDims.slice(0, STANDARD_METADATA_LIMIT).map((d) => `\`${d.apiName ?? d.uiName}\``).join(", ");
927
+ const overflow = standardDims.length > STANDARD_METADATA_LIMIT ? ` _(+${standardDims.length - STANDARD_METADATA_LIMIT} more)_` : "";
928
+ sections.push(names + overflow, "");
929
+ }
930
+ if (standardMets.length > 0) {
931
+ sections.push(`##### Standard metrics (${standardMets.length})`);
932
+ const names = standardMets.slice(0, STANDARD_METADATA_LIMIT).map((m) => `\`${m.apiName ?? m.uiName}\``).join(", ");
933
+ const overflow = standardMets.length > STANDARD_METADATA_LIMIT ? ` _(+${standardMets.length - STANDARD_METADATA_LIMIT} more)_` : "";
934
+ sections.push(names + overflow, "");
935
+ }
936
+ }
937
+ function renderProbeFailure(sections, propertyId, probe, serviceAccountEmail) {
938
+ sections.push(`#### Connection probe failed`, "");
939
+ if (probe.status === 403) {
940
+ sections.push(
941
+ `_The service account could not access GA4 property \`${propertyId}\`._`,
942
+ "",
943
+ `**Fix**: In GA4 (Admin > Property access management), grant **Viewer** role to the service account below, then re-run setup.`,
944
+ ""
945
+ );
946
+ if (serviceAccountEmail) {
947
+ sections.push(`\`${serviceAccountEmail}\``, "");
948
+ }
949
+ } else if (probe.status === 404) {
950
+ sections.push(
951
+ `_GA4 property \`${propertyId}\` was not found. Double-check the Property ID in **Admin > Property Settings**._`,
952
+ ""
953
+ );
954
+ } else if (probe.status === "network") {
955
+ sections.push(
956
+ `_Could not reach the GA4 Data API: ${probe.body}_`,
957
+ ""
958
+ );
959
+ } else {
960
+ sections.push(
961
+ `_GA4 Data API returned ${probe.status}: ${probe.body.slice(0, 400)}_`,
962
+ ""
963
+ );
964
+ }
965
+ }
966
+ async function exploreProperty(sections, params, propertyId, serviceAccountEmail) {
967
+ const probe = await probePropertyAccess(params, propertyId);
968
+ if (!probe.ok) {
969
+ renderProbeFailure(sections, propertyId, probe, serviceAccountEmail);
970
+ return;
971
+ }
972
+ const batch1Requests = [
973
+ buildHeadlineRequest(),
974
+ buildTopDimensionRequest("sessionSourceMedium", "sessions", TOP_N_SOURCES),
975
+ buildTopDimensionRequest("country", "activeUsers", TOP_N_COUNTRIES),
976
+ buildTopDimensionRequest("deviceCategory", "sessions", 5),
977
+ buildTopDimensionRequest("browser", "activeUsers", TOP_N_BROWSERS)
978
+ ];
979
+ const batch2Requests = [buildTopPagesRequest(), buildTopEventsRequest()];
980
+ const [batch1, batch2, metadata] = await Promise.all([
981
+ batchRunReports(params, propertyId, batch1Requests),
982
+ batchRunReports(params, propertyId, batch2Requests),
983
+ getMetadata(params, propertyId)
984
+ ]);
985
+ const [headline, sourceMedium, country, device, browser] = batch1;
986
+ const [topPages, topEvents] = batch2;
987
+ if (headline) renderHeadline(sections, headline);
988
+ const hasTrafficSignal = (sourceMedium?.rows?.length ?? 0) + (country?.rows?.length ?? 0) + (device?.rows?.length ?? 0) + (browser?.rows?.length ?? 0) > 0;
989
+ if (hasTrafficSignal) {
990
+ sections.push(
991
+ "#### Traffic mix (last 30 days)",
992
+ "",
993
+ "_Where users are coming from \u2014 useful for dashboards that segment by acquisition channel, geography, or device._",
994
+ ""
995
+ );
996
+ if (sourceMedium)
997
+ renderBreakdown(
998
+ sections,
999
+ "Top source / medium",
1000
+ "_Acquisition channels driving sessions._",
1001
+ sourceMedium,
1002
+ TOP_N_SOURCES
1003
+ );
1004
+ if (country)
1005
+ renderBreakdown(
1006
+ sections,
1007
+ "Top countries by active users",
1008
+ "_Geography \u2014 informs which date/time filters and locale segments to expose._",
1009
+ country,
1010
+ TOP_N_COUNTRIES
1011
+ );
1012
+ if (device)
1013
+ renderBreakdown(
1014
+ sections,
1015
+ "Device categories",
1016
+ "_Desktop / mobile / tablet split \u2014 informs responsive cuts._",
1017
+ device,
1018
+ 5
1019
+ );
1020
+ if (browser)
1021
+ renderBreakdown(
1022
+ sections,
1023
+ "Top browsers",
1024
+ "_Browser distribution \u2014 informs compatibility dashboards or QA filters._",
1025
+ browser,
1026
+ TOP_N_BROWSERS
1027
+ );
1028
+ }
1029
+ if (topPages) renderTopPages(sections, topPages);
1030
+ if (topEvents) renderTopEvents(sections, topEvents);
1031
+ if (metadata) renderMetadata(sections, metadata);
645
1032
  }
646
1033
  var googleAnalyticsSetupFlow = {
647
1034
  initialState: () => ({}),
@@ -710,8 +1097,8 @@ var googleAnalyticsSetupFlow = {
710
1097
  slug: "manualPropertyId",
711
1098
  type: "text",
712
1099
  question: {
713
- ja: "GA4 \u30D7\u30ED\u30D1\u30C6\u30A3 ID \u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: 123456789\uFF09\u3002GA4 \u7BA1\u7406\u753B\u9762 > \u30D7\u30ED\u30D1\u30C6\u30A3\u8A2D\u5B9A\u3067\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002",
714
- en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings."
1100
+ ja: "GA4 \u30D7\u30ED\u30D1\u30C6\u30A3 ID \u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: 123456789\uFF09\u3002GA4 \u7BA1\u7406\u753B\u9762 > \u30D7\u30ED\u30D1\u30C6\u30A3\u8A2D\u5B9A\u3067\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002\u5165\u529B\u5F8C\u3001\u30C7\u30FC\u30BF\u53D6\u5F97\u3092\u8A66\u3057\u3066\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u63D0\u6848\u7528\u306E\u30B5\u30DE\u30EA\u3092\u751F\u6210\u3057\u307E\u3059\u3002",
1101
+ en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings. After entry we'll probe the property via the Data API and assemble a dashboard-ready summary."
715
1102
  },
716
1103
  async fetchOptions(state) {
717
1104
  if (state.properties?.length) return [];
@@ -725,85 +1112,107 @@ var googleAnalyticsSetupFlow = {
725
1112
  ],
726
1113
  async finalize(state, rt) {
727
1114
  const sections = ["## Google Analytics", ""];
1115
+ let serviceAccountEmail = null;
1116
+ try {
1117
+ const keyJsonBase64 = rt.params[parameters.serviceAccountKeyJsonBase64.slug];
1118
+ if (keyJsonBase64) {
1119
+ serviceAccountEmail = decodeServiceAccount(keyJsonBase64).client_email ?? null;
1120
+ }
1121
+ } catch {
1122
+ }
1123
+ let targetPropertyIds = [];
1124
+ let accountLabel = null;
1125
+ let accountSummary;
728
1126
  if (state.account && state.properties) {
729
- let summaries = [];
730
1127
  try {
731
- summaries = await listAccountSummaries(rt.params);
1128
+ const summaries = await listAccountSummaries(rt.params);
1129
+ accountSummary = summaries.find(
1130
+ (s) => (s.account ?? s.name) === state.account
1131
+ );
1132
+ accountLabel = accountSummary?.displayName ?? state.account.replace(/^accounts\//, "");
732
1133
  } catch {
1134
+ accountLabel = state.account.replace(/^accounts\//, "");
733
1135
  }
734
- const account = summaries.find(
735
- (s) => (s.account ?? s.name) === state.account
736
- );
737
- const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
738
- const targetPropertyIds = await resolveSetupSelection({
1136
+ targetPropertyIds = await resolveSetupSelection({
739
1137
  selected: state.properties,
740
1138
  allSentinel: ALL_PROPERTIES,
741
- fetchAll: async () => (account?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
1139
+ fetchAll: async () => (accountSummary?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
742
1140
  limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
743
1141
  });
744
- sections.push(`### Account: ${accountLabel}`, "");
745
- if (targetPropertyIds.length === 0) {
746
- sections.push("_No properties selected._", "");
747
- return sections.join("\n");
748
- }
749
- sections.push(
750
- "| Property ID | Display Name | Time Zone | Currency | Industry |"
751
- );
1142
+ } else if (state.manualPropertyId) {
1143
+ targetPropertyIds = [state.manualPropertyId];
1144
+ }
1145
+ if (targetPropertyIds.length === 0) {
752
1146
  sections.push(
753
- "|-------------|--------------|-----------|----------|----------|"
1147
+ "_Could not identify a target GA4 property. Either enable the Google Analytics Admin API on the service account's GCP project, or re-run setup and provide the Property ID manually._",
1148
+ ""
754
1149
  );
755
- for (const pid of targetPropertyIds) {
756
- const prop = await getProperty(rt.params, pid).catch(() => null);
757
- const displayName = (prop?.displayName ?? "-").replace(/\|/g, "\\|");
758
- const timeZone = prop?.timeZone ?? "-";
759
- const currency = prop?.currencyCode ?? "-";
760
- const industry = (prop?.industryCategory ?? "-").replace(
761
- /\|/g,
762
- "\\|"
763
- );
1150
+ if (serviceAccountEmail) {
764
1151
  sections.push(
765
- `| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
766
- );
767
- }
768
- sections.push("");
769
- const firstPropertyId = targetPropertyIds[0];
770
- if (firstPropertyId) {
771
- const { dimensions, metrics } = await getMetadata(
772
- rt.params,
773
- firstPropertyId
1152
+ `_Service account email (share your GA4 property with this address)_: \`${serviceAccountEmail}\``,
1153
+ ""
774
1154
  );
775
- appendMetadataSection(sections, dimensions, metrics);
776
1155
  }
777
1156
  return sections.join("\n");
778
1157
  }
779
- const propertyId = state.manualPropertyId;
780
- if (propertyId) {
781
- sections.push(`### Property: ${propertyId}`, "");
782
- const { dimensions, metrics } = await getMetadata(
783
- rt.params,
784
- propertyId
785
- );
786
- appendMetadataSection(sections, dimensions, metrics);
787
- return sections.join("\n");
788
- }
789
1158
  sections.push(
790
- "_Could not list GA4 accounts. Please enable the Google Analytics Admin API in your GCP project. Property ID can be specified per request at runtime._",
1159
+ "_The downstream coding agent should use this summary as the canonical baseline of the property's traffic shape for dashboard / app proposals. Headline metrics, traffic mix, top pages, top events, and custom dimensions/metrics together cover the most common dashboarding patterns; reach for the standard query surface for anything custom._",
791
1160
  ""
792
1161
  );
1162
+ if (accountLabel) sections.push(`### Account: ${accountLabel}`, "");
1163
+ sections.push("### Properties", "");
1164
+ sections.push(
1165
+ "| Property ID | Display Name | Time Zone | Currency | Industry |"
1166
+ );
1167
+ sections.push(
1168
+ "|-------------|--------------|-----------|----------|----------|"
1169
+ );
1170
+ const properties = await Promise.all(
1171
+ targetPropertyIds.map((id) => getProperty(rt.params, id))
1172
+ );
1173
+ for (let i = 0; i < targetPropertyIds.length; i++) {
1174
+ const pid = targetPropertyIds[i];
1175
+ const prop = properties[i];
1176
+ const displayName = escapePipe(prop?.displayName ?? "-");
1177
+ const timeZone = prop?.timeZone ?? "-";
1178
+ const currency = prop?.currencyCode ?? "-";
1179
+ const industry = escapePipe(prop?.industryCategory ?? "-");
1180
+ sections.push(
1181
+ `| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
1182
+ );
1183
+ }
1184
+ sections.push("");
1185
+ const propertiesToExplore = targetPropertyIds.slice(
1186
+ 0,
1187
+ MAX_PROPERTIES_TO_EXPLORE
1188
+ );
1189
+ for (let i = 0; i < propertiesToExplore.length; i++) {
1190
+ const pid = propertiesToExplore[i];
1191
+ const prop = properties[i];
1192
+ const heading = prop?.displayName && prop.displayName !== "-" ? `### Data exploration: ${prop.displayName} (${pid})` : `### Data exploration: property ${pid}`;
1193
+ sections.push(heading, "");
1194
+ await exploreProperty(sections, rt.params, pid, serviceAccountEmail);
1195
+ }
1196
+ if (targetPropertyIds.length > propertiesToExplore.length) {
1197
+ const skipped = targetPropertyIds.length - propertiesToExplore.length;
1198
+ sections.push(
1199
+ `_Deep exploration limited to the first ${propertiesToExplore.length} of ${targetPropertyIds.length} properties to keep this summary bounded. The remaining ${skipped} can be explored on demand via \`connector_google-analytics-service-account_request\`._`,
1200
+ ""
1201
+ );
1202
+ }
793
1203
  return sections.join("\n");
794
1204
  }
795
1205
  };
796
1206
 
797
1207
  // ../connectors/src/connectors/google-analytics/tools/request.ts
798
1208
  import { z } from "zod";
799
- var BASE_URL2 = "https://analyticsdata.googleapis.com/v1beta/";
1209
+ var BASE_URL2 = "https://analyticsdata.googleapis.com";
800
1210
  var REQUEST_TIMEOUT_MS = 6e4;
801
1211
  var inputSchema = z.object({
802
1212
  toolUseIntent: z.string().optional().describe("Brief description of what you intend to accomplish with this tool call"),
803
1213
  connectionId: z.string().describe("ID of the Google Analytics connection to use"),
804
- propertyId: z.string().describe("GA4 property ID (e.g., '123456789')"),
805
1214
  method: z.enum(["GET", "POST"]).describe("HTTP method"),
806
- path: z.string().describe("API path (e.g., 'properties/{propertyId}:runReport'). {propertyId} is replaced with the propertyId parameter."),
1215
+ path: z.string().describe("Full request path including the API version prefix, appended to https://analyticsdata.googleapis.com. Use the same form as the GA4 Data API reference, e.g., '/v1beta/properties/123456789:runReport'."),
807
1216
  body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
808
1217
  });
809
1218
  var outputSchema = z.discriminatedUnion("success", [
@@ -820,11 +1229,10 @@ var outputSchema = z.discriminatedUnion("success", [
820
1229
  var requestTool = new ConnectorTool({
821
1230
  name: "request",
822
1231
  description: `Send authenticated requests to the Google Analytics Data API.
823
- Authentication is handled automatically using a service account.
824
- {propertyId} in the path is automatically replaced with the propertyId parameter.`,
1232
+ Authentication is handled automatically using a service account.`,
825
1233
  inputSchema,
826
1234
  outputSchema,
827
- async execute({ connectionId, propertyId, method, path: path2, body }, connections) {
1235
+ async execute({ connectionId, method, path: path2, body }, connections) {
828
1236
  const connection2 = connections.find((c) => c.id === connectionId);
829
1237
  if (!connection2) {
830
1238
  return { success: false, error: `Connection ${connectionId} not found` };
@@ -844,8 +1252,7 @@ Authentication is handled automatically using a service account.
844
1252
  if (!token) {
845
1253
  return { success: false, error: "Failed to obtain access token" };
846
1254
  }
847
- const resolvedPath = path2.replace(/\{propertyId\}/g, propertyId);
848
- const url = `${BASE_URL2}${resolvedPath}`;
1255
+ const url = `${BASE_URL2}${path2}`;
849
1256
  const controller = new AbortController();
850
1257
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
851
1258
  try {
@@ -892,18 +1299,20 @@ var googleAnalyticsConnector = new ConnectorPlugin({
892
1299
  systemPrompt: {
893
1300
  en: `### Tools
894
1301
 
895
- - \`google-analytics-service-account_request\`: The only way to call the GA4 Data API. Use it to fetch metadata, run reports, or run realtime reports. Requires a \`propertyId\` parameter. See the GA4 Data API Reference below for available endpoints and request bodies.
1302
+ - \`connector_google-analytics-service-account_request\`: The only way to call the GA4 Data API. Use it to fetch metadata, run reports, or run realtime reports. See the GA4 Data API Reference below for available endpoints and request bodies.
896
1303
 
897
1304
  ### Business Logic
898
1305
 
899
1306
  The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
900
1307
 
901
1308
  SDK methods (client created via \`connection(connectionId)\`):
902
- - \`client.runReport(propertyId, request)\` \u2014 run a GA4 report
903
- - \`client.runRealtimeReport(propertyId, request)\` \u2014 run a realtime report
904
- - \`client.getMetadata(propertyId)\` \u2014 fetch available dimensions/metrics
1309
+ - \`client.runReport(request)\` \u2014 run a GA4 report
1310
+ - \`client.runRealtimeReport(request)\` \u2014 run a realtime report
1311
+ - \`client.getMetadata(request)\` \u2014 fetch available dimensions/metrics
905
1312
  - \`client.request(path, init?)\` \u2014 low-level authenticated fetch
906
1313
 
1314
+ **IMPORTANT**: You MUST always include \`propertyId\` in every SDK method call. The property ID is obtained during the setup flow. Without it, the request will fail.
1315
+
907
1316
  \`\`\`ts
908
1317
  import type { Context } from "hono";
909
1318
  import { connection } from "@squadbase/vite-server/connectors/google-analytics";
@@ -917,7 +1326,8 @@ export default async function handler(c: Context) {
917
1326
  endDate?: string;
918
1327
  }>();
919
1328
 
920
- const { rows } = await ga.runReport(propertyId, {
1329
+ const { rows } = await ga.runReport({
1330
+ propertyId,
921
1331
  dateRanges: [{ startDate, endDate }],
922
1332
  dimensions: [{ name: "date" }],
923
1333
  metrics: [{ name: "activeUsers" }, { name: "sessions" }],
@@ -938,8 +1348,8 @@ export default async function handler(c: Context) {
938
1348
 
939
1349
  Common operations:
940
1350
 
941
- - **Get metadata** (list available dimensions/metrics for a property): \`GET properties/{propertyId}/metadata\`
942
- - **Run report** (query analytics data): \`POST properties/{propertyId}:runReport\` with a body like:
1351
+ - **Get metadata** (list available dimensions/metrics for a property): \`GET /v1beta/properties/{propertyId}/metadata\`
1352
+ - **Run report** (query analytics data): \`POST /v1beta/properties/{propertyId}:runReport\` with a body like:
943
1353
  \`\`\`json
944
1354
  {
945
1355
  "dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
@@ -948,7 +1358,7 @@ Common operations:
948
1358
  "limit": 100
949
1359
  }
950
1360
  \`\`\`
951
- - **Run realtime report**: \`POST properties/{propertyId}:runRealtimeReport\`
1361
+ - **Run realtime report**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
952
1362
 
953
1363
  #### Common dimensions
954
1364
  date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
@@ -961,18 +1371,20 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
961
1371
  - Relative: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``,
962
1372
  ja: `### \u30C4\u30FC\u30EB
963
1373
 
964
- - \`google-analytics-service-account_request\`: GA4 Data API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\`propertyId\` \u30D1\u30E9\u30E1\u30FC\u30BF\u30FC\u304C\u5FC5\u8981\u3067\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306F\u4E0B\u90E8\u306E\u300CGA4 Data API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9\u300D\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
1374
+ - \`connector_google-analytics-service-account_request\`: GA4 Data API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306F\u4E0B\u90E8\u306E\u300CGA4 Data API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9\u300D\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
965
1375
 
966
1376
  ### Business Logic
967
1377
 
968
1378
  \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
969
1379
 
970
1380
  SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
971
- - \`client.runReport(propertyId, request)\` \u2014 GA4\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
972
- - \`client.runRealtimeReport(propertyId, request)\` \u2014 \u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
973
- - \`client.getMetadata(propertyId)\` \u2014 \u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u3092\u53D6\u5F97
1381
+ - \`client.runReport(request)\` \u2014 GA4\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
1382
+ - \`client.runRealtimeReport(request)\` \u2014 \u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
1383
+ - \`client.getMetadata(request)\` \u2014 \u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u3092\u53D6\u5F97
974
1384
  - \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304Dfetch
975
1385
 
1386
+ **\u91CD\u8981**: \u3059\u3079\u3066\u306ESDK\u30E1\u30BD\u30C3\u30C9\u547C\u3073\u51FA\u3057\u306B\u306F\u5FC5\u305A \`propertyId\` \u3092\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002\u30D7\u30ED\u30D1\u30C6\u30A3ID\u306F\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u30D5\u30ED\u30FC\u3067\u53D6\u5F97\u3055\u308C\u307E\u3059\u3002\u6307\u5B9A\u3057\u306A\u3044\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u5931\u6557\u3057\u307E\u3059\u3002
1387
+
976
1388
  \`\`\`ts
977
1389
  import type { Context } from "hono";
978
1390
  import { connection } from "@squadbase/vite-server/connectors/google-analytics";
@@ -986,7 +1398,8 @@ export default async function handler(c: Context) {
986
1398
  endDate?: string;
987
1399
  }>();
988
1400
 
989
- const { rows } = await ga.runReport(propertyId, {
1401
+ const { rows } = await ga.runReport({
1402
+ propertyId,
990
1403
  dateRanges: [{ startDate, endDate }],
991
1404
  dimensions: [{ name: "date" }],
992
1405
  metrics: [{ name: "activeUsers" }, { name: "sessions" }],
@@ -1007,8 +1420,8 @@ export default async function handler(c: Context) {
1007
1420
 
1008
1421
  \u4E3B\u306A\u64CD\u4F5C:
1009
1422
 
1010
- - **\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97** (\u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7): \`GET properties/{propertyId}/metadata\`
1011
- - **\u30EC\u30DD\u30FC\u30C8\u306E\u53D6\u5F97** (\u5206\u6790\u30C7\u30FC\u30BF\u306E\u30AF\u30A8\u30EA): \`POST properties/{propertyId}:runReport\`\u3001\u30DC\u30C7\u30A3\u4F8B:
1423
+ - **\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97** (\u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7): \`GET /v1beta/properties/{propertyId}/metadata\`
1424
+ - **\u30EC\u30DD\u30FC\u30C8\u306E\u53D6\u5F97** (\u5206\u6790\u30C7\u30FC\u30BF\u306E\u30AF\u30A8\u30EA): \`POST /v1beta/properties/{propertyId}:runReport\`\u3001\u30DC\u30C7\u30A3\u4F8B:
1012
1425
  \`\`\`json
1013
1426
  {
1014
1427
  "dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
@@ -1017,7 +1430,7 @@ export default async function handler(c: Context) {
1017
1430
  "limit": 100
1018
1431
  }
1019
1432
  \`\`\`
1020
- - **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST properties/{propertyId}:runRealtimeReport\`
1433
+ - **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
1021
1434
 
1022
1435
  #### \u4E3B\u8981\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3
1023
1436
  date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
@@ -1039,8 +1452,22 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
1039
1452
  error: "google-analytics: missing service account key"
1040
1453
  };
1041
1454
  }
1455
+ const propertyId = params[parameters.propertyId.slug];
1456
+ if (!propertyId) {
1457
+ try {
1458
+ const sa = decodeServiceAccount(keyJsonBase64);
1459
+ await getAccessToken(sa);
1460
+ return { success: true };
1461
+ } catch (err) {
1462
+ const msg = err instanceof Error ? err.message : String(err);
1463
+ return { success: false, error: msg };
1464
+ }
1465
+ }
1042
1466
  try {
1043
- const res = await dataFetch(params, `/metadata`);
1467
+ const res = await dataFetch(
1468
+ params,
1469
+ `/properties/${propertyId}/metadata`
1470
+ );
1044
1471
  if (!res.ok) {
1045
1472
  const body = await res.text().catch(() => res.statusText);
1046
1473
  return {