@dudousxd/nestjs-catalog 0.5.0 → 0.6.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/README.md +163 -0
- package/dist/access.controller.js +25 -0
- package/dist/catalog.controller.d.ts +46 -0
- package/dist/catalog.controller.js +199 -23
- package/dist/catalog.environment.d.ts +65 -12
- package/dist/catalog.environment.js +14 -1
- package/dist/catalog.events.d.ts +67 -3
- package/dist/catalog.events.js +15 -2
- package/dist/catalog.principal.d.ts +49 -1
- package/dist/catalog.principal.js +92 -0
- package/dist/catalog.service.d.ts +53 -10
- package/dist/catalog.service.js +180 -15
- package/dist/catalog.types.d.ts +7 -1
- package/dist/catalog.workspace.d.ts +68 -8
- package/dist/catalog.workspace.js +23 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -2
- package/package.json +1 -1
package/dist/catalog.service.js
CHANGED
|
@@ -14,6 +14,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.CatalogService = void 0;
|
|
16
16
|
const common_1 = require("@nestjs/common");
|
|
17
|
+
const catalog_events_1 = require("./catalog.events");
|
|
17
18
|
const catalog_options_1 = require("./catalog.options");
|
|
18
19
|
const catalog_query_1 = require("./catalog.query");
|
|
19
20
|
const catalog_query_cache_1 = require("./catalog.query-cache");
|
|
@@ -79,6 +80,17 @@ let CatalogService = class CatalogService {
|
|
|
79
80
|
visibleColumns(type) {
|
|
80
81
|
return type.properties.filter((p) => !p.hidden && p.type !== 'json');
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Rows of one type, paged.
|
|
85
|
+
*
|
|
86
|
+
* **No principal, and so no access control.** This applies the guardrails that
|
|
87
|
+
* hold for every caller — the type exists, the page is bounded, the sort names
|
|
88
|
+
* a real column — and none that depend on who is asking: a classified column
|
|
89
|
+
* comes back to whoever the host's guard let through the door. That is the
|
|
90
|
+
* library's declare-and-enforce split, written out at length above `mayWrite`
|
|
91
|
+
* in `catalog.principal.ts`. A host that wants per-principal reads passes this
|
|
92
|
+
* page through `readableObjectPage`.
|
|
93
|
+
*/
|
|
82
94
|
async readObjects(typeName, query) {
|
|
83
95
|
const type = this.registry.getType(typeName);
|
|
84
96
|
if (!type)
|
|
@@ -213,23 +225,120 @@ let CatalogService = class CatalogService {
|
|
|
213
225
|
throw new common_1.NotFoundException(`No saved query ${id}`);
|
|
214
226
|
return found;
|
|
215
227
|
}
|
|
216
|
-
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Sharing is audited, and the six methods below are where.
|
|
230
|
+
//
|
|
231
|
+
// `shared` on a saved query or a dashboard is the entire embed boundary: it is
|
|
232
|
+
// the one field that hands another company's frontend rows out of this
|
|
233
|
+
// catalog, and it is set by a person clicking a toggle. So it is emitted the
|
|
234
|
+
// way every other governance decision here is — `type.curated`,
|
|
235
|
+
// `transform.changed` — rather than being the one that is not.
|
|
236
|
+
//
|
|
237
|
+
// Two rules hold across all six, and both are load-bearing:
|
|
238
|
+
//
|
|
239
|
+
// *On the transition, never on the write.* A save that leaves the flag where
|
|
240
|
+
// it was is not a sharing decision. A trail that recorded one per keystroke
|
|
241
|
+
// would be a trail people learn to scroll past, which costs more than the
|
|
242
|
+
// entries are worth. Un-sharing is emitted too, under the same event name with
|
|
243
|
+
// `shared: false` — a trail that records only grants cannot answer "was this
|
|
244
|
+
// still shared last Tuesday".
|
|
245
|
+
//
|
|
246
|
+
// *Against what the store returned, never against what the caller asked for.*
|
|
247
|
+
// A store that ignores `shared` must not produce an entry claiming access was
|
|
248
|
+
// granted when nothing was.
|
|
249
|
+
//
|
|
250
|
+
// **Deleting is a transition.** For a while the first rule was applied only to
|
|
251
|
+
// the writes, so revoking access with the delete button — which is how it
|
|
252
|
+
// actually gets revoked — left nothing at all, and the only way to date the
|
|
253
|
+
// revocation was to notice that a thing had stopped appearing. Deleting
|
|
254
|
+
// something shared now emits `shared: false` with `deleted: true`, under the
|
|
255
|
+
// same event name, so the one filter anybody runs answers the whole question.
|
|
256
|
+
//
|
|
257
|
+
// Deleting something *un*shared emits nothing, and that is the first rule
|
|
258
|
+
// rather than an exception to it: an unshared query was not reachable from
|
|
259
|
+
// outside before and is not reachable after, so no access changed. Recording
|
|
260
|
+
// it would put entries carrying no grant and no revocation on the one channel
|
|
261
|
+
// whose entries all carry one. A host that wants every deletion in the trail
|
|
262
|
+
// wants a workspace-lifecycle event, which is a different event and not this
|
|
263
|
+
// one.
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
/**
|
|
266
|
+
* @param createdBy who saved it — the row's author and the audit entry's
|
|
267
|
+
* actor. The host's resolved principal id where the host resolves one; see
|
|
268
|
+
* the enforcement note in `catalog.principal.ts` for why this library cannot
|
|
269
|
+
* work it out itself.
|
|
270
|
+
*/
|
|
271
|
+
async saveQuery(input, createdBy) {
|
|
217
272
|
if (!input?.name?.trim()) {
|
|
218
273
|
throw new common_1.BadRequestException('A saved query needs a name.');
|
|
219
274
|
}
|
|
220
275
|
(0, catalog_query_1.assertReadOnlyShape)(input.sql ?? '');
|
|
221
|
-
|
|
276
|
+
const saved = await this.requireWorkspace().saveQuery(input, createdBy);
|
|
277
|
+
// Born shared is a grant with nothing to transition from: an outside
|
|
278
|
+
// application can fetch it the moment this returns. Born unshared is not an
|
|
279
|
+
// event at all.
|
|
280
|
+
if (saved.shared) {
|
|
281
|
+
(0, catalog_events_1.emitCatalog)('query.shared', {
|
|
282
|
+
savedQueryId: saved.id,
|
|
283
|
+
name: saved.name,
|
|
284
|
+
shared: true,
|
|
285
|
+
principalId: createdBy,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return saved;
|
|
222
289
|
}
|
|
223
|
-
|
|
290
|
+
/** @param changedBy who made the change, for the audit trail. */
|
|
291
|
+
async updateSavedQuery(id, input, changedBy) {
|
|
224
292
|
if (input.sql !== undefined)
|
|
225
293
|
(0, catalog_query_1.assertReadOnlyShape)(input.sql);
|
|
294
|
+
// Read the old value only when the flag is in play. A transition needs both
|
|
295
|
+
// ends, and every other edit — a rename, a new chart type — should not pay
|
|
296
|
+
// for a round trip it does not need.
|
|
297
|
+
const before = input.shared === undefined ? undefined : await this.requireWorkspace().getSavedQuery(id);
|
|
226
298
|
const updated = await this.requireWorkspace().updateSavedQuery(id, input);
|
|
227
299
|
if (!updated)
|
|
228
300
|
throw new common_1.NotFoundException(`No saved query ${id}`);
|
|
301
|
+
// `before` missing while the update succeeded means a store that disagrees
|
|
302
|
+
// with itself about whether this query exists. The transition is then
|
|
303
|
+
// unknowable, and an audit trail should over-record a grant rather than
|
|
304
|
+
// miss one, so it is emitted.
|
|
305
|
+
if (input.shared !== undefined && before?.shared !== updated.shared) {
|
|
306
|
+
(0, catalog_events_1.emitCatalog)('query.shared', {
|
|
307
|
+
savedQueryId: updated.id,
|
|
308
|
+
name: updated.name,
|
|
309
|
+
shared: updated.shared,
|
|
310
|
+
principalId: changedBy,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
229
313
|
return updated;
|
|
230
314
|
}
|
|
231
|
-
|
|
232
|
-
|
|
315
|
+
/**
|
|
316
|
+
* @param deletedBy who deleted it, for the audit trail. Required rather than
|
|
317
|
+
* defaulted, matching `saveQuery` and `updateSavedQuery`: a default would
|
|
318
|
+
* quietly attribute revocations to nobody in every caller that was not
|
|
319
|
+
* updated, and the trail's whole value here is that it names somebody.
|
|
320
|
+
*/
|
|
321
|
+
async deleteSavedQuery(id, deletedBy) {
|
|
322
|
+
// Read unconditionally, unlike `updateSavedQuery` which reads only when the
|
|
323
|
+
// flag is in play. A delete carries no statement of intent about `shared`,
|
|
324
|
+
// so there is nothing to branch on — whether this revokes access is a
|
|
325
|
+
// property of the row, and the row is about to stop existing.
|
|
326
|
+
const before = await this.requireWorkspace().getSavedQuery(id);
|
|
327
|
+
const deleted = await this.requireWorkspace().deleteSavedQuery(id);
|
|
328
|
+
// Only when the store says it went, and only when it was reachable from
|
|
329
|
+
// outside beforehand. A delete that removed nothing revoked nothing, and an
|
|
330
|
+
// unshared query's deletion is not an access event.
|
|
331
|
+
if (deleted && before?.shared) {
|
|
332
|
+
(0, catalog_events_1.emitCatalog)('query.shared', {
|
|
333
|
+
savedQueryId: before.id,
|
|
334
|
+
// The name as it last read. Nothing can look it up after this.
|
|
335
|
+
name: before.name,
|
|
336
|
+
shared: false,
|
|
337
|
+
principalId: deletedBy,
|
|
338
|
+
deleted: true,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return deleted;
|
|
233
342
|
}
|
|
234
343
|
/** Runs a saved query, honouring the TTL it was saved with. */
|
|
235
344
|
async runSavedQuery(id, maxRows) {
|
|
@@ -250,20 +359,61 @@ let CatalogService = class CatalogService {
|
|
|
250
359
|
throw new common_1.NotFoundException(`No dashboard ${id}`);
|
|
251
360
|
return found;
|
|
252
361
|
}
|
|
253
|
-
|
|
362
|
+
/**
|
|
363
|
+
* `shared` is declared here, and that is not cosmetic.
|
|
364
|
+
*
|
|
365
|
+
* The store has always accepted it, so it worked as long as the body reached
|
|
366
|
+
* the store untouched. A host with a whitelisting `ValidationPipe` — the
|
|
367
|
+
* normal, recommended configuration — strips a property no type declares, and
|
|
368
|
+
* the symptom is a dashboard that cannot be shared with no error anywhere:
|
|
369
|
+
* the toggle saves, the response says `shared: false`, and the embed API
|
|
370
|
+
* keeps answering 403 for a board somebody just shared.
|
|
371
|
+
*/
|
|
372
|
+
async saveDashboard(input, createdBy) {
|
|
254
373
|
if (!input?.name?.trim()) {
|
|
255
374
|
throw new common_1.BadRequestException('A dashboard needs a name.');
|
|
256
375
|
}
|
|
257
|
-
|
|
376
|
+
const saved = await this.requireWorkspace().saveDashboard(input, createdBy);
|
|
377
|
+
if (saved.shared) {
|
|
378
|
+
(0, catalog_events_1.emitCatalog)('dashboard.shared', {
|
|
379
|
+
dashboardId: saved.id,
|
|
380
|
+
name: saved.name,
|
|
381
|
+
shared: true,
|
|
382
|
+
principalId: createdBy,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return saved;
|
|
258
386
|
}
|
|
259
|
-
|
|
387
|
+
/** @param changedBy who made the change, for the audit trail. */
|
|
388
|
+
async updateDashboard(id, input, changedBy) {
|
|
389
|
+
const before = input.shared === undefined ? undefined : await this.requireWorkspace().getDashboard(id);
|
|
260
390
|
const updated = await this.requireWorkspace().updateDashboard(id, input);
|
|
261
391
|
if (!updated)
|
|
262
392
|
throw new common_1.NotFoundException(`No dashboard ${id}`);
|
|
393
|
+
if (input.shared !== undefined && before?.shared !== updated.shared) {
|
|
394
|
+
(0, catalog_events_1.emitCatalog)('dashboard.shared', {
|
|
395
|
+
dashboardId: updated.id,
|
|
396
|
+
name: updated.name,
|
|
397
|
+
shared: updated.shared,
|
|
398
|
+
principalId: changedBy,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
263
401
|
return updated;
|
|
264
402
|
}
|
|
265
|
-
|
|
266
|
-
|
|
403
|
+
/** @param deletedBy who deleted it. See {@link deleteSavedQuery}. */
|
|
404
|
+
async deleteDashboard(id, deletedBy) {
|
|
405
|
+
const before = await this.requireWorkspace().getDashboard(id);
|
|
406
|
+
const deleted = await this.requireWorkspace().deleteDashboard(id);
|
|
407
|
+
if (deleted && before?.shared) {
|
|
408
|
+
(0, catalog_events_1.emitCatalog)('dashboard.shared', {
|
|
409
|
+
dashboardId: before.id,
|
|
410
|
+
name: before.name,
|
|
411
|
+
shared: false,
|
|
412
|
+
principalId: deletedBy,
|
|
413
|
+
deleted: true,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return deleted;
|
|
267
417
|
}
|
|
268
418
|
// ---------------------------------------------------------------------------
|
|
269
419
|
// Embed: what another application's frontend gets.
|
|
@@ -298,8 +448,16 @@ let CatalogService = class CatalogService {
|
|
|
298
448
|
})),
|
|
299
449
|
};
|
|
300
450
|
}
|
|
301
|
-
/**
|
|
302
|
-
|
|
451
|
+
/**
|
|
452
|
+
* One chart, rendered.
|
|
453
|
+
*
|
|
454
|
+
* `placement` is what the dashboard card said, and it is honoured rather than
|
|
455
|
+
* merely carried: a card's `title` and `library` exist to override the saved
|
|
456
|
+
* query on THIS board, so an embed that ignored them would show a different
|
|
457
|
+
* heading and a different chart from the console for the same dashboard —
|
|
458
|
+
* silently, with nothing thrown and nothing logged.
|
|
459
|
+
*/
|
|
460
|
+
async embedChart(savedQueryId, placement) {
|
|
303
461
|
const saved = await this.getSavedQuery(savedQueryId);
|
|
304
462
|
if (!saved.shared) {
|
|
305
463
|
throw new common_1.ForbiddenException(`"${saved.name}" has not been shared. Mark it shared in the console to make it embeddable.`);
|
|
@@ -308,12 +466,15 @@ let CatalogService = class CatalogService {
|
|
|
308
466
|
sql: saved.sql,
|
|
309
467
|
cacheTtlSeconds: saved.cacheTtlSeconds,
|
|
310
468
|
});
|
|
469
|
+
// A card whose title was cleared falls back to the query's name rather than
|
|
470
|
+
// embedding a blank heading — an empty override is the absence of one.
|
|
471
|
+
const overridden = placement?.title?.trim();
|
|
311
472
|
return {
|
|
312
473
|
id: saved.id,
|
|
313
|
-
title: saved.name,
|
|
474
|
+
title: overridden ? overridden : saved.name,
|
|
314
475
|
description: saved.description,
|
|
315
|
-
visualization: saved.visualization,
|
|
316
|
-
layout,
|
|
476
|
+
visualization: (0, catalog_workspace_1.embeddedVisualization)(saved.visualization, placement?.library),
|
|
477
|
+
layout: placement ? { width: placement.width, position: placement.position } : undefined,
|
|
317
478
|
columns: result.columns,
|
|
318
479
|
rows: result.rows,
|
|
319
480
|
rowCount: result.rowCount,
|
|
@@ -336,6 +497,10 @@ let CatalogService = class CatalogService {
|
|
|
336
497
|
charts.push(await this.embedChart(card.savedQueryId, {
|
|
337
498
|
width: card.width,
|
|
338
499
|
position: card.position,
|
|
500
|
+
// Everything the card says about this chart, not only where it
|
|
501
|
+
// sits. See `EmbeddedChartPlacement`.
|
|
502
|
+
...(card.title !== undefined ? { title: card.title } : {}),
|
|
503
|
+
...(card.library !== undefined ? { library: card.library } : {}),
|
|
339
504
|
}));
|
|
340
505
|
}
|
|
341
506
|
catch {
|
package/dist/catalog.types.d.ts
CHANGED
|
@@ -166,7 +166,13 @@ export interface CatalogObjectPage {
|
|
|
166
166
|
size: number;
|
|
167
167
|
total: number;
|
|
168
168
|
pages: number;
|
|
169
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* The visible, non-blob columns, in overlay order.
|
|
171
|
+
*
|
|
172
|
+
* Visible means "not hidden by the overlay". It does **not** mean redacted for
|
|
173
|
+
* a caller: a classified column is here, with its `classification` on it, and
|
|
174
|
+
* dropping it is the host's move — see `readableObjectPage`.
|
|
175
|
+
*/
|
|
170
176
|
columns: Array<{
|
|
171
177
|
name: string;
|
|
172
178
|
displayName: string;
|
|
@@ -34,8 +34,16 @@ export interface SavedQuery {
|
|
|
34
34
|
* relations, so working out "which types does this touch" means parsing the
|
|
35
35
|
* statement — and a permission derived from a parser is a permission that
|
|
36
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
|
-
*
|
|
37
|
+
* it shared is a decision a person made, and it shows up in the audit trail as
|
|
38
|
+
* one: `CatalogService` emits `query.shared` on the transition, in both
|
|
39
|
+
* directions, naming whoever made it. Deleting a shared query is one of those
|
|
40
|
+
* directions — it revokes outside access as surely as the toggle does — and is
|
|
41
|
+
* emitted the same way, with `deleted: true` to say which ending it was.
|
|
42
|
+
*
|
|
43
|
+
* That last sentence was a claim before it was true — the event did not exist
|
|
44
|
+
* and the one act that hands an outside application data left no trace at all.
|
|
45
|
+
* Anything written here about what is recorded should be checkable against
|
|
46
|
+
* `CATALOG_EVENTS` and an emit site.
|
|
39
47
|
*/
|
|
40
48
|
shared: boolean;
|
|
41
49
|
}
|
|
@@ -71,7 +79,11 @@ export interface Dashboard {
|
|
|
71
79
|
createdAt: string;
|
|
72
80
|
updatedAt: string;
|
|
73
81
|
cards: DashboardCard[];
|
|
74
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Fetchable through the embed API by an application with `catalog:embed`.
|
|
84
|
+
*
|
|
85
|
+
* Audited the same way {@link SavedQuery.shared} is, as `dashboard.shared`.
|
|
86
|
+
*/
|
|
75
87
|
shared: boolean;
|
|
76
88
|
}
|
|
77
89
|
export interface DashboardCard {
|
|
@@ -225,11 +237,17 @@ export interface CatalogTrace {
|
|
|
225
237
|
/**
|
|
226
238
|
* True when the whole story fits inside one tick of the recorder's clock.
|
|
227
239
|
*
|
|
228
|
-
* Worth saying out loud rather than quietly drawing zero-width bars:
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
240
|
+
* Worth saying out loud rather than quietly drawing zero-width bars: when a
|
|
241
|
+
* load finishes inside one tick it has no measurable internal timing at all,
|
|
242
|
+
* and a waterfall drawn from it would be a picture of rounding error.
|
|
243
|
+
* Ordering is still correct — see the lifecycle rank the store sorts by — but
|
|
244
|
+
* proportions are not, and a consumer should say so.
|
|
245
|
+
*
|
|
246
|
+
* How coarse a tick is belongs to the store, not to this field: read
|
|
247
|
+
* `clockResolutionMs` rather than assuming. The bundled MySQL store keeps
|
|
248
|
+
* milliseconds, so this is now true only of loads that really did finish
|
|
249
|
+
* inside one — and of rows written before that column was widened, which
|
|
250
|
+
* collapse onto a whole second and are honestly still coarse.
|
|
233
251
|
*/
|
|
234
252
|
coarse: boolean;
|
|
235
253
|
/** Ordered: what started it first, how it ended last. */
|
|
@@ -414,11 +432,53 @@ export interface EmbeddedDashboard {
|
|
|
414
432
|
charts: EmbeddedChart[];
|
|
415
433
|
generatedAt: string;
|
|
416
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* What the *card* says about a chart, as opposed to what the saved query says.
|
|
437
|
+
*
|
|
438
|
+
* A card carries two kinds of statement and they are easy to conflate. Width
|
|
439
|
+
* and position are a hint about the grid, which a consumer may ignore. `title`
|
|
440
|
+
* and `library` are overrides — the board's answer to a question the query has
|
|
441
|
+
* already answered — and dropping them is not a hint being ignored, it is the
|
|
442
|
+
* embed disagreeing with the console about the same dashboard.
|
|
443
|
+
*/
|
|
444
|
+
export interface EmbeddedChartPlacement {
|
|
445
|
+
width: number;
|
|
446
|
+
position: number;
|
|
447
|
+
/** The card's title override. Blank or absent falls back to the query's name. */
|
|
448
|
+
title?: string;
|
|
449
|
+
/** The card's library override. Absent falls back to the query's own. */
|
|
450
|
+
library?: string;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Which library draws an embedded chart, given the two places that can say.
|
|
454
|
+
*
|
|
455
|
+
* The server twin of `visualizationFor` in the React package, and it must stay
|
|
456
|
+
* the same rule: the card wins, then the query, then the built-in renderer. Two
|
|
457
|
+
* different precedences for one field would mean the console and an embedding
|
|
458
|
+
* application draw the same board differently, which is exactly the bug the
|
|
459
|
+
* override exists to prevent.
|
|
460
|
+
*
|
|
461
|
+
* Restated here rather than imported, because a server package must not depend
|
|
462
|
+
* on a React one. Kept as a named function rather than two lines inside
|
|
463
|
+
* `embedChart` for the same reason the React side did: a precedence a test can
|
|
464
|
+
* hold by name cannot drift silently.
|
|
465
|
+
*
|
|
466
|
+
* When neither chose, the key is ABSENT rather than explicitly undefined — this
|
|
467
|
+
* shape is serialised to a consumer, and "the key is there and empty" is a
|
|
468
|
+
* different statement from "nobody chose".
|
|
469
|
+
*/
|
|
470
|
+
export declare function embeddedVisualization(saved: QueryVisualization | undefined, cardLibrary: string | undefined): QueryVisualization;
|
|
417
471
|
export interface CatalogWorkspaceStore {
|
|
418
472
|
listSavedQueries(): Promise<SavedQuery[]>;
|
|
419
473
|
getSavedQuery(id: string): Promise<SavedQuery | undefined>;
|
|
420
474
|
saveQuery(input: SaveQueryInput, createdBy: string): Promise<SavedQuery>;
|
|
421
475
|
updateSavedQuery(id: string, input: Partial<SaveQueryInput>): Promise<SavedQuery | undefined>;
|
|
476
|
+
/**
|
|
477
|
+
* Unchanged by the audit work above it: the *store* takes no actor, because a
|
|
478
|
+
* store that emitted would emit on every path into it and could not tell a
|
|
479
|
+
* revocation from a cascade. `CatalogService.deleteSavedQuery` reads the row
|
|
480
|
+
* first and decides.
|
|
481
|
+
*/
|
|
422
482
|
deleteSavedQuery(id: string): Promise<boolean>;
|
|
423
483
|
listDashboards(): Promise<Dashboard[]>;
|
|
424
484
|
getDashboard(id: string): Promise<Dashboard | undefined>;
|
|
@@ -13,6 +13,7 @@ exports.CATALOG_WORKSPACE_STORE = exports.CATALOG_TRACE_STORE = exports.CATALOG_
|
|
|
13
13
|
exports.isCatalogTraceOutcome = isCatalogTraceOutcome;
|
|
14
14
|
exports.traceOutcomeFilter = traceOutcomeFilter;
|
|
15
15
|
exports.isTraceStore = isTraceStore;
|
|
16
|
+
exports.embeddedVisualization = embeddedVisualization;
|
|
16
17
|
exports.isWorkspaceStore = isWorkspaceStore;
|
|
17
18
|
/**
|
|
18
19
|
* The same events, told as stories instead of as a list.
|
|
@@ -79,6 +80,28 @@ function isTraceStore(store) {
|
|
|
79
80
|
store !== null &&
|
|
80
81
|
typeof Reflect.get(store, 'listTraces') === 'function');
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Which library draws an embedded chart, given the two places that can say.
|
|
85
|
+
*
|
|
86
|
+
* The server twin of `visualizationFor` in the React package, and it must stay
|
|
87
|
+
* the same rule: the card wins, then the query, then the built-in renderer. Two
|
|
88
|
+
* different precedences for one field would mean the console and an embedding
|
|
89
|
+
* application draw the same board differently, which is exactly the bug the
|
|
90
|
+
* override exists to prevent.
|
|
91
|
+
*
|
|
92
|
+
* Restated here rather than imported, because a server package must not depend
|
|
93
|
+
* on a React one. Kept as a named function rather than two lines inside
|
|
94
|
+
* `embedChart` for the same reason the React side did: a precedence a test can
|
|
95
|
+
* hold by name cannot drift silently.
|
|
96
|
+
*
|
|
97
|
+
* When neither chose, the key is ABSENT rather than explicitly undefined — this
|
|
98
|
+
* shape is serialised to a consumer, and "the key is there and empty" is a
|
|
99
|
+
* different statement from "nobody chose".
|
|
100
|
+
*/
|
|
101
|
+
function embeddedVisualization(saved, cardLibrary) {
|
|
102
|
+
const base = saved ?? { kind: 'table' };
|
|
103
|
+
return cardLibrary ? { ...base, library: cardLibrary } : base;
|
|
104
|
+
}
|
|
82
105
|
function isWorkspaceStore(store) {
|
|
83
106
|
return (typeof store === 'object' &&
|
|
84
107
|
store !== null &&
|
package/dist/index.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ export * from './catalog.environment';
|
|
|
12
12
|
export { QueryCache, toCsv } from './catalog.query-cache';
|
|
13
13
|
export { SubprocessTransformRunner, type TransformRunnerOptions, } from './transform-runner';
|
|
14
14
|
export { CatalogService } from './catalog.service';
|
|
15
|
-
export { type AuditQuery, CATALOG_TRACE_OUTCOMES, CATALOG_TRACE_STORE, CATALOG_WORKSPACE_STORE, type CatalogAuditEvent, type CatalogTrace, type CatalogTraceList, type CatalogTraceOutcome, type CatalogTraceSpan, type CatalogTraceStore, type CatalogTraceTotals, type CatalogUnlinkedList, type CatalogWorkspaceStore, type Dashboard, type DashboardCard, isCatalogTraceOutcome, isTraceStore, isWorkspaceStore, type QueryVisualization, type SaveQueryInput, type SavedQuery, type TraceQuery, traceOutcomeFilter, } from './catalog.workspace';
|
|
16
|
-
export { CATALOG_PRINCIPAL_RESOLVER, type CatalogActor, type CatalogGrants, type CatalogPrincipal, type CatalogPrincipalResolver, type CatalogScope, composePrincipalId, delegatePrincipal, expandScopes, hasScope, parsePrincipalId, PRINCIPAL_ACTOR_SEPARATOR, maySeeClassification, mayRead, mayWrite, StaticKeyPrincipalResolver, } from './catalog.principal';
|
|
15
|
+
export { type AuditQuery, CATALOG_TRACE_OUTCOMES, CATALOG_TRACE_STORE, CATALOG_WORKSPACE_STORE, type CatalogAuditEvent, type CatalogTrace, type CatalogTraceList, type CatalogTraceOutcome, type CatalogTraceSpan, type CatalogTraceStore, type CatalogTraceTotals, type CatalogUnlinkedList, type CatalogWorkspaceStore, type Dashboard, type DashboardCard, type EmbeddedChart, type EmbeddedChartPlacement, type EmbeddedDashboard, embeddedVisualization, isCatalogTraceOutcome, isTraceStore, isWorkspaceStore, type QueryVisualization, type SaveQueryInput, type SavedQuery, type TraceQuery, traceOutcomeFilter, } from './catalog.workspace';
|
|
16
|
+
export { CATALOG_PRINCIPAL_RESOLVER, type CatalogActor, type CatalogGrants, type CatalogPrincipal, type CatalogPrincipalResolver, type CatalogScope, composePrincipalId, delegatePrincipal, expandScopes, hasScope, parsePrincipalId, PRINCIPAL_ACTOR_SEPARATOR, maySeeClassification, mayRead, mayWrite, readableObjectPage, StaticKeyPrincipalResolver, } from './catalog.principal';
|
|
17
17
|
export * from './catalog.access';
|
|
18
18
|
export { assertNoColumnCollisions, CATALOG_RESERVED_COLUMNS, CATALOG_SNAPSHOT_MODES, CATALOG_STORE, type CarryForwardResult, type CatalogColumnCollision, CatalogColumnCollisionError, type CatalogMergeStore, type CatalogReadQuery, type CatalogReadResult, type CatalogReadStore, type CatalogReservedColumn, type CatalogSnapshotMode, type CatalogStoreCapabilities, type CatalogWriteStore, type ColumnCollisionOptions, findColumnCollisions, isCatalogStoreCapabilities, isReservedColumn, isWriteStore, type SnapshotRef, supportsCarryForward, } from './catalog.store';
|
|
19
19
|
export { MikroOrmReadStore } from './stores/mikro-orm-read.store';
|
package/dist/index.js
CHANGED
|
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
18
|
-
exports.RequireScopes = exports.RequireHuman = exports.REQUIRES_HUMAN = exports.REQUIRED_SCOPES = exports.MikroOrmReadStore = exports.supportsCarryForward = exports.isWriteStore = exports.isReservedColumn = exports.isCatalogStoreCapabilities = exports.findColumnCollisions = exports.CatalogColumnCollisionError = exports.CATALOG_STORE = exports.CATALOG_SNAPSHOT_MODES = exports.CATALOG_RESERVED_COLUMNS = exports.assertNoColumnCollisions = exports.StaticKeyPrincipalResolver = exports.mayWrite = exports.mayRead = exports.maySeeClassification = exports.PRINCIPAL_ACTOR_SEPARATOR = exports.parsePrincipalId = exports.hasScope = exports.expandScopes = exports.delegatePrincipal = exports.composePrincipalId = void 0;
|
|
17
|
+
exports.traceOutcomeFilter = exports.isWorkspaceStore = exports.isTraceStore = exports.isCatalogTraceOutcome = exports.embeddedVisualization = exports.CATALOG_WORKSPACE_STORE = exports.CATALOG_TRACE_STORE = exports.CATALOG_TRACE_OUTCOMES = exports.CatalogService = exports.SubprocessTransformRunner = exports.toCsv = exports.QueryCache = exports.workflowRunOrder = exports.workflowGraphHash = exports.WORKFLOW_NODE_KINDS = exports.WORKFLOW_NODE_ID_PATTERN = exports.WORKFLOW_ISSUE_CODES = exports.WORKFLOW_EXECUTION_MODES = exports.validateWorkflow = exports.TRANSFORM_LANGUAGES = exports.TRANSFORM_RUNNER = exports.supportsWorkflowStages = exports.supportsWorkflows = exports.isWorkflowNodeKind = exports.isWorkflowNode = exports.isWorkflowExecutionMode = exports.isWorkflowEdge = exports.isTransformLanguage = exports.isPipelineStore = exports.isConnectorKind = exports.CONNECTOR_KINDS = exports.CATALOG_PIPELINE_STORE = exports.CatalogRegistry = exports.MikroOrmCatalogRegistry = exports.CATALOG_OVERLAY_STORE = exports.InMemoryCatalogOverlayStore = exports.FileCatalogOverlayStore = exports.CATALOG_OPTIONS = exports.isQueryStore = exports.assertReadOnlyShape = exports.CatalogModule = exports.emitCatalog = exports.channelNameFor = exports.catalogEventPhase = exports.CATALOG_LIB = exports.CATALOG_EVENTS = exports.CATALOG_EVENT_PHASE_FALLBACK = exports.CATALOG_EVENT_PHASE = exports.CatalogType = exports.CatalogProperty = void 0;
|
|
18
|
+
exports.RequireScopes = exports.RequireHuman = exports.REQUIRES_HUMAN = exports.REQUIRED_SCOPES = exports.MikroOrmReadStore = exports.supportsCarryForward = exports.isWriteStore = exports.isReservedColumn = exports.isCatalogStoreCapabilities = exports.findColumnCollisions = exports.CatalogColumnCollisionError = exports.CATALOG_STORE = exports.CATALOG_SNAPSHOT_MODES = exports.CATALOG_RESERVED_COLUMNS = exports.assertNoColumnCollisions = exports.StaticKeyPrincipalResolver = exports.readableObjectPage = exports.mayWrite = exports.mayRead = exports.maySeeClassification = exports.PRINCIPAL_ACTOR_SEPARATOR = exports.parsePrincipalId = exports.hasScope = exports.expandScopes = exports.delegatePrincipal = exports.composePrincipalId = exports.CATALOG_PRINCIPAL_RESOLVER = void 0;
|
|
19
19
|
var catalog_decorators_1 = require("./catalog.decorators");
|
|
20
20
|
Object.defineProperty(exports, "CatalogProperty", { enumerable: true, get: function () { return catalog_decorators_1.CatalogProperty; } });
|
|
21
21
|
Object.defineProperty(exports, "CatalogType", { enumerable: true, get: function () { return catalog_decorators_1.CatalogType; } });
|
|
@@ -78,6 +78,7 @@ var catalog_workspace_1 = require("./catalog.workspace");
|
|
|
78
78
|
Object.defineProperty(exports, "CATALOG_TRACE_OUTCOMES", { enumerable: true, get: function () { return catalog_workspace_1.CATALOG_TRACE_OUTCOMES; } });
|
|
79
79
|
Object.defineProperty(exports, "CATALOG_TRACE_STORE", { enumerable: true, get: function () { return catalog_workspace_1.CATALOG_TRACE_STORE; } });
|
|
80
80
|
Object.defineProperty(exports, "CATALOG_WORKSPACE_STORE", { enumerable: true, get: function () { return catalog_workspace_1.CATALOG_WORKSPACE_STORE; } });
|
|
81
|
+
Object.defineProperty(exports, "embeddedVisualization", { enumerable: true, get: function () { return catalog_workspace_1.embeddedVisualization; } });
|
|
81
82
|
Object.defineProperty(exports, "isCatalogTraceOutcome", { enumerable: true, get: function () { return catalog_workspace_1.isCatalogTraceOutcome; } });
|
|
82
83
|
Object.defineProperty(exports, "isTraceStore", { enumerable: true, get: function () { return catalog_workspace_1.isTraceStore; } });
|
|
83
84
|
Object.defineProperty(exports, "isWorkspaceStore", { enumerable: true, get: function () { return catalog_workspace_1.isWorkspaceStore; } });
|
|
@@ -93,6 +94,7 @@ Object.defineProperty(exports, "PRINCIPAL_ACTOR_SEPARATOR", { enumerable: true,
|
|
|
93
94
|
Object.defineProperty(exports, "maySeeClassification", { enumerable: true, get: function () { return catalog_principal_1.maySeeClassification; } });
|
|
94
95
|
Object.defineProperty(exports, "mayRead", { enumerable: true, get: function () { return catalog_principal_1.mayRead; } });
|
|
95
96
|
Object.defineProperty(exports, "mayWrite", { enumerable: true, get: function () { return catalog_principal_1.mayWrite; } });
|
|
97
|
+
Object.defineProperty(exports, "readableObjectPage", { enumerable: true, get: function () { return catalog_principal_1.readableObjectPage; } });
|
|
96
98
|
Object.defineProperty(exports, "StaticKeyPrincipalResolver", { enumerable: true, get: function () { return catalog_principal_1.StaticKeyPrincipalResolver; } });
|
|
97
99
|
// Everything, deliberately. The last release exported the directory interface
|
|
98
100
|
// but not the two types its one method takes and returns, so the seam could be
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-catalog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A metadata registry for NestJS: object types, properties and relations, derived from your ORM and enriched with decorators.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Davide Carvalho",
|