@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,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Warehouse attach / detach for a single tenant zone.
|
|
3
|
+
*
|
|
4
|
+
* Attaches R2 Iceberg data as a named DuckDB catalog, so downstream code can
|
|
5
|
+
* `SELECT ... FROM zone_<tenantId>.<table>`. The full sequence lives here so
|
|
6
|
+
* the state machine (T-09) can invoke it as a single actor step.
|
|
7
|
+
*
|
|
8
|
+
* Sequence per spec §Attach Protocol:
|
|
9
|
+
* 1. Build warehouse URI via `warehouseUriBuilder(brand, scope, tenantId)`.
|
|
10
|
+
* 2. Fetch bearer token via `tokenVendor.fetch({ brand, scope, tenantId })`.
|
|
11
|
+
* 3. `CREATE OR REPLACE SECRET` bound to the catalog endpoint + bearer token.
|
|
12
|
+
* 4. `ATTACH '<warehouseUri>' AS zone_<tenantId> (TYPE ICEBERG)`.
|
|
13
|
+
* 5. If `tenantScope === 'family'`, prime member ids via
|
|
14
|
+
* `familyMembersProvider({ brand, familyId })` (for retention math).
|
|
15
|
+
*
|
|
16
|
+
* All failures map to `AnalyticsError('attach_failed' | 'token_vendor_failed',
|
|
17
|
+
* <phase>, cause)`.
|
|
18
|
+
*/
|
|
19
|
+
import { AnalyticsError } from './errors';
|
|
20
|
+
/**
|
|
21
|
+
* Peel one layer of `AnalyticsError('query_failed')` off engine-wrapped
|
|
22
|
+
* errors so the outer `attach_failed` error's `cause` chain surfaces the
|
|
23
|
+
* root native failure, not our own intermediate wrap.
|
|
24
|
+
*/
|
|
25
|
+
function rootCause(err) {
|
|
26
|
+
if (err instanceof AnalyticsError && err.code === 'query_failed') {
|
|
27
|
+
return err.cause ?? err;
|
|
28
|
+
}
|
|
29
|
+
return err;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Derive the DuckDB catalog alias for a tenant. Also used as the SECRET name.
|
|
33
|
+
*
|
|
34
|
+
* Sanitised to `[A-Za-z0-9_]` so it's always a valid DuckDB identifier.
|
|
35
|
+
*/
|
|
36
|
+
export function warehouseSecretName(tenantId) {
|
|
37
|
+
return `zone_${tenantId.replace(/[^A-Za-z0-9_]/g, '_')}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Attach a warehouse for a single tenant zone.
|
|
41
|
+
*
|
|
42
|
+
* Steps are surfaced explicitly in the error message so callers (machine,
|
|
43
|
+
* logger) can see which phase failed.
|
|
44
|
+
*/
|
|
45
|
+
export async function attachWarehouse(db, ctx, deps) {
|
|
46
|
+
const secretName = warehouseSecretName(ctx.tenantId);
|
|
47
|
+
// Step 1: warehouse URI
|
|
48
|
+
let uri;
|
|
49
|
+
try {
|
|
50
|
+
uri = deps.warehouseUriBuilder(ctx.brand, ctx.tenantScope, ctx.tenantId);
|
|
51
|
+
}
|
|
52
|
+
catch (cause) {
|
|
53
|
+
throw new AnalyticsError('attach_failed', `warehouseUriBuilder failed for ${ctx.tenantId}`, cause);
|
|
54
|
+
}
|
|
55
|
+
// Step 2: bearer token
|
|
56
|
+
let token;
|
|
57
|
+
try {
|
|
58
|
+
token = await deps.tokenVendor.fetch({
|
|
59
|
+
brand: ctx.brand,
|
|
60
|
+
tenantScope: ctx.tenantScope,
|
|
61
|
+
tenantId: ctx.tenantId,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (cause) {
|
|
65
|
+
throw new AnalyticsError('token_vendor_failed', `tokenVendor.fetch failed for ${ctx.tenantId}`, cause);
|
|
66
|
+
}
|
|
67
|
+
// Step 3: CREATE OR REPLACE SECRET
|
|
68
|
+
try {
|
|
69
|
+
await db.execute(`CREATE OR REPLACE SECRET ${secretName} (TYPE ICEBERG, TOKEN $token, ENDPOINT $endpoint);`, { token: token.token, endpoint: deps.catalogEndpoint });
|
|
70
|
+
}
|
|
71
|
+
catch (cause) {
|
|
72
|
+
throw new AnalyticsError('attach_failed', `CREATE SECRET failed for ${secretName}`, rootCause(cause));
|
|
73
|
+
}
|
|
74
|
+
// Step 4: ATTACH
|
|
75
|
+
try {
|
|
76
|
+
await db.execute(`ATTACH '${uri}' AS ${secretName} (TYPE ICEBERG);`);
|
|
77
|
+
}
|
|
78
|
+
catch (cause) {
|
|
79
|
+
throw new AnalyticsError('attach_failed', `ATTACH failed for ${secretName}`, rootCause(cause));
|
|
80
|
+
}
|
|
81
|
+
// Step 5: prime family members (retention math in T-14)
|
|
82
|
+
let familyMemberIds;
|
|
83
|
+
if (ctx.tenantScope === 'family') {
|
|
84
|
+
try {
|
|
85
|
+
familyMemberIds = await deps.familyMembersProvider({
|
|
86
|
+
brand: ctx.brand,
|
|
87
|
+
familyId: ctx.tenantId,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (cause) {
|
|
91
|
+
throw new AnalyticsError('attach_failed', `familyMembersProvider failed for ${ctx.tenantId}`, cause);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
warehouseSecret: secretName,
|
|
96
|
+
warehouseUri: uri,
|
|
97
|
+
tokenExpiresAt: token.expiresAt,
|
|
98
|
+
familyMemberIds,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Reverse of `attachWarehouse`: `DETACH` the catalog then drop its secret.
|
|
103
|
+
* Idempotent from the caller's perspective — a `DETACH` of an unknown alias
|
|
104
|
+
* throws in DuckDB, but the machine only calls this from `attached`.
|
|
105
|
+
*/
|
|
106
|
+
export async function detachWarehouse(db, tenantId) {
|
|
107
|
+
const secretName = warehouseSecretName(tenantId);
|
|
108
|
+
try {
|
|
109
|
+
await db.execute(`DETACH ${secretName};`);
|
|
110
|
+
await db.execute(`DROP SECRET ${secretName};`);
|
|
111
|
+
}
|
|
112
|
+
catch (cause) {
|
|
113
|
+
throw new AnalyticsError('detach_failed', `detach failed for ${secretName}`, rootCause(cause));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=warehouse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warehouse.js","sourceRoot":"","sources":["../../src/core/warehouse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sensor buffer (T-10 + T-12).
|
|
3
|
+
*
|
|
4
|
+
* In-memory ring per table with byte-budget accounting and pluggable overflow
|
|
5
|
+
* policies. All ops are synchronous except `push` (which may await overflow
|
|
6
|
+
* writes when the ring exceeds its byte budget).
|
|
7
|
+
*
|
|
8
|
+
* Byte accounting: we estimate a row's contribution as
|
|
9
|
+
* `JSON.stringify(rows).length` at push time. This is a rough proxy for the
|
|
10
|
+
* DuckDB Appender's actual memory footprint but is stable and cheap. The
|
|
11
|
+
* budget is a soft limit — one push at a time can exceed it before overflow
|
|
12
|
+
* fires.
|
|
13
|
+
*
|
|
14
|
+
* Overflow policies (T-12):
|
|
15
|
+
* - `drop-oldest`: when the ring is full, spill the oldest ring entries to
|
|
16
|
+
* `OverflowStore` until the incoming push fits in memory. Overflow is
|
|
17
|
+
* durable (MMKV via KVStore) so nothing is actually dropped.
|
|
18
|
+
* - `drop-newest`: reject the incoming push and increment a dropped
|
|
19
|
+
* counter. Warning event is emitted via the injected `onDrop` callback.
|
|
20
|
+
* Data is lost — used only when the app opts in for battery-only sync.
|
|
21
|
+
* - `block`: `push` awaits an external drain. Implemented via a
|
|
22
|
+
* `Promise`-based backpressure signal.
|
|
23
|
+
*/
|
|
24
|
+
import type { OverflowStore } from './overflow';
|
|
25
|
+
import type { BufferEntry, BufferSize, OverflowPolicy, SensorBatch } from './types';
|
|
26
|
+
export interface SensorBufferConfig {
|
|
27
|
+
overflow: OverflowStore;
|
|
28
|
+
maxBufferBytes?: number;
|
|
29
|
+
policy?: OverflowPolicy;
|
|
30
|
+
/** Fired when a `drop-newest` policy discards an incoming push. */
|
|
31
|
+
onDrop?: (entry: {
|
|
32
|
+
table: string;
|
|
33
|
+
droppedRowCount: number;
|
|
34
|
+
reason: 'drop-newest';
|
|
35
|
+
}) => void;
|
|
36
|
+
/** Optional clock injection for deterministic tests. */
|
|
37
|
+
now?: () => number;
|
|
38
|
+
}
|
|
39
|
+
export declare class SensorBuffer {
|
|
40
|
+
#private;
|
|
41
|
+
constructor(config: SensorBufferConfig);
|
|
42
|
+
push(batch: SensorBatch): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Drain the buffer for a single table. Overflow entries are drained first
|
|
45
|
+
* so FIFO order across in-memory + overflow is preserved.
|
|
46
|
+
*/
|
|
47
|
+
drain(table: string): Promise<BufferEntry[]>;
|
|
48
|
+
size(table?: string): Promise<BufferSize>;
|
|
49
|
+
clear(table?: string): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../src/sync/buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,WAAW,EACZ,MAAM,SAAS,CAAA;AAIhB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,aAAa,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,mEAAmE;IACnE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE;QACf,KAAK,EAAE,MAAM,CAAA;QACb,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,aAAa,CAAA;KACtB,KAAK,IAAI,CAAA;IACV,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,qBAAa,YAAY;;gBAWX,MAAM,EAAE,kBAAkB;IAQhC,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAa7C;;;OAGG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAU5C,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAkBzC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAqG3C"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sensor buffer (T-10 + T-12).
|
|
3
|
+
*
|
|
4
|
+
* In-memory ring per table with byte-budget accounting and pluggable overflow
|
|
5
|
+
* policies. All ops are synchronous except `push` (which may await overflow
|
|
6
|
+
* writes when the ring exceeds its byte budget).
|
|
7
|
+
*
|
|
8
|
+
* Byte accounting: we estimate a row's contribution as
|
|
9
|
+
* `JSON.stringify(rows).length` at push time. This is a rough proxy for the
|
|
10
|
+
* DuckDB Appender's actual memory footprint but is stable and cheap. The
|
|
11
|
+
* budget is a soft limit — one push at a time can exceed it before overflow
|
|
12
|
+
* fires.
|
|
13
|
+
*
|
|
14
|
+
* Overflow policies (T-12):
|
|
15
|
+
* - `drop-oldest`: when the ring is full, spill the oldest ring entries to
|
|
16
|
+
* `OverflowStore` until the incoming push fits in memory. Overflow is
|
|
17
|
+
* durable (MMKV via KVStore) so nothing is actually dropped.
|
|
18
|
+
* - `drop-newest`: reject the incoming push and increment a dropped
|
|
19
|
+
* counter. Warning event is emitted via the injected `onDrop` callback.
|
|
20
|
+
* Data is lost — used only when the app opts in for battery-only sync.
|
|
21
|
+
* - `block`: `push` awaits an external drain. Implemented via a
|
|
22
|
+
* `Promise`-based backpressure signal.
|
|
23
|
+
*/
|
|
24
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
25
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
26
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
27
|
+
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");
|
|
28
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
29
|
+
};
|
|
30
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
31
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
32
|
+
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");
|
|
33
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
34
|
+
};
|
|
35
|
+
var _SensorBuffer_instances, _SensorBuffer_overflow, _SensorBuffer_maxBytes, _SensorBuffer_policy, _SensorBuffer_onDrop, _SensorBuffer_now, _SensorBuffer_rings, _SensorBuffer_inMemoryBytes, _SensorBuffer_blockWaiters, _SensorBuffer_pushEntry, _SensorBuffer_appendInMemory, _SensorBuffer_sizeOne, _SensorBuffer_waitForDrain, _SensorBuffer_wakeBlockers;
|
|
36
|
+
const DEFAULT_MAX_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
37
|
+
export class SensorBuffer {
|
|
38
|
+
constructor(config) {
|
|
39
|
+
_SensorBuffer_instances.add(this);
|
|
40
|
+
_SensorBuffer_overflow.set(this, void 0);
|
|
41
|
+
_SensorBuffer_maxBytes.set(this, void 0);
|
|
42
|
+
_SensorBuffer_policy.set(this, void 0);
|
|
43
|
+
_SensorBuffer_onDrop.set(this, void 0);
|
|
44
|
+
_SensorBuffer_now.set(this, void 0);
|
|
45
|
+
_SensorBuffer_rings.set(this, new Map());
|
|
46
|
+
_SensorBuffer_inMemoryBytes.set(this, new Map());
|
|
47
|
+
_SensorBuffer_blockWaiters.set(this, new Map());
|
|
48
|
+
__classPrivateFieldSet(this, _SensorBuffer_overflow, config.overflow, "f");
|
|
49
|
+
__classPrivateFieldSet(this, _SensorBuffer_maxBytes, config.maxBufferBytes ?? DEFAULT_MAX_BYTES, "f");
|
|
50
|
+
__classPrivateFieldSet(this, _SensorBuffer_policy, config.policy ?? 'drop-oldest', "f");
|
|
51
|
+
__classPrivateFieldSet(this, _SensorBuffer_onDrop, config.onDrop, "f");
|
|
52
|
+
__classPrivateFieldSet(this, _SensorBuffer_now, config.now ?? (() => Date.now()), "f");
|
|
53
|
+
}
|
|
54
|
+
async push(batch) {
|
|
55
|
+
const entry = {
|
|
56
|
+
rows: batch.rows,
|
|
57
|
+
brand: batch.brand,
|
|
58
|
+
familyId: batch.familyId,
|
|
59
|
+
userId: batch.userId,
|
|
60
|
+
deviceId: batch.deviceId,
|
|
61
|
+
enqueuedAt: __classPrivateFieldGet(this, _SensorBuffer_now, "f").call(this),
|
|
62
|
+
byteSize: estimateBytes(batch.rows),
|
|
63
|
+
};
|
|
64
|
+
await __classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_pushEntry).call(this, batch.table, entry);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Drain the buffer for a single table. Overflow entries are drained first
|
|
68
|
+
* so FIFO order across in-memory + overflow is preserved.
|
|
69
|
+
*/
|
|
70
|
+
async drain(table) {
|
|
71
|
+
const overflow = await __classPrivateFieldGet(this, _SensorBuffer_overflow, "f").drain(table);
|
|
72
|
+
const ring = __classPrivateFieldGet(this, _SensorBuffer_rings, "f").get(table) ?? [];
|
|
73
|
+
__classPrivateFieldGet(this, _SensorBuffer_rings, "f").set(table, []);
|
|
74
|
+
__classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").set(table, 0);
|
|
75
|
+
// Wake any block-policy waiters now that room is available.
|
|
76
|
+
__classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_wakeBlockers).call(this, table);
|
|
77
|
+
return [...overflow, ...ring];
|
|
78
|
+
}
|
|
79
|
+
async size(table) {
|
|
80
|
+
if (table)
|
|
81
|
+
return __classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_sizeOne).call(this, table);
|
|
82
|
+
let inMemory = 0;
|
|
83
|
+
let inMemoryBytes = 0;
|
|
84
|
+
let overflow = 0;
|
|
85
|
+
const tables = new Set([
|
|
86
|
+
...__classPrivateFieldGet(this, _SensorBuffer_rings, "f").keys(),
|
|
87
|
+
...__classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").keys(),
|
|
88
|
+
]);
|
|
89
|
+
for (const t of tables) {
|
|
90
|
+
const s = await __classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_sizeOne).call(this, t);
|
|
91
|
+
inMemory += s.inMemory;
|
|
92
|
+
inMemoryBytes += s.inMemoryBytes;
|
|
93
|
+
overflow += s.overflow;
|
|
94
|
+
}
|
|
95
|
+
return { inMemory, inMemoryBytes, overflow };
|
|
96
|
+
}
|
|
97
|
+
async clear(table) {
|
|
98
|
+
if (table) {
|
|
99
|
+
__classPrivateFieldGet(this, _SensorBuffer_rings, "f").delete(table);
|
|
100
|
+
__classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").set(table, 0);
|
|
101
|
+
await __classPrivateFieldGet(this, _SensorBuffer_overflow, "f").clear(table);
|
|
102
|
+
__classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_wakeBlockers).call(this, table);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
for (const t of __classPrivateFieldGet(this, _SensorBuffer_rings, "f").keys())
|
|
106
|
+
await __classPrivateFieldGet(this, _SensorBuffer_overflow, "f").clear(t);
|
|
107
|
+
__classPrivateFieldGet(this, _SensorBuffer_rings, "f").clear();
|
|
108
|
+
__classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").clear();
|
|
109
|
+
for (const t of __classPrivateFieldGet(this, _SensorBuffer_blockWaiters, "f").keys())
|
|
110
|
+
__classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_wakeBlockers).call(this, t);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
_SensorBuffer_overflow = new WeakMap(), _SensorBuffer_maxBytes = new WeakMap(), _SensorBuffer_policy = new WeakMap(), _SensorBuffer_onDrop = new WeakMap(), _SensorBuffer_now = new WeakMap(), _SensorBuffer_rings = new WeakMap(), _SensorBuffer_inMemoryBytes = new WeakMap(), _SensorBuffer_blockWaiters = new WeakMap(), _SensorBuffer_instances = new WeakSet(), _SensorBuffer_pushEntry = async function _SensorBuffer_pushEntry(table, entry) {
|
|
114
|
+
const currentBytes = __classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").get(table) ?? 0;
|
|
115
|
+
const wouldOverflow = currentBytes + entry.byteSize > __classPrivateFieldGet(this, _SensorBuffer_maxBytes, "f");
|
|
116
|
+
if (!wouldOverflow) {
|
|
117
|
+
__classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_appendInMemory).call(this, table, entry);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
switch (__classPrivateFieldGet(this, _SensorBuffer_policy, "f")) {
|
|
121
|
+
case 'drop-oldest': {
|
|
122
|
+
// Spill oldest ring entries to overflow until the incoming push fits.
|
|
123
|
+
const ring = __classPrivateFieldGet(this, _SensorBuffer_rings, "f").get(table) ?? [];
|
|
124
|
+
while (ring.length > 0
|
|
125
|
+
&& (__classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").get(table) ?? 0) + entry.byteSize
|
|
126
|
+
> __classPrivateFieldGet(this, _SensorBuffer_maxBytes, "f")) {
|
|
127
|
+
const oldest = ring.shift();
|
|
128
|
+
__classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").set(table, (__classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").get(table) ?? 0) - oldest.byteSize);
|
|
129
|
+
await __classPrivateFieldGet(this, _SensorBuffer_overflow, "f").push(table, oldest);
|
|
130
|
+
}
|
|
131
|
+
__classPrivateFieldGet(this, _SensorBuffer_rings, "f").set(table, ring);
|
|
132
|
+
// If the incoming entry itself exceeds the budget, park it in
|
|
133
|
+
// overflow directly rather than blowing the ring.
|
|
134
|
+
if (entry.byteSize > __classPrivateFieldGet(this, _SensorBuffer_maxBytes, "f")) {
|
|
135
|
+
await __classPrivateFieldGet(this, _SensorBuffer_overflow, "f").push(table, entry);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
__classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_appendInMemory).call(this, table, entry);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
case 'drop-newest': {
|
|
143
|
+
__classPrivateFieldGet(this, _SensorBuffer_onDrop, "f")?.call(this, {
|
|
144
|
+
table,
|
|
145
|
+
droppedRowCount: entry.rows.length,
|
|
146
|
+
reason: 'drop-newest',
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
case 'block': {
|
|
151
|
+
// Wait for someone to drain; then retry the push.
|
|
152
|
+
await __classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_waitForDrain).call(this, table);
|
|
153
|
+
return __classPrivateFieldGet(this, _SensorBuffer_instances, "m", _SensorBuffer_pushEntry).call(this, table, entry);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}, _SensorBuffer_appendInMemory = function _SensorBuffer_appendInMemory(table, entry) {
|
|
157
|
+
const ring = __classPrivateFieldGet(this, _SensorBuffer_rings, "f").get(table) ?? [];
|
|
158
|
+
ring.push(entry);
|
|
159
|
+
__classPrivateFieldGet(this, _SensorBuffer_rings, "f").set(table, ring);
|
|
160
|
+
__classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").set(table, (__classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").get(table) ?? 0) + entry.byteSize);
|
|
161
|
+
}, _SensorBuffer_sizeOne = async function _SensorBuffer_sizeOne(table) {
|
|
162
|
+
const ring = __classPrivateFieldGet(this, _SensorBuffer_rings, "f").get(table) ?? [];
|
|
163
|
+
const inMemoryRows = ring.reduce((a, e) => a + e.rows.length, 0);
|
|
164
|
+
const overflowCount = await __classPrivateFieldGet(this, _SensorBuffer_overflow, "f").count(table);
|
|
165
|
+
return {
|
|
166
|
+
inMemory: inMemoryRows,
|
|
167
|
+
inMemoryBytes: __classPrivateFieldGet(this, _SensorBuffer_inMemoryBytes, "f").get(table) ?? 0,
|
|
168
|
+
overflow: overflowCount,
|
|
169
|
+
};
|
|
170
|
+
}, _SensorBuffer_waitForDrain = function _SensorBuffer_waitForDrain(table) {
|
|
171
|
+
return new Promise((resolve) => {
|
|
172
|
+
const q = __classPrivateFieldGet(this, _SensorBuffer_blockWaiters, "f").get(table) ?? [];
|
|
173
|
+
q.push(resolve);
|
|
174
|
+
__classPrivateFieldGet(this, _SensorBuffer_blockWaiters, "f").set(table, q);
|
|
175
|
+
});
|
|
176
|
+
}, _SensorBuffer_wakeBlockers = function _SensorBuffer_wakeBlockers(table) {
|
|
177
|
+
const q = __classPrivateFieldGet(this, _SensorBuffer_blockWaiters, "f").get(table);
|
|
178
|
+
if (!q || q.length === 0)
|
|
179
|
+
return;
|
|
180
|
+
__classPrivateFieldGet(this, _SensorBuffer_blockWaiters, "f").set(table, []);
|
|
181
|
+
for (const resolve of q)
|
|
182
|
+
resolve();
|
|
183
|
+
};
|
|
184
|
+
function estimateBytes(rows) {
|
|
185
|
+
// Approximation: rough byte count of the JSON encoding. Fast and stable.
|
|
186
|
+
return JSON.stringify(rows).length;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer.js","sourceRoot":"","sources":["../../src/sync/buffer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React context for the SyncManager.
|
|
3
|
+
*
|
|
4
|
+
* `SyncProvider` wires an already-constructed `SyncManager` into the subtree;
|
|
5
|
+
* hooks (`useSyncState`, `useSensorSink`, `useSyncProgress`) resolve it via
|
|
6
|
+
* `useSyncManager()`.
|
|
7
|
+
*/
|
|
8
|
+
import * as React from 'react';
|
|
9
|
+
import type { SyncManager } from './manager';
|
|
10
|
+
export interface SyncProviderProps {
|
|
11
|
+
manager: SyncManager;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
export declare function SyncProvider({ manager, children }: SyncProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
/** Resolves the SyncManager. Throws when used outside `<SyncProvider>`. */
|
|
16
|
+
export declare function useSyncManager(): SyncManager;
|
|
17
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/sync/context.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAI5C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,WAAW,CAAA;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,iBAAiB,2CAMpE;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,IAAI,WAAW,CAS5C"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* React context for the SyncManager.
|
|
4
|
+
*
|
|
5
|
+
* `SyncProvider` wires an already-constructed `SyncManager` into the subtree;
|
|
6
|
+
* hooks (`useSyncState`, `useSensorSink`, `useSyncProgress`) resolve it via
|
|
7
|
+
* `useSyncManager()`.
|
|
8
|
+
*/
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
const SyncContext = React.createContext(null);
|
|
11
|
+
export function SyncProvider({ manager, children }) {
|
|
12
|
+
return (_jsx(SyncContext.Provider, { value: manager, children: children }));
|
|
13
|
+
}
|
|
14
|
+
/** Resolves the SyncManager. Throws when used outside `<SyncProvider>`. */
|
|
15
|
+
export function useSyncManager() {
|
|
16
|
+
const manager = React.useContext(SyncContext);
|
|
17
|
+
if (!manager) {
|
|
18
|
+
throw new Error('@mongrov/analytics/sync: hook used outside <SyncProvider>. '
|
|
19
|
+
+ 'Wrap your app with <SyncProvider manager={createSyncManager(...)}>.');
|
|
20
|
+
}
|
|
21
|
+
return manager;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/sync/context.tsx"],"names":[],"mappings":";AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,CAAqB,IAAI,CAAC,CAAA;AAOjE,MAAM,UAAU,YAAY,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAqB;IACnE,OAAO,CACL,KAAC,WAAW,CAAC,QAAQ,IAAC,KAAK,EAAE,OAAO,YACjC,QAAQ,GACY,CACxB,CAAA;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc;IAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;IAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,6DAA6D;cAC3D,qEAAqE,CACxE,CAAA;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncError taxonomy (design.md §Errors).
|
|
3
|
+
*
|
|
4
|
+
* Codes:
|
|
5
|
+
* - `flush_failed` — Appender write failed (all retries exhausted or fatal)
|
|
6
|
+
* - `push_failed` — R2 push failed (all retries exhausted)
|
|
7
|
+
* - `fetch_failed` — R2 fetch failed
|
|
8
|
+
* - `overflow_dropped` — drop-newest policy discarded a batch
|
|
9
|
+
* - `token_expired` — 401 from R2 after token refresh
|
|
10
|
+
* - `constraint_not_met` — scheduler skipped a cycle (battery / network)
|
|
11
|
+
* - `mapper_failed` — firmware mapper raised
|
|
12
|
+
*/
|
|
13
|
+
export type SyncErrorCode = 'flush_failed' | 'push_failed' | 'fetch_failed' | 'overflow_dropped' | 'token_expired' | 'constraint_not_met' | 'mapper_failed';
|
|
14
|
+
export declare class SyncError extends Error {
|
|
15
|
+
readonly code: SyncErrorCode;
|
|
16
|
+
readonly cause: unknown;
|
|
17
|
+
constructor(code: SyncErrorCode, message: string, cause?: unknown);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/sync/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,aAAa,GACnB,cAAc,GACd,aAAa,GACb,cAAc,GACd,kBAAkB,GAClB,eAAe,GACf,oBAAoB,GACpB,eAAe,CAAA;AAErB,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;IAC5B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;gBAEX,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAMlE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncError taxonomy (design.md §Errors).
|
|
3
|
+
*
|
|
4
|
+
* Codes:
|
|
5
|
+
* - `flush_failed` — Appender write failed (all retries exhausted or fatal)
|
|
6
|
+
* - `push_failed` — R2 push failed (all retries exhausted)
|
|
7
|
+
* - `fetch_failed` — R2 fetch failed
|
|
8
|
+
* - `overflow_dropped` — drop-newest policy discarded a batch
|
|
9
|
+
* - `token_expired` — 401 from R2 after token refresh
|
|
10
|
+
* - `constraint_not_met` — scheduler skipped a cycle (battery / network)
|
|
11
|
+
* - `mapper_failed` — firmware mapper raised
|
|
12
|
+
*/
|
|
13
|
+
export class SyncError extends Error {
|
|
14
|
+
constructor(code, message, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'SyncError';
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.cause = cause;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/sync/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAIlC,YAAY,IAAmB,EAAE,OAAe,EAAE,KAAe;QAC/D,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,WAAW,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;CACF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T-25 — Event bus integration.
|
|
3
|
+
*
|
|
4
|
+
* The sync layer publishes two families of invalidation events:
|
|
5
|
+
* `{table}:insert` — emitted after a successful flush of local rows.
|
|
6
|
+
* `{table}:sync_complete` — emitted after a successful push to the remote R2
|
|
7
|
+
* zone completes with rows written.
|
|
8
|
+
*
|
|
9
|
+
* The `EventBus` is injected by the app (typically `@mongrov/core`'s event bus).
|
|
10
|
+
* Consumers use these events to invalidate caches / re-run subscriptions.
|
|
11
|
+
*/
|
|
12
|
+
import type { FlushedEvent, SyncEmitter } from './flusher';
|
|
13
|
+
import type { PushResult } from './pusher';
|
|
14
|
+
/**
|
|
15
|
+
* Minimal event-bus contract. Kept narrow so any host (React Query cache,
|
|
16
|
+
* mitt, EventEmitter, custom fan-out) satisfies it structurally.
|
|
17
|
+
*/
|
|
18
|
+
export interface EventBus {
|
|
19
|
+
emit: (name: string, payload?: unknown) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface FlushInsertPayload {
|
|
22
|
+
table: string;
|
|
23
|
+
rowsFlushed: number;
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
export interface SyncCompletePayload {
|
|
27
|
+
table: string;
|
|
28
|
+
rowsPushed: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Adapts an `EventBus` into a `SyncEmitter` (as consumed by `BatchFlusher.emit`).
|
|
32
|
+
* Only successful flushes produce an `${table}:insert` event; failures pass
|
|
33
|
+
* through silently at the bus layer (the flusher already emits `flush-failed`
|
|
34
|
+
* to any co-subscribed emitter).
|
|
35
|
+
*/
|
|
36
|
+
export declare function bindFlushEvents(bus: EventBus): SyncEmitter;
|
|
37
|
+
export type PushEmitter = (result: PushResult) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Adapts an `EventBus` into a `PushEmitter`. Only successful pushes with at
|
|
40
|
+
* least one row published produce `${table}:sync_complete`; ok-but-empty pushes
|
|
41
|
+
* are silent (no downstream invalidation is needed).
|
|
42
|
+
*/
|
|
43
|
+
export declare function bindPushEvents(bus: EventBus): PushEmitter;
|
|
44
|
+
/**
|
|
45
|
+
* Re-export the FlushedEvent shape for callers that need to type-narrow their
|
|
46
|
+
* bus payloads.
|
|
47
|
+
*/
|
|
48
|
+
export type { FlushedEvent };
|
|
49
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/sync/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAChD;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,GAAG,WAAW,CAW1D;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAA;AAEtD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,WAAW,CAUzD;AAED;;;GAGG;AACH,YAAY,EAAE,YAAY,EAAE,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T-25 — Event bus integration.
|
|
3
|
+
*
|
|
4
|
+
* The sync layer publishes two families of invalidation events:
|
|
5
|
+
* `{table}:insert` — emitted after a successful flush of local rows.
|
|
6
|
+
* `{table}:sync_complete` — emitted after a successful push to the remote R2
|
|
7
|
+
* zone completes with rows written.
|
|
8
|
+
*
|
|
9
|
+
* The `EventBus` is injected by the app (typically `@mongrov/core`'s event bus).
|
|
10
|
+
* Consumers use these events to invalidate caches / re-run subscriptions.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Adapts an `EventBus` into a `SyncEmitter` (as consumed by `BatchFlusher.emit`).
|
|
14
|
+
* Only successful flushes produce an `${table}:insert` event; failures pass
|
|
15
|
+
* through silently at the bus layer (the flusher already emits `flush-failed`
|
|
16
|
+
* to any co-subscribed emitter).
|
|
17
|
+
*/
|
|
18
|
+
export function bindFlushEvents(bus) {
|
|
19
|
+
return (event) => {
|
|
20
|
+
if (event.type === 'flushed') {
|
|
21
|
+
const payload = {
|
|
22
|
+
table: event.payload.table,
|
|
23
|
+
rowsFlushed: event.payload.rowsFlushed,
|
|
24
|
+
reason: event.payload.reason,
|
|
25
|
+
};
|
|
26
|
+
bus.emit(`${event.payload.table}:insert`, payload);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Adapts an `EventBus` into a `PushEmitter`. Only successful pushes with at
|
|
32
|
+
* least one row published produce `${table}:sync_complete`; ok-but-empty pushes
|
|
33
|
+
* are silent (no downstream invalidation is needed).
|
|
34
|
+
*/
|
|
35
|
+
export function bindPushEvents(bus) {
|
|
36
|
+
return (result) => {
|
|
37
|
+
if (result.ok && result.rowsPushed > 0) {
|
|
38
|
+
const payload = {
|
|
39
|
+
table: result.table,
|
|
40
|
+
rowsPushed: result.rowsPushed,
|
|
41
|
+
};
|
|
42
|
+
bus.emit(`${result.table}:sync_complete`, payload);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/sync/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAwBH;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAa;IAC3C,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAuB;gBAClC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;gBAC1B,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,WAAW;gBACtC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM;aAC7B,CAAA;YACD,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,SAAS,EAAE,OAAO,CAAC,CAAA;QACpD,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAID;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,GAAa;IAC1C,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,IAAI,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YACvC,MAAM,OAAO,GAAwB;gBACnC,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,UAAU,EAAE,MAAM,CAAC,UAAU;aAC9B,CAAA;YACD,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,gBAAgB,EAAE,OAAO,CAAC,CAAA;QACpD,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
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 type { AnalyticsEngine, AttachContext, KVStore } from '../core/types';
|
|
20
|
+
import type { EventBus } from './events';
|
|
21
|
+
import type { PrefetchPolicy } from './fetcher';
|
|
22
|
+
import { type BackgroundTaskPort, type ConstraintPort, type SchedulerLogger } from './scheduler';
|
|
23
|
+
import type { SyncManager } from './manager';
|
|
24
|
+
import type { OverflowPolicy } from './types';
|
|
25
|
+
export interface CreateSyncManagerConfig {
|
|
26
|
+
analytics: AnalyticsEngine;
|
|
27
|
+
storage: KVStore;
|
|
28
|
+
ctx: AttachContext;
|
|
29
|
+
tables: readonly string[];
|
|
30
|
+
columnOrder: Record<string, readonly string[]>;
|
|
31
|
+
prefetchPolicy: PrefetchPolicy;
|
|
32
|
+
eventBus?: EventBus;
|
|
33
|
+
refreshToken?: () => Promise<void>;
|
|
34
|
+
backgroundTask?: BackgroundTaskPort;
|
|
35
|
+
constraints?: ConstraintPort;
|
|
36
|
+
flush?: {
|
|
37
|
+
maxRows?: number;
|
|
38
|
+
maxAgeMs?: number;
|
|
39
|
+
concurrency?: number;
|
|
40
|
+
};
|
|
41
|
+
overflow?: {
|
|
42
|
+
maxBufferBytes?: number;
|
|
43
|
+
policy?: OverflowPolicy;
|
|
44
|
+
};
|
|
45
|
+
scheduler?: {
|
|
46
|
+
requiresCharging?: boolean;
|
|
47
|
+
requiresWifi?: boolean;
|
|
48
|
+
taskName?: string;
|
|
49
|
+
};
|
|
50
|
+
logger?: SchedulerLogger;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Assemble a fully wired `SyncManager`. Throws `z.ZodError` if the config is
|
|
54
|
+
* malformed (e.g., missing columnOrder entries or negative row limits).
|
|
55
|
+
*/
|
|
56
|
+
export declare function createSyncManager(config: CreateSyncManagerConfig): SyncManager;
|
|
57
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/sync/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAG5E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAO/C,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,eAAe,EAGrB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAc,WAAW,EAAkC,MAAM,WAAW,CAAA;AAExF,OAAO,KAAK,EAAe,cAAc,EAAe,MAAM,SAAS,CAAA;AAmCvE,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,eAAe,CAAA;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,aAAa,CAAA;IAClB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAA;IAC9C,cAAc,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,cAAc,CAAC,EAAE,kBAAkB,CAAA;IACnC,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,QAAQ,CAAC,EAAE;QACT,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,MAAM,CAAC,EAAE,cAAc,CAAA;KACxB,CAAA;IACD,SAAS,CAAC,EAAE;QACV,gBAAgB,CAAC,EAAE,OAAO,CAAA;QAC1B,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,MAAM,CAAC,EAAE,eAAe,CAAA;CACzB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,WAAW,CAgI9E"}
|