@wtfalch/reporting 0.1.0 → 0.1.1

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/dist/writer.d.ts CHANGED
@@ -27,11 +27,18 @@ export declare class Writer {
27
27
  private enqueue;
28
28
  /** Once per idle period through `defer`, and always on the timer. */
29
29
  private schedule;
30
- /** Drain until empty or the deadline. One flush in flight at a time; a second call waits on the first. */
30
+ /**
31
+ * Drain until the queue is empty and nothing is in flight, or the deadline.
32
+ * Two callers overlap all the time (the timer and a request's `after()`,
33
+ * the tick and its own timer), so "empty" is not enough: a row another
34
+ * flush spliced off the queue a moment ago is still on its way, and a
35
+ * caller that returned on an empty queue would read the table too early.
36
+ * Every caller therefore waits for whatever is in flight and looks again.
37
+ */
31
38
  flush(opts?: {
32
39
  deadlineMs?: number;
33
40
  }): Promise<void>;
34
- private lastFailed;
41
+ /** One insert of one batch. Resolves true on success; never throws. */
35
42
  private flushOnce;
36
43
  stats(): Stats;
37
44
  }
package/dist/writer.js CHANGED
@@ -95,25 +95,41 @@ export class Writer {
95
95
  this.timer.unref?.();
96
96
  }
97
97
  }
98
- /** Drain until empty or the deadline. One flush in flight at a time; a second call waits on the first. */
98
+ /**
99
+ * Drain until the queue is empty and nothing is in flight, or the deadline.
100
+ * Two callers overlap all the time (the timer and a request's `after()`,
101
+ * the tick and its own timer), so "empty" is not enough: a row another
102
+ * flush spliced off the queue a moment ago is still on its way, and a
103
+ * caller that returned on an empty queue would read the table too early.
104
+ * Every caller therefore waits for whatever is in flight and looks again.
105
+ */
99
106
  async flush(opts = {}) {
100
107
  const deadline = Date.now() + (opts.deadlineMs ?? 5000);
101
- if (this.inFlight)
102
- await this.inFlight;
103
- while (this.queue.length > 0 && Date.now() < deadline) {
104
- this.inFlight = this.flushOnce();
105
- try {
108
+ while (Date.now() < deadline) {
109
+ if (this.inFlight) {
106
110
  await this.inFlight;
111
+ continue;
112
+ }
113
+ if (this.queue.length === 0)
114
+ break;
115
+ const attempt = this.flushOnce();
116
+ this.inFlight = attempt;
117
+ let ok = false;
118
+ try {
119
+ ok = await attempt;
107
120
  }
108
121
  finally {
109
- this.inFlight = null;
122
+ if (this.inFlight === attempt)
123
+ this.inFlight = null;
110
124
  }
111
- if (this.failedFlushes > 0 && this.queue.length > 0 && this.lastFailed)
125
+ // A failed insert leaves its rows queued; do not spin on the database
126
+ // inside one call. The timer and the next defer try again.
127
+ if (!ok)
112
128
  break;
113
129
  }
114
130
  this.deferred = false;
115
131
  }
116
- lastFailed = false;
132
+ /** One insert of one batch. Resolves true on success; never throws. */
117
133
  async flushOnce() {
118
134
  const batch = this.queue.splice(0, this.o.batchSize);
119
135
  if (this.droppedSinceReport > 0) {
@@ -133,11 +149,10 @@ export class Writer {
133
149
  try {
134
150
  await this.o.db.insert(reportingEvents).values(batch);
135
151
  this.flushed += batch.length;
136
- this.lastFailed = false;
152
+ return true;
137
153
  }
138
154
  catch (error) {
139
155
  this.failedFlushes += 1;
140
- this.lastFailed = true;
141
156
  // Back at the front, so order survives; the bound still applies, and
142
157
  // whatever does not fit is counted as dropped rather than kept for ever.
143
158
  const room = Math.max(0, this.o.queueLimit - this.queue.length);
@@ -146,6 +161,7 @@ export class Writer {
146
161
  this.droppedSinceReport += batch.length - kept.length;
147
162
  this.queue.unshift(...kept);
148
163
  this.o.log.error({ err: describe(error), rows: batch.length, queued: this.queue.length }, 'reporting: flush failed; rows stay queued');
164
+ return false;
149
165
  }
150
166
  }
151
167
  stats() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/reporting",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The estate's operational event log and housekeeping tick: a writer that never fails a request, a claim protocol that needs no scheduler, and the readers an operator opens. Per-app Postgres, host-supplied logger, framework-neutral core with a Next entry.",
5
5
  "repository": {
6
6
  "type": "git",