@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.
@@ -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
- * The links, as lines to draw.
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
- * 1. **One edge per link.** A link declared at both ends produces two rows, and
410
- * keying the de-duplication on the property name only caught the case where
411
- * both ends happened to be spelled alike so `Mvr.base` plus `Base.mvrs`,
412
- * the ordinary shape, drew two lines between the same pair of nodes. See
413
- * {@link linkKey}.
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
- * Hidden relations are deliberately still drawn. Hiding is a statement about a
419
- * table cell; a graph that quietly dropped edges would be a picture nobody could
420
- * read as complete, which is the only thing a graph is for.
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 buildEdges(types) {
423
- const byLink = new Map();
424
- for (const type of types) {
425
- for (const relation of type.relations) {
426
- if (!relation.targetPublished)
427
- continue;
428
- const key = linkKey(type.name, relation);
429
- const seen = byLink.get(key);
430
- // Replace only when this row is the owning one and the row already held
431
- // is not: anything else keeps the first, so the edge order stays the type
432
- // order rather than shuffling with every rebuild.
433
- if (seen && (seen.relation.owner || !relation.owner))
434
- continue;
435
- byLink.set(key, { holder: type.name, relation });
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 [...byLink.values()].map(({ holder, relation }) => ({
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
  }
@@ -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.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;
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; } });
@@ -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 {};