@basaltkit/audit-viewer 1.2.0 → 1.3.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/plugin.js +5 -2
- package/dist/viewer.d.ts +29 -1
- package/dist/viewer.js +27 -4
- package/package.json +2 -2
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createToken, ctx, definePlugin } from '@basaltkit/core';
|
|
1
|
+
import { createToken, ctx, definePlugin, ensureMetadata } from '@basaltkit/core';
|
|
2
2
|
import { AUDIT } from '@basaltkit/audit';
|
|
3
3
|
import { route } from '@basaltkit/http';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -9,7 +9,10 @@ export function auditViewerPlugin(options = {}) {
|
|
|
9
9
|
return definePlugin({
|
|
10
10
|
name: 'basalt:audit-viewer',
|
|
11
11
|
register({ container }) {
|
|
12
|
-
|
|
12
|
+
// 'tenancy:active' is tenancyPlugin's marker: how a generic package
|
|
13
|
+
// learns the app is multi-tenant without importing @basaltkit/tenancy.
|
|
14
|
+
const metadata = ensureMetadata(container);
|
|
15
|
+
container.singleton(AUDIT_VIEWER, () => new AuditViewer(container.get(AUDIT), options, () => metadata.get('tenancy:active').length > 0));
|
|
13
16
|
},
|
|
14
17
|
});
|
|
15
18
|
}
|
package/dist/viewer.d.ts
CHANGED
|
@@ -64,10 +64,28 @@ export interface AuditViewerOptions {
|
|
|
64
64
|
*/
|
|
65
65
|
export declare class AuditViewer {
|
|
66
66
|
private readonly audit;
|
|
67
|
+
/**
|
|
68
|
+
* Whether the host app is multi-tenant, i.e. whether `@basaltkit/tenancy`
|
|
69
|
+
* is registered. `auditViewerPlugin` wires this to the container's
|
|
70
|
+
* `'tenancy:active'` metadata marker — a signal, not an import, so this
|
|
71
|
+
* generic package never depends on the opt-in SaaS layer.
|
|
72
|
+
*
|
|
73
|
+
* Defaults to `false`: a hand-built viewer behaves single-tenant.
|
|
74
|
+
*/
|
|
75
|
+
private readonly tenancyActive;
|
|
67
76
|
private readonly bucketMs;
|
|
68
77
|
private readonly topN;
|
|
69
78
|
private readonly maxScan;
|
|
70
|
-
constructor(audit: Audit, options?: AuditViewerOptions
|
|
79
|
+
constructor(audit: Audit, options?: AuditViewerOptions,
|
|
80
|
+
/**
|
|
81
|
+
* Whether the host app is multi-tenant, i.e. whether `@basaltkit/tenancy`
|
|
82
|
+
* is registered. `auditViewerPlugin` wires this to the container's
|
|
83
|
+
* `'tenancy:active'` metadata marker — a signal, not an import, so this
|
|
84
|
+
* generic package never depends on the opt-in SaaS layer.
|
|
85
|
+
*
|
|
86
|
+
* Defaults to `false`: a hand-built viewer behaves single-tenant.
|
|
87
|
+
*/
|
|
88
|
+
tenancyActive?: () => boolean);
|
|
71
89
|
page(query?: ViewerQuery): Promise<AuditPage>;
|
|
72
90
|
get(id: string, tenantId?: string): Promise<AuditEntry | null>;
|
|
73
91
|
stats(query?: ViewerQuery): Promise<AuditStats>;
|
|
@@ -75,5 +93,15 @@ export declare class AuditViewer {
|
|
|
75
93
|
/** Reads at most `maxScan` rows and reports whether the trail had more. */
|
|
76
94
|
private match;
|
|
77
95
|
private top;
|
|
96
|
+
/**
|
|
97
|
+
* The tenant to scope a read to, or `undefined` when the app has no tenant
|
|
98
|
+
* dimension at all.
|
|
99
|
+
*
|
|
100
|
+
* In a multi-tenant app an unresolvable tenant is an error — an unscoped read
|
|
101
|
+
* would cross tenants. In a single-tenant app (no `tenancyPlugin`) there is
|
|
102
|
+
* nothing to scope to and nothing to cross, so the read proceeds unscoped;
|
|
103
|
+
* `Audit.trail()` applies the same rule one layer down and still forces the
|
|
104
|
+
* ambient tenant whenever one exists.
|
|
105
|
+
*/
|
|
78
106
|
private tenant;
|
|
79
107
|
}
|
package/dist/viewer.js
CHANGED
|
@@ -14,11 +14,22 @@ const DEFAULT_MAX_SCAN = 10_000;
|
|
|
14
14
|
*/
|
|
15
15
|
export class AuditViewer {
|
|
16
16
|
audit;
|
|
17
|
+
tenancyActive;
|
|
17
18
|
bucketMs;
|
|
18
19
|
topN;
|
|
19
20
|
maxScan;
|
|
20
|
-
constructor(audit, options = {}
|
|
21
|
+
constructor(audit, options = {},
|
|
22
|
+
/**
|
|
23
|
+
* Whether the host app is multi-tenant, i.e. whether `@basaltkit/tenancy`
|
|
24
|
+
* is registered. `auditViewerPlugin` wires this to the container's
|
|
25
|
+
* `'tenancy:active'` metadata marker — a signal, not an import, so this
|
|
26
|
+
* generic package never depends on the opt-in SaaS layer.
|
|
27
|
+
*
|
|
28
|
+
* Defaults to `false`: a hand-built viewer behaves single-tenant.
|
|
29
|
+
*/
|
|
30
|
+
tenancyActive = () => false) {
|
|
21
31
|
this.audit = audit;
|
|
32
|
+
this.tenancyActive = tenancyActive;
|
|
22
33
|
this.bucketMs = options.bucketMs ?? DAY;
|
|
23
34
|
this.topN = options.topN ?? 20;
|
|
24
35
|
this.maxScan = options.maxScan ?? DEFAULT_MAX_SCAN;
|
|
@@ -61,7 +72,7 @@ export class AuditViewer {
|
|
|
61
72
|
async match(query) {
|
|
62
73
|
const tenantId = this.tenant(query.tenantId);
|
|
63
74
|
const trail = await this.audit.trail({
|
|
64
|
-
tenantId,
|
|
75
|
+
...(tenantId !== undefined ? { tenantId } : {}),
|
|
65
76
|
limit: this.maxScan,
|
|
66
77
|
...(query.event !== undefined ? { event: query.event } : {}),
|
|
67
78
|
...(query.actorId !== undefined ? { actorId: query.actorId } : {}),
|
|
@@ -74,10 +85,22 @@ export class AuditViewer {
|
|
|
74
85
|
top(counts) {
|
|
75
86
|
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, this.topN);
|
|
76
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* The tenant to scope a read to, or `undefined` when the app has no tenant
|
|
90
|
+
* dimension at all.
|
|
91
|
+
*
|
|
92
|
+
* In a multi-tenant app an unresolvable tenant is an error — an unscoped read
|
|
93
|
+
* would cross tenants. In a single-tenant app (no `tenancyPlugin`) there is
|
|
94
|
+
* nothing to scope to and nothing to cross, so the read proceeds unscoped;
|
|
95
|
+
* `Audit.trail()` applies the same rule one layer down and still forces the
|
|
96
|
+
* ambient tenant whenever one exists.
|
|
97
|
+
*/
|
|
77
98
|
tenant(explicit) {
|
|
78
99
|
const id = explicit ?? tryCtx()?.['tenant']?.id;
|
|
79
|
-
if (
|
|
100
|
+
if (id)
|
|
101
|
+
return id;
|
|
102
|
+
if (this.tenancyActive())
|
|
80
103
|
throw new AuditTenantRequiredError();
|
|
81
|
-
return
|
|
104
|
+
return undefined;
|
|
82
105
|
}
|
|
83
106
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basaltkit/audit-viewer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=22.5.0"
|
|
6
6
|
},
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@basaltkit/audit": "^1.
|
|
21
|
+
"@basaltkit/audit": "^1.4.0",
|
|
22
22
|
"@basaltkit/core": "^1.3.1",
|
|
23
23
|
"@basaltkit/http": "^1.14.0"
|
|
24
24
|
},
|