@mongrov/analytics 0.1.0-alpha.1 → 0.1.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +456 -0
  2. package/dist/core/context.d.ts +24 -0
  3. package/dist/core/context.d.ts.map +1 -0
  4. package/dist/core/context.js +30 -0
  5. package/dist/core/context.js.map +1 -0
  6. package/dist/core/engine.d.ts +56 -0
  7. package/dist/core/engine.d.ts.map +1 -0
  8. package/dist/core/engine.js +119 -0
  9. package/dist/core/engine.js.map +1 -0
  10. package/dist/core/errors.d.ts +1 -1
  11. package/dist/core/errors.d.ts.map +1 -1
  12. package/dist/core/errors.js.map +1 -1
  13. package/dist/core/extensions.d.ts +33 -0
  14. package/dist/core/extensions.d.ts.map +1 -0
  15. package/dist/core/extensions.js +58 -0
  16. package/dist/core/extensions.js.map +1 -0
  17. package/dist/core/factory.d.ts +22 -5
  18. package/dist/core/factory.d.ts.map +1 -1
  19. package/dist/core/factory.js +231 -6
  20. package/dist/core/factory.js.map +1 -1
  21. package/dist/core/hooks.d.ts +32 -13
  22. package/dist/core/hooks.d.ts.map +1 -1
  23. package/dist/core/hooks.js +137 -15
  24. package/dist/core/hooks.js.map +1 -1
  25. package/dist/core/index.d.ts +3 -0
  26. package/dist/core/index.d.ts.map +1 -1
  27. package/dist/core/index.js +3 -1
  28. package/dist/core/index.js.map +1 -1
  29. package/dist/core/logger.d.ts +25 -0
  30. package/dist/core/logger.d.ts.map +1 -0
  31. package/dist/core/logger.js +33 -0
  32. package/dist/core/logger.js.map +1 -0
  33. package/dist/core/machine.d.ts +174 -0
  34. package/dist/core/machine.d.ts.map +1 -0
  35. package/dist/core/machine.js +228 -0
  36. package/dist/core/machine.js.map +1 -0
  37. package/dist/core/metric_metadata.d.ts +128 -0
  38. package/dist/core/metric_metadata.d.ts.map +1 -0
  39. package/dist/core/metric_metadata.js +47 -0
  40. package/dist/core/metric_metadata.js.map +1 -0
  41. package/dist/core/migrations.d.ts +52 -0
  42. package/dist/core/migrations.d.ts.map +1 -0
  43. package/dist/core/migrations.js +66 -0
  44. package/dist/core/migrations.js.map +1 -0
  45. package/dist/core/persistence.d.ts +50 -0
  46. package/dist/core/persistence.d.ts.map +1 -0
  47. package/dist/core/persistence.js +80 -0
  48. package/dist/core/persistence.js.map +1 -0
  49. package/dist/core/retention.d.ts +90 -0
  50. package/dist/core/retention.d.ts.map +1 -0
  51. package/dist/core/retention.js +119 -0
  52. package/dist/core/retention.js.map +1 -0
  53. package/dist/core/schemas.d.ts +35 -0
  54. package/dist/core/schemas.d.ts.map +1 -0
  55. package/dist/core/schemas.js +209 -0
  56. package/dist/core/schemas.js.map +1 -0
  57. package/dist/core/types.d.ts +10 -0
  58. package/dist/core/types.d.ts.map +1 -1
  59. package/dist/core/warehouse.d.ts +58 -0
  60. package/dist/core/warehouse.d.ts.map +1 -0
  61. package/dist/core/warehouse.js +116 -0
  62. package/dist/core/warehouse.js.map +1 -0
  63. package/dist/sync/buffer.d.ts +51 -0
  64. package/dist/sync/buffer.d.ts.map +1 -0
  65. package/dist/sync/buffer.js +188 -0
  66. package/dist/sync/buffer.js.map +1 -0
  67. package/dist/sync/context.d.ts +17 -0
  68. package/dist/sync/context.d.ts.map +1 -0
  69. package/dist/sync/context.js +23 -0
  70. package/dist/sync/context.js.map +1 -0
  71. package/dist/sync/errors.d.ts +19 -0
  72. package/dist/sync/errors.d.ts.map +1 -0
  73. package/dist/sync/errors.js +21 -0
  74. package/dist/sync/errors.js.map +1 -0
  75. package/dist/sync/events.d.ts +49 -0
  76. package/dist/sync/events.d.ts.map +1 -0
  77. package/dist/sync/events.js +46 -0
  78. package/dist/sync/events.js.map +1 -0
  79. package/dist/sync/factory.d.ts +57 -0
  80. package/dist/sync/factory.d.ts.map +1 -0
  81. package/dist/sync/factory.js +249 -0
  82. package/dist/sync/factory.js.map +1 -0
  83. package/dist/sync/fetcher.d.ts +74 -0
  84. package/dist/sync/fetcher.d.ts.map +1 -0
  85. package/dist/sync/fetcher.js +166 -0
  86. package/dist/sync/fetcher.js.map +1 -0
  87. package/dist/sync/flusher.d.ts +93 -0
  88. package/dist/sync/flusher.d.ts.map +1 -0
  89. package/dist/sync/flusher.js +234 -0
  90. package/dist/sync/flusher.js.map +1 -0
  91. package/dist/sync/hooks.d.ts +17 -0
  92. package/dist/sync/hooks.d.ts.map +1 -0
  93. package/dist/sync/hooks.js +48 -0
  94. package/dist/sync/hooks.js.map +1 -0
  95. package/dist/sync/index.d.ts +32 -1
  96. package/dist/sync/index.d.ts.map +1 -1
  97. package/dist/sync/index.js +36 -1
  98. package/dist/sync/index.js.map +1 -1
  99. package/dist/sync/manager.d.ts +48 -0
  100. package/dist/sync/manager.d.ts.map +1 -0
  101. package/dist/sync/manager.js +9 -0
  102. package/dist/sync/manager.js.map +1 -0
  103. package/dist/sync/mapper/activity.d.ts +31 -0
  104. package/dist/sync/mapper/activity.d.ts.map +1 -0
  105. package/dist/sync/mapper/activity.js +56 -0
  106. package/dist/sync/mapper/activity.js.map +1 -0
  107. package/dist/sync/mapper/battery.d.ts +12 -0
  108. package/dist/sync/mapper/battery.d.ts.map +1 -0
  109. package/dist/sync/mapper/battery.js +22 -0
  110. package/dist/sync/mapper/battery.js.map +1 -0
  111. package/dist/sync/mapper/firmware.d.ts +28 -0
  112. package/dist/sync/mapper/firmware.d.ts.map +1 -0
  113. package/dist/sync/mapper/firmware.js +43 -0
  114. package/dist/sync/mapper/firmware.js.map +1 -0
  115. package/dist/sync/mapper/heart-rate.d.ts +10 -0
  116. package/dist/sync/mapper/heart-rate.d.ts.map +1 -0
  117. package/dist/sync/mapper/heart-rate.js +19 -0
  118. package/dist/sync/mapper/heart-rate.js.map +1 -0
  119. package/dist/sync/mapper/hrv.d.ts +22 -0
  120. package/dist/sync/mapper/hrv.d.ts.map +1 -0
  121. package/dist/sync/mapper/hrv.js +56 -0
  122. package/dist/sync/mapper/hrv.js.map +1 -0
  123. package/dist/sync/mapper/ring-config.d.ts +33 -0
  124. package/dist/sync/mapper/ring-config.d.ts.map +1 -0
  125. package/dist/sync/mapper/ring-config.js +48 -0
  126. package/dist/sync/mapper/ring-config.js.map +1 -0
  127. package/dist/sync/mapper/sleep.d.ts +32 -0
  128. package/dist/sync/mapper/sleep.d.ts.map +1 -0
  129. package/dist/sync/mapper/sleep.js +103 -0
  130. package/dist/sync/mapper/sleep.js.map +1 -0
  131. package/dist/sync/mapper/spo2.d.ts +9 -0
  132. package/dist/sync/mapper/spo2.d.ts.map +1 -0
  133. package/dist/sync/mapper/spo2.js +18 -0
  134. package/dist/sync/mapper/spo2.js.map +1 -0
  135. package/dist/sync/mapper/temperature.d.ts +10 -0
  136. package/dist/sync/mapper/temperature.d.ts.map +1 -0
  137. package/dist/sync/mapper/temperature.js +19 -0
  138. package/dist/sync/mapper/temperature.js.map +1 -0
  139. package/dist/sync/mapper/time.d.ts +49 -0
  140. package/dist/sync/mapper/time.d.ts.map +1 -0
  141. package/dist/sync/mapper/time.js +107 -0
  142. package/dist/sync/mapper/time.js.map +1 -0
  143. package/dist/sync/mapper/types.d.ts +152 -0
  144. package/dist/sync/mapper/types.d.ts.map +1 -0
  145. package/dist/sync/mapper/types.js +15 -0
  146. package/dist/sync/mapper/types.js.map +1 -0
  147. package/dist/sync/overflow.d.ts +30 -0
  148. package/dist/sync/overflow.d.ts.map +1 -0
  149. package/dist/sync/overflow.js +114 -0
  150. package/dist/sync/overflow.js.map +1 -0
  151. package/dist/sync/pusher.d.ts +59 -0
  152. package/dist/sync/pusher.d.ts.map +1 -0
  153. package/dist/sync/pusher.js +115 -0
  154. package/dist/sync/pusher.js.map +1 -0
  155. package/dist/sync/scheduler.d.ts +84 -0
  156. package/dist/sync/scheduler.d.ts.map +1 -0
  157. package/dist/sync/scheduler.js +132 -0
  158. package/dist/sync/scheduler.js.map +1 -0
  159. package/dist/sync/triggers.d.ts +64 -0
  160. package/dist/sync/triggers.d.ts.map +1 -0
  161. package/dist/sync/triggers.js +113 -0
  162. package/dist/sync/triggers.js.map +1 -0
  163. package/dist/sync/types.d.ts +50 -0
  164. package/dist/sync/types.d.ts.map +1 -0
  165. package/dist/sync/types.js +8 -0
  166. package/dist/sync/types.js.map +1 -0
  167. package/dist/sync/watermark.d.ts +32 -0
  168. package/dist/sync/watermark.d.ts.map +1 -0
  169. package/dist/sync/watermark.js +61 -0
  170. package/dist/sync/watermark.js.map +1 -0
  171. package/package.json +12 -2
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Firmware timestamp + timezone utilities (T-01).
3
+ *
4
+ * Firmware exports ring readings in a `"YYYY.MM.DD HH:MM:SS"` string, wall-clock
5
+ * UTC. The mapper needs two operations:
6
+ *
7
+ * 1. `parseTimestamp(fwStr)` — turn the firmware string into an ISO instant.
8
+ * 2. `computeNightOf(ts, tz)` — assign a reading to a sleep-night bucket via
9
+ * the 6pm-6pm local rule (readings between 18:00 local of day D and 18:00
10
+ * local of D+1 belong to `night_of = D`).
11
+ *
12
+ * The `night_of` computation must be timezone-aware because the rule is defined
13
+ * in the user's IANA timezone (`ctx.userTimezone`), and DST transitions can
14
+ * shift the wall-clock boundary. We defer to `date-fns-tz` for the IANA math.
15
+ *
16
+ * See `.specifica/features/analytics-sync/spec.md` §Firmware mapper for the
17
+ * timestamp contract and `design.md` §sync/mapper/firmware.ts for the module
18
+ * shape.
19
+ */
20
+ import { formatInTimeZone, fromZonedTime } from 'date-fns-tz';
21
+ /**
22
+ * Match `"YYYY.MM.DD HH:MM:SS"` exactly. Firmware writes UTC into this shape.
23
+ *
24
+ * We accept a single-digit month/day/hour/minute/second tolerantly because the
25
+ * ring firmware has been observed to omit leading zeros on some fields.
26
+ */
27
+ const FIRMWARE_TIMESTAMP_RE = /^(\d{4})\.(\d{1,2})\.(\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/;
28
+ /**
29
+ * Parse a firmware timestamp string into a `Date` (ISO instant, UTC).
30
+ *
31
+ * Contract: input is UTC wall-clock (`"2026.06.17 03:15:00"` = 03:15 UTC).
32
+ *
33
+ * @throws {Error} if the string does not match `"YYYY.MM.DD HH:MM:SS"`.
34
+ */
35
+ export function parseTimestamp(fwStr) {
36
+ if (typeof fwStr !== 'string') {
37
+ throw new TypeError(`parseTimestamp: expected string, got ${typeof fwStr}`);
38
+ }
39
+ const match = fwStr.match(FIRMWARE_TIMESTAMP_RE);
40
+ if (!match) {
41
+ throw new Error(`parseTimestamp: unrecognised firmware timestamp shape: ${JSON.stringify(fwStr)}`);
42
+ }
43
+ const [, y, m, d, hh, mm, ss] = match;
44
+ const iso = `${y}-${pad(m)}-${pad(d)}T${pad(hh)}:${pad(mm)}:${pad(ss)}.000Z`;
45
+ const parsed = new Date(iso);
46
+ if (Number.isNaN(parsed.getTime())) {
47
+ throw new Error(`parseTimestamp: produced an invalid Date from ${JSON.stringify(fwStr)}`);
48
+ }
49
+ return parsed;
50
+ }
51
+ /**
52
+ * Assign an instant to a sleep-night bucket in the user's timezone.
53
+ *
54
+ * Rule (spec.md §Firmware mapper):
55
+ * Readings whose local (in `tz`) wall-clock is in the interval
56
+ * `[D 18:00, D+1 18:00)`
57
+ * are attributed to `night_of = D`.
58
+ *
59
+ * Returned as a UTC `Date` at midnight of `D` in the user's timezone. This
60
+ * matches the `DATE` column shape in `sleep_session.night_of` and lets
61
+ * `date_trunc`/`GROUP BY` queries stay timezone-honest.
62
+ *
63
+ * DST handling:
64
+ * - Spring-forward gap (02:00 becomes 03:00): a reading in the vanished hour
65
+ * is treated as the following instant by `date-fns-tz`, so the boundary is
66
+ * preserved.
67
+ * - Fall-back double hour (01:00 occurs twice): both occurrences share the
68
+ * same local wall-clock and therefore the same `night_of`. This is the
69
+ * desired behavior — the user experiences one continuous night.
70
+ */
71
+ export function computeNightOf(ts, tz) {
72
+ if (!(ts instanceof Date) || Number.isNaN(ts.getTime())) {
73
+ throw new TypeError('computeNightOf: ts must be a valid Date');
74
+ }
75
+ if (typeof tz !== 'string' || tz.length === 0) {
76
+ throw new TypeError('computeNightOf: tz must be a non-empty IANA string');
77
+ }
78
+ // `formatInTimeZone` is unambiguous: it renders `ts` as wall-clock in `tz`
79
+ // regardless of the JS runtime's local zone. `toZonedTime` semantics changed
80
+ // in v3 (returns a Date whose *local* methods reflect zoned time), so we
81
+ // avoid it here in favour of pure string extraction.
82
+ const localDate = formatInTimeZone(ts, tz, 'yyyy-MM-dd'); // e.g. '2026-06-17'
83
+ const localHour = Number(formatInTimeZone(ts, tz, 'HH')); // 0..23
84
+ const [y, m, d] = localDate.split('-').map(Number);
85
+ // If before 18:00 local, roll back one day. Use UTC arithmetic so we get
86
+ // safe rollover across month/year boundaries without accidentally consulting
87
+ // the runtime's local timezone.
88
+ let nightY = y;
89
+ let nightM = m;
90
+ let nightD = d;
91
+ if (localHour < 18) {
92
+ const rolled = new Date(Date.UTC(y, m - 1, d - 1));
93
+ nightY = rolled.getUTCFullYear();
94
+ nightM = rolled.getUTCMonth() + 1;
95
+ nightD = rolled.getUTCDate();
96
+ }
97
+ // Build the local-midnight ISO string for the night's day, then convert to
98
+ // the equivalent UTC instant via `fromZonedTime`.
99
+ const isoLocalMidnight = `${nightY.toString().padStart(4, '0')}-${nightM
100
+ .toString()
101
+ .padStart(2, '0')}-${nightD.toString().padStart(2, '0')}T00:00:00.000`;
102
+ return fromZonedTime(isoLocalMidnight, tz);
103
+ }
104
+ function pad(n) {
105
+ return n.length === 1 ? `0${n}` : n;
106
+ }
107
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../../../src/sync/mapper/time.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Firmware and mapped-row types shared across every mapper module.
3
+ *
4
+ * Firmware shapes (`Firmware*Row`, `FirmwareExport`, `FirmwareRingConfig`) are
5
+ * *input* — they mirror the JSON the ring firmware emits at sync time.
6
+ *
7
+ * Row shapes (`HrvRow`, `HeartRateRow`, ...) are *output* — they match the
8
+ * warehouse DDL in `core/schemas.ts`. Every mapper is a pure function from
9
+ * `Firmware*Row[]` (+ `MapperContext`) to `<Row>[]`.
10
+ *
11
+ * See `.specifica/features/analytics-sync/spec.md` §Firmware mapper and
12
+ * `.specifica/features/analytics-core/spec.md` §Table schema.
13
+ */
14
+ export interface MapperContext {
15
+ brand: string;
16
+ familyId: string;
17
+ userId: string;
18
+ deviceId: string;
19
+ /** IANA timezone string, e.g. 'America/Los_Angeles'. */
20
+ userTimezone: string;
21
+ }
22
+ export interface FirmwareHRVRow {
23
+ timestamp: string;
24
+ hrv?: number;
25
+ stress?: number;
26
+ /** Sentinel 0 → NULL. Preserved as collected_only. */
27
+ heartRate?: number;
28
+ systolicBP?: number;
29
+ diastolicBP?: number;
30
+ vascularAging?: number;
31
+ }
32
+ export interface FirmwareHRRow {
33
+ timestamp: string;
34
+ singleHR: number;
35
+ }
36
+ export interface FirmwareSpO2Row {
37
+ timestamp: string;
38
+ automaticSpo2Data: number;
39
+ }
40
+ export interface FirmwareTempRow {
41
+ timestamp: string;
42
+ temperature: number;
43
+ }
44
+ export interface FirmwareActivityRow {
45
+ timestamp: string;
46
+ step: number;
47
+ calories: number;
48
+ distance: number;
49
+ arraySteps: number[];
50
+ }
51
+ export interface FirmwareSleepRow {
52
+ start: string;
53
+ end: string;
54
+ block_type: string;
55
+ confidence: number;
56
+ timestamp: string;
57
+ }
58
+ export interface FirmwareBatteryRow {
59
+ timestamp: string;
60
+ battery: number;
61
+ }
62
+ export interface FirmwareRingConfig {
63
+ automaticMonitoringData: FirmwareMonitoringWindow[];
64
+ }
65
+ export interface FirmwareMonitoringWindow {
66
+ metric: string;
67
+ interval_minutes: number;
68
+ start_hour: number;
69
+ end_hour: number;
70
+ }
71
+ export interface FirmwareExport {
72
+ heartrate: FirmwareHRRow[];
73
+ hrv_table: FirmwareHRVRow[];
74
+ spo2: FirmwareSpO2Row[];
75
+ temperature_table: FirmwareTempRow[];
76
+ activitydetails: FirmwareActivityRow[];
77
+ sleep_processed: FirmwareSleepRow[];
78
+ battery_table: FirmwareBatteryRow[];
79
+ ring: FirmwareRingConfig;
80
+ }
81
+ interface BaseRow {
82
+ ts: Date;
83
+ brand: string;
84
+ family_id: string;
85
+ user_id: string;
86
+ device_id: string;
87
+ }
88
+ export interface HrvRow extends BaseRow {
89
+ hrv_ms: number | null;
90
+ stress: number | null;
91
+ systolic_bp: number | null;
92
+ diastolic_bp: number | null;
93
+ vascular_aging: number | null;
94
+ }
95
+ export interface HeartRateRow extends BaseRow {
96
+ bpm: number;
97
+ }
98
+ export interface Spo2Row extends BaseRow {
99
+ spo2: number;
100
+ }
101
+ export interface TemperatureRow extends BaseRow {
102
+ temp_c: number;
103
+ }
104
+ export interface ActivityRow extends BaseRow {
105
+ steps: number;
106
+ }
107
+ export interface ActivityBucketRow extends BaseRow {
108
+ steps: number;
109
+ calories: number;
110
+ distance_km: number;
111
+ }
112
+ export interface SleepSessionRow extends BaseRow {
113
+ session_id: string;
114
+ start_ts: Date;
115
+ end_ts: Date;
116
+ night_of: Date;
117
+ }
118
+ export interface SleepStageRow extends BaseRow {
119
+ session_id: string;
120
+ stage: string;
121
+ confidence: number;
122
+ }
123
+ export interface SleepRawRow extends BaseRow {
124
+ payload: FirmwareSleepRow;
125
+ }
126
+ export interface DeviceEventRow extends BaseRow {
127
+ event_type: string;
128
+ payload: Record<string, unknown>;
129
+ }
130
+ export interface DeviceConfigRow extends BaseRow {
131
+ metric: string;
132
+ interval_minutes: number;
133
+ start_hour: number;
134
+ end_hour: number;
135
+ valid_from: Date;
136
+ valid_to: Date | null;
137
+ }
138
+ export interface MappedBatch {
139
+ hrv: HrvRow[];
140
+ heart_rate: HeartRateRow[];
141
+ spo2: Spo2Row[];
142
+ temperature: TemperatureRow[];
143
+ activity: ActivityRow[];
144
+ activity_bucket: ActivityBucketRow[];
145
+ sleep_session: SleepSessionRow[];
146
+ sleep_stage: SleepStageRow[];
147
+ sleep_raw: SleepRawRow[];
148
+ device_event: DeviceEventRow[];
149
+ device_config: DeviceConfigRow[];
150
+ }
151
+ export {};
152
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/sync/mapper/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAA;CACrB;AAID,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,uBAAuB,EAAE,wBAAwB,EAAE,CAAA;CACpD;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,SAAS,EAAE,cAAc,EAAE,CAAA;IAC3B,IAAI,EAAE,eAAe,EAAE,CAAA;IACvB,iBAAiB,EAAE,eAAe,EAAE,CAAA;IACpC,eAAe,EAAE,mBAAmB,EAAE,CAAA;IACtC,eAAe,EAAE,gBAAgB,EAAE,CAAA;IACnC,aAAa,EAAE,kBAAkB,EAAE,CAAA;IACnC,IAAI,EAAE,kBAAkB,CAAA;CACzB;AAID,UAAU,OAAO;IACf,EAAE,EAAE,IAAI,CAAA;IACR,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,MAAO,SAAQ,OAAO;IACrC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,MAAM,WAAW,YAAa,SAAQ,OAAO;IAC3C,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,OAAQ,SAAQ,OAAO;IACtC,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,cAAe,SAAQ,OAAO;IAC7C,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAY,SAAQ,OAAO;IAC1C,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,iBAAkB,SAAQ,OAAO;IAChD,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,OAAO;IAC9C,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,IAAI,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;IACZ,QAAQ,EAAE,IAAI,CAAA;CACf;AAED,MAAM,WAAW,aAAc,SAAQ,OAAO;IAC5C,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAY,SAAQ,OAAO;IAC1C,OAAO,EAAE,gBAAgB,CAAA;CAC1B;AAED,MAAM,WAAW,cAAe,SAAQ,OAAO;IAC7C,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,eAAgB,SAAQ,OAAO;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,IAAI,CAAA;IAChB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,EAAE,CAAA;IACb,UAAU,EAAE,YAAY,EAAE,CAAA;IAC1B,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,eAAe,EAAE,iBAAiB,EAAE,CAAA;IACpC,aAAa,EAAE,eAAe,EAAE,CAAA;IAChC,WAAW,EAAE,aAAa,EAAE,CAAA;IAC5B,SAAS,EAAE,WAAW,EAAE,CAAA;IACxB,YAAY,EAAE,cAAc,EAAE,CAAA;IAC9B,aAAa,EAAE,eAAe,EAAE,CAAA;CACjC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Firmware and mapped-row types shared across every mapper module.
3
+ *
4
+ * Firmware shapes (`Firmware*Row`, `FirmwareExport`, `FirmwareRingConfig`) are
5
+ * *input* — they mirror the JSON the ring firmware emits at sync time.
6
+ *
7
+ * Row shapes (`HrvRow`, `HeartRateRow`, ...) are *output* — they match the
8
+ * warehouse DDL in `core/schemas.ts`. Every mapper is a pure function from
9
+ * `Firmware*Row[]` (+ `MapperContext`) to `<Row>[]`.
10
+ *
11
+ * See `.specifica/features/analytics-sync/spec.md` §Firmware mapper and
12
+ * `.specifica/features/analytics-core/spec.md` §Table schema.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/sync/mapper/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Overflow store (T-11).
3
+ *
4
+ * Persistent tail of the sensor buffer. When the in-memory ring exceeds its
5
+ * byte budget, chunks spill to the KVStore keyed by
6
+ * `sync:overflow:{table}:{seq}`
7
+ *
8
+ * A per-table manifest at `sync:overflow:{table}:manifest` holds the ordered
9
+ * list of live seq numbers so we can drain / count / clear without a
10
+ * `list()` op on KVStore. The manifest is kept in-memory too — hydrated once
11
+ * per table on first access and mutated in lock-step with the KV writes.
12
+ *
13
+ * Ordering guarantee: seq numbers are monotonic per table, so a FIFO drain
14
+ * over the manifest reproduces enqueue order.
15
+ */
16
+ import type { KVStore } from '../core/types';
17
+ import type { BufferEntry } from './types';
18
+ export declare class OverflowStore {
19
+ #private;
20
+ constructor(kv: KVStore);
21
+ push(table: string, entry: BufferEntry): Promise<void>;
22
+ /**
23
+ * Drain up to `limit` entries in FIFO order. Reads + deletes them from KV
24
+ * and updates the manifest. If `limit` is omitted, drains everything.
25
+ */
26
+ drain(table: string, limit?: number): Promise<BufferEntry[]>;
27
+ count(table: string): Promise<number>;
28
+ clear(table: string): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=overflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overflow.d.ts","sourceRoot":"","sources":["../../src/sync/overflow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAoB1C,qBAAa,aAAa;;gBAIZ,EAAE,EAAE,OAAO;IAIjB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAW5D;;;OAGG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA4B5D,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKrC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA6B1C"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Overflow store (T-11).
3
+ *
4
+ * Persistent tail of the sensor buffer. When the in-memory ring exceeds its
5
+ * byte budget, chunks spill to the KVStore keyed by
6
+ * `sync:overflow:{table}:{seq}`
7
+ *
8
+ * A per-table manifest at `sync:overflow:{table}:manifest` holds the ordered
9
+ * list of live seq numbers so we can drain / count / clear without a
10
+ * `list()` op on KVStore. The manifest is kept in-memory too — hydrated once
11
+ * per table on first access and mutated in lock-step with the KV writes.
12
+ *
13
+ * Ordering guarantee: seq numbers are monotonic per table, so a FIFO drain
14
+ * over the manifest reproduces enqueue order.
15
+ */
16
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
17
+ if (kind === "m") throw new TypeError("Private method is not writable");
18
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
19
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
20
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
21
+ };
22
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
23
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
24
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
25
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
26
+ };
27
+ var _OverflowStore_instances, _OverflowStore_kv, _OverflowStore_tables, _OverflowStore_hydrate;
28
+ const KEY_PREFIX = 'sync:overflow';
29
+ function chunkKey(table, seq) {
30
+ return `${KEY_PREFIX}:${table}:${seq}`;
31
+ }
32
+ function manifestKey(table) {
33
+ return `${KEY_PREFIX}:${table}:manifest`;
34
+ }
35
+ export class OverflowStore {
36
+ constructor(kv) {
37
+ _OverflowStore_instances.add(this);
38
+ _OverflowStore_kv.set(this, void 0);
39
+ _OverflowStore_tables.set(this, new Map());
40
+ __classPrivateFieldSet(this, _OverflowStore_kv, kv, "f");
41
+ }
42
+ async push(table, entry) {
43
+ const state = await __classPrivateFieldGet(this, _OverflowStore_instances, "m", _OverflowStore_hydrate).call(this, table);
44
+ const seq = state.nextSeq++;
45
+ state.seqs.push(seq);
46
+ await __classPrivateFieldGet(this, _OverflowStore_kv, "f").set(chunkKey(table, seq), entry);
47
+ await __classPrivateFieldGet(this, _OverflowStore_kv, "f").set(manifestKey(table), {
48
+ seqs: state.seqs,
49
+ nextSeq: state.nextSeq,
50
+ });
51
+ }
52
+ /**
53
+ * Drain up to `limit` entries in FIFO order. Reads + deletes them from KV
54
+ * and updates the manifest. If `limit` is omitted, drains everything.
55
+ */
56
+ async drain(table, limit) {
57
+ const state = await __classPrivateFieldGet(this, _OverflowStore_instances, "m", _OverflowStore_hydrate).call(this, table);
58
+ const take = limit ?? state.seqs.length;
59
+ if (take <= 0 || state.seqs.length === 0)
60
+ return [];
61
+ const drained = [];
62
+ const seqsToRemove = state.seqs.slice(0, take);
63
+ for (const seq of seqsToRemove) {
64
+ const entry = await __classPrivateFieldGet(this, _OverflowStore_kv, "f").get(chunkKey(table, seq));
65
+ if (entry)
66
+ drained.push(entry);
67
+ await __classPrivateFieldGet(this, _OverflowStore_kv, "f").delete(chunkKey(table, seq));
68
+ }
69
+ state.seqs = state.seqs.slice(take);
70
+ if (state.seqs.length === 0) {
71
+ // Reset nextSeq so a re-fill after a full drain doesn't leak counter
72
+ // growth. Manifest is deleted rather than persisted as empty.
73
+ state.nextSeq = 0;
74
+ await __classPrivateFieldGet(this, _OverflowStore_kv, "f").delete(manifestKey(table));
75
+ }
76
+ else {
77
+ await __classPrivateFieldGet(this, _OverflowStore_kv, "f").set(manifestKey(table), {
78
+ seqs: state.seqs,
79
+ nextSeq: state.nextSeq,
80
+ });
81
+ }
82
+ return drained;
83
+ }
84
+ async count(table) {
85
+ const state = await __classPrivateFieldGet(this, _OverflowStore_instances, "m", _OverflowStore_hydrate).call(this, table);
86
+ return state.seqs.length;
87
+ }
88
+ async clear(table) {
89
+ const state = await __classPrivateFieldGet(this, _OverflowStore_instances, "m", _OverflowStore_hydrate).call(this, table);
90
+ for (const seq of state.seqs) {
91
+ await __classPrivateFieldGet(this, _OverflowStore_kv, "f").delete(chunkKey(table, seq));
92
+ }
93
+ state.seqs = [];
94
+ state.nextSeq = 0;
95
+ await __classPrivateFieldGet(this, _OverflowStore_kv, "f").delete(manifestKey(table));
96
+ }
97
+ }
98
+ _OverflowStore_kv = new WeakMap(), _OverflowStore_tables = new WeakMap(), _OverflowStore_instances = new WeakSet(), _OverflowStore_hydrate = async function _OverflowStore_hydrate(table) {
99
+ let state = __classPrivateFieldGet(this, _OverflowStore_tables, "f").get(table);
100
+ if (state && state.hydrated)
101
+ return state;
102
+ if (!state) {
103
+ state = { seqs: [], nextSeq: 0, hydrated: false };
104
+ __classPrivateFieldGet(this, _OverflowStore_tables, "f").set(table, state);
105
+ }
106
+ const manifest = await __classPrivateFieldGet(this, _OverflowStore_kv, "f").get(manifestKey(table));
107
+ if (manifest && Array.isArray(manifest.seqs)) {
108
+ state.seqs = [...manifest.seqs];
109
+ state.nextSeq = Number.isFinite(manifest.nextSeq) ? manifest.nextSeq : 0;
110
+ }
111
+ state.hydrated = true;
112
+ return state;
113
+ };
114
+ //# sourceMappingURL=overflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overflow.js","sourceRoot":"","sources":["../../src/sync/overflow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;;;;;;;;;;;;;AAKH,MAAM,UAAU,GAAG,eAAe,CAAA;AAElC,SAAS,QAAQ,CAAC,KAAa,EAAE,GAAW;IAC1C,OAAO,GAAG,UAAU,IAAI,KAAK,IAAI,GAAG,EAAE,CAAA;AACxC,CAAC;AACD,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,GAAG,UAAU,IAAI,KAAK,WAAW,CAAA;AAC1C,CAAC;AAWD,MAAM,OAAO,aAAa;IAIxB,YAAY,EAAW;;QAHd,oCAAY;QACZ,gCAAU,IAAI,GAAG,EAAsB,EAAA;QAG9C,uBAAA,IAAI,qBAAO,EAAE,MAAA,CAAA;IACf,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAa,EAAE,KAAkB;QAC1C,MAAM,KAAK,GAAG,MAAM,uBAAA,IAAI,wDAAS,MAAb,IAAI,EAAU,KAAK,CAAC,CAAA;QACxC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,EAAE,CAAA;QAC3B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACpB,MAAM,uBAAA,IAAI,yBAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAA;QAC/C,MAAM,uBAAA,IAAI,yBAAI,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE;YACrC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CAAA;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,KAAc;QACvC,MAAM,KAAK,GAAG,MAAM,uBAAA,IAAI,wDAAS,MAAb,IAAI,EAAU,KAAK,CAAC,CAAA;QACxC,MAAM,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAA;QACvC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAA;QAEnD,MAAM,OAAO,GAAkB,EAAE,CAAA;QACjC,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;QAC9C,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,MAAM,uBAAA,IAAI,yBAAI,CAAC,GAAG,CAAc,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAA;YACnE,IAAI,KAAK;gBAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAC9B,MAAM,uBAAA,IAAI,yBAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAA;QAC7C,CAAC;QACD,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,qEAAqE;YACrE,8DAA8D;YAC9D,KAAK,CAAC,OAAO,GAAG,CAAC,CAAA;YACjB,MAAM,uBAAA,IAAI,yBAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;QAC3C,CAAC;aACI,CAAC;YACJ,MAAM,uBAAA,IAAI,yBAAI,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE;gBACrC,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO,EAAE,KAAK,CAAC,OAAO;aACvB,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAa;QACvB,MAAM,KAAK,GAAG,MAAM,uBAAA,IAAI,wDAAS,MAAb,IAAI,EAAU,KAAK,CAAC,CAAA;QACxC,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAA;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAa;QACvB,MAAM,KAAK,GAAG,MAAM,uBAAA,IAAI,wDAAS,MAAb,IAAI,EAAU,KAAK,CAAC,CAAA;QACxC,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YAC7B,MAAM,uBAAA,IAAI,yBAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAA;QAC7C,CAAC;QACD,KAAK,CAAC,IAAI,GAAG,EAAE,CAAA;QACf,KAAK,CAAC,OAAO,GAAG,CAAC,CAAA;QACjB,MAAM,uBAAA,IAAI,yBAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;IAC3C,CAAC;CAqBF;6IAnBC,KAAK,iCAAU,KAAa;IAC1B,IAAI,KAAK,GAAG,uBAAA,IAAI,6BAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IACnC,IAAI,KAAK,IAAI,KAAK,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAA;IAEzC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;QACjD,uBAAA,IAAI,6BAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IAChC,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,uBAAA,IAAI,yBAAI,CAAC,GAAG,CACjC,WAAW,CAAC,KAAK,CAAC,CACnB,CAAA;IACD,IAAI,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,KAAK,CAAC,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC/B,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1E,CAAC;IACD,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAA;IACrB,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * R2 pusher (T-17 + T-18).
3
+ *
4
+ * Pushes per-table deltas from the local DuckDB warehouse to the attached R2
5
+ * Iceberg zone. Each table push:
6
+ * 1. Reads the push watermark for `(brand, familyId, table)`.
7
+ * 2. Computes the max `ts` currently in local for that filter.
8
+ * 3. Issues `INSERT INTO <remote>.<table> SELECT * FROM <local>.<table>
9
+ * WHERE ts > $watermark AND family_id = $familyId`.
10
+ * 4. On success, advances the watermark to the max `ts` observed.
11
+ *
12
+ * Auth recovery (spec §R2 pusher): a 401 triggers `refreshToken()` and one
13
+ * retry. Other errors preserve the watermark so nothing is lost.
14
+ *
15
+ * Parallel push (T-18): `pushAll(tables, ctx)` fans out via
16
+ * `Promise.allSettled` so per-table failures don't hold up the rest.
17
+ */
18
+ import type { HybridDuckDB } from '../core/engine';
19
+ import type { AttachContext } from '../core/types';
20
+ import { SyncError } from './errors';
21
+ import type { PushEmitter } from './events';
22
+ import type { WatermarkStore } from './watermark';
23
+ export interface PushResult {
24
+ table: string;
25
+ rowsPushed: number;
26
+ ok: boolean;
27
+ error?: SyncError;
28
+ }
29
+ export interface R2PusherConfig {
30
+ engine: HybridDuckDB;
31
+ watermark: WatermarkStore;
32
+ /** Refresh the attach token on 401. Called once per push before retrying. */
33
+ refreshToken?: () => Promise<void>;
34
+ /**
35
+ * Detects a 401 from an arbitrary thrown value. Defaults to a `.message`
36
+ * substring match on '401' or 'unauthorized'.
37
+ */
38
+ is401?: (err: unknown) => boolean;
39
+ /**
40
+ * SQL name builders. Defaults follow the warehouse convention:
41
+ * local = `main.<table>`
42
+ * remote = `zone_<familyId>.<table>`
43
+ * Override if the warehouse module produces different aliases.
44
+ */
45
+ localTable?: (ctx: AttachContext, table: string) => string;
46
+ remoteTable?: (ctx: AttachContext, table: string) => string;
47
+ /**
48
+ * Optional event-bus adapter. Emits `${table}:sync_complete` after each
49
+ * successful push. See `events.ts` / `bindPushEvents()`.
50
+ */
51
+ emit?: PushEmitter;
52
+ }
53
+ export declare class R2Pusher {
54
+ #private;
55
+ constructor(config: R2PusherConfig);
56
+ push(table: string, ctx: AttachContext): Promise<PushResult>;
57
+ pushAll(tables: readonly string[], ctx: AttachContext): Promise<PushResult[]>;
58
+ }
59
+ //# sourceMappingURL=pusher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pusher.d.ts","sourceRoot":"","sources":["../../src/sync/pusher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,EAAE,EAAE,OAAO,CAAA;IACX,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,cAAc,CAAA;IACzB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAA;IACjC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3D;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB;AAYD,qBAAa,QAAQ;;gBASP,MAAM,EAAE,cAAc;IAU5B,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC;IAwB5D,OAAO,CACX,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,UAAU,EAAE,CAAC;CAkEzB"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * R2 pusher (T-17 + T-18).
3
+ *
4
+ * Pushes per-table deltas from the local DuckDB warehouse to the attached R2
5
+ * Iceberg zone. Each table push:
6
+ * 1. Reads the push watermark for `(brand, familyId, table)`.
7
+ * 2. Computes the max `ts` currently in local for that filter.
8
+ * 3. Issues `INSERT INTO <remote>.<table> SELECT * FROM <local>.<table>
9
+ * WHERE ts > $watermark AND family_id = $familyId`.
10
+ * 4. On success, advances the watermark to the max `ts` observed.
11
+ *
12
+ * Auth recovery (spec §R2 pusher): a 401 triggers `refreshToken()` and one
13
+ * retry. Other errors preserve the watermark so nothing is lost.
14
+ *
15
+ * Parallel push (T-18): `pushAll(tables, ctx)` fans out via
16
+ * `Promise.allSettled` so per-table failures don't hold up the rest.
17
+ */
18
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
19
+ if (kind === "m") throw new TypeError("Private method is not writable");
20
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
21
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
22
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
23
+ };
24
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
25
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
26
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
27
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
28
+ };
29
+ var _R2Pusher_instances, _R2Pusher_engine, _R2Pusher_watermark, _R2Pusher_refreshToken, _R2Pusher_is401, _R2Pusher_localTable, _R2Pusher_remoteTable, _R2Pusher_emit, _R2Pusher_pushOrCatch, _R2Pusher_pushOnce, _R2Pusher_toFailure;
30
+ import { SyncError } from './errors';
31
+ const DEFAULT_IS_401 = (err) => {
32
+ if (!err || typeof err !== 'object')
33
+ return false;
34
+ const msg = String(err.message ?? '').toLowerCase();
35
+ return msg.includes('401') || msg.includes('unauthorized');
36
+ };
37
+ const DEFAULT_LOCAL = (_c, table) => `main.${table}`;
38
+ const DEFAULT_REMOTE = (c, table) => `zone_${c.tenantId}.${table}`;
39
+ export class R2Pusher {
40
+ constructor(config) {
41
+ _R2Pusher_instances.add(this);
42
+ _R2Pusher_engine.set(this, void 0);
43
+ _R2Pusher_watermark.set(this, void 0);
44
+ _R2Pusher_refreshToken.set(this, void 0);
45
+ _R2Pusher_is401.set(this, void 0);
46
+ _R2Pusher_localTable.set(this, void 0);
47
+ _R2Pusher_remoteTable.set(this, void 0);
48
+ _R2Pusher_emit.set(this, void 0);
49
+ __classPrivateFieldSet(this, _R2Pusher_engine, config.engine, "f");
50
+ __classPrivateFieldSet(this, _R2Pusher_watermark, config.watermark, "f");
51
+ __classPrivateFieldSet(this, _R2Pusher_refreshToken, config.refreshToken, "f");
52
+ __classPrivateFieldSet(this, _R2Pusher_is401, config.is401 ?? DEFAULT_IS_401, "f");
53
+ __classPrivateFieldSet(this, _R2Pusher_localTable, config.localTable ?? DEFAULT_LOCAL, "f");
54
+ __classPrivateFieldSet(this, _R2Pusher_remoteTable, config.remoteTable ?? DEFAULT_REMOTE, "f");
55
+ __classPrivateFieldSet(this, _R2Pusher_emit, config.emit, "f");
56
+ }
57
+ async push(table, ctx) {
58
+ const result = await __classPrivateFieldGet(this, _R2Pusher_instances, "m", _R2Pusher_pushOrCatch).call(this, table, ctx);
59
+ __classPrivateFieldGet(this, _R2Pusher_emit, "f")?.call(this, result);
60
+ return result;
61
+ }
62
+ async pushAll(tables, ctx) {
63
+ const settled = await Promise.allSettled(tables.map(t => this.push(t, ctx)));
64
+ return settled.map((r, i) => {
65
+ if (r.status === 'fulfilled')
66
+ return r.value;
67
+ const err = r.reason instanceof SyncError
68
+ ? r.reason
69
+ : new SyncError('push_failed', 'push rejected', r.reason);
70
+ return { table: tables[i], rowsPushed: 0, ok: false, error: err };
71
+ });
72
+ }
73
+ }
74
+ _R2Pusher_engine = new WeakMap(), _R2Pusher_watermark = new WeakMap(), _R2Pusher_refreshToken = new WeakMap(), _R2Pusher_is401 = new WeakMap(), _R2Pusher_localTable = new WeakMap(), _R2Pusher_remoteTable = new WeakMap(), _R2Pusher_emit = new WeakMap(), _R2Pusher_instances = new WeakSet(), _R2Pusher_pushOrCatch = async function _R2Pusher_pushOrCatch(table, ctx) {
75
+ try {
76
+ return await __classPrivateFieldGet(this, _R2Pusher_instances, "m", _R2Pusher_pushOnce).call(this, table, ctx);
77
+ }
78
+ catch (err) {
79
+ if (__classPrivateFieldGet(this, _R2Pusher_is401, "f").call(this, err) && __classPrivateFieldGet(this, _R2Pusher_refreshToken, "f")) {
80
+ try {
81
+ await __classPrivateFieldGet(this, _R2Pusher_refreshToken, "f").call(this);
82
+ return await __classPrivateFieldGet(this, _R2Pusher_instances, "m", _R2Pusher_pushOnce).call(this, table, ctx);
83
+ }
84
+ catch (retryErr) {
85
+ return __classPrivateFieldGet(this, _R2Pusher_instances, "m", _R2Pusher_toFailure).call(this, table, retryErr, 'token_expired');
86
+ }
87
+ }
88
+ return __classPrivateFieldGet(this, _R2Pusher_instances, "m", _R2Pusher_toFailure).call(this, table, err, 'push_failed');
89
+ }
90
+ }, _R2Pusher_pushOnce = async function _R2Pusher_pushOnce(table, ctx) {
91
+ const watermark = await __classPrivateFieldGet(this, _R2Pusher_watermark, "f").get(ctx.brand, ctx.tenantId, table, 'push');
92
+ const local = __classPrivateFieldGet(this, _R2Pusher_localTable, "f").call(this, ctx, table);
93
+ const remote = __classPrivateFieldGet(this, _R2Pusher_remoteTable, "f").call(this, ctx, table);
94
+ // Max ts to advance the watermark to. Null result → no new rows.
95
+ const maxRows = await __classPrivateFieldGet(this, _R2Pusher_engine, "f").execute(`SELECT MAX(ts) AS max_ts, COUNT(*) AS row_count FROM ${local} WHERE ts > $watermark AND family_id = $familyId`, { watermark: watermark.toISOString(), familyId: ctx.tenantId });
96
+ const first = maxRows[0];
97
+ const rowCount = Number(first?.row_count ?? 0);
98
+ if (rowCount === 0) {
99
+ return { table, rowsPushed: 0, ok: true };
100
+ }
101
+ await __classPrivateFieldGet(this, _R2Pusher_engine, "f").execute(`INSERT INTO ${remote} SELECT * FROM ${local} WHERE ts > $watermark AND family_id = $familyId`, { watermark: watermark.toISOString(), familyId: ctx.tenantId });
102
+ if (first?.max_ts) {
103
+ const nextWatermark = new Date(first.max_ts);
104
+ if (!Number.isNaN(nextWatermark.getTime())) {
105
+ await __classPrivateFieldGet(this, _R2Pusher_watermark, "f").advance(ctx.brand, ctx.tenantId, table, 'push', nextWatermark);
106
+ }
107
+ }
108
+ return { table, rowsPushed: rowCount, ok: true };
109
+ }, _R2Pusher_toFailure = function _R2Pusher_toFailure(table, cause, code) {
110
+ const err = cause instanceof SyncError
111
+ ? cause
112
+ : new SyncError(code, `push failed for ${table}`, cause);
113
+ return { table, rowsPushed: 0, ok: false, error: err };
114
+ };
115
+ //# sourceMappingURL=pusher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pusher.js","sourceRoot":"","sources":["../../src/sync/pusher.ts"],"names":[],"mappings":"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"}