@dudousxd/nestjs-catalog 0.6.0 → 0.8.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.decorators.d.ts +17 -0
- package/dist/catalog.decorators.js +17 -0
- package/dist/catalog.environment.d.ts +39 -0
- package/dist/catalog.environment.js +50 -0
- package/dist/catalog.pipeline.d.ts +19 -0
- package/dist/catalog.registry.base.d.ts +46 -1
- package/dist/catalog.registry.base.js +102 -0
- package/dist/catalog.registry.d.ts +1 -2
- package/dist/catalog.registry.js +53 -39
- package/dist/catalog.service.d.ts +17 -0
- package/dist/catalog.service.js +72 -0
- package/dist/catalog.types.d.ts +107 -2
- 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/package.json +1 -1
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/catalog.types.d.ts
CHANGED
|
@@ -54,7 +54,24 @@ export interface CatalogPropertyDef {
|
|
|
54
54
|
/** True when the value came from a hand-written decorator rather than the ORM. */
|
|
55
55
|
enriched: boolean;
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* A link between two object types — the thing that makes this an ontology
|
|
59
|
+
* rather than a list of tables.
|
|
60
|
+
*
|
|
61
|
+
* **Structure derived, semantics declared**, the same split as everywhere else.
|
|
62
|
+
* A `@ManyToOne` already names its target, its kind and its join column, so none
|
|
63
|
+
* of that is ever written by hand — a decorator that could restate it is a
|
|
64
|
+
* decorator that can disagree with the schema. What a human adds is what they
|
|
65
|
+
* add to a scalar, a label and a meaning, through `@CatalogProperty` or the
|
|
66
|
+
* overlay; both key on the property name and so reach a relation without having
|
|
67
|
+
* to know it is one.
|
|
68
|
+
*
|
|
69
|
+
* **One row per declaration, not per link.** `@ManyToOne(() => Base)` on `Mvr`
|
|
70
|
+
* with the matching `@OneToMany` on `Base` is two rows describing one link.
|
|
71
|
+
* Collapsing them here would mean `Base` could not carry its own label for the
|
|
72
|
+
* end it declares, and a type could not say what it points at without consulting
|
|
73
|
+
* every other type. The graph collapses them instead — see {@link CatalogGraph}.
|
|
74
|
+
*/
|
|
58
75
|
export interface CatalogRelationDef {
|
|
59
76
|
name: string;
|
|
60
77
|
displayName: string;
|
|
@@ -67,6 +84,39 @@ export interface CatalogRelationDef {
|
|
|
67
84
|
nullable: boolean;
|
|
68
85
|
hidden: boolean;
|
|
69
86
|
order: number;
|
|
87
|
+
/**
|
|
88
|
+
* True when this side physically holds the key.
|
|
89
|
+
*
|
|
90
|
+
* The two ends of a link are not interchangeable. The owning end is where the
|
|
91
|
+
* foreign key actually is, so it is the end a join is written from, the end
|
|
92
|
+
* whose column can be indexed, and the end whose removal breaks the link. A
|
|
93
|
+
* `1:m` is never the owner — the key lives on the many side.
|
|
94
|
+
*/
|
|
95
|
+
owner: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* The property on {@link targetType} that is the other end of this same link,
|
|
98
|
+
* when the ORM knows it (MikroORM's `mappedBy` / `inversedBy`).
|
|
99
|
+
*
|
|
100
|
+
* This is what lets two rows be recognised as one link. Pairing them by name
|
|
101
|
+
* instead only works for the accident of both ends being spelled the same:
|
|
102
|
+
* `Mvr.base` and `Base.mvrs` are one link and would otherwise draw two edges,
|
|
103
|
+
* which is exactly the picture a graph is supposed to prevent.
|
|
104
|
+
*/
|
|
105
|
+
inverseName?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Whether {@link targetType} is a type this catalog actually holds.
|
|
108
|
+
*
|
|
109
|
+
* False when the target was excluded by configuration, or belongs to an
|
|
110
|
+
* application that has not published it. The relation is still reported: that
|
|
111
|
+
* an MVR points at something called `Base` is true, and when the other end is
|
|
112
|
+
* missing that is the most useful single fact about it. Dropping it silently
|
|
113
|
+
* would leave a type looking unlinked when it is really linked to something
|
|
114
|
+
* out of reach — but drawing it as a navigable edge promises a node that
|
|
115
|
+
* cannot be opened, so the graph omits it and the type page keeps it, marked.
|
|
116
|
+
*/
|
|
117
|
+
targetPublished: boolean;
|
|
118
|
+
/** True when a human has labelled or described this link. */
|
|
119
|
+
enriched: boolean;
|
|
70
120
|
}
|
|
71
121
|
/** One node of the ontology. */
|
|
72
122
|
export interface CatalogObjectTypeDef {
|
|
@@ -97,6 +147,40 @@ export interface CatalogObjectTypeDef {
|
|
|
97
147
|
* came from a regex and are probably wrong.
|
|
98
148
|
*/
|
|
99
149
|
enriched: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* When readers started seeing the data this type currently serves.
|
|
152
|
+
*
|
|
153
|
+
* `committedAt` of the newest committed snapshot, not `createdAt`: a load that
|
|
154
|
+
* was written and never committed is not what anybody is reading, and dating
|
|
155
|
+
* the type by it would report freshness nobody has.
|
|
156
|
+
*
|
|
157
|
+
* **Absent means no committed snapshot ever**, which is a different statement
|
|
158
|
+
* from "committed a year ago" — a type published by a schema and never loaded
|
|
159
|
+
* looked exactly like a type loaded daily until this field existed, and both
|
|
160
|
+
* looked exactly like a type whose publisher was deleted six months ago.
|
|
161
|
+
*
|
|
162
|
+
* Carried on the type rather than fetched per type on demand. The value of
|
|
163
|
+
* this signal is that it arrives without anybody going to look for it, and a
|
|
164
|
+
* field costing one request per row on a screen listing every type is a field
|
|
165
|
+
* that screen will not use.
|
|
166
|
+
*/
|
|
167
|
+
lastCommittedAt?: string;
|
|
168
|
+
/**
|
|
169
|
+
* Rows in that snapshot.
|
|
170
|
+
*
|
|
171
|
+
* Here for a failure the timestamp cannot show: a connector that starts
|
|
172
|
+
* returning 12 rows where it returned 40,000 produces data that is wrong and
|
|
173
|
+
* *fresh*, so every staleness signal reports it as healthy. The count next to
|
|
174
|
+
* the date is what makes that visible, and it is the same read.
|
|
175
|
+
*/
|
|
176
|
+
rowCount?: number;
|
|
177
|
+
/**
|
|
178
|
+
* Which application committed it.
|
|
179
|
+
*
|
|
180
|
+
* Because "stale since March" is never the last question — "so who was
|
|
181
|
+
* loading this?" is — and the answer is already in the row being read.
|
|
182
|
+
*/
|
|
183
|
+
lastPrincipalId?: string;
|
|
100
184
|
properties: CatalogPropertyDef[];
|
|
101
185
|
relations: CatalogRelationDef[];
|
|
102
186
|
}
|
|
@@ -108,12 +192,27 @@ export interface CatalogSnapshot {
|
|
|
108
192
|
stats: {
|
|
109
193
|
types: number;
|
|
110
194
|
properties: number;
|
|
195
|
+
/**
|
|
196
|
+
* Declared relations, summed over the types — **not** distinct links. A link
|
|
197
|
+
* declared at both ends counts twice, because that is what this number is
|
|
198
|
+
* derived from and quietly halving it would make it disagree with the rows
|
|
199
|
+
* on the type pages that produce it. `getGraph().edges.length` is the count
|
|
200
|
+
* of links.
|
|
201
|
+
*/
|
|
111
202
|
relations: number;
|
|
112
203
|
enrichedTypes: number;
|
|
113
204
|
};
|
|
114
205
|
types: CatalogObjectTypeDef[];
|
|
115
206
|
}
|
|
116
|
-
/**
|
|
207
|
+
/**
|
|
208
|
+
* Nodes and edges, for drawing the ontology.
|
|
209
|
+
*
|
|
210
|
+
* One edge per **link**, not per declaration: a link declared at both ends is
|
|
211
|
+
* one line on the picture, drawn from the end that holds the key so the arrow
|
|
212
|
+
* points the way a join is written. And every edge lands on a node that is
|
|
213
|
+
* present — a target this catalog does not hold produces no edge, because an
|
|
214
|
+
* edge to nowhere is a node the reader will try to click.
|
|
215
|
+
*/
|
|
117
216
|
export interface CatalogGraph {
|
|
118
217
|
nodes: Array<{
|
|
119
218
|
id: string;
|
|
@@ -143,6 +242,12 @@ export interface CatalogOverlay {
|
|
|
143
242
|
icon?: string;
|
|
144
243
|
group?: string;
|
|
145
244
|
titleProperty?: string;
|
|
245
|
+
/**
|
|
246
|
+
* Keyed by property name — and a relation is a property to whoever is
|
|
247
|
+
* looking, so a link's label and description are curated through this map
|
|
248
|
+
* too, under the relation's own name. That is why curating a link needs no
|
|
249
|
+
* new route and no new patch shape: `patchProperty` already accepts one.
|
|
250
|
+
*/
|
|
146
251
|
properties?: Record<string, {
|
|
147
252
|
displayName?: string;
|
|
148
253
|
description?: string;
|
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 {};
|