hae-vault 0.1.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/.env.example +7 -0
- package/CLAUDE.md +220 -0
- package/README.md +206 -0
- package/SKILL.md +60 -0
- package/dist/cli/dashboard.d.ts +3 -0
- package/dist/cli/dashboard.d.ts.map +1 -0
- package/dist/cli/dashboard.js +206 -0
- package/dist/cli/dashboard.js.map +1 -0
- package/dist/cli/import.d.ts +3 -0
- package/dist/cli/import.d.ts.map +1 -0
- package/dist/cli/import.js +78 -0
- package/dist/cli/import.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +31 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/info.d.ts +5 -0
- package/dist/cli/info.d.ts.map +1 -0
- package/dist/cli/info.js +34 -0
- package/dist/cli/info.js.map +1 -0
- package/dist/cli/metrics.d.ts +3 -0
- package/dist/cli/metrics.d.ts.map +1 -0
- package/dist/cli/metrics.js +20 -0
- package/dist/cli/metrics.js.map +1 -0
- package/dist/cli/query.d.ts +3 -0
- package/dist/cli/query.d.ts.map +1 -0
- package/dist/cli/query.js +18 -0
- package/dist/cli/query.js.map +1 -0
- package/dist/cli/serve.d.ts +3 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +19 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/sleep.d.ts +3 -0
- package/dist/cli/sleep.d.ts.map +1 -0
- package/dist/cli/sleep.js +19 -0
- package/dist/cli/sleep.js.map +1 -0
- package/dist/cli/summary.d.ts +3 -0
- package/dist/cli/summary.d.ts.map +1 -0
- package/dist/cli/summary.js +53 -0
- package/dist/cli/summary.js.map +1 -0
- package/dist/cli/trends.d.ts +3 -0
- package/dist/cli/trends.d.ts.map +1 -0
- package/dist/cli/trends.js +77 -0
- package/dist/cli/trends.js.map +1 -0
- package/dist/cli/watch.d.ts +12 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +89 -0
- package/dist/cli/watch.js.map +1 -0
- package/dist/cli/workouts.d.ts +3 -0
- package/dist/cli/workouts.d.ts.map +1 -0
- package/dist/cli/workouts.js +19 -0
- package/dist/cli/workouts.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -0
- package/dist/db/importLog.d.ts +5 -0
- package/dist/db/importLog.d.ts.map +1 -0
- package/dist/db/importLog.js +10 -0
- package/dist/db/importLog.js.map +1 -0
- package/dist/db/metrics.d.ts +4 -0
- package/dist/db/metrics.d.ts.map +1 -0
- package/dist/db/metrics.js +14 -0
- package/dist/db/metrics.js.map +1 -0
- package/dist/db/schema.d.ts +5 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +100 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/sleep.d.ts +4 -0
- package/dist/db/sleep.d.ts.map +1 -0
- package/dist/db/sleep.js +13 -0
- package/dist/db/sleep.js.map +1 -0
- package/dist/db/workouts.d.ts +4 -0
- package/dist/db/workouts.d.ts.map +1 -0
- package/dist/db/workouts.js +11 -0
- package/dist/db/workouts.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/parse/metrics.d.ts +17 -0
- package/dist/parse/metrics.d.ts.map +1 -0
- package/dist/parse/metrics.js +33 -0
- package/dist/parse/metrics.js.map +1 -0
- package/dist/parse/sleep.d.ts +23 -0
- package/dist/parse/sleep.d.ts.map +1 -0
- package/dist/parse/sleep.js +58 -0
- package/dist/parse/sleep.js.map +1 -0
- package/dist/parse/time.d.ts +4 -0
- package/dist/parse/time.d.ts.map +1 -0
- package/dist/parse/time.js +41 -0
- package/dist/parse/time.js.map +1 -0
- package/dist/parse/workouts.d.ts +17 -0
- package/dist/parse/workouts.d.ts.map +1 -0
- package/dist/parse/workouts.js +24 -0
- package/dist/parse/workouts.js.map +1 -0
- package/dist/server/app.d.ts +5 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +39 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/ingest.d.ts +15 -0
- package/dist/server/ingest.d.ts.map +1 -0
- package/dist/server/ingest.js +41 -0
- package/dist/server/ingest.js.map +1 -0
- package/dist/types/hae.d.ts +103 -0
- package/dist/types/hae.d.ts.map +1 -0
- package/dist/types/hae.js +2 -0
- package/dist/types/hae.js.map +1 -0
- package/dist/util/zip.d.ts +3 -0
- package/dist/util/zip.d.ts.map +1 -0
- package/dist/util/zip.js +24 -0
- package/dist/util/zip.js.map +1 -0
- package/docs/COMMANDS.md +315 -0
- package/docs/plans/2026-02-18-hae-vault-initial-implementation.md +2015 -0
- package/docs/plans/2026-02-18-readme-dashboard-design.md +213 -0
- package/docs/plans/2026-02-18-readme-dashboard-plan.md +1306 -0
- package/docs/plans/2026-02-18-zip-env-watch-design.md +213 -0
- package/docs/plans/2026-02-18-zip-env-watch.md +966 -0
- package/package.json +57 -0
- package/src/cli/dashboard.ts +242 -0
- package/src/cli/import.ts +85 -0
- package/src/cli/index.ts +32 -0
- package/src/cli/info.ts +36 -0
- package/src/cli/metrics.ts +20 -0
- package/src/cli/query.ts +17 -0
- package/src/cli/serve.ts +18 -0
- package/src/cli/sleep.ts +19 -0
- package/src/cli/summary.ts +58 -0
- package/src/cli/trends.ts +103 -0
- package/src/cli/watch.ts +111 -0
- package/src/cli/workouts.ts +19 -0
- package/src/config.ts +28 -0
- package/src/db/importLog.ts +18 -0
- package/src/db/metrics.ts +15 -0
- package/src/db/schema.ts +105 -0
- package/src/db/sleep.ts +15 -0
- package/src/db/workouts.ts +13 -0
- package/src/index.ts +4 -0
- package/src/parse/metrics.ts +50 -0
- package/src/parse/sleep.ts +82 -0
- package/src/parse/time.ts +43 -0
- package/src/parse/workouts.ts +42 -0
- package/src/server/app.ts +46 -0
- package/src/server/ingest.ts +68 -0
- package/src/types/hae.ts +94 -0
- package/src/util/zip.ts +24 -0
- package/tests/cli-watch.test.ts +64 -0
- package/tests/db-import-log.test.ts +40 -0
- package/tests/db-metrics.test.ts +44 -0
- package/tests/db-schema.test.ts +55 -0
- package/tests/db-sleep.test.ts +36 -0
- package/tests/db-workouts.test.ts +34 -0
- package/tests/ingest.test.ts +99 -0
- package/tests/parse-metrics.test.ts +55 -0
- package/tests/parse-sleep.test.ts +65 -0
- package/tests/parse-time.test.ts +48 -0
- package/tests/parse-workouts.test.ts +43 -0
- package/tests/types.test.ts +27 -0
- package/tests/util-zip.test.ts +46 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { parseHaeTime, toIso, toDateStr } from './time.js';
|
|
2
|
+
export function detectSleepVariant(dp) {
|
|
3
|
+
if ('startDate' in dp)
|
|
4
|
+
return 'detailed';
|
|
5
|
+
if ('core' in dp)
|
|
6
|
+
return 'aggregated_v2';
|
|
7
|
+
return 'aggregated_v1';
|
|
8
|
+
}
|
|
9
|
+
export function normalizeSleep(dp, target, sessionId) {
|
|
10
|
+
const variant = detectSleepVariant(dp);
|
|
11
|
+
if (variant === 'detailed') {
|
|
12
|
+
const raw = dp;
|
|
13
|
+
const start = parseHaeTime(raw.startDate);
|
|
14
|
+
return {
|
|
15
|
+
date: toDateStr(start),
|
|
16
|
+
sleep_start: toIso(start),
|
|
17
|
+
sleep_end: toIso(parseHaeTime(raw.endDate)),
|
|
18
|
+
in_bed_start: null, in_bed_end: null,
|
|
19
|
+
core_h: null, deep_h: null, rem_h: null, awake_h: null, asleep_h: null, in_bed_h: null,
|
|
20
|
+
schema_ver: 'detailed',
|
|
21
|
+
source: raw.source,
|
|
22
|
+
target,
|
|
23
|
+
meta: JSON.stringify(dp),
|
|
24
|
+
session_id: sessionId,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (variant === 'aggregated_v2') {
|
|
28
|
+
const v2 = dp;
|
|
29
|
+
const sleepStart = parseHaeTime(v2.sleepStart);
|
|
30
|
+
return {
|
|
31
|
+
date: toDateStr(sleepStart),
|
|
32
|
+
sleep_start: toIso(sleepStart),
|
|
33
|
+
sleep_end: toIso(parseHaeTime(v2.sleepEnd)),
|
|
34
|
+
in_bed_start: null, in_bed_end: null,
|
|
35
|
+
core_h: v2.core, deep_h: v2.deep, rem_h: v2.rem, awake_h: v2.awake,
|
|
36
|
+
asleep_h: v2.asleep, in_bed_h: v2.inBed,
|
|
37
|
+
schema_ver: 'aggregated_v2',
|
|
38
|
+
source: v2.source,
|
|
39
|
+
target, meta: null, session_id: sessionId,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// aggregated_v1
|
|
43
|
+
const v1 = dp;
|
|
44
|
+
const sleepStart = parseHaeTime(v1.sleepStart);
|
|
45
|
+
return {
|
|
46
|
+
date: toDateStr(sleepStart),
|
|
47
|
+
sleep_start: toIso(sleepStart),
|
|
48
|
+
sleep_end: toIso(parseHaeTime(v1.sleepEnd)),
|
|
49
|
+
in_bed_start: v1.inBedStart ? toIso(parseHaeTime(v1.inBedStart)) : null,
|
|
50
|
+
in_bed_end: v1.inBedEnd ? toIso(parseHaeTime(v1.inBedEnd)) : null,
|
|
51
|
+
core_h: null, deep_h: null, rem_h: null, awake_h: null,
|
|
52
|
+
asleep_h: v1.asleep, in_bed_h: v1.inBed,
|
|
53
|
+
schema_ver: 'aggregated_v1',
|
|
54
|
+
source: v1.sleepSource ?? null,
|
|
55
|
+
target, meta: null, session_id: sessionId,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=sleep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/parse/sleep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/parse/time.ts"],"names":[],"mappings":"AAiBA,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAiB5C;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAErC;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAEzC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// HAE outputs 5 different timestamp formats depending on iPhone locale.
|
|
2
|
+
function parse24h(date, h, m, s, tz) {
|
|
3
|
+
const tzFormatted = `${tz.slice(0, 3)}:${tz.slice(3)}`;
|
|
4
|
+
return new Date(`${date}T${h}:${m}:${s}${tzFormatted}`);
|
|
5
|
+
}
|
|
6
|
+
function parse12h(date, h, m, s, ampm, tz) {
|
|
7
|
+
let hour = parseInt(h, 10);
|
|
8
|
+
const isAm = ampm.toLowerCase() === 'am';
|
|
9
|
+
if (isAm && hour === 12)
|
|
10
|
+
hour = 0;
|
|
11
|
+
if (!isAm && hour !== 12)
|
|
12
|
+
hour += 12;
|
|
13
|
+
const hh = String(hour).padStart(2, '0');
|
|
14
|
+
const tzFormatted = `${tz.slice(0, 3)}:${tz.slice(3)}`;
|
|
15
|
+
return new Date(`${date}T${hh}:${m}:${s}${tzFormatted}`);
|
|
16
|
+
}
|
|
17
|
+
export function parseHaeTime(s) {
|
|
18
|
+
// Try 24-hour format: "2026-01-15 14:30:00 +0000"
|
|
19
|
+
const m24 = s.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}):(\d{2}):(\d{2}) ([+-]\d{4})$/);
|
|
20
|
+
if (m24) {
|
|
21
|
+
const d = parse24h(m24[1], m24[2], m24[3], m24[4], m24[5]);
|
|
22
|
+
if (!isNaN(d.getTime()))
|
|
23
|
+
return d;
|
|
24
|
+
}
|
|
25
|
+
// Try 12-hour format (space or narrow non-breaking space \u202f before AM/PM)
|
|
26
|
+
// "2026-01-15 2:30:00 PM +0000" or "2026-01-15 2:30:00\u202fPM +0000"
|
|
27
|
+
const m12 = s.match(/^(\d{4}-\d{2}-\d{2}) (\d{1,2}):(\d{2}):(\d{2})[\u202f ]([APap][Mm]) ([+-]\d{4})$/);
|
|
28
|
+
if (m12) {
|
|
29
|
+
const d = parse12h(m12[1], m12[2], m12[3], m12[4], m12[5], m12[6]);
|
|
30
|
+
if (!isNaN(d.getTime()))
|
|
31
|
+
return d;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Failed to parse HAE timestamp: "${s}"`);
|
|
34
|
+
}
|
|
35
|
+
export function toIso(d) {
|
|
36
|
+
return d.toISOString();
|
|
37
|
+
}
|
|
38
|
+
export function toDateStr(d) {
|
|
39
|
+
return d.toISOString().slice(0, 10);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/parse/time.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAExE,SAAS,QAAQ,CAAC,IAAY,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,EAAU;IACzE,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,OAAO,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,WAAW,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,IAAY,EAAE,EAAU;IACvF,IAAI,IAAI,GAAG,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC;IACzC,IAAI,IAAI,IAAI,IAAI,KAAK,EAAE;QAAE,IAAI,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,EAAE;QAAE,IAAI,IAAI,EAAE,CAAC;IACrC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,OAAO,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,WAAW,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,kDAAkD;IAClD,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;IACjF,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,8EAA8E;IAC9E,sEAAsE;IACtE,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,kFAAkF,CAAC,CAAC;IACxG,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,CAAO;IAC3B,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,CAAO;IAC/B,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WorkoutData } from '../types/hae.js';
|
|
2
|
+
export interface NormalizedWorkout {
|
|
3
|
+
ts: string;
|
|
4
|
+
date: string;
|
|
5
|
+
name: string;
|
|
6
|
+
duration_s: number | null;
|
|
7
|
+
calories_kj: number | null;
|
|
8
|
+
distance: number | null;
|
|
9
|
+
distance_unit: string | null;
|
|
10
|
+
avg_hr: number | null;
|
|
11
|
+
max_hr: number | null;
|
|
12
|
+
target: string;
|
|
13
|
+
meta: string;
|
|
14
|
+
session_id: string | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function parseWorkout(w: WorkoutData, target: string, sessionId: string | null): NormalizedWorkout;
|
|
17
|
+
//# sourceMappingURL=workouts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workouts.d.ts","sourceRoot":"","sources":["../../src/parse/workouts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,CAuBxG"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { parseHaeTime, toIso, toDateStr } from './time.js';
|
|
2
|
+
export function parseWorkout(w, target, sessionId) {
|
|
3
|
+
const start = parseHaeTime(w.start);
|
|
4
|
+
const end = parseHaeTime(w.end);
|
|
5
|
+
const duration_s = Math.round((end.getTime() - start.getTime()) / 1000);
|
|
6
|
+
const hrValues = (w.heartRateData ?? []).map((h) => h.qty).filter((v) => typeof v === 'number');
|
|
7
|
+
const avg_hr = hrValues.length > 0 ? hrValues.reduce((a, b) => a + b, 0) / hrValues.length : null;
|
|
8
|
+
const max_hr = hrValues.length > 0 ? Math.max(...hrValues) : null;
|
|
9
|
+
return {
|
|
10
|
+
ts: toIso(start),
|
|
11
|
+
date: toDateStr(start),
|
|
12
|
+
name: w.name,
|
|
13
|
+
duration_s,
|
|
14
|
+
calories_kj: w.activeEnergyBurned?.qty ?? null,
|
|
15
|
+
distance: w.distance?.qty ?? null,
|
|
16
|
+
distance_unit: w.distance?.units ?? null,
|
|
17
|
+
avg_hr,
|
|
18
|
+
max_hr,
|
|
19
|
+
target,
|
|
20
|
+
meta: JSON.stringify(w),
|
|
21
|
+
session_id: sessionId,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=workouts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workouts.js","sourceRoot":"","sources":["../../src/parse/workouts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAkB3D,MAAM,UAAU,YAAY,CAAC,CAAc,EAAE,MAAc,EAAE,SAAwB;IACnF,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAExE,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IAC7G,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAClG,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAElE,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC;QAChB,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC;QACtB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,UAAU;QACV,WAAW,EAAE,CAAC,CAAC,kBAAkB,EAAE,GAAG,IAAI,IAAI;QAC9C,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,IAAI,IAAI;QACjC,aAAa,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,IAAI,IAAI;QACxC,MAAM;QACN,MAAM;QACN,MAAM;QACN,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QACvB,UAAU,EAAE,SAAS;KACtB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAI3C,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,+CAwC7E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { ingest } from './ingest.js';
|
|
3
|
+
export function createApp(db, opts = {}) {
|
|
4
|
+
const app = express();
|
|
5
|
+
app.use(express.json({ limit: '50mb' }));
|
|
6
|
+
app.post('/api/ingest', (req, res) => {
|
|
7
|
+
// Optional bearer token auth
|
|
8
|
+
if (opts.token) {
|
|
9
|
+
const authHeader = req.headers['authorization'] ?? '';
|
|
10
|
+
const apiKey = req.headers['x-api-key'] ?? '';
|
|
11
|
+
const bearer = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : '';
|
|
12
|
+
if (bearer !== opts.token && apiKey !== opts.token) {
|
|
13
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const payload = req.body;
|
|
19
|
+
if (!payload?.data) {
|
|
20
|
+
res.status(400).json({ error: 'Missing data field' });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
ingest(db, payload, {
|
|
24
|
+
target: req.query['target'] ?? 'default',
|
|
25
|
+
sessionId: req.headers['session-id'] ?? null,
|
|
26
|
+
automationName: req.headers['automation-name'],
|
|
27
|
+
automationPeriod: req.headers['automation-period'],
|
|
28
|
+
});
|
|
29
|
+
res.json({ ok: true });
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error('Ingest error:', err);
|
|
33
|
+
res.status(400).json({ error: String(err) });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
app.get('/health', (_req, res) => res.json({ status: 'ok' }));
|
|
37
|
+
return app;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAAwC,MAAM,SAAS,CAAC;AAE/D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,MAAM,UAAU,SAAS,CAAC,EAAqB,EAAE,OAA2B,EAAE;IAC5E,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAEzC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACtD,6BAA6B;QAC7B,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;YACtD,MAAM,MAAM,GAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAY,IAAI,EAAE,CAAC;YAC1D,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,IAAI,MAAM,KAAK,IAAI,CAAC,KAAK,IAAI,MAAM,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;gBACnD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,GAAG,CAAC,IAAkB,CAAC;YACvC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;gBACnB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBACtD,OAAO;YACT,CAAC;YAED,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE;gBAClB,MAAM,EAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAY,IAAI,SAAS;gBACpD,SAAS,EAAG,GAAG,CAAC,OAAO,CAAC,YAAY,CAAY,IAAI,IAAI;gBACxD,cAAc,EAAE,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAuB;gBACpE,gBAAgB,EAAE,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAuB;aACzE,CAAC,CAAC;YAEH,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;YACpC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAE9D,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { HaePayload } from '../types/hae.js';
|
|
3
|
+
export interface IngestOptions {
|
|
4
|
+
target: string;
|
|
5
|
+
sessionId: string | null;
|
|
6
|
+
automationName?: string;
|
|
7
|
+
automationPeriod?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface IngestResult {
|
|
10
|
+
metricsAdded: number;
|
|
11
|
+
sleepAdded: number;
|
|
12
|
+
workoutsAdded: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function ingest(db: Database.Database, payload: HaePayload, opts: IngestOptions): IngestResult;
|
|
15
|
+
//# sourceMappingURL=ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/server/ingest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,iBAAiB,CAAC;AAQlE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,GAAG,YAAY,CA6CpG"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parseMetric } from '../parse/metrics.js';
|
|
2
|
+
import { normalizeSleep } from '../parse/sleep.js';
|
|
3
|
+
import { parseWorkout } from '../parse/workouts.js';
|
|
4
|
+
import { upsertMetrics } from '../db/metrics.js';
|
|
5
|
+
import { upsertSleep } from '../db/sleep.js';
|
|
6
|
+
import { upsertWorkout } from '../db/workouts.js';
|
|
7
|
+
export function ingest(db, payload, opts) {
|
|
8
|
+
const { target, sessionId } = opts;
|
|
9
|
+
const { data } = payload;
|
|
10
|
+
let metricsAdded = 0;
|
|
11
|
+
let sleepAdded = 0;
|
|
12
|
+
let workoutsAdded = 0;
|
|
13
|
+
// Process metrics
|
|
14
|
+
for (const m of data.metrics ?? []) {
|
|
15
|
+
if (m.name === 'sleep_analysis') {
|
|
16
|
+
for (const dp of m.data) {
|
|
17
|
+
const row = normalizeSleep(dp, target, sessionId);
|
|
18
|
+
upsertSleep(db, row);
|
|
19
|
+
sleepAdded++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const rows = parseMetric(m, target, sessionId);
|
|
24
|
+
upsertMetrics(db, rows);
|
|
25
|
+
metricsAdded += rows.length;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Process workouts
|
|
29
|
+
for (const w of data.workouts ?? []) {
|
|
30
|
+
const row = parseWorkout(w, target, sessionId);
|
|
31
|
+
upsertWorkout(db, row);
|
|
32
|
+
workoutsAdded++;
|
|
33
|
+
}
|
|
34
|
+
// Log sync
|
|
35
|
+
db.prepare(`
|
|
36
|
+
INSERT INTO sync_log (received_at, target, session_id, metrics_count, workouts_count, automation_name, automation_period)
|
|
37
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
38
|
+
`).run(new Date().toISOString(), target, sessionId, metricsAdded, workoutsAdded, opts.automationName ?? null, opts.automationPeriod ?? null);
|
|
39
|
+
return { metricsAdded, sleepAdded, workoutsAdded };
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/server/ingest.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAelD,MAAM,UAAU,MAAM,CAAC,EAAqB,EAAE,OAAmB,EAAE,IAAmB;IACpF,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAEzB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,kBAAkB;IAClB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;YAChC,KAAK,MAAM,EAAE,IAAK,CAAC,CAAC,IAAyB,EAAE,CAAC;gBAC9C,MAAM,GAAG,GAAG,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;gBAClD,WAAW,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;gBACrB,UAAU,EAAE,CAAC;YACf,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;YAC/C,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACxB,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAC/C,aAAa,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACvB,aAAa,EAAE,CAAC;IAClB,CAAC;IAED,WAAW;IACX,EAAE,CAAC,OAAO,CAAC;;;GAGV,CAAC,CAAC,GAAG,CACJ,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EACxB,MAAM,EACN,SAAS,EACT,YAAY,EACZ,aAAa,EACb,IAAI,CAAC,cAAc,IAAI,IAAI,EAC3B,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAC9B,CAAC;IAEF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export interface RawDatapoint {
|
|
2
|
+
date: string;
|
|
3
|
+
qty?: number;
|
|
4
|
+
Min?: number;
|
|
5
|
+
Avg?: number;
|
|
6
|
+
Max?: number;
|
|
7
|
+
systolic?: number;
|
|
8
|
+
diastolic?: number;
|
|
9
|
+
units?: string;
|
|
10
|
+
source?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SleepAnalysisRaw {
|
|
13
|
+
startDate: string;
|
|
14
|
+
endDate: string;
|
|
15
|
+
value: string;
|
|
16
|
+
source: string;
|
|
17
|
+
qty?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface AggregatedSleepV2 {
|
|
20
|
+
sleepStart: string;
|
|
21
|
+
sleepEnd: string;
|
|
22
|
+
core: number;
|
|
23
|
+
deep: number;
|
|
24
|
+
rem: number;
|
|
25
|
+
awake: number;
|
|
26
|
+
asleep: number;
|
|
27
|
+
inBed: number;
|
|
28
|
+
source: string;
|
|
29
|
+
}
|
|
30
|
+
export interface AggregatedSleepV1 {
|
|
31
|
+
sleepStart: string;
|
|
32
|
+
sleepEnd: string;
|
|
33
|
+
inBedStart: string;
|
|
34
|
+
inBedEnd: string;
|
|
35
|
+
asleep: number;
|
|
36
|
+
inBed: number;
|
|
37
|
+
sleepSource?: string;
|
|
38
|
+
inBedSource?: string;
|
|
39
|
+
}
|
|
40
|
+
export type SleepDatapoint = SleepAnalysisRaw | AggregatedSleepV2 | AggregatedSleepV1;
|
|
41
|
+
export interface MetricData {
|
|
42
|
+
name: string;
|
|
43
|
+
units: string;
|
|
44
|
+
data: RawDatapoint[] | SleepDatapoint[];
|
|
45
|
+
}
|
|
46
|
+
export interface WorkoutData {
|
|
47
|
+
name: string;
|
|
48
|
+
start: string;
|
|
49
|
+
end: string;
|
|
50
|
+
duration?: number;
|
|
51
|
+
activeEnergyBurned?: {
|
|
52
|
+
qty: number;
|
|
53
|
+
units: string;
|
|
54
|
+
};
|
|
55
|
+
distance?: {
|
|
56
|
+
qty: number;
|
|
57
|
+
units: string;
|
|
58
|
+
};
|
|
59
|
+
heartRateData?: Array<{
|
|
60
|
+
date: string;
|
|
61
|
+
qty: number;
|
|
62
|
+
units: string;
|
|
63
|
+
}>;
|
|
64
|
+
heartRateRecovery?: Array<{
|
|
65
|
+
date: string;
|
|
66
|
+
qty: number;
|
|
67
|
+
units: string;
|
|
68
|
+
}>;
|
|
69
|
+
route?: Array<{
|
|
70
|
+
lat: number;
|
|
71
|
+
lon: number;
|
|
72
|
+
altitude: number;
|
|
73
|
+
timestamp: string;
|
|
74
|
+
}>;
|
|
75
|
+
elevation?: {
|
|
76
|
+
ascent: number;
|
|
77
|
+
descent: number;
|
|
78
|
+
units: string;
|
|
79
|
+
};
|
|
80
|
+
[key: string]: unknown;
|
|
81
|
+
}
|
|
82
|
+
export interface HaePayload {
|
|
83
|
+
data: {
|
|
84
|
+
metrics?: MetricData[];
|
|
85
|
+
workouts?: WorkoutData[];
|
|
86
|
+
stateOfMind?: unknown[];
|
|
87
|
+
medications?: unknown[];
|
|
88
|
+
symptoms?: unknown[];
|
|
89
|
+
cycleTracking?: unknown[];
|
|
90
|
+
ecg?: unknown[];
|
|
91
|
+
heartRateNotifications?: unknown[];
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export interface HaeHeaders {
|
|
95
|
+
'automation-name'?: string;
|
|
96
|
+
'automation-id'?: string;
|
|
97
|
+
'automation-aggregation'?: string;
|
|
98
|
+
'automation-period'?: string;
|
|
99
|
+
'session-id'?: string;
|
|
100
|
+
'authorization'?: string;
|
|
101
|
+
'x-api-key'?: string;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=hae.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hae.d.ts","sourceRoot":"","sources":["../../src/types/hae.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hae.js","sourceRoot":"","sources":["../../src/types/hae.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zip.d.ts","sourceRoot":"","sources":["../../src/util/zip.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIlD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAkBpE"}
|
package/dist/util/zip.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import AdmZip from 'adm-zip';
|
|
2
|
+
const HAE_JSON_PATTERN = /HealthAutoExport.*\.json$/i;
|
|
3
|
+
export function extractPayloadFromZip(buf) {
|
|
4
|
+
try {
|
|
5
|
+
const zip = new AdmZip(buf);
|
|
6
|
+
const entry = zip.getEntries().find(e => HAE_JSON_PATTERN.test(e.entryName));
|
|
7
|
+
if (!entry)
|
|
8
|
+
return null;
|
|
9
|
+
let payload;
|
|
10
|
+
try {
|
|
11
|
+
payload = JSON.parse(entry.getData().toString('utf-8'));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (!payload || typeof payload !== 'object' || !('data' in payload))
|
|
17
|
+
return null;
|
|
18
|
+
return payload;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=zip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zip.js","sourceRoot":"","sources":["../../src/util/zip.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAG7B,MAAM,gBAAgB,GAAG,4BAA4B,CAAC;AAEtD,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAC7E,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,IAAI,OAAgB,CAAC;QACrB,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QACjF,OAAO,OAAqB,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|