@hyperdrive.bot/fleet-server 0.3.101 → 0.3.103

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.
@@ -66,6 +66,15 @@ export declare class IngestionService {
66
66
  */
67
67
  connect(kind: SourceKind, appSlug: string): Promise<SourceConnectResult>;
68
68
  list(): Promise<SourceSummary[]>;
69
+ /**
70
+ * sourceId -> number of filters watching it.
71
+ *
72
+ * Best-effort: a filter store that cannot be read must not blank the sources
73
+ * list, because the count is a decoration on a row whose real subject is the
74
+ * connection. Degrades to an empty map, so every row reports zero rather than
75
+ * the whole screen reporting nothing.
76
+ */
77
+ private filterCounts;
69
78
  /** Check one source and persist the verdict. Null when the source is gone. */
70
79
  check(sourceId: string): Promise<SourceSummary | null>;
71
80
  private hasConfiguredGateway;
@@ -130,11 +130,37 @@ export class IngestionService {
130
130
  lastError: null,
131
131
  });
132
132
  this.startConnectPoll(source);
133
- return { source: this.toSummary(source), redirectUrl: handoff.redirectUrl };
133
+ // 0 is correct and not a placeholder: a source created moments ago cannot
134
+ // have a filter bound to it yet.
135
+ return { source: this.toSummary(source, 0), redirectUrl: handoff.redirectUrl };
134
136
  }
135
137
  async list() {
136
138
  const sources = await this.store.list();
137
- return sources.map((source) => this.toSummary(source));
139
+ // ONE filter read for the whole list, not one per source. `list()` is
140
+ // readdir + parse of every filter file, so calling it inside the map would
141
+ // make a nine-source list do nine full scans of the same directory.
142
+ const counts = await this.filterCounts();
143
+ return sources.map((source) => this.toSummary(source, counts.get(source.id) ?? 0));
144
+ }
145
+ /**
146
+ * sourceId -> number of filters watching it.
147
+ *
148
+ * Best-effort: a filter store that cannot be read must not blank the sources
149
+ * list, because the count is a decoration on a row whose real subject is the
150
+ * connection. Degrades to an empty map, so every row reports zero rather than
151
+ * the whole screen reporting nothing.
152
+ */
153
+ async filterCounts() {
154
+ const counts = new Map();
155
+ try {
156
+ for (const filter of await this.filterStore.list()) {
157
+ counts.set(filter.sourceId, (counts.get(filter.sourceId) ?? 0) + 1);
158
+ }
159
+ }
160
+ catch (error) {
161
+ this.logger.warn({ err: error }, "Filter count unavailable; reporting zero per source");
162
+ }
163
+ return counts;
138
164
  }
139
165
  /** Check one source and persist the verdict. Null when the source is gone. */
140
166
  async check(sourceId) {
@@ -144,7 +170,14 @@ export class IngestionService {
144
170
  }
145
171
  const health = await checkSource(source, this.healthDeps());
146
172
  const updated = await this.persistHealth(sourceId, health);
147
- return updated ? this.toSummary(updated) : null;
173
+ if (!updated) {
174
+ return null;
175
+ }
176
+ // Counted, not defaulted: `check` returns an existing source, and reporting
177
+ // zero filters for a source that has some would let a re-check silently
178
+ // blank the row's work line.
179
+ const counts = await this.filterCounts();
180
+ return this.toSummary(updated, counts.get(updated.id) ?? 0);
148
181
  }
149
182
  hasConfiguredGateway() {
150
183
  return SOURCE_CAPABLE_KINDS.some((kind) => this.resolveGateway(kind).configured);
@@ -421,7 +454,7 @@ export class IngestionService {
421
454
  * StoredSource later reaches the wire the day it is added, with no diff on
422
455
  * this file to review.
423
456
  */
424
- toSummary(source) {
457
+ toSummary(source, filterCount = 0) {
425
458
  return {
426
459
  id: source.id,
427
460
  kind: source.kind,
@@ -435,6 +468,7 @@ export class IngestionService {
435
468
  updatedAt: source.updatedAt,
436
469
  lastCheckedAt: source.lastCheckedAt,
437
470
  lastError: source.lastError,
471
+ filterCount,
438
472
  };
439
473
  }
440
474
  }