@beesolve/analytics-processor 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +23 -3
- package/dist/cdk.js +46 -0
- package/dist/dailyRollup/dailyRollup.mjs.map +2 -2
- package/dist/hourly/hourly.mjs +3 -3
- package/dist/hourly/hourly.mjs.map +3 -3
- package/dist/index.d.ts +273 -11
- package/dist/index.js +579 -67
- package/dist/monthlyRollup/monthlyRollup.mjs.map +2 -2
- package/dist/sitemap/bowser-UJEXXM6S.mjs +2 -0
- package/dist/sitemap/bowser-UJEXXM6S.mjs.map +7 -0
- package/dist/sitemap/chunk-3UGRDNDZ.mjs +6 -0
- package/dist/sitemap/chunk-3UGRDNDZ.mjs.map +7 -0
- package/dist/sitemap/chunk-BH7ZEBAB.mjs +2 -0
- package/dist/sitemap/chunk-BH7ZEBAB.mjs.map +7 -0
- package/dist/sitemap/chunk-CFZ5JWD3.mjs +2 -0
- package/dist/sitemap/chunk-CFZ5JWD3.mjs.map +7 -0
- package/dist/sitemap/chunk-KRS5YV2Z.mjs +2 -0
- package/dist/sitemap/chunk-KRS5YV2Z.mjs.map +7 -0
- package/dist/sitemap/chunk-LB33VBGK.mjs +2 -0
- package/dist/sitemap/chunk-LB33VBGK.mjs.map +7 -0
- package/dist/sitemap/chunk-OC3UN3OS.mjs +2 -0
- package/dist/sitemap/chunk-OC3UN3OS.mjs.map +7 -0
- package/dist/sitemap/chunk-PD2XGJQD.mjs +10 -0
- package/dist/sitemap/chunk-PD2XGJQD.mjs.map +7 -0
- package/dist/sitemap/chunk-QPD5NJHJ.mjs +2 -0
- package/dist/sitemap/chunk-QPD5NJHJ.mjs.map +7 -0
- package/dist/sitemap/chunk-REMPUQ6B.mjs +19 -0
- package/dist/sitemap/chunk-REMPUQ6B.mjs.map +7 -0
- package/dist/sitemap/chunk-SNCZ4GCT.mjs +2 -0
- package/dist/sitemap/chunk-SNCZ4GCT.mjs.map +7 -0
- package/dist/sitemap/chunk-TZ6TJSKE.mjs +10 -0
- package/dist/sitemap/chunk-TZ6TJSKE.mjs.map +7 -0
- package/dist/sitemap/chunk-YMGL4RSC.mjs +2 -0
- package/dist/sitemap/chunk-YMGL4RSC.mjs.map +7 -0
- package/dist/sitemap/chunk-YY2HYMK3.mjs +4 -0
- package/dist/sitemap/chunk-YY2HYMK3.mjs.map +7 -0
- package/dist/sitemap/client-WGCJRHWA.mjs +2 -0
- package/dist/sitemap/client-WGCJRHWA.mjs.map +7 -0
- package/dist/sitemap/dist-es-3FAKVHIH.mjs +2 -0
- package/dist/sitemap/dist-es-3FAKVHIH.mjs.map +7 -0
- package/dist/sitemap/dist-es-A36NQ5CO.mjs +2 -0
- package/dist/sitemap/dist-es-A36NQ5CO.mjs.map +7 -0
- package/dist/sitemap/dist-es-F7GXZJIC.mjs +3 -0
- package/dist/sitemap/dist-es-F7GXZJIC.mjs.map +7 -0
- package/dist/sitemap/dist-es-GRXF7RTK.mjs +2 -0
- package/dist/sitemap/dist-es-GRXF7RTK.mjs.map +7 -0
- package/dist/sitemap/dist-es-OKUHAI6O.mjs +2 -0
- package/dist/sitemap/dist-es-OKUHAI6O.mjs.map +7 -0
- package/dist/sitemap/dist-es-P7BXOYHO.mjs +2 -0
- package/dist/sitemap/dist-es-P7BXOYHO.mjs.map +7 -0
- package/dist/sitemap/dist-es-SQ53M5JX.mjs +7 -0
- package/dist/sitemap/dist-es-SQ53M5JX.mjs.map +7 -0
- package/dist/sitemap/dist-es-TCGA3UGX.mjs +2 -0
- package/dist/sitemap/dist-es-TCGA3UGX.mjs.map +7 -0
- package/dist/sitemap/dist-es-UXKF5GQY.mjs +3 -0
- package/dist/sitemap/dist-es-UXKF5GQY.mjs.map +7 -0
- package/dist/sitemap/dist-es-YJ4PQZC7.mjs +2 -0
- package/dist/sitemap/dist-es-YJ4PQZC7.mjs.map +7 -0
- package/dist/sitemap/event-streams-6V3Q6GHC.mjs +2 -0
- package/dist/sitemap/event-streams-6V3Q6GHC.mjs.map +7 -0
- package/dist/sitemap/loadCognitoIdentity-RIENBHOX.mjs +2 -0
- package/dist/sitemap/loadCognitoIdentity-RIENBHOX.mjs.map +7 -0
- package/dist/sitemap/loadSso-DTQ4R4CV.mjs +2 -0
- package/dist/sitemap/loadSso-DTQ4R4CV.mjs.map +7 -0
- package/dist/sitemap/loadSts-GZDTRQPT.mjs +2 -0
- package/dist/sitemap/loadSts-GZDTRQPT.mjs.map +7 -0
- package/dist/sitemap/signin-6D536OAO.mjs +2 -0
- package/dist/sitemap/signin-6D536OAO.mjs.map +7 -0
- package/dist/sitemap/sitemap.mjs +3 -0
- package/dist/sitemap/sitemap.mjs.map +7 -0
- package/dist/sitemap/sso-oidc-T5CK3NX3.mjs +2 -0
- package/dist/sitemap/sso-oidc-T5CK3NX3.mjs.map +7 -0
- package/dist/sitemap/sts-62ECLMPL.mjs +2 -0
- package/dist/sitemap/sts-62ECLMPL.mjs.map +7 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -328,7 +328,7 @@ async function waitForQuery({
|
|
|
328
328
|
delayMs = Math.min(delayMs * 2, maxDelayMs);
|
|
329
329
|
}
|
|
330
330
|
}
|
|
331
|
-
async function
|
|
331
|
+
async function fetchRawRows({
|
|
332
332
|
athena,
|
|
333
333
|
queryExecutionId
|
|
334
334
|
}) {
|
|
@@ -340,25 +340,20 @@ async function fetchRows({
|
|
|
340
340
|
const resultRows = results.ResultSet?.Rows ?? [];
|
|
341
341
|
const dataRows = isFirstPage ? resultRows.slice(1) : resultRows;
|
|
342
342
|
for (const dataRow of dataRows) {
|
|
343
|
-
|
|
344
|
-
rows.push(parseRow(cells));
|
|
343
|
+
rows.push((dataRow.Data ?? []).map((datum) => datum.VarCharValue));
|
|
345
344
|
}
|
|
346
345
|
nextToken = results.NextToken;
|
|
347
346
|
isFirstPage = false;
|
|
348
347
|
} while (nextToken != null);
|
|
349
348
|
return rows;
|
|
350
349
|
}
|
|
351
|
-
async function
|
|
350
|
+
async function startAndFetchRows({
|
|
352
351
|
athena,
|
|
353
352
|
database,
|
|
354
|
-
table,
|
|
355
353
|
workgroup,
|
|
356
354
|
outputLocation,
|
|
357
|
-
|
|
358
|
-
hourKey,
|
|
359
|
-
topPaths
|
|
355
|
+
queryString
|
|
360
356
|
}) {
|
|
361
|
-
const queryString = buildHourlyQuery({ table, source, hourKey, topPaths });
|
|
362
357
|
const started = await athena.send(new StartQueryExecutionCommand({
|
|
363
358
|
QueryString: queryString,
|
|
364
359
|
QueryExecutionContext: { Database: database },
|
|
@@ -370,7 +365,27 @@ async function runHourlyAthenaQuery({
|
|
|
370
365
|
throw new Error("Athena StartQueryExecution returned no QueryExecutionId");
|
|
371
366
|
}
|
|
372
367
|
await waitForQuery({ athena, queryExecutionId });
|
|
373
|
-
|
|
368
|
+
return fetchRawRows({ athena, queryExecutionId });
|
|
369
|
+
}
|
|
370
|
+
async function runHourlyAthenaQuery({
|
|
371
|
+
athena,
|
|
372
|
+
database,
|
|
373
|
+
table,
|
|
374
|
+
workgroup,
|
|
375
|
+
outputLocation,
|
|
376
|
+
source,
|
|
377
|
+
hourKey,
|
|
378
|
+
topPaths
|
|
379
|
+
}) {
|
|
380
|
+
const queryString = buildHourlyQuery({ table, source, hourKey, topPaths });
|
|
381
|
+
const rawRows = await startAndFetchRows({
|
|
382
|
+
athena,
|
|
383
|
+
database,
|
|
384
|
+
workgroup,
|
|
385
|
+
outputLocation,
|
|
386
|
+
queryString
|
|
387
|
+
});
|
|
388
|
+
const rows = rawRows.map((cells) => parseRow(cells));
|
|
374
389
|
return assembleResult({ rows, topPaths });
|
|
375
390
|
}
|
|
376
391
|
// packages/analytics-processor/src/breakdowns.ts
|
|
@@ -489,7 +504,11 @@ var configSchema = v3.object({
|
|
|
489
504
|
pk: v3.literal("system#config"),
|
|
490
505
|
sk: v3.literal("analytics"),
|
|
491
506
|
startKey: v3.string(),
|
|
492
|
-
startedAt: v3.string()
|
|
507
|
+
startedAt: v3.string(),
|
|
508
|
+
sitemapUrl: v3.optional(v3.string())
|
|
509
|
+
});
|
|
510
|
+
var sitemapConfigSchema = v3.looseObject({
|
|
511
|
+
sitemapUrl: v3.optional(v3.string())
|
|
493
512
|
});
|
|
494
513
|
var configKey = { pk: "system#config", sk: "analytics" };
|
|
495
514
|
function isConditionalCheckFailure(error) {
|
|
@@ -510,6 +529,23 @@ class Config {
|
|
|
510
529
|
return null;
|
|
511
530
|
return v3.parse(configSchema, item);
|
|
512
531
|
};
|
|
532
|
+
getSitemapConfig = async () => {
|
|
533
|
+
const { Item: item } = await this.props.dynamo.send(new GetCommand2({ TableName: this.props.tableName, Key: configKey }));
|
|
534
|
+
if (item == null)
|
|
535
|
+
return {};
|
|
536
|
+
return v3.parse(sitemapConfigSchema, item);
|
|
537
|
+
};
|
|
538
|
+
setSitemapUrl = async ({
|
|
539
|
+
sitemapUrl
|
|
540
|
+
}) => {
|
|
541
|
+
await this.props.dynamo.send(new UpdateCommand2({
|
|
542
|
+
TableName: this.props.tableName,
|
|
543
|
+
Key: configKey,
|
|
544
|
+
UpdateExpression: "SET #sitemapUrl = :url",
|
|
545
|
+
ExpressionAttributeNames: { "#sitemapUrl": "sitemapUrl" },
|
|
546
|
+
ExpressionAttributeValues: { ":url": sitemapUrl }
|
|
547
|
+
}));
|
|
548
|
+
};
|
|
513
549
|
ensureStart = async (props) => {
|
|
514
550
|
try {
|
|
515
551
|
await this.props.dynamo.send(new UpdateCommand2({
|
|
@@ -837,6 +873,7 @@ function toBreakdownRow(item) {
|
|
|
837
873
|
return { value: item.value, hits: item.hits, bytes: item.bytes };
|
|
838
874
|
}
|
|
839
875
|
// packages/analytics-processor/src/resolveLive.ts
|
|
876
|
+
var bucketStates = ["sealed", "partial", "pending"];
|
|
840
877
|
var emptyTotals = {
|
|
841
878
|
hits: 0,
|
|
842
879
|
success: 0,
|
|
@@ -870,21 +907,22 @@ async function resolveTotalsLive({
|
|
|
870
907
|
});
|
|
871
908
|
return sumTotals(hours);
|
|
872
909
|
}
|
|
873
|
-
const
|
|
874
|
-
const sealedDays = await models.totals.queryWindow({
|
|
910
|
+
const storedDays = await models.totals.queryWindow({
|
|
875
911
|
grain: "day",
|
|
876
912
|
startKey: `${periodKey}-01`,
|
|
877
913
|
endKey: `${periodKey}-31`
|
|
878
914
|
});
|
|
879
|
-
const
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
915
|
+
const base = sumTotals(storedDays.filter((item) => !isCurrentPeriod({ grain: "day", periodKey: item.timeKey, now })));
|
|
916
|
+
const currentDayKey = currentChildKey({ parentGrain: "month", periodKey, now });
|
|
917
|
+
if (currentDayKey == null)
|
|
918
|
+
return base;
|
|
919
|
+
const currentDay = await resolveTotalsLive({
|
|
920
|
+
models,
|
|
921
|
+
grain: "day",
|
|
922
|
+
periodKey: currentDayKey,
|
|
923
|
+
now
|
|
924
|
+
});
|
|
925
|
+
return sumTotals([base, currentDay]);
|
|
888
926
|
}
|
|
889
927
|
async function resolveBreakdownLive({
|
|
890
928
|
models,
|
|
@@ -901,10 +939,39 @@ async function resolveBreakdownLive({
|
|
|
901
939
|
if (stored.length > 0)
|
|
902
940
|
return toRows(stored);
|
|
903
941
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
942
|
+
if (grain === "day") {
|
|
943
|
+
const hourKeys = hourKeysInProgress({ periodKey, now });
|
|
944
|
+
const hourRows = await Promise.all(hourKeys.map((hourKey) => models.breakdowns.getForPeriod({ dimension, grain: "hour", periodKey: hourKey })));
|
|
945
|
+
return sumBreakdown(hourRows.flat().map(toRow));
|
|
946
|
+
}
|
|
947
|
+
const storedDayKeys = pastDayKeys({ periodKey, now });
|
|
948
|
+
const storedDayRows = await Promise.all(storedDayKeys.map((dayKey) => models.breakdowns.getForPeriod({ dimension, grain: "day", periodKey: dayKey })));
|
|
949
|
+
const rows = storedDayRows.flat().map(toRow);
|
|
950
|
+
const currentDayKey = currentChildKey({ parentGrain: "month", periodKey, now });
|
|
951
|
+
if (currentDayKey != null) {
|
|
952
|
+
const currentDay = await resolveBreakdownLive({
|
|
953
|
+
models,
|
|
954
|
+
dimension,
|
|
955
|
+
grain: "day",
|
|
956
|
+
periodKey: currentDayKey,
|
|
957
|
+
now
|
|
958
|
+
});
|
|
959
|
+
rows.push(...currentDay);
|
|
960
|
+
}
|
|
961
|
+
return sumBreakdown(rows);
|
|
962
|
+
}
|
|
963
|
+
async function classifyBucketState({
|
|
964
|
+
models,
|
|
965
|
+
grain,
|
|
966
|
+
periodKey,
|
|
967
|
+
now,
|
|
968
|
+
hasData
|
|
969
|
+
}) {
|
|
970
|
+
if (await isSealed({ models, grain, periodKey, now }))
|
|
971
|
+
return "sealed";
|
|
972
|
+
if (hasData)
|
|
973
|
+
return "partial";
|
|
974
|
+
return "pending";
|
|
908
975
|
}
|
|
909
976
|
async function isSealed({
|
|
910
977
|
models,
|
|
@@ -912,7 +979,7 @@ async function isSealed({
|
|
|
912
979
|
periodKey,
|
|
913
980
|
now
|
|
914
981
|
}) {
|
|
915
|
-
if (
|
|
982
|
+
if (!isPastPeriod({ grain, periodKey, now }))
|
|
916
983
|
return false;
|
|
917
984
|
return models.markers.isApplied({ grain, periodKey });
|
|
918
985
|
}
|
|
@@ -923,6 +990,13 @@ function isCurrentPeriod({
|
|
|
923
990
|
}) {
|
|
924
991
|
return periodKey === currentKeyFor({ grain, now });
|
|
925
992
|
}
|
|
993
|
+
function isPastPeriod({
|
|
994
|
+
grain,
|
|
995
|
+
periodKey,
|
|
996
|
+
now
|
|
997
|
+
}) {
|
|
998
|
+
return periodKey < currentKeyFor({ grain, now });
|
|
999
|
+
}
|
|
926
1000
|
function currentKeyFor({ grain, now }) {
|
|
927
1001
|
const year = pad4(now.getUTCFullYear());
|
|
928
1002
|
const month = pad2(now.getUTCMonth() + 1);
|
|
@@ -934,8 +1008,14 @@ function currentKeyFor({ grain, now }) {
|
|
|
934
1008
|
return `${year}-${month}-${day}`;
|
|
935
1009
|
return `${year}-${month}-${day}-${hour}`;
|
|
936
1010
|
}
|
|
937
|
-
function
|
|
938
|
-
|
|
1011
|
+
function currentChildKey({
|
|
1012
|
+
parentGrain,
|
|
1013
|
+
periodKey,
|
|
1014
|
+
now
|
|
1015
|
+
}) {
|
|
1016
|
+
if (!isCurrentPeriod({ grain: parentGrain, periodKey, now }))
|
|
1017
|
+
return null;
|
|
1018
|
+
return currentKeyFor({ grain: "day", now });
|
|
939
1019
|
}
|
|
940
1020
|
function hourKeysInProgress({
|
|
941
1021
|
periodKey,
|
|
@@ -948,11 +1028,12 @@ function hourKeysInProgress({
|
|
|
948
1028
|
}
|
|
949
1029
|
return keys;
|
|
950
1030
|
}
|
|
951
|
-
function
|
|
1031
|
+
function pastDayKeys({
|
|
952
1032
|
periodKey,
|
|
953
1033
|
now
|
|
954
1034
|
}) {
|
|
955
|
-
const
|
|
1035
|
+
const isCurrentMonth = isCurrentPeriod({ grain: "month", periodKey, now });
|
|
1036
|
+
const lastDay = isCurrentMonth ? now.getUTCDate() - 1 : daysInMonth(periodKey);
|
|
956
1037
|
const keys = [];
|
|
957
1038
|
for (let day = 1;day <= lastDay; day += 1) {
|
|
958
1039
|
keys.push(`${periodKey}-${pad2(day)}`);
|
|
@@ -963,15 +1044,11 @@ function daysInMonth(monthKey) {
|
|
|
963
1044
|
const [year, month] = monthKey.split("-").map(Number);
|
|
964
1045
|
return new Date(Date.UTC(year ?? 1970, month ?? 1, 0)).getUTCDate();
|
|
965
1046
|
}
|
|
966
|
-
function
|
|
967
|
-
|
|
968
|
-
for (const item of items) {
|
|
969
|
-
byKey[item.timeKey] = item;
|
|
970
|
-
}
|
|
971
|
-
return byKey;
|
|
1047
|
+
function toRow(item) {
|
|
1048
|
+
return { value: item.value, hits: item.hits, bytes: item.bytes };
|
|
972
1049
|
}
|
|
973
1050
|
function toRows(items) {
|
|
974
|
-
return items.map(
|
|
1051
|
+
return items.map(toRow);
|
|
975
1052
|
}
|
|
976
1053
|
function pickTotals(item) {
|
|
977
1054
|
return {
|
|
@@ -990,6 +1067,423 @@ function pad2(value) {
|
|
|
990
1067
|
function pad4(value) {
|
|
991
1068
|
return value.toString().padStart(4, "0");
|
|
992
1069
|
}
|
|
1070
|
+
// packages/analytics-processor/src/sitemap.ts
|
|
1071
|
+
import { UpdateCommand as UpdateCommand3 } from "@aws-sdk/lib-dynamodb";
|
|
1072
|
+
import { queryAll as queryAll2 } from "@beesolve/dynamo-helpers";
|
|
1073
|
+
import * as v4 from "valibot";
|
|
1074
|
+
function canonicalPath(path) {
|
|
1075
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
1076
|
+
return path.slice(0, -1);
|
|
1077
|
+
}
|
|
1078
|
+
return path;
|
|
1079
|
+
}
|
|
1080
|
+
function normalizeSitemapLoc(loc) {
|
|
1081
|
+
let parsed;
|
|
1082
|
+
try {
|
|
1083
|
+
parsed = new URL(loc);
|
|
1084
|
+
} catch {
|
|
1085
|
+
throw new Error(`Unparseable sitemap <loc>: ${loc}`);
|
|
1086
|
+
}
|
|
1087
|
+
return canonicalPath(parsed.pathname);
|
|
1088
|
+
}
|
|
1089
|
+
function parseSitemap(xml) {
|
|
1090
|
+
if (sitemapIndexPattern.test(xml)) {
|
|
1091
|
+
throw new Error("Sitemap index documents are not supported");
|
|
1092
|
+
}
|
|
1093
|
+
const paths = [];
|
|
1094
|
+
const seen = new Set;
|
|
1095
|
+
for (const match of xml.matchAll(locPattern)) {
|
|
1096
|
+
const raw = match[1];
|
|
1097
|
+
if (raw == null)
|
|
1098
|
+
continue;
|
|
1099
|
+
const loc = unescapeXml(raw.trim());
|
|
1100
|
+
if (loc === "")
|
|
1101
|
+
continue;
|
|
1102
|
+
const path = normalizeSitemapLoc(loc);
|
|
1103
|
+
if (seen.has(path))
|
|
1104
|
+
continue;
|
|
1105
|
+
seen.add(path);
|
|
1106
|
+
paths.push(path);
|
|
1107
|
+
}
|
|
1108
|
+
return paths;
|
|
1109
|
+
}
|
|
1110
|
+
var sitemapUserAgent = "@beesolve/analytics-processor sitemap fetcher (+https://github.com/BeeSolve/packages)";
|
|
1111
|
+
async function fetchSitemapPaths(url) {
|
|
1112
|
+
const res = await fetch(url, { headers: { "user-agent": sitemapUserAgent } });
|
|
1113
|
+
if (!res.ok) {
|
|
1114
|
+
throw new Error(`Failed to fetch sitemap ${url}: ${res.status} ${res.statusText}`);
|
|
1115
|
+
}
|
|
1116
|
+
const text = await res.text();
|
|
1117
|
+
return parseSitemap(text);
|
|
1118
|
+
}
|
|
1119
|
+
function hourKeyParts2(hourKey) {
|
|
1120
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})-(\d{2})$/.exec(hourKey);
|
|
1121
|
+
if (match == null) {
|
|
1122
|
+
throw new Error(`Invalid hourKey: ${hourKey}`);
|
|
1123
|
+
}
|
|
1124
|
+
const [, year, month, day, hour] = match;
|
|
1125
|
+
if (year == null || month == null || day == null || hour == null) {
|
|
1126
|
+
throw new Error(`Invalid hourKey: ${hourKey}`);
|
|
1127
|
+
}
|
|
1128
|
+
return { year, month, day, hour, monthKey: `${year}-${month}`, dayAttr: `d${day}` };
|
|
1129
|
+
}
|
|
1130
|
+
function buildSitemapHourlyQuery({
|
|
1131
|
+
table,
|
|
1132
|
+
source,
|
|
1133
|
+
hourKey,
|
|
1134
|
+
paths
|
|
1135
|
+
}) {
|
|
1136
|
+
const parts = hourKeyParts2(hourKey);
|
|
1137
|
+
const columns = columnMaps[source];
|
|
1138
|
+
const canonicalUri = canonicalUriExpr(columns.uriStem);
|
|
1139
|
+
const hourPredicate = source === "v2" ? `year = '${parts.year}' AND month = '${parts.month}' AND day = '${parts.day}' AND hour = '${parts.hour}'` : `"date" = DATE '${parts.year}-${parts.month}-${parts.day}' AND substr(${columns.timestamp}, 1, 2) = '${parts.hour}'`;
|
|
1140
|
+
const whereClause = paths.length === 0 ? "1 = 0" : `${hourPredicate} AND ${canonicalUri} IN (${paths.map((path) => `'${escapeSqlString(path)}'`).join(", ")})`;
|
|
1141
|
+
return [
|
|
1142
|
+
"SELECT",
|
|
1143
|
+
` ${canonicalUri} AS path_value,`,
|
|
1144
|
+
" count(*) AS hits",
|
|
1145
|
+
`FROM "${table}"`,
|
|
1146
|
+
`WHERE ${whereClause}`,
|
|
1147
|
+
`GROUP BY ${canonicalUri}`
|
|
1148
|
+
].join(`
|
|
1149
|
+
`);
|
|
1150
|
+
}
|
|
1151
|
+
async function runSitemapHourlyQuery({
|
|
1152
|
+
athena,
|
|
1153
|
+
database,
|
|
1154
|
+
table,
|
|
1155
|
+
workgroup,
|
|
1156
|
+
outputLocation,
|
|
1157
|
+
source,
|
|
1158
|
+
hourKey,
|
|
1159
|
+
paths
|
|
1160
|
+
}) {
|
|
1161
|
+
if (paths.length === 0) {
|
|
1162
|
+
return [];
|
|
1163
|
+
}
|
|
1164
|
+
const queryString = buildSitemapHourlyQuery({ table, source, hourKey, paths });
|
|
1165
|
+
const rawRows = await startAndFetchRows({
|
|
1166
|
+
athena,
|
|
1167
|
+
database,
|
|
1168
|
+
workgroup,
|
|
1169
|
+
outputLocation,
|
|
1170
|
+
queryString
|
|
1171
|
+
});
|
|
1172
|
+
const byPath = {};
|
|
1173
|
+
for (const cells of rawRows) {
|
|
1174
|
+
const path = cells[0];
|
|
1175
|
+
const hits = toCount(cells[1]);
|
|
1176
|
+
if (path == null || hits === 0)
|
|
1177
|
+
continue;
|
|
1178
|
+
byPath[path] = (byPath[path] ?? 0) + hits;
|
|
1179
|
+
}
|
|
1180
|
+
return Object.entries(byPath).map(([path, hits]) => ({ path, hits }));
|
|
1181
|
+
}
|
|
1182
|
+
function buildSitemapMonthlyQuery({
|
|
1183
|
+
table,
|
|
1184
|
+
source,
|
|
1185
|
+
monthKey,
|
|
1186
|
+
paths
|
|
1187
|
+
}) {
|
|
1188
|
+
const match = /^(\d{4})-(\d{2})$/.exec(monthKey);
|
|
1189
|
+
if (match == null) {
|
|
1190
|
+
throw new Error(`Invalid monthKey: ${monthKey}`);
|
|
1191
|
+
}
|
|
1192
|
+
const [, year, month] = match;
|
|
1193
|
+
if (year == null || month == null) {
|
|
1194
|
+
throw new Error(`Invalid monthKey: ${monthKey}`);
|
|
1195
|
+
}
|
|
1196
|
+
const columns = columnMaps[source];
|
|
1197
|
+
const canonicalUri = canonicalUriExpr(columns.uriStem);
|
|
1198
|
+
const dayExpr = source === "v2" ? "day" : `day_of_month(CAST("date" AS date))`;
|
|
1199
|
+
const monthPredicate = source === "v2" ? `year = '${year}' AND month = '${month}'` : `"date" BETWEEN DATE '${year}-${month}-01' AND DATE '${year}-${month}-31'`;
|
|
1200
|
+
const whereClause = paths.length === 0 ? "1 = 0" : `${monthPredicate} AND ${canonicalUri} IN (${paths.map((path) => `'${escapeSqlString(path)}'`).join(", ")})`;
|
|
1201
|
+
return [
|
|
1202
|
+
"SELECT",
|
|
1203
|
+
` ${dayExpr} AS day_value,`,
|
|
1204
|
+
` ${canonicalUri} AS path_value,`,
|
|
1205
|
+
" count(*) AS hits",
|
|
1206
|
+
`FROM "${table}"`,
|
|
1207
|
+
`WHERE ${whereClause}`,
|
|
1208
|
+
`GROUP BY ${dayExpr}, ${canonicalUri}`
|
|
1209
|
+
].join(`
|
|
1210
|
+
`);
|
|
1211
|
+
}
|
|
1212
|
+
async function runSitemapMonthlyQuery({
|
|
1213
|
+
athena,
|
|
1214
|
+
database,
|
|
1215
|
+
table,
|
|
1216
|
+
workgroup,
|
|
1217
|
+
outputLocation,
|
|
1218
|
+
source,
|
|
1219
|
+
monthKey,
|
|
1220
|
+
paths
|
|
1221
|
+
}) {
|
|
1222
|
+
if (paths.length === 0) {
|
|
1223
|
+
return [];
|
|
1224
|
+
}
|
|
1225
|
+
const queryString = buildSitemapMonthlyQuery({ table, source, monthKey, paths });
|
|
1226
|
+
const rawRows = await startAndFetchRows({
|
|
1227
|
+
athena,
|
|
1228
|
+
database,
|
|
1229
|
+
workgroup,
|
|
1230
|
+
outputLocation,
|
|
1231
|
+
queryString
|
|
1232
|
+
});
|
|
1233
|
+
const byPath = {};
|
|
1234
|
+
for (const cells of rawRows) {
|
|
1235
|
+
const dayCell = cells[0];
|
|
1236
|
+
const path = cells[1];
|
|
1237
|
+
const hits = toCount(cells[2]);
|
|
1238
|
+
if (path == null || dayCell == null || hits === 0)
|
|
1239
|
+
continue;
|
|
1240
|
+
const dayKey = String(dayCell).padStart(2, "0");
|
|
1241
|
+
const existing = byPath[path] ?? { days: {}, hits: 0 };
|
|
1242
|
+
existing.days[dayKey] = (existing.days[dayKey] ?? 0) + hits;
|
|
1243
|
+
existing.hits += hits;
|
|
1244
|
+
byPath[path] = existing;
|
|
1245
|
+
}
|
|
1246
|
+
return Object.entries(byPath).map(([path, entry]) => ({
|
|
1247
|
+
path,
|
|
1248
|
+
month: monthKey,
|
|
1249
|
+
days: entry.days,
|
|
1250
|
+
hits: entry.hits
|
|
1251
|
+
}));
|
|
1252
|
+
}
|
|
1253
|
+
async function processSitemapHour({
|
|
1254
|
+
hourKey,
|
|
1255
|
+
source,
|
|
1256
|
+
paths,
|
|
1257
|
+
models,
|
|
1258
|
+
athena,
|
|
1259
|
+
options,
|
|
1260
|
+
dynamo
|
|
1261
|
+
}) {
|
|
1262
|
+
const alreadyApplied = await models.markers.isApplied({ grain: "sitemap", periodKey: hourKey });
|
|
1263
|
+
if (alreadyApplied)
|
|
1264
|
+
return { status: "skipped", urls: 0 };
|
|
1265
|
+
const parts = hourKeyParts2(hourKey);
|
|
1266
|
+
const counts = paths.length === 0 ? [] : await runSitemapHourlyQuery({
|
|
1267
|
+
athena: athena.client,
|
|
1268
|
+
database: athena.database,
|
|
1269
|
+
table: athena.table,
|
|
1270
|
+
workgroup: athena.workgroup,
|
|
1271
|
+
outputLocation: athena.outputLocation,
|
|
1272
|
+
source,
|
|
1273
|
+
hourKey,
|
|
1274
|
+
paths
|
|
1275
|
+
});
|
|
1276
|
+
const ttl = computeMarkerTtl(options?.markerTtlDays);
|
|
1277
|
+
const markerPut = models.markers.buildMarkerPut({
|
|
1278
|
+
grain: "sitemap",
|
|
1279
|
+
periodKey: hourKey,
|
|
1280
|
+
source,
|
|
1281
|
+
ttl
|
|
1282
|
+
});
|
|
1283
|
+
const addUpdates = counts.map((count) => models.sitemapUrlStats.buildAdd({
|
|
1284
|
+
path: count.path,
|
|
1285
|
+
month: parts.monthKey,
|
|
1286
|
+
dayAttr: parts.dayAttr,
|
|
1287
|
+
hits: count.hits
|
|
1288
|
+
}));
|
|
1289
|
+
const result = await sendTransacted({ dynamo, leading: [markerPut], rest: addUpdates });
|
|
1290
|
+
return { status: result.status, urls: result.status === "applied" ? counts.length : 0 };
|
|
1291
|
+
}
|
|
1292
|
+
async function processSitemapMonth({
|
|
1293
|
+
monthKey,
|
|
1294
|
+
source,
|
|
1295
|
+
paths,
|
|
1296
|
+
models,
|
|
1297
|
+
athena
|
|
1298
|
+
}) {
|
|
1299
|
+
if (paths.length === 0) {
|
|
1300
|
+
return { urls: 0 };
|
|
1301
|
+
}
|
|
1302
|
+
const counts = await runSitemapMonthlyQuery({
|
|
1303
|
+
athena: athena.client,
|
|
1304
|
+
database: athena.database,
|
|
1305
|
+
table: athena.table,
|
|
1306
|
+
workgroup: athena.workgroup,
|
|
1307
|
+
outputLocation: athena.outputLocation,
|
|
1308
|
+
source,
|
|
1309
|
+
monthKey,
|
|
1310
|
+
paths
|
|
1311
|
+
});
|
|
1312
|
+
await models.sitemapUrlStats.overwriteMany(counts);
|
|
1313
|
+
return { urls: counts.length };
|
|
1314
|
+
}
|
|
1315
|
+
var sitemapUrlStatSchema = v4.looseObject({
|
|
1316
|
+
pk: v4.string(),
|
|
1317
|
+
sk: v4.string(),
|
|
1318
|
+
path: v4.string(),
|
|
1319
|
+
month: v4.string(),
|
|
1320
|
+
hits: v4.number(),
|
|
1321
|
+
updatedAt: v4.number()
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
class SitemapUrlStats {
|
|
1325
|
+
props;
|
|
1326
|
+
constructor(props) {
|
|
1327
|
+
this.props = props;
|
|
1328
|
+
}
|
|
1329
|
+
buildAdd = ({
|
|
1330
|
+
path,
|
|
1331
|
+
month,
|
|
1332
|
+
dayAttr,
|
|
1333
|
+
hits
|
|
1334
|
+
}) => {
|
|
1335
|
+
const update = {
|
|
1336
|
+
TableName: this.props.tableName,
|
|
1337
|
+
Key: { pk: partitionKey3(path), sk: month },
|
|
1338
|
+
UpdateExpression: "SET #path = :path, #month = :month, #updatedAt = :now ADD #hits :hits, #day :hits",
|
|
1339
|
+
ExpressionAttributeNames: {
|
|
1340
|
+
"#path": "path",
|
|
1341
|
+
"#month": "month",
|
|
1342
|
+
"#updatedAt": "updatedAt",
|
|
1343
|
+
"#hits": "hits",
|
|
1344
|
+
"#day": dayAttr
|
|
1345
|
+
},
|
|
1346
|
+
ExpressionAttributeValues: {
|
|
1347
|
+
":path": path,
|
|
1348
|
+
":month": month,
|
|
1349
|
+
":now": Date.now(),
|
|
1350
|
+
":hits": hits
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
return { Update: update };
|
|
1354
|
+
};
|
|
1355
|
+
overwriteMany = async (stats) => {
|
|
1356
|
+
for (const stat of stats) {
|
|
1357
|
+
await this.props.dynamo.send(new UpdateCommand3(this.buildOverwrite(stat)));
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
buildOverwrite = ({
|
|
1361
|
+
path,
|
|
1362
|
+
month,
|
|
1363
|
+
days,
|
|
1364
|
+
hits
|
|
1365
|
+
}) => {
|
|
1366
|
+
const names = {
|
|
1367
|
+
"#path": "path",
|
|
1368
|
+
"#month": "month",
|
|
1369
|
+
"#hits": "hits",
|
|
1370
|
+
"#updatedAt": "updatedAt"
|
|
1371
|
+
};
|
|
1372
|
+
const valuesMap = {
|
|
1373
|
+
":path": path,
|
|
1374
|
+
":month": month,
|
|
1375
|
+
":hits": hits,
|
|
1376
|
+
":now": Date.now()
|
|
1377
|
+
};
|
|
1378
|
+
const setParts = ["#path = :path", "#month = :month", "#hits = :hits", "#updatedAt = :now"];
|
|
1379
|
+
for (const [dayKey, count] of Object.entries(days)) {
|
|
1380
|
+
const dayAttr = `d${dayKey}`;
|
|
1381
|
+
names[`#${dayAttr}`] = dayAttr;
|
|
1382
|
+
valuesMap[`:${dayAttr}`] = count;
|
|
1383
|
+
setParts.push(`#${dayAttr} = :${dayAttr}`);
|
|
1384
|
+
}
|
|
1385
|
+
return {
|
|
1386
|
+
TableName: this.props.tableName,
|
|
1387
|
+
Key: { pk: partitionKey3(path), sk: month },
|
|
1388
|
+
UpdateExpression: `SET ${setParts.join(", ")}`,
|
|
1389
|
+
ExpressionAttributeNames: names,
|
|
1390
|
+
ExpressionAttributeValues: valuesMap
|
|
1391
|
+
};
|
|
1392
|
+
};
|
|
1393
|
+
getForUrl = async ({
|
|
1394
|
+
path,
|
|
1395
|
+
startMonth,
|
|
1396
|
+
endMonth
|
|
1397
|
+
}) => {
|
|
1398
|
+
const items = await queryAll2({
|
|
1399
|
+
dynamo: this.props.dynamo,
|
|
1400
|
+
input: {
|
|
1401
|
+
TableName: this.props.tableName,
|
|
1402
|
+
KeyConditionExpression: "#pk = :pk AND #sk BETWEEN :start AND :end",
|
|
1403
|
+
ExpressionAttributeNames: { "#pk": "pk", "#sk": "sk" },
|
|
1404
|
+
ExpressionAttributeValues: {
|
|
1405
|
+
":pk": partitionKey3(path),
|
|
1406
|
+
":start": startMonth,
|
|
1407
|
+
":end": endMonth
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
return items.map((item) => this.parseOne(item));
|
|
1412
|
+
};
|
|
1413
|
+
getForMonth = async ({
|
|
1414
|
+
month
|
|
1415
|
+
}) => {
|
|
1416
|
+
if (this.props.reverseIndexName == null) {
|
|
1417
|
+
throw new Error("getForMonth requires a reverseIndexName");
|
|
1418
|
+
}
|
|
1419
|
+
const items = await queryAll2({
|
|
1420
|
+
dynamo: this.props.dynamo,
|
|
1421
|
+
input: {
|
|
1422
|
+
TableName: this.props.tableName,
|
|
1423
|
+
IndexName: this.props.reverseIndexName,
|
|
1424
|
+
KeyConditionExpression: "#sk = :month",
|
|
1425
|
+
ExpressionAttributeNames: { "#sk": "sk" },
|
|
1426
|
+
ExpressionAttributeValues: { ":month": month }
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
return items.map((item) => this.parseOne(item));
|
|
1430
|
+
};
|
|
1431
|
+
parseOne = (item) => {
|
|
1432
|
+
const result = v4.safeParse(sitemapUrlStatSchema, item);
|
|
1433
|
+
if (!result.success) {
|
|
1434
|
+
console.error(v4.flatten(result.issues));
|
|
1435
|
+
throw new Error("Malformed stored sitemap url stat record");
|
|
1436
|
+
}
|
|
1437
|
+
const days = {};
|
|
1438
|
+
for (const [key, value] of Object.entries(result.output)) {
|
|
1439
|
+
const dayMatch = /^d(\d{2})$/.exec(key);
|
|
1440
|
+
if (dayMatch?.[1] != null && typeof value === "number") {
|
|
1441
|
+
days[dayMatch[1]] = value;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
return {
|
|
1445
|
+
pk: result.output.pk,
|
|
1446
|
+
sk: result.output.sk,
|
|
1447
|
+
path: result.output.path,
|
|
1448
|
+
month: result.output.month,
|
|
1449
|
+
hits: result.output.hits,
|
|
1450
|
+
updatedAt: result.output.updatedAt,
|
|
1451
|
+
days
|
|
1452
|
+
};
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
function partitionKey3(path) {
|
|
1456
|
+
return `sitemapurl#${path}`;
|
|
1457
|
+
}
|
|
1458
|
+
var locPattern = /<loc>([\s\S]*?)<\/loc>/gi;
|
|
1459
|
+
var sitemapIndexPattern = /<sitemapindex/i;
|
|
1460
|
+
var xmlEntities = {
|
|
1461
|
+
"&": "&",
|
|
1462
|
+
"<": "<",
|
|
1463
|
+
">": ">",
|
|
1464
|
+
""": '"',
|
|
1465
|
+
"'": "'"
|
|
1466
|
+
};
|
|
1467
|
+
function unescapeXml(value) {
|
|
1468
|
+
return value.replace(/&|<|>|"|'/g, (entity) => xmlEntities[entity] ?? entity);
|
|
1469
|
+
}
|
|
1470
|
+
function canonicalUriExpr(uriColumn) {
|
|
1471
|
+
return `CASE WHEN length(${uriColumn}) > 1 AND substr(${uriColumn}, -1) = '/' THEN substr(${uriColumn}, 1, length(${uriColumn}) - 1) ELSE ${uriColumn} END`;
|
|
1472
|
+
}
|
|
1473
|
+
function escapeSqlString(value) {
|
|
1474
|
+
return value.replace(/'/g, "''");
|
|
1475
|
+
}
|
|
1476
|
+
function toCount(value) {
|
|
1477
|
+
if (value == null || value === "")
|
|
1478
|
+
return 0;
|
|
1479
|
+
const parsed = Number(value);
|
|
1480
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
1481
|
+
}
|
|
1482
|
+
var defaultMarkerTtlDays = 90;
|
|
1483
|
+
function computeMarkerTtl(markerTtlDays) {
|
|
1484
|
+
const days = markerTtlDays ?? defaultMarkerTtlDays;
|
|
1485
|
+
return Math.floor(Date.now() / 1000) + days * 24 * 60 * 60;
|
|
1486
|
+
}
|
|
993
1487
|
// packages/analytics-processor/src/time.ts
|
|
994
1488
|
function pad22(value) {
|
|
995
1489
|
return value.toString().padStart(2, "0");
|
|
@@ -1075,9 +1569,9 @@ function hoursBetween({ from, to }) {
|
|
|
1075
1569
|
return keys;
|
|
1076
1570
|
}
|
|
1077
1571
|
// packages/analytics-processor/src/totals.ts
|
|
1078
|
-
import { GetCommand as GetCommand3, UpdateCommand as
|
|
1079
|
-
import { queryAll as
|
|
1080
|
-
import * as
|
|
1572
|
+
import { GetCommand as GetCommand3, UpdateCommand as UpdateCommand4 } from "@aws-sdk/lib-dynamodb";
|
|
1573
|
+
import { queryAll as queryAll3 } from "@beesolve/dynamo-helpers";
|
|
1574
|
+
import * as v5 from "valibot";
|
|
1081
1575
|
var additiveFields = [
|
|
1082
1576
|
"hits",
|
|
1083
1577
|
"success",
|
|
@@ -1093,23 +1587,23 @@ var hourlyDetailFields = [
|
|
|
1093
1587
|
"cacheHitRatio",
|
|
1094
1588
|
"uniqueIpCount"
|
|
1095
1589
|
];
|
|
1096
|
-
var totalsSchema =
|
|
1097
|
-
pk:
|
|
1098
|
-
sk:
|
|
1099
|
-
grain:
|
|
1100
|
-
timeKey:
|
|
1101
|
-
hits:
|
|
1102
|
-
success:
|
|
1103
|
-
errors:
|
|
1104
|
-
errors4xx:
|
|
1105
|
-
errors5xx:
|
|
1106
|
-
bytesSent:
|
|
1107
|
-
bytesReceived:
|
|
1108
|
-
p50TimeTaken:
|
|
1109
|
-
p95TimeTaken:
|
|
1110
|
-
cacheHitRatio:
|
|
1111
|
-
uniqueIpCount:
|
|
1112
|
-
ttl:
|
|
1590
|
+
var totalsSchema = v5.object({
|
|
1591
|
+
pk: v5.string(),
|
|
1592
|
+
sk: v5.string(),
|
|
1593
|
+
grain: v5.picklist(grains),
|
|
1594
|
+
timeKey: v5.string(),
|
|
1595
|
+
hits: v5.number(),
|
|
1596
|
+
success: v5.number(),
|
|
1597
|
+
errors: v5.number(),
|
|
1598
|
+
errors4xx: v5.number(),
|
|
1599
|
+
errors5xx: v5.number(),
|
|
1600
|
+
bytesSent: v5.number(),
|
|
1601
|
+
bytesReceived: v5.number(),
|
|
1602
|
+
p50TimeTaken: v5.optional(v5.number()),
|
|
1603
|
+
p95TimeTaken: v5.optional(v5.number()),
|
|
1604
|
+
cacheHitRatio: v5.optional(v5.number()),
|
|
1605
|
+
uniqueIpCount: v5.optional(v5.number()),
|
|
1606
|
+
ttl: v5.optional(v5.number())
|
|
1113
1607
|
});
|
|
1114
1608
|
|
|
1115
1609
|
class Totals {
|
|
@@ -1119,11 +1613,11 @@ class Totals {
|
|
|
1119
1613
|
}
|
|
1120
1614
|
addHourly = async (props) => {
|
|
1121
1615
|
const update = this.buildHourlyUpdate(props);
|
|
1122
|
-
await this.props.dynamo.send(new
|
|
1616
|
+
await this.props.dynamo.send(new UpdateCommand4(update.Update));
|
|
1123
1617
|
};
|
|
1124
1618
|
addAggregate = async (props) => {
|
|
1125
1619
|
const update = this.buildAggregateUpdate(props);
|
|
1126
|
-
await this.props.dynamo.send(new
|
|
1620
|
+
await this.props.dynamo.send(new UpdateCommand4(update.Update));
|
|
1127
1621
|
};
|
|
1128
1622
|
buildHourlyUpdate = (props) => ({
|
|
1129
1623
|
Update: this.buildUpdateMembers({
|
|
@@ -1141,14 +1635,14 @@ class Totals {
|
|
|
1141
1635
|
})
|
|
1142
1636
|
});
|
|
1143
1637
|
queryWindow = async (props) => {
|
|
1144
|
-
const items = await
|
|
1638
|
+
const items = await queryAll3({
|
|
1145
1639
|
dynamo: this.props.dynamo,
|
|
1146
1640
|
input: {
|
|
1147
1641
|
TableName: this.props.tableName,
|
|
1148
1642
|
KeyConditionExpression: "#pk = :pk AND #sk BETWEEN :start AND :end",
|
|
1149
1643
|
ExpressionAttributeNames: { "#pk": "pk", "#sk": "sk" },
|
|
1150
1644
|
ExpressionAttributeValues: {
|
|
1151
|
-
":pk":
|
|
1645
|
+
":pk": partitionKey4(props.grain),
|
|
1152
1646
|
":start": props.startKey,
|
|
1153
1647
|
":end": props.endKey
|
|
1154
1648
|
}
|
|
@@ -1159,7 +1653,7 @@ class Totals {
|
|
|
1159
1653
|
getOne = async (props) => {
|
|
1160
1654
|
const { Item: item } = await this.props.dynamo.send(new GetCommand3({
|
|
1161
1655
|
TableName: this.props.tableName,
|
|
1162
|
-
Key: { pk:
|
|
1656
|
+
Key: { pk: partitionKey4(props.grain), sk: props.timeKey }
|
|
1163
1657
|
}));
|
|
1164
1658
|
if (item == null)
|
|
1165
1659
|
return;
|
|
@@ -1195,55 +1689,73 @@ class Totals {
|
|
|
1195
1689
|
}
|
|
1196
1690
|
return {
|
|
1197
1691
|
TableName: this.props.tableName,
|
|
1198
|
-
Key: { pk:
|
|
1692
|
+
Key: { pk: partitionKey4(props.grain), sk: props.item.timeKey },
|
|
1199
1693
|
UpdateExpression: `SET ${setParts.join(", ")} ADD ${addParts.join(", ")}`,
|
|
1200
1694
|
ExpressionAttributeNames: names,
|
|
1201
1695
|
ExpressionAttributeValues: values
|
|
1202
1696
|
};
|
|
1203
1697
|
};
|
|
1204
1698
|
parseOne = (item) => {
|
|
1205
|
-
const result =
|
|
1699
|
+
const result = v5.safeParse(totalsSchema, item);
|
|
1206
1700
|
if (!result.success) {
|
|
1207
|
-
console.error(
|
|
1701
|
+
console.error(v5.flatten(result.issues));
|
|
1208
1702
|
throw new Error("Malformed stored totals record");
|
|
1209
1703
|
}
|
|
1210
1704
|
return result.output;
|
|
1211
1705
|
};
|
|
1212
1706
|
}
|
|
1213
|
-
function
|
|
1707
|
+
function partitionKey4(grain) {
|
|
1214
1708
|
return `totals#${grain}`;
|
|
1215
1709
|
}
|
|
1216
1710
|
export {
|
|
1217
1711
|
AppliedMarkers,
|
|
1218
1712
|
Breakdowns,
|
|
1219
1713
|
Config,
|
|
1714
|
+
SitemapUrlStats,
|
|
1220
1715
|
Totals,
|
|
1221
1716
|
appliedMarkerSchema,
|
|
1222
1717
|
appliedSources,
|
|
1223
1718
|
applyDailyRollup,
|
|
1224
1719
|
applyMonthlyRollup,
|
|
1225
1720
|
breakdownSchema,
|
|
1721
|
+
bucketStates,
|
|
1226
1722
|
buildHourlyQuery,
|
|
1723
|
+
buildSitemapHourlyQuery,
|
|
1724
|
+
buildSitemapMonthlyQuery,
|
|
1725
|
+
canonicalPath,
|
|
1227
1726
|
chunkTransactItems,
|
|
1727
|
+
classifyBucketState,
|
|
1228
1728
|
classifyDevice,
|
|
1229
1729
|
columnMaps,
|
|
1230
1730
|
configSchema,
|
|
1231
1731
|
dayKey,
|
|
1232
1732
|
dimensions,
|
|
1733
|
+
fetchSitemapPaths,
|
|
1233
1734
|
grainForPeriodKey,
|
|
1234
1735
|
grains,
|
|
1235
1736
|
hourKey,
|
|
1236
1737
|
hourKeyParts,
|
|
1237
1738
|
hoursBetween,
|
|
1739
|
+
isMarkerRaceError,
|
|
1238
1740
|
maxWindowFor,
|
|
1239
1741
|
monthKey,
|
|
1742
|
+
normalizeSitemapLoc,
|
|
1240
1743
|
parsePeriodKey,
|
|
1744
|
+
parseSitemap,
|
|
1241
1745
|
processHour,
|
|
1746
|
+
processSitemapHour,
|
|
1747
|
+
processSitemapMonth,
|
|
1242
1748
|
requiredRecordFields,
|
|
1243
1749
|
resolveBreakdownLive,
|
|
1244
1750
|
resolveTotalsLive,
|
|
1245
1751
|
runHourlyAthenaQuery,
|
|
1752
|
+
runSitemapHourlyQuery,
|
|
1753
|
+
runSitemapMonthlyQuery,
|
|
1754
|
+
sendTransacted,
|
|
1246
1755
|
shiftAnchor,
|
|
1756
|
+
sitemapUrlStatSchema,
|
|
1757
|
+
sitemapUserAgent,
|
|
1758
|
+
startAndFetchRows,
|
|
1247
1759
|
sumBreakdown,
|
|
1248
1760
|
sumTotals,
|
|
1249
1761
|
topNWithOther,
|