@wtfalch/reporting 0.7.0 → 0.9.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.
@@ -0,0 +1,82 @@
1
+ import { sql } from 'drizzle-orm';
2
+ import { deployTags } from './deploy.js';
3
+ import { reportingRequests } from './tables.js';
4
+ /**
5
+ * Counts responses in memory and writes one upsert per minute bucket, so a
6
+ * request costs a map increment and the database sees a handful of rows a
7
+ * minute. `errors` is status >= 500. A failed write puts its counts back for
8
+ * the next one; the process dying loses at most `flushEveryMs` of counts.
9
+ */
10
+ export class RequestCounter {
11
+ o;
12
+ buckets = new Map();
13
+ timer = null;
14
+ constructor(o) {
15
+ this.o = o;
16
+ }
17
+ /** Counts one response. */
18
+ record(status) {
19
+ this.add(status >= 500 ? 1 : 0, 1);
20
+ }
21
+ /**
22
+ * Counts one 5xx for a request already counted by `record`, such as a page
23
+ * that threw after the proxy let it through. Adds no request.
24
+ */
25
+ recordError() {
26
+ this.add(1, 0);
27
+ }
28
+ add(errors, requests) {
29
+ const minute = Math.floor(this.o.now().getTime() / 60_000) * 60_000;
30
+ const b = this.buckets.get(minute) ?? { requests: 0, errors: 0 };
31
+ b.requests += requests;
32
+ b.errors += errors;
33
+ this.buckets.set(minute, b);
34
+ if (!this.timer) {
35
+ this.timer = setTimeout(() => {
36
+ this.timer = null;
37
+ void this.flush();
38
+ }, this.o.flushEveryMs);
39
+ this.timer.unref?.();
40
+ }
41
+ }
42
+ async flush() {
43
+ if (this.buckets.size === 0)
44
+ return;
45
+ const taken = [...this.buckets];
46
+ this.buckets.clear();
47
+ const { color, release } = deployTags();
48
+ try {
49
+ await this.o.db
50
+ .insert(reportingRequests)
51
+ .values(taken.map(([minute, c]) => ({
52
+ bucket: new Date(minute),
53
+ site: this.o.site,
54
+ color: color ?? '',
55
+ release: release ?? '',
56
+ requests: c.requests,
57
+ errors: c.errors,
58
+ })))
59
+ .onConflictDoUpdate({
60
+ target: [
61
+ reportingRequests.bucket,
62
+ reportingRequests.site,
63
+ reportingRequests.color,
64
+ reportingRequests.release,
65
+ ],
66
+ set: {
67
+ requests: sql `${reportingRequests.requests} + excluded.requests`,
68
+ errors: sql `${reportingRequests.errors} + excluded.errors`,
69
+ },
70
+ });
71
+ }
72
+ catch (error) {
73
+ for (const [minute, c] of taken) {
74
+ const b = this.buckets.get(minute) ?? { requests: 0, errors: 0 };
75
+ b.requests += c.requests;
76
+ b.errors += c.errors;
77
+ this.buckets.set(minute, b);
78
+ }
79
+ this.o.log.error({ err: error instanceof Error ? error.name : 'unknown' }, 'reporting: request counter flush failed');
80
+ }
81
+ }
82
+ }
package/dist/tables.d.ts CHANGED
@@ -130,6 +130,40 @@ export declare const reportingEvents: import("drizzle-orm/pg-core").PgTableWithC
130
130
  identity: undefined;
131
131
  generated: undefined;
132
132
  }, {}, {}>;
133
+ release: import("drizzle-orm/pg-core").PgColumn<{
134
+ name: "release";
135
+ tableName: "reporting_events";
136
+ dataType: "string";
137
+ columnType: "PgText";
138
+ data: string;
139
+ driverParam: string;
140
+ notNull: false;
141
+ hasDefault: false;
142
+ isPrimaryKey: false;
143
+ isAutoincrement: false;
144
+ hasRuntimeDefault: false;
145
+ enumValues: [string, ...string[]];
146
+ baseColumn: never;
147
+ identity: undefined;
148
+ generated: undefined;
149
+ }, {}, {}>;
150
+ color: import("drizzle-orm/pg-core").PgColumn<{
151
+ name: "color";
152
+ tableName: "reporting_events";
153
+ dataType: "string";
154
+ columnType: "PgText";
155
+ data: string;
156
+ driverParam: string;
157
+ notNull: false;
158
+ hasDefault: false;
159
+ isPrimaryKey: false;
160
+ isAutoincrement: false;
161
+ hasRuntimeDefault: false;
162
+ enumValues: [string, ...string[]];
163
+ baseColumn: never;
164
+ identity: undefined;
165
+ generated: undefined;
166
+ }, {}, {}>;
133
167
  tenantId: import("drizzle-orm/pg-core").PgColumn<{
134
168
  name: "tenant_id";
135
169
  tableName: "reporting_events";
@@ -878,6 +912,40 @@ export declare const reportingAnalytics: import("drizzle-orm/pg-core").PgTableWi
878
912
  identity: undefined;
879
913
  generated: undefined;
880
914
  }, {}, {}>;
915
+ release: import("drizzle-orm/pg-core").PgColumn<{
916
+ name: "release";
917
+ tableName: "reporting_analytics";
918
+ dataType: "string";
919
+ columnType: "PgText";
920
+ data: string;
921
+ driverParam: string;
922
+ notNull: false;
923
+ hasDefault: false;
924
+ isPrimaryKey: false;
925
+ isAutoincrement: false;
926
+ hasRuntimeDefault: false;
927
+ enumValues: [string, ...string[]];
928
+ baseColumn: never;
929
+ identity: undefined;
930
+ generated: undefined;
931
+ }, {}, {}>;
932
+ color: import("drizzle-orm/pg-core").PgColumn<{
933
+ name: "color";
934
+ tableName: "reporting_analytics";
935
+ dataType: "string";
936
+ columnType: "PgText";
937
+ data: string;
938
+ driverParam: string;
939
+ notNull: false;
940
+ hasDefault: false;
941
+ isPrimaryKey: false;
942
+ isAutoincrement: false;
943
+ hasRuntimeDefault: false;
944
+ enumValues: [string, ...string[]];
945
+ baseColumn: never;
946
+ identity: undefined;
947
+ generated: undefined;
948
+ }, {}, {}>;
881
949
  };
882
950
  dialect: "pg";
883
951
  }>;
@@ -1494,6 +1562,23 @@ export declare const reportingErrors: import("drizzle-orm/pg-core").PgTableWithC
1494
1562
  identity: undefined;
1495
1563
  generated: undefined;
1496
1564
  }, {}, {}>;
1565
+ color: import("drizzle-orm/pg-core").PgColumn<{
1566
+ name: "color";
1567
+ tableName: "reporting_errors";
1568
+ dataType: "string";
1569
+ columnType: "PgText";
1570
+ data: string;
1571
+ driverParam: string;
1572
+ notNull: false;
1573
+ hasDefault: false;
1574
+ isPrimaryKey: false;
1575
+ isAutoincrement: false;
1576
+ hasRuntimeDefault: false;
1577
+ enumValues: [string, ...string[]];
1578
+ baseColumn: never;
1579
+ identity: undefined;
1580
+ generated: undefined;
1581
+ }, {}, {}>;
1497
1582
  firstSeenAt: import("drizzle-orm/pg-core").PgColumn<{
1498
1583
  name: "first_seen_at";
1499
1584
  tableName: "reporting_errors";
@@ -1741,17 +1826,128 @@ export type ReportingErrorRow = typeof reportingErrors.$inferSelect;
1741
1826
  * Counts, times and
1742
1827
  * `tenantId` are the tenant's own (reporting_error_tenants).
1743
1828
  */
1744
- export type TenantErrorRow = Omit<ReportingErrorRow, 'message' | 'stack' | 'runtime' | 'release' | 'requestId' | 'resolvedBy' | 'state' | 'resolvedAt'> & {
1829
+ export type TenantErrorRow = Omit<ReportingErrorRow, 'message' | 'stack' | 'runtime' | 'release' | 'color' | 'requestId' | 'resolvedBy' | 'state' | 'resolvedAt'> & {
1745
1830
  readonly message: null;
1746
1831
  readonly stack: null;
1747
1832
  readonly runtime: null;
1748
1833
  readonly release: null;
1834
+ readonly color: null;
1749
1835
  readonly requestId: null;
1750
1836
  readonly resolvedBy: null;
1751
1837
  /** Triage state is shared across tenants, so a tenant cannot see it. */
1752
1838
  readonly state: null;
1753
1839
  readonly resolvedAt: null;
1754
1840
  };
1841
+ /** Request and 5xx counts per minute, color and release; mirrored from migrations/0010_requests.sql. */
1842
+ export declare const reportingRequests: import("drizzle-orm/pg-core").PgTableWithColumns<{
1843
+ name: "reporting_requests";
1844
+ schema: undefined;
1845
+ columns: {
1846
+ bucket: import("drizzle-orm/pg-core").PgColumn<{
1847
+ name: "bucket";
1848
+ tableName: "reporting_requests";
1849
+ dataType: "date";
1850
+ columnType: "PgTimestamp";
1851
+ data: Date;
1852
+ driverParam: string;
1853
+ notNull: true;
1854
+ hasDefault: false;
1855
+ isPrimaryKey: false;
1856
+ isAutoincrement: false;
1857
+ hasRuntimeDefault: false;
1858
+ enumValues: undefined;
1859
+ baseColumn: never;
1860
+ identity: undefined;
1861
+ generated: undefined;
1862
+ }, {}, {}>;
1863
+ site: import("drizzle-orm/pg-core").PgColumn<{
1864
+ name: "site";
1865
+ tableName: "reporting_requests";
1866
+ dataType: "string";
1867
+ columnType: "PgText";
1868
+ data: string;
1869
+ driverParam: string;
1870
+ notNull: true;
1871
+ hasDefault: false;
1872
+ isPrimaryKey: false;
1873
+ isAutoincrement: false;
1874
+ hasRuntimeDefault: false;
1875
+ enumValues: [string, ...string[]];
1876
+ baseColumn: never;
1877
+ identity: undefined;
1878
+ generated: undefined;
1879
+ }, {}, {}>;
1880
+ color: import("drizzle-orm/pg-core").PgColumn<{
1881
+ name: "color";
1882
+ tableName: "reporting_requests";
1883
+ dataType: "string";
1884
+ columnType: "PgText";
1885
+ data: string;
1886
+ driverParam: string;
1887
+ notNull: true;
1888
+ hasDefault: true;
1889
+ isPrimaryKey: false;
1890
+ isAutoincrement: false;
1891
+ hasRuntimeDefault: false;
1892
+ enumValues: [string, ...string[]];
1893
+ baseColumn: never;
1894
+ identity: undefined;
1895
+ generated: undefined;
1896
+ }, {}, {}>;
1897
+ release: import("drizzle-orm/pg-core").PgColumn<{
1898
+ name: "release";
1899
+ tableName: "reporting_requests";
1900
+ dataType: "string";
1901
+ columnType: "PgText";
1902
+ data: string;
1903
+ driverParam: string;
1904
+ notNull: true;
1905
+ hasDefault: true;
1906
+ isPrimaryKey: false;
1907
+ isAutoincrement: false;
1908
+ hasRuntimeDefault: false;
1909
+ enumValues: [string, ...string[]];
1910
+ baseColumn: never;
1911
+ identity: undefined;
1912
+ generated: undefined;
1913
+ }, {}, {}>;
1914
+ requests: import("drizzle-orm/pg-core").PgColumn<{
1915
+ name: "requests";
1916
+ tableName: "reporting_requests";
1917
+ dataType: "number";
1918
+ columnType: "PgBigInt53";
1919
+ data: number;
1920
+ driverParam: string | number;
1921
+ notNull: true;
1922
+ hasDefault: true;
1923
+ isPrimaryKey: false;
1924
+ isAutoincrement: false;
1925
+ hasRuntimeDefault: false;
1926
+ enumValues: undefined;
1927
+ baseColumn: never;
1928
+ identity: undefined;
1929
+ generated: undefined;
1930
+ }, {}, {}>;
1931
+ errors: import("drizzle-orm/pg-core").PgColumn<{
1932
+ name: "errors";
1933
+ tableName: "reporting_requests";
1934
+ dataType: "number";
1935
+ columnType: "PgBigInt53";
1936
+ data: number;
1937
+ driverParam: string | number;
1938
+ notNull: true;
1939
+ hasDefault: true;
1940
+ isPrimaryKey: false;
1941
+ isAutoincrement: false;
1942
+ hasRuntimeDefault: false;
1943
+ enumValues: undefined;
1944
+ baseColumn: never;
1945
+ identity: undefined;
1946
+ generated: undefined;
1947
+ }, {}, {}>;
1948
+ };
1949
+ dialect: "pg";
1950
+ }>;
1755
1951
  export declare const tables: {
1756
1952
  events: import("drizzle-orm/pg-core").PgTableWithColumns<{
1757
1953
  name: "reporting_events";
@@ -1878,6 +2074,40 @@ export declare const tables: {
1878
2074
  identity: undefined;
1879
2075
  generated: undefined;
1880
2076
  }, {}, {}>;
2077
+ release: import("drizzle-orm/pg-core").PgColumn<{
2078
+ name: "release";
2079
+ tableName: "reporting_events";
2080
+ dataType: "string";
2081
+ columnType: "PgText";
2082
+ data: string;
2083
+ driverParam: string;
2084
+ notNull: false;
2085
+ hasDefault: false;
2086
+ isPrimaryKey: false;
2087
+ isAutoincrement: false;
2088
+ hasRuntimeDefault: false;
2089
+ enumValues: [string, ...string[]];
2090
+ baseColumn: never;
2091
+ identity: undefined;
2092
+ generated: undefined;
2093
+ }, {}, {}>;
2094
+ color: import("drizzle-orm/pg-core").PgColumn<{
2095
+ name: "color";
2096
+ tableName: "reporting_events";
2097
+ dataType: "string";
2098
+ columnType: "PgText";
2099
+ data: string;
2100
+ driverParam: string;
2101
+ notNull: false;
2102
+ hasDefault: false;
2103
+ isPrimaryKey: false;
2104
+ isAutoincrement: false;
2105
+ hasRuntimeDefault: false;
2106
+ enumValues: [string, ...string[]];
2107
+ baseColumn: never;
2108
+ identity: undefined;
2109
+ generated: undefined;
2110
+ }, {}, {}>;
1881
2111
  tenantId: import("drizzle-orm/pg-core").PgColumn<{
1882
2112
  name: "tenant_id";
1883
2113
  tableName: "reporting_events";
@@ -2624,6 +2854,40 @@ export declare const tables: {
2624
2854
  identity: undefined;
2625
2855
  generated: undefined;
2626
2856
  }, {}, {}>;
2857
+ release: import("drizzle-orm/pg-core").PgColumn<{
2858
+ name: "release";
2859
+ tableName: "reporting_analytics";
2860
+ dataType: "string";
2861
+ columnType: "PgText";
2862
+ data: string;
2863
+ driverParam: string;
2864
+ notNull: false;
2865
+ hasDefault: false;
2866
+ isPrimaryKey: false;
2867
+ isAutoincrement: false;
2868
+ hasRuntimeDefault: false;
2869
+ enumValues: [string, ...string[]];
2870
+ baseColumn: never;
2871
+ identity: undefined;
2872
+ generated: undefined;
2873
+ }, {}, {}>;
2874
+ color: import("drizzle-orm/pg-core").PgColumn<{
2875
+ name: "color";
2876
+ tableName: "reporting_analytics";
2877
+ dataType: "string";
2878
+ columnType: "PgText";
2879
+ data: string;
2880
+ driverParam: string;
2881
+ notNull: false;
2882
+ hasDefault: false;
2883
+ isPrimaryKey: false;
2884
+ isAutoincrement: false;
2885
+ hasRuntimeDefault: false;
2886
+ enumValues: [string, ...string[]];
2887
+ baseColumn: never;
2888
+ identity: undefined;
2889
+ generated: undefined;
2890
+ }, {}, {}>;
2627
2891
  };
2628
2892
  dialect: "pg";
2629
2893
  }>;
@@ -3237,6 +3501,23 @@ export declare const tables: {
3237
3501
  identity: undefined;
3238
3502
  generated: undefined;
3239
3503
  }, {}, {}>;
3504
+ color: import("drizzle-orm/pg-core").PgColumn<{
3505
+ name: "color";
3506
+ tableName: "reporting_errors";
3507
+ dataType: "string";
3508
+ columnType: "PgText";
3509
+ data: string;
3510
+ driverParam: string;
3511
+ notNull: false;
3512
+ hasDefault: false;
3513
+ isPrimaryKey: false;
3514
+ isAutoincrement: false;
3515
+ hasRuntimeDefault: false;
3516
+ enumValues: [string, ...string[]];
3517
+ baseColumn: never;
3518
+ identity: undefined;
3519
+ generated: undefined;
3520
+ }, {}, {}>;
3240
3521
  firstSeenAt: import("drizzle-orm/pg-core").PgColumn<{
3241
3522
  name: "first_seen_at";
3242
3523
  tableName: "reporting_errors";
@@ -3468,4 +3749,113 @@ export declare const tables: {
3468
3749
  };
3469
3750
  dialect: "pg";
3470
3751
  }>;
3752
+ requests: import("drizzle-orm/pg-core").PgTableWithColumns<{
3753
+ name: "reporting_requests";
3754
+ schema: undefined;
3755
+ columns: {
3756
+ bucket: import("drizzle-orm/pg-core").PgColumn<{
3757
+ name: "bucket";
3758
+ tableName: "reporting_requests";
3759
+ dataType: "date";
3760
+ columnType: "PgTimestamp";
3761
+ data: Date;
3762
+ driverParam: string;
3763
+ notNull: true;
3764
+ hasDefault: false;
3765
+ isPrimaryKey: false;
3766
+ isAutoincrement: false;
3767
+ hasRuntimeDefault: false;
3768
+ enumValues: undefined;
3769
+ baseColumn: never;
3770
+ identity: undefined;
3771
+ generated: undefined;
3772
+ }, {}, {}>;
3773
+ site: import("drizzle-orm/pg-core").PgColumn<{
3774
+ name: "site";
3775
+ tableName: "reporting_requests";
3776
+ dataType: "string";
3777
+ columnType: "PgText";
3778
+ data: string;
3779
+ driverParam: string;
3780
+ notNull: true;
3781
+ hasDefault: false;
3782
+ isPrimaryKey: false;
3783
+ isAutoincrement: false;
3784
+ hasRuntimeDefault: false;
3785
+ enumValues: [string, ...string[]];
3786
+ baseColumn: never;
3787
+ identity: undefined;
3788
+ generated: undefined;
3789
+ }, {}, {}>;
3790
+ color: import("drizzle-orm/pg-core").PgColumn<{
3791
+ name: "color";
3792
+ tableName: "reporting_requests";
3793
+ dataType: "string";
3794
+ columnType: "PgText";
3795
+ data: string;
3796
+ driverParam: string;
3797
+ notNull: true;
3798
+ hasDefault: true;
3799
+ isPrimaryKey: false;
3800
+ isAutoincrement: false;
3801
+ hasRuntimeDefault: false;
3802
+ enumValues: [string, ...string[]];
3803
+ baseColumn: never;
3804
+ identity: undefined;
3805
+ generated: undefined;
3806
+ }, {}, {}>;
3807
+ release: import("drizzle-orm/pg-core").PgColumn<{
3808
+ name: "release";
3809
+ tableName: "reporting_requests";
3810
+ dataType: "string";
3811
+ columnType: "PgText";
3812
+ data: string;
3813
+ driverParam: string;
3814
+ notNull: true;
3815
+ hasDefault: true;
3816
+ isPrimaryKey: false;
3817
+ isAutoincrement: false;
3818
+ hasRuntimeDefault: false;
3819
+ enumValues: [string, ...string[]];
3820
+ baseColumn: never;
3821
+ identity: undefined;
3822
+ generated: undefined;
3823
+ }, {}, {}>;
3824
+ requests: import("drizzle-orm/pg-core").PgColumn<{
3825
+ name: "requests";
3826
+ tableName: "reporting_requests";
3827
+ dataType: "number";
3828
+ columnType: "PgBigInt53";
3829
+ data: number;
3830
+ driverParam: string | number;
3831
+ notNull: true;
3832
+ hasDefault: true;
3833
+ isPrimaryKey: false;
3834
+ isAutoincrement: false;
3835
+ hasRuntimeDefault: false;
3836
+ enumValues: undefined;
3837
+ baseColumn: never;
3838
+ identity: undefined;
3839
+ generated: undefined;
3840
+ }, {}, {}>;
3841
+ errors: import("drizzle-orm/pg-core").PgColumn<{
3842
+ name: "errors";
3843
+ tableName: "reporting_requests";
3844
+ dataType: "number";
3845
+ columnType: "PgBigInt53";
3846
+ data: number;
3847
+ driverParam: string | number;
3848
+ notNull: true;
3849
+ hasDefault: true;
3850
+ isPrimaryKey: false;
3851
+ isAutoincrement: false;
3852
+ hasRuntimeDefault: false;
3853
+ enumValues: undefined;
3854
+ baseColumn: never;
3855
+ identity: undefined;
3856
+ generated: undefined;
3857
+ }, {}, {}>;
3858
+ };
3859
+ dialect: "pg";
3860
+ }>;
3471
3861
  };
package/dist/tables.js CHANGED
@@ -15,6 +15,8 @@ export const reportingEvents = pgTable('reporting_events', {
15
15
  kindNs: text('kind_ns').generatedAlwaysAs(sql `split_part(kind, '.', 1)`),
16
16
  site: text('site').notNull(),
17
17
  environment: text('environment'),
18
+ release: text('release'),
19
+ color: text('color'),
18
20
  tenantId: uuid('tenant_id'),
19
21
  actorClass: text('actor_class'),
20
22
  actorId: text('actor_id'),
@@ -91,6 +93,8 @@ export const reportingAnalytics = pgTable('reporting_analytics', {
91
93
  .notNull()
92
94
  .default(sql `'{}'::jsonb`),
93
95
  schemaVersion: smallint('schema_version').notNull().default(1),
96
+ release: text('release'),
97
+ color: text('color'),
94
98
  }, (t) => [
95
99
  index('reporting_analytics_site_time_idx').on(t.site, t.occurredAt.desc(), t.id.desc()),
96
100
  index('reporting_analytics_tenant_time_idx')
@@ -162,6 +166,7 @@ export const reportingErrors = pgTable('reporting_errors', {
162
166
  stack: text('stack'),
163
167
  runtime: text('runtime').notNull(),
164
168
  release: text('release'),
169
+ color: text('color'),
165
170
  firstSeenAt: timestamp('first_seen_at', { withTimezone: true }).notNull(),
166
171
  lastSeenAt: timestamp('last_seen_at', { withTimezone: true }).notNull(),
167
172
  occurrences: bigint('occurrences', { mode: 'number' }).notNull().default(1),
@@ -187,6 +192,20 @@ export const reportingErrorTenants = pgTable('reporting_error_tenants', {
187
192
  primaryKey({ name: 'reporting_error_tenants_pkey', columns: [t.fingerprint, t.tenantId] }),
188
193
  index('reporting_error_tenants_tenant_idx').on(t.tenantId, t.lastSeenAt.desc()),
189
194
  ]);
195
+ /** Request and 5xx counts per minute, color and release; mirrored from migrations/0010_requests.sql. */
196
+ export const reportingRequests = pgTable('reporting_requests', {
197
+ bucket: timestamp('bucket', { withTimezone: true }).notNull(),
198
+ site: text('site').notNull(),
199
+ color: text('color').notNull().default(''),
200
+ release: text('release').notNull().default(''),
201
+ requests: bigint('requests', { mode: 'number' }).notNull().default(0),
202
+ errors: bigint('errors', { mode: 'number' }).notNull().default(0),
203
+ }, (t) => [
204
+ primaryKey({
205
+ name: 'reporting_requests_pkey',
206
+ columns: [t.bucket, t.site, t.color, t.release],
207
+ }),
208
+ ]);
190
209
  export const tables = {
191
210
  events: reportingEvents,
192
211
  tasks: reportingTasks,
@@ -197,4 +216,5 @@ export const tables = {
197
216
  analyticsWeekly: reportingAnalyticsWeekly,
198
217
  errors: reportingErrors,
199
218
  errorTenants: reportingErrorTenants,
219
+ requests: reportingRequests,
200
220
  };
package/dist/types.d.ts CHANGED
@@ -81,6 +81,8 @@ export interface ReportingOptions {
81
81
  batchSize?: number;
82
82
  /** Timer flush interval, ms. Default 1,000. */
83
83
  flushEveryMs?: number;
84
+ /** How often counted requests are written, ms. Default 10,000. */
85
+ requestFlushEveryMs?: number;
84
86
  }
85
87
  export interface EventsPageOptions {
86
88
  readonly limit?: number;
@@ -198,6 +200,11 @@ export interface Reporting {
198
200
  readonly site: string;
199
201
  readonly log: Logger;
200
202
  readonly analytics: Analytics;
203
+ /** Counts one response for the release-health read; `flush` writes the counts. Never throws. */
204
+ readonly requests: {
205
+ record(status: number): void;
206
+ recordError(): void;
207
+ };
201
208
  stats(): Stats;
202
209
  }
203
210
  export type { Actor, EventInput, FlatData, Level };
package/dist/writer.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { deployTags } from './deploy.js';
1
2
  import { eventInputSchema } from './schema.js';
2
3
  import { reportingEvents } from './tables.js';
3
4
  export class Writer {
@@ -55,6 +56,7 @@ export class Writer {
55
56
  kind: v.kind,
56
57
  site: this.o.site,
57
58
  environment: this.o.environment,
59
+ ...deployTags(),
58
60
  tenantId: v.tenantId ?? null,
59
61
  actorClass: v.actor?.class ?? null,
60
62
  actorId: v.actor?.id ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/reporting",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
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",
@@ -26,10 +26,6 @@
26
26
  "types": "./dist/next/index.d.ts",
27
27
  "default": "./dist/next/index.js"
28
28
  },
29
- "./fake": {
30
- "types": "./dist/fake/index.d.ts",
31
- "default": "./dist/fake/index.js"
32
- },
33
29
  "./browser": {
34
30
  "types": "./dist/browser/index.d.ts",
35
31
  "default": "./dist/browser/index.js"
@@ -1,22 +0,0 @@
1
- import type { ReportingEventRow } from '../tables.js';
2
- import type { AlertFinding, Logger, Reporting, TrackInput } from '../types.js';
3
- /**
4
- * An in-memory `Reporting` for a host's unit tests: same validation, same
5
- * shape, no database. `rows` is the table; `alerts` is what `onAlert` would
6
- * have received. Validation failures throw, as in development.
7
- */
8
- export interface FakeReporting extends Reporting {
9
- readonly rows: ReportingEventRow[];
10
- readonly alerts: AlertFinding[];
11
- /** What `analytics.track` received. */
12
- readonly tracked: (TrackInput & {
13
- site: string;
14
- })[];
15
- reset(): void;
16
- }
17
- export declare function createFakeReporting(opts?: {
18
- site?: string;
19
- log?: Logger;
20
- redactEnvVars?: readonly string[];
21
- environment?: string | null;
22
- }): FakeReporting;