@fabriktor/fx 0.0.139 → 0.0.141

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/src/chat/data.d.ts +23 -0
  2. package/dist/src/chat/data.js +76 -0
  3. package/dist/src/core/address.d.ts +2 -0
  4. package/dist/src/core/address.js +6 -0
  5. package/dist/src/core/date.d.ts +6 -0
  6. package/dist/src/core/date.js +27 -0
  7. package/dist/src/core/measurement.d.ts +15 -0
  8. package/dist/src/core/measurement.js +45 -0
  9. package/dist/src/core/server-clock.d.ts +10 -0
  10. package/dist/src/core/server-clock.js +36 -0
  11. package/dist/src/fx.d.ts +5 -1
  12. package/dist/src/fx.js +6 -1
  13. package/dist/src/index.d.ts +12 -0
  14. package/dist/src/index.js +12 -0
  15. package/dist/src/routes/arrival.d.ts +16 -0
  16. package/dist/src/routes/arrival.js +50 -0
  17. package/dist/src/routes/availability.d.ts +35 -0
  18. package/dist/src/routes/availability.js +132 -0
  19. package/dist/src/routes/departure.d.ts +45 -0
  20. package/dist/src/routes/departure.js +98 -0
  21. package/dist/src/routes/planning.d.ts +34 -0
  22. package/dist/src/routes/planning.js +94 -0
  23. package/dist/src/routes/punch.d.ts +9 -0
  24. package/dist/src/routes/punch.js +23 -0
  25. package/dist/src/routes/routes.d.ts +45 -12
  26. package/dist/src/routes/routes.js +223 -139
  27. package/dist/src/routes/tracking.d.ts +59 -0
  28. package/dist/src/routes/tracking.js +178 -0
  29. package/dist/src/routes/validation.d.ts +13 -0
  30. package/dist/src/routes/validation.js +227 -0
  31. package/dist/src/routes/workspace.d.ts +74 -0
  32. package/dist/src/routes/workspace.js +212 -0
  33. package/dist/src/timesheets/timesheets.d.ts +3 -1
  34. package/dist/src/timesheets/timesheets.js +3 -0
  35. package/dist/src/timesheets/workspace.d.ts +9 -1
  36. package/dist/src/timesheets/workspace.js +50 -8
  37. package/package.json +4 -3
@@ -3,12 +3,14 @@
3
3
  // Licensed under the Apache License, Version 2.0 (the "License"); you may
4
4
  // not use this file except in compliance with the License. You may obtain
5
5
  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ import { validateRoutePlanAddresses, } from "./departure.js";
6
7
  import { errResolveFailed, errValidation } from "../core/err.js";
8
+ import { RoutesWorkspace, routeCanManage, routeCanPlan, } from "./workspace.js";
9
+ import { RoutePreparationBlocked, RouteMutationBlock, routePlanChanged, } from "./planning.js";
10
+ import { PreActionBlocked } from "../core/pre-action.js";
11
+ import { normalizedTagInput, routeInput, validateClientAvailabilityInput, validateRouteInput, validateRouteTeamInput, validateTrackingInput, } from "./validation.js";
7
12
  import { requiredOperationID } from "../core/id.js";
8
13
  import { runOwnerSearch, } from "../core/search.js";
9
- const maxHour = 23;
10
- const maxMinute = 59;
11
- const maxMinuteOfDay = 1440;
12
14
  const resourceRoute = "route";
13
15
  const resourceTag = "tag";
14
16
  const resourceClientAvailability = "client_availability";
@@ -16,6 +18,7 @@ const resourceTracking = "tracking";
16
18
  const resourceRouteTeam = "route_team";
17
19
  const msgCreatedRouteIDMissing = "Created route response did not contain an ID.";
18
20
  const msgRouteMissing = "Route could not be resolved after the mutation.";
21
+ const msgRouteOwner = "This route does not belong to the active employer.";
19
22
  const msgCreatedTagIDMissing = "Created tag response did not contain an ID.";
20
23
  const msgTagMissing = "Tag could not be resolved after the mutation.";
21
24
  const msgCreatedClientAvailabilityIDMissing = "Created client availability response did not contain an ID.";
@@ -24,42 +27,198 @@ const msgCreatedTrackingIDMissing = "Created tracking response did not contain a
24
27
  const msgTrackingMissing = "Tracking could not be resolved after the mutation.";
25
28
  const msgCreatedRouteTeamIDMissing = "Created route team response did not contain an ID.";
26
29
  const msgRouteTeamMissing = "Route team could not be resolved after the mutation.";
27
- const msgGlobalWindowSameDay = "The global route window must stay within one calendar day.";
28
- const msgGlobalWindowInvalid = "The global route window is invalid.";
29
- const msgEmployeeDuplicate = "An employee cannot be selected in more than one vehicle.";
30
- const msgEmployeeUnavailable = "A selected employee is not available for this route.";
31
- const msgLocationDuplicate = "A location cannot be selected more than once.";
32
- const msgLocationUnavailable = "A selected location is not available for this route.";
33
- const msgDestinationWindowInvalid = "A destination time window is invalid.";
34
- const msgDestinationWindowOutsideGlobal = "A destination time window must stay within the global route window.";
35
30
  export class RoutesFX {
31
+ opts;
36
32
  routes;
33
+ routeMutationPending = false;
34
+ workspace;
37
35
  constructor(opts) {
36
+ this.opts = opts;
38
37
  this.routes = opts.routes;
38
+ this.workspace = new RoutesWorkspace(opts.routes);
39
39
  }
40
40
  async createRoute(opts) {
41
41
  const out = await this.createValidatedRoute(opts);
42
42
  const id = requiredOperationID(out, msgCreatedRouteIDMissing);
43
- return await this.findRoute(id);
43
+ return await this.findRoute(id, opts.signal);
44
44
  }
45
- async createValidatedRoute(opts) {
46
- validateRoute(opts.in, opts.availability);
47
- return await this.routes.insertOneRoute({
45
+ // Recheck availability immediately before each write. An acknowledged ID
46
+ // resumes only the read, so a failed follow-up GET cannot repeat optimization.
47
+ async createPlannedRoute(opts) {
48
+ const { attempt, actor } = opts;
49
+ if (!routeCanPlan(actor))
50
+ throw errValidation({ msg: msgRouteOwner });
51
+ if (attempt.created_id) {
52
+ const route = await this.findRoute(attempt.created_id, attempt.signal);
53
+ if (!routeCanManage(actor, route))
54
+ throw errValidation({ msg: msgRouteOwner });
55
+ return route;
56
+ }
57
+ try {
58
+ validateRouteInput(opts.in);
59
+ await validateRoutePlanAddresses(this.opts, {
60
+ owner_id: actor.owner_id,
61
+ input: opts.in,
62
+ departure: opts.departure,
63
+ signal: attempt.signal,
64
+ });
65
+ }
66
+ catch (error) {
67
+ throw new RoutePreparationBlocked(error);
68
+ }
69
+ const prepared = await this.workspace
70
+ .prepareRoute({
71
+ actor,
48
72
  in: opts.in,
73
+ days_ago: opts.days_ago,
74
+ request: { signal: attempt.signal },
75
+ })
76
+ .catch((error) => {
77
+ // No creation was attempted. Keep the draft editable in the shared queue.
78
+ throw new RoutePreparationBlocked(error);
79
+ });
80
+ return this.createRoute({
81
+ in: prepared.in,
82
+ availability: prepared.availability,
83
+ signal: attempt.signal,
84
+ idempotency_key: attempt.idempotency_key,
85
+ before_request: attempt.before_request,
86
+ on_created: attempt.on_created,
49
87
  });
50
88
  }
89
+ async createValidatedRoute(opts) {
90
+ const input = routeInput(opts.in);
91
+ validateRouteInput(input, opts.availability);
92
+ opts.before_request?.();
93
+ const out = await this.routes.insertOneRoute({
94
+ in: input,
95
+ signal: opts.signal,
96
+ idempotency_key: opts.idempotency_key,
97
+ });
98
+ opts.on_created?.(requiredOperationID(out, msgCreatedRouteIDMissing));
99
+ return out;
100
+ }
51
101
  async replaceRoute(opts) {
52
- await this.routes.replaceOneRoute(opts);
53
- return await this.findRoute(opts.id);
102
+ const current = await this.findRoute(opts.id, opts.signal);
103
+ if (opts.expected)
104
+ this.requireCurrentRoute(current, opts.expected);
105
+ const input = routeInput({ ...current, ...opts.in });
106
+ validateRouteInput(input, opts.availability, opts.id);
107
+ // Recompute changed plans, but do not invalidate vehicle/tracking IDs for
108
+ // an unchanged save. An explicit override still requests reoptimization.
109
+ const override_itinerary = routePlanChanged(current, input);
110
+ if (!override_itinerary)
111
+ return current;
112
+ opts.before_request?.();
113
+ await this.routes.replaceOneRoute({
114
+ id: opts.id,
115
+ signal: opts.signal,
116
+ in: {
117
+ ...opts.in,
118
+ ...(opts.in.from !== undefined ? { from: input.from } : {}),
119
+ ...(opts.in.to !== undefined ? { to: input.to } : {}),
120
+ ...(opts.in.vehicles !== undefined ? { vehicles: input.vehicles } : {}),
121
+ ...(opts.in.destinations !== undefined
122
+ ? { destinations: input.destinations }
123
+ : {}),
124
+ ...(opts.in.start_place_id !== undefined
125
+ ? { start_place_id: input.start_place_id }
126
+ : {}),
127
+ override_itinerary,
128
+ },
129
+ });
130
+ return await this.findRoute(opts.id, opts.signal);
131
+ }
132
+ async replacePlannedRoute(opts) {
133
+ return this.withRouteMutation(async () => {
134
+ const current = await this.checkedRoute(opts);
135
+ const input = routeInput(opts.in);
136
+ validateRouteInput(input);
137
+ if (!routePlanChanged(current, input))
138
+ return current;
139
+ await validateRoutePlanAddresses(this.opts, {
140
+ owner_id: opts.actor.owner_id,
141
+ input,
142
+ departure: opts.departure,
143
+ current,
144
+ signal: opts.signal,
145
+ });
146
+ const availability = await this.workspace.availability({
147
+ actor: opts.actor,
148
+ employee_ids: input.vehicles.flatMap((vehicle) => vehicle.employee_ids),
149
+ date: input.from,
150
+ days_ago: opts.days_ago,
151
+ request: { signal: opts.signal },
152
+ });
153
+ // Retain this route's own assignments, but never another route's.
154
+ validateRouteInput(input, availability, current.id);
155
+ const result = await this.replaceRoute({
156
+ id: current.id,
157
+ in: input,
158
+ expected: current,
159
+ availability,
160
+ signal: opts.signal,
161
+ before_request: opts.before_request,
162
+ });
163
+ if (result.override_itinerary ||
164
+ routePlanChanged(result, {
165
+ ...input,
166
+ override_itinerary: false,
167
+ }))
168
+ throw new PreActionBlocked(RouteMutationBlock.Unconfirmed);
169
+ return result;
170
+ });
171
+ }
172
+ async deletePlannedRoute(opts) {
173
+ return this.withRouteMutation(async () => {
174
+ const current = await this.checkedRoute(opts);
175
+ opts.signal?.throwIfAborted();
176
+ opts.before_request?.();
177
+ await this.routes.deleteOneRoute({ id: current.id });
178
+ });
179
+ }
180
+ async checkedRoute(opts) {
181
+ if (!routeCanManage(opts.actor, opts.expected))
182
+ throw errValidation({ msg: msgRouteOwner });
183
+ const current = await this.findRoute(opts.expected.id, opts.signal);
184
+ if (!routeCanManage(opts.actor, current))
185
+ throw errValidation({ msg: msgRouteOwner });
186
+ this.requireCurrentRoute(current, opts.expected);
187
+ return current;
188
+ }
189
+ requireCurrentRoute(current, expected) {
190
+ if (current.id !== expected.id ||
191
+ current.updated_at !== expected.updated_at)
192
+ throw new PreActionBlocked(RouteMutationBlock.Changed);
193
+ }
194
+ async withRouteMutation(fn) {
195
+ if (this.routeMutationPending)
196
+ throw new PreActionBlocked(RouteMutationBlock.Busy);
197
+ this.routeMutationPending = true;
198
+ try {
199
+ return await fn();
200
+ }
201
+ finally {
202
+ this.routeMutationPending = false;
203
+ }
54
204
  }
55
205
  async createTag(opts) {
56
- const out = await this.routes.insertOneTag(opts);
206
+ const { on_created, before_request, ...request } = opts;
207
+ const input = normalizedTagInput(opts.in);
208
+ before_request?.();
209
+ const out = await this.routes.insertOneTag({
210
+ ...request,
211
+ in: input,
212
+ });
57
213
  const id = requiredOperationID(out, msgCreatedTagIDMissing);
58
- return await this.findTag(id);
214
+ on_created?.(id);
215
+ return await this.findTag(id, opts.signal);
59
216
  }
60
217
  async replaceTag(opts) {
61
- await this.routes.replaceOneTag(opts);
62
- return await this.findTag(opts.id);
218
+ const current = await this.findTag(opts.id, opts.signal);
219
+ const input = normalizedTagInput({ ...current, ...opts.in });
220
+ await this.routes.replaceOneTag({ ...opts, in: input });
221
+ return await this.findTag(opts.id, opts.signal);
63
222
  }
64
223
  async searchTags(opts) {
65
224
  return await runOwnerSearch({
@@ -68,31 +227,53 @@ export class RoutesFX {
68
227
  });
69
228
  }
70
229
  async createClientAvailability(opts) {
71
- const out = await this.routes.insertOneClientAvailability(opts);
230
+ validateClientAvailabilityInput(opts.in);
231
+ const { before_request, on_created, ...request } = opts;
232
+ before_request?.();
233
+ const out = await this.routes.insertOneClientAvailability(request);
72
234
  const id = requiredOperationID(out, msgCreatedClientAvailabilityIDMissing);
73
- return await this.findClientAvailability(id);
235
+ on_created?.(id);
236
+ return await this.findClientAvailability(id, opts.signal);
74
237
  }
75
238
  async replaceClientAvailability(opts) {
76
- await this.routes.replaceOneClientAvailability(opts);
77
- return await this.findClientAvailability(opts.id);
239
+ const current = await this.findClientAvailability(opts.id, opts.signal);
240
+ validateClientAvailabilityInput({ ...current, ...opts.in });
241
+ const { before_request, ...request } = opts;
242
+ before_request?.();
243
+ await this.routes.replaceOneClientAvailability(request);
244
+ return await this.findClientAvailability(opts.id, opts.signal);
78
245
  }
79
246
  async createTracking(opts) {
80
- const out = await this.routes.insertOneTracking(opts);
247
+ validateTrackingInput(opts.in);
248
+ const { before_request, on_created, ...request } = opts;
249
+ before_request?.();
250
+ const out = await this.routes.insertOneTracking(request);
81
251
  const id = requiredOperationID(out, msgCreatedTrackingIDMissing);
82
- return await this.findTracking(id);
252
+ on_created?.(id);
253
+ return await this.findTracking(id, opts.signal);
83
254
  }
84
255
  async replaceTracking(opts) {
85
- await this.routes.replaceOneTracking(opts);
86
- return await this.findTracking(opts.id);
256
+ const current = await this.findTracking(opts.id, opts.signal);
257
+ validateTrackingInput({ ...current, ...opts.in }, current);
258
+ const { before_request, ...request } = opts;
259
+ before_request?.();
260
+ await this.routes.replaceOneTracking(request);
261
+ return await this.findTracking(opts.id, opts.signal);
87
262
  }
88
263
  async createRouteTeam(opts) {
89
- const out = await this.routes.insertOneRouteTeam(opts);
264
+ const { on_created, before_request, ...request } = opts;
265
+ validateRouteTeamInput(opts.in);
266
+ before_request?.();
267
+ const out = await this.routes.insertOneRouteTeam(request);
90
268
  const id = requiredOperationID(out, msgCreatedRouteTeamIDMissing);
91
- return await this.findRouteTeam(id);
269
+ on_created?.(id);
270
+ return await this.findRouteTeam(id, opts.signal);
92
271
  }
93
272
  async replaceRouteTeam(opts) {
273
+ const current = await this.findRouteTeam(opts.id, opts.signal);
274
+ validateRouteTeamInput({ ...current, ...opts.in });
94
275
  await this.routes.replaceOneRouteTeam(opts);
95
- return await this.findRouteTeam(opts.id);
276
+ return await this.findRouteTeam(opts.id, opts.signal);
96
277
  }
97
278
  async searchRouteTeams(opts) {
98
279
  return await runOwnerSearch({
@@ -100,9 +281,10 @@ export class RoutesFX {
100
281
  search: (searchOpts) => this.routes.searchManyRouteTeams(searchOpts),
101
282
  });
102
283
  }
103
- async findRoute(id) {
284
+ async findRoute(id, signal) {
104
285
  const out = await this.routes.findOneRoute({
105
286
  id,
287
+ signal,
106
288
  });
107
289
  const route = out.value;
108
290
  if (route === undefined) {
@@ -113,9 +295,10 @@ export class RoutesFX {
113
295
  }
114
296
  return route;
115
297
  }
116
- async findTag(id) {
298
+ async findTag(id, signal) {
117
299
  const out = await this.routes.findOneTag({
118
300
  id,
301
+ signal,
119
302
  });
120
303
  const tag = out.value;
121
304
  if (tag === undefined) {
@@ -126,9 +309,10 @@ export class RoutesFX {
126
309
  }
127
310
  return tag;
128
311
  }
129
- async findClientAvailability(id) {
312
+ async findClientAvailability(id, signal) {
130
313
  const out = await this.routes.findOneClientAvailability({
131
314
  id,
315
+ signal,
132
316
  });
133
317
  const client_availability = out.value;
134
318
  if (client_availability === undefined) {
@@ -139,9 +323,10 @@ export class RoutesFX {
139
323
  }
140
324
  return client_availability;
141
325
  }
142
- async findTracking(id) {
326
+ async findTracking(id, signal) {
143
327
  const out = await this.routes.findOneTracking({
144
328
  id,
329
+ signal,
145
330
  });
146
331
  const tracking = out.value;
147
332
  if (tracking === undefined) {
@@ -152,9 +337,10 @@ export class RoutesFX {
152
337
  }
153
338
  return tracking;
154
339
  }
155
- async findRouteTeam(id) {
340
+ async findRouteTeam(id, signal) {
156
341
  const out = await this.routes.findOneRouteTeam({
157
342
  id,
343
+ signal,
158
344
  });
159
345
  const route_team = out.value;
160
346
  if (route_team === undefined) {
@@ -169,105 +355,3 @@ export class RoutesFX {
169
355
  export function newRoutesFX(opts) {
170
356
  return new RoutesFX(opts);
171
357
  }
172
- function validateRoute(input, availability) {
173
- if (!sameDay(input.from, input.to)) {
174
- throw errValidation({
175
- field: "to",
176
- msg: msgGlobalWindowSameDay,
177
- });
178
- }
179
- const fromMinute = minuteOfDay(input.from);
180
- const toMinute = minuteOfDay(input.to);
181
- if (fromMinute === undefined ||
182
- toMinute === undefined ||
183
- fromMinute >= toMinute) {
184
- throw errValidation({
185
- field: "to",
186
- msg: msgGlobalWindowInvalid,
187
- });
188
- }
189
- validateVehicles(input, availability);
190
- validateDestinations(input, availability, fromMinute, toMinute);
191
- }
192
- function validateVehicles(input, availability) {
193
- const availableEmployeeIDs = new Set(availability.availables.employees.map((id) => id.trim()));
194
- const employeeIDs = new Set();
195
- for (const vehicle of input.vehicles) {
196
- for (const rawEmployeeID of vehicle.employee_ids) {
197
- const employeeID = rawEmployeeID.trim();
198
- if (employeeID === "") {
199
- continue;
200
- }
201
- if (employeeIDs.has(employeeID)) {
202
- throw errValidation({
203
- field: "vehicles",
204
- msg: msgEmployeeDuplicate,
205
- });
206
- }
207
- if (!availableEmployeeIDs.has(employeeID)) {
208
- throw errValidation({
209
- field: "vehicles",
210
- msg: msgEmployeeUnavailable,
211
- });
212
- }
213
- employeeIDs.add(employeeID);
214
- }
215
- }
216
- }
217
- function validateDestinations(input, availability, fromMinute, toMinute) {
218
- const availableLocationIDs = new Set(availability.availables.locations.map((id) => id.trim()));
219
- const locationIDs = new Set();
220
- for (const destination of input.destinations) {
221
- const locationID = destination.location_id.trim();
222
- if (locationID !== "") {
223
- if (locationIDs.has(locationID)) {
224
- throw errValidation({
225
- field: "destinations",
226
- msg: msgLocationDuplicate,
227
- });
228
- }
229
- if (!availableLocationIDs.has(locationID)) {
230
- throw errValidation({
231
- field: "destinations",
232
- msg: msgLocationUnavailable,
233
- });
234
- }
235
- locationIDs.add(locationID);
236
- }
237
- if (!destination.use_time_window) {
238
- continue;
239
- }
240
- const start = destination.time_window_start_hour_in_minutes;
241
- const end = destination.time_window_end_hour_in_minutes;
242
- if (!Number.isInteger(start) ||
243
- !Number.isInteger(end) ||
244
- start < 0 ||
245
- end > maxMinuteOfDay ||
246
- start >= end) {
247
- throw errValidation({
248
- field: "destinations",
249
- msg: msgDestinationWindowInvalid,
250
- });
251
- }
252
- if (start < fromMinute || end > toMinute) {
253
- throw errValidation({
254
- field: "destinations",
255
- msg: msgDestinationWindowOutsideGlobal,
256
- });
257
- }
258
- }
259
- }
260
- function sameDay(a, b) {
261
- return a.y === b.y && a.m === b.m && a.d === b.d;
262
- }
263
- function minuteOfDay(v) {
264
- if (!Number.isInteger(v.h) ||
265
- !Number.isInteger(v.min) ||
266
- v.h < 0 ||
267
- v.h > maxHour ||
268
- v.min < 0 ||
269
- v.min > maxMinute) {
270
- return undefined;
271
- }
272
- return v.h * 60 + v.min;
273
- }
@@ -0,0 +1,59 @@
1
+ import type { RoutesOperator } from "@fabriktor/client";
2
+ import { type Route, type Tracking } from "@fabriktor/schema";
3
+ import type { ServerClock } from "../core/server-clock.js";
4
+ import type { RoutesFXOperator } from "./routes.js";
5
+ import { type RoutesActor, type RoutesWorkspace } from "./workspace.js";
6
+ export declare const trackingRefreshDelayMS = 15000;
7
+ export declare const trackingRefreshMaxAttempts = 240;
8
+ export declare const trackingStaleAfterMS = 90000;
9
+ export declare const trackingMaximumAccuracyMeters = 200;
10
+ export declare const trackingMaximumSampleAgeMS = 30000;
11
+ export declare const trackingReadTimeoutMS = 15000;
12
+ export declare const TrackingIssue: {
13
+ readonly Access: "tracking_access";
14
+ readonly OutsideRoute: "tracking_outside_route";
15
+ readonly Timezone: "tracking_timezone";
16
+ readonly Accuracy: "tracking_accuracy";
17
+ readonly Busy: "tracking_busy";
18
+ readonly Unconfirmed: "tracking_unconfirmed";
19
+ readonly Record: "tracking_record";
20
+ };
21
+ export declare class TrackingError extends Error {
22
+ readonly reason: (typeof TrackingIssue)[keyof typeof TrackingIssue];
23
+ constructor(reason: (typeof TrackingIssue)[keyof typeof TrackingIssue]);
24
+ }
25
+ export type TrackingPosition = Pick<Tracking, "lat" | "lon"> & {
26
+ accuracy: number;
27
+ };
28
+ export type TrackingEntry = {
29
+ employee_id: string;
30
+ tracking?: Tracking;
31
+ };
32
+ export type TrackingFreshness = "recent" | "stale" | "unknown";
33
+ export type TrackingScope = {
34
+ actor: RoutesActor;
35
+ route: Route;
36
+ vehicle_id: string;
37
+ };
38
+ export declare function trackingFreshness(record: Tracking | undefined, now: number | null): TrackingFreshness;
39
+ export declare function loadRouteTracking(workspace: RoutesWorkspace, scope: TrackingScope, signal?: AbortSignal): Promise<TrackingEntry[]>;
40
+ type ReportOptions = TrackingScope & {
41
+ timezone: string;
42
+ position: TrackingPosition;
43
+ signal: AbortSignal;
44
+ still_active: () => boolean;
45
+ sample_current: () => boolean;
46
+ };
47
+ export declare class RouteTrackingReporter {
48
+ private readonly routes;
49
+ private readonly actions;
50
+ private readonly clock;
51
+ private busy;
52
+ private readonly checkpoints;
53
+ constructor(routes: RoutesOperator, actions: RoutesFXOperator, clock: ServerClock);
54
+ private key;
55
+ unconfirmed(scope: TrackingScope): boolean;
56
+ allowNewRecord(scope: TrackingScope): void;
57
+ report(opts: ReportOptions): Promise<Tracking>;
58
+ }
59
+ export {};