@indigoai-us/hq-cloud 5.26.0 → 5.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +34 -0
- package/dist/bin/sync-runner.d.ts +38 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +75 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/sync/feature-flags.d.ts +136 -0
- package/dist/sync/feature-flags.d.ts.map +1 -0
- package/dist/sync/feature-flags.js +160 -0
- package/dist/sync/feature-flags.js.map +1 -0
- package/dist/sync/feature-flags.test.d.ts +24 -0
- package/dist/sync/feature-flags.test.d.ts.map +1 -0
- package/dist/sync/feature-flags.test.js +330 -0
- package/dist/sync/feature-flags.test.js.map +1 -0
- package/dist/sync/index.d.ts +10 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +5 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/logger.d.ts +61 -0
- package/dist/sync/logger.d.ts.map +1 -0
- package/dist/sync/logger.js +51 -0
- package/dist/sync/logger.js.map +1 -0
- package/dist/sync/logger.test.d.ts +19 -0
- package/dist/sync/logger.test.d.ts.map +1 -0
- package/dist/sync/logger.test.js +199 -0
- package/dist/sync/logger.test.js.map +1 -0
- package/dist/sync/metrics.d.ts +89 -0
- package/dist/sync/metrics.d.ts.map +1 -0
- package/dist/sync/metrics.js +105 -0
- package/dist/sync/metrics.js.map +1 -0
- package/dist/sync/metrics.test.d.ts +19 -0
- package/dist/sync/metrics.test.d.ts.map +1 -0
- package/dist/sync/metrics.test.js +280 -0
- package/dist/sync/metrics.test.js.map +1 -0
- package/dist/sync/push-receiver.d.ts +442 -0
- package/dist/sync/push-receiver.d.ts.map +1 -0
- package/dist/sync/push-receiver.js +782 -0
- package/dist/sync/push-receiver.js.map +1 -0
- package/dist/sync/push-receiver.test.d.ts +25 -0
- package/dist/sync/push-receiver.test.d.ts.map +1 -0
- package/dist/sync/push-receiver.test.js +477 -0
- package/dist/sync/push-receiver.test.js.map +1 -0
- package/dist/sync/push-transport.d.ts +84 -1
- package/dist/sync/push-transport.d.ts.map +1 -1
- package/dist/sync/push-transport.js +84 -0
- package/dist/sync/push-transport.js.map +1 -1
- package/dist/watcher.d.ts +127 -11
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +294 -57
- package/dist/watcher.js.map +1 -1
- package/package.json +9 -5
- package/src/bin/sync-runner.ts +102 -1
- package/src/index.ts +21 -0
- package/src/sync/feature-flags.test.ts +392 -0
- package/src/sync/feature-flags.ts +229 -0
- package/src/sync/index.ts +57 -2
- package/src/sync/logger.test.ts +241 -0
- package/src/sync/logger.ts +79 -0
- package/src/sync/metrics.test.ts +380 -0
- package/src/sync/metrics.ts +158 -0
- package/src/sync/push-receiver.test.ts +545 -0
- package/src/sync/push-receiver.ts +1077 -0
- package/src/sync/push-transport.ts +148 -1
- package/src/watcher.ts +408 -51
- package/test/e2e/sync/cross-tenant-isolation.test.ts +502 -0
- package/test/e2e/watcher-real-chokidar.test.ts +105 -0
- package/test/e2e/watcher-recursive-backend.test.ts +115 -0
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* Ported from indigoai-us/hq-pro PR #112 (src/sync/push-transport.ts) into
|
|
31
31
|
* @indigoai-us/hq-cloud (Path B) per project event-driven-sync-menubar US-007.
|
|
32
32
|
*/
|
|
33
|
-
import type
|
|
33
|
+
import { type PushEvent } from "./push-event.js";
|
|
34
34
|
export interface PushTransport {
|
|
35
35
|
/** Open sockets, refresh tokens. Awaited before the watcher starts. */
|
|
36
36
|
start(): Promise<void>;
|
|
@@ -64,4 +64,87 @@ export declare class NoopPushTransport implements PushTransport {
|
|
|
64
64
|
publish(_event: PushEvent): Promise<void>;
|
|
65
65
|
dispose(): Promise<void>;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Minimal fetch surface so tests can inject a mocked `fetch` without pulling
|
|
69
|
+
* in DOM/undici types. Matches the subset of the global `fetch` we use.
|
|
70
|
+
*/
|
|
71
|
+
export type FetchLike = (input: string, init?: {
|
|
72
|
+
method?: string;
|
|
73
|
+
headers?: Record<string, string>;
|
|
74
|
+
body?: string;
|
|
75
|
+
signal?: AbortSignal;
|
|
76
|
+
}) => Promise<{
|
|
77
|
+
ok: boolean;
|
|
78
|
+
status: number;
|
|
79
|
+
text(): Promise<string>;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Async getter for the current Cognito access token. Long-running daemons MUST
|
|
83
|
+
* pass a getter (not a captured string) so each publish resolves the freshest
|
|
84
|
+
* token — mirrors {@link VaultServiceConfig.authToken} semantics in types.ts.
|
|
85
|
+
* A static string is also accepted for short-lived tools and tests.
|
|
86
|
+
*/
|
|
87
|
+
export type AuthTokenSource = string | (() => string | Promise<string>);
|
|
88
|
+
export interface HttpPushTransportOptions {
|
|
89
|
+
/**
|
|
90
|
+
* Vault API base URL (e.g. `https://vault-api.example.com`). The same
|
|
91
|
+
* `apiUrl` the runner already resolves for VaultClient. Trailing slashes
|
|
92
|
+
* are stripped. Required — config/env-driven, no hard-coded default.
|
|
93
|
+
*/
|
|
94
|
+
apiUrl: string;
|
|
95
|
+
/**
|
|
96
|
+
* Endpoint path the PushEvent is POSTed to. Defaults to `/sync/push`
|
|
97
|
+
* (matches the deployed hq-pro endpoint from US-006). Override for testing
|
|
98
|
+
* or future endpoint moves.
|
|
99
|
+
*/
|
|
100
|
+
pushPath?: string;
|
|
101
|
+
/** Cognito JWT — static string OR async getter. See {@link AuthTokenSource}. */
|
|
102
|
+
authToken: AuthTokenSource;
|
|
103
|
+
/**
|
|
104
|
+
* Optional extra headers (e.g. client identification) merged onto every
|
|
105
|
+
* request. Authorization + Content-Type are always set by the transport.
|
|
106
|
+
*/
|
|
107
|
+
headers?: Record<string, string>;
|
|
108
|
+
/**
|
|
109
|
+
* Per-request timeout in milliseconds. Default 10_000. On timeout the
|
|
110
|
+
* publish rejects — the daemon treats a rejected publish as a transient
|
|
111
|
+
* miss (the cadence poll still covers it) and MUST NOT crash.
|
|
112
|
+
*/
|
|
113
|
+
timeoutMs?: number;
|
|
114
|
+
/** Injectable fetch (tests). Defaults to the global `fetch`. */
|
|
115
|
+
fetchImpl?: FetchLike;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Real client `PushTransport` that POSTs encoded PushEvents to the deployed
|
|
119
|
+
* `/sync/push` endpoint, authenticating with the menubar's existing Cognito
|
|
120
|
+
* bearer token — the same auth path VaultClient uses (Authorization: Bearer
|
|
121
|
+
* <accessToken>, token resolved per-request via the supplied getter so it
|
|
122
|
+
* self-heals across refreshes).
|
|
123
|
+
*
|
|
124
|
+
* Failure posture
|
|
125
|
+
* ───────────────
|
|
126
|
+
* `publish()` rejects on a network error, a non-2xx response, or a timeout.
|
|
127
|
+
* The DAEMON is responsible for not letting that rejection crash it — the
|
|
128
|
+
* watcher's emit path catches publish errors and logs them; the periodic
|
|
129
|
+
* cadence poll remains the safety net that eventually ships the change. This
|
|
130
|
+
* transport never swallows errors itself, so callers retain full visibility.
|
|
131
|
+
*
|
|
132
|
+
* `connected` flips true on `start()` and false on `dispose()`. It is purely
|
|
133
|
+
* advisory (HTTP is connectionless); the health endpoint may read it but it
|
|
134
|
+
* carries no delivery guarantee.
|
|
135
|
+
*/
|
|
136
|
+
export declare class HttpPushTransport implements PushTransport {
|
|
137
|
+
private readonly apiUrl;
|
|
138
|
+
private readonly pushPath;
|
|
139
|
+
private readonly getAuthToken;
|
|
140
|
+
private readonly extraHeaders;
|
|
141
|
+
private readonly timeoutMs;
|
|
142
|
+
private readonly fetchImpl;
|
|
143
|
+
private _connected;
|
|
144
|
+
constructor(opts: HttpPushTransportOptions);
|
|
145
|
+
get connected(): boolean;
|
|
146
|
+
start(): Promise<void>;
|
|
147
|
+
publish(event: PushEvent): Promise<void>;
|
|
148
|
+
dispose(): Promise<void>;
|
|
149
|
+
}
|
|
67
150
|
//# sourceMappingURL=push-transport.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push-transport.d.ts","sourceRoot":"","sources":["../../src/sync/push-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"push-transport.d.ts","sourceRoot":"","sources":["../../src/sync/push-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAElE,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,yEAAyE;IACzE,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,2EAA2E;IAC3E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAK;IAEnB,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,oEAAoE;IACpE,IAAI,cAAc,IAAI,MAAM,CAE3B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,OAAO,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B;AAID;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,KACE,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AAExE,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,SAAS,EAAE,eAAe,CAAC;IAC3B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwB;IACrD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyB;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,UAAU,CAAS;gBAEf,IAAI,EAAE,wBAAwB;IAmB1C,IAAI,SAAS,IAAI,OAAO,CAEvB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCxC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* Ported from indigoai-us/hq-pro PR #112 (src/sync/push-transport.ts) into
|
|
31
31
|
* @indigoai-us/hq-cloud (Path B) per project event-driven-sync-menubar US-007.
|
|
32
32
|
*/
|
|
33
|
+
import { encodePushEvent } from "./push-event.js";
|
|
33
34
|
/**
|
|
34
35
|
* Default `PushTransport` used until a real implementation lands.
|
|
35
36
|
*
|
|
@@ -63,4 +64,87 @@ export class NoopPushTransport {
|
|
|
63
64
|
this._connected = false;
|
|
64
65
|
}
|
|
65
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Real client `PushTransport` that POSTs encoded PushEvents to the deployed
|
|
69
|
+
* `/sync/push` endpoint, authenticating with the menubar's existing Cognito
|
|
70
|
+
* bearer token — the same auth path VaultClient uses (Authorization: Bearer
|
|
71
|
+
* <accessToken>, token resolved per-request via the supplied getter so it
|
|
72
|
+
* self-heals across refreshes).
|
|
73
|
+
*
|
|
74
|
+
* Failure posture
|
|
75
|
+
* ───────────────
|
|
76
|
+
* `publish()` rejects on a network error, a non-2xx response, or a timeout.
|
|
77
|
+
* The DAEMON is responsible for not letting that rejection crash it — the
|
|
78
|
+
* watcher's emit path catches publish errors and logs them; the periodic
|
|
79
|
+
* cadence poll remains the safety net that eventually ships the change. This
|
|
80
|
+
* transport never swallows errors itself, so callers retain full visibility.
|
|
81
|
+
*
|
|
82
|
+
* `connected` flips true on `start()` and false on `dispose()`. It is purely
|
|
83
|
+
* advisory (HTTP is connectionless); the health endpoint may read it but it
|
|
84
|
+
* carries no delivery guarantee.
|
|
85
|
+
*/
|
|
86
|
+
export class HttpPushTransport {
|
|
87
|
+
apiUrl;
|
|
88
|
+
pushPath;
|
|
89
|
+
getAuthToken;
|
|
90
|
+
extraHeaders;
|
|
91
|
+
timeoutMs;
|
|
92
|
+
fetchImpl;
|
|
93
|
+
_connected = false;
|
|
94
|
+
constructor(opts) {
|
|
95
|
+
if (!opts.apiUrl || opts.apiUrl.trim() === "") {
|
|
96
|
+
throw new Error("HttpPushTransport: apiUrl is required");
|
|
97
|
+
}
|
|
98
|
+
this.apiUrl = opts.apiUrl.replace(/\/+$/, "");
|
|
99
|
+
const path = opts.pushPath ?? "/sync/push";
|
|
100
|
+
this.pushPath = path.startsWith("/") ? path : `/${path}`;
|
|
101
|
+
const tok = opts.authToken;
|
|
102
|
+
// Normalize string|getter into a single async getter (mirrors VaultClient).
|
|
103
|
+
this.getAuthToken =
|
|
104
|
+
typeof tok === "function" ? async () => tok() : async () => tok;
|
|
105
|
+
this.extraHeaders = opts.headers ?? {};
|
|
106
|
+
this.timeoutMs = opts.timeoutMs ?? 10_000;
|
|
107
|
+
// Bind so a destructured global fetch keeps its receiver.
|
|
108
|
+
this.fetchImpl =
|
|
109
|
+
opts.fetchImpl ??
|
|
110
|
+
((input, init) => globalThis.fetch(input, init));
|
|
111
|
+
}
|
|
112
|
+
get connected() {
|
|
113
|
+
return this._connected;
|
|
114
|
+
}
|
|
115
|
+
async start() {
|
|
116
|
+
this._connected = true;
|
|
117
|
+
}
|
|
118
|
+
async publish(event) {
|
|
119
|
+
// Validate-on-encode (US-007 contract) — a malformed event throws a typed
|
|
120
|
+
// PushEventDecodeError BEFORE we hit the network.
|
|
121
|
+
const body = encodePushEvent(event);
|
|
122
|
+
const token = await this.getAuthToken();
|
|
123
|
+
const controller = new AbortController();
|
|
124
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
125
|
+
try {
|
|
126
|
+
const res = await this.fetchImpl(`${this.apiUrl}${this.pushPath}`, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: {
|
|
129
|
+
...this.extraHeaders,
|
|
130
|
+
Authorization: `Bearer ${token}`,
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
Accept: "application/json",
|
|
133
|
+
},
|
|
134
|
+
body,
|
|
135
|
+
signal: controller.signal,
|
|
136
|
+
});
|
|
137
|
+
if (!res.ok) {
|
|
138
|
+
const text = await res.text().catch(() => "");
|
|
139
|
+
throw new Error(`HttpPushTransport: POST ${this.pushPath} failed (${res.status})${text ? `: ${text}` : ""}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async dispose() {
|
|
147
|
+
this._connected = false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
66
150
|
//# sourceMappingURL=push-transport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push-transport.js","sourceRoot":"","sources":["../../src/sync/push-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;
|
|
1
|
+
{"version":3,"file":"push-transport.js","sourceRoot":"","sources":["../../src/sync/push-transport.ts"],"names":[],"mappings":"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"}
|
package/dist/watcher.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { EntityContext } from "./types.js";
|
|
10
10
|
import type { UploadAuthor } from "./s3.js";
|
|
11
|
+
import type { PushTransport } from "./sync/push-transport.js";
|
|
12
|
+
import type { EventDrivenPushFlagProvider } from "./sync/feature-flags.js";
|
|
11
13
|
/**
|
|
12
14
|
* Injectable clock seam (US-001).
|
|
13
15
|
*
|
|
@@ -145,42 +147,156 @@ export interface TreeWatcherOptions {
|
|
|
145
147
|
* closes the chokidar watcher (releasing fds) and cancels any pending debounce
|
|
146
148
|
* timer. dispose() is stop() + permanent shutdown.
|
|
147
149
|
*/
|
|
150
|
+
/**
|
|
151
|
+
* One settled change-burst, handed to {@link TreeWatcher} listeners. Carries
|
|
152
|
+
* the set of relative paths that changed during the quiet window so a listener
|
|
153
|
+
* (e.g. {@link PushEventEmitter}) can build one PushEvent per path. `paths` is
|
|
154
|
+
* absolute-path → relative-path; both are needed (relative for the wire shape,
|
|
155
|
+
* absolute for hashing/statting the file on disk).
|
|
156
|
+
*/
|
|
157
|
+
export interface TreeChangeBatch {
|
|
158
|
+
/** Map of absolutePath → relativePath for every path in the settled burst. */
|
|
159
|
+
paths: Map<string, string>;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Listener invoked once per settled debounce window.
|
|
163
|
+
*
|
|
164
|
+
* Backwards compatible with the US-003 `WatcherSurface` contract: the first
|
|
165
|
+
* argument is the OPTIONAL changed relative path the loop routes its targeted
|
|
166
|
+
* push to (the first path of the burst; undefined when the window settled with
|
|
167
|
+
* no captured path). US-008's {@link PushEventEmitter} consumes the SECOND
|
|
168
|
+
* argument — the full {@link TreeChangeBatch} of every path in the burst — to
|
|
169
|
+
* build one PushEvent per path. Listeners are free to ignore either argument.
|
|
170
|
+
*/
|
|
171
|
+
export type TreeChangeListener = (changedRelPath?: string, batch?: TreeChangeBatch) => void;
|
|
148
172
|
export declare class TreeWatcher {
|
|
149
173
|
private readonly hqRoot;
|
|
150
174
|
private readonly debounceMs;
|
|
151
175
|
private readonly clock;
|
|
152
176
|
private readonly shouldEmit;
|
|
153
|
-
private
|
|
177
|
+
private backend;
|
|
154
178
|
private timer;
|
|
155
179
|
private listeners;
|
|
180
|
+
/** Paths accumulated for the current (in-flight) debounce window. */
|
|
181
|
+
private pending;
|
|
156
182
|
private disposed;
|
|
157
183
|
constructor(opts: TreeWatcherOptions);
|
|
158
|
-
/**
|
|
159
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Register a debounced-`changed` listener. Returns an unsubscribe fn.
|
|
186
|
+
*
|
|
187
|
+
* Listeners receive a {@link TreeChangeBatch} of the paths that changed in
|
|
188
|
+
* the settled window. Existing US-003 callers that only need the "something
|
|
189
|
+
* changed" signal can ignore the argument — the contract is backwards
|
|
190
|
+
* compatible (a zero-arg callback still type-checks).
|
|
191
|
+
*/
|
|
192
|
+
onChange(listener: TreeChangeListener): () => void;
|
|
160
193
|
/**
|
|
161
194
|
* Begin watching. Idempotent — a second call while already running is a
|
|
162
|
-
* no-op (no second
|
|
195
|
+
* no-op (no second watch backend, no leaked handles).
|
|
196
|
+
*
|
|
197
|
+
* Uses a SINGLE recursive `fs.watch` on macOS/Windows (1 OS handle for the
|
|
198
|
+
* whole tree) and falls back to chokidar on Linux. See {@link startTreeWatch}
|
|
199
|
+
* for why per-path watching is avoided (kqueue fd exhaustion → EMFILE).
|
|
163
200
|
*/
|
|
164
201
|
start(): void;
|
|
165
202
|
/**
|
|
166
|
-
* Test/seam entry point: feed a raw filesystem path as if
|
|
167
|
-
* it. Applies the emit filter then arms the debounce. Real
|
|
168
|
-
* route through here too
|
|
203
|
+
* Test/seam entry point: feed a raw filesystem path as if the backend
|
|
204
|
+
* reported it. Applies the emit filter then arms the debounce. Real watch
|
|
205
|
+
* events route through here too — and for the recursive backend this is the
|
|
206
|
+
* ONLY place filtering happens, so out-of-scope paths are dropped here.
|
|
169
207
|
*/
|
|
170
208
|
handleEvent(absolutePath: string): void;
|
|
171
209
|
private arm;
|
|
172
210
|
private emit;
|
|
173
|
-
/** True while the
|
|
211
|
+
/** True while the watch backend is active. */
|
|
174
212
|
isWatching(): boolean;
|
|
175
213
|
/** Number of pending debounce timers — a leak check for tests. */
|
|
176
214
|
pendingTimerCount(): number;
|
|
177
215
|
/**
|
|
178
|
-
* Stop watching: close the
|
|
179
|
-
* pending debounce timer. Idempotent. The instance can be
|
|
180
|
-
* {@link start} unless {@link dispose} was called.
|
|
216
|
+
* Stop watching: close the watch backend (releasing its OS handle) and
|
|
217
|
+
* cancel any pending debounce timer. Idempotent. The instance can be
|
|
218
|
+
* restarted with {@link start} unless {@link dispose} was called.
|
|
181
219
|
*/
|
|
182
220
|
stop(): void;
|
|
183
221
|
/** Permanent shutdown: stop() + drop listeners; further events are no-ops. */
|
|
184
222
|
dispose(): void;
|
|
185
223
|
}
|
|
224
|
+
export interface PushEventEmitterOptions {
|
|
225
|
+
/** Tenant identifier stamped onto every PushEvent + checked against the flag. */
|
|
226
|
+
originTenantId: string;
|
|
227
|
+
/** Device identifier stamped onto every PushEvent. */
|
|
228
|
+
originDeviceId: string;
|
|
229
|
+
/** Transport that ships each PushEvent (US-007 NoopPushTransport / HttpPushTransport). */
|
|
230
|
+
transport: PushTransport;
|
|
231
|
+
/**
|
|
232
|
+
* Feature-flag seam. When `isEnabled(originTenantId)` is false the emitter is
|
|
233
|
+
* dormant: {@link attach} subscribes nothing and {@link emitForBatch} is a
|
|
234
|
+
* no-op. Defaults are NOT supplied here — the caller injects an
|
|
235
|
+
* EventDrivenPushFlagProvider so dormancy is explicit.
|
|
236
|
+
*/
|
|
237
|
+
flagProvider: EventDrivenPushFlagProvider;
|
|
238
|
+
/**
|
|
239
|
+
* Returns the next monotonic sequence number for this device. Default: an
|
|
240
|
+
* internal counter starting at 0. Inject to persist across daemon restarts.
|
|
241
|
+
*/
|
|
242
|
+
getSequenceNumber?: () => number;
|
|
243
|
+
/** Clock for eventTimestamp. Default `() => new Date()`. */
|
|
244
|
+
now?: () => Date;
|
|
245
|
+
/**
|
|
246
|
+
* Where publish failures + hash/stat errors go. Default `console.error`.
|
|
247
|
+
* Receives the offending PushEvent (when known) so callers can correlate.
|
|
248
|
+
*/
|
|
249
|
+
onError?: (err: Error, ctx: {
|
|
250
|
+
relativePath?: string;
|
|
251
|
+
}) => void;
|
|
252
|
+
/**
|
|
253
|
+
* Optional structured logger for the US-011 3-log diagnostic chain. When
|
|
254
|
+
* supplied, the emitter logs `event=watcher.emit` (the 1st correlated link)
|
|
255
|
+
* carrying the PushEvent's `sequenceNumber` — the same join key stamped by
|
|
256
|
+
* the server `push.receive` log and the client `fanout.receive` log. Default:
|
|
257
|
+
* no log (the daemon stays quiet unless wired with a logger).
|
|
258
|
+
*/
|
|
259
|
+
logger?: EmitterLogger;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Minimal structured logger surface for {@link PushEventEmitter}. A pino
|
|
263
|
+
* `Logger` (from `./sync/logger.ts`) satisfies this; tests inject a fake.
|
|
264
|
+
*/
|
|
265
|
+
export interface EmitterLogger {
|
|
266
|
+
info(obj: Record<string, unknown>, msg?: string): void;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Bridges {@link TreeWatcher} change batches to a {@link PushTransport} as
|
|
270
|
+
* typed PushEvents. Construct once per daemon, then {@link attach} to a
|
|
271
|
+
* running TreeWatcher (returns an unsubscribe fn). Flag-gated + failure-safe.
|
|
272
|
+
*/
|
|
273
|
+
export declare class PushEventEmitter {
|
|
274
|
+
private readonly originTenantId;
|
|
275
|
+
private readonly originDeviceId;
|
|
276
|
+
private readonly transport;
|
|
277
|
+
private readonly flagProvider;
|
|
278
|
+
private readonly now;
|
|
279
|
+
private readonly onError;
|
|
280
|
+
private readonly logger;
|
|
281
|
+
private internalSeq;
|
|
282
|
+
private readonly nextSeq;
|
|
283
|
+
constructor(opts: PushEventEmitterOptions);
|
|
284
|
+
/** True iff event-driven push is enabled for this emitter's tenant. */
|
|
285
|
+
get enabled(): boolean;
|
|
286
|
+
/**
|
|
287
|
+
* Subscribe to a TreeWatcher's change batches. Dormant (no subscription)
|
|
288
|
+
* when the flag is OFF for this tenant. Returns an unsubscribe fn (a no-op
|
|
289
|
+
* when dormant).
|
|
290
|
+
*/
|
|
291
|
+
attach(watcher: TreeWatcher): () => void;
|
|
292
|
+
/**
|
|
293
|
+
* Build + ship one PushEvent per changed path in the batch. No-op when the
|
|
294
|
+
* flag is OFF. Each path is independent: a hash/stat failure or a transport
|
|
295
|
+
* publish rejection for one path is caught + surfaced via `onError` and does
|
|
296
|
+
* NOT abort the others or propagate (the daemon must not crash; the cadence
|
|
297
|
+
* poll covers any miss).
|
|
298
|
+
*/
|
|
299
|
+
emitForBatch(batch: TreeChangeBatch): Promise<void>;
|
|
300
|
+
private emitOne;
|
|
301
|
+
}
|
|
186
302
|
//# sourceMappingURL=watcher.d.ts.map
|
package/dist/watcher.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"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"}
|