@mongrov/analytics 0.1.0-alpha.0 → 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.
- package/README.md +456 -0
- package/dist/core/context.d.ts +24 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +30 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/engine.d.ts +56 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +119 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/extensions.d.ts +33 -0
- package/dist/core/extensions.d.ts.map +1 -0
- package/dist/core/extensions.js +58 -0
- package/dist/core/extensions.js.map +1 -0
- package/dist/core/factory.d.ts +22 -5
- package/dist/core/factory.d.ts.map +1 -1
- package/dist/core/factory.js +231 -6
- package/dist/core/factory.js.map +1 -1
- package/dist/core/hooks.d.ts +32 -13
- package/dist/core/hooks.d.ts.map +1 -1
- package/dist/core/hooks.js +137 -15
- package/dist/core/hooks.js.map +1 -1
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/logger.d.ts +25 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +33 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/machine.d.ts +174 -0
- package/dist/core/machine.d.ts.map +1 -0
- package/dist/core/machine.js +228 -0
- package/dist/core/machine.js.map +1 -0
- package/dist/core/metric_metadata.d.ts +128 -0
- package/dist/core/metric_metadata.d.ts.map +1 -0
- package/dist/core/metric_metadata.js +47 -0
- package/dist/core/metric_metadata.js.map +1 -0
- package/dist/core/migrations.d.ts +52 -0
- package/dist/core/migrations.d.ts.map +1 -0
- package/dist/core/migrations.js +66 -0
- package/dist/core/migrations.js.map +1 -0
- package/dist/core/persistence.d.ts +50 -0
- package/dist/core/persistence.d.ts.map +1 -0
- package/dist/core/persistence.js +80 -0
- package/dist/core/persistence.js.map +1 -0
- package/dist/core/retention.d.ts +90 -0
- package/dist/core/retention.d.ts.map +1 -0
- package/dist/core/retention.js +119 -0
- package/dist/core/retention.js.map +1 -0
- package/dist/core/schemas.d.ts +35 -0
- package/dist/core/schemas.d.ts.map +1 -0
- package/dist/core/schemas.js +209 -0
- package/dist/core/schemas.js.map +1 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/warehouse.d.ts +58 -0
- package/dist/core/warehouse.d.ts.map +1 -0
- package/dist/core/warehouse.js +116 -0
- package/dist/core/warehouse.js.map +1 -0
- package/dist/sync/buffer.d.ts +51 -0
- package/dist/sync/buffer.d.ts.map +1 -0
- package/dist/sync/buffer.js +188 -0
- package/dist/sync/buffer.js.map +1 -0
- package/dist/sync/context.d.ts +17 -0
- package/dist/sync/context.d.ts.map +1 -0
- package/dist/sync/context.js +23 -0
- package/dist/sync/context.js.map +1 -0
- package/dist/sync/errors.d.ts +19 -0
- package/dist/sync/errors.d.ts.map +1 -0
- package/dist/sync/errors.js +21 -0
- package/dist/sync/errors.js.map +1 -0
- package/dist/sync/events.d.ts +49 -0
- package/dist/sync/events.d.ts.map +1 -0
- package/dist/sync/events.js +46 -0
- package/dist/sync/events.js.map +1 -0
- package/dist/sync/factory.d.ts +57 -0
- package/dist/sync/factory.d.ts.map +1 -0
- package/dist/sync/factory.js +249 -0
- package/dist/sync/factory.js.map +1 -0
- package/dist/sync/fetcher.d.ts +74 -0
- package/dist/sync/fetcher.d.ts.map +1 -0
- package/dist/sync/fetcher.js +166 -0
- package/dist/sync/fetcher.js.map +1 -0
- package/dist/sync/flusher.d.ts +93 -0
- package/dist/sync/flusher.d.ts.map +1 -0
- package/dist/sync/flusher.js +234 -0
- package/dist/sync/flusher.js.map +1 -0
- package/dist/sync/hooks.d.ts +17 -0
- package/dist/sync/hooks.d.ts.map +1 -0
- package/dist/sync/hooks.js +48 -0
- package/dist/sync/hooks.js.map +1 -0
- package/dist/sync/index.d.ts +32 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +36 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/manager.d.ts +48 -0
- package/dist/sync/manager.d.ts.map +1 -0
- package/dist/sync/manager.js +9 -0
- package/dist/sync/manager.js.map +1 -0
- package/dist/sync/mapper/activity.d.ts +31 -0
- package/dist/sync/mapper/activity.d.ts.map +1 -0
- package/dist/sync/mapper/activity.js +56 -0
- package/dist/sync/mapper/activity.js.map +1 -0
- package/dist/sync/mapper/battery.d.ts +12 -0
- package/dist/sync/mapper/battery.d.ts.map +1 -0
- package/dist/sync/mapper/battery.js +22 -0
- package/dist/sync/mapper/battery.js.map +1 -0
- package/dist/sync/mapper/firmware.d.ts +28 -0
- package/dist/sync/mapper/firmware.d.ts.map +1 -0
- package/dist/sync/mapper/firmware.js +43 -0
- package/dist/sync/mapper/firmware.js.map +1 -0
- package/dist/sync/mapper/heart-rate.d.ts +10 -0
- package/dist/sync/mapper/heart-rate.d.ts.map +1 -0
- package/dist/sync/mapper/heart-rate.js +19 -0
- package/dist/sync/mapper/heart-rate.js.map +1 -0
- package/dist/sync/mapper/hrv.d.ts +22 -0
- package/dist/sync/mapper/hrv.d.ts.map +1 -0
- package/dist/sync/mapper/hrv.js +56 -0
- package/dist/sync/mapper/hrv.js.map +1 -0
- package/dist/sync/mapper/ring-config.d.ts +33 -0
- package/dist/sync/mapper/ring-config.d.ts.map +1 -0
- package/dist/sync/mapper/ring-config.js +48 -0
- package/dist/sync/mapper/ring-config.js.map +1 -0
- package/dist/sync/mapper/sleep.d.ts +32 -0
- package/dist/sync/mapper/sleep.d.ts.map +1 -0
- package/dist/sync/mapper/sleep.js +103 -0
- package/dist/sync/mapper/sleep.js.map +1 -0
- package/dist/sync/mapper/spo2.d.ts +9 -0
- package/dist/sync/mapper/spo2.d.ts.map +1 -0
- package/dist/sync/mapper/spo2.js +18 -0
- package/dist/sync/mapper/spo2.js.map +1 -0
- package/dist/sync/mapper/temperature.d.ts +10 -0
- package/dist/sync/mapper/temperature.d.ts.map +1 -0
- package/dist/sync/mapper/temperature.js +19 -0
- package/dist/sync/mapper/temperature.js.map +1 -0
- package/dist/sync/mapper/time.d.ts +49 -0
- package/dist/sync/mapper/time.d.ts.map +1 -0
- package/dist/sync/mapper/time.js +107 -0
- package/dist/sync/mapper/time.js.map +1 -0
- package/dist/sync/mapper/types.d.ts +152 -0
- package/dist/sync/mapper/types.d.ts.map +1 -0
- package/dist/sync/mapper/types.js +15 -0
- package/dist/sync/mapper/types.js.map +1 -0
- package/dist/sync/overflow.d.ts +30 -0
- package/dist/sync/overflow.d.ts.map +1 -0
- package/dist/sync/overflow.js +114 -0
- package/dist/sync/overflow.js.map +1 -0
- package/dist/sync/pusher.d.ts +59 -0
- package/dist/sync/pusher.d.ts.map +1 -0
- package/dist/sync/pusher.js +115 -0
- package/dist/sync/pusher.js.map +1 -0
- package/dist/sync/scheduler.d.ts +84 -0
- package/dist/sync/scheduler.d.ts.map +1 -0
- package/dist/sync/scheduler.js +132 -0
- package/dist/sync/scheduler.js.map +1 -0
- package/dist/sync/triggers.d.ts +64 -0
- package/dist/sync/triggers.d.ts.map +1 -0
- package/dist/sync/triggers.js +113 -0
- package/dist/sync/triggers.js.map +1 -0
- package/dist/sync/types.d.ts +50 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +8 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/sync/watermark.d.ts +32 -0
- package/dist/sync/watermark.d.ts.map +1 -0
- package/dist/sync/watermark.js +61 -0
- package/dist/sync/watermark.js.map +1 -0
- package/package.json +27 -16
- package/LICENSE +0 -21
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T-26 — `createSyncManager()` factory.
|
|
3
|
+
*
|
|
4
|
+
* Zod-validates config at construction time (fail-fast), then assembles the
|
|
5
|
+
* full sync stack:
|
|
6
|
+
*
|
|
7
|
+
* OverflowStore ← storage KV
|
|
8
|
+
* SensorBuffer ← OverflowStore + policy + byte budget
|
|
9
|
+
* BatchFlusher ← engine + buffer + columnOrder + optional event bus
|
|
10
|
+
* FlushTriggers ← buffer + flusher + row/age thresholds
|
|
11
|
+
* WatermarkStore← storage KV
|
|
12
|
+
* R2Pusher ← engine + WatermarkStore + optional refreshToken + event bus
|
|
13
|
+
* R2Fetcher ← engine + WatermarkStore + tables + prefetchPolicy
|
|
14
|
+
* SyncScheduler ← wires flush + push + fetch into a background cycle
|
|
15
|
+
*
|
|
16
|
+
* The returned `SyncManager` exposes a compact `SensorSink` + scheduler
|
|
17
|
+
* controls; hooks in `hooks.ts` subscribe to state transitions.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { SensorBuffer } from './buffer';
|
|
21
|
+
import { bindFlushEvents, bindPushEvents } from './events';
|
|
22
|
+
import { R2Fetcher } from './fetcher';
|
|
23
|
+
import { BatchFlusher } from './flusher';
|
|
24
|
+
import { mapFirmwareExport } from './mapper/firmware';
|
|
25
|
+
import { OverflowStore } from './overflow';
|
|
26
|
+
import { R2Pusher } from './pusher';
|
|
27
|
+
import { SyncScheduler, } from './scheduler';
|
|
28
|
+
import { FlushTriggers } from './triggers';
|
|
29
|
+
import { WatermarkStore } from './watermark';
|
|
30
|
+
const overflowPolicyEnum = z.enum(['drop-oldest', 'drop-newest', 'block']);
|
|
31
|
+
const prefetchPolicySchema = z.discriminatedUnion('kind', [
|
|
32
|
+
z.object({ kind: z.literal('all-family-on-attach'), windowDays: z.number().int().positive() }),
|
|
33
|
+
z.object({
|
|
34
|
+
kind: z.literal('recent-active-only'),
|
|
35
|
+
activeDays: z.number().int().positive(),
|
|
36
|
+
windowDays: z.number().int().positive(),
|
|
37
|
+
}),
|
|
38
|
+
z.object({ kind: z.literal('lazy') }),
|
|
39
|
+
]);
|
|
40
|
+
const configSchema = z.object({
|
|
41
|
+
tables: z.array(z.string().min(1)).min(1),
|
|
42
|
+
columnOrder: z.record(z.string(), z.array(z.string()).readonly()),
|
|
43
|
+
prefetchPolicy: prefetchPolicySchema,
|
|
44
|
+
flush: z.object({
|
|
45
|
+
maxRows: z.number().int().positive().optional(),
|
|
46
|
+
maxAgeMs: z.number().int().positive().optional(),
|
|
47
|
+
concurrency: z.number().int().positive().optional(),
|
|
48
|
+
}).optional(),
|
|
49
|
+
overflow: z.object({
|
|
50
|
+
maxBufferBytes: z.number().int().positive().optional(),
|
|
51
|
+
policy: overflowPolicyEnum.optional(),
|
|
52
|
+
}).optional(),
|
|
53
|
+
scheduler: z.object({
|
|
54
|
+
requiresCharging: z.boolean().optional(),
|
|
55
|
+
requiresWifi: z.boolean().optional(),
|
|
56
|
+
taskName: z.string().optional(),
|
|
57
|
+
}).optional(),
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Assemble a fully wired `SyncManager`. Throws `z.ZodError` if the config is
|
|
61
|
+
* malformed (e.g., missing columnOrder entries or negative row limits).
|
|
62
|
+
*/
|
|
63
|
+
export function createSyncManager(config) {
|
|
64
|
+
configSchema.parse({
|
|
65
|
+
tables: config.tables,
|
|
66
|
+
columnOrder: config.columnOrder,
|
|
67
|
+
prefetchPolicy: config.prefetchPolicy,
|
|
68
|
+
flush: config.flush,
|
|
69
|
+
overflow: config.overflow,
|
|
70
|
+
scheduler: config.scheduler,
|
|
71
|
+
});
|
|
72
|
+
const overflow = new OverflowStore(config.storage);
|
|
73
|
+
const buffer = new SensorBuffer({
|
|
74
|
+
overflow,
|
|
75
|
+
maxBufferBytes: config.overflow?.maxBufferBytes,
|
|
76
|
+
policy: config.overflow?.policy,
|
|
77
|
+
});
|
|
78
|
+
const progress = { pendingByTable: {} };
|
|
79
|
+
const busFlushEmit = config.eventBus ? bindFlushEvents(config.eventBus) : undefined;
|
|
80
|
+
// Compose the flusher emitter with local progress tracking so hooks + the
|
|
81
|
+
// event bus both see every event.
|
|
82
|
+
const flushEmitter = (event) => {
|
|
83
|
+
if (event.type === 'flushed') {
|
|
84
|
+
progress.pendingByTable[event.payload.table] = 0;
|
|
85
|
+
progress.lastFlushAt = Date.now();
|
|
86
|
+
}
|
|
87
|
+
busFlushEmit?.(event);
|
|
88
|
+
};
|
|
89
|
+
const engineAsHybrid = config.analytics;
|
|
90
|
+
const flusher = new BatchFlusher({
|
|
91
|
+
engine: engineAsHybrid,
|
|
92
|
+
buffer,
|
|
93
|
+
columnOrder: config.columnOrder,
|
|
94
|
+
concurrency: config.flush?.concurrency,
|
|
95
|
+
emit: flushEmitter,
|
|
96
|
+
});
|
|
97
|
+
const triggers = new FlushTriggers({
|
|
98
|
+
buffer,
|
|
99
|
+
flusher,
|
|
100
|
+
maxRows: config.flush?.maxRows,
|
|
101
|
+
maxAgeMs: config.flush?.maxAgeMs,
|
|
102
|
+
});
|
|
103
|
+
const watermark = new WatermarkStore({ kv: config.storage });
|
|
104
|
+
const busPushEmit = config.eventBus ? bindPushEvents(config.eventBus) : undefined;
|
|
105
|
+
const pusher = new R2Pusher({
|
|
106
|
+
engine: engineAsHybrid,
|
|
107
|
+
watermark,
|
|
108
|
+
refreshToken: config.refreshToken,
|
|
109
|
+
emit: (result) => {
|
|
110
|
+
if (result.ok && result.rowsPushed > 0) {
|
|
111
|
+
progress.lastSyncCompleteAt = Date.now();
|
|
112
|
+
}
|
|
113
|
+
busPushEmit?.(result);
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
const fetcher = new R2Fetcher({
|
|
117
|
+
engine: engineAsHybrid,
|
|
118
|
+
watermark,
|
|
119
|
+
tables: [...config.tables],
|
|
120
|
+
});
|
|
121
|
+
const scheduler = new SyncScheduler({
|
|
122
|
+
backgroundTask: config.backgroundTask ?? noopBackgroundTask(),
|
|
123
|
+
constraints: config.constraints ?? alwaysAllowedConstraints(),
|
|
124
|
+
coordinator: {
|
|
125
|
+
flushAll: async () => {
|
|
126
|
+
await Promise.all(config.tables.map(t => flusher.flush(t, 'scheduled')));
|
|
127
|
+
},
|
|
128
|
+
pushAll: async (tables, ctx) => pusher.pushAll(tables, ctx),
|
|
129
|
+
fetchIncremental: async (ctx) => fetcher.fetchIncremental(ctx),
|
|
130
|
+
},
|
|
131
|
+
tables: [...config.tables],
|
|
132
|
+
ctx: config.ctx,
|
|
133
|
+
taskName: config.scheduler?.taskName,
|
|
134
|
+
constraintPolicy: {
|
|
135
|
+
requireCharging: config.scheduler?.requiresCharging,
|
|
136
|
+
requireWifi: config.scheduler?.requiresWifi,
|
|
137
|
+
},
|
|
138
|
+
logger: config.logger,
|
|
139
|
+
});
|
|
140
|
+
const state = {
|
|
141
|
+
scheduler: 'idle',
|
|
142
|
+
};
|
|
143
|
+
const subscribers = new Set();
|
|
144
|
+
const notify = () => {
|
|
145
|
+
for (const fn of subscribers)
|
|
146
|
+
fn(state);
|
|
147
|
+
};
|
|
148
|
+
scheduler.subscribe((next) => {
|
|
149
|
+
state.scheduler = next;
|
|
150
|
+
notify();
|
|
151
|
+
});
|
|
152
|
+
const sink = createSensorSink({ buffer, flusher, triggers, tables: config.tables });
|
|
153
|
+
return {
|
|
154
|
+
analytics: config.analytics,
|
|
155
|
+
sink,
|
|
156
|
+
flusher,
|
|
157
|
+
pusher,
|
|
158
|
+
fetcher,
|
|
159
|
+
scheduler,
|
|
160
|
+
state: () => ({ ...state }),
|
|
161
|
+
progress: () => ({
|
|
162
|
+
pendingByTable: { ...progress.pendingByTable },
|
|
163
|
+
lastFlushAt: progress.lastFlushAt,
|
|
164
|
+
lastSyncCompleteAt: progress.lastSyncCompleteAt,
|
|
165
|
+
}),
|
|
166
|
+
subscribe: (fn) => {
|
|
167
|
+
subscribers.add(fn);
|
|
168
|
+
return () => subscribers.delete(fn);
|
|
169
|
+
},
|
|
170
|
+
start: async () => {
|
|
171
|
+
await scheduler.start();
|
|
172
|
+
},
|
|
173
|
+
stop: async () => {
|
|
174
|
+
await scheduler.stop();
|
|
175
|
+
},
|
|
176
|
+
triggerNow: () => scheduler.triggerNow(),
|
|
177
|
+
prefetch: (ctx) => fetcher.prefetchOnAttach(ctx, config.prefetchPolicy),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function createSensorSink({ buffer, flusher, triggers, tables }) {
|
|
181
|
+
return {
|
|
182
|
+
push: async (batch) => {
|
|
183
|
+
await buffer.push(batch);
|
|
184
|
+
triggers.noteEnqueue(batch.table, Date.now());
|
|
185
|
+
},
|
|
186
|
+
pushFirmware: async (fw, ctx) => {
|
|
187
|
+
const mapped = mapFirmwareExport(fw, ctx);
|
|
188
|
+
const asRows = (rows) => rows;
|
|
189
|
+
const mappedTables = [
|
|
190
|
+
['hrv', asRows(mapped.hrv)],
|
|
191
|
+
['heart_rate', asRows(mapped.heart_rate)],
|
|
192
|
+
['spo2', asRows(mapped.spo2)],
|
|
193
|
+
['temperature', asRows(mapped.temperature)],
|
|
194
|
+
['activity', asRows(mapped.activity)],
|
|
195
|
+
['activity_bucket', asRows(mapped.activity_bucket)],
|
|
196
|
+
['sleep_session', asRows(mapped.sleep_session)],
|
|
197
|
+
['sleep_stage', asRows(mapped.sleep_stage)],
|
|
198
|
+
['sleep_raw', asRows(mapped.sleep_raw)],
|
|
199
|
+
['device_event', asRows(mapped.device_event)],
|
|
200
|
+
['device_config', asRows(mapped.device_config)],
|
|
201
|
+
];
|
|
202
|
+
for (const [table, rows] of mappedTables) {
|
|
203
|
+
if (!rows || rows.length === 0)
|
|
204
|
+
continue;
|
|
205
|
+
await buffer.push({
|
|
206
|
+
table,
|
|
207
|
+
brand: ctx.brand,
|
|
208
|
+
familyId: ctx.familyId,
|
|
209
|
+
userId: ctx.userId,
|
|
210
|
+
deviceId: ctx.deviceId,
|
|
211
|
+
rows,
|
|
212
|
+
});
|
|
213
|
+
triggers.noteEnqueue(table, Date.now());
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
flush: async () => {
|
|
217
|
+
const results = [];
|
|
218
|
+
for (const table of tables) {
|
|
219
|
+
results.push(await flusher.flush(table, 'manual'));
|
|
220
|
+
triggers.noteDrain(table);
|
|
221
|
+
}
|
|
222
|
+
return results;
|
|
223
|
+
},
|
|
224
|
+
pendingRowCount: async (table) => {
|
|
225
|
+
const s = await buffer.size(table);
|
|
226
|
+
return s.inMemory + s.overflow;
|
|
227
|
+
},
|
|
228
|
+
clear: async () => {
|
|
229
|
+
await buffer.clear();
|
|
230
|
+
for (const t of tables)
|
|
231
|
+
triggers.noteDrain(t);
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function noopBackgroundTask() {
|
|
236
|
+
const handlers = new Map();
|
|
237
|
+
return {
|
|
238
|
+
register: async (name, handler) => { handlers.set(name, handler); },
|
|
239
|
+
unregister: async (name) => { handlers.delete(name); },
|
|
240
|
+
isRegistered: async (name) => handlers.has(name),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function alwaysAllowedConstraints() {
|
|
244
|
+
return {
|
|
245
|
+
isCharging: async () => true,
|
|
246
|
+
isOnWifi: async () => true,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/sync/factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R2 fetcher (T-19 + T-20 + T-21).
|
|
3
|
+
*
|
|
4
|
+
* Reads-back path from the attached R2 Iceberg zone into the local DuckDB
|
|
5
|
+
* warehouse.
|
|
6
|
+
*
|
|
7
|
+
* Three modes:
|
|
8
|
+
* - `prefetchOnAttach(ctx, policy)` — one-shot bulk pull immediately after
|
|
9
|
+
* `analytics.attach()`, driven by a policy:
|
|
10
|
+
* * `all-family-on-attach` — every family row within `windowDays`
|
|
11
|
+
* * `recent-active-only` — active users first (CTE)
|
|
12
|
+
* * `lazy` — no-op
|
|
13
|
+
* - `fetchIncremental(ctx)` — pulls only rows newer than the fetch
|
|
14
|
+
* watermark; `ON CONFLICT DO NOTHING` keeps re-runs idempotent.
|
|
15
|
+
* - `fetchOnDemand(ctx, params)` — bypasses watermark; caller-supplied
|
|
16
|
+
* bounds (`since`, optional `until`, optional `limit`).
|
|
17
|
+
*
|
|
18
|
+
* Every path advances the fetch watermark on success so subsequent
|
|
19
|
+
* incremental pulls stay bounded.
|
|
20
|
+
*/
|
|
21
|
+
import type { HybridDuckDB } from '../core/engine';
|
|
22
|
+
import type { AttachContext } from '../core/types';
|
|
23
|
+
import { SyncError } from './errors';
|
|
24
|
+
import type { WatermarkStore } from './watermark';
|
|
25
|
+
export type PrefetchPolicy = {
|
|
26
|
+
kind: 'all-family-on-attach';
|
|
27
|
+
windowDays: number;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'recent-active-only';
|
|
30
|
+
activeDays: number;
|
|
31
|
+
windowDays: number;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'lazy';
|
|
34
|
+
};
|
|
35
|
+
export interface FetchResult {
|
|
36
|
+
table: string;
|
|
37
|
+
rowsFetched: number;
|
|
38
|
+
ok: boolean;
|
|
39
|
+
error?: SyncError;
|
|
40
|
+
}
|
|
41
|
+
export interface FetchParams {
|
|
42
|
+
table: string;
|
|
43
|
+
since: Date;
|
|
44
|
+
until?: Date;
|
|
45
|
+
limit?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface R2FetcherConfig {
|
|
48
|
+
engine: HybridDuckDB;
|
|
49
|
+
watermark: WatermarkStore;
|
|
50
|
+
/** Tables that participate in prefetch / incremental fetch. */
|
|
51
|
+
tables: readonly string[];
|
|
52
|
+
now?: () => Date;
|
|
53
|
+
localTable?: (ctx: AttachContext, table: string) => string;
|
|
54
|
+
remoteTable?: (ctx: AttachContext, table: string) => string;
|
|
55
|
+
}
|
|
56
|
+
export declare class R2Fetcher {
|
|
57
|
+
#private;
|
|
58
|
+
constructor(config: R2FetcherConfig);
|
|
59
|
+
/**
|
|
60
|
+
* T-19: one-shot bulk fetch keyed by policy. Returns per-table results so
|
|
61
|
+
* partial failure is observable.
|
|
62
|
+
*/
|
|
63
|
+
prefetchOnAttach(ctx: AttachContext, policy: PrefetchPolicy): Promise<FetchResult[]>;
|
|
64
|
+
/**
|
|
65
|
+
* T-20: pulls only rows newer than the fetch watermark; idempotent.
|
|
66
|
+
*/
|
|
67
|
+
fetchIncremental(ctx: AttachContext): Promise<FetchResult[]>;
|
|
68
|
+
/**
|
|
69
|
+
* T-21: caller-driven fetch that bypasses the watermark. Advances the
|
|
70
|
+
* fetch watermark only if the pulled range extends past the current one.
|
|
71
|
+
*/
|
|
72
|
+
fetchOnDemand(ctx: AttachContext, params: FetchParams): Promise<FetchResult>;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=fetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetcher.d.ts","sourceRoot":"","sources":["../../src/sync/fetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;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,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,MAAM,cAAc,GACpB;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,EAAE,EAAE,OAAO,CAAA;IACX,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,IAAI,CAAA;IACX,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,cAAc,CAAA;IACzB,+DAA+D;IAC/D,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,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;CAC5D;AAMD,qBAAa,SAAS;;gBAQR,MAAM,EAAE,eAAe;IASnC;;;OAGG;IACG,gBAAgB,CACpB,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,WAAW,EAAE,CAAC;IAoBzB;;OAEG;IACG,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAWlE;;;OAGG;IACG,aAAa,CACjB,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,WAAW,CAAC;CAuIxB"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* R2 fetcher (T-19 + T-20 + T-21).
|
|
3
|
+
*
|
|
4
|
+
* Reads-back path from the attached R2 Iceberg zone into the local DuckDB
|
|
5
|
+
* warehouse.
|
|
6
|
+
*
|
|
7
|
+
* Three modes:
|
|
8
|
+
* - `prefetchOnAttach(ctx, policy)` — one-shot bulk pull immediately after
|
|
9
|
+
* `analytics.attach()`, driven by a policy:
|
|
10
|
+
* * `all-family-on-attach` — every family row within `windowDays`
|
|
11
|
+
* * `recent-active-only` — active users first (CTE)
|
|
12
|
+
* * `lazy` — no-op
|
|
13
|
+
* - `fetchIncremental(ctx)` — pulls only rows newer than the fetch
|
|
14
|
+
* watermark; `ON CONFLICT DO NOTHING` keeps re-runs idempotent.
|
|
15
|
+
* - `fetchOnDemand(ctx, params)` — bypasses watermark; caller-supplied
|
|
16
|
+
* bounds (`since`, optional `until`, optional `limit`).
|
|
17
|
+
*
|
|
18
|
+
* Every path advances the fetch watermark on success so subsequent
|
|
19
|
+
* incremental pulls stay bounded.
|
|
20
|
+
*/
|
|
21
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
22
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
23
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
24
|
+
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");
|
|
25
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
26
|
+
};
|
|
27
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
28
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
29
|
+
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");
|
|
30
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
31
|
+
};
|
|
32
|
+
var _R2Fetcher_instances, _R2Fetcher_engine, _R2Fetcher_watermark, _R2Fetcher_tables, _R2Fetcher_now, _R2Fetcher_localTable, _R2Fetcher_remoteTable, _R2Fetcher_prefetchOne, _R2Fetcher_fetchIncrementalOne, _R2Fetcher_countFetched, _R2Fetcher_toFailure;
|
|
33
|
+
import { SyncError } from './errors';
|
|
34
|
+
const DEFAULT_LOCAL = (_c, table) => `main.${table}`;
|
|
35
|
+
const DEFAULT_REMOTE = (c, table) => `zone_${c.tenantId}.${table}`;
|
|
36
|
+
export class R2Fetcher {
|
|
37
|
+
constructor(config) {
|
|
38
|
+
_R2Fetcher_instances.add(this);
|
|
39
|
+
_R2Fetcher_engine.set(this, void 0);
|
|
40
|
+
_R2Fetcher_watermark.set(this, void 0);
|
|
41
|
+
_R2Fetcher_tables.set(this, void 0);
|
|
42
|
+
_R2Fetcher_now.set(this, void 0);
|
|
43
|
+
_R2Fetcher_localTable.set(this, void 0);
|
|
44
|
+
_R2Fetcher_remoteTable.set(this, void 0);
|
|
45
|
+
__classPrivateFieldSet(this, _R2Fetcher_engine, config.engine, "f");
|
|
46
|
+
__classPrivateFieldSet(this, _R2Fetcher_watermark, config.watermark, "f");
|
|
47
|
+
__classPrivateFieldSet(this, _R2Fetcher_tables, config.tables, "f");
|
|
48
|
+
__classPrivateFieldSet(this, _R2Fetcher_now, config.now ?? (() => new Date()), "f");
|
|
49
|
+
__classPrivateFieldSet(this, _R2Fetcher_localTable, config.localTable ?? DEFAULT_LOCAL, "f");
|
|
50
|
+
__classPrivateFieldSet(this, _R2Fetcher_remoteTable, config.remoteTable ?? DEFAULT_REMOTE, "f");
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* T-19: one-shot bulk fetch keyed by policy. Returns per-table results so
|
|
54
|
+
* partial failure is observable.
|
|
55
|
+
*/
|
|
56
|
+
async prefetchOnAttach(ctx, policy) {
|
|
57
|
+
if (policy.kind === 'lazy') {
|
|
58
|
+
return __classPrivateFieldGet(this, _R2Fetcher_tables, "f").map(t => ({ table: t, rowsFetched: 0, ok: true }));
|
|
59
|
+
}
|
|
60
|
+
const cutoffMs = __classPrivateFieldGet(this, _R2Fetcher_now, "f").call(this).getTime() - policy.windowDays * 24 * 60 * 60 * 1000;
|
|
61
|
+
const cutoff = new Date(cutoffMs).toISOString();
|
|
62
|
+
const settled = await Promise.allSettled(__classPrivateFieldGet(this, _R2Fetcher_tables, "f").map(table => __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_prefetchOne).call(this, ctx, table, policy, cutoff)));
|
|
63
|
+
return settled.map((r, i) => {
|
|
64
|
+
const table = __classPrivateFieldGet(this, _R2Fetcher_tables, "f")[i];
|
|
65
|
+
if (r.status === 'fulfilled')
|
|
66
|
+
return r.value;
|
|
67
|
+
return __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_toFailure).call(this, table, r.reason);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* T-20: pulls only rows newer than the fetch watermark; idempotent.
|
|
72
|
+
*/
|
|
73
|
+
async fetchIncremental(ctx) {
|
|
74
|
+
const settled = await Promise.allSettled(__classPrivateFieldGet(this, _R2Fetcher_tables, "f").map(table => __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_fetchIncrementalOne).call(this, ctx, table)));
|
|
75
|
+
return settled.map((r, i) => {
|
|
76
|
+
const table = __classPrivateFieldGet(this, _R2Fetcher_tables, "f")[i];
|
|
77
|
+
if (r.status === 'fulfilled')
|
|
78
|
+
return r.value;
|
|
79
|
+
return __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_toFailure).call(this, table, r.reason);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* T-21: caller-driven fetch that bypasses the watermark. Advances the
|
|
84
|
+
* fetch watermark only if the pulled range extends past the current one.
|
|
85
|
+
*/
|
|
86
|
+
async fetchOnDemand(ctx, params) {
|
|
87
|
+
try {
|
|
88
|
+
const local = __classPrivateFieldGet(this, _R2Fetcher_localTable, "f").call(this, ctx, params.table);
|
|
89
|
+
const remote = __classPrivateFieldGet(this, _R2Fetcher_remoteTable, "f").call(this, ctx, params.table);
|
|
90
|
+
const bindings = {
|
|
91
|
+
since: params.since.toISOString(),
|
|
92
|
+
familyId: ctx.tenantId,
|
|
93
|
+
};
|
|
94
|
+
let where = 'ts >= $since AND family_id = $familyId';
|
|
95
|
+
if (params.until) {
|
|
96
|
+
where += ' AND ts <= $until';
|
|
97
|
+
bindings.until = params.until.toISOString();
|
|
98
|
+
}
|
|
99
|
+
const limitClause = params.limit ? ` LIMIT ${Math.max(1, Math.floor(params.limit))}` : '';
|
|
100
|
+
const sql = `INSERT INTO ${local} SELECT * FROM ${remote} WHERE ${where}${limitClause} ON CONFLICT DO NOTHING`;
|
|
101
|
+
await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(sql, bindings);
|
|
102
|
+
const countRows = await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(`SELECT COUNT(*) AS c FROM ${remote} WHERE ${where}${limitClause}`, bindings);
|
|
103
|
+
const rowsFetched = Number(countRows[0]?.c ?? 0);
|
|
104
|
+
const upperBound = params.until ?? __classPrivateFieldGet(this, _R2Fetcher_now, "f").call(this);
|
|
105
|
+
await __classPrivateFieldGet(this, _R2Fetcher_watermark, "f").advance(ctx.brand, ctx.tenantId, params.table, 'fetch', upperBound);
|
|
106
|
+
return { table: params.table, rowsFetched, ok: true };
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
return __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_toFailure).call(this, params.table, err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
_R2Fetcher_engine = new WeakMap(), _R2Fetcher_watermark = new WeakMap(), _R2Fetcher_tables = new WeakMap(), _R2Fetcher_now = new WeakMap(), _R2Fetcher_localTable = new WeakMap(), _R2Fetcher_remoteTable = new WeakMap(), _R2Fetcher_instances = new WeakSet(), _R2Fetcher_prefetchOne = async function _R2Fetcher_prefetchOne(ctx, table, policy, cutoff) {
|
|
114
|
+
const local = __classPrivateFieldGet(this, _R2Fetcher_localTable, "f").call(this, ctx, table);
|
|
115
|
+
const remote = __classPrivateFieldGet(this, _R2Fetcher_remoteTable, "f").call(this, ctx, table);
|
|
116
|
+
if (policy.kind === 'all-family-on-attach') {
|
|
117
|
+
const sql = `INSERT INTO ${local} SELECT * FROM ${remote} WHERE family_id = $familyId AND ts >= $cutoff ON CONFLICT DO NOTHING`;
|
|
118
|
+
await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(sql, {
|
|
119
|
+
familyId: ctx.tenantId,
|
|
120
|
+
cutoff,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else if (policy.kind === 'recent-active-only') {
|
|
124
|
+
const activeCutoffMs = __classPrivateFieldGet(this, _R2Fetcher_now, "f").call(this).getTime() - policy.activeDays * 24 * 60 * 60 * 1000;
|
|
125
|
+
const activeCutoff = new Date(activeCutoffMs).toISOString();
|
|
126
|
+
// CTE picks user_ids seen in the recent window, then filters the window
|
|
127
|
+
// pull down to those users.
|
|
128
|
+
const sql = `WITH active AS (SELECT DISTINCT user_id FROM ${remote} WHERE family_id = $familyId AND ts >= $activeCutoff) INSERT INTO ${local} SELECT r.* FROM ${remote} r JOIN active a ON r.user_id = a.user_id WHERE r.family_id = $familyId AND r.ts >= $cutoff ON CONFLICT DO NOTHING`;
|
|
129
|
+
await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(sql, {
|
|
130
|
+
familyId: ctx.tenantId,
|
|
131
|
+
cutoff,
|
|
132
|
+
activeCutoff,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const rowsFetched = await __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_countFetched).call(this, remote, ctx, cutoff);
|
|
136
|
+
await __classPrivateFieldGet(this, _R2Fetcher_watermark, "f").advance(ctx.brand, ctx.tenantId, table, 'fetch', __classPrivateFieldGet(this, _R2Fetcher_now, "f").call(this));
|
|
137
|
+
return { table, rowsFetched, ok: true };
|
|
138
|
+
}, _R2Fetcher_fetchIncrementalOne = async function _R2Fetcher_fetchIncrementalOne(ctx, table) {
|
|
139
|
+
const watermark = await __classPrivateFieldGet(this, _R2Fetcher_watermark, "f").get(ctx.brand, ctx.tenantId, table, 'fetch');
|
|
140
|
+
const local = __classPrivateFieldGet(this, _R2Fetcher_localTable, "f").call(this, ctx, table);
|
|
141
|
+
const remote = __classPrivateFieldGet(this, _R2Fetcher_remoteTable, "f").call(this, ctx, table);
|
|
142
|
+
const sql = `INSERT INTO ${local} SELECT * FROM ${remote} WHERE ts > $watermark AND family_id = $familyId ON CONFLICT DO NOTHING`;
|
|
143
|
+
await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(sql, {
|
|
144
|
+
watermark: watermark.toISOString(),
|
|
145
|
+
familyId: ctx.tenantId,
|
|
146
|
+
});
|
|
147
|
+
const maxRows = await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(`SELECT MAX(ts) AS max_ts, COUNT(*) AS row_count FROM ${remote} WHERE ts > $watermark AND family_id = $familyId`, { watermark: watermark.toISOString(), familyId: ctx.tenantId });
|
|
148
|
+
const first = maxRows[0];
|
|
149
|
+
const rowsFetched = Number(first?.row_count ?? 0);
|
|
150
|
+
if (first?.max_ts) {
|
|
151
|
+
const nextWatermark = new Date(first.max_ts);
|
|
152
|
+
if (!Number.isNaN(nextWatermark.getTime())) {
|
|
153
|
+
await __classPrivateFieldGet(this, _R2Fetcher_watermark, "f").advance(ctx.brand, ctx.tenantId, table, 'fetch', nextWatermark);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { table, rowsFetched, ok: true };
|
|
157
|
+
}, _R2Fetcher_countFetched = async function _R2Fetcher_countFetched(remote, ctx, cutoff) {
|
|
158
|
+
const rows = await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(`SELECT COUNT(*) AS c FROM ${remote} WHERE family_id = $familyId AND ts >= $cutoff`, { familyId: ctx.tenantId, cutoff });
|
|
159
|
+
return Number(rows[0]?.c ?? 0);
|
|
160
|
+
}, _R2Fetcher_toFailure = function _R2Fetcher_toFailure(table, cause) {
|
|
161
|
+
const err = cause instanceof SyncError
|
|
162
|
+
? cause
|
|
163
|
+
: new SyncError('fetch_failed', `fetch failed for ${table}`, cause);
|
|
164
|
+
return { table, rowsFetched: 0, ok: false, error: err };
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=fetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../../src/sync/fetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;;;;;;;;;;;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAgCpC,MAAM,aAAa,GAAG,CAAC,EAAiB,EAAE,KAAa,EAAE,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAA;AAC3E,MAAM,cAAc,GAAG,CAAC,CAAgB,EAAE,KAAa,EAAE,EAAE,CACzD,QAAQ,CAAC,CAAC,QAAQ,IAAI,KAAK,EAAE,CAAA;AAE/B,MAAM,OAAO,SAAS;IAQpB,YAAY,MAAuB;;QAP1B,oCAAqB;QACrB,uCAA0B;QAC1B,oCAA0B;QAC1B,iCAAgB;QAChB,wCAA0D;QAC1D,yCAA2D;QAGlE,uBAAA,IAAI,qBAAW,MAAM,CAAC,MAAM,MAAA,CAAA;QAC5B,uBAAA,IAAI,wBAAc,MAAM,CAAC,SAAS,MAAA,CAAA;QAClC,uBAAA,IAAI,qBAAW,MAAM,CAAC,MAAM,MAAA,CAAA;QAC5B,uBAAA,IAAI,kBAAQ,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,MAAA,CAAA;QAC5C,uBAAA,IAAI,yBAAe,MAAM,CAAC,UAAU,IAAI,aAAa,MAAA,CAAA;QACrD,uBAAA,IAAI,0BAAgB,MAAM,CAAC,WAAW,IAAI,cAAc,MAAA,CAAA;IAC1D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,gBAAgB,CACpB,GAAkB,EAClB,MAAsB;QAEtB,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC3B,OAAO,uBAAA,IAAI,yBAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACxE,CAAC;QAED,MAAM,QAAQ,GAAG,uBAAA,IAAI,sBAAK,MAAT,IAAI,CAAO,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;QAChF,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAA;QAE/C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,uBAAA,IAAI,yBAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CACvB,uBAAA,IAAI,oDAAa,MAAjB,IAAI,EAAc,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAC9C,CACF,CAAA;QACD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC1B,MAAM,KAAK,GAAG,uBAAA,IAAI,yBAAQ,CAAC,CAAC,CAAE,CAAA;YAC9B,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW;gBAAE,OAAO,CAAC,CAAC,KAAK,CAAA;YAC5C,OAAO,uBAAA,IAAI,kDAAW,MAAf,IAAI,EAAY,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAA;QACzC,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,gBAAgB,CAAC,GAAkB;QACvC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,uBAAA,IAAI,yBAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,uBAAA,IAAI,4DAAqB,MAAzB,IAAI,EAAsB,GAAG,EAAE,KAAK,CAAC,CAAC,CACjE,CAAA;QACD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC1B,MAAM,KAAK,GAAG,uBAAA,IAAI,yBAAQ,CAAC,CAAC,CAAE,CAAA;YAC9B,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW;gBAAE,OAAO,CAAC,CAAC,KAAK,CAAA;YAC5C,OAAO,uBAAA,IAAI,kDAAW,MAAf,IAAI,EAAY,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAA;QACzC,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,aAAa,CACjB,GAAkB,EAClB,MAAmB;QAEnB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,uBAAA,IAAI,6BAAY,MAAhB,IAAI,EAAa,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;YACjD,MAAM,MAAM,GAAG,uBAAA,IAAI,8BAAa,MAAjB,IAAI,EAAc,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;YACnD,MAAM,QAAQ,GAA4B;gBACxC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE;gBACjC,QAAQ,EAAE,GAAG,CAAC,QAAQ;aACvB,CAAA;YACD,IAAI,KAAK,GAAG,wCAAwC,CAAA;YACpD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,KAAK,IAAI,mBAAmB,CAAA;gBAC5B,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAA;YAC7C,CAAC;YACD,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YACzF,MAAM,GAAG,GAAG,eAAe,KAAK,kBAAkB,MAAM,UAAU,KAAK,GAAG,WAAW,yBAAyB,CAAA;YAC9G,MAAM,uBAAA,IAAI,yBAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;YAEzC,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,yBAAQ,CAAC,OAAO,CAC1C,6BAA6B,MAAM,UAAU,KAAK,GAAG,WAAW,EAAE,EAClE,QAAQ,CACgB,CAAA;YAC1B,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;YAEhD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,IAAI,uBAAA,IAAI,sBAAK,MAAT,IAAI,CAAO,CAAA;YAC9C,MAAM,uBAAA,IAAI,4BAAW,CAAC,OAAO,CAC3B,GAAG,CAAC,KAAK,EACT,GAAG,CAAC,QAAQ,EACZ,MAAM,CAAC,KAAK,EACZ,OAAO,EACP,UAAU,CACX,CAAA;YACD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QACvD,CAAC;QACD,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,uBAAA,IAAI,kDAAW,MAAf,IAAI,EAAY,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC;CAmGF;0RAjGC,KAAK,iCACH,GAAkB,EAClB,KAAa,EACb,MAAsB,EACtB,MAAc;IAEd,MAAM,KAAK,GAAG,uBAAA,IAAI,6BAAY,MAAhB,IAAI,EAAa,GAAG,EAAE,KAAK,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAG,uBAAA,IAAI,8BAAa,MAAjB,IAAI,EAAc,GAAG,EAAE,KAAK,CAAC,CAAA;IAE5C,IAAI,MAAM,CAAC,IAAI,KAAK,sBAAsB,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,eAAe,KAAK,kBAAkB,MAAM,uEAAuE,CAAA;QAC/H,MAAM,uBAAA,IAAI,yBAAQ,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,MAAM;SACP,CAAC,CAAA;IACJ,CAAC;SACI,IAAI,MAAM,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;QAC9C,MAAM,cAAc,GAAG,uBAAA,IAAI,sBAAK,MAAT,IAAI,CAAO,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;QACtF,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,CAAA;QAC3D,wEAAwE;QACxE,4BAA4B;QAC5B,MAAM,GAAG,GAAG,gDAAgD,MAAM,qEAAqE,KAAK,oBAAoB,MAAM,oHAAoH,CAAA;QAC1R,MAAM,uBAAA,IAAI,yBAAQ,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,MAAM;YACN,YAAY;SACb,CAAC,CAAA;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,uBAAA,IAAI,qDAAc,MAAlB,IAAI,EAAe,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAA;IACjE,MAAM,uBAAA,IAAI,4BAAW,CAAC,OAAO,CAC3B,GAAG,CAAC,KAAK,EACT,GAAG,CAAC,QAAQ,EACZ,KAAK,EACL,OAAO,EACP,uBAAA,IAAI,sBAAK,MAAT,IAAI,CAAO,CACZ,CAAA;IACD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;AACzC,CAAC,mCAED,KAAK,yCACH,GAAkB,EAClB,KAAa;IAEb,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,4BAAW,CAAC,GAAG,CACzC,GAAG,CAAC,KAAK,EACT,GAAG,CAAC,QAAQ,EACZ,KAAK,EACL,OAAO,CACR,CAAA;IACD,MAAM,KAAK,GAAG,uBAAA,IAAI,6BAAY,MAAhB,IAAI,EAAa,GAAG,EAAE,KAAK,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAG,uBAAA,IAAI,8BAAa,MAAjB,IAAI,EAAc,GAAG,EAAE,KAAK,CAAC,CAAA;IAC5C,MAAM,GAAG,GAAG,eAAe,KAAK,kBAAkB,MAAM,yEAAyE,CAAA;IACjI,MAAM,uBAAA,IAAI,yBAAQ,CAAC,OAAO,CAAC,GAAG,EAAE;QAC9B,SAAS,EAAE,SAAS,CAAC,WAAW,EAAE;QAClC,QAAQ,EAAE,GAAG,CAAC,QAAQ;KACvB,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,MAAM,uBAAA,IAAI,yBAAQ,CAAC,OAAO,CACxC,wDAAwD,MAAM,kDAAkD,EAChH,EAAE,SAAS,EAAE,SAAS,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CACN,CAAA;IAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IACxB,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC,CAAC,CAAA;IACjD,IAAI,KAAK,EAAE,MAAM,EAAE,CAAC;QAClB,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QAC5C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC3C,MAAM,uBAAA,IAAI,4BAAW,CAAC,OAAO,CAC3B,GAAG,CAAC,KAAK,EACT,GAAG,CAAC,QAAQ,EACZ,KAAK,EACL,OAAO,EACP,aAAa,CACd,CAAA;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;AACzC,CAAC,4BAED,KAAK,kCACH,MAAc,EACd,GAAkB,EAClB,MAAc;IAEd,MAAM,IAAI,GAAG,MAAM,uBAAA,IAAI,yBAAQ,CAAC,OAAO,CACrC,6BAA6B,MAAM,gDAAgD,EACnF,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,CACV,CAAA;IAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;AAChC,CAAC,uDAEU,KAAa,EAAE,KAAc;IACtC,MAAM,GAAG,GAAG,KAAK,YAAY,SAAS;QACpC,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,IAAI,SAAS,CAAC,cAAc,EAAE,oBAAoB,KAAK,EAAE,EAAE,KAAK,CAAC,CAAA;IACrE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAA;AACzD,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch flusher (T-13 + T-15).
|
|
3
|
+
*
|
|
4
|
+
* Drains a `SensorBuffer` into a DuckDB Appender. Successful flushes are
|
|
5
|
+
* durably observable (buffer.drain() removes both in-memory and overflow
|
|
6
|
+
* copies), and a `SyncEmitter` fan-out surfaces `flushed` events for hooks +
|
|
7
|
+
* downstream invalidation.
|
|
8
|
+
*
|
|
9
|
+
* Retry / concurrency (T-15):
|
|
10
|
+
* - Wrapped in a `p-queue` (concurrency=2 by default) so multi-table flushes
|
|
11
|
+
* run in parallel without hammering the CPU.
|
|
12
|
+
* - Priorities: `foreground` (0) > `manual` (1) > `scheduled` (5) >
|
|
13
|
+
* `background` (10). Lower numeric priority = runs sooner.
|
|
14
|
+
* - Backoff schedule: 1s, 2s, 4s, 8s, 16s, 32s, 60s (capped). 5 consecutive
|
|
15
|
+
* failures per table → the flusher stops re-enqueuing and surfaces
|
|
16
|
+
* `error` state until the next explicit `flush()`.
|
|
17
|
+
* - Per-attempt timeout: 30s. Timeout aborts the attempt and counts as a
|
|
18
|
+
* failure toward the 5-failure limit.
|
|
19
|
+
*
|
|
20
|
+
* Data safety: attempts do *not* remove rows from the buffer until the
|
|
21
|
+
* appender reports success. On failure the drained entries are re-pushed onto
|
|
22
|
+
* the in-memory ring (front of queue) so subsequent retries see the same
|
|
23
|
+
* batch.
|
|
24
|
+
*/
|
|
25
|
+
import type { HybridDuckDB } from '../core/engine';
|
|
26
|
+
import type { SensorBuffer } from './buffer';
|
|
27
|
+
import { SyncError } from './errors';
|
|
28
|
+
import type { FlushResult } from './types';
|
|
29
|
+
export type FlushReason = 'row-count' | 'age' | 'foreground' | 'background' | 'manual' | 'scheduled';
|
|
30
|
+
/** Exponential backoff in ms, per attempt index (0-based). Capped at 60s. */
|
|
31
|
+
export declare const BACKOFF_SEQUENCE_MS: readonly number[];
|
|
32
|
+
export declare const MAX_CONSECUTIVE_FAILURES = 5;
|
|
33
|
+
export declare const FLUSH_TIMEOUT_MS = 30000;
|
|
34
|
+
export type FlusherState = 'idle' | 'flushing' | 'error';
|
|
35
|
+
export interface FlushedEvent {
|
|
36
|
+
table: string;
|
|
37
|
+
rowsFlushed: number;
|
|
38
|
+
reason: FlushReason;
|
|
39
|
+
}
|
|
40
|
+
export type SyncEmitter = (event: {
|
|
41
|
+
type: 'flushed';
|
|
42
|
+
payload: FlushedEvent;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'flush-failed';
|
|
45
|
+
payload: {
|
|
46
|
+
table: string;
|
|
47
|
+
error: SyncError;
|
|
48
|
+
};
|
|
49
|
+
}) => void;
|
|
50
|
+
export interface BatchFlusherConfig {
|
|
51
|
+
engine: HybridDuckDB;
|
|
52
|
+
buffer: SensorBuffer;
|
|
53
|
+
/**
|
|
54
|
+
* Column order per table. Row objects are projected into positional
|
|
55
|
+
* `appendRow(values[])` calls using this order. Missing tables raise
|
|
56
|
+
* `flush_failed` before a single row is written.
|
|
57
|
+
*/
|
|
58
|
+
columnOrder: Record<string, readonly string[]>;
|
|
59
|
+
/** Concurrent flush workers. Default 2 (spec §Retry orchestration). */
|
|
60
|
+
concurrency?: number;
|
|
61
|
+
/** Optional test hook. Defaults to `setTimeout`. */
|
|
62
|
+
sleep?: (ms: number) => Promise<void>;
|
|
63
|
+
/** Optional test hook. Defaults to `Date.now`. */
|
|
64
|
+
now?: () => number;
|
|
65
|
+
/** Optional emitter for `flushed` / `flush-failed` events. */
|
|
66
|
+
emit?: SyncEmitter;
|
|
67
|
+
}
|
|
68
|
+
export declare class BatchFlusher {
|
|
69
|
+
#private;
|
|
70
|
+
constructor(config: BatchFlusherConfig);
|
|
71
|
+
/** Runtime state for a table. `idle` if we've never touched it. */
|
|
72
|
+
stateOf(table: string): FlusherState;
|
|
73
|
+
lastErrorOf(table: string): SyncError | undefined;
|
|
74
|
+
/** Consecutive failures (reset on any success). */
|
|
75
|
+
failureCountOf(table: string): number;
|
|
76
|
+
/**
|
|
77
|
+
* Single flush attempt (no retry). Used directly by explicit user code and
|
|
78
|
+
* indirectly by `scheduleFlush` for the retry loop.
|
|
79
|
+
*/
|
|
80
|
+
flush(table: string, reason?: FlushReason): Promise<FlushResult>;
|
|
81
|
+
/**
|
|
82
|
+
* Enqueue a flush with retry orchestration. Non-blocking — returns the
|
|
83
|
+
* promise for callers that want to await final resolution.
|
|
84
|
+
*/
|
|
85
|
+
scheduleFlush(table: string, reason: FlushReason): Promise<FlushResult>;
|
|
86
|
+
/** Wait until every in-flight + queued flush settles. */
|
|
87
|
+
drainQueue(): Promise<void>;
|
|
88
|
+
/** Pause the queue — new `scheduleFlush()` calls enqueue but don't run. */
|
|
89
|
+
pauseQueue(): void;
|
|
90
|
+
/** Resume a paused queue. */
|
|
91
|
+
resumeQueue(): void;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=flusher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flusher.d.ts","sourceRoot":"","sources":["../../src/sync/flusher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,SAAS,CAAA;AAEvD,MAAM,MAAM,WAAW,GACjB,WAAW,GACX,KAAK,GACL,YAAY,GACZ,YAAY,GACZ,QAAQ,GACR,WAAW,CAAA;AAajB,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAQhD,CAAA;AAED,eAAO,MAAM,wBAAwB,IAAI,CAAA;AACzC,eAAO,MAAM,gBAAgB,QAAS,CAAA;AAEtC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AAExD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,WAAW,CAAA;CACpB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAC5B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;CAAE,KACrE,IAAI,CAAA;AAET,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,YAAY,CAAA;IACpB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;IAC9C,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oDAAoD;IACpD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB;AAQD,qBAAa,YAAY;;gBAUX,MAAM,EAAE,kBAAkB;IAUtC,mEAAmE;IACnE,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY;IAIpC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAIjD,mDAAmD;IACnD,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIrC;;;OAGG;IACG,KAAK,CACT,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,WAAsB,GAC7B,OAAO,CAAC,WAAW,CAAC;IA+BvB;;;OAGG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAQvE,yDAAyD;IACnD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,2EAA2E;IAC3E,UAAU,IAAI,IAAI;IAIlB,6BAA6B;IAC7B,WAAW,IAAI,IAAI;CA0GpB"}
|