@liiift-studio/sanity-visitor-insights 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.mjs CHANGED
@@ -86,19 +86,41 @@ ${detail}`);
86
86
 
87
87
  // src/core/cutover.ts
88
88
  var PREEXISTING = "preexisting";
89
+ function toHistory(cutover) {
90
+ if (cutover === null || cutover === void 0) return null;
91
+ if (typeof cutover === "string") return { from: cutover };
92
+ if (!cutover.from) return null;
93
+ return cutover;
94
+ }
95
+ function overlaps(aStart, aEnd, bStart, bEnd) {
96
+ return aStart <= bEnd && (aEnd === null || aEnd >= bStart);
97
+ }
89
98
  function coverageForRange(cutovers, eventName, range) {
90
99
  if (!(eventName in cutovers)) return { status: "none", reason: "unknown_event" };
91
- const cutover = cutovers[eventName];
92
- if (cutover === null || cutover === void 0) return { status: "none", reason: "not_instrumented" };
93
- if (cutover === PREEXISTING) return { status: "full" };
94
- const cutoverTime = Date.parse(cutover);
95
- if (Number.isNaN(cutoverTime)) return { status: "none", reason: "unknown_event" };
96
- if (Date.parse(range.end) < cutoverTime) return { status: "none", reason: "before_cutover", cutover };
97
- if (Date.parse(range.start) < cutoverTime) return { status: "partial", cutover };
100
+ const history = toHistory(cutovers[eventName]);
101
+ if (!history) return { status: "none", reason: "not_instrumented" };
102
+ let cutover;
103
+ let startsBeforeCutover = false;
104
+ if (history.from !== PREEXISTING) {
105
+ const cutoverTime = Date.parse(history.from);
106
+ if (Number.isNaN(cutoverTime)) return { status: "none", reason: "unknown_event" };
107
+ cutover = history.from;
108
+ if (Date.parse(range.end) < cutoverTime) return { status: "none", reason: "before_cutover", cutover };
109
+ startsBeforeCutover = Date.parse(range.start) < cutoverTime;
110
+ }
111
+ const hit = (history.outages ?? []).filter((o) => overlaps(o.start, o.until, range.start, range.end));
112
+ const swallowed = hit.find((o) => o.start <= range.start && (o.until === null || o.until > range.end));
113
+ if (swallowed) return { status: "none", reason: "outage", outages: [swallowed], cutover };
114
+ if (hit.length > 0) return { status: "partial", reason: "spans_outage", outages: hit, cutover };
115
+ if (startsBeforeCutover) return { status: "partial", reason: "spans_cutover", outages: [], cutover };
98
116
  return { status: "full" };
99
117
  }
100
118
  function applyCoverage(count, coverage) {
101
119
  if (coverage.status === "none") {
120
+ if (coverage.reason === "outage") {
121
+ const outage = coverage.outages?.[0];
122
+ return unavailable("outage", outage ? describeOutage(outage) : void 0);
123
+ }
102
124
  return unavailable(
103
125
  coverage.reason === "before_cutover" ? "before_cutover" : "not_instrumented",
104
126
  coverage.cutover ? `Instrumented from ${coverage.cutover}` : void 0
@@ -106,16 +128,31 @@ function applyCoverage(count, coverage) {
106
128
  }
107
129
  if (count === null) return unavailable("source_error");
108
130
  if (coverage.status === "partial") {
109
- return partial(count, coverage.cutover, `Only counted from ${coverage.cutover}, when this event was added`);
131
+ if (coverage.reason === "spans_outage") {
132
+ const outage = coverage.outages[0];
133
+ const from2 = outage?.until ?? outage?.start ?? coverage.cutover ?? "";
134
+ return partial(count, from2, `Undercounted: ${describeOutage(outage)}`);
135
+ }
136
+ const from = coverage.cutover ?? "";
137
+ return partial(count, from, `Only counted from ${from}, when this event was added`);
110
138
  }
111
139
  return { status: "ok", value: count };
112
140
  }
141
+ function describeOutage(outage) {
142
+ if (!outage) return "an outage affected part of this range";
143
+ const period = outage.until ? `${outage.start} to ${outage.until}` : `${outage.start} onwards`;
144
+ return outage.reason ? `not recorded ${period} (${outage.reason})` : `not recorded ${period}`;
145
+ }
113
146
  function coverageNotices(cutovers, eventNames, range) {
114
147
  const notices = [];
115
148
  for (const name of eventNames) {
116
149
  const coverage = coverageForRange(cutovers, name, range);
117
- if (coverage.status === "partial") {
150
+ if (coverage.status === "partial" && coverage.reason === "spans_outage") {
151
+ notices.push(`${name} was ${describeOutage(coverage.outages[0])}, so figures for this range are too low \u2014 not a real decline.`);
152
+ } else if (coverage.status === "partial") {
118
153
  notices.push(`${name} was instrumented on ${coverage.cutover}; figures before that date are missing, not zero.`);
154
+ } else if (coverage.status === "none" && coverage.reason === "outage") {
155
+ notices.push(`${name} was ${describeOutage(coverage.outages?.[0])}, covering this whole range. The figure is unavailable, not zero.`);
119
156
  } else if (coverage.status === "none" && coverage.reason === "not_instrumented") {
120
157
  notices.push(`${name} is not instrumented on this site, so it cannot be reported for any range.`);
121
158
  } else if (coverage.status === "none" && coverage.reason === "before_cutover") {
@@ -370,6 +407,12 @@ function sumFirstMetric(report) {
370
407
 
371
408
  // src/server/vercel.ts
372
409
  var API_BASE = "https://api.vercel.com/v1/query/web-analytics";
410
+ function granularityFor(start, end) {
411
+ const days = Math.round((Date.parse(`${end}T00:00:00Z`) - Date.parse(`${start}T00:00:00Z`)) / 864e5) + 1;
412
+ if (days <= 62) return "day";
413
+ if (days <= 26 * 7) return "week";
414
+ return "month";
415
+ }
373
416
  function createVercelClient(projectId, token, teamId) {
374
417
  async function query(path, extra) {
375
418
  const params = new URLSearchParams({ projectId, ...extra });
@@ -392,8 +435,11 @@ function createVercelClient(projectId, token, teamId) {
392
435
  query("visits/aggregate", {
393
436
  since: start,
394
437
  until: end,
395
- by: "day",
438
+ by: granularityFor(start, end),
396
439
  limit: "100"
440
+ }).catch((e) => {
441
+ console.error("Visitor insights: Vercel series unavailable:", e.message);
442
+ return { data: [] };
397
443
  })
398
444
  ]);
399
445
  const byDate = {};
@@ -1151,6 +1197,8 @@ export {
1151
1197
  createVercelClient,
1152
1198
  createVisitorInsightsHandler,
1153
1199
  daysBetween,
1200
+ describeOutage,
1201
+ eventNameFilter,
1154
1202
  formatInTimeZone,
1155
1203
  isReportName,
1156
1204
  isValidTimeZone,
@@ -1164,6 +1212,7 @@ export {
1164
1212
  resolveRange,
1165
1213
  runDiagnostics,
1166
1214
  shiftDays,
1215
+ sumFirstMetric,
1167
1216
  unavailable,
1168
1217
  validateSiteConfig,
1169
1218
  valueOrNull,