@dudousxd/nestjs-catalog 0.1.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/LICENSE +21 -0
- package/README.md +123 -0
- package/dist/catalog.controller.d.ts +8 -0
- package/dist/catalog.controller.js +482 -0
- package/dist/catalog.decorators.d.ts +37 -0
- package/dist/catalog.decorators.js +50 -0
- package/dist/catalog.environment.d.ts +442 -0
- package/dist/catalog.environment.js +645 -0
- package/dist/catalog.events.d.ts +179 -0
- package/dist/catalog.events.js +110 -0
- package/dist/catalog.module.d.ts +5 -0
- package/dist/catalog.module.js +71 -0
- package/dist/catalog.options.d.ts +79 -0
- package/dist/catalog.options.js +4 -0
- package/dist/catalog.overlay-store.d.ts +25 -0
- package/dist/catalog.overlay-store.js +44 -0
- package/dist/catalog.overlay-store.token.d.ts +1 -0
- package/dist/catalog.overlay-store.token.js +4 -0
- package/dist/catalog.pipeline.d.ts +800 -0
- package/dist/catalog.pipeline.js +606 -0
- package/dist/catalog.principal.d.ts +209 -0
- package/dist/catalog.principal.js +245 -0
- package/dist/catalog.query-cache.d.ts +25 -0
- package/dist/catalog.query-cache.js +0 -0
- package/dist/catalog.query.d.ts +76 -0
- package/dist/catalog.query.js +64 -0
- package/dist/catalog.registry.base.d.ts +21 -0
- package/dist/catalog.registry.base.js +17 -0
- package/dist/catalog.registry.d.ts +44 -0
- package/dist/catalog.registry.js +359 -0
- package/dist/catalog.service.d.ts +115 -0
- package/dist/catalog.service.js +366 -0
- package/dist/catalog.store.d.ts +419 -0
- package/dist/catalog.store.js +175 -0
- package/dist/catalog.types.d.ts +165 -0
- package/dist/catalog.types.js +19 -0
- package/dist/catalog.workspace.d.ts +426 -0
- package/dist/catalog.workspace.js +87 -0
- package/dist/client.d.ts +86 -0
- package/dist/client.js +83 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +109 -0
- package/dist/stores/mikro-orm-read.store.d.ts +20 -0
- package/dist/stores/mikro-orm-read.store.js +120 -0
- package/dist/transform-runner.d.ts +54 -0
- package/dist/transform-runner.js +280 -0
- package/package.json +54 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What this package publishes on `aviary:catalog:*`.
|
|
3
|
+
*
|
|
4
|
+
* Diagnostics rather than a logger, and certainly rather than a direct
|
|
5
|
+
* Telescope dependency: the channel is the neutral seam the ecosystem is built
|
|
6
|
+
* on, so a host that wants these in Telescope installs
|
|
7
|
+
* `@dudousxd/nestjs-catalog-telescope`, one that wants them in its own tracing
|
|
8
|
+
* subscribes directly, and one that wants neither pays nothing — `emit` builds
|
|
9
|
+
* no envelope unless the channel has subscribers.
|
|
10
|
+
*
|
|
11
|
+
* These events are also the lineage feed. Since the catalog deliberately has no
|
|
12
|
+
* dataset history to walk (see the Iceberg decision), the record of who loaded
|
|
13
|
+
* what and when only exists because it was emitted as it happened.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CATALOG_LIB = "catalog";
|
|
16
|
+
/** Every event name this package emits. Exported so a watcher can claim them. */
|
|
17
|
+
export declare const CATALOG_EVENTS: readonly ["schema.changed", "snapshot.written", "snapshot.committed", "snapshot.dropped", "type.curated", "connector.run.started", "connector.run.finished", "transform.changed", "workflow.changed"];
|
|
18
|
+
export type CatalogEvent = (typeof CATALOG_EVENTS)[number];
|
|
19
|
+
/**
|
|
20
|
+
* Where each event sits in the life of one load.
|
|
21
|
+
*
|
|
22
|
+
* This exists because a timestamp is not enough to order a trace. A fast load
|
|
23
|
+
* emits its whole story inside a single tick of whatever clock the recorder
|
|
24
|
+
* writes — the bundled MySQL store keeps whole seconds — so sorting by time
|
|
25
|
+
* alone leaves the events in insertion order, and insertion order is arbitrary.
|
|
26
|
+
* The observed result is a trace that reads `finished → written → committed →
|
|
27
|
+
* started`: causally impossible, rendered with total confidence. Sorting by
|
|
28
|
+
* time *and then* by this rank puts a same-tick story back in the only order it
|
|
29
|
+
* could have happened in, and leaves genuinely separated events alone.
|
|
30
|
+
*
|
|
31
|
+
* It is a rank, not a schedule. Nothing here says an event must occur, or must
|
|
32
|
+
* occur once — a load writes many batches, and a retried run emits a second
|
|
33
|
+
* `connector.run.started` — it only says which came first when the clock cannot.
|
|
34
|
+
*/
|
|
35
|
+
export declare const CATALOG_EVENT_PHASE: Record<CatalogEvent, number>;
|
|
36
|
+
/**
|
|
37
|
+
* Where an event nobody here declared is placed: in the middle, with the work.
|
|
38
|
+
*
|
|
39
|
+
* Not at either end deliberately. An unknown event sorted last would be read as
|
|
40
|
+
* the thing that ended the trace, and one sorted first as the thing that caused
|
|
41
|
+
* it — both are claims this library has no basis to make about a name it does
|
|
42
|
+
* not recognise.
|
|
43
|
+
*/
|
|
44
|
+
export declare const CATALOG_EVENT_PHASE_FALLBACK = 4;
|
|
45
|
+
export declare function catalogEventPhase(event: string): number;
|
|
46
|
+
export interface CatalogEventPayloads {
|
|
47
|
+
/** DDL was applied to an object type's physical table. Always additive. */
|
|
48
|
+
'schema.changed': {
|
|
49
|
+
typeName: string;
|
|
50
|
+
table: string;
|
|
51
|
+
addedColumns: string[];
|
|
52
|
+
created: boolean;
|
|
53
|
+
};
|
|
54
|
+
/** A batch landed. Fires per batch, so a large load emits many. */
|
|
55
|
+
'snapshot.written': {
|
|
56
|
+
typeName: string;
|
|
57
|
+
snapshotId: string;
|
|
58
|
+
principalId: string;
|
|
59
|
+
rows: number;
|
|
60
|
+
};
|
|
61
|
+
/** A load became the one readers get. */
|
|
62
|
+
'snapshot.committed': {
|
|
63
|
+
typeName: string;
|
|
64
|
+
snapshotId: string;
|
|
65
|
+
principalId: string;
|
|
66
|
+
rowCount: number;
|
|
67
|
+
};
|
|
68
|
+
'snapshot.dropped': {
|
|
69
|
+
typeName: string;
|
|
70
|
+
snapshotId: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Someone changed a label, description, unit or visibility.
|
|
74
|
+
*
|
|
75
|
+
* Presentation-only, and emitted anyway: "who renamed this column and when"
|
|
76
|
+
* is a governance question, and the answer is otherwise nowhere.
|
|
77
|
+
*/
|
|
78
|
+
'type.curated': {
|
|
79
|
+
typeName: string;
|
|
80
|
+
property?: string;
|
|
81
|
+
changed: string[];
|
|
82
|
+
};
|
|
83
|
+
/** A connector began pulling. */
|
|
84
|
+
'connector.run.started': {
|
|
85
|
+
connectorId: string;
|
|
86
|
+
connectorName: string;
|
|
87
|
+
typeName: string;
|
|
88
|
+
snapshotId: string;
|
|
89
|
+
principalId: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* It finished, either way.
|
|
93
|
+
*
|
|
94
|
+
* One event for both outcomes rather than two: a watcher that cares about
|
|
95
|
+
* failures has to subscribe to successes anyway to notice the ones that never
|
|
96
|
+
* finished.
|
|
97
|
+
*/
|
|
98
|
+
'connector.run.finished': {
|
|
99
|
+
connectorId: string;
|
|
100
|
+
connectorName: string;
|
|
101
|
+
typeName: string;
|
|
102
|
+
snapshotId: string;
|
|
103
|
+
principalId: string;
|
|
104
|
+
status: 'succeeded' | 'failed';
|
|
105
|
+
fetched: number;
|
|
106
|
+
written: number;
|
|
107
|
+
error?: string;
|
|
108
|
+
transformVersion?: number;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Someone changed code that shapes stored data.
|
|
112
|
+
*
|
|
113
|
+
* The one event on this channel that is not about data movement, and the most
|
|
114
|
+
* important for governance: a load that produced surprising numbers is
|
|
115
|
+
* investigated afterwards, and "who changed the transform, and when" is the
|
|
116
|
+
* first question.
|
|
117
|
+
*/
|
|
118
|
+
'transform.changed': {
|
|
119
|
+
transformId: string;
|
|
120
|
+
name: string;
|
|
121
|
+
language: string;
|
|
122
|
+
version: number;
|
|
123
|
+
changedBy: string;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Someone changed the wiring rather than the code.
|
|
127
|
+
*
|
|
128
|
+
* Emitted for the same governance reason as `transform.changed`, and it is
|
|
129
|
+
* genuinely a second question: a load can change behaviour because a
|
|
130
|
+
* transform was edited *or* because a node was rewired between two transforms
|
|
131
|
+
* that both stayed exactly as they were. The graph hash rides along because a
|
|
132
|
+
* version number only identifies a graph within one catalog database, and
|
|
133
|
+
* these events are read across environments.
|
|
134
|
+
*/
|
|
135
|
+
'workflow.changed': {
|
|
136
|
+
workflowId: string;
|
|
137
|
+
name: string;
|
|
138
|
+
version: number;
|
|
139
|
+
graphHash: string;
|
|
140
|
+
/** The type its sink writes, so a lineage view can place the edit. */
|
|
141
|
+
targetType: string;
|
|
142
|
+
nodeCount: number;
|
|
143
|
+
changedBy: string;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Teach the diagnostics channel what this library puts on it.
|
|
148
|
+
*
|
|
149
|
+
* Without this, `CatalogEventPayloads` only helps callers who go through
|
|
150
|
+
* `emitCatalog`. Anyone subscribing the way the ecosystem actually subscribes —
|
|
151
|
+
* generic `emit`/`trace`/watcher code that names the lib as a string — gets
|
|
152
|
+
* `unknown` and narrows by hand, which is where a payload field gets misspelled
|
|
153
|
+
* and nothing says so until the panel is blank in production.
|
|
154
|
+
*
|
|
155
|
+
* Pointed at the interface rather than restating its members. The sibling
|
|
156
|
+
* libraries hand-list every event here, which is a second copy of a map that
|
|
157
|
+
* already exists, and a second copy is the thing that stops matching. There is
|
|
158
|
+
* nothing to keep in step: this *is* the map.
|
|
159
|
+
*/
|
|
160
|
+
declare module '@dudousxd/nestjs-diagnostics' {
|
|
161
|
+
interface ChannelRegistry {
|
|
162
|
+
catalog: CatalogEventPayloads;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The channel an event is published on.
|
|
167
|
+
*
|
|
168
|
+
* Exported so a recorder or a watcher can subscribe without rebuilding the
|
|
169
|
+
* `aviary:<lib>:<event>` convention by hand — the one place that string is
|
|
170
|
+
* assembled is the one place it can drift.
|
|
171
|
+
*/
|
|
172
|
+
export declare function channelNameFor(event: CatalogEvent | string): string;
|
|
173
|
+
/**
|
|
174
|
+
* Typed wrapper over `emit`.
|
|
175
|
+
*
|
|
176
|
+
* Never throws — observability that can break a load is worse than no
|
|
177
|
+
* observability — which `emit` already guarantees; this exists for the types.
|
|
178
|
+
*/
|
|
179
|
+
export declare function emitCatalog<E extends CatalogEvent>(event: E, payload: CatalogEventPayloads[E]): void;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CATALOG_EVENT_PHASE_FALLBACK = exports.CATALOG_EVENT_PHASE = exports.CATALOG_EVENTS = exports.CATALOG_LIB = void 0;
|
|
4
|
+
exports.catalogEventPhase = catalogEventPhase;
|
|
5
|
+
exports.channelNameFor = channelNameFor;
|
|
6
|
+
exports.emitCatalog = emitCatalog;
|
|
7
|
+
const nestjs_diagnostics_1 = require("@dudousxd/nestjs-diagnostics");
|
|
8
|
+
/**
|
|
9
|
+
* What this package publishes on `aviary:catalog:*`.
|
|
10
|
+
*
|
|
11
|
+
* Diagnostics rather than a logger, and certainly rather than a direct
|
|
12
|
+
* Telescope dependency: the channel is the neutral seam the ecosystem is built
|
|
13
|
+
* on, so a host that wants these in Telescope installs
|
|
14
|
+
* `@dudousxd/nestjs-catalog-telescope`, one that wants them in its own tracing
|
|
15
|
+
* subscribes directly, and one that wants neither pays nothing — `emit` builds
|
|
16
|
+
* no envelope unless the channel has subscribers.
|
|
17
|
+
*
|
|
18
|
+
* These events are also the lineage feed. Since the catalog deliberately has no
|
|
19
|
+
* dataset history to walk (see the Iceberg decision), the record of who loaded
|
|
20
|
+
* what and when only exists because it was emitted as it happened.
|
|
21
|
+
*/
|
|
22
|
+
exports.CATALOG_LIB = 'catalog';
|
|
23
|
+
/** Every event name this package emits. Exported so a watcher can claim them. */
|
|
24
|
+
exports.CATALOG_EVENTS = [
|
|
25
|
+
'schema.changed',
|
|
26
|
+
'snapshot.written',
|
|
27
|
+
'snapshot.committed',
|
|
28
|
+
'snapshot.dropped',
|
|
29
|
+
'type.curated',
|
|
30
|
+
'connector.run.started',
|
|
31
|
+
'connector.run.finished',
|
|
32
|
+
'transform.changed',
|
|
33
|
+
'workflow.changed',
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Where each event sits in the life of one load.
|
|
37
|
+
*
|
|
38
|
+
* This exists because a timestamp is not enough to order a trace. A fast load
|
|
39
|
+
* emits its whole story inside a single tick of whatever clock the recorder
|
|
40
|
+
* writes — the bundled MySQL store keeps whole seconds — so sorting by time
|
|
41
|
+
* alone leaves the events in insertion order, and insertion order is arbitrary.
|
|
42
|
+
* The observed result is a trace that reads `finished → written → committed →
|
|
43
|
+
* started`: causally impossible, rendered with total confidence. Sorting by
|
|
44
|
+
* time *and then* by this rank puts a same-tick story back in the only order it
|
|
45
|
+
* could have happened in, and leaves genuinely separated events alone.
|
|
46
|
+
*
|
|
47
|
+
* It is a rank, not a schedule. Nothing here says an event must occur, or must
|
|
48
|
+
* occur once — a load writes many batches, and a retried run emits a second
|
|
49
|
+
* `connector.run.started` — it only says which came first when the clock cannot.
|
|
50
|
+
*/
|
|
51
|
+
exports.CATALOG_EVENT_PHASE = {
|
|
52
|
+
'connector.run.started': 0,
|
|
53
|
+
'transform.changed': 1,
|
|
54
|
+
// The same rank as the transform edit above, which is allowed and meant:
|
|
55
|
+
// these ranks order a same-tick trace, and editing a graph and editing the
|
|
56
|
+
// code inside it are the same kind of thing at the same point in the story —
|
|
57
|
+
// an authoring event that precedes whatever run is being investigated. Giving
|
|
58
|
+
// one an earlier rank than the other would assert a causal order between two
|
|
59
|
+
// edits that have none.
|
|
60
|
+
'workflow.changed': 1,
|
|
61
|
+
// Curation carries no snapshot id, so it never lands inside a trace and this
|
|
62
|
+
// rank is never consulted. It is written out rather than left to the fallback
|
|
63
|
+
// because the type demands every event have one, and a `Record` that has to
|
|
64
|
+
// be complete is exactly the mechanism that will fail the build the day a new
|
|
65
|
+
// event is added and nobody thinks about where it belongs.
|
|
66
|
+
'type.curated': 2,
|
|
67
|
+
'schema.changed': 3,
|
|
68
|
+
'snapshot.written': 4,
|
|
69
|
+
'snapshot.committed': 5,
|
|
70
|
+
'snapshot.dropped': 6,
|
|
71
|
+
'connector.run.finished': 7,
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Where an event nobody here declared is placed: in the middle, with the work.
|
|
75
|
+
*
|
|
76
|
+
* Not at either end deliberately. An unknown event sorted last would be read as
|
|
77
|
+
* the thing that ended the trace, and one sorted first as the thing that caused
|
|
78
|
+
* it — both are claims this library has no basis to make about a name it does
|
|
79
|
+
* not recognise.
|
|
80
|
+
*/
|
|
81
|
+
exports.CATALOG_EVENT_PHASE_FALLBACK = 4;
|
|
82
|
+
function catalogEventPhase(event) {
|
|
83
|
+
const phase = Reflect.get(exports.CATALOG_EVENT_PHASE, event);
|
|
84
|
+
return typeof phase === 'number' ? phase : exports.CATALOG_EVENT_PHASE_FALLBACK;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The channel an event is published on.
|
|
88
|
+
*
|
|
89
|
+
* Exported so a recorder or a watcher can subscribe without rebuilding the
|
|
90
|
+
* `aviary:<lib>:<event>` convention by hand — the one place that string is
|
|
91
|
+
* assembled is the one place it can drift.
|
|
92
|
+
*/
|
|
93
|
+
function channelNameFor(event) {
|
|
94
|
+
return (0, nestjs_diagnostics_1.channelName)(exports.CATALOG_LIB, event);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Typed wrapper over `emit`.
|
|
98
|
+
*
|
|
99
|
+
* Never throws — observability that can break a load is worse than no
|
|
100
|
+
* observability — which `emit` already guarantees; this exists for the types.
|
|
101
|
+
*/
|
|
102
|
+
function emitCatalog(event, payload) {
|
|
103
|
+
// The type arguments are named rather than inferred. `emit` types its payload
|
|
104
|
+
// as `PayloadOf<TLib, TEvent>`, a conditional that stays unresolved while
|
|
105
|
+
// `TEvent` is still an open generic — so inference leaves the compiler unable
|
|
106
|
+
// to see that the registry entry and `CatalogEventPayloads` are now the same
|
|
107
|
+
// type. Passing the whole event union makes the conditional distribute into a
|
|
108
|
+
// union of every payload, which the one being emitted is a member of.
|
|
109
|
+
(0, nestjs_diagnostics_1.emit)(exports.CATALOG_LIB, event, payload);
|
|
110
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var CatalogModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.CatalogModule = void 0;
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const common_1 = require("@nestjs/common");
|
|
13
|
+
const catalog_controller_1 = require("./catalog.controller");
|
|
14
|
+
const catalog_options_1 = require("./catalog.options");
|
|
15
|
+
const catalog_overlay_store_1 = require("./catalog.overlay-store");
|
|
16
|
+
const catalog_overlay_store_token_1 = require("./catalog.overlay-store.token");
|
|
17
|
+
const catalog_registry_1 = require("./catalog.registry");
|
|
18
|
+
const catalog_registry_base_1 = require("./catalog.registry.base");
|
|
19
|
+
const catalog_service_1 = require("./catalog.service");
|
|
20
|
+
const catalog_store_1 = require("./catalog.store");
|
|
21
|
+
const mikro_orm_read_store_1 = require("./stores/mikro-orm-read.store");
|
|
22
|
+
let CatalogModule = CatalogModule_1 = class CatalogModule {
|
|
23
|
+
static forRoot(options = {}) {
|
|
24
|
+
const path = options.path ?? 'api/catalog';
|
|
25
|
+
const mountController = options.controller !== false;
|
|
26
|
+
const controllers = mountController
|
|
27
|
+
? [(0, catalog_controller_1.createCatalogController)(path, options.guards ?? [], options.decorators ?? [])]
|
|
28
|
+
: [];
|
|
29
|
+
const overlayStore = options.overlayStore ??
|
|
30
|
+
new catalog_overlay_store_1.FileCatalogOverlayStore(options.overlayPath ?? (0, node_path_1.join)(process.cwd(), '.catalog', 'overlay.json'));
|
|
31
|
+
return {
|
|
32
|
+
module: CatalogModule_1,
|
|
33
|
+
imports: options.imports ?? [],
|
|
34
|
+
controllers,
|
|
35
|
+
providers: [
|
|
36
|
+
{ provide: catalog_options_1.CATALOG_OPTIONS, useValue: options },
|
|
37
|
+
{ provide: catalog_overlay_store_token_1.CATALOG_OVERLAY_STORE, useValue: overlayStore },
|
|
38
|
+
// The MikroORM-backed defaults are registered only when nothing
|
|
39
|
+
// overrides them. Registering them unconditionally would be worse than
|
|
40
|
+
// wasteful: a provider declared in this module shadows the same token
|
|
41
|
+
// exported by an imported one, so the default would quietly win over
|
|
42
|
+
// the host's override and the failure would look like the override
|
|
43
|
+
// never took effect.
|
|
44
|
+
//
|
|
45
|
+
// Derived is the right default — the model comes from the ORM of the
|
|
46
|
+
// application that owns the tables, and the rows come from those same
|
|
47
|
+
// tables, so nothing is stale and no infrastructure is needed. A
|
|
48
|
+
// warehouse overrides both: its model arrived over the wire and there
|
|
49
|
+
// are no entity classes to reflect over.
|
|
50
|
+
...(options.registry
|
|
51
|
+
? [options.registry]
|
|
52
|
+
: [
|
|
53
|
+
catalog_registry_1.MikroOrmCatalogRegistry,
|
|
54
|
+
{
|
|
55
|
+
provide: catalog_registry_base_1.CatalogRegistry,
|
|
56
|
+
useExisting: catalog_registry_1.MikroOrmCatalogRegistry,
|
|
57
|
+
},
|
|
58
|
+
]),
|
|
59
|
+
...(options.store
|
|
60
|
+
? [options.store]
|
|
61
|
+
: [mikro_orm_read_store_1.MikroOrmReadStore, { provide: catalog_store_1.CATALOG_STORE, useExisting: mikro_orm_read_store_1.MikroOrmReadStore }]),
|
|
62
|
+
catalog_service_1.CatalogService,
|
|
63
|
+
],
|
|
64
|
+
exports: [catalog_registry_base_1.CatalogRegistry, catalog_service_1.CatalogService, catalog_store_1.CATALOG_STORE],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
exports.CatalogModule = CatalogModule;
|
|
69
|
+
exports.CatalogModule = CatalogModule = CatalogModule_1 = __decorate([
|
|
70
|
+
(0, common_1.Module)({})
|
|
71
|
+
], CatalogModule);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { DynamicModule, ForwardReference, Provider, Type } from '@nestjs/common';
|
|
2
|
+
import type { CatalogOverlayStore } from './catalog.overlay-store';
|
|
3
|
+
export declare const CATALOG_OPTIONS: unique symbol;
|
|
4
|
+
export interface CatalogModuleOptions {
|
|
5
|
+
/** Only expose these entity class names. Empty/undefined means all of them. */
|
|
6
|
+
include?: string[];
|
|
7
|
+
/** Never expose these, whatever else says. */
|
|
8
|
+
exclude?: string[];
|
|
9
|
+
/** Group for types that declare none. */
|
|
10
|
+
defaultGroup?: string;
|
|
11
|
+
/** Where tier-0 edits are persisted. Defaults to a JSON file. */
|
|
12
|
+
overlayPath?: string;
|
|
13
|
+
overlayStore?: CatalogOverlayStore;
|
|
14
|
+
/**
|
|
15
|
+
* Route prefix for the introspection + object-read API.
|
|
16
|
+
* Defaults to `api/catalog`.
|
|
17
|
+
*/
|
|
18
|
+
path?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Guards applied to every catalog route. The library ships none: an
|
|
21
|
+
* introspection endpoint that lists every table in the system is exactly the
|
|
22
|
+
* kind of thing that should never be open by default, and only the host app
|
|
23
|
+
* knows what its auth looks like.
|
|
24
|
+
*/
|
|
25
|
+
guards?: Type<unknown>[];
|
|
26
|
+
/**
|
|
27
|
+
* Extra class decorators applied to the generated controller — the escape
|
|
28
|
+
* hatch for host-specific metadata (roles, rate limits, audit tags) that a
|
|
29
|
+
* generic library has no business knowing the shape of.
|
|
30
|
+
*/
|
|
31
|
+
decorators?: ClassDecorator[];
|
|
32
|
+
/**
|
|
33
|
+
* Modules this dynamic module should import.
|
|
34
|
+
*
|
|
35
|
+
* Needed because Nest instantiates a guard in the injector of the module that
|
|
36
|
+
* *declares* the controller — which, for a library that generates its own
|
|
37
|
+
* controller, is the library's module and not yours. Whatever your guards
|
|
38
|
+
* inject has to be resolvable from here.
|
|
39
|
+
*/
|
|
40
|
+
imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
|
|
41
|
+
/**
|
|
42
|
+
* Where the objects live. A provider for `CATALOG_STORE`.
|
|
43
|
+
*
|
|
44
|
+
* Defaults to reading the host application's own tables through MikroORM,
|
|
45
|
+
* which is always current and needs no infrastructure. Supply a warehouse
|
|
46
|
+
* adapter instead when the catalog should hold its own copy and keep history
|
|
47
|
+
* — the trade is a load to schedule and a staleness window to explain.
|
|
48
|
+
*/
|
|
49
|
+
store?: Provider;
|
|
50
|
+
/**
|
|
51
|
+
* Where the type definitions come from. A provider for `CatalogRegistry`.
|
|
52
|
+
*
|
|
53
|
+
* Defaults to deriving them from the host's MikroORM metadata. A warehouse
|
|
54
|
+
* that receives its model from other applications binds a stored
|
|
55
|
+
* implementation here instead.
|
|
56
|
+
*/
|
|
57
|
+
registry?: Provider;
|
|
58
|
+
/**
|
|
59
|
+
* Mount the built-in controller. Default true.
|
|
60
|
+
*
|
|
61
|
+
* Set false to keep the registry and reads but publish your own routes:
|
|
62
|
+
* inject `CatalogService` by class and shape the HTTP surface however your
|
|
63
|
+
* app already shapes it. Everything the built-in controller does is on that
|
|
64
|
+
* one service.
|
|
65
|
+
*/
|
|
66
|
+
controller?: boolean;
|
|
67
|
+
/** Hard cap on generic object reads, so the explorer cannot pull a table. */
|
|
68
|
+
maxPageSize?: number;
|
|
69
|
+
/** Hard cap on rows an ad-hoc query may return. Default 1000. */
|
|
70
|
+
maxQueryRows?: number;
|
|
71
|
+
/**
|
|
72
|
+
* How long a query may run. Default 15s.
|
|
73
|
+
*
|
|
74
|
+
* A console where anyone can write a cartesian join needs this more than it
|
|
75
|
+
* needs a bigger row cap — a query that returns nothing for four minutes is
|
|
76
|
+
* holding a connection the whole time.
|
|
77
|
+
*/
|
|
78
|
+
queryTimeoutMs?: number;
|
|
79
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CatalogOverlay } from './catalog.types';
|
|
2
|
+
/**
|
|
3
|
+
* Where tier-0 edits live.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately not the application database. The overlay is control-plane
|
|
6
|
+
* state, and mixing it into the operational schema is the first step toward a
|
|
7
|
+
* service that owns a database connection it should not have.
|
|
8
|
+
*/
|
|
9
|
+
export interface CatalogOverlayStore {
|
|
10
|
+
load(): Promise<CatalogOverlay>;
|
|
11
|
+
save(overlay: CatalogOverlay): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/** The default: a JSON file. Good enough until there is a control plane. */
|
|
14
|
+
export declare class FileCatalogOverlayStore implements CatalogOverlayStore {
|
|
15
|
+
private readonly path;
|
|
16
|
+
constructor(path: string);
|
|
17
|
+
load(): Promise<CatalogOverlay>;
|
|
18
|
+
save(overlay: CatalogOverlay): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** For tests, and for deployments that want the catalog strictly read-only. */
|
|
21
|
+
export declare class InMemoryCatalogOverlayStore implements CatalogOverlayStore {
|
|
22
|
+
private overlay;
|
|
23
|
+
load(): Promise<CatalogOverlay>;
|
|
24
|
+
save(overlay: CatalogOverlay): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InMemoryCatalogOverlayStore = exports.FileCatalogOverlayStore = void 0;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
/** The default: a JSON file. Good enough until there is a control plane. */
|
|
7
|
+
class FileCatalogOverlayStore {
|
|
8
|
+
path;
|
|
9
|
+
constructor(path) {
|
|
10
|
+
this.path = path;
|
|
11
|
+
}
|
|
12
|
+
async load() {
|
|
13
|
+
try {
|
|
14
|
+
const raw = await (0, promises_1.readFile)(this.path, 'utf8');
|
|
15
|
+
const parsed = JSON.parse(raw);
|
|
16
|
+
if (parsed &&
|
|
17
|
+
typeof parsed === 'object' &&
|
|
18
|
+
'types' in parsed &&
|
|
19
|
+
typeof parsed.types === 'object') {
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
return { types: {} };
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return { types: {} };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async save(overlay) {
|
|
29
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(this.path), { recursive: true });
|
|
30
|
+
await (0, promises_1.writeFile)(this.path, `${JSON.stringify(overlay, null, 2)}\n`, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.FileCatalogOverlayStore = FileCatalogOverlayStore;
|
|
34
|
+
/** For tests, and for deployments that want the catalog strictly read-only. */
|
|
35
|
+
class InMemoryCatalogOverlayStore {
|
|
36
|
+
overlay = { types: {} };
|
|
37
|
+
async load() {
|
|
38
|
+
return this.overlay;
|
|
39
|
+
}
|
|
40
|
+
async save(overlay) {
|
|
41
|
+
this.overlay = overlay;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.InMemoryCatalogOverlayStore = InMemoryCatalogOverlayStore;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CATALOG_OVERLAY_STORE: unique symbol;
|