@wtfalch/reporting 0.3.0 → 0.4.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/dist/tables.d.ts CHANGED
@@ -113,6 +113,23 @@ export declare const reportingEvents: import("drizzle-orm/pg-core").PgTableWithC
113
113
  identity: undefined;
114
114
  generated: undefined;
115
115
  }, {}, {}>;
116
+ environment: import("drizzle-orm/pg-core").PgColumn<{
117
+ name: "environment";
118
+ tableName: "reporting_events";
119
+ dataType: "string";
120
+ columnType: "PgText";
121
+ data: string;
122
+ driverParam: string;
123
+ notNull: false;
124
+ hasDefault: false;
125
+ isPrimaryKey: false;
126
+ isAutoincrement: false;
127
+ hasRuntimeDefault: false;
128
+ enumValues: [string, ...string[]];
129
+ baseColumn: never;
130
+ identity: undefined;
131
+ generated: undefined;
132
+ }, {}, {}>;
116
133
  tenantId: import("drizzle-orm/pg-core").PgColumn<{
117
134
  name: "tenant_id";
118
135
  tableName: "reporting_events";
@@ -506,6 +523,99 @@ export declare const reportingSettings: import("drizzle-orm/pg-core").PgTableWit
506
523
  };
507
524
  dialect: "pg";
508
525
  }>;
526
+ /** A tenant's own override of a retention setting; mirrored from migrations/0006_tenant_settings.sql. */
527
+ export declare const reportingTenantSettings: import("drizzle-orm/pg-core").PgTableWithColumns<{
528
+ name: "reporting_tenant_settings";
529
+ schema: undefined;
530
+ columns: {
531
+ tenantId: import("drizzle-orm/pg-core").PgColumn<{
532
+ name: "tenant_id";
533
+ tableName: "reporting_tenant_settings";
534
+ dataType: "string";
535
+ columnType: "PgUUID";
536
+ data: string;
537
+ driverParam: string;
538
+ notNull: true;
539
+ hasDefault: false;
540
+ isPrimaryKey: false;
541
+ isAutoincrement: false;
542
+ hasRuntimeDefault: false;
543
+ enumValues: undefined;
544
+ baseColumn: never;
545
+ identity: undefined;
546
+ generated: undefined;
547
+ }, {}, {}>;
548
+ key: import("drizzle-orm/pg-core").PgColumn<{
549
+ name: "key";
550
+ tableName: "reporting_tenant_settings";
551
+ dataType: "string";
552
+ columnType: "PgText";
553
+ data: string;
554
+ driverParam: string;
555
+ notNull: true;
556
+ hasDefault: false;
557
+ isPrimaryKey: false;
558
+ isAutoincrement: false;
559
+ hasRuntimeDefault: false;
560
+ enumValues: [string, ...string[]];
561
+ baseColumn: never;
562
+ identity: undefined;
563
+ generated: undefined;
564
+ }, {}, {}>;
565
+ value: import("drizzle-orm/pg-core").PgColumn<{
566
+ name: "value";
567
+ tableName: "reporting_tenant_settings";
568
+ dataType: "json";
569
+ columnType: "PgJsonb";
570
+ data: unknown;
571
+ driverParam: unknown;
572
+ notNull: true;
573
+ hasDefault: false;
574
+ isPrimaryKey: false;
575
+ isAutoincrement: false;
576
+ hasRuntimeDefault: false;
577
+ enumValues: undefined;
578
+ baseColumn: never;
579
+ identity: undefined;
580
+ generated: undefined;
581
+ }, {}, {}>;
582
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
583
+ name: "updated_at";
584
+ tableName: "reporting_tenant_settings";
585
+ dataType: "date";
586
+ columnType: "PgTimestamp";
587
+ data: Date;
588
+ driverParam: string;
589
+ notNull: true;
590
+ hasDefault: true;
591
+ isPrimaryKey: false;
592
+ isAutoincrement: false;
593
+ hasRuntimeDefault: false;
594
+ enumValues: undefined;
595
+ baseColumn: never;
596
+ identity: undefined;
597
+ generated: undefined;
598
+ }, {}, {}>;
599
+ updatedBy: import("drizzle-orm/pg-core").PgColumn<{
600
+ name: "updated_by";
601
+ tableName: "reporting_tenant_settings";
602
+ dataType: "string";
603
+ columnType: "PgText";
604
+ data: string;
605
+ driverParam: string;
606
+ notNull: false;
607
+ hasDefault: false;
608
+ isPrimaryKey: false;
609
+ isAutoincrement: false;
610
+ hasRuntimeDefault: false;
611
+ enumValues: [string, ...string[]];
612
+ baseColumn: never;
613
+ identity: undefined;
614
+ generated: undefined;
615
+ }, {}, {}>;
616
+ };
617
+ dialect: "pg";
618
+ }>;
509
619
  /** Raw analytics rows, a window's worth; mirrored from migrations/0002_analytics.sql. */
510
620
  export declare const reportingAnalytics: import("drizzle-orm/pg-core").PgTableWithColumns<{
511
621
  name: "reporting_analytics";
@@ -1282,6 +1392,23 @@ export declare const reportingErrors: import("drizzle-orm/pg-core").PgTableWithC
1282
1392
  identity: undefined;
1283
1393
  generated: undefined;
1284
1394
  }, {}, {}>;
1395
+ environment: import("drizzle-orm/pg-core").PgColumn<{
1396
+ name: "environment";
1397
+ tableName: "reporting_errors";
1398
+ dataType: "string";
1399
+ columnType: "PgText";
1400
+ data: string;
1401
+ driverParam: string;
1402
+ notNull: false;
1403
+ hasDefault: false;
1404
+ isPrimaryKey: false;
1405
+ isAutoincrement: false;
1406
+ hasRuntimeDefault: false;
1407
+ enumValues: [string, ...string[]];
1408
+ baseColumn: never;
1409
+ identity: undefined;
1410
+ generated: undefined;
1411
+ }, {}, {}>;
1285
1412
  kind: import("drizzle-orm/pg-core").PgColumn<{
1286
1413
  name: "kind";
1287
1414
  tableName: "reporting_errors";
@@ -1512,6 +1639,7 @@ export type ReportingAnalyticsDailyRow = typeof reportingAnalyticsDaily.$inferSe
1512
1639
  export type ReportingAnalyticsWeeklyRow = typeof reportingAnalyticsWeekly.$inferSelect;
1513
1640
  export type ReportingTaskRow = typeof reportingTasks.$inferSelect;
1514
1641
  export type ReportingSettingRow = typeof reportingSettings.$inferSelect;
1642
+ export type ReportingTenantSettingRow = typeof reportingTenantSettings.$inferSelect;
1515
1643
  export type ReportingErrorRow = typeof reportingErrors.$inferSelect;
1516
1644
  export declare const tables: {
1517
1645
  events: import("drizzle-orm/pg-core").PgTableWithColumns<{
@@ -1622,6 +1750,23 @@ export declare const tables: {
1622
1750
  identity: undefined;
1623
1751
  generated: undefined;
1624
1752
  }, {}, {}>;
1753
+ environment: import("drizzle-orm/pg-core").PgColumn<{
1754
+ name: "environment";
1755
+ tableName: "reporting_events";
1756
+ dataType: "string";
1757
+ columnType: "PgText";
1758
+ data: string;
1759
+ driverParam: string;
1760
+ notNull: false;
1761
+ hasDefault: false;
1762
+ isPrimaryKey: false;
1763
+ isAutoincrement: false;
1764
+ hasRuntimeDefault: false;
1765
+ enumValues: [string, ...string[]];
1766
+ baseColumn: never;
1767
+ identity: undefined;
1768
+ generated: undefined;
1769
+ }, {}, {}>;
1625
1770
  tenantId: import("drizzle-orm/pg-core").PgColumn<{
1626
1771
  name: "tenant_id";
1627
1772
  tableName: "reporting_events";
@@ -2015,6 +2160,98 @@ export declare const tables: {
2015
2160
  };
2016
2161
  dialect: "pg";
2017
2162
  }>;
2163
+ tenantSettings: import("drizzle-orm/pg-core").PgTableWithColumns<{
2164
+ name: "reporting_tenant_settings";
2165
+ schema: undefined;
2166
+ columns: {
2167
+ tenantId: import("drizzle-orm/pg-core").PgColumn<{
2168
+ name: "tenant_id";
2169
+ tableName: "reporting_tenant_settings";
2170
+ dataType: "string";
2171
+ columnType: "PgUUID";
2172
+ data: string;
2173
+ driverParam: string;
2174
+ notNull: true;
2175
+ hasDefault: false;
2176
+ isPrimaryKey: false;
2177
+ isAutoincrement: false;
2178
+ hasRuntimeDefault: false;
2179
+ enumValues: undefined;
2180
+ baseColumn: never;
2181
+ identity: undefined;
2182
+ generated: undefined;
2183
+ }, {}, {}>;
2184
+ key: import("drizzle-orm/pg-core").PgColumn<{
2185
+ name: "key";
2186
+ tableName: "reporting_tenant_settings";
2187
+ dataType: "string";
2188
+ columnType: "PgText";
2189
+ data: string;
2190
+ driverParam: string;
2191
+ notNull: true;
2192
+ hasDefault: false;
2193
+ isPrimaryKey: false;
2194
+ isAutoincrement: false;
2195
+ hasRuntimeDefault: false;
2196
+ enumValues: [string, ...string[]];
2197
+ baseColumn: never;
2198
+ identity: undefined;
2199
+ generated: undefined;
2200
+ }, {}, {}>;
2201
+ value: import("drizzle-orm/pg-core").PgColumn<{
2202
+ name: "value";
2203
+ tableName: "reporting_tenant_settings";
2204
+ dataType: "json";
2205
+ columnType: "PgJsonb";
2206
+ data: unknown;
2207
+ driverParam: unknown;
2208
+ notNull: true;
2209
+ hasDefault: false;
2210
+ isPrimaryKey: false;
2211
+ isAutoincrement: false;
2212
+ hasRuntimeDefault: false;
2213
+ enumValues: undefined;
2214
+ baseColumn: never;
2215
+ identity: undefined;
2216
+ generated: undefined;
2217
+ }, {}, {}>;
2218
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
2219
+ name: "updated_at";
2220
+ tableName: "reporting_tenant_settings";
2221
+ dataType: "date";
2222
+ columnType: "PgTimestamp";
2223
+ data: Date;
2224
+ driverParam: string;
2225
+ notNull: true;
2226
+ hasDefault: true;
2227
+ isPrimaryKey: false;
2228
+ isAutoincrement: false;
2229
+ hasRuntimeDefault: false;
2230
+ enumValues: undefined;
2231
+ baseColumn: never;
2232
+ identity: undefined;
2233
+ generated: undefined;
2234
+ }, {}, {}>;
2235
+ updatedBy: import("drizzle-orm/pg-core").PgColumn<{
2236
+ name: "updated_by";
2237
+ tableName: "reporting_tenant_settings";
2238
+ dataType: "string";
2239
+ columnType: "PgText";
2240
+ data: string;
2241
+ driverParam: string;
2242
+ notNull: false;
2243
+ hasDefault: false;
2244
+ isPrimaryKey: false;
2245
+ isAutoincrement: false;
2246
+ hasRuntimeDefault: false;
2247
+ enumValues: [string, ...string[]];
2248
+ baseColumn: never;
2249
+ identity: undefined;
2250
+ generated: undefined;
2251
+ }, {}, {}>;
2252
+ };
2253
+ dialect: "pg";
2254
+ }>;
2018
2255
  analytics: import("drizzle-orm/pg-core").PgTableWithColumns<{
2019
2256
  name: "reporting_analytics";
2020
2257
  schema: undefined;
@@ -2787,6 +3024,23 @@ export declare const tables: {
2787
3024
  identity: undefined;
2788
3025
  generated: undefined;
2789
3026
  }, {}, {}>;
3027
+ environment: import("drizzle-orm/pg-core").PgColumn<{
3028
+ name: "environment";
3029
+ tableName: "reporting_errors";
3030
+ dataType: "string";
3031
+ columnType: "PgText";
3032
+ data: string;
3033
+ driverParam: string;
3034
+ notNull: false;
3035
+ hasDefault: false;
3036
+ isPrimaryKey: false;
3037
+ isAutoincrement: false;
3038
+ hasRuntimeDefault: false;
3039
+ enumValues: [string, ...string[]];
3040
+ baseColumn: never;
3041
+ identity: undefined;
3042
+ generated: undefined;
3043
+ }, {}, {}>;
2790
3044
  kind: import("drizzle-orm/pg-core").PgColumn<{
2791
3045
  name: "kind";
2792
3046
  tableName: "reporting_errors";
package/dist/tables.js CHANGED
@@ -14,6 +14,7 @@ export const reportingEvents = pgTable('reporting_events', {
14
14
  kind: text('kind').notNull(),
15
15
  kindNs: text('kind_ns').generatedAlwaysAs(sql `split_part(kind, '.', 1)`),
16
16
  site: text('site').notNull(),
17
+ environment: text('environment'),
17
18
  tenantId: uuid('tenant_id'),
18
19
  actorClass: text('actor_class'),
19
20
  actorId: text('actor_id'),
@@ -57,6 +58,19 @@ export const reportingSettings = pgTable('reporting_settings', {
57
58
  updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().default(sql `now()`),
58
59
  updatedBy: text('updated_by'),
59
60
  });
61
+ /** A tenant's own override of a retention setting; mirrored from migrations/0006_tenant_settings.sql. */
62
+ export const reportingTenantSettings = pgTable('reporting_tenant_settings', {
63
+ tenantId: uuid('tenant_id').notNull(),
64
+ key: text('key').notNull(),
65
+ value: jsonb('value').notNull(),
66
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().default(sql `now()`),
67
+ updatedBy: text('updated_by'),
68
+ }, (t) => [
69
+ primaryKey({
70
+ name: 'reporting_tenant_settings_pkey',
71
+ columns: [t.tenantId, t.key],
72
+ }),
73
+ ]);
60
74
  /** Raw analytics rows, a window's worth; mirrored from migrations/0002_analytics.sql. */
61
75
  export const reportingAnalytics = pgTable('reporting_analytics', {
62
76
  id: bigint('id', { mode: 'number' }).generatedAlwaysAsIdentity().primaryKey(),
@@ -142,6 +156,7 @@ export const reportingAnalyticsWeekly = pgTable('reporting_analytics_weekly', {
142
156
  export const reportingErrors = pgTable('reporting_errors', {
143
157
  fingerprint: text('fingerprint').primaryKey(),
144
158
  site: text('site').notNull(),
159
+ environment: text('environment'),
145
160
  kind: text('kind').notNull(),
146
161
  message: text('message').notNull(),
147
162
  stack: text('stack'),
@@ -165,6 +180,7 @@ export const tables = {
165
180
  events: reportingEvents,
166
181
  tasks: reportingTasks,
167
182
  settings: reportingSettings,
183
+ tenantSettings: reportingTenantSettings,
168
184
  analytics: reportingAnalytics,
169
185
  analyticsDaily: reportingAnalyticsDaily,
170
186
  analyticsWeekly: reportingAnalyticsWeekly,
package/dist/types.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
2
2
  import type { RecentEvent, RecentOptions, SeriesOptions, SeriesPoint, WeeklyPoint } from './analytics/reader.js';
3
3
  import type { Device } from './analytics/schema.js';
4
4
  import type { Actor, ErrorRuntime, ErrorState, EventInput, FlatData, Level } from './schema.js';
5
+ import type { TenantRetentionKey } from './settings.js';
5
6
  import type { ReportingErrorRow, ReportingEventRow, tables } from './tables.js';
6
7
  /**
7
8
  * The host's drizzle handle, whatever driver it runs on. postgres-js in the
@@ -41,6 +42,15 @@ export interface ReportingOptions {
41
42
  log: Logger;
42
43
  /** This host's id, stamped on every row: `^[a-z][a-z0-9-]{0,63}$`. */
43
44
  site: string;
45
+ /**
46
+ * The deploy target, stamped on every row the same way `site` is:
47
+ * `production`, `stage`, a per-PR `preview`, or the host's own scheme
48
+ * (`^[a-z][a-z0-9_-]{0,31}$`). A factory app runs prod, stage and one
49
+ * preview per PR under the same `site`; without this there is no way to
50
+ * keep preview noise out of a production view. Optional and unset by
51
+ * default, same as `release`.
52
+ */
53
+ environment?: string | null;
44
54
  /**
45
55
  * Runs a flush after the current unit of work. The `./next` entry passes
46
56
  * `after()`; the default is `setTimeout(0)`, which a script keeps until it
@@ -56,6 +66,15 @@ export interface ReportingOptions {
56
66
  audit?: (change: SettingsChange) => Promise<void>;
57
67
  /** The host's route normalisation for analytics paths (addendum A7); the package's default otherwise. */
58
68
  normalisePath?: (pathname: string) => string;
69
+ /**
70
+ * Extra environment variable names to redact from a captured error's
71
+ * `message` and `stack`, beyond the estate's own `KEYSTORE_KEK`/
72
+ * `KEYSTORE_KEK_PREVIOUS`. Each is read the same way
73
+ * `KEYSTORE_KEK_PREVIOUS` is: comma-separated values allowed, each
74
+ * trimmed. A company app the factory stamps may hold secret env vars the
75
+ * estate's own convention does not name.
76
+ */
77
+ redactEnvVars?: readonly string[];
59
78
  /** Queue bound; past it rows go to the logger only. Default 1,000. */
60
79
  queueLimit?: number;
61
80
  /** Rows per insert. Default 500. */
@@ -74,6 +93,7 @@ export interface EventsPageOptions {
74
93
  readonly tenantId?: string;
75
94
  readonly site?: string;
76
95
  readonly requestId?: string;
96
+ readonly environment?: string;
77
97
  }
78
98
  export interface EventsPage {
79
99
  readonly items: readonly ReportingEventRow[];
@@ -91,6 +111,9 @@ export interface ErrorsPageOptions {
91
111
  readonly site?: string;
92
112
  readonly state?: ErrorState;
93
113
  readonly runtime?: ErrorRuntime;
114
+ /** A case-insensitive substring match against `message` or `stack`, for triaging an incident by grepping the group list. Capped at 200 characters. */
115
+ readonly search?: string;
116
+ readonly environment?: string;
94
117
  }
95
118
  export interface ErrorsPage {
96
119
  readonly items: readonly ReportingErrorRow[];
@@ -163,6 +186,12 @@ export interface Reporting {
163
186
  get(): Promise<Settings>;
164
187
  set(patch: Partial<Settings>, by: Actor): Promise<SettingsChange>;
165
188
  };
189
+ /** A per-tenant override of a retention setting (gap issue #11); a key a tenant has not set falls back to `settings`. */
190
+ readonly tenantSettings: {
191
+ get(tenantId: string): Promise<Partial<Record<TenantRetentionKey, number>>>;
192
+ /** `days: null` clears the override, falling back to the site-wide default. Throws on an out-of-bounds value. */
193
+ set(tenantId: string, key: TenantRetentionKey, days: number | null, by: Actor): Promise<void>;
194
+ };
166
195
  readonly tables: typeof tables;
167
196
  readonly site: string;
168
197
  readonly log: Logger;
package/dist/writer.d.ts CHANGED
@@ -3,6 +3,7 @@ export interface WriterOptions {
3
3
  db: Db;
4
4
  log: Logger;
5
5
  site: string;
6
+ environment: string | null;
6
7
  mode: Mode;
7
8
  now: () => Date;
8
9
  defer: (fn: () => Promise<void>) => void;
package/dist/writer.js CHANGED
@@ -54,6 +54,7 @@ export class Writer {
54
54
  level: v.level,
55
55
  kind: v.kind,
56
56
  site: this.o.site,
57
+ environment: this.o.environment,
57
58
  tenantId: v.tenantId ?? null,
58
59
  actorClass: v.actor?.class ?? null,
59
60
  actorId: v.actor?.id ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/reporting",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",