@dudousxd/nestjs-catalog 0.12.0 → 0.14.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.
@@ -15,6 +15,7 @@
15
15
  * need a migration and never need an engineer. That boundary is what makes
16
16
  * it safe to hand the editor to a non-engineer.
17
17
  */
18
+ import type { CatalogFilterOperator } from './catalog.filters';
18
19
  export type ScalarType = 'string' | 'number' | 'boolean' | 'date' | 'json' | 'uuid' | 'unknown';
19
20
  export type RelationKind = '1:1' | '1:m' | 'm:1' | 'm:n';
20
21
  /** A single scalar field on an object type. */
@@ -264,6 +265,16 @@ export interface CatalogObjectQuery {
264
265
  search?: string;
265
266
  sort?: string;
266
267
  dir?: 'asc' | 'desc';
268
+ /**
269
+ * Column filters, as they arrived: `property:operator:value`, one string each.
270
+ *
271
+ * Unvalidated, exactly like `sort` and `search` beside it — these are what a
272
+ * caller typed. `CatalogService.readObjects` resolves them against the type
273
+ * before any store sees them, and refuses the read if any of them cannot be
274
+ * honoured. See `catalog.filters.ts`, which owns both halves of that rule and
275
+ * is also what a console derives its controls from.
276
+ */
277
+ filters?: string[];
267
278
  }
268
279
  export interface CatalogObjectPage {
269
280
  type: string;
@@ -284,6 +295,52 @@ export interface CatalogObjectPage {
284
295
  type: ScalarType;
285
296
  classification?: string;
286
297
  unit?: string;
298
+ /**
299
+ * How the source spells this column, when it is not how the property is
300
+ * named.
301
+ *
302
+ * Carried because on a published type the two really do differ: a source
303
+ * column called `Asset Id` cannot be a SQL identifier, so the property is
304
+ * `Asset_Id` and `columnName` keeps the original. A reader recognises the
305
+ * source spelling — it is what is on their spreadsheet — and a filter has to
306
+ * be built from the property name, so a screen that shows only one of the two
307
+ * either fails to be recognised or invites a filter on a name that resolves
308
+ * to nothing. Both are here so a screen can show one and send the other.
309
+ *
310
+ * Optional: a page served by a version of this library that predates the
311
+ * field simply does not say, and a screen falls back to the property name.
312
+ */
313
+ columnName?: string;
314
+ /**
315
+ * What this column may be filtered with, here and now.
316
+ *
317
+ * Derived from the column by `filterOperatorsFor` and then narrowed to what
318
+ * the mounted store can actually apply, so the list is the server's answer
319
+ * rather than the screen's guess. **Empty means not filterable** — a
320
+ * classified column, a blob, or a store that does not filter at all.
321
+ *
322
+ * Optional, and absent is not the same as empty: a server older than this
323
+ * field has not been asked. A screen must read absent the pessimistic way and
324
+ * offer nothing, because offering a control the server will refuse is worse
325
+ * than offering none.
326
+ */
327
+ filterOperators?: CatalogFilterOperator[];
287
328
  }>;
288
329
  rows: Array<Record<string, unknown>>;
330
+ /**
331
+ * Which load these rows came from, when the store keeps history.
332
+ *
333
+ * Reported by the store as part of the read rather than looked up separately,
334
+ * so it costs nothing and — more importantly — it describes the snapshot that
335
+ * was actually read rather than the one the caller believes it asked for. A
336
+ * screen that drew its "you are looking at an old load" banner from its own
337
+ * state would be trusting the wrong end of the request.
338
+ *
339
+ * Absent when the store keeps no snapshots at all.
340
+ */
341
+ snapshot?: {
342
+ id: string;
343
+ /** False means these rows are NOT what a reader gets by default. */
344
+ current: boolean;
345
+ };
289
346
  }
package/dist/client.d.ts CHANGED
@@ -9,11 +9,30 @@
9
9
  * own UI. The endpoints alone are not an API; the endpoints plus the response
10
10
  * types are.
11
11
  */
12
+ import type { DeleteReconciliation, LoadExpectation, RowCountBound, StoredLoadExpectation } from './catalog.pipeline';
12
13
  export type { AuditQuery, CatalogAuditEvent, CatalogRevision, Dashboard, DashboardCard, QueryVisualization, SaveQueryInput, SavedQuery, } from './catalog.workspace';
13
14
  export { CATALOG_REVISION_LIMIT } from './catalog.workspace';
14
15
  export type { CatalogQueryRelation, CatalogQueryRequest, CatalogQueryResult, } from './catalog.query';
15
16
  export type { CatalogSearchField, CatalogSearchHit, CatalogSearchKind, CatalogSearchRank, CatalogSearchResult, } from './search.types';
16
17
  export type { CatalogGraph, CatalogObjectPage, CatalogObjectQuery, CatalogObjectTypeDef, CatalogOverlay, CatalogPropertyDef, CatalogRelationDef, CatalogSnapshot, RelationKind, ScalarType, } from './catalog.types';
18
+ /**
19
+ * The filter rule, shipped to the browser deliberately — the same exception, for
20
+ * the same reason, that `validateWorkflow` further down is.
21
+ *
22
+ * A console has to know which control to draw for a column, and the only way for
23
+ * that answer to match what the server will accept is for both to run this
24
+ * function. A screen with its own copy of the rules eventually lies: it offers a
25
+ * control the read refuses, or omits one that would have worked, and on types
26
+ * that are created at runtime nobody notices until a publisher adds a column. The
27
+ * functions are pure and import nothing.
28
+ *
29
+ * `SnapshotRef` rides along because a snapshot picker is a browser screen and
30
+ * `GET objects/:name/snapshots` is what fills it. The endpoints alone are not an
31
+ * API; the endpoints plus the response types are.
32
+ */
33
+ export { CATALOG_FILTER_LIMIT, CATALOG_FILTER_OPERATORS, coerceFilterValue, encodeObjectFilter, filterOperatorTakesValue, filterOperatorsFor, isCatalogFilterOperator, offeredFilterOperators, parseObjectFilter, resolveObjectFilters, VALUELESS_FILTER_OPERATORS, } from './catalog.filters';
34
+ export type { CatalogFilterableColumn, CatalogFilterOperator, CatalogFilterResolution, CatalogObjectFilter, CatalogResolvedFilter, } from './catalog.filters';
35
+ export type { SnapshotRef } from './catalog.store';
17
36
  /** What a tier-0 edit to a type may change. */
18
37
  export interface TypePatch {
19
38
  displayName?: string;
@@ -38,6 +57,26 @@ export interface ObjectQueryParams {
38
57
  search?: string;
39
58
  sort?: string;
40
59
  dir?: 'asc' | 'desc';
60
+ /**
61
+ * `property:operator:value`, one entry per filter, ANDed by the server.
62
+ *
63
+ * Named `filter` rather than `filters` because that is the query parameter the
64
+ * route reads, and this object is handed to a transport that serialises it
65
+ * verbatim — a name that disagreed with the route would be a filter that is
66
+ * sent, ignored, and reported by the screen as applied.
67
+ *
68
+ * Build entries with `encodeObjectFilter` rather than by hand: it is what the
69
+ * server parses with, and the two colons are load-bearing.
70
+ */
71
+ filter?: string[];
72
+ /**
73
+ * Read the type as of an earlier load. Omit for the current one, which is what
74
+ * every reader must get by default.
75
+ *
76
+ * Ids come from `GET objects/:name/snapshots`. A store that keeps no history
77
+ * refuses this rather than answering with current state.
78
+ */
79
+ snapshot?: string;
41
80
  }
42
81
  /**
43
82
  * Builds the paths the catalog controller serves, relative to wherever it was
@@ -106,3 +145,111 @@ export { CONNECTOR_KINDS, isConnectorKind, isTransformLanguage, isWorkflowEdge,
106
145
  export { validateWorkflow, WORKFLOW_EXECUTION_MODES, WORKFLOW_ISSUE_CODES, WORKFLOW_NODE_ID_PATTERN, WORKFLOW_NODE_KINDS, WORKFLOW_STATUSES, workflowGraphHash, workflowRunOrder, isWorkflowExecutionMode, isWorkflowNodeKind, isWorkflowStatus, } from './catalog.pipeline';
107
146
  export type { WorkflowStatus } from './catalog.pipeline';
108
147
  export type { CatalogTrace, CatalogTraceList, CatalogTraceOutcome, CatalogTraceSpan, TraceQuery, } from './catalog.workspace';
148
+ export type { CatalogLoadExpectations, DeleteReconciliation, LoadExpectation, RowCountBound, StoredLoadExpectation, } from './catalog.pipeline';
149
+ /**
150
+ * The three answers to "how do deletions at the source reach this type", as a
151
+ * value.
152
+ *
153
+ * A list rather than only the union, because the editor on the Model screen has
154
+ * to offer them and a hand-written array in a component is the copy that drifts
155
+ * — the same argument {@link CONNECTOR_KINDS} makes one screen over. The server
156
+ * validates against this too: a `strategy` off the wire is `string` until
157
+ * something checks it, and the check and the dropdown reading one list is what
158
+ * stops the two from disagreeing about what is acceptable.
159
+ *
160
+ * **There are three and there is deliberately no fourth.** Tombstones off a
161
+ * change feed is the correct answer and needs machinery nothing here has; a
162
+ * strategy name that nothing implements is a dropdown with a lie in it. See
163
+ * `DeleteReconciliation` for the whole argument.
164
+ */
165
+ export declare const DELETE_RECONCILIATION_STRATEGIES: readonly ["accepted", "soft-deleted-at-source", "periodic-full-reload"];
166
+ export type DeleteReconciliationStrategy = (typeof DELETE_RECONCILIATION_STRATEGIES)[number];
167
+ /** Whether a value off the wire names one of the three. */
168
+ export declare function isDeleteReconciliationStrategy(value: unknown): value is DeleteReconciliationStrategy;
169
+ /**
170
+ * What an operator sends to set a type's expectation.
171
+ *
172
+ * Both fields optional and both meaning "leave this alone" when absent, which is
173
+ * why they are not simply `LoadExpectation`: a request that omits `rowCount`
174
+ * has said nothing about row counts, and reading that as "clear it" would let a
175
+ * form that only renders the delete strategy silently drop a bound somebody set.
176
+ * Clearing the whole stored row is `DELETE`, which is a decision with a verb on
177
+ * it.
178
+ */
179
+ export interface LoadExpectationInput {
180
+ deletes?: DeleteReconciliation;
181
+ rowCount?: Partial<RowCountBound>;
182
+ }
183
+ /**
184
+ * The resolved expectation for one type, and which layer won each field.
185
+ *
186
+ * The provenance is the whole reason this is not just a `LoadExpectation`. The
187
+ * policy is sourced from three layers — `host.byType[type]`, then the stored row
188
+ * an operator set, then `host.default` — and a screen that showed only the
189
+ * answer would let somebody edit a field this deployment has pinned in code and
190
+ * watch the edit vanish on the next read, with nothing anywhere saying why.
191
+ * {@link hostLocked} is what lets it say "this deployment fixed it" instead.
192
+ */
193
+ export interface ResolvedLoadExpectation {
194
+ typeName: string;
195
+ /** The three layers merged, field by field. What the load is actually judged against. */
196
+ resolved: LoadExpectation;
197
+ /**
198
+ * Which layer supplied the delete strategy.
199
+ *
200
+ * `'default'` means the host's house-wide `default` did. `'none'` means
201
+ * nothing did, anywhere — which is not a gap to be filled in silently: it is
202
+ * the state that refuses every incremental load of this type, and the one the
203
+ * screen most needs to name.
204
+ */
205
+ deletesFrom: 'host' | 'stored' | 'default' | 'none';
206
+ /**
207
+ * Which layer supplied the row-count bound — the strongest one that set any
208
+ * field of it, since the three are merged key by key rather than replaced
209
+ * whole.
210
+ *
211
+ * There is no `'none'`, and that is a statement rather than an omission: a
212
+ * bound always applies. Where no layer says anything the built-in
213
+ * `DEFAULT_ROW_COUNT_BOUND` does, and that is what `'default'` covers as well
214
+ * as the host's own `default`.
215
+ */
216
+ rowCountFrom: 'host' | 'stored' | 'default';
217
+ /** The operator's row, present whether or not it won anything. */
218
+ stored?: StoredLoadExpectation;
219
+ /**
220
+ * Which fields this deployment declared in code, per field.
221
+ *
222
+ * True means a stored value for that field would never apply, so the editor
223
+ * shows it disabled and says so. Only `host.byType[type]` locks: `host.default`
224
+ * is the weakest layer and an operator's row beats it, so a house-wide default
225
+ * is not a lock and must not be drawn as one.
226
+ */
227
+ hostLocked: {
228
+ deletes: boolean;
229
+ rowCount: boolean;
230
+ };
231
+ }
232
+ /**
233
+ * Where the per-type expectations sit, relative to wherever the pipeline
234
+ * controller was mounted.
235
+ *
236
+ * A function of the base path rather than a frozen object like
237
+ * {@link catalogRoutes}, and the difference is the one `routes.ts` in the React
238
+ * package draws: the catalog controller's paths cannot move, and these move with
239
+ * whatever `path` the host passed to `CatalogPipelineModule.forRoot` — the
240
+ * routes below are `<path>/pipeline/expectations`, and `/pipeline` is only the
241
+ * default.
242
+ *
243
+ * Two builders for four routes: `PUT` and `DELETE` address the same path a `GET`
244
+ * of one type does, and a builder per verb would be three names for one string.
245
+ */
246
+ export declare function pipelineExpectationRoutes(basePath?: string): {
247
+ /** Every stored row, plus the types the host has locked. `GET`, `catalog:read`. */
248
+ readonly expectations: () => string;
249
+ /**
250
+ * One type: `GET` for the resolved expectation and its provenance, `PUT` to
251
+ * set the stored row, `DELETE` to drop it. The writes need `catalog:curate`
252
+ * and a signed-in person.
253
+ */
254
+ readonly expectation: (typeName: string) => string;
255
+ };
package/dist/client.js CHANGED
@@ -11,12 +11,41 @@
11
11
  * types are.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.isWorkflowStatus = exports.isWorkflowNodeKind = exports.isWorkflowExecutionMode = exports.workflowRunOrder = exports.workflowGraphHash = exports.WORKFLOW_STATUSES = exports.WORKFLOW_NODE_KINDS = exports.WORKFLOW_NODE_ID_PATTERN = exports.WORKFLOW_ISSUE_CODES = exports.WORKFLOW_EXECUTION_MODES = exports.validateWorkflow = exports.TRANSFORM_LANGUAGES = exports.isWorkflowNode = exports.isWorkflowEdge = exports.isTransformLanguage = exports.isConnectorKind = exports.CONNECTOR_KINDS = exports.catalogRoutes = exports.CATALOG_REVISION_LIMIT = void 0;
14
+ exports.DELETE_RECONCILIATION_STRATEGIES = exports.isWorkflowStatus = exports.isWorkflowNodeKind = exports.isWorkflowExecutionMode = exports.workflowRunOrder = exports.workflowGraphHash = exports.WORKFLOW_STATUSES = exports.WORKFLOW_NODE_KINDS = exports.WORKFLOW_NODE_ID_PATTERN = exports.WORKFLOW_ISSUE_CODES = exports.WORKFLOW_EXECUTION_MODES = exports.validateWorkflow = exports.TRANSFORM_LANGUAGES = exports.isWorkflowNode = exports.isWorkflowEdge = exports.isTransformLanguage = exports.isConnectorKind = exports.CONNECTOR_KINDS = exports.catalogRoutes = exports.VALUELESS_FILTER_OPERATORS = exports.resolveObjectFilters = exports.parseObjectFilter = exports.offeredFilterOperators = exports.isCatalogFilterOperator = exports.filterOperatorsFor = exports.filterOperatorTakesValue = exports.encodeObjectFilter = exports.coerceFilterValue = exports.CATALOG_FILTER_OPERATORS = exports.CATALOG_FILTER_LIMIT = exports.CATALOG_REVISION_LIMIT = void 0;
15
+ exports.isDeleteReconciliationStrategy = isDeleteReconciliationStrategy;
16
+ exports.pipelineExpectationRoutes = pipelineExpectationRoutes;
15
17
  // A value, not a type: a screen saying how far back the history goes should read
16
18
  // the number rather than print one of its own. See its docblock for what the cap
17
19
  // costs.
18
20
  var catalog_workspace_1 = require("./catalog.workspace");
19
21
  Object.defineProperty(exports, "CATALOG_REVISION_LIMIT", { enumerable: true, get: function () { return catalog_workspace_1.CATALOG_REVISION_LIMIT; } });
22
+ /**
23
+ * The filter rule, shipped to the browser deliberately — the same exception, for
24
+ * the same reason, that `validateWorkflow` further down is.
25
+ *
26
+ * A console has to know which control to draw for a column, and the only way for
27
+ * that answer to match what the server will accept is for both to run this
28
+ * function. A screen with its own copy of the rules eventually lies: it offers a
29
+ * control the read refuses, or omits one that would have worked, and on types
30
+ * that are created at runtime nobody notices until a publisher adds a column. The
31
+ * functions are pure and import nothing.
32
+ *
33
+ * `SnapshotRef` rides along because a snapshot picker is a browser screen and
34
+ * `GET objects/:name/snapshots` is what fills it. The endpoints alone are not an
35
+ * API; the endpoints plus the response types are.
36
+ */
37
+ var catalog_filters_1 = require("./catalog.filters");
38
+ Object.defineProperty(exports, "CATALOG_FILTER_LIMIT", { enumerable: true, get: function () { return catalog_filters_1.CATALOG_FILTER_LIMIT; } });
39
+ Object.defineProperty(exports, "CATALOG_FILTER_OPERATORS", { enumerable: true, get: function () { return catalog_filters_1.CATALOG_FILTER_OPERATORS; } });
40
+ Object.defineProperty(exports, "coerceFilterValue", { enumerable: true, get: function () { return catalog_filters_1.coerceFilterValue; } });
41
+ Object.defineProperty(exports, "encodeObjectFilter", { enumerable: true, get: function () { return catalog_filters_1.encodeObjectFilter; } });
42
+ Object.defineProperty(exports, "filterOperatorTakesValue", { enumerable: true, get: function () { return catalog_filters_1.filterOperatorTakesValue; } });
43
+ Object.defineProperty(exports, "filterOperatorsFor", { enumerable: true, get: function () { return catalog_filters_1.filterOperatorsFor; } });
44
+ Object.defineProperty(exports, "isCatalogFilterOperator", { enumerable: true, get: function () { return catalog_filters_1.isCatalogFilterOperator; } });
45
+ Object.defineProperty(exports, "offeredFilterOperators", { enumerable: true, get: function () { return catalog_filters_1.offeredFilterOperators; } });
46
+ Object.defineProperty(exports, "parseObjectFilter", { enumerable: true, get: function () { return catalog_filters_1.parseObjectFilter; } });
47
+ Object.defineProperty(exports, "resolveObjectFilters", { enumerable: true, get: function () { return catalog_filters_1.resolveObjectFilters; } });
48
+ Object.defineProperty(exports, "VALUELESS_FILTER_OPERATORS", { enumerable: true, get: function () { return catalog_filters_1.VALUELESS_FILTER_OPERATORS; } });
20
49
  /**
21
50
  * Builds the paths the catalog controller serves, relative to wherever it was
22
51
  * mounted. Kept as string builders rather than a fetch wrapper so the host
@@ -113,3 +142,56 @@ Object.defineProperty(exports, "workflowRunOrder", { enumerable: true, get: func
113
142
  Object.defineProperty(exports, "isWorkflowExecutionMode", { enumerable: true, get: function () { return catalog_pipeline_2.isWorkflowExecutionMode; } });
114
143
  Object.defineProperty(exports, "isWorkflowNodeKind", { enumerable: true, get: function () { return catalog_pipeline_2.isWorkflowNodeKind; } });
115
144
  Object.defineProperty(exports, "isWorkflowStatus", { enumerable: true, get: function () { return catalog_pipeline_2.isWorkflowStatus; } });
145
+ /**
146
+ * The three answers to "how do deletions at the source reach this type", as a
147
+ * value.
148
+ *
149
+ * A list rather than only the union, because the editor on the Model screen has
150
+ * to offer them and a hand-written array in a component is the copy that drifts
151
+ * — the same argument {@link CONNECTOR_KINDS} makes one screen over. The server
152
+ * validates against this too: a `strategy` off the wire is `string` until
153
+ * something checks it, and the check and the dropdown reading one list is what
154
+ * stops the two from disagreeing about what is acceptable.
155
+ *
156
+ * **There are three and there is deliberately no fourth.** Tombstones off a
157
+ * change feed is the correct answer and needs machinery nothing here has; a
158
+ * strategy name that nothing implements is a dropdown with a lie in it. See
159
+ * `DeleteReconciliation` for the whole argument.
160
+ */
161
+ exports.DELETE_RECONCILIATION_STRATEGIES = [
162
+ 'accepted',
163
+ 'soft-deleted-at-source',
164
+ 'periodic-full-reload',
165
+ ];
166
+ /** Whether a value off the wire names one of the three. */
167
+ function isDeleteReconciliationStrategy(value) {
168
+ return (typeof value === 'string' &&
169
+ exports.DELETE_RECONCILIATION_STRATEGIES.includes(value));
170
+ }
171
+ /**
172
+ * Where the per-type expectations sit, relative to wherever the pipeline
173
+ * controller was mounted.
174
+ *
175
+ * A function of the base path rather than a frozen object like
176
+ * {@link catalogRoutes}, and the difference is the one `routes.ts` in the React
177
+ * package draws: the catalog controller's paths cannot move, and these move with
178
+ * whatever `path` the host passed to `CatalogPipelineModule.forRoot` — the
179
+ * routes below are `<path>/pipeline/expectations`, and `/pipeline` is only the
180
+ * default.
181
+ *
182
+ * Two builders for four routes: `PUT` and `DELETE` address the same path a `GET`
183
+ * of one type does, and a builder per verb would be three names for one string.
184
+ */
185
+ function pipelineExpectationRoutes(basePath = '/pipeline') {
186
+ const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
187
+ return {
188
+ /** Every stored row, plus the types the host has locked. `GET`, `catalog:read`. */
189
+ expectations: () => `${base}/expectations`,
190
+ /**
191
+ * One type: `GET` for the resolved expectation and its provenance, `PUT` to
192
+ * set the stored row, `DELETE` to drop it. The writes need `catalog:curate`
193
+ * and a signed-in person.
194
+ */
195
+ expectation: (typeName) => `${base}/expectations/${encodeURIComponent(typeName)}`,
196
+ };
197
+ }
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export { type CatalogOverlayStore, FileCatalogOverlayStore, InMemoryCatalogOverl
8
8
  export { CATALOG_OVERLAY_STORE } from './catalog.overlay-store.token';
9
9
  export { MikroOrmCatalogRegistry } from './catalog.registry';
10
10
  export { CatalogRegistry } from './catalog.registry.base';
11
- export { CATALOG_PIPELINE_STORE, type CatalogConnection, type CatalogConnector, type ConnectionCheck, CONNECTOR_KINDS, type CatalogPipelineStore, type CatalogStageStore, type CatalogTransform, type CatalogWorkflow, type CatalogWorkflowCapabilities, type CatalogWorkflowStore, type ConnectorKind, type ConnectorRun, isConnectorKind, isPipelineStore, isTransformLanguage, supportsTransformRevisions, isWorkflowEdge, isWorkflowExecutionMode, isWorkflowNode, isWorkflowNodeKind, isWorkflowStatus, supportsWorkflows, supportsWorkflowStages, TRANSFORM_RUNNER, TRANSFORM_LANGUAGES, type TransformLanguage, type TransformResult, type TransformRunner, validateWorkflow, WORKFLOW_EXECUTION_MODES, WORKFLOW_ISSUE_CODES, WORKFLOW_NODE_ID_PATTERN, WORKFLOW_NODE_KINDS, WORKFLOW_STATUSES, type WorkflowEdge, type WorkflowExecutionMode, type WorkflowGraph, workflowGraphHash, type WorkflowIssueCode, type WorkflowNode, type WorkflowNodeKind, type WorkflowNodeOutcome, type WorkflowNodeStepInput, type WorkflowNodeStepOutput, workflowRunOrder, type WorkflowSinkNode, type WorkflowSourceNode, type WorkflowStageRef, type WorkflowStatus, type WorkflowTransformNode, type WorkflowValidationIssue, } from './catalog.pipeline';
11
+ export { CATALOG_PIPELINE_STORE, type CatalogConnection, type CatalogConnector, type ConnectionCheck, CONNECTOR_KINDS, type CatalogLoadExpectations, type CatalogLoadExpectationStore, type CatalogPipelineStore, type CatalogStageStore, type CatalogTransform, type CatalogWorkflow, type CatalogWorkflowCapabilities, type CatalogWorkflowStore, type ConnectorKind, type ConnectorRun, type DeleteReconciliation, isConnectorKind, isPipelineStore, isTransformLanguage, type LoadExpectation, type RowCountBound, type StoredLoadExpectation, supportsLoadExpectations, supportsTransformRevisions, isWorkflowEdge, isWorkflowExecutionMode, isWorkflowNode, isWorkflowNodeKind, isWorkflowStatus, supportsWorkflows, supportsWorkflowStages, TRANSFORM_RUNNER, TRANSFORM_LANGUAGES, type TransformLanguage, type TransformResult, type TransformRunner, validateWorkflow, WORKFLOW_EXECUTION_MODES, WORKFLOW_ISSUE_CODES, WORKFLOW_NODE_ID_PATTERN, WORKFLOW_NODE_KINDS, WORKFLOW_STATUSES, type WorkflowEdge, type WorkflowExecutionMode, type WorkflowGraph, workflowGraphHash, type WorkflowIssueCode, type WorkflowNode, type WorkflowNodeKind, type WorkflowNodeOutcome, type WorkflowNodeStepInput, type WorkflowNodeStepOutput, workflowRunOrder, type WorkflowSinkNode, type WorkflowSourceNode, type WorkflowStageRef, type WorkflowStatus, type WorkflowTransformNode, type WorkflowValidationIssue, } from './catalog.pipeline';
12
12
  export * from './catalog.environment';
13
13
  export { QueryCache, toCsv } from './catalog.query-cache';
14
14
  export { SubprocessTransformRunner, type TransformRunnerOptions, } from './transform-runner';
@@ -18,7 +18,8 @@ export type { CatalogSearchField, CatalogSearchHit, CatalogSearchKind, CatalogSe
18
18
  export { type AuditQuery, CATALOG_REVISION_LIMIT, CATALOG_TRACE_OUTCOMES, CATALOG_TRACE_STORE, CATALOG_WORKSPACE_STORE, type CatalogAuditEvent, type CatalogRevision, 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, supportsSavedQueryRevisions, type TraceQuery, traceOutcomeFilter, } from './catalog.workspace';
19
19
  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';
20
20
  export * from './catalog.access';
21
- export { assertNoColumnCollisions, CATALOG_RESERVED_COLUMNS, CATALOG_SNAPSHOT_MODES, CATALOG_STORE, type CarryForwardResult, type CatalogColumnCollision, CatalogColumnCollisionError, type CatalogMergeStore, type CatalogReadQuery, type CatalogReadResult, type CatalogReadStore, type CatalogReservedColumn, type CatalogSnapshotMode, type CatalogStoreCapabilities, type CatalogWriteStore, type ColumnCollisionOptions, findColumnCollisions, isCatalogStoreCapabilities, isReservedColumn, isWriteStore, type SnapshotRef, supportsCarryForward, } from './catalog.store';
21
+ export * from './catalog.filters';
22
+ export { assertNoColumnCollisions, assertSafeIdentifier, CATALOG_RESERVED_COLUMNS, CATALOG_SNAPSHOT_MODES, CATALOG_STORE, type CarryForwardResult, type CatalogColumnCollision, CatalogColumnCollisionError, type CatalogFilteringReadStore, supportsObjectFilters, type CatalogMergeStore, type CatalogReadQuery, type CatalogReadResult, type CatalogReadStore, type CatalogReservedColumn, type CatalogSnapshotMode, type CatalogStoreCapabilities, type CatalogWriteStore, type ColumnCollisionOptions, findColumnCollisions, isCatalogStoreCapabilities, isReservedColumn, isSafeIdentifier, isWriteStore, type SnapshotRef, supportsCarryForward, UnsafeIdentifierError, } from './catalog.store';
22
23
  export { MikroOrmReadStore } from './stores/mikro-orm-read.store';
23
24
  export type { CatalogGraph, CatalogObjectPage, CatalogObjectQuery, CatalogObjectTypeDef, CatalogOverlay, CatalogPropertyDef, CatalogRelationDef, CatalogSnapshot, RelationKind, ScalarType, } from './catalog.types';
24
25
  export { REQUIRED_SCOPES, REQUIRES_HUMAN, RequireHuman, RequireScopes, } from './catalog.route-auth';
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.bestMatch = exports.MAX_SEARCH_LIMIT = exports.DEFAULT_SEARCH_LIMIT = exports.CatalogService = exports.SubprocessTransformRunner = exports.toCsv = exports.QueryCache = exports.workflowRunOrder = exports.workflowGraphHash = exports.WORKFLOW_STATUSES = 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.isWorkflowStatus = exports.isWorkflowNodeKind = exports.isWorkflowNode = exports.isWorkflowExecutionMode = exports.isWorkflowEdge = exports.supportsTransformRevisions = 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.curationActor = exports.channelNameFor = exports.catalogEventPhase = exports.UNATTRIBUTED_PRINCIPAL_ID = 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.supportsSavedQueryRevisions = exports.isWorkspaceStore = exports.isTraceStore = exports.isCatalogTraceOutcome = exports.embeddedVisualization = exports.CATALOG_WORKSPACE_STORE = exports.CATALOG_TRACE_STORE = exports.CATALOG_TRACE_OUTCOMES = exports.CATALOG_REVISION_LIMIT = exports.visibleToPrincipal = exports.searchCatalog = exports.maySearch = exports.emptySearch = void 0;
17
+ exports.MAX_SEARCH_LIMIT = exports.DEFAULT_SEARCH_LIMIT = exports.CatalogService = exports.SubprocessTransformRunner = exports.toCsv = exports.QueryCache = exports.workflowRunOrder = exports.workflowGraphHash = exports.WORKFLOW_STATUSES = 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.isWorkflowStatus = exports.isWorkflowNodeKind = exports.isWorkflowNode = exports.isWorkflowExecutionMode = exports.isWorkflowEdge = exports.supportsTransformRevisions = exports.supportsLoadExpectations = 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.curationActor = exports.channelNameFor = exports.catalogEventPhase = exports.UNATTRIBUTED_PRINCIPAL_ID = 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.UnsafeIdentifierError = exports.supportsCarryForward = exports.isWriteStore = exports.isSafeIdentifier = exports.isReservedColumn = exports.isCatalogStoreCapabilities = exports.findColumnCollisions = exports.supportsObjectFilters = exports.CatalogColumnCollisionError = exports.CATALOG_STORE = exports.CATALOG_SNAPSHOT_MODES = exports.CATALOG_RESERVED_COLUMNS = exports.assertSafeIdentifier = 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.supportsSavedQueryRevisions = exports.isWorkspaceStore = exports.isTraceStore = exports.isCatalogTraceOutcome = exports.embeddedVisualization = exports.CATALOG_WORKSPACE_STORE = exports.CATALOG_TRACE_STORE = exports.CATALOG_TRACE_OUTCOMES = exports.CATALOG_REVISION_LIMIT = exports.visibleToPrincipal = exports.searchCatalog = exports.maySearch = exports.emptySearch = exports.bestMatch = 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; } });
@@ -59,6 +59,7 @@ Object.defineProperty(exports, "CONNECTOR_KINDS", { enumerable: true, get: funct
59
59
  Object.defineProperty(exports, "isConnectorKind", { enumerable: true, get: function () { return catalog_pipeline_1.isConnectorKind; } });
60
60
  Object.defineProperty(exports, "isPipelineStore", { enumerable: true, get: function () { return catalog_pipeline_1.isPipelineStore; } });
61
61
  Object.defineProperty(exports, "isTransformLanguage", { enumerable: true, get: function () { return catalog_pipeline_1.isTransformLanguage; } });
62
+ Object.defineProperty(exports, "supportsLoadExpectations", { enumerable: true, get: function () { return catalog_pipeline_1.supportsLoadExpectations; } });
62
63
  Object.defineProperty(exports, "supportsTransformRevisions", { enumerable: true, get: function () { return catalog_pipeline_1.supportsTransformRevisions; } });
63
64
  Object.defineProperty(exports, "isWorkflowEdge", { enumerable: true, get: function () { return catalog_pipeline_1.isWorkflowEdge; } });
64
65
  Object.defineProperty(exports, "isWorkflowExecutionMode", { enumerable: true, get: function () { return catalog_pipeline_1.isWorkflowExecutionMode; } });
@@ -131,17 +132,28 @@ Object.defineProperty(exports, "StaticKeyPrincipalResolver", { enumerable: true,
131
132
  // implemented only by a host willing to restate them — which nothing in this
132
133
  // repo would have caught: no consumer compiles against the built barrel here.
133
134
  __exportStar(require("./catalog.access"), exports);
135
+ // The filter rule, whole. A store implementing `CatalogFilteringReadStore` needs
136
+ // the operator list to declare what it applies and `CatalogResolvedFilter` to
137
+ // read what it was handed, and a host writing its own objects route needs
138
+ // `resolveObjectFilters` — shipping the interface without them would be the same
139
+ // unimplementable seam the barrel spec above was written after. It is also on
140
+ // `/client`, because the console derives its controls from the same function.
141
+ __exportStar(require("./catalog.filters"), exports);
134
142
  var catalog_store_1 = require("./catalog.store");
135
143
  Object.defineProperty(exports, "assertNoColumnCollisions", { enumerable: true, get: function () { return catalog_store_1.assertNoColumnCollisions; } });
144
+ Object.defineProperty(exports, "assertSafeIdentifier", { enumerable: true, get: function () { return catalog_store_1.assertSafeIdentifier; } });
136
145
  Object.defineProperty(exports, "CATALOG_RESERVED_COLUMNS", { enumerable: true, get: function () { return catalog_store_1.CATALOG_RESERVED_COLUMNS; } });
137
146
  Object.defineProperty(exports, "CATALOG_SNAPSHOT_MODES", { enumerable: true, get: function () { return catalog_store_1.CATALOG_SNAPSHOT_MODES; } });
138
147
  Object.defineProperty(exports, "CATALOG_STORE", { enumerable: true, get: function () { return catalog_store_1.CATALOG_STORE; } });
139
148
  Object.defineProperty(exports, "CatalogColumnCollisionError", { enumerable: true, get: function () { return catalog_store_1.CatalogColumnCollisionError; } });
149
+ Object.defineProperty(exports, "supportsObjectFilters", { enumerable: true, get: function () { return catalog_store_1.supportsObjectFilters; } });
140
150
  Object.defineProperty(exports, "findColumnCollisions", { enumerable: true, get: function () { return catalog_store_1.findColumnCollisions; } });
141
151
  Object.defineProperty(exports, "isCatalogStoreCapabilities", { enumerable: true, get: function () { return catalog_store_1.isCatalogStoreCapabilities; } });
142
152
  Object.defineProperty(exports, "isReservedColumn", { enumerable: true, get: function () { return catalog_store_1.isReservedColumn; } });
153
+ Object.defineProperty(exports, "isSafeIdentifier", { enumerable: true, get: function () { return catalog_store_1.isSafeIdentifier; } });
143
154
  Object.defineProperty(exports, "isWriteStore", { enumerable: true, get: function () { return catalog_store_1.isWriteStore; } });
144
155
  Object.defineProperty(exports, "supportsCarryForward", { enumerable: true, get: function () { return catalog_store_1.supportsCarryForward; } });
156
+ Object.defineProperty(exports, "UnsafeIdentifierError", { enumerable: true, get: function () { return catalog_store_1.UnsafeIdentifierError; } });
145
157
  var mikro_orm_read_store_1 = require("./stores/mikro-orm-read.store");
146
158
  Object.defineProperty(exports, "MikroOrmReadStore", { enumerable: true, get: function () { return mikro_orm_read_store_1.MikroOrmReadStore; } });
147
159
  var catalog_route_auth_1 = require("./catalog.route-auth");
@@ -1,6 +1,6 @@
1
1
  import { EntityManager } from '@mikro-orm/core';
2
2
  import { MikroOrmCatalogRegistry } from '../catalog.registry';
3
- import type { CatalogReadQuery, CatalogReadResult, CatalogReadStore, CatalogStoreCapabilities } from '../catalog.store';
3
+ import type { CatalogFilteringReadStore, CatalogReadQuery, CatalogReadResult, CatalogStoreCapabilities } from '../catalog.store';
4
4
  import type { CatalogObjectTypeDef } from '../catalog.types';
5
5
  /**
6
6
  * Reads objects straight out of the application's own tables, through the ORM
@@ -11,10 +11,16 @@ import type { CatalogObjectTypeDef } from '../catalog.types';
11
11
  * trade is that there is also no history — the tables hold current state, and
12
12
  * nothing here can show you last Tuesday.
13
13
  */
14
- export declare class MikroOrmReadStore implements CatalogReadStore {
14
+ export declare class MikroOrmReadStore implements CatalogFilteringReadStore {
15
15
  private readonly registry;
16
16
  private readonly em;
17
17
  readonly capabilities: CatalogStoreCapabilities;
18
+ /**
19
+ * All of them: every operator maps onto a MikroORM query-builder operator, and
20
+ * the ORM writes the column name from the entity metadata rather than from
21
+ * anything a caller sent.
22
+ */
23
+ readonly objectFilterOperators: readonly ["eq", "ne", "contains", "gte", "lte", "gt", "lt", "empty", "notEmpty"];
18
24
  constructor(registry: MikroOrmCatalogRegistry, em: EntityManager);
19
25
  read(type: CatalogObjectTypeDef, fields: string[], query: CatalogReadQuery): Promise<CatalogReadResult>;
20
26
  }
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.MikroOrmReadStore = void 0;
13
13
  const core_1 = require("@mikro-orm/core");
14
14
  const common_1 = require("@nestjs/common");
15
+ const catalog_filters_1 = require("../catalog.filters");
15
16
  const catalog_registry_1 = require("../catalog.registry");
16
17
  /**
17
18
  * Reads objects straight out of the application's own tables, through the ORM
@@ -30,6 +31,12 @@ let MikroOrmReadStore = class MikroOrmReadStore {
30
31
  writable: false,
31
32
  timeTravel: false,
32
33
  };
34
+ /**
35
+ * All of them: every operator maps onto a MikroORM query-builder operator, and
36
+ * the ORM writes the column name from the entity metadata rather than from
37
+ * anything a caller sent.
38
+ */
39
+ objectFilterOperators = catalog_filters_1.CATALOG_FILTER_OPERATORS;
33
40
  constructor(registry, em) {
34
41
  this.registry = registry;
35
42
  this.em = em;
@@ -48,7 +55,7 @@ let MikroOrmReadStore = class MikroOrmReadStore {
48
55
  // No explicit type arguments: `findAndCount` declares `Fields extends string
49
56
  // = never`, so naming even one generic makes the rest fall back to their
50
57
  // defaults and types `fields` as `never[]`. Inference gets it right.
51
- const [rows, total] = await em.findAndCount(entityClass, buildWhere(type, query.search), {
58
+ const [rows, total] = await em.findAndCount(entityClass, buildWhere(type, query), {
52
59
  limit: size,
53
60
  offset: (page - 1) * size,
54
61
  orderBy: buildOrderBy(type, query.sort, query.dir),
@@ -67,19 +74,78 @@ exports.MikroOrmReadStore = MikroOrmReadStore = __decorate([
67
74
  core_1.EntityManager])
68
75
  ], MikroOrmReadStore);
69
76
  /**
70
- * Only string columns the catalog says are visible.
77
+ * The search term and the column filters, ANDed.
71
78
  *
72
- * A search that reached a classified column would leak it through row
73
- * membership even though the value is never rendered.
79
+ * Search reaches only string columns the catalog says are visible: a search that
80
+ * reached a classified column would leak it through row membership even though
81
+ * the value is never rendered. `filterOperatorsFor` refuses a classified column
82
+ * for the same reason and a sharper one — a range filter lets a reader
83
+ * binary-search a value they may not see.
84
+ *
85
+ * The filters are ANDed with each other and with the search, which is what makes
86
+ * a filter narrowing: two conditions on one column express a range, and a caller
87
+ * that wanted alternatives has `contains` or a second request.
74
88
  */
75
- function buildWhere(type, search) {
76
- const term = search?.trim();
77
- if (!term)
78
- return {};
79
- const searchable = type.properties.filter((p) => !p.hidden && p.type === 'string' && !p.classification);
80
- if (searchable.length === 0)
89
+ function buildWhere(type, query) {
90
+ const conditions = [];
91
+ const term = query.search?.trim();
92
+ if (term) {
93
+ const searchable = type.properties.filter((p) => !p.hidden && p.type === 'string' && !p.classification);
94
+ if (searchable.length > 0) {
95
+ conditions.push({ $or: searchable.map((p) => ({ [p.name]: { $like: `%${term}%` } })) });
96
+ }
97
+ }
98
+ for (const filter of query.filters ?? []) {
99
+ conditions.push({ [filter.property.name]: comparison(filter) });
100
+ }
101
+ if (conditions.length === 0)
81
102
  return {};
82
- return { $or: searchable.map((p) => ({ [p.name]: { $like: `%${term}%` } })) };
103
+ if (conditions.length === 1)
104
+ return conditions[0];
105
+ return { $and: conditions };
106
+ }
107
+ /**
108
+ * One operator as MikroORM spells it.
109
+ *
110
+ * The property name is the ORM's own — it came off the type, which was built
111
+ * from the entity metadata — so the column in the emitted SQL is written by the
112
+ * ORM from that metadata and never by string concatenation here. That is the same
113
+ * guarantee the sort above relies on.
114
+ */
115
+ function comparison(filter) {
116
+ const value = filter.value;
117
+ switch (filter.op) {
118
+ case 'eq':
119
+ return { $eq: value };
120
+ case 'ne':
121
+ // `!=` in SQL is never true of NULL, so a row whose column is empty would
122
+ // drop out of "is not X" — which reads as those rows having the value.
123
+ return { $or: [{ $ne: value }, { $eq: null }] };
124
+ case 'contains':
125
+ return { $like: `%${String(value)}%` };
126
+ case 'gt':
127
+ return { $gt: value };
128
+ case 'gte':
129
+ return { $gte: value };
130
+ case 'lt':
131
+ return { $lt: value };
132
+ case 'lte':
133
+ return { $lte: value };
134
+ case 'empty':
135
+ // A blank string is empty to a reader, and only a text column can hold
136
+ // one. Both spellings, so "no value" means what it says on either.
137
+ return { $or: [{ $eq: null }, { $eq: '' }] };
138
+ case 'notEmpty':
139
+ return { $and: [{ $ne: null }, { $ne: '' }] };
140
+ default:
141
+ // No operator falls through to a silent `{}`, which would be a filter
142
+ // that matches everything. An operator added to the contract and not to
143
+ // this switch fails to compile here rather than at read time.
144
+ return unknownOperator(filter.op);
145
+ }
146
+ }
147
+ function unknownOperator(operator) {
148
+ throw new common_1.BadRequestException(`This store cannot filter with ${String(operator)}.`);
83
149
  }
84
150
  /** Only ever a column the catalog vouched for; falls back to the key. */
85
151
  function buildOrderBy(type, sort, dir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dudousxd/nestjs-catalog",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "A metadata registry for NestJS: object types, properties and relations, derived from your ORM and enriched with decorators.",
5
5
  "license": "MIT",
6
6
  "author": "Davide Carvalho",