@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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/dist/catalog.controller.d.ts +8 -0
  4. package/dist/catalog.controller.js +482 -0
  5. package/dist/catalog.decorators.d.ts +37 -0
  6. package/dist/catalog.decorators.js +50 -0
  7. package/dist/catalog.environment.d.ts +442 -0
  8. package/dist/catalog.environment.js +645 -0
  9. package/dist/catalog.events.d.ts +179 -0
  10. package/dist/catalog.events.js +110 -0
  11. package/dist/catalog.module.d.ts +5 -0
  12. package/dist/catalog.module.js +71 -0
  13. package/dist/catalog.options.d.ts +79 -0
  14. package/dist/catalog.options.js +4 -0
  15. package/dist/catalog.overlay-store.d.ts +25 -0
  16. package/dist/catalog.overlay-store.js +44 -0
  17. package/dist/catalog.overlay-store.token.d.ts +1 -0
  18. package/dist/catalog.overlay-store.token.js +4 -0
  19. package/dist/catalog.pipeline.d.ts +800 -0
  20. package/dist/catalog.pipeline.js +606 -0
  21. package/dist/catalog.principal.d.ts +209 -0
  22. package/dist/catalog.principal.js +245 -0
  23. package/dist/catalog.query-cache.d.ts +25 -0
  24. package/dist/catalog.query-cache.js +0 -0
  25. package/dist/catalog.query.d.ts +76 -0
  26. package/dist/catalog.query.js +64 -0
  27. package/dist/catalog.registry.base.d.ts +21 -0
  28. package/dist/catalog.registry.base.js +17 -0
  29. package/dist/catalog.registry.d.ts +44 -0
  30. package/dist/catalog.registry.js +359 -0
  31. package/dist/catalog.service.d.ts +115 -0
  32. package/dist/catalog.service.js +366 -0
  33. package/dist/catalog.store.d.ts +419 -0
  34. package/dist/catalog.store.js +175 -0
  35. package/dist/catalog.types.d.ts +165 -0
  36. package/dist/catalog.types.js +19 -0
  37. package/dist/catalog.workspace.d.ts +426 -0
  38. package/dist/catalog.workspace.js +87 -0
  39. package/dist/client.d.ts +86 -0
  40. package/dist/client.js +83 -0
  41. package/dist/index.d.ts +19 -0
  42. package/dist/index.js +109 -0
  43. package/dist/stores/mikro-orm-read.store.d.ts +20 -0
  44. package/dist/stores/mikro-orm-read.store.js +120 -0
  45. package/dist/transform-runner.d.ts +54 -0
  46. package/dist/transform-runner.js +280 -0
  47. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Davide Carvalho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # @dudousxd/nestjs-catalog
2
+
3
+ A metadata registry for NestJS. It reads your ORM and gives you back your data
4
+ model as data: object types, their properties, their relations — queryable over
5
+ HTTP, and labelled by humans rather than by whoever named the class.
6
+
7
+ ```bash
8
+ pnpm add @dudousxd/nestjs-catalog
9
+ ```
10
+
11
+ ## Why
12
+
13
+ Most applications describe their model three or four times: once in the entity,
14
+ once in a filter definition, once in a table component, once in whatever the
15
+ admin screen is. Each copy drifts.
16
+
17
+ This library takes the position that **structure should be derived and semantics
18
+ should be declared.** Column names, SQL types, nullability and foreign keys are
19
+ read off the ORM, because the database already knows them. What the database
20
+ cannot know — what a thing is called, what it means, what unit it is in, who may
21
+ see it — is declared once and served to everything downstream.
22
+
23
+ ## Mount it
24
+
25
+ ```ts
26
+ import { CatalogModule } from "@dudousxd/nestjs-catalog";
27
+
28
+ @Module({
29
+ imports: [
30
+ CatalogModule.forRoot({
31
+ path: "catalog",
32
+ // No guard ships by default. An endpoint that enumerates every table in
33
+ // your database should never be open because a library decided so.
34
+ imports: [AuthModule],
35
+ guards: [RolesGuard],
36
+ }),
37
+ ],
38
+ })
39
+ export class AppModule {}
40
+ ```
41
+
42
+ `imports` is not decoration: Nest builds a guard from the injector of the module
43
+ that *declares* the controller, and this library generates its own. Whatever
44
+ your guards inject has to be resolvable from there.
45
+
46
+ ## Declare what the database cannot know
47
+
48
+ ```ts
49
+ @CatalogType({
50
+ displayName: "Vehicle",
51
+ group: "Fleet",
52
+ icon: "🚚",
53
+ description: "One registered vehicle. Work orders and funding hang off it.",
54
+ })
55
+ @Entity()
56
+ export class Mvr {
57
+ @CatalogProperty({ displayName: "Risk Score", unit: "0–100" })
58
+ @Property({ fieldName: "Risk Score" })
59
+ riskScore?: number;
60
+ }
61
+ ```
62
+
63
+ Entities with no decorators still appear — with a name derived from the class,
64
+ which is exactly the point: the console shows you which types nobody has named
65
+ yet.
66
+
67
+ ## The HTTP surface
68
+
69
+ | Method | Path | What it does |
70
+ |---|---|---|
71
+ | `GET` | `/catalog` | The whole model, as data |
72
+ | `GET` | `/catalog/graph` | Nodes and edges, for drawing it |
73
+ | `GET` | `/catalog/types/:name` | One type |
74
+ | `PATCH` | `/catalog/types/:name` | Rename, regroup, re-describe |
75
+ | `PATCH` | `/catalog/types/:name/properties/:property` | Label, unit, description, visibility |
76
+ | `POST` | `/catalog/reset` | Drop every runtime edit |
77
+ | `GET` | `/catalog/objects/:name` | Read rows of any catalogued type |
78
+
79
+ Everything a `PATCH` can change is presentation-only. **No route in this library
80
+ issues DDL, and the catalog never writes to your tables.** Runtime edits go to an
81
+ overlay store (a JSON file by default; bring your own via `overlayStore`).
82
+
83
+ `GET /catalog/objects/:name` validates the type name, the sort column and the
84
+ searched columns against the catalog before anything reaches SQL, selects only
85
+ the columns the catalog says are visible, and caps the page size.
86
+
87
+ ## Build your own endpoints, or use ours
88
+
89
+ The built-in controller is a convenience, not the interface. Pass
90
+ `controller: false` and inject `CatalogService` — by class, there is no token to
91
+ import — to publish whatever HTTP surface your app already uses:
92
+
93
+ ```ts
94
+ @Controller("data-model")
95
+ export class MyController {
96
+ constructor(private readonly catalog: CatalogService) {}
97
+
98
+ @Get() model() { return this.catalog.getSnapshot(); }
99
+ @Get("graph") graph() { return this.catalog.getGraph(); }
100
+ @Get(":type") rows(@Param("type") t: string, @Query() q) {
101
+ return this.catalog.readObjects(t, q);
102
+ }
103
+ }
104
+ ```
105
+
106
+ Everything the built-in controller does is on that one service — the model, the
107
+ graph, presentation edits, reads, and the snapshot list. The registry and the
108
+ store stay injectable for anything more unusual, but an ordinary endpoint should
109
+ not need them.
110
+
111
+ ## Build your own UI, or use ours
112
+
113
+ The endpoints are the product. Import `@dudousxd/nestjs-catalog/client` for the
114
+ response types and route builders — no NestJS, no ORM, browser-safe — and write
115
+ whatever screens you want.
116
+
117
+ If you would rather not, [`@dudousxd/nestjs-catalog-react`](../nestjs-catalog-react)
118
+ ships a model manager and a generic object explorer that you drop into your own
119
+ app shell.
120
+
121
+ ## License
122
+
123
+ MIT
@@ -0,0 +1,8 @@
1
+ import { type Type } from '@nestjs/common';
2
+ /**
3
+ * Built as a factory rather than a plain class because the route prefix and the
4
+ * guards both come from `forRoot`. A library that hardcodes either one forces
5
+ * every host app to accept its idea of auth, which for an endpoint that
6
+ * enumerates every table in the database is not a reasonable default.
7
+ */
8
+ export declare function createCatalogController(path: string, guards: Type<unknown>[], decorators?: ClassDecorator[]): Type<unknown>;
@@ -0,0 +1,482 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.createCatalogController = createCatalogController;
16
+ const common_1 = require("@nestjs/common");
17
+ const catalog_query_cache_1 = require("./catalog.query-cache");
18
+ const catalog_registry_base_1 = require("./catalog.registry.base");
19
+ const catalog_service_1 = require("./catalog.service");
20
+ const catalog_workspace_1 = require("./catalog.workspace");
21
+ /**
22
+ * Built as a factory rather than a plain class because the route prefix and the
23
+ * guards both come from `forRoot`. A library that hardcodes either one forces
24
+ * every host app to accept its idea of auth, which for an endpoint that
25
+ * enumerates every table in the database is not a reasonable default.
26
+ */
27
+ function createCatalogController(path, guards, decorators = []) {
28
+ let CatalogController = class CatalogController {
29
+ registry;
30
+ service;
31
+ traces;
32
+ constructor(registry, service, traces) {
33
+ this.registry = registry;
34
+ this.service = service;
35
+ this.traces = traces;
36
+ }
37
+ requireTraces() {
38
+ if (!this.traces) {
39
+ throw new common_1.BadRequestException('This catalog has no trace store, so the audit trail can be listed but not grouped into traces.');
40
+ }
41
+ return this.traces;
42
+ }
43
+ /** The whole ontology, as data. */
44
+ snapshot() {
45
+ return this.registry.getSnapshot();
46
+ }
47
+ /** Nodes and edges, for drawing it. */
48
+ graph() {
49
+ return this.registry.getGraph();
50
+ }
51
+ type(name) {
52
+ const type = this.registry.getType(name);
53
+ if (!type)
54
+ throw new common_1.NotFoundException(`Unknown object type: ${name}`);
55
+ return type;
56
+ }
57
+ /**
58
+ * Tier 0. Renames a type, regroups it, changes its icon. No migration, no
59
+ * deploy, no engineer.
60
+ */
61
+ async patchType(name, body) {
62
+ const updated = await this.registry.patchType(name, body);
63
+ if (!updated)
64
+ throw new common_1.NotFoundException(`Unknown object type: ${name}`);
65
+ return updated;
66
+ }
67
+ /** Tier 0, one property at a time. */
68
+ async patchProperty(name, property, body) {
69
+ const updated = await this.registry.patchProperty(name, property, body);
70
+ if (!updated) {
71
+ throw new common_1.NotFoundException(`Unknown property: ${name}.${property}`);
72
+ }
73
+ return updated;
74
+ }
75
+ /** Drops every tier-0 edit and falls back to what the ORM says. */
76
+ async reset() {
77
+ await this.registry.resetOverlay();
78
+ return this.registry.getSnapshot();
79
+ }
80
+ /** One generic read endpoint for every type in the catalog. */
81
+ objects(name, page, size, search, sort, dir, snapshot) {
82
+ return this.service.readObjects(name, {
83
+ page: page ? Number(page) : undefined,
84
+ size: size ? Number(size) : undefined,
85
+ search,
86
+ sort,
87
+ dir: dir === 'desc' ? 'desc' : 'asc',
88
+ snapshot,
89
+ });
90
+ }
91
+ /** What an ad-hoc query may select from, and whether it can run at all. */
92
+ queryRelations() {
93
+ return this.service.queryRelations();
94
+ }
95
+ /** Run one read-only statement. */
96
+ runQuery(body) {
97
+ return this.service.runQuery(body ?? { sql: '' });
98
+ }
99
+ /** Everything a saved query or dashboard needs to know is here. */
100
+ workspaceCapabilities() {
101
+ return {
102
+ workspace: this.service.workspaceAvailable(),
103
+ ...this.service.capabilities(),
104
+ };
105
+ }
106
+ savedQueries() {
107
+ return this.service.listSavedQueries();
108
+ }
109
+ saveSavedQuery(body) {
110
+ return this.service.saveQuery(body, body?.createdBy ?? 'console');
111
+ }
112
+ savedQuery(id) {
113
+ return this.service.getSavedQuery(id);
114
+ }
115
+ patchSavedQuery(id, body) {
116
+ return this.service.updateSavedQuery(id, body);
117
+ }
118
+ removeSavedQuery(id) {
119
+ return this.service.deleteSavedQuery(id).then((deleted) => ({ deleted }));
120
+ }
121
+ /** Run a saved query, honouring the cache TTL it was saved with. */
122
+ runSavedQuery(id, body) {
123
+ return this.service.runSavedQuery(id, body?.maxRows);
124
+ }
125
+ /**
126
+ * The same result as CSV.
127
+ *
128
+ * A GET, not a POST, so it can be a plain link — a download that only works
129
+ * from JavaScript cannot be pasted into a mail or a scheduled job.
130
+ */
131
+ async exportSavedQuery(id, response) {
132
+ const { savedQuery, result } = await this.service.runSavedQuery(id);
133
+ const filename = `${savedQuery.name.replace(/[^A-Za-z0-9_-]+/g, '-')}.csv`;
134
+ response.setHeader('content-type', 'text/csv; charset=utf-8');
135
+ response.setHeader('content-disposition', `attachment; filename="${filename}"`);
136
+ return (0, catalog_query_cache_1.toCsv)(result);
137
+ }
138
+ dashboards() {
139
+ return this.service.listDashboards();
140
+ }
141
+ createDashboard(body) {
142
+ return this.service.saveDashboard(body, body?.createdBy ?? 'console');
143
+ }
144
+ dashboard(id) {
145
+ return this.service.getDashboard(id);
146
+ }
147
+ patchDashboard(id, body) {
148
+ return this.service.updateDashboard(id, body);
149
+ }
150
+ removeDashboard(id) {
151
+ return this.service.deleteDashboard(id).then((deleted) => ({ deleted }));
152
+ }
153
+ /**
154
+ * What this caller may embed.
155
+ *
156
+ * A discovery endpoint so a consuming frontend can list what it is allowed
157
+ * to render rather than being told the ids out of band.
158
+ */
159
+ embeddable() {
160
+ return this.service.listEmbeddable();
161
+ }
162
+ /** A whole dashboard, every chart resolved and ready to draw. */
163
+ embedDashboard(id) {
164
+ return this.service.embedDashboard(id);
165
+ }
166
+ /** One chart, for a consumer that wants to place it itself. */
167
+ embedChart(id) {
168
+ return this.service.embedChart(id);
169
+ }
170
+ /** The audit trail: what happened, to what, by whom. */
171
+ events(event, typeName, principalId, since, limit) {
172
+ return this.service.listEvents({
173
+ event,
174
+ typeName,
175
+ principalId,
176
+ since,
177
+ limit: limit ? Number(limit) : undefined,
178
+ });
179
+ }
180
+ /**
181
+ * The same trail, grouped into causal stories.
182
+ *
183
+ * Declared after `events` and before anything with a parameter in that
184
+ * position, so `traces` is matched as the literal it is. Nest resolves
185
+ * routes in declaration order, and a `:something` registered first would
186
+ * swallow this path and answer it with a lookup for an event named
187
+ * "traces" — a 404 nobody could explain.
188
+ *
189
+ * `event` and `outcome` pick *which traces* come back, never which spans:
190
+ * a trace returned with only its matching events would be a story with
191
+ * pages missing that still looked complete.
192
+ */
193
+ traceList(typeName, principalId, event, outcome, since, limit, offset) {
194
+ return this.requireTraces().listTraces({
195
+ typeName,
196
+ principalId,
197
+ event,
198
+ outcome: parseOutcomes(outcome),
199
+ since,
200
+ limit: limit ? Number(limit) : undefined,
201
+ offset: offset ? Number(offset) : undefined,
202
+ });
203
+ }
204
+ /** One story in full, by correlation id. */
205
+ async trace(id) {
206
+ const found = await this.requireTraces().getTrace(id);
207
+ if (!found) {
208
+ throw new common_1.NotFoundException(`No trace ${id} in the audit trail.`);
209
+ }
210
+ return found;
211
+ }
212
+ /** Every load of this type: when, by whom, how many rows. */
213
+ snapshots(name) {
214
+ return this.service.listSnapshots(name);
215
+ }
216
+ };
217
+ __decorate([
218
+ (0, common_1.Get)(),
219
+ __metadata("design:type", Function),
220
+ __metadata("design:paramtypes", []),
221
+ __metadata("design:returntype", void 0)
222
+ ], CatalogController.prototype, "snapshot", null);
223
+ __decorate([
224
+ (0, common_1.Get)('graph'),
225
+ __metadata("design:type", Function),
226
+ __metadata("design:paramtypes", []),
227
+ __metadata("design:returntype", void 0)
228
+ ], CatalogController.prototype, "graph", null);
229
+ __decorate([
230
+ (0, common_1.Get)('types/:name'),
231
+ __param(0, (0, common_1.Param)('name')),
232
+ __metadata("design:type", Function),
233
+ __metadata("design:paramtypes", [String]),
234
+ __metadata("design:returntype", void 0)
235
+ ], CatalogController.prototype, "type", null);
236
+ __decorate([
237
+ (0, common_1.Patch)('types/:name'),
238
+ __param(0, (0, common_1.Param)('name')),
239
+ __param(1, (0, common_1.Body)()),
240
+ __metadata("design:type", Function),
241
+ __metadata("design:paramtypes", [String, Object]),
242
+ __metadata("design:returntype", Promise)
243
+ ], CatalogController.prototype, "patchType", null);
244
+ __decorate([
245
+ (0, common_1.Patch)('types/:name/properties/:property'),
246
+ __param(0, (0, common_1.Param)('name')),
247
+ __param(1, (0, common_1.Param)('property')),
248
+ __param(2, (0, common_1.Body)()),
249
+ __metadata("design:type", Function),
250
+ __metadata("design:paramtypes", [String, String, Object]),
251
+ __metadata("design:returntype", Promise)
252
+ ], CatalogController.prototype, "patchProperty", null);
253
+ __decorate([
254
+ (0, common_1.Post)('reset'),
255
+ __metadata("design:type", Function),
256
+ __metadata("design:paramtypes", []),
257
+ __metadata("design:returntype", Promise)
258
+ ], CatalogController.prototype, "reset", null);
259
+ __decorate([
260
+ (0, common_1.Get)('objects/:name'),
261
+ __param(0, (0, common_1.Param)('name')),
262
+ __param(1, (0, common_1.Query)('page')),
263
+ __param(2, (0, common_1.Query)('size')),
264
+ __param(3, (0, common_1.Query)('search')),
265
+ __param(4, (0, common_1.Query)('sort')),
266
+ __param(5, (0, common_1.Query)('dir')),
267
+ __param(6, (0, common_1.Query)('snapshot')),
268
+ __metadata("design:type", Function),
269
+ __metadata("design:paramtypes", [String, String, String, String, String, String, String]),
270
+ __metadata("design:returntype", void 0)
271
+ ], CatalogController.prototype, "objects", null);
272
+ __decorate([
273
+ (0, common_1.Get)('query/relations'),
274
+ __metadata("design:type", Function),
275
+ __metadata("design:paramtypes", []),
276
+ __metadata("design:returntype", void 0)
277
+ ], CatalogController.prototype, "queryRelations", null);
278
+ __decorate([
279
+ (0, common_1.Post)('query'),
280
+ __param(0, (0, common_1.Body)()),
281
+ __metadata("design:type", Function),
282
+ __metadata("design:paramtypes", [Object]),
283
+ __metadata("design:returntype", void 0)
284
+ ], CatalogController.prototype, "runQuery", null);
285
+ __decorate([
286
+ (0, common_1.Get)('workspace/capabilities'),
287
+ __metadata("design:type", Function),
288
+ __metadata("design:paramtypes", []),
289
+ __metadata("design:returntype", void 0)
290
+ ], CatalogController.prototype, "workspaceCapabilities", null);
291
+ __decorate([
292
+ (0, common_1.Get)('saved-queries'),
293
+ __metadata("design:type", Function),
294
+ __metadata("design:paramtypes", []),
295
+ __metadata("design:returntype", void 0)
296
+ ], CatalogController.prototype, "savedQueries", null);
297
+ __decorate([
298
+ (0, common_1.Post)('saved-queries'),
299
+ __param(0, (0, common_1.Body)()),
300
+ __metadata("design:type", Function),
301
+ __metadata("design:paramtypes", [Object]),
302
+ __metadata("design:returntype", void 0)
303
+ ], CatalogController.prototype, "saveSavedQuery", null);
304
+ __decorate([
305
+ (0, common_1.Get)('saved-queries/:id'),
306
+ __param(0, (0, common_1.Param)('id')),
307
+ __metadata("design:type", Function),
308
+ __metadata("design:paramtypes", [String]),
309
+ __metadata("design:returntype", void 0)
310
+ ], CatalogController.prototype, "savedQuery", null);
311
+ __decorate([
312
+ (0, common_1.Patch)('saved-queries/:id'),
313
+ __param(0, (0, common_1.Param)('id')),
314
+ __param(1, (0, common_1.Body)()),
315
+ __metadata("design:type", Function),
316
+ __metadata("design:paramtypes", [String, Object]),
317
+ __metadata("design:returntype", void 0)
318
+ ], CatalogController.prototype, "patchSavedQuery", null);
319
+ __decorate([
320
+ (0, common_1.Delete)('saved-queries/:id'),
321
+ __param(0, (0, common_1.Param)('id')),
322
+ __metadata("design:type", Function),
323
+ __metadata("design:paramtypes", [String]),
324
+ __metadata("design:returntype", void 0)
325
+ ], CatalogController.prototype, "removeSavedQuery", null);
326
+ __decorate([
327
+ (0, common_1.Post)('saved-queries/:id/run'),
328
+ __param(0, (0, common_1.Param)('id')),
329
+ __param(1, (0, common_1.Body)()),
330
+ __metadata("design:type", Function),
331
+ __metadata("design:paramtypes", [String, Object]),
332
+ __metadata("design:returntype", void 0)
333
+ ], CatalogController.prototype, "runSavedQuery", null);
334
+ __decorate([
335
+ (0, common_1.Get)('saved-queries/:id/export.csv'),
336
+ __param(0, (0, common_1.Param)('id')),
337
+ __param(1, (0, common_1.Res)({ passthrough: true })),
338
+ __metadata("design:type", Function),
339
+ __metadata("design:paramtypes", [String, Object]),
340
+ __metadata("design:returntype", Promise)
341
+ ], CatalogController.prototype, "exportSavedQuery", null);
342
+ __decorate([
343
+ (0, common_1.Get)('dashboards'),
344
+ __metadata("design:type", Function),
345
+ __metadata("design:paramtypes", []),
346
+ __metadata("design:returntype", void 0)
347
+ ], CatalogController.prototype, "dashboards", null);
348
+ __decorate([
349
+ (0, common_1.Post)('dashboards'),
350
+ __param(0, (0, common_1.Body)()),
351
+ __metadata("design:type", Function),
352
+ __metadata("design:paramtypes", [Object]),
353
+ __metadata("design:returntype", void 0)
354
+ ], CatalogController.prototype, "createDashboard", null);
355
+ __decorate([
356
+ (0, common_1.Get)('dashboards/:id'),
357
+ __param(0, (0, common_1.Param)('id')),
358
+ __metadata("design:type", Function),
359
+ __metadata("design:paramtypes", [String]),
360
+ __metadata("design:returntype", void 0)
361
+ ], CatalogController.prototype, "dashboard", null);
362
+ __decorate([
363
+ (0, common_1.Patch)('dashboards/:id'),
364
+ __param(0, (0, common_1.Param)('id')),
365
+ __param(1, (0, common_1.Body)()),
366
+ __metadata("design:type", Function),
367
+ __metadata("design:paramtypes", [String, Object]),
368
+ __metadata("design:returntype", void 0)
369
+ ], CatalogController.prototype, "patchDashboard", null);
370
+ __decorate([
371
+ (0, common_1.Delete)('dashboards/:id'),
372
+ __param(0, (0, common_1.Param)('id')),
373
+ __metadata("design:type", Function),
374
+ __metadata("design:paramtypes", [String]),
375
+ __metadata("design:returntype", void 0)
376
+ ], CatalogController.prototype, "removeDashboard", null);
377
+ __decorate([
378
+ (0, common_1.Get)('embed'),
379
+ __metadata("design:type", Function),
380
+ __metadata("design:paramtypes", []),
381
+ __metadata("design:returntype", void 0)
382
+ ], CatalogController.prototype, "embeddable", null);
383
+ __decorate([
384
+ (0, common_1.Get)('embed/dashboards/:id'),
385
+ __param(0, (0, common_1.Param)('id')),
386
+ __metadata("design:type", Function),
387
+ __metadata("design:paramtypes", [String]),
388
+ __metadata("design:returntype", void 0)
389
+ ], CatalogController.prototype, "embedDashboard", null);
390
+ __decorate([
391
+ (0, common_1.Get)('embed/charts/:id'),
392
+ __param(0, (0, common_1.Param)('id')),
393
+ __metadata("design:type", Function),
394
+ __metadata("design:paramtypes", [String]),
395
+ __metadata("design:returntype", void 0)
396
+ ], CatalogController.prototype, "embedChart", null);
397
+ __decorate([
398
+ (0, common_1.Get)('events'),
399
+ __param(0, (0, common_1.Query)('event')),
400
+ __param(1, (0, common_1.Query)('type')),
401
+ __param(2, (0, common_1.Query)('principal')),
402
+ __param(3, (0, common_1.Query)('since')),
403
+ __param(4, (0, common_1.Query)('limit')),
404
+ __metadata("design:type", Function),
405
+ __metadata("design:paramtypes", [String, String, String, String, String]),
406
+ __metadata("design:returntype", void 0)
407
+ ], CatalogController.prototype, "events", null);
408
+ __decorate([
409
+ (0, common_1.Get)('events/traces'),
410
+ __param(0, (0, common_1.Query)('type')),
411
+ __param(1, (0, common_1.Query)('principal')),
412
+ __param(2, (0, common_1.Query)('event')),
413
+ __param(3, (0, common_1.Query)('outcome')),
414
+ __param(4, (0, common_1.Query)('since')),
415
+ __param(5, (0, common_1.Query)('limit')),
416
+ __param(6, (0, common_1.Query)('offset')),
417
+ __metadata("design:type", Function),
418
+ __metadata("design:paramtypes", [String, String, String, Object, String, String, String]),
419
+ __metadata("design:returntype", void 0)
420
+ ], CatalogController.prototype, "traceList", null);
421
+ __decorate([
422
+ (0, common_1.Get)('events/traces/:id'),
423
+ __param(0, (0, common_1.Param)('id')),
424
+ __metadata("design:type", Function),
425
+ __metadata("design:paramtypes", [String]),
426
+ __metadata("design:returntype", Promise)
427
+ ], CatalogController.prototype, "trace", null);
428
+ __decorate([
429
+ (0, common_1.Get)('objects/:name/snapshots'),
430
+ __param(0, (0, common_1.Param)('name')),
431
+ __metadata("design:type", Function),
432
+ __metadata("design:paramtypes", [String]),
433
+ __metadata("design:returntype", void 0)
434
+ ], CatalogController.prototype, "snapshots", null);
435
+ CatalogController = __decorate([
436
+ (0, common_1.Controller)(path),
437
+ __param(2, (0, common_1.Optional)()),
438
+ __param(2, (0, common_1.Inject)(catalog_workspace_1.CATALOG_TRACE_STORE)),
439
+ __metadata("design:paramtypes", [catalog_registry_base_1.CatalogRegistry,
440
+ catalog_service_1.CatalogService, Object])
441
+ ], CatalogController);
442
+ if (guards.length > 0) {
443
+ (0, common_1.UseGuards)(...guards)(CatalogController);
444
+ }
445
+ for (const decorate of decorators) {
446
+ decorate(CatalogController);
447
+ }
448
+ return CatalogController;
449
+ }
450
+ /**
451
+ * `?outcome=failed`, `?outcome=failed,incomplete`, `?outcome=a&outcome=b`.
452
+ *
453
+ * Two behaviours here are load-bearing and neither is obvious.
454
+ *
455
+ * An unrecognised outcome is dropped rather than passed down as a filter that
456
+ * matches nothing, and if nothing recognisable is left the filter goes away
457
+ * entirely. Returning an empty list for a typo would read as "no traces
458
+ * failed", which is the most expensive wrong answer this endpoint could give.
459
+ *
460
+ * A single outcome comes back as a bare string rather than a one-element array.
461
+ * `TraceQuery.outcome` accepts both, but a trace store written against the
462
+ * earlier contract expects only the string and would compare its column to an
463
+ * array — matching nothing, and reporting it as a clean empty page. So the
464
+ * array form is only ever produced when the caller genuinely asked for more
465
+ * than one, which is also the only case where an older store could not have
466
+ * answered anyway.
467
+ */
468
+ function parseOutcomes(raw) {
469
+ if (raw === undefined)
470
+ return undefined;
471
+ const candidates = (Array.isArray(raw) ? raw : [raw]).flatMap((value) => String(value).split(','));
472
+ const outcomes = [];
473
+ for (const candidate of candidates) {
474
+ const trimmed = candidate.trim();
475
+ if ((0, catalog_workspace_1.isCatalogTraceOutcome)(trimmed) && !outcomes.includes(trimmed)) {
476
+ outcomes.push(trimmed);
477
+ }
478
+ }
479
+ if (outcomes.length === 0)
480
+ return undefined;
481
+ return outcomes.length === 1 ? outcomes[0] : outcomes;
482
+ }
@@ -0,0 +1,37 @@
1
+ import 'reflect-metadata';
2
+ export declare const CATALOG_TYPE_META: unique symbol;
3
+ export declare const CATALOG_PROPERTY_META: unique symbol;
4
+ export interface CatalogTypeOptions {
5
+ displayName?: string;
6
+ pluralDisplayName?: string;
7
+ description?: string;
8
+ icon?: string;
9
+ group?: string;
10
+ titleProperty?: string;
11
+ }
12
+ export interface CatalogPropertyOptions {
13
+ displayName?: string;
14
+ description?: string;
15
+ hidden?: boolean;
16
+ order?: number;
17
+ classification?: string;
18
+ unit?: string;
19
+ }
20
+ /**
21
+ * Declares the semantics the database cannot know: what this entity is called
22
+ * in the business, which section it belongs to, and what to show when it
23
+ * appears as a link somewhere else.
24
+ *
25
+ * Structure is never declared here — it is read off the ORM. An entity with no
26
+ * `@CatalogType` still appears in the catalog, just with a name derived from
27
+ * its class and no group.
28
+ */
29
+ export declare function CatalogType(options?: CatalogTypeOptions): ClassDecorator;
30
+ /**
31
+ * Enriches one property. Everything it sets is tier 0 — the overlay can
32
+ * override any of it at runtime without a migration, which is precisely why
33
+ * these live in metadata rather than in the column definition.
34
+ */
35
+ export declare function CatalogProperty(options?: CatalogPropertyOptions): PropertyDecorator;
36
+ export declare function readTypeOptions(target: unknown): CatalogTypeOptions;
37
+ export declare function readPropertyOptions(target: unknown): Record<string, CatalogPropertyOptions>;