@beesolve/analytics-processor 0.2.5 → 0.3.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/README.md +8 -4
- package/dist/dailyRollup/dailyRollup.mjs +1 -1
- package/dist/dailyRollup/dailyRollup.mjs.map +2 -2
- package/dist/hourly/hourly.mjs +3 -2
- package/dist/hourly/hourly.mjs.map +4 -4
- package/dist/index.d.ts +169 -102
- package/dist/index.js +299 -57
- package/dist/monthlyRollup/monthlyRollup.mjs +1 -1
- package/dist/monthlyRollup/monthlyRollup.mjs.map +2 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -130,10 +130,10 @@ var requiredRecordFields = [
|
|
|
130
130
|
];
|
|
131
131
|
var columnMaps = {
|
|
132
132
|
v2: {
|
|
133
|
-
status: "sc_status",
|
|
134
|
-
bytesSent: "sc_bytes",
|
|
135
|
-
bytesReceived: "cs_bytes",
|
|
136
|
-
timeTaken: "time_taken",
|
|
133
|
+
status: "CAST(sc_status AS integer)",
|
|
134
|
+
bytesSent: "CAST(sc_bytes AS bigint)",
|
|
135
|
+
bytesReceived: "CAST(cs_bytes AS bigint)",
|
|
136
|
+
timeTaken: "CAST(time_taken AS double)",
|
|
137
137
|
resultType: "x_edge_result_type",
|
|
138
138
|
detailedResultType: "x_edge_detailed_result_type",
|
|
139
139
|
uriStem: "cs_uri_stem",
|
|
@@ -203,32 +203,41 @@ function buildHourlyQuery({
|
|
|
203
203
|
const columns = columnMaps[source];
|
|
204
204
|
const predicate = predicateFor({ source, hourKey, columns });
|
|
205
205
|
const device = deviceCaseExpression(columns.userAgent);
|
|
206
|
-
const
|
|
207
|
-
"
|
|
208
|
-
`
|
|
209
|
-
|
|
210
|
-
`
|
|
211
|
-
`
|
|
212
|
-
|
|
206
|
+
const inner = [
|
|
207
|
+
" SELECT",
|
|
208
|
+
` ${columns.status} AS st,`,
|
|
209
|
+
` ${columns.bytesSent} AS scb,`,
|
|
210
|
+
` ${columns.bytesReceived} AS csb,`,
|
|
211
|
+
` ${columns.timeTaken} AS tt,`,
|
|
212
|
+
` ${columns.resultType} AS rt,`,
|
|
213
|
+
` ${columns.uriStem} AS uri,`,
|
|
214
|
+
` ${columns.clientIp} AS ip,`,
|
|
215
|
+
` ${device} AS device`,
|
|
216
|
+
` FROM "${table}"`,
|
|
217
|
+
` WHERE ${predicate}`
|
|
218
|
+
].join(`
|
|
219
|
+
`);
|
|
220
|
+
const groupingSets = ["()", "(st)", "(device)", "(uri)", "(ip)"].join(", ");
|
|
213
221
|
return [
|
|
214
222
|
"SELECT",
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
223
|
+
" st AS status_value,",
|
|
224
|
+
" device,",
|
|
225
|
+
" uri AS path_value,",
|
|
226
|
+
" ip AS ip_value,",
|
|
219
227
|
" count(*) AS hits,",
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
228
|
+
" sum(CASE WHEN st BETWEEN 200 AND 399 THEN 1 ELSE 0 END) AS success,",
|
|
229
|
+
" sum(CASE WHEN st BETWEEN 400 AND 599 THEN 1 ELSE 0 END) AS errors,",
|
|
230
|
+
" sum(CASE WHEN st BETWEEN 400 AND 499 THEN 1 ELSE 0 END) AS errors4xx,",
|
|
231
|
+
" sum(CASE WHEN st BETWEEN 500 AND 599 THEN 1 ELSE 0 END) AS errors5xx,",
|
|
232
|
+
" sum(scb) AS bytes_sent,",
|
|
233
|
+
" sum(csb) AS bytes_received,",
|
|
234
|
+
" approx_percentile(tt, 0.5) AS p50_time_taken,",
|
|
235
|
+
" approx_percentile(tt, 0.95) AS p95_time_taken,",
|
|
236
|
+
" sum(CASE WHEN rt = 'Hit' THEN 1 ELSE 0 END) AS cache_hits,",
|
|
237
|
+
" count(DISTINCT ip) AS unique_ip_count",
|
|
238
|
+
"FROM (",
|
|
239
|
+
inner,
|
|
240
|
+
")",
|
|
232
241
|
`GROUP BY GROUPING SETS (${groupingSets})`
|
|
233
242
|
].join(`
|
|
234
243
|
`);
|
|
@@ -473,6 +482,57 @@ class Breakdowns {
|
|
|
473
482
|
function partitionKey2(props) {
|
|
474
483
|
return `breakdown#${props.dimension}#${props.grain}#${props.periodKey}`;
|
|
475
484
|
}
|
|
485
|
+
// packages/analytics-processor/src/config.ts
|
|
486
|
+
import { GetCommand as GetCommand2, UpdateCommand as UpdateCommand2 } from "@aws-sdk/lib-dynamodb";
|
|
487
|
+
import * as v3 from "valibot";
|
|
488
|
+
var configSchema = v3.object({
|
|
489
|
+
pk: v3.literal("system#config"),
|
|
490
|
+
sk: v3.literal("analytics"),
|
|
491
|
+
startKey: v3.string(),
|
|
492
|
+
startedAt: v3.string()
|
|
493
|
+
});
|
|
494
|
+
var configKey = { pk: "system#config", sk: "analytics" };
|
|
495
|
+
function isConditionalCheckFailure(error) {
|
|
496
|
+
if (error == null || typeof error !== "object")
|
|
497
|
+
return false;
|
|
498
|
+
const name = "name" in error ? error.name : undefined;
|
|
499
|
+
return name === "ConditionalCheckFailedException";
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
class Config {
|
|
503
|
+
props;
|
|
504
|
+
constructor(props) {
|
|
505
|
+
this.props = props;
|
|
506
|
+
}
|
|
507
|
+
get = async () => {
|
|
508
|
+
const { Item: item } = await this.props.dynamo.send(new GetCommand2({ TableName: this.props.tableName, Key: configKey }));
|
|
509
|
+
if (item == null)
|
|
510
|
+
return null;
|
|
511
|
+
return v3.parse(configSchema, item);
|
|
512
|
+
};
|
|
513
|
+
ensureStart = async (props) => {
|
|
514
|
+
try {
|
|
515
|
+
await this.props.dynamo.send(new UpdateCommand2({
|
|
516
|
+
TableName: this.props.tableName,
|
|
517
|
+
Key: configKey,
|
|
518
|
+
UpdateExpression: "SET #startKey = :hourKey, #startedAt = if_not_exists(#startedAt, :now)",
|
|
519
|
+
ConditionExpression: "attribute_not_exists(#startKey) OR :hourKey < #startKey",
|
|
520
|
+
ExpressionAttributeNames: {
|
|
521
|
+
"#startKey": "startKey",
|
|
522
|
+
"#startedAt": "startedAt"
|
|
523
|
+
},
|
|
524
|
+
ExpressionAttributeValues: {
|
|
525
|
+
":hourKey": props.hourKey,
|
|
526
|
+
":now": new Date().toISOString()
|
|
527
|
+
}
|
|
528
|
+
}));
|
|
529
|
+
} catch (error) {
|
|
530
|
+
if (isConditionalCheckFailure(error))
|
|
531
|
+
return;
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
476
536
|
// packages/analytics-processor/src/device.ts
|
|
477
537
|
var botPattern = /bot|crawler|spider|crawl|slurp|mediapartners|facebookexternalhit|bingpreview|duckduckbot|baiduspider|yandex|semrush|ahrefs|headlesschrome|python-requests|curl|wget|monitor|pingdom|uptime/i;
|
|
478
538
|
var mobilePattern = /mobi|android|iphone|ipad|ipod|iemobile|blackberry|opera mini|windows phone|webos/i;
|
|
@@ -553,11 +613,15 @@ async function processHour({
|
|
|
553
613
|
byPath: query.byPath,
|
|
554
614
|
byCountry
|
|
555
615
|
});
|
|
556
|
-
|
|
616
|
+
const result = await sendTransacted({
|
|
557
617
|
dynamo,
|
|
558
618
|
leading: [markerPut, totalsUpdate],
|
|
559
619
|
rest: breakdownUpdates
|
|
560
620
|
});
|
|
621
|
+
if (result.status === "applied" && query.totals.hits > 0 && models.config != null) {
|
|
622
|
+
await models.config.ensureStart({ hourKey });
|
|
623
|
+
}
|
|
624
|
+
return result;
|
|
561
625
|
}
|
|
562
626
|
async function applyDailyRollup({
|
|
563
627
|
dayKey,
|
|
@@ -772,24 +836,196 @@ function computeHourlyTtl(hourlyTtlDays) {
|
|
|
772
836
|
function toBreakdownRow(item) {
|
|
773
837
|
return { value: item.value, hits: item.hits, bytes: item.bytes };
|
|
774
838
|
}
|
|
775
|
-
// packages/analytics-processor/src/
|
|
839
|
+
// packages/analytics-processor/src/resolveLive.ts
|
|
840
|
+
var emptyTotals = {
|
|
841
|
+
hits: 0,
|
|
842
|
+
success: 0,
|
|
843
|
+
errors: 0,
|
|
844
|
+
errors4xx: 0,
|
|
845
|
+
errors5xx: 0,
|
|
846
|
+
bytesSent: 0,
|
|
847
|
+
bytesReceived: 0
|
|
848
|
+
};
|
|
849
|
+
async function resolveTotalsLive({
|
|
850
|
+
models,
|
|
851
|
+
grain,
|
|
852
|
+
periodKey,
|
|
853
|
+
now
|
|
854
|
+
}) {
|
|
855
|
+
if (grain === "hour") {
|
|
856
|
+
const stored = await models.totals.getOne({ grain: "hour", timeKey: periodKey });
|
|
857
|
+
return stored == null ? emptyTotals : pickTotals(stored);
|
|
858
|
+
}
|
|
859
|
+
if (await isSealed({ models, grain, periodKey, now })) {
|
|
860
|
+
const stored = await models.totals.getOne({ grain, timeKey: periodKey });
|
|
861
|
+
if (stored != null)
|
|
862
|
+
return pickTotals(stored);
|
|
863
|
+
}
|
|
864
|
+
if (grain === "day") {
|
|
865
|
+
const lastHour = isCurrentPeriod({ grain: "day", periodKey, now }) ? now.getUTCHours() : 23;
|
|
866
|
+
const hours = await models.totals.queryWindow({
|
|
867
|
+
grain: "hour",
|
|
868
|
+
startKey: `${periodKey}-00`,
|
|
869
|
+
endKey: `${periodKey}-${pad2(lastHour)}`
|
|
870
|
+
});
|
|
871
|
+
return sumTotals(hours);
|
|
872
|
+
}
|
|
873
|
+
const dayKeys = dayKeysInProgress({ periodKey, now });
|
|
874
|
+
const sealedDays = await models.totals.queryWindow({
|
|
875
|
+
grain: "day",
|
|
876
|
+
startKey: `${periodKey}-01`,
|
|
877
|
+
endKey: `${periodKey}-31`
|
|
878
|
+
});
|
|
879
|
+
const sealedByKey = indexByTimeKey(sealedDays);
|
|
880
|
+
const resolvedDays = await Promise.all(dayKeys.map(async (dayKey) => {
|
|
881
|
+
const stored = sealedByKey[dayKey];
|
|
882
|
+
if (stored != null && !isCurrentPeriod({ grain: "day", periodKey: dayKey, now })) {
|
|
883
|
+
return pickTotals(stored);
|
|
884
|
+
}
|
|
885
|
+
return resolveTotalsLive({ models, grain: "day", periodKey: dayKey, now });
|
|
886
|
+
}));
|
|
887
|
+
return sumTotals(resolvedDays);
|
|
888
|
+
}
|
|
889
|
+
async function resolveBreakdownLive({
|
|
890
|
+
models,
|
|
891
|
+
dimension,
|
|
892
|
+
grain,
|
|
893
|
+
periodKey,
|
|
894
|
+
now
|
|
895
|
+
}) {
|
|
896
|
+
if (grain === "hour") {
|
|
897
|
+
return toRows(await models.breakdowns.getForPeriod({ dimension, grain: "hour", periodKey }));
|
|
898
|
+
}
|
|
899
|
+
if (await isSealed({ models, grain, periodKey, now })) {
|
|
900
|
+
const stored = await models.breakdowns.getForPeriod({ dimension, grain, periodKey });
|
|
901
|
+
if (stored.length > 0)
|
|
902
|
+
return toRows(stored);
|
|
903
|
+
}
|
|
904
|
+
const finer = finerGrain(grain);
|
|
905
|
+
const childKeys = grain === "day" ? hourKeysInProgress({ periodKey, now }) : dayKeysInProgress({ periodKey, now });
|
|
906
|
+
const children = await Promise.all(childKeys.map((childKey) => resolveBreakdownLive({ models, dimension, grain: finer, periodKey: childKey, now })));
|
|
907
|
+
return sumBreakdown(children.flat());
|
|
908
|
+
}
|
|
909
|
+
async function isSealed({
|
|
910
|
+
models,
|
|
911
|
+
grain,
|
|
912
|
+
periodKey,
|
|
913
|
+
now
|
|
914
|
+
}) {
|
|
915
|
+
if (isCurrentPeriod({ grain, periodKey, now }))
|
|
916
|
+
return false;
|
|
917
|
+
return models.markers.isApplied({ grain, periodKey });
|
|
918
|
+
}
|
|
919
|
+
function isCurrentPeriod({
|
|
920
|
+
grain,
|
|
921
|
+
periodKey,
|
|
922
|
+
now
|
|
923
|
+
}) {
|
|
924
|
+
return periodKey === currentKeyFor({ grain, now });
|
|
925
|
+
}
|
|
926
|
+
function currentKeyFor({ grain, now }) {
|
|
927
|
+
const year = pad4(now.getUTCFullYear());
|
|
928
|
+
const month = pad2(now.getUTCMonth() + 1);
|
|
929
|
+
const day = pad2(now.getUTCDate());
|
|
930
|
+
const hour = pad2(now.getUTCHours());
|
|
931
|
+
if (grain === "month")
|
|
932
|
+
return `${year}-${month}`;
|
|
933
|
+
if (grain === "day")
|
|
934
|
+
return `${year}-${month}-${day}`;
|
|
935
|
+
return `${year}-${month}-${day}-${hour}`;
|
|
936
|
+
}
|
|
937
|
+
function finerGrain(grain) {
|
|
938
|
+
return grain === "month" ? "day" : "hour";
|
|
939
|
+
}
|
|
940
|
+
function hourKeysInProgress({
|
|
941
|
+
periodKey,
|
|
942
|
+
now
|
|
943
|
+
}) {
|
|
944
|
+
const lastHour = isCurrentPeriod({ grain: "day", periodKey, now }) ? now.getUTCHours() : 23;
|
|
945
|
+
const keys = [];
|
|
946
|
+
for (let hour = 0;hour <= lastHour; hour += 1) {
|
|
947
|
+
keys.push(`${periodKey}-${pad2(hour)}`);
|
|
948
|
+
}
|
|
949
|
+
return keys;
|
|
950
|
+
}
|
|
951
|
+
function dayKeysInProgress({
|
|
952
|
+
periodKey,
|
|
953
|
+
now
|
|
954
|
+
}) {
|
|
955
|
+
const lastDay = isCurrentPeriod({ grain: "month", periodKey, now }) ? now.getUTCDate() : daysInMonth(periodKey);
|
|
956
|
+
const keys = [];
|
|
957
|
+
for (let day = 1;day <= lastDay; day += 1) {
|
|
958
|
+
keys.push(`${periodKey}-${pad2(day)}`);
|
|
959
|
+
}
|
|
960
|
+
return keys;
|
|
961
|
+
}
|
|
962
|
+
function daysInMonth(monthKey) {
|
|
963
|
+
const [year, month] = monthKey.split("-").map(Number);
|
|
964
|
+
return new Date(Date.UTC(year ?? 1970, month ?? 1, 0)).getUTCDate();
|
|
965
|
+
}
|
|
966
|
+
function indexByTimeKey(items) {
|
|
967
|
+
const byKey = {};
|
|
968
|
+
for (const item of items) {
|
|
969
|
+
byKey[item.timeKey] = item;
|
|
970
|
+
}
|
|
971
|
+
return byKey;
|
|
972
|
+
}
|
|
973
|
+
function toRows(items) {
|
|
974
|
+
return items.map((item) => ({ value: item.value, hits: item.hits, bytes: item.bytes }));
|
|
975
|
+
}
|
|
976
|
+
function pickTotals(item) {
|
|
977
|
+
return {
|
|
978
|
+
hits: item.hits,
|
|
979
|
+
success: item.success,
|
|
980
|
+
errors: item.errors,
|
|
981
|
+
errors4xx: item.errors4xx,
|
|
982
|
+
errors5xx: item.errors5xx,
|
|
983
|
+
bytesSent: item.bytesSent,
|
|
984
|
+
bytesReceived: item.bytesReceived
|
|
985
|
+
};
|
|
986
|
+
}
|
|
776
987
|
function pad2(value) {
|
|
777
988
|
return value.toString().padStart(2, "0");
|
|
778
989
|
}
|
|
779
990
|
function pad4(value) {
|
|
780
991
|
return value.toString().padStart(4, "0");
|
|
781
992
|
}
|
|
993
|
+
// packages/analytics-processor/src/time.ts
|
|
994
|
+
function pad22(value) {
|
|
995
|
+
return value.toString().padStart(2, "0");
|
|
996
|
+
}
|
|
997
|
+
function pad42(value) {
|
|
998
|
+
return value.toString().padStart(4, "0");
|
|
999
|
+
}
|
|
782
1000
|
function yearKey(date) {
|
|
783
|
-
return
|
|
1001
|
+
return pad42(date.getUTCFullYear());
|
|
784
1002
|
}
|
|
785
1003
|
function monthKey(date) {
|
|
786
|
-
return `${
|
|
1004
|
+
return `${pad42(date.getUTCFullYear())}-${pad22(date.getUTCMonth() + 1)}`;
|
|
787
1005
|
}
|
|
788
1006
|
function dayKey(date) {
|
|
789
|
-
return `${monthKey(date)}-${
|
|
1007
|
+
return `${monthKey(date)}-${pad22(date.getUTCDate())}`;
|
|
790
1008
|
}
|
|
791
1009
|
function hourKey(date) {
|
|
792
|
-
return `${dayKey(date)}-${
|
|
1010
|
+
return `${dayKey(date)}-${pad22(date.getUTCHours())}`;
|
|
1011
|
+
}
|
|
1012
|
+
function parsePeriodKey(periodKey) {
|
|
1013
|
+
const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(periodKey);
|
|
1014
|
+
if (match == null) {
|
|
1015
|
+
throw new Error(`Invalid period key: ${periodKey}`);
|
|
1016
|
+
}
|
|
1017
|
+
const [, year, month, day, hour] = match;
|
|
1018
|
+
return new Date(Date.UTC(Number(year), month == null ? 0 : Number(month) - 1, day == null ? 1 : Number(day), hour == null ? 0 : Number(hour)));
|
|
1019
|
+
}
|
|
1020
|
+
function grainForPeriodKey(periodKey) {
|
|
1021
|
+
const segments = periodKey.split("-").length;
|
|
1022
|
+
if (segments === 1)
|
|
1023
|
+
return "year";
|
|
1024
|
+
if (segments === 2)
|
|
1025
|
+
return "month";
|
|
1026
|
+
if (segments === 3)
|
|
1027
|
+
return "day";
|
|
1028
|
+
return "hour";
|
|
793
1029
|
}
|
|
794
1030
|
function maxWindowFor(grain) {
|
|
795
1031
|
if (grain === "hour")
|
|
@@ -839,9 +1075,9 @@ function hoursBetween({ from, to }) {
|
|
|
839
1075
|
return keys;
|
|
840
1076
|
}
|
|
841
1077
|
// packages/analytics-processor/src/totals.ts
|
|
842
|
-
import { GetCommand as
|
|
1078
|
+
import { GetCommand as GetCommand3, UpdateCommand as UpdateCommand3 } from "@aws-sdk/lib-dynamodb";
|
|
843
1079
|
import { queryAll as queryAll2 } from "@beesolve/dynamo-helpers";
|
|
844
|
-
import * as
|
|
1080
|
+
import * as v4 from "valibot";
|
|
845
1081
|
var additiveFields = [
|
|
846
1082
|
"hits",
|
|
847
1083
|
"success",
|
|
@@ -857,23 +1093,23 @@ var hourlyDetailFields = [
|
|
|
857
1093
|
"cacheHitRatio",
|
|
858
1094
|
"uniqueIpCount"
|
|
859
1095
|
];
|
|
860
|
-
var totalsSchema =
|
|
861
|
-
pk:
|
|
862
|
-
sk:
|
|
863
|
-
grain:
|
|
864
|
-
timeKey:
|
|
865
|
-
hits:
|
|
866
|
-
success:
|
|
867
|
-
errors:
|
|
868
|
-
errors4xx:
|
|
869
|
-
errors5xx:
|
|
870
|
-
bytesSent:
|
|
871
|
-
bytesReceived:
|
|
872
|
-
p50TimeTaken:
|
|
873
|
-
p95TimeTaken:
|
|
874
|
-
cacheHitRatio:
|
|
875
|
-
uniqueIpCount:
|
|
876
|
-
ttl:
|
|
1096
|
+
var totalsSchema = v4.object({
|
|
1097
|
+
pk: v4.string(),
|
|
1098
|
+
sk: v4.string(),
|
|
1099
|
+
grain: v4.picklist(grains),
|
|
1100
|
+
timeKey: v4.string(),
|
|
1101
|
+
hits: v4.number(),
|
|
1102
|
+
success: v4.number(),
|
|
1103
|
+
errors: v4.number(),
|
|
1104
|
+
errors4xx: v4.number(),
|
|
1105
|
+
errors5xx: v4.number(),
|
|
1106
|
+
bytesSent: v4.number(),
|
|
1107
|
+
bytesReceived: v4.number(),
|
|
1108
|
+
p50TimeTaken: v4.optional(v4.number()),
|
|
1109
|
+
p95TimeTaken: v4.optional(v4.number()),
|
|
1110
|
+
cacheHitRatio: v4.optional(v4.number()),
|
|
1111
|
+
uniqueIpCount: v4.optional(v4.number()),
|
|
1112
|
+
ttl: v4.optional(v4.number())
|
|
877
1113
|
});
|
|
878
1114
|
|
|
879
1115
|
class Totals {
|
|
@@ -883,11 +1119,11 @@ class Totals {
|
|
|
883
1119
|
}
|
|
884
1120
|
addHourly = async (props) => {
|
|
885
1121
|
const update = this.buildHourlyUpdate(props);
|
|
886
|
-
await this.props.dynamo.send(new
|
|
1122
|
+
await this.props.dynamo.send(new UpdateCommand3(update.Update));
|
|
887
1123
|
};
|
|
888
1124
|
addAggregate = async (props) => {
|
|
889
1125
|
const update = this.buildAggregateUpdate(props);
|
|
890
|
-
await this.props.dynamo.send(new
|
|
1126
|
+
await this.props.dynamo.send(new UpdateCommand3(update.Update));
|
|
891
1127
|
};
|
|
892
1128
|
buildHourlyUpdate = (props) => ({
|
|
893
1129
|
Update: this.buildUpdateMembers({
|
|
@@ -921,7 +1157,7 @@ class Totals {
|
|
|
921
1157
|
return items.map((item) => this.parseOne(item));
|
|
922
1158
|
};
|
|
923
1159
|
getOne = async (props) => {
|
|
924
|
-
const { Item: item } = await this.props.dynamo.send(new
|
|
1160
|
+
const { Item: item } = await this.props.dynamo.send(new GetCommand3({
|
|
925
1161
|
TableName: this.props.tableName,
|
|
926
1162
|
Key: { pk: partitionKey3(props.grain), sk: props.timeKey }
|
|
927
1163
|
}));
|
|
@@ -966,9 +1202,9 @@ class Totals {
|
|
|
966
1202
|
};
|
|
967
1203
|
};
|
|
968
1204
|
parseOne = (item) => {
|
|
969
|
-
const result =
|
|
1205
|
+
const result = v4.safeParse(totalsSchema, item);
|
|
970
1206
|
if (!result.success) {
|
|
971
|
-
console.error(
|
|
1207
|
+
console.error(v4.flatten(result.issues));
|
|
972
1208
|
throw new Error("Malformed stored totals record");
|
|
973
1209
|
}
|
|
974
1210
|
return result.output;
|
|
@@ -980,6 +1216,7 @@ function partitionKey3(grain) {
|
|
|
980
1216
|
export {
|
|
981
1217
|
AppliedMarkers,
|
|
982
1218
|
Breakdowns,
|
|
1219
|
+
Config,
|
|
983
1220
|
Totals,
|
|
984
1221
|
appliedMarkerSchema,
|
|
985
1222
|
appliedSources,
|
|
@@ -990,16 +1227,21 @@ export {
|
|
|
990
1227
|
chunkTransactItems,
|
|
991
1228
|
classifyDevice,
|
|
992
1229
|
columnMaps,
|
|
1230
|
+
configSchema,
|
|
993
1231
|
dayKey,
|
|
994
1232
|
dimensions,
|
|
1233
|
+
grainForPeriodKey,
|
|
995
1234
|
grains,
|
|
996
1235
|
hourKey,
|
|
997
1236
|
hourKeyParts,
|
|
998
1237
|
hoursBetween,
|
|
999
1238
|
maxWindowFor,
|
|
1000
1239
|
monthKey,
|
|
1240
|
+
parsePeriodKey,
|
|
1001
1241
|
processHour,
|
|
1002
1242
|
requiredRecordFields,
|
|
1243
|
+
resolveBreakdownLive,
|
|
1244
|
+
resolveTotalsLive,
|
|
1003
1245
|
runHourlyAthenaQuery,
|
|
1004
1246
|
shiftAnchor,
|
|
1005
1247
|
sumBreakdown,
|