@dudousxd/nestjs-catalog 0.1.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/LICENSE +21 -0
- package/README.md +123 -0
- package/dist/catalog.controller.d.ts +8 -0
- package/dist/catalog.controller.js +482 -0
- package/dist/catalog.decorators.d.ts +37 -0
- package/dist/catalog.decorators.js +50 -0
- package/dist/catalog.environment.d.ts +442 -0
- package/dist/catalog.environment.js +645 -0
- package/dist/catalog.events.d.ts +179 -0
- package/dist/catalog.events.js +110 -0
- package/dist/catalog.module.d.ts +5 -0
- package/dist/catalog.module.js +71 -0
- package/dist/catalog.options.d.ts +79 -0
- package/dist/catalog.options.js +4 -0
- package/dist/catalog.overlay-store.d.ts +25 -0
- package/dist/catalog.overlay-store.js +44 -0
- package/dist/catalog.overlay-store.token.d.ts +1 -0
- package/dist/catalog.overlay-store.token.js +4 -0
- package/dist/catalog.pipeline.d.ts +800 -0
- package/dist/catalog.pipeline.js +606 -0
- package/dist/catalog.principal.d.ts +209 -0
- package/dist/catalog.principal.js +245 -0
- package/dist/catalog.query-cache.d.ts +25 -0
- package/dist/catalog.query-cache.js +0 -0
- package/dist/catalog.query.d.ts +76 -0
- package/dist/catalog.query.js +64 -0
- package/dist/catalog.registry.base.d.ts +21 -0
- package/dist/catalog.registry.base.js +17 -0
- package/dist/catalog.registry.d.ts +44 -0
- package/dist/catalog.registry.js +359 -0
- package/dist/catalog.service.d.ts +115 -0
- package/dist/catalog.service.js +366 -0
- package/dist/catalog.store.d.ts +419 -0
- package/dist/catalog.store.js +175 -0
- package/dist/catalog.types.d.ts +165 -0
- package/dist/catalog.types.js +19 -0
- package/dist/catalog.workspace.d.ts +426 -0
- package/dist/catalog.workspace.js +87 -0
- package/dist/client.d.ts +86 -0
- package/dist/client.js +83 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +109 -0
- package/dist/stores/mikro-orm-read.store.d.ts +20 -0
- package/dist/stores/mikro-orm-read.store.js +120 -0
- package/dist/transform-runner.d.ts +54 -0
- package/dist/transform-runner.js +280 -0
- package/package.json +54 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The metadata model.
|
|
3
|
+
*
|
|
4
|
+
* Two ideas hold this together:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Structure is derived, semantics are declared.** Everything that can be
|
|
7
|
+
* read off the ORM (column names, SQL types, nullability, foreign keys) is
|
|
8
|
+
* derived — no human writes it twice. Everything a human knows and the
|
|
9
|
+
* database does not (what to call it, what it means, who may see it) is
|
|
10
|
+
* declared with decorators or, for the purely cosmetic parts, edited at
|
|
11
|
+
* runtime through the overlay.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Tier 0 changes are not schema changes.** Display names, descriptions,
|
|
14
|
+
* grouping, visibility and ordering never touch the database, so they never
|
|
15
|
+
* need a migration and never need an engineer. That boundary is what makes
|
|
16
|
+
* it safe to hand the editor to a non-engineer.
|
|
17
|
+
*/
|
|
18
|
+
export type ScalarType = 'string' | 'number' | 'boolean' | 'date' | 'json' | 'uuid' | 'unknown';
|
|
19
|
+
export type RelationKind = '1:1' | '1:m' | 'm:1' | 'm:n';
|
|
20
|
+
/** A single scalar field on an object type. */
|
|
21
|
+
export interface CatalogPropertyDef {
|
|
22
|
+
/** Code name, as written in the entity. Stable — the overlay never changes it. */
|
|
23
|
+
name: string;
|
|
24
|
+
/** Human label. Tier 0: editable at runtime, no migration. */
|
|
25
|
+
displayName: string;
|
|
26
|
+
/** What this field means in the business. Tier 0. */
|
|
27
|
+
description?: string;
|
|
28
|
+
type: ScalarType;
|
|
29
|
+
/** The physical column. Derived from the ORM; shown so engineers can trace it. */
|
|
30
|
+
columnName: string;
|
|
31
|
+
nullable: boolean;
|
|
32
|
+
primary: boolean;
|
|
33
|
+
/** Tier 0: hide from generic UIs without dropping the column. */
|
|
34
|
+
hidden: boolean;
|
|
35
|
+
/** Tier 0: display order. Lower sorts first. */
|
|
36
|
+
order: number;
|
|
37
|
+
/** e.g. "CUI", "UNCLASSIFIED". Drives redaction in generic UIs. Tier 0. */
|
|
38
|
+
classification?: string;
|
|
39
|
+
/** e.g. "miles", "USD", "days". Tier 0. */
|
|
40
|
+
unit?: string;
|
|
41
|
+
/** True when the value came from a hand-written decorator rather than the ORM. */
|
|
42
|
+
enriched: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** A link between two object types. Derived entirely from the ORM. */
|
|
45
|
+
export interface CatalogRelationDef {
|
|
46
|
+
name: string;
|
|
47
|
+
displayName: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
kind: RelationKind;
|
|
50
|
+
/** Class name of the type on the other end. */
|
|
51
|
+
targetType: string;
|
|
52
|
+
/** Property on this side holding the key, when the ORM exposes one. */
|
|
53
|
+
localKey?: string;
|
|
54
|
+
nullable: boolean;
|
|
55
|
+
hidden: boolean;
|
|
56
|
+
order: number;
|
|
57
|
+
}
|
|
58
|
+
/** One node of the ontology. */
|
|
59
|
+
export interface CatalogObjectTypeDef {
|
|
60
|
+
/** Entity class name — the stable identity. */
|
|
61
|
+
name: string;
|
|
62
|
+
/** Tier 0. */
|
|
63
|
+
displayName: string;
|
|
64
|
+
/** Tier 0. */
|
|
65
|
+
pluralDisplayName: string;
|
|
66
|
+
/** Tier 0. */
|
|
67
|
+
description?: string;
|
|
68
|
+
/** Physical table. Derived. */
|
|
69
|
+
tableName: string;
|
|
70
|
+
/** Tier 0: an emoji or icon key, for the generic UIs. */
|
|
71
|
+
icon?: string;
|
|
72
|
+
/** Tier 0: the section this type belongs to, e.g. "Fleet", "Maintenance". */
|
|
73
|
+
group: string;
|
|
74
|
+
/**
|
|
75
|
+
* Tier 0: which property to render when this object appears as a link.
|
|
76
|
+
* Falls back to the primary key.
|
|
77
|
+
*/
|
|
78
|
+
titleProperty?: string;
|
|
79
|
+
primaryKey: string[];
|
|
80
|
+
/**
|
|
81
|
+
* True when a human has said anything about this type — a `@CatalogType`, a
|
|
82
|
+
* `@CatalogProperty`, or a tier-0 edit. The whole point of showing it is to
|
|
83
|
+
* make the un-enriched types visible, because those are the ones whose names
|
|
84
|
+
* came from a regex and are probably wrong.
|
|
85
|
+
*/
|
|
86
|
+
enriched: boolean;
|
|
87
|
+
properties: CatalogPropertyDef[];
|
|
88
|
+
relations: CatalogRelationDef[];
|
|
89
|
+
}
|
|
90
|
+
export interface CatalogSnapshot {
|
|
91
|
+
/** Bumps whenever the overlay changes, so clients can cache. */
|
|
92
|
+
version: number;
|
|
93
|
+
generatedAt: string;
|
|
94
|
+
/** How many types the ORM exposed vs. how many carry hand-written semantics. */
|
|
95
|
+
stats: {
|
|
96
|
+
types: number;
|
|
97
|
+
properties: number;
|
|
98
|
+
relations: number;
|
|
99
|
+
enrichedTypes: number;
|
|
100
|
+
};
|
|
101
|
+
types: CatalogObjectTypeDef[];
|
|
102
|
+
}
|
|
103
|
+
/** Nodes and edges, for drawing the ontology. */
|
|
104
|
+
export interface CatalogGraph {
|
|
105
|
+
nodes: Array<{
|
|
106
|
+
id: string;
|
|
107
|
+
label: string;
|
|
108
|
+
group: string;
|
|
109
|
+
icon?: string;
|
|
110
|
+
propertyCount: number;
|
|
111
|
+
relationCount: number;
|
|
112
|
+
}>;
|
|
113
|
+
edges: Array<{
|
|
114
|
+
id: string;
|
|
115
|
+
source: string;
|
|
116
|
+
target: string;
|
|
117
|
+
label: string;
|
|
118
|
+
kind: RelationKind;
|
|
119
|
+
}>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The runtime-editable slice. Everything here is tier 0 — it changes what
|
|
123
|
+
* people see, never what the database holds.
|
|
124
|
+
*/
|
|
125
|
+
export interface CatalogOverlay {
|
|
126
|
+
types: Record<string, {
|
|
127
|
+
displayName?: string;
|
|
128
|
+
pluralDisplayName?: string;
|
|
129
|
+
description?: string;
|
|
130
|
+
icon?: string;
|
|
131
|
+
group?: string;
|
|
132
|
+
titleProperty?: string;
|
|
133
|
+
properties?: Record<string, {
|
|
134
|
+
displayName?: string;
|
|
135
|
+
description?: string;
|
|
136
|
+
hidden?: boolean;
|
|
137
|
+
order?: number;
|
|
138
|
+
classification?: string;
|
|
139
|
+
unit?: string;
|
|
140
|
+
}>;
|
|
141
|
+
}>;
|
|
142
|
+
}
|
|
143
|
+
export interface CatalogObjectQuery {
|
|
144
|
+
page?: number;
|
|
145
|
+
size?: number;
|
|
146
|
+
search?: string;
|
|
147
|
+
sort?: string;
|
|
148
|
+
dir?: 'asc' | 'desc';
|
|
149
|
+
}
|
|
150
|
+
export interface CatalogObjectPage {
|
|
151
|
+
type: string;
|
|
152
|
+
page: number;
|
|
153
|
+
size: number;
|
|
154
|
+
total: number;
|
|
155
|
+
pages: number;
|
|
156
|
+
/** Only the visible, non-redacted columns, in overlay order. */
|
|
157
|
+
columns: Array<{
|
|
158
|
+
name: string;
|
|
159
|
+
displayName: string;
|
|
160
|
+
type: ScalarType;
|
|
161
|
+
classification?: string;
|
|
162
|
+
unit?: string;
|
|
163
|
+
}>;
|
|
164
|
+
rows: Array<Record<string, unknown>>;
|
|
165
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The metadata model.
|
|
4
|
+
*
|
|
5
|
+
* Two ideas hold this together:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Structure is derived, semantics are declared.** Everything that can be
|
|
8
|
+
* read off the ORM (column names, SQL types, nullability, foreign keys) is
|
|
9
|
+
* derived — no human writes it twice. Everything a human knows and the
|
|
10
|
+
* database does not (what to call it, what it means, who may see it) is
|
|
11
|
+
* declared with decorators or, for the purely cosmetic parts, edited at
|
|
12
|
+
* runtime through the overlay.
|
|
13
|
+
*
|
|
14
|
+
* 2. **Tier 0 changes are not schema changes.** Display names, descriptions,
|
|
15
|
+
* grouping, visibility and ordering never touch the database, so they never
|
|
16
|
+
* need a migration and never need an engineer. That boundary is what makes
|
|
17
|
+
* it safe to hand the editor to a non-engineer.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The things people make *with* a catalog, as opposed to the model itself:
|
|
3
|
+
* saved queries, dashboards, and the record of what happened to all of it.
|
|
4
|
+
*
|
|
5
|
+
* Kept apart from the store interfaces because these are optional in a
|
|
6
|
+
* different way. A store that cannot run SQL simply has no saved queries; a
|
|
7
|
+
* store with no event log still serves every read. A host should be able to
|
|
8
|
+
* mount the catalog without any of this and notice nothing missing.
|
|
9
|
+
*/
|
|
10
|
+
export interface SavedQuery {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
sql: string;
|
|
15
|
+
/** Free-form grouping, the way a folder would work without being one. */
|
|
16
|
+
folder?: string;
|
|
17
|
+
/** Which application or person saved it. */
|
|
18
|
+
createdBy: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
/**
|
|
22
|
+
* How long a result may be reused, in seconds. Zero means never cache.
|
|
23
|
+
*
|
|
24
|
+
* Per query rather than global: "how many vehicles are critical" tolerates a
|
|
25
|
+
* five-minute-old answer, and the query behind a month-end report does not.
|
|
26
|
+
*/
|
|
27
|
+
cacheTtlSeconds: number;
|
|
28
|
+
/** How the dashboard should draw it. */
|
|
29
|
+
visualization: QueryVisualization;
|
|
30
|
+
/**
|
|
31
|
+
* Whether another application may fetch this through the embed API.
|
|
32
|
+
*
|
|
33
|
+
* Explicit, and never inferred from the SQL. A saved query can join five
|
|
34
|
+
* relations, so working out "which types does this touch" means parsing the
|
|
35
|
+
* statement — and a permission derived from a parser is a permission that
|
|
36
|
+
* silently widens the day the parser meets a query it did not expect. Marking
|
|
37
|
+
* it shared is a decision a person made, and it shows up in the audit trail
|
|
38
|
+
* as one.
|
|
39
|
+
*/
|
|
40
|
+
shared: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface QueryVisualization {
|
|
43
|
+
kind: 'table' | 'bar' | 'line' | 'area' | 'number';
|
|
44
|
+
/**
|
|
45
|
+
* Which chart library draws it, by registered name.
|
|
46
|
+
*
|
|
47
|
+
* Undefined means the built-in renderer, which needs no dependency. Naming a
|
|
48
|
+
* library nobody registered falls back to that rather than failing — a
|
|
49
|
+
* dashboard should degrade to a plainer chart, not to an error.
|
|
50
|
+
*/
|
|
51
|
+
library?: string;
|
|
52
|
+
/** Column for the category axis, or the label of a single number. */
|
|
53
|
+
labelColumn?: string;
|
|
54
|
+
/** Columns to plot. Empty means every numeric column. */
|
|
55
|
+
valueColumns?: string[];
|
|
56
|
+
}
|
|
57
|
+
export interface SaveQueryInput {
|
|
58
|
+
name: string;
|
|
59
|
+
sql: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
folder?: string;
|
|
62
|
+
cacheTtlSeconds?: number;
|
|
63
|
+
visualization?: QueryVisualization;
|
|
64
|
+
shared?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface Dashboard {
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
createdBy: string;
|
|
71
|
+
createdAt: string;
|
|
72
|
+
updatedAt: string;
|
|
73
|
+
cards: DashboardCard[];
|
|
74
|
+
/** Fetchable through the embed API by an application with `catalog:embed`. */
|
|
75
|
+
shared: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface DashboardCard {
|
|
78
|
+
id: string;
|
|
79
|
+
savedQueryId: string;
|
|
80
|
+
/** Overrides the saved query's own title on this dashboard. */
|
|
81
|
+
title?: string;
|
|
82
|
+
/** 1–4, in a twelve-column grid. Kept coarse on purpose. */
|
|
83
|
+
width: 1 | 2 | 3 | 4;
|
|
84
|
+
position: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* One thing that happened.
|
|
88
|
+
*
|
|
89
|
+
* The same events the library emits on `aviary:catalog:*`, durably recorded.
|
|
90
|
+
* The channel is for observers that are listening *now*; governance asks about
|
|
91
|
+
* six weeks ago, and a diagnostics channel has no memory.
|
|
92
|
+
*/
|
|
93
|
+
export interface CatalogAuditEvent {
|
|
94
|
+
id: string;
|
|
95
|
+
/** `snapshot.committed`, `type.curated`, … */
|
|
96
|
+
event: string;
|
|
97
|
+
typeName?: string;
|
|
98
|
+
principalId?: string;
|
|
99
|
+
snapshotId?: string;
|
|
100
|
+
/** The event payload, verbatim. */
|
|
101
|
+
detail: Record<string, unknown>;
|
|
102
|
+
occurredAt: string;
|
|
103
|
+
}
|
|
104
|
+
export interface AuditQuery {
|
|
105
|
+
event?: string;
|
|
106
|
+
typeName?: string;
|
|
107
|
+
principalId?: string;
|
|
108
|
+
since?: string;
|
|
109
|
+
limit?: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The same events, told as stories instead of as a list.
|
|
113
|
+
*
|
|
114
|
+
* A flat trail answers "what happened" and is useless for "what happened to
|
|
115
|
+
* *this load*" — the question anybody actually has when a number looks wrong.
|
|
116
|
+
* The spine of the answer is already in the data and needed no new plumbing:
|
|
117
|
+
* a connector run, the snapshot it writes, every batch, the commit and the
|
|
118
|
+
* finish all carry the same `snapshotId`, and when the durable engine schedules
|
|
119
|
+
* the run that id *is* the durable run id. So a trace is not something this
|
|
120
|
+
* library invents; it is the correlation that was always there, grouped.
|
|
121
|
+
*
|
|
122
|
+
* What is deliberately *not* here is a parent for events that carry no such id.
|
|
123
|
+
* A curation edit and a transform change are standalone acts, and the fact that
|
|
124
|
+
* one happened in the same second as a load is a coincidence, not causality —
|
|
125
|
+
* attaching them to the nearest run would fabricate a lineage that reads as
|
|
126
|
+
* evidence. They come back separately, as {@link CatalogTraceList.unlinked}.
|
|
127
|
+
*/
|
|
128
|
+
export declare const CATALOG_TRACE_OUTCOMES: readonly ["running", "succeeded", "failed", "incomplete"];
|
|
129
|
+
export type CatalogTraceOutcome = (typeof CATALOG_TRACE_OUTCOMES)[number];
|
|
130
|
+
/**
|
|
131
|
+
* Narrows an outcome that arrived as a string — a query parameter, a database
|
|
132
|
+
* column. Same reason as every other guard in this package: a second
|
|
133
|
+
* hand-maintained copy of these names is what drifts.
|
|
134
|
+
*/
|
|
135
|
+
export declare function isCatalogTraceOutcome(value: unknown): value is CatalogTraceOutcome;
|
|
136
|
+
/** One event, placed on the trace's clock. */
|
|
137
|
+
export interface CatalogTraceSpan {
|
|
138
|
+
/** The audit event id, so a span links back to the row in the flat trail. */
|
|
139
|
+
id: string;
|
|
140
|
+
event: string;
|
|
141
|
+
typeName?: string;
|
|
142
|
+
/**
|
|
143
|
+
* Whoever the recorder attributed the event to, passed through untouched.
|
|
144
|
+
*
|
|
145
|
+
* Deliberately not decomposed here. This string is already the place
|
|
146
|
+
* attribution lives everywhere else in the catalog, and a delegated caller
|
|
147
|
+
* encodes the person inside it, so a trace that re-models "who" would be a
|
|
148
|
+
* second answer to a question that already has one.
|
|
149
|
+
*/
|
|
150
|
+
principalId?: string;
|
|
151
|
+
/** The event payload, verbatim — the same bytes the flat trail shows. */
|
|
152
|
+
detail: Record<string, unknown>;
|
|
153
|
+
occurredAt: string;
|
|
154
|
+
/** Milliseconds from the first event of the trace. Where the bar starts. */
|
|
155
|
+
offsetMs: number;
|
|
156
|
+
/**
|
|
157
|
+
* Milliseconds until the next event in the trace; zero on the last one.
|
|
158
|
+
*
|
|
159
|
+
* This is the width of the bar, and it is the *work that followed the event*,
|
|
160
|
+
* not the event itself — events are instants, and an instant has no width to
|
|
161
|
+
* draw. The gap between `connector.run.started` and the first
|
|
162
|
+
* `snapshot.written` is the fetch and the transform, which is exactly the
|
|
163
|
+
* step that usually owns the wall clock.
|
|
164
|
+
*/
|
|
165
|
+
durationMs: number;
|
|
166
|
+
/** This event carries a failure. Set from the payload, never inferred. */
|
|
167
|
+
failed: boolean;
|
|
168
|
+
/** The message, when there is one. Kept per span so a retry shows both. */
|
|
169
|
+
error?: string;
|
|
170
|
+
}
|
|
171
|
+
/** One causal story: what started it, what happened in between, how it ended. */
|
|
172
|
+
export interface CatalogTrace {
|
|
173
|
+
/** The correlation id — the snapshot id, which is also the durable run id. */
|
|
174
|
+
id: string;
|
|
175
|
+
typeName?: string;
|
|
176
|
+
principalId?: string;
|
|
177
|
+
connectorId?: string;
|
|
178
|
+
connectorName?: string;
|
|
179
|
+
outcome: CatalogTraceOutcome;
|
|
180
|
+
startedAt: string;
|
|
181
|
+
/** The most recent event. Always set, including while still running. */
|
|
182
|
+
lastEventAt: string;
|
|
183
|
+
/**
|
|
184
|
+
* When it ended, and **only** when a terminal event says it did.
|
|
185
|
+
*
|
|
186
|
+
* Absent for `running` and `incomplete`, and absent rather than filled in
|
|
187
|
+
* with the last event's time, because "we stopped hearing from it" and "it
|
|
188
|
+
* finished" are different facts and this field is the one a caller will read
|
|
189
|
+
* as the second. A consumer that wants "how long has this been going" uses
|
|
190
|
+
* {@link lastEventAt} and knows what it is looking at.
|
|
191
|
+
*/
|
|
192
|
+
endedAt?: string;
|
|
193
|
+
/** Wall clock of the whole story. Absent whenever {@link endedAt} is. */
|
|
194
|
+
durationMs?: number;
|
|
195
|
+
eventCount: number;
|
|
196
|
+
/**
|
|
197
|
+
* How many spans carried a failure.
|
|
198
|
+
*
|
|
199
|
+
* Separate from {@link outcome} because a retry reuses the snapshot id, so a
|
|
200
|
+
* trace can genuinely be "succeeded, on the third attempt". Reporting that as
|
|
201
|
+
* a plain success hides the two failures; reporting it as a failure hides the
|
|
202
|
+
* data that did land.
|
|
203
|
+
*/
|
|
204
|
+
failureCount: number;
|
|
205
|
+
/** Rows the commit made visible. Absent when nothing was ever committed. */
|
|
206
|
+
rowsCommitted?: number;
|
|
207
|
+
/** The most recent failure message, hoisted so a UI need not dig for it. */
|
|
208
|
+
error?: string;
|
|
209
|
+
/**
|
|
210
|
+
* True when the whole story fits inside one tick of the recorder's clock.
|
|
211
|
+
*
|
|
212
|
+
* Worth saying out loud rather than quietly drawing zero-width bars: with a
|
|
213
|
+
* second-resolution timestamp column a fast load has no measurable internal
|
|
214
|
+
* timing at all, and a waterfall drawn from it would be a picture of rounding
|
|
215
|
+
* error. Ordering is still correct — see the lifecycle rank the store sorts
|
|
216
|
+
* by — but proportions are not, and a consumer should say so.
|
|
217
|
+
*/
|
|
218
|
+
coarse: boolean;
|
|
219
|
+
/** Ordered: what started it first, how it ended last. */
|
|
220
|
+
spans: CatalogTraceSpan[];
|
|
221
|
+
}
|
|
222
|
+
export interface TraceQuery {
|
|
223
|
+
typeName?: string;
|
|
224
|
+
principalId?: string;
|
|
225
|
+
/**
|
|
226
|
+
* Keeps traces that contain this event, and still returns all of their spans.
|
|
227
|
+
*
|
|
228
|
+
* Filtering the spans instead would produce a trace with holes in it that
|
|
229
|
+
* still looked whole, which is the one thing a causal view must never do.
|
|
230
|
+
*/
|
|
231
|
+
event?: string;
|
|
232
|
+
/**
|
|
233
|
+
* One outcome, or any of several.
|
|
234
|
+
*
|
|
235
|
+
* A list rather than only a single value because the question an operations
|
|
236
|
+
* screen actually asks is "what needs attention", and that is `failed` **and**
|
|
237
|
+
* `incomplete` — two outcomes that are deliberately not one (see
|
|
238
|
+
* {@link CATALOG_TRACE_OUTCOMES}) and are still always wanted together.
|
|
239
|
+
* Answering it with two queries and merging their pages client-side does not
|
|
240
|
+
* work: each page is the newest N of its own outcome, so the merged list is
|
|
241
|
+
* not the newest N of the union, and on a night with many failures the
|
|
242
|
+
* incomplete loads — the ones that lost data silently — fall off the bottom.
|
|
243
|
+
*
|
|
244
|
+
* An empty array is not "no filter", it is a filter that matches nothing, and
|
|
245
|
+
* a store must answer it with an empty page. Treating it as unfiltered would
|
|
246
|
+
* turn a caller's empty selection into every trace in the window, which on
|
|
247
|
+
* this screen reads as "all of these need attention".
|
|
248
|
+
*/
|
|
249
|
+
outcome?: CatalogTraceOutcome | CatalogTraceOutcome[];
|
|
250
|
+
since?: string;
|
|
251
|
+
limit?: number;
|
|
252
|
+
offset?: number;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Normalises {@link TraceQuery.outcome} to a list, or `undefined` for no filter.
|
|
256
|
+
*
|
|
257
|
+
* Exported so a store and a controller narrow it the same way. The distinction
|
|
258
|
+
* that has to survive is `undefined` (no filter) versus `[]` (a filter nothing
|
|
259
|
+
* satisfies), and a store hand-rolling this is one `?? []` away from collapsing
|
|
260
|
+
* the two.
|
|
261
|
+
*/
|
|
262
|
+
export declare function traceOutcomeFilter(outcome: TraceQuery['outcome']): CatalogTraceOutcome[] | undefined;
|
|
263
|
+
export interface CatalogTraceList {
|
|
264
|
+
traces: CatalogTrace[];
|
|
265
|
+
/** Matching traces before paging, so a UI can page honestly. */
|
|
266
|
+
total: number;
|
|
267
|
+
limit: number;
|
|
268
|
+
offset: number;
|
|
269
|
+
/**
|
|
270
|
+
* Events that carry no correlation id, and so belong to no trace.
|
|
271
|
+
*
|
|
272
|
+
* Returned beside the traces rather than hidden, because "these three things
|
|
273
|
+
* happened and are not part of any story" is itself the honest answer. See
|
|
274
|
+
* the note on {@link CATALOG_TRACE_OUTCOMES} for why they are not adopted.
|
|
275
|
+
*/
|
|
276
|
+
unlinked: CatalogAuditEvent[];
|
|
277
|
+
unlinkedTotal: number;
|
|
278
|
+
/**
|
|
279
|
+
* The finest interval the store's timestamps can distinguish, in
|
|
280
|
+
* milliseconds. Reported by the store rather than assumed by the caller: one
|
|
281
|
+
* store keeps microseconds and another keeps whole seconds, and a renderer
|
|
282
|
+
* that guesses wrong either throws away detail or draws noise.
|
|
283
|
+
*/
|
|
284
|
+
clockResolutionMs: number;
|
|
285
|
+
}
|
|
286
|
+
/** Totals over every trace a query matches, without fetching any of them. */
|
|
287
|
+
export interface CatalogTraceTotals {
|
|
288
|
+
/**
|
|
289
|
+
* Matching traces. The same number {@link CatalogTraceList.total} would carry
|
|
290
|
+
* for the same query — repeated here so the sum below has a denominator and a
|
|
291
|
+
* caller drawing both needs one round trip rather than two.
|
|
292
|
+
*/
|
|
293
|
+
traces: number;
|
|
294
|
+
/**
|
|
295
|
+
* Rows the matching traces' commits made visible to readers.
|
|
296
|
+
*
|
|
297
|
+
* Committed rows specifically. Rows written and never committed are invisible
|
|
298
|
+
* to every reader of the catalog, so counting them here would report a load
|
|
299
|
+
* that moved no data as though it had.
|
|
300
|
+
*
|
|
301
|
+
* Traces that committed nothing contribute no term, exactly as
|
|
302
|
+
* {@link CatalogTrace.rowsCommitted} is absent rather than zero for them. So
|
|
303
|
+
* this is a sum over the loads that committed, not an average anybody can
|
|
304
|
+
* divide by `traces`.
|
|
305
|
+
*/
|
|
306
|
+
rowsCommitted: number;
|
|
307
|
+
}
|
|
308
|
+
/** The events that belong to no trace, on their own. */
|
|
309
|
+
export interface CatalogUnlinkedList {
|
|
310
|
+
events: CatalogAuditEvent[];
|
|
311
|
+
/** Matching events before paging, so a UI can page honestly. */
|
|
312
|
+
total: number;
|
|
313
|
+
limit: number;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Reading the trail as stories.
|
|
317
|
+
*
|
|
318
|
+
* A separate interface and a separate token from {@link CatalogWorkspaceStore}
|
|
319
|
+
* on purpose. Grouping, ranking and paging tens of thousands of audit rows is a
|
|
320
|
+
* query the storage engine should run; a store that cannot express it should be
|
|
321
|
+
* able to say so by not providing this, and everything else still works.
|
|
322
|
+
*/
|
|
323
|
+
export interface CatalogTraceStore {
|
|
324
|
+
listTraces(query: TraceQuery): Promise<CatalogTraceList>;
|
|
325
|
+
getTrace(id: string): Promise<CatalogTrace | undefined>;
|
|
326
|
+
/**
|
|
327
|
+
* Aggregate the matching traces instead of listing them.
|
|
328
|
+
*
|
|
329
|
+
* Exists because the alternative is arithmetic over a page, and a page is a
|
|
330
|
+
* bound the caller had to choose. A dashboard summing `rowsCommitted` over the
|
|
331
|
+
* newest 500 successful loads is exact until the 501st, and then it is wrong
|
|
332
|
+
* in a direction it has to pick deliberately: undercounting makes a good night
|
|
333
|
+
* look quiet, overcounting makes a bad night look productive. That is a real
|
|
334
|
+
* choice to have to make and it should not have to be made — the engine can
|
|
335
|
+
* sum the column.
|
|
336
|
+
*
|
|
337
|
+
* Optional, and the fallback is the bounded page sum the callers already
|
|
338
|
+
* write, so a store that cannot express the aggregate costs a caller accuracy
|
|
339
|
+
* rather than the panel. A caller falling back should say which it is showing;
|
|
340
|
+
* a number that is sometimes exact and sometimes not, with nothing to tell
|
|
341
|
+
* them apart, is worse than either.
|
|
342
|
+
*/
|
|
343
|
+
traceTotals?(query: TraceQuery): Promise<CatalogTraceTotals>;
|
|
344
|
+
/**
|
|
345
|
+
* The unlinked events on their own — schema changes, curation edits,
|
|
346
|
+
* transform edits: everything carrying no snapshot id and so belonging to no
|
|
347
|
+
* load.
|
|
348
|
+
*
|
|
349
|
+
* The same set {@link CatalogTraceList.unlinked} carries. Separate because
|
|
350
|
+
* they are the answer to the second question of any incident ("what changed?")
|
|
351
|
+
* and a caller asking only that had to fetch, assemble and discard a page of
|
|
352
|
+
* traces to reach them — grouping and ranking every audit row of the window to
|
|
353
|
+
* throw the result away.
|
|
354
|
+
*
|
|
355
|
+
* The two must agree, so this honours {@link TraceQuery} the same way
|
|
356
|
+
* `listTraces` does, including the part that looks like a bug and is not: a
|
|
357
|
+
* query naming an `outcome` returns nothing here. An outcome is a property of
|
|
358
|
+
* a trace, and an event that is in no trace has none — returning these anyway
|
|
359
|
+
* under a `failed` filter would put unrelated rows on a screen whose whole
|
|
360
|
+
* point was to show failures.
|
|
361
|
+
*
|
|
362
|
+
* Optional; the fallback is to read `unlinked` off a `listTraces` page, which
|
|
363
|
+
* is correct and merely wasteful.
|
|
364
|
+
*/
|
|
365
|
+
listUnlinked?(query: TraceQuery): Promise<CatalogUnlinkedList>;
|
|
366
|
+
}
|
|
367
|
+
export declare const CATALOG_TRACE_STORE: unique symbol;
|
|
368
|
+
export declare function isTraceStore(store: unknown): store is CatalogTraceStore;
|
|
369
|
+
/**
|
|
370
|
+
* What an external frontend gets.
|
|
371
|
+
*
|
|
372
|
+
* Deliberately a rendered shape rather than raw SQL and columns: the point of
|
|
373
|
+
* the embed API is that a consumer builds its own UI without needing to know
|
|
374
|
+
* this catalog's query language, and handing back SQL would make every consumer
|
|
375
|
+
* a second implementation of the console.
|
|
376
|
+
*/
|
|
377
|
+
export interface EmbeddedChart {
|
|
378
|
+
id: string;
|
|
379
|
+
title: string;
|
|
380
|
+
description?: string;
|
|
381
|
+
visualization: QueryVisualization;
|
|
382
|
+
/** Position and width from the dashboard, as a hint the consumer may ignore. */
|
|
383
|
+
layout?: {
|
|
384
|
+
width: number;
|
|
385
|
+
position: number;
|
|
386
|
+
};
|
|
387
|
+
columns: string[];
|
|
388
|
+
rows: Array<Record<string, unknown>>;
|
|
389
|
+
rowCount: number;
|
|
390
|
+
/** True when this came from cache; tells a consumer how fresh it is. */
|
|
391
|
+
cached: boolean;
|
|
392
|
+
generatedAt: string;
|
|
393
|
+
}
|
|
394
|
+
export interface EmbeddedDashboard {
|
|
395
|
+
id: string;
|
|
396
|
+
name: string;
|
|
397
|
+
description?: string;
|
|
398
|
+
charts: EmbeddedChart[];
|
|
399
|
+
generatedAt: string;
|
|
400
|
+
}
|
|
401
|
+
export interface CatalogWorkspaceStore {
|
|
402
|
+
listSavedQueries(): Promise<SavedQuery[]>;
|
|
403
|
+
getSavedQuery(id: string): Promise<SavedQuery | undefined>;
|
|
404
|
+
saveQuery(input: SaveQueryInput, createdBy: string): Promise<SavedQuery>;
|
|
405
|
+
updateSavedQuery(id: string, input: Partial<SaveQueryInput>): Promise<SavedQuery | undefined>;
|
|
406
|
+
deleteSavedQuery(id: string): Promise<boolean>;
|
|
407
|
+
listDashboards(): Promise<Dashboard[]>;
|
|
408
|
+
getDashboard(id: string): Promise<Dashboard | undefined>;
|
|
409
|
+
saveDashboard(input: {
|
|
410
|
+
name: string;
|
|
411
|
+
description?: string;
|
|
412
|
+
cards?: DashboardCard[];
|
|
413
|
+
shared?: boolean;
|
|
414
|
+
}, createdBy: string): Promise<Dashboard>;
|
|
415
|
+
updateDashboard(id: string, input: Partial<{
|
|
416
|
+
name: string;
|
|
417
|
+
description: string;
|
|
418
|
+
cards: DashboardCard[];
|
|
419
|
+
shared: boolean;
|
|
420
|
+
}>): Promise<Dashboard | undefined>;
|
|
421
|
+
deleteDashboard(id: string): Promise<boolean>;
|
|
422
|
+
recordEvent(event: Omit<CatalogAuditEvent, 'id'>): Promise<void>;
|
|
423
|
+
listEvents(query: AuditQuery): Promise<CatalogAuditEvent[]>;
|
|
424
|
+
}
|
|
425
|
+
export declare function isWorkspaceStore(store: unknown): store is CatalogWorkspaceStore;
|
|
426
|
+
export declare const CATALOG_WORKSPACE_STORE: unique symbol;
|