@dudousxd/nestjs-catalog 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog.controller.d.ts +6 -0
- package/dist/catalog.controller.js +54 -0
- package/dist/catalog.events.d.ts +72 -1
- package/dist/catalog.events.js +6 -0
- package/dist/catalog.overlay-store.d.ts +22 -1
- package/dist/catalog.overlay-store.js +47 -6
- package/dist/catalog.pipeline.d.ts +22 -1
- package/dist/catalog.registry.base.d.ts +66 -1
- package/dist/catalog.registry.base.js +102 -0
- package/dist/catalog.registry.d.ts +12 -2
- package/dist/catalog.registry.js +39 -68
- package/dist/catalog.service.d.ts +17 -0
- package/dist/catalog.service.js +72 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +10 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17 -2
- package/dist/search.d.ts +184 -0
- package/dist/search.js +345 -0
- package/dist/search.types.d.ts +93 -0
- package/dist/search.types.js +17 -0
- package/dist/transform-runner.js +223 -10
- package/package.json +1 -1
package/dist/catalog.registry.js
CHANGED
|
@@ -51,26 +51,6 @@ function isOwningSide(prop, kind) {
|
|
|
51
51
|
return true;
|
|
52
52
|
return Boolean(prop.owner);
|
|
53
53
|
}
|
|
54
|
-
/**
|
|
55
|
-
* A key both ends of one link agree on, so the graph can draw it once.
|
|
56
|
-
*
|
|
57
|
-
* The owning end names the link — `Mvr.base` — and the inverse end, which knows
|
|
58
|
-
* the owner's property through `mappedBy`, arrives at the same string. That is
|
|
59
|
-
* the whole trick, and it is why `inverseName` is carried on the def at all.
|
|
60
|
-
*
|
|
61
|
-
* The fallback covers metadata that names neither end of the pair: both rows
|
|
62
|
-
* then reduce to the unordered pair plus the property name, which collapses the
|
|
63
|
-
* symmetric case (two `m:n` sides spelled alike) and leaves genuinely different
|
|
64
|
-
* names as two links. Guessing harder than that would mean pairing links by
|
|
65
|
-
* shape, and drawing one line where the schema has two is the worse error.
|
|
66
|
-
*/
|
|
67
|
-
function linkKey(holder, relation) {
|
|
68
|
-
if (relation.owner)
|
|
69
|
-
return `${holder}.${relation.name}`;
|
|
70
|
-
if (relation.inverseName)
|
|
71
|
-
return `${relation.targetType}.${relation.inverseName}`;
|
|
72
|
-
return `${[holder, relation.targetType].sort().join('::')}::${relation.name}`;
|
|
73
|
-
}
|
|
74
54
|
/** Classify one type name. Returns "unknown" when nothing matches. */
|
|
75
55
|
function classify(raw) {
|
|
76
56
|
const t = raw.toLowerCase();
|
|
@@ -198,18 +178,6 @@ let MikroOrmCatalogRegistry = MikroOrmCatalogRegistry_1 = class MikroOrmCatalogR
|
|
|
198
178
|
this.rebuild();
|
|
199
179
|
return this.entityClasses.get(name);
|
|
200
180
|
}
|
|
201
|
-
getGraph() {
|
|
202
|
-
const snapshot = this.getSnapshot();
|
|
203
|
-
const nodes = snapshot.types.map((t) => ({
|
|
204
|
-
id: t.name,
|
|
205
|
-
label: t.displayName,
|
|
206
|
-
group: t.group,
|
|
207
|
-
icon: t.icon,
|
|
208
|
-
propertyCount: t.properties.length,
|
|
209
|
-
relationCount: t.relations.length,
|
|
210
|
-
}));
|
|
211
|
-
return { nodes, edges: buildEdges(snapshot.types) };
|
|
212
|
-
}
|
|
213
181
|
/** Tier-0 edit on a type. Never touches the database. */
|
|
214
182
|
async patchType(typeName, patch) {
|
|
215
183
|
const type = this.getType(typeName);
|
|
@@ -253,9 +221,22 @@ let MikroOrmCatalogRegistry = MikroOrmCatalogRegistry_1 = class MikroOrmCatalogR
|
|
|
253
221
|
});
|
|
254
222
|
return this.getType(type.name);
|
|
255
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Drop every tier-0 edit, and leave a record that it happened.
|
|
226
|
+
*
|
|
227
|
+
* The summary is taken before the overlay is cleared because it is the only
|
|
228
|
+
* record there will ever be: nothing versions an overlay, so the discarded
|
|
229
|
+
* values are gone the instant the store is written. See `overlay.reset` in
|
|
230
|
+
* `catalog.events.ts` for why the payload is a summary and not a copy.
|
|
231
|
+
*
|
|
232
|
+
* Emitted after the write, like the two patches above, so the trail says what
|
|
233
|
+
* happened rather than what was about to.
|
|
234
|
+
*/
|
|
256
235
|
async resetOverlay() {
|
|
236
|
+
const discarded = summariseOverlay(this.overlay);
|
|
257
237
|
this.overlay = { types: {} };
|
|
258
238
|
await this.persist();
|
|
239
|
+
(0, catalog_events_1.emitCatalog)('overlay.reset', discarded);
|
|
259
240
|
}
|
|
260
241
|
async persist() {
|
|
261
242
|
await this.overlayStore.save(this.overlay);
|
|
@@ -401,47 +382,37 @@ exports.MikroOrmCatalogRegistry = MikroOrmCatalogRegistry = MikroOrmCatalogRegis
|
|
|
401
382
|
__metadata("design:paramtypes", [core_1.MikroORM, Object, Object])
|
|
402
383
|
], MikroOrmCatalogRegistry);
|
|
403
384
|
/**
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
* Two rules, and both exist because the naive version of this drew a picture
|
|
407
|
-
* that was wrong in a way nobody would notice:
|
|
385
|
+
* What a reset is about to destroy, in the shape the trail keeps it.
|
|
408
386
|
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* 2. **Drawn from the end that holds the key**, so the arrow points the way a
|
|
415
|
-
* join is written. Both ends are collected before either is chosen, because
|
|
416
|
-
* otherwise the direction depends on which type was discovered first.
|
|
387
|
+
* Here rather than in `catalog.events.ts` because it reads a `CatalogOverlay`,
|
|
388
|
+
* and this is the only registry that has one — the payload type is the contract,
|
|
389
|
+
* this is one producer of it. Pure and taking the overlay as an argument so the
|
|
390
|
+
* order is forced: a caller has to hold the old overlay to call it, and cannot
|
|
391
|
+
* accidentally summarise the empty one it just installed.
|
|
417
392
|
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
393
|
+
* A type entry counts whatever it holds, including an entry that ended up empty.
|
|
394
|
+
* `buildType` treats a present entry as enrichment on the same terms, and the
|
|
395
|
+
* honest reading of one is "somebody patched this type" — which is exactly what
|
|
396
|
+
* the reset undid.
|
|
421
397
|
*/
|
|
422
|
-
function
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
//
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
398
|
+
function summariseOverlay(overlay) {
|
|
399
|
+
const typeNames = Object.keys(overlay.types);
|
|
400
|
+
const classifications = [];
|
|
401
|
+
let properties = 0;
|
|
402
|
+
for (const typeName of typeNames) {
|
|
403
|
+
const patched = overlay.types[typeName]?.properties ?? {};
|
|
404
|
+
for (const [property, patch] of Object.entries(patched)) {
|
|
405
|
+
properties += 1;
|
|
406
|
+
const { classification } = patch;
|
|
407
|
+
// Only a classification that was actually set. An entry that merely
|
|
408
|
+
// renamed the column carries the key as `undefined`, and listing it would
|
|
409
|
+
// report a classification lost that nobody had applied.
|
|
410
|
+
if (classification !== undefined) {
|
|
411
|
+
classifications.push({ typeName, property, classification });
|
|
412
|
+
}
|
|
436
413
|
}
|
|
437
414
|
}
|
|
438
|
-
return
|
|
439
|
-
id: `${holder}.${relation.name}`,
|
|
440
|
-
source: holder,
|
|
441
|
-
target: relation.targetType,
|
|
442
|
-
label: relation.displayName,
|
|
443
|
-
kind: relation.kind,
|
|
444
|
-
}));
|
|
415
|
+
return { typeNames, properties, classifications };
|
|
445
416
|
}
|
|
446
417
|
/**
|
|
447
418
|
* How one field is presented, resolved across the tiers.
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type CatalogModuleOptions } from './catalog.options';
|
|
2
|
+
import type { CatalogPrincipal } from './catalog.principal';
|
|
2
3
|
import { type CatalogQueryRelation, type CatalogQueryResult } from './catalog.query';
|
|
3
4
|
import { CatalogRegistry } from './catalog.registry.base';
|
|
4
5
|
import { type CatalogReadStore, type SnapshotRef } from './catalog.store';
|
|
5
6
|
import type { CatalogGraph, CatalogObjectPage, CatalogObjectQuery, CatalogObjectTypeDef, CatalogOverlay, CatalogSnapshot } from './catalog.types';
|
|
6
7
|
import { type AuditQuery, type CatalogAuditEvent, type CatalogWorkspaceStore, type Dashboard, type DashboardCard, type EmbeddedChart, type EmbeddedChartPlacement, type EmbeddedDashboard, type SaveQueryInput, type SavedQuery } from './catalog.workspace';
|
|
8
|
+
import type { CatalogSearchResult } from './search.types';
|
|
7
9
|
/**
|
|
8
10
|
* Reads objects of any catalogued type through one endpoint.
|
|
9
11
|
*
|
|
@@ -155,4 +157,19 @@ export declare class CatalogService {
|
|
|
155
157
|
/** A whole dashboard, every chart resolved. */
|
|
156
158
|
embedDashboard(dashboardId: string): Promise<EmbeddedDashboard>;
|
|
157
159
|
listEvents(query: AuditQuery): Promise<CatalogAuditEvent[]>;
|
|
160
|
+
/**
|
|
161
|
+
* Everything matching `term` that this principal may see.
|
|
162
|
+
*
|
|
163
|
+
* @param principal the caller, when the host resolved one. **Optional, and its
|
|
164
|
+
* absence filters nothing** — the declare-and-enforce split written out above
|
|
165
|
+
* `mayWrite` in `catalog.principal.ts` means this library never resolves a
|
|
166
|
+
* principal itself. In a deployment with no guard, `GET /catalog` already
|
|
167
|
+
* hands over the whole snapshot, so search is exactly as open as what is
|
|
168
|
+
* already there and strictly narrower the moment a principal appears. See
|
|
169
|
+
* {@link visibleToPrincipal}.
|
|
170
|
+
*/
|
|
171
|
+
search(term: string, options?: {
|
|
172
|
+
principal?: CatalogPrincipal;
|
|
173
|
+
limit?: number;
|
|
174
|
+
}): Promise<CatalogSearchResult>;
|
|
158
175
|
}
|
package/dist/catalog.service.js
CHANGED
|
@@ -21,6 +21,7 @@ const catalog_query_cache_1 = require("./catalog.query-cache");
|
|
|
21
21
|
const catalog_registry_base_1 = require("./catalog.registry.base");
|
|
22
22
|
const catalog_store_1 = require("./catalog.store");
|
|
23
23
|
const catalog_workspace_1 = require("./catalog.workspace");
|
|
24
|
+
const search_1 = require("./search");
|
|
24
25
|
const DEFAULT_PAGE_SIZE = 25;
|
|
25
26
|
const DEFAULT_MAX_PAGE_SIZE = 200;
|
|
26
27
|
/**
|
|
@@ -519,6 +520,77 @@ let CatalogService = class CatalogService {
|
|
|
519
520
|
listEvents(query) {
|
|
520
521
|
return this.workspace ? this.workspace.listEvents(query) : Promise.resolve([]);
|
|
521
522
|
}
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// Search: one term, four kinds of thing.
|
|
525
|
+
//
|
|
526
|
+
// **One call that fans out, rather than four the client merges**, and the
|
|
527
|
+
// reason is not the round trips.
|
|
528
|
+
//
|
|
529
|
+
// Half of this is already free: the registry snapshot is in memory, so every
|
|
530
|
+
// type and every property costs a loop over an object this process is holding
|
|
531
|
+
// anyway. Only the workspace half touches a store, and it does so as one
|
|
532
|
+
// `Promise.all` — so the wall clock is the slower of two reads, not four
|
|
533
|
+
// sequential fetches from a browser. A client that split this to render the
|
|
534
|
+
// free half a few milliseconds earlier would be buying that with a second
|
|
535
|
+
// request and a second cache key.
|
|
536
|
+
//
|
|
537
|
+
// What actually decides it is that a merged list needs ONE ranking. Four
|
|
538
|
+
// routes means the client owns the ordering across kinds, which means the
|
|
539
|
+
// ordering lives in the browser, which means every other consumer of this HTTP
|
|
540
|
+
// API — and there is meant to be one, that is what `client.ts` is for —
|
|
541
|
+
// reinvents it slightly differently. And the access filter would have four
|
|
542
|
+
// places to be forgotten instead of one, which for the thing that decides
|
|
543
|
+
// whether a caller learns the name of a type they cannot read is not a
|
|
544
|
+
// trade worth making for a progress spinner.
|
|
545
|
+
//
|
|
546
|
+
// The cost, stated because it is real: a deployment whose workspace store is
|
|
547
|
+
// slow makes the free half wait for it. If that ever bites, the fix is a
|
|
548
|
+
// `kinds` parameter on this one route, not four routes.
|
|
549
|
+
// ---------------------------------------------------------------------------
|
|
550
|
+
/**
|
|
551
|
+
* Everything matching `term` that this principal may see.
|
|
552
|
+
*
|
|
553
|
+
* @param principal the caller, when the host resolved one. **Optional, and its
|
|
554
|
+
* absence filters nothing** — the declare-and-enforce split written out above
|
|
555
|
+
* `mayWrite` in `catalog.principal.ts` means this library never resolves a
|
|
556
|
+
* principal itself. In a deployment with no guard, `GET /catalog` already
|
|
557
|
+
* hands over the whole snapshot, so search is exactly as open as what is
|
|
558
|
+
* already there and strictly narrower the moment a principal appears. See
|
|
559
|
+
* {@link visibleToPrincipal}.
|
|
560
|
+
*/
|
|
561
|
+
async search(term, options = {}) {
|
|
562
|
+
const trimmed = (term ?? '').trim();
|
|
563
|
+
if (!trimmed)
|
|
564
|
+
return (0, search_1.emptySearch)();
|
|
565
|
+
// Answered before either store is touched. A principal that may read
|
|
566
|
+
// nothing should not cost a workspace query to be told so.
|
|
567
|
+
if (!(0, search_1.maySearch)(options.principal))
|
|
568
|
+
return (0, search_1.emptySearch)(trimmed);
|
|
569
|
+
const [savedQueries, dashboards] = await Promise.all([
|
|
570
|
+
this.listSavedQueries(),
|
|
571
|
+
this.listDashboards(),
|
|
572
|
+
]);
|
|
573
|
+
return (0, search_1.searchCatalog)({
|
|
574
|
+
term: trimmed,
|
|
575
|
+
types: (0, search_1.visibleToPrincipal)(options.principal, this.registry.getSnapshot().types),
|
|
576
|
+
// Narrowed here rather than handed over whole. `searchCatalog` takes the
|
|
577
|
+
// fields it ranks and nothing else, so `sql` cannot reach the matcher even
|
|
578
|
+
// by accident — see `SearchableSavedQuery` for why matching a statement is
|
|
579
|
+
// the wrong feature rather than a missing one.
|
|
580
|
+
savedQueries: savedQueries.map((query) => ({
|
|
581
|
+
id: query.id,
|
|
582
|
+
name: query.name,
|
|
583
|
+
description: query.description,
|
|
584
|
+
folder: query.folder,
|
|
585
|
+
})),
|
|
586
|
+
dashboards: dashboards.map((dashboard) => ({
|
|
587
|
+
id: dashboard.id,
|
|
588
|
+
name: dashboard.name,
|
|
589
|
+
description: dashboard.description,
|
|
590
|
+
})),
|
|
591
|
+
limit: options.limit,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
522
594
|
};
|
|
523
595
|
exports.CatalogService = CatalogService;
|
|
524
596
|
exports.CatalogService = CatalogService = __decorate([
|
package/dist/client.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export type { AuditQuery, CatalogAuditEvent, Dashboard, DashboardCard, QueryVisualization, SaveQueryInput, SavedQuery, } from './catalog.workspace';
|
|
13
13
|
export type { CatalogQueryRelation, CatalogQueryRequest, CatalogQueryResult, } from './catalog.query';
|
|
14
|
+
export type { CatalogSearchField, CatalogSearchHit, CatalogSearchKind, CatalogSearchRank, CatalogSearchResult, } from './search.types';
|
|
14
15
|
export type { CatalogGraph, CatalogObjectPage, CatalogObjectQuery, CatalogObjectTypeDef, CatalogOverlay, CatalogPropertyDef, CatalogRelationDef, CatalogSnapshot, RelationKind, ScalarType, } from './catalog.types';
|
|
15
16
|
/** What a tier-0 edit to a type may change. */
|
|
16
17
|
export interface TypePatch {
|
|
@@ -45,6 +46,16 @@ export interface ObjectQueryParams {
|
|
|
45
46
|
export declare const catalogRoutes: {
|
|
46
47
|
readonly snapshot: () => string;
|
|
47
48
|
readonly graph: () => string;
|
|
49
|
+
/**
|
|
50
|
+
* One term across types, properties, saved queries and dashboards.
|
|
51
|
+
*
|
|
52
|
+
* No arguments, unlike `type(name)` and friends: `q` and `limit` are a query
|
|
53
|
+
* string, and every route here that takes one — `objects`, `events`, `traces`
|
|
54
|
+
* — leaves it to the caller's HTTP client, because that is the layer that
|
|
55
|
+
* already knows how to serialise and encode one. `accessRoutes.people` in the
|
|
56
|
+
* React package does it the other way and is the odd one out.
|
|
57
|
+
*/
|
|
58
|
+
readonly search: () => string;
|
|
48
59
|
readonly type: (name: string) => string;
|
|
49
60
|
readonly property: (name: string, property: string) => string;
|
|
50
61
|
readonly reset: () => string;
|
package/dist/client.js
CHANGED
|
@@ -20,6 +20,16 @@ exports.isWorkflowNodeKind = exports.isWorkflowExecutionMode = exports.workflowR
|
|
|
20
20
|
exports.catalogRoutes = {
|
|
21
21
|
snapshot: () => '/catalog',
|
|
22
22
|
graph: () => '/catalog/graph',
|
|
23
|
+
/**
|
|
24
|
+
* One term across types, properties, saved queries and dashboards.
|
|
25
|
+
*
|
|
26
|
+
* No arguments, unlike `type(name)` and friends: `q` and `limit` are a query
|
|
27
|
+
* string, and every route here that takes one — `objects`, `events`, `traces`
|
|
28
|
+
* — leaves it to the caller's HTTP client, because that is the layer that
|
|
29
|
+
* already knows how to serialise and encode one. `accessRoutes.people` in the
|
|
30
|
+
* React package does it the other way and is the odd one out.
|
|
31
|
+
*/
|
|
32
|
+
search: () => '/catalog/search',
|
|
23
33
|
type: (name) => `/catalog/types/${encodeURIComponent(name)}`,
|
|
24
34
|
property: (name, property) => `/catalog/types/${encodeURIComponent(name)}/properties/${encodeURIComponent(property)}`,
|
|
25
35
|
reset: () => '/catalog/reset',
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +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 { DEFAULT_SEARCH_LIMIT, MAX_SEARCH_LIMIT, bestMatch, emptySearch, maySearch, type SearchInput, type SearchableDashboard, type SearchableSavedQuery, searchCatalog, visibleToPrincipal, } from './search';
|
|
16
|
+
export type { CatalogSearchField, CatalogSearchHit, CatalogSearchKind, CatalogSearchRank, CatalogSearchResult, } from './search.types';
|
|
15
17
|
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
18
|
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
19
|
export * from './catalog.access';
|
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.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;
|
|
17
|
+
exports.CATALOG_TRACE_OUTCOMES = exports.visibleToPrincipal = exports.searchCatalog = exports.maySearch = exports.emptySearch = exports.bestMatch = exports.MAX_SEARCH_LIMIT = exports.DEFAULT_SEARCH_LIMIT = 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 = exports.traceOutcomeFilter = exports.isWorkspaceStore = exports.isTraceStore = exports.isCatalogTraceOutcome = exports.embeddedVisualization = exports.CATALOG_WORKSPACE_STORE = exports.CATALOG_TRACE_STORE = 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; } });
|
|
@@ -74,6 +74,21 @@ var transform_runner_1 = require("./transform-runner");
|
|
|
74
74
|
Object.defineProperty(exports, "SubprocessTransformRunner", { enumerable: true, get: function () { return transform_runner_1.SubprocessTransformRunner; } });
|
|
75
75
|
var catalog_service_1 = require("./catalog.service");
|
|
76
76
|
Object.defineProperty(exports, "CatalogService", { enumerable: true, get: function () { return catalog_service_1.CatalogService; } });
|
|
77
|
+
// Search. The result types are on `/client` too, for a browser; these are here
|
|
78
|
+
// because a host that passed `controller: false` and wrote its own routes needs
|
|
79
|
+
// to type the handler, and — more importantly — needs `visibleToPrincipal` and
|
|
80
|
+
// `maySearch` if it calls `searchCatalog` directly rather than going through
|
|
81
|
+
// `CatalogService.search`. Exporting the matcher without them would ship the
|
|
82
|
+
// half that ranks and withhold the half that decides who may see what, which is
|
|
83
|
+
// the exact shape of the gap `index.barrel.spec.ts` was written after.
|
|
84
|
+
var search_1 = require("./search");
|
|
85
|
+
Object.defineProperty(exports, "DEFAULT_SEARCH_LIMIT", { enumerable: true, get: function () { return search_1.DEFAULT_SEARCH_LIMIT; } });
|
|
86
|
+
Object.defineProperty(exports, "MAX_SEARCH_LIMIT", { enumerable: true, get: function () { return search_1.MAX_SEARCH_LIMIT; } });
|
|
87
|
+
Object.defineProperty(exports, "bestMatch", { enumerable: true, get: function () { return search_1.bestMatch; } });
|
|
88
|
+
Object.defineProperty(exports, "emptySearch", { enumerable: true, get: function () { return search_1.emptySearch; } });
|
|
89
|
+
Object.defineProperty(exports, "maySearch", { enumerable: true, get: function () { return search_1.maySearch; } });
|
|
90
|
+
Object.defineProperty(exports, "searchCatalog", { enumerable: true, get: function () { return search_1.searchCatalog; } });
|
|
91
|
+
Object.defineProperty(exports, "visibleToPrincipal", { enumerable: true, get: function () { return search_1.visibleToPrincipal; } });
|
|
77
92
|
var catalog_workspace_1 = require("./catalog.workspace");
|
|
78
93
|
Object.defineProperty(exports, "CATALOG_TRACE_OUTCOMES", { enumerable: true, get: function () { return catalog_workspace_1.CATALOG_TRACE_OUTCOMES; } });
|
|
79
94
|
Object.defineProperty(exports, "CATALOG_TRACE_STORE", { enumerable: true, get: function () { return catalog_workspace_1.CATALOG_TRACE_STORE; } });
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One box that crosses the catalog.
|
|
3
|
+
*
|
|
4
|
+
* A catalog with two hundred object types is a catalog where finding anything
|
|
5
|
+
* means already knowing which screen it lives on — types and properties on the
|
|
6
|
+
* model screen, saved queries on the query screen, boards on the dashboards
|
|
7
|
+
* screen — and the thing people actually type is a word they half-remember. This
|
|
8
|
+
* module is the half of that which has no request in it: given a term, some
|
|
9
|
+
* types, some saved queries and some dashboards, which rows come back and in
|
|
10
|
+
* what order.
|
|
11
|
+
*
|
|
12
|
+
* Pure on purpose. The ranking is the part a reader has to be able to predict
|
|
13
|
+
* and the part that must not change by accident, so it is a function with no
|
|
14
|
+
* store, no principal and no clock in it, and the two things that DO depend on
|
|
15
|
+
* who is asking — {@link visibleToPrincipal} and the route's scope — sit either
|
|
16
|
+
* side of it where they can be read.
|
|
17
|
+
*/
|
|
18
|
+
import { type CatalogPrincipal } from './catalog.principal';
|
|
19
|
+
import type { CatalogObjectTypeDef } from './catalog.types';
|
|
20
|
+
import type { CatalogSearchField, CatalogSearchRank, CatalogSearchResult } from './search.types';
|
|
21
|
+
/**
|
|
22
|
+
* What comes back when nothing was asked for, or when the caller may see
|
|
23
|
+
* nothing at all.
|
|
24
|
+
*
|
|
25
|
+
* A function rather than a shared constant so no two responses can hand out the
|
|
26
|
+
* same `hits` array — a frozen empty list is safe until somebody downstream
|
|
27
|
+
* decides an empty result is a fine thing to push a "nothing found" placeholder
|
|
28
|
+
* onto.
|
|
29
|
+
*
|
|
30
|
+
* The two cases are deliberately indistinguishable from outside. "You may see
|
|
31
|
+
* none of the eleven things that matched" and "eleven things matched, none of
|
|
32
|
+
* them yours" are the same sentence to a caller, and the second one is the
|
|
33
|
+
* disclosure.
|
|
34
|
+
*/
|
|
35
|
+
export declare function emptySearch(term?: string): CatalogSearchResult;
|
|
36
|
+
export declare const DEFAULT_SEARCH_LIMIT = 50;
|
|
37
|
+
export declare const MAX_SEARCH_LIMIT = 200;
|
|
38
|
+
interface Candidate {
|
|
39
|
+
field: CatalogSearchField;
|
|
40
|
+
value: string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Whether this field says what the thing is CALLED, as opposed to what
|
|
43
|
+
* somebody wrote about it. Identifying fields can reach every rank; describing
|
|
44
|
+
* fields only ever reach `text`.
|
|
45
|
+
*/
|
|
46
|
+
identifying: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The best rank any of these fields can claim for this term, and which field
|
|
50
|
+
* claimed it.
|
|
51
|
+
*
|
|
52
|
+
* Ties go to the field declared first, which is why every call site below lists
|
|
53
|
+
* `name` before `displayName`: on equal evidence the code name wins, because it
|
|
54
|
+
* is the stable identity, the string a URL carries, and the one a person who
|
|
55
|
+
* typed it was almost certainly typing on purpose.
|
|
56
|
+
*
|
|
57
|
+
* `term` is expected already lower-cased and trimmed — done once by the caller
|
|
58
|
+
* rather than per field, since this runs over every property of every type.
|
|
59
|
+
*/
|
|
60
|
+
export declare function bestMatch(term: string, candidates: Candidate[]): {
|
|
61
|
+
rank: CatalogSearchRank;
|
|
62
|
+
field: CatalogSearchField;
|
|
63
|
+
} | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* The catalog as this principal is allowed to see it.
|
|
66
|
+
*
|
|
67
|
+
* Two rules, and both are about names rather than values:
|
|
68
|
+
*
|
|
69
|
+
* *A type they may not read does not exist here*, and neither do its properties.
|
|
70
|
+
* A search that answers "there is a type called `PayrollAdjustment`" to somebody
|
|
71
|
+
* whose `readTypes` excludes it has disclosed the thing they were excluded from,
|
|
72
|
+
* even though not one row came back.
|
|
73
|
+
*
|
|
74
|
+
* *A classified property they do not hold the classification for is dropped, not
|
|
75
|
+
* blanked.* `readableObjectPage` deletes such a column from a page of rows for
|
|
76
|
+
* the same reason; here the sensitive part IS the name — `settlement_amount` on
|
|
77
|
+
* a table called `Dispute` is the disclosure, and a hit saying "there is a
|
|
78
|
+
* property here you may not see" is worse than no hit, because it also confirms
|
|
79
|
+
* the guess that produced the search term.
|
|
80
|
+
*
|
|
81
|
+
* **An absent principal filters nothing**, and that is not a fail-open. This
|
|
82
|
+
* library resolves no principal and ships no guard — the split is written out at
|
|
83
|
+
* length above `mayWrite` in `catalog.principal.ts` — so `undefined` here means
|
|
84
|
+
* the host wired no guard, and in that deployment `GET /catalog` already hands
|
|
85
|
+
* the entire snapshot, every type and every property name, to whoever asks.
|
|
86
|
+
* Search must never be a SOFTER path to something than the routes that exist;
|
|
87
|
+
* being exactly as soft as the snapshot route, and strictly harder the moment a
|
|
88
|
+
* principal appears, is the guarantee this can honestly make.
|
|
89
|
+
*
|
|
90
|
+
* Hidden properties are kept. `hidden` is a tier-0 display flag any curator can
|
|
91
|
+
* flip back, it is already in the snapshot, and excluding it would make search
|
|
92
|
+
* the one place a curator cannot find the property they just hid in order to
|
|
93
|
+
* un-hide it.
|
|
94
|
+
*/
|
|
95
|
+
export declare function visibleToPrincipal(principal: CatalogPrincipal | undefined, types: CatalogObjectTypeDef[]): CatalogObjectTypeDef[];
|
|
96
|
+
/**
|
|
97
|
+
* Whether this principal may use the search route at all.
|
|
98
|
+
*
|
|
99
|
+
* The route declares `catalog:read` and a host's guard is what enforces it, so
|
|
100
|
+
* in a correctly wired deployment this can never be false. It is asked anyway
|
|
101
|
+
* because of what would otherwise be inconsistent: `visibleToPrincipal` drops
|
|
102
|
+
* every type for a principal without `catalog:read` — `mayRead` checks the scope
|
|
103
|
+
* first — while the saved queries and dashboards, which have no per-object grant
|
|
104
|
+
* to check, would sail through. A route that answers "no types, but here are
|
|
105
|
+
* eleven board names" to somebody who may read nothing is a route whose access
|
|
106
|
+
* story depends on which half of it you read.
|
|
107
|
+
*/
|
|
108
|
+
export declare function maySearch(principal: CatalogPrincipal | undefined): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* What a saved query contributes to a search. A subset, not the row: `sql` is
|
|
111
|
+
* deliberately absent from the input as well as the output.
|
|
112
|
+
*
|
|
113
|
+
* Matching on the statement is a tempting feature — "which query touches
|
|
114
|
+
* `mvr`?" — and it is the wrong one here. It turns a name search into a code
|
|
115
|
+
* search, so a term like `select` matches everything; the hit it produces cannot
|
|
116
|
+
* be explained in a row without showing the SQL that justified it; and the row
|
|
117
|
+
* would then be a fragment of a statement rendered somewhere a statement was
|
|
118
|
+
* never meant to appear. A host that wants to grep saved SQL wants a different
|
|
119
|
+
* route with a different name.
|
|
120
|
+
*/
|
|
121
|
+
export interface SearchableSavedQuery {
|
|
122
|
+
id: string;
|
|
123
|
+
name: string;
|
|
124
|
+
description?: string;
|
|
125
|
+
/** Free-form grouping. Ranked as a `group`, which is what it is. */
|
|
126
|
+
folder?: string;
|
|
127
|
+
}
|
|
128
|
+
export interface SearchableDashboard {
|
|
129
|
+
id: string;
|
|
130
|
+
name: string;
|
|
131
|
+
description?: string;
|
|
132
|
+
}
|
|
133
|
+
export interface SearchInput {
|
|
134
|
+
term: string;
|
|
135
|
+
types: CatalogObjectTypeDef[];
|
|
136
|
+
savedQueries: SearchableSavedQuery[];
|
|
137
|
+
dashboards: SearchableDashboard[];
|
|
138
|
+
/** Bounded by {@link MAX_SEARCH_LIMIT} whatever is passed. */
|
|
139
|
+
limit?: number;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Search four kinds of thing and return them in one ranked list.
|
|
143
|
+
*
|
|
144
|
+
* ---------------------------------------------------------------------------
|
|
145
|
+
* **Why connectors and transforms are not in here.**
|
|
146
|
+
*
|
|
147
|
+
* Not an oversight, and not something to add later without moving something
|
|
148
|
+
* else first. Connectors and transforms are served by
|
|
149
|
+
* `@dudousxd/nestjs-catalog-pipeline`, a package this one does not depend on and
|
|
150
|
+
* should not: `routes.ts` in the React package makes the argument in full, but
|
|
151
|
+
* the short version is that the catalog library ships no controller for them
|
|
152
|
+
* because how a deployment exposes the code that reshapes its data is the
|
|
153
|
+
* deployment's decision, not this library's.
|
|
154
|
+
*
|
|
155
|
+
* The access consequence is the deciding one. This route declares
|
|
156
|
+
* `catalog:read`. A connector carries a connection reference and a
|
|
157
|
+
* `secretEnvVar` naming where its credential lives, and whatever guard a host
|
|
158
|
+
* put on its pipeline routes, it was not necessarily this one. Folding
|
|
159
|
+
* connectors into a `catalog:read` result would quietly re-grant them under a
|
|
160
|
+
* scope their owner never agreed to — the exact shape of the failure the scope
|
|
161
|
+
* table at the top of `catalog.controller.ts` exists to prevent.
|
|
162
|
+
*
|
|
163
|
+
* So the seam is stated rather than hidden: this searches the registry snapshot
|
|
164
|
+
* plus the workspace store, which are the two things the catalog module owns. A
|
|
165
|
+
* console that wants connectors in the same box makes a second call against the
|
|
166
|
+
* pipeline's own routes, under the pipeline's own guard, and merges two lists —
|
|
167
|
+
* which is honest about the fact that they are two permissions.
|
|
168
|
+
* ---------------------------------------------------------------------------
|
|
169
|
+
*
|
|
170
|
+
* The order, in full, so it can be argued with:
|
|
171
|
+
*
|
|
172
|
+
* 1. rank — `exact`, then `prefix`, then `name`, then `text`;
|
|
173
|
+
* 2. kind — type, property, saved query, dashboard;
|
|
174
|
+
* 3. label, then id, lexicographically.
|
|
175
|
+
*
|
|
176
|
+
* Rank outranks kind because an exact property match is a better answer than a
|
|
177
|
+
* type whose description happens to mention the word. Steps 2 and 3 exist so the
|
|
178
|
+
* result is *total*: a search that returned the same rows in a different order
|
|
179
|
+
* on the next call would make the top of the list flicker under a debounced
|
|
180
|
+
* input, and would make every test of this function a test of `Array.sort`
|
|
181
|
+
* stability.
|
|
182
|
+
*/
|
|
183
|
+
export declare function searchCatalog(input: SearchInput): CatalogSearchResult;
|
|
184
|
+
export {};
|