@shortlink-org/portolan 0.3.0 → 0.4.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/README.md +151 -6
- package/catalog/model.go +147 -0
- package/catalog/roundtrip_test.go +7 -0
- package/cli/portolan.mjs +6 -1
- package/go.mod +7 -0
- package/go.sum +20 -6
- package/package.json +10 -9
- package/plugin/describe.go +38 -0
- package/plugins/README.md +296 -5
- package/plugins/extract-argocd/options.schema.json +44 -0
- package/plugins/extract-celery/main.py +1 -0
- package/plugins/extract-csharp-ddd/README.md +213 -0
- package/plugins/extract-csharp-ddd/options.schema.json +55 -0
- package/plugins/extract-django/README.md +13 -2
- package/plugins/extract-django/choices.py +47 -0
- package/plugins/extract-django/domain.py +2 -1
- package/plugins/extract-django/extract_test.py +13 -0
- package/plugins/extract-django/lifecycle.py +2 -28
- package/plugins/extract-django/main.py +1 -0
- package/plugins/extract-django/rules.py +154 -0
- package/plugins/extract-django/rules_test.py +158 -0
- package/plugins/extract-go-sqs/options.schema.json +21 -0
- package/plugins/extract-http-clients/describe.go +5 -4
- package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
- package/plugins/extract-k8s/options.schema.json +32 -0
- package/plugins/extract-laravel/Cargo.lock +962 -0
- package/plugins/extract-laravel/Cargo.toml +20 -0
- package/plugins/extract-laravel/README.md +200 -0
- package/plugins/extract-laravel/options.schema.json +68 -0
- package/plugins/extract-laravel/rustfmt.toml +1 -0
- package/plugins/extract-php-ddd/Cargo.lock +972 -0
- package/plugins/extract-php-ddd/Cargo.toml +22 -0
- package/plugins/extract-php-ddd/README.md +141 -0
- package/plugins/extract-php-ddd/options.schema.json +50 -0
- package/plugins/extract-php-ddd/rustfmt.toml +1 -0
- package/plugins/extract-python-kafka/main.py +1 -0
- package/plugins/extract-terraform/options.schema.json +26 -0
- package/plugins/extract-ts/main.ts +1 -0
- package/plugins/phpscan/Cargo.lock +953 -0
- package/plugins/phpscan/Cargo.toml +19 -0
- package/plugins/phpscan/rustfmt.toml +1 -0
- package/plugins/portolan-go.wasm +0 -0
- package/plugins/pyplugin/catalog.py +24 -1
- package/plugins/verify-otel/options.schema.json +12 -0
- package/portolan.json +130 -3
- package/rules/builtin.json +218 -0
- package/schema/portolan.schema.json +880 -13
- package/scripts/README.md +3 -1
- package/scripts/delivery-presets.mjs +36 -3
- package/scripts/gen-likec4.mjs +149 -16
- package/scripts/gen-likec4.test.mjs +96 -0
- package/scripts/gen.mjs +30 -3
- package/scripts/gitops-example.test.mjs +108 -0
- package/scripts/host-plugins/fetch-argocd.mjs +338 -0
- package/scripts/host-plugins/fetch-argocd.options.json +49 -0
- package/scripts/host-plugins/fetch-argocd.test.mjs +274 -0
- package/scripts/host-plugins/fetch-bsr.mjs +1 -0
- package/scripts/host-plugins/fetch-csr.mjs +1 -0
- package/scripts/host-plugins/fetch-git.mjs +1 -0
- package/scripts/host-plugins/fetch-k8s.mjs +263 -0
- package/scripts/host-plugins/fetch-k8s.options.json +50 -0
- package/scripts/host-plugins/fetch-k8s.test.mjs +259 -0
- package/scripts/host-plugins/k8s-topology.mjs +183 -0
- package/scripts/json-format.mjs +192 -0
- package/scripts/json-format.test.mjs +97 -0
- package/scripts/local-api.mjs +216 -8
- package/scripts/local-api.test.mjs +88 -2
- package/scripts/local-discovery.mjs +12 -0
- package/scripts/manifest.mjs +10 -1
- package/scripts/plugin-host.mjs +23 -1
- package/scripts/plugins-fresh.mjs +97 -0
- package/scripts/plugins-fresh.test.mjs +64 -0
- package/scripts/run-builtin.mjs +26 -5
- package/scripts/schema.mjs +138 -19
- package/scripts/trace-trials.mjs +176 -0
- package/scripts/trace-trials.test.mjs +142 -0
- package/scripts/warning-policy.mjs +12 -6
- package/scripts/warning-policy.test.mjs +40 -3
- package/src/app/Breadcrumbs.test.ts +3 -0
- package/src/app/Breadcrumbs.tsx +3 -0
- package/src/app/CatalogApp.tsx +2 -0
- package/src/app/SidebarFooter.tsx +20 -4
- package/src/catalog-index.ts +25 -0
- package/src/catalog-model.ts +246 -1
- package/src/catalog-validation.ts +104 -2
- package/src/catalog.test.ts +40 -1
- package/src/components/ChannelRows.test.tsx +76 -1
- package/src/components/ChannelRows.tsx +223 -60
- package/src/components/DeploymentRows.tsx +131 -0
- package/src/components/DocsLinks.test.tsx +27 -0
- package/src/components/DocsLinks.tsx +56 -0
- package/src/components/FieldTree.tsx +5 -2
- package/src/components/HTTPDestinationEvidence.tsx +1 -1
- package/src/components/PluginIcon.tsx +77 -0
- package/src/components/ProblemRow.tsx +173 -153
- package/src/components/RuleMarks.tsx +22 -0
- package/src/components/ShapeRows.tsx +24 -20
- package/src/data.ts +21 -0
- package/src/enrich.test.ts +127 -3
- package/src/enrich.ts +104 -6
- package/src/er/ErCanvas.tsx +218 -12
- package/src/er/GroupNode.tsx +57 -0
- package/src/er/StoreHeader.tsx +1 -0
- package/src/er/layout.test.ts +85 -2
- package/src/er/layout.ts +140 -5
- package/src/er/spec.test.ts +17 -0
- package/src/er/spec.ts +23 -10
- package/src/flow/Recordings.test.tsx +52 -0
- package/src/flow/Recordings.tsx +236 -0
- package/src/flow/StepDetail.tsx +53 -0
- package/src/flow/TraceTrial.tsx +419 -0
- package/src/flow/examples.test.ts +33 -0
- package/src/flow/examples.ts +37 -0
- package/src/flow/outline.test.ts +28 -0
- package/src/flow/outline.ts +7 -2
- package/src/flow/trace-trial-resume.test.ts +42 -0
- package/src/flow/trace-trial-resume.ts +74 -0
- package/src/graph/elk.ts +78 -0
- package/src/index.css +24 -0
- package/src/landing/LandingPage.tsx +15 -11
- package/src/landing/ProductTour.tsx +6 -6
- package/src/lib/all-problems.ts +27 -17
- package/src/lib/centrality.test.ts +251 -0
- package/src/lib/centrality.ts +232 -0
- package/src/lib/confluence.test.ts +37 -0
- package/src/lib/confluence.ts +41 -0
- package/src/lib/context-color.ts +1 -1
- package/src/lib/deployment-drift.ts +22 -0
- package/src/lib/derive.ts +43 -69
- package/src/lib/environments.test.ts +83 -0
- package/src/lib/environments.ts +57 -0
- package/src/lib/integration-url.test.ts +30 -0
- package/src/lib/integration-url.ts +63 -0
- package/src/lib/kafka-ui.ts +3 -45
- package/src/lib/local-api.ts +96 -2
- package/src/lib/notion.ts +13 -0
- package/src/lib/plugin-index.json +2765 -0
- package/src/lib/plugins.test.ts +68 -0
- package/src/lib/plugins.ts +264 -0
- package/src/lib/problem-flows.test.ts +61 -0
- package/src/lib/problem-flows.ts +78 -0
- package/src/lib/problem-rules-cel.d.mts +43 -0
- package/src/lib/problem-rules-cel.mjs +407 -0
- package/src/lib/problem-rules.test.ts +287 -0
- package/src/lib/problem-rules.ts +271 -0
- package/src/lib/problem-subjects.ts +737 -0
- package/src/lib/rule-entries.ts +39 -0
- package/src/lib/{data-problems.test.ts → rules-data.test.ts} +18 -16
- package/src/lib/rules-deploy.test.ts +133 -0
- package/src/lib/{problems.test.ts → rules-edges.test.ts} +8 -3
- package/src/lib/{proto-problems.test.ts → rules-proto.test.ts} +9 -25
- package/src/lib/{wire-problems.test.ts → rules-wire.test.ts} +13 -11
- package/src/lib/rules.test.ts +51 -0
- package/src/lib/rules.ts +86 -0
- package/src/lib/shape.test.ts +32 -0
- package/src/lib/shape.ts +30 -6
- package/src/lib/tech.ts +16 -0
- package/src/lib/trace-project.test.ts +34 -0
- package/src/lib/trace-project.ts +50 -0
- package/src/lib/use-problems.ts +23 -0
- package/src/lib/warnings.test.ts +9 -0
- package/src/lib/warnings.ts +10 -10
- package/src/likec4/ids.test.ts +6 -2
- package/src/likec4/ids.ts +43 -0
- package/src/main.tsx +23 -0
- package/src/merge-deployments.test.ts +127 -0
- package/src/merge.test.ts +82 -0
- package/src/merge.ts +173 -9
- package/src/pages/AggregatePage.tsx +57 -7
- package/src/pages/ContextMap.tsx +45 -3
- package/src/pages/ContextPage.tsx +2 -0
- package/src/pages/EventPage.tsx +15 -5
- package/src/pages/FlowDetail.tsx +23 -2
- package/src/pages/GraphPage.tsx +40 -3
- package/src/pages/Overview.tsx +152 -12
- package/src/pages/PluginIndex.tsx +190 -0
- package/src/pages/Problems.tsx +396 -128
- package/src/pages/ServicePage.tsx +58 -2
- package/src/pages/Settings.tsx +28 -2
- package/src/pages/settings/AboutSettings.tsx +8 -1
- package/src/pages/settings/IntegrationsSettings.tsx +63 -17
- package/src/pages/settings/RecordingSettings.tsx +138 -0
- package/src/pages/settings/RulesSettings.tsx +825 -0
- package/src/routes.test.ts +9 -0
- package/src/routes.ts +22 -1
- package/src/lib/data-problems.ts +0 -314
- package/src/lib/proto-problems.ts +0 -237
- package/src/lib/wire-problems.ts +0 -342
package/src/lib/wire-problems.ts
DELETED
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
// Where two services meet on one channel, and where a service's document and
|
|
2
|
-
// its code do not tell the same story about the bus.
|
|
3
|
-
//
|
|
4
|
-
// A channel belongs to the service that publishes on it, the way a store
|
|
5
|
-
// belongs to the service that writes it: a subscriber to `shop.cart.basket`
|
|
6
|
-
// is told, by the name, whose events it is reading and whose schema to vendor.
|
|
7
|
-
// A second publisher on the same channel breaks that promise silently - the
|
|
8
|
-
// subscriber dispatches on the event's name and never notices the message
|
|
9
|
-
// came from somewhere else, until one arrives that it cannot parse.
|
|
10
|
-
//
|
|
11
|
-
// Two sources say what a service puts on the bus, and they are read out of
|
|
12
|
-
// different files by different extractors. The domain says an aggregate raises
|
|
13
|
-
// an event and how it leaves, in `wire`. An AsyncAPI document says the service
|
|
14
|
-
// declares a channel and what travels on it, in `channels`. Neither is the
|
|
15
|
-
// truth on its own - a document is a promise and a wire is what the code does -
|
|
16
|
-
// and where they disagree is a fact worth a row, because one of them is stale.
|
|
17
|
-
//
|
|
18
|
-
// This is the messaging half of `shared-store`. It is done here, over the
|
|
19
|
-
// merged catalog, for the same reason the store rule is: each extractor sees
|
|
20
|
-
// one repository and cannot know what another declares.
|
|
21
|
-
|
|
22
|
-
import type { Catalog, CatalogIndex, ChannelMessage, Event, Service } from "../catalog";
|
|
23
|
-
import type { Problem } from "./derive";
|
|
24
|
-
|
|
25
|
-
interface Publisher {
|
|
26
|
-
service: Service;
|
|
27
|
-
context: string;
|
|
28
|
-
/** The events of this service whose wire names the channel. */
|
|
29
|
-
events: Event[];
|
|
30
|
-
/** Whether the service's own document declares it sends here. */
|
|
31
|
-
declared: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function wireProblems(
|
|
35
|
-
catalog: Catalog,
|
|
36
|
-
index: CatalogIndex,
|
|
37
|
-
): Problem[] {
|
|
38
|
-
return [
|
|
39
|
-
...sharedChannels(catalog),
|
|
40
|
-
...documentAgainstCode(catalog),
|
|
41
|
-
...messageEncodingMismatches(catalog),
|
|
42
|
-
...unresolvedSubscriptions(catalog, index),
|
|
43
|
-
];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface MessageEndpoint {
|
|
47
|
-
context: string;
|
|
48
|
-
service: Service;
|
|
49
|
-
address: string;
|
|
50
|
-
source?: string;
|
|
51
|
-
message: ChannelMessage;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Producer and subscriber have both named a format, and those formats differ. */
|
|
55
|
-
function messageEncodingMismatches(catalog: Catalog): Problem[] {
|
|
56
|
-
const sends = new Map<string, MessageEndpoint[]>();
|
|
57
|
-
const receives: MessageEndpoint[] = [];
|
|
58
|
-
for (const context of catalog.contexts) {
|
|
59
|
-
for (const service of context.services) {
|
|
60
|
-
for (const channel of service.channels ?? []) {
|
|
61
|
-
for (const message of channel.messages) {
|
|
62
|
-
const endpoint = { context: context.id, service, address: channel.address, source: channel.source, message };
|
|
63
|
-
const key = `${channel.address}\u0000${message.name}`;
|
|
64
|
-
if (message.direction === "send") {
|
|
65
|
-
const publishers = sends.get(key) ?? [];
|
|
66
|
-
publishers.push(endpoint);
|
|
67
|
-
sends.set(key, publishers);
|
|
68
|
-
} else {
|
|
69
|
-
receives.push(endpoint);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const out: Problem[] = [];
|
|
77
|
-
for (const receiver of receives) {
|
|
78
|
-
const expected = wireFormat(receiver.message);
|
|
79
|
-
if (!expected) continue;
|
|
80
|
-
const key = `${receiver.address}\u0000${receiver.message.name}`;
|
|
81
|
-
for (const publisher of sends.get(key) ?? []) {
|
|
82
|
-
const actual = wireFormat(publisher.message);
|
|
83
|
-
if (!actual || actual === expected) continue;
|
|
84
|
-
out.push({
|
|
85
|
-
kind: "message-encoding",
|
|
86
|
-
severity: "error",
|
|
87
|
-
context: receiver.context,
|
|
88
|
-
service: receiver.service.id,
|
|
89
|
-
id: receiver.service.id,
|
|
90
|
-
peer: publisher.service.id,
|
|
91
|
-
note: `${receiver.service.id} expects ${receiver.message.name} on ${receiver.address} as ${expected}, but ${publisher.service.id} sends it as ${actual}.`,
|
|
92
|
-
source: receiver.source,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return out;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function wireFormat(message: ChannelMessage): string {
|
|
100
|
-
if (message.encoding) return message.encoding.trim().toLowerCase();
|
|
101
|
-
const contentType = message.contentType?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
102
|
-
if (contentType.includes("msgpack") || contentType.includes("messagepack")) return "msgpack";
|
|
103
|
-
return contentType;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Every service that publishes on a channel another service publishes on
|
|
108
|
-
* too: one row per service per such channel, pointing at the other side.
|
|
109
|
-
*
|
|
110
|
-
* Publishing is either of the two claims - an event whose wire names the
|
|
111
|
-
* channel, or a document declaring a send on it - because both are the service
|
|
112
|
-
* saying it owns the channel, and a document that says so while the code has
|
|
113
|
-
* not caught up is the same collision one release earlier.
|
|
114
|
-
*
|
|
115
|
-
* A service with several aggregates on one channel is not this: the channel
|
|
116
|
-
* is still one service's. An event without a wire is skipped, because a
|
|
117
|
-
* channel nobody named cannot be shared.
|
|
118
|
-
*/
|
|
119
|
-
function sharedChannels(catalog: Catalog): Problem[] {
|
|
120
|
-
const byChannel = new Map<string, Map<string, Publisher>>();
|
|
121
|
-
|
|
122
|
-
const publisher = (
|
|
123
|
-
channel: string,
|
|
124
|
-
service: Service,
|
|
125
|
-
context: string,
|
|
126
|
-
): Publisher => {
|
|
127
|
-
const publishers = byChannel.get(channel) ?? new Map<string, Publisher>();
|
|
128
|
-
byChannel.set(channel, publishers);
|
|
129
|
-
const mine = publishers.get(service.id) ?? {
|
|
130
|
-
service,
|
|
131
|
-
context,
|
|
132
|
-
events: [],
|
|
133
|
-
declared: false,
|
|
134
|
-
};
|
|
135
|
-
publishers.set(service.id, mine);
|
|
136
|
-
|
|
137
|
-
return mine;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
for (const context of catalog.contexts) {
|
|
141
|
-
for (const service of context.services) {
|
|
142
|
-
for (const aggregate of service.aggregates) {
|
|
143
|
-
for (const event of aggregate.events) {
|
|
144
|
-
const channel = event.wire?.channel;
|
|
145
|
-
if (!channel) continue;
|
|
146
|
-
publisher(channel, service, context.id).events.push(event);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
for (const channel of service.channels ?? []) {
|
|
151
|
-
if (channel.kind && channel.kind !== "event") continue;
|
|
152
|
-
if (!sends(channel.messages)) continue;
|
|
153
|
-
publisher(channel.address, service, context.id).declared = true;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const out: Problem[] = [];
|
|
159
|
-
for (const [channel, publishers] of byChannel) {
|
|
160
|
-
if (publishers.size < 2) continue;
|
|
161
|
-
for (const mine of publishers.values()) {
|
|
162
|
-
for (const theirs of publishers.values()) {
|
|
163
|
-
if (theirs === mine) continue;
|
|
164
|
-
const [first] = mine.events;
|
|
165
|
-
out.push({
|
|
166
|
-
kind: "shared-channel",
|
|
167
|
-
// An error, like a second writer in a database: a subscriber vendors
|
|
168
|
-
// one publisher's schema, and a message from the other is one it
|
|
169
|
-
// will fail on, not one it will merely misfile.
|
|
170
|
-
severity: "error",
|
|
171
|
-
context: mine.context,
|
|
172
|
-
service: mine.service.id,
|
|
173
|
-
// The near end is one event when this side has one, so the row leads
|
|
174
|
-
// to a page; a side known only from its document leads to the
|
|
175
|
-
// service, which is the only page there is.
|
|
176
|
-
id: first?.id ?? mine.service.id,
|
|
177
|
-
peer: theirs.service.id,
|
|
178
|
-
note: `${mine.service.id} publishes on ${channel}, and so does ${theirs.service.id}: ${claim(theirs, channel)}. A subscriber reads both without being told.`,
|
|
179
|
-
source: first?.versions[0]?.source ?? sourceOf(mine.service, channel),
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return out;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** What a publisher is putting on a channel, as the catalog knows it. */
|
|
189
|
-
function claim(publisher: Publisher, channel: string): string {
|
|
190
|
-
if (publisher.events.length > 0) {
|
|
191
|
-
return publisher.events.map((e) => e.name).join(", ");
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const declared = publisher.service.channels?.find(
|
|
195
|
-
(c) => c.address === channel,
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
return (
|
|
199
|
-
declared?.messages
|
|
200
|
-
.filter((m) => m.direction === "send")
|
|
201
|
-
.map((m) => m.name)
|
|
202
|
-
.join(", ") ?? "nothing the catalog can name"
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Where a service's document and its own code disagree about the bus.
|
|
208
|
-
*
|
|
209
|
-
* Both directions are a warning rather than an error, and for the same reason:
|
|
210
|
-
* neither claim is wrong on its own. A channel the code publishes on and the
|
|
211
|
-
* document does not declare is a document that has fallen behind - or an event
|
|
212
|
-
* going out where nobody agreed it would. A channel the document declares and
|
|
213
|
-
* no event names is a promise nothing keeps yet.
|
|
214
|
-
*
|
|
215
|
-
* A service with no document says nothing here. Silence is not a disagreement,
|
|
216
|
-
* and a rule that fired on every service without an AsyncAPI file would be a
|
|
217
|
-
* page of rows about work nobody has started.
|
|
218
|
-
*/
|
|
219
|
-
function documentAgainstCode(catalog: Catalog): Problem[] {
|
|
220
|
-
const out: Problem[] = [];
|
|
221
|
-
|
|
222
|
-
for (const context of catalog.contexts) {
|
|
223
|
-
for (const service of context.services) {
|
|
224
|
-
const channels = service.channels ?? [];
|
|
225
|
-
const declared = new Set(
|
|
226
|
-
channels
|
|
227
|
-
.filter((c) => (!c.kind || c.kind === "event") && sends(c.messages))
|
|
228
|
-
.map((c) => c.address),
|
|
229
|
-
);
|
|
230
|
-
// Channels that only receive say nothing about what the service
|
|
231
|
-
// sends: a subscription read out of the code is not a document of
|
|
232
|
-
// the service's publications, and holding the events against it
|
|
233
|
-
// would report every one of them.
|
|
234
|
-
if (declared.size === 0) continue;
|
|
235
|
-
const published = new Map<string, Event>();
|
|
236
|
-
|
|
237
|
-
for (const aggregate of service.aggregates) {
|
|
238
|
-
for (const event of aggregate.events) {
|
|
239
|
-
const channel = event.wire?.channel;
|
|
240
|
-
if (!channel) continue;
|
|
241
|
-
if (!published.has(channel)) published.set(channel, event);
|
|
242
|
-
if (declared.has(channel)) continue;
|
|
243
|
-
|
|
244
|
-
out.push({
|
|
245
|
-
kind: "channel-undeclared",
|
|
246
|
-
severity: "warning",
|
|
247
|
-
context: context.id,
|
|
248
|
-
service: service.id,
|
|
249
|
-
id: event.id,
|
|
250
|
-
peer: channel,
|
|
251
|
-
note: `${event.name} goes out on ${channel}, which ${service.id} does not declare it sends on. A subscriber reading the document does not know this message exists.`,
|
|
252
|
-
source: event.versions[0]?.source,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
for (const channel of channels) {
|
|
258
|
-
if (channel.kind && channel.kind !== "event") continue;
|
|
259
|
-
if (!sends(channel.messages)) continue;
|
|
260
|
-
if (published.has(channel.address)) continue;
|
|
261
|
-
|
|
262
|
-
out.push({
|
|
263
|
-
kind: "channel-unpublished",
|
|
264
|
-
severity: "warning",
|
|
265
|
-
context: context.id,
|
|
266
|
-
service: service.id,
|
|
267
|
-
id: service.id,
|
|
268
|
-
peer: channel.address,
|
|
269
|
-
note: `${service.id} declares it sends on ${channel.address}, and no event of it names that channel: ${channel.messages
|
|
270
|
-
.filter((m) => m.direction === "send")
|
|
271
|
-
.map((m) => m.name)
|
|
272
|
-
.join(", ")}.`,
|
|
273
|
-
source: channel.source,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return out;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* A message a service listens for that nothing in the estate publishes.
|
|
284
|
-
*
|
|
285
|
-
* This is the only edge in the catalog that runs the other way. Everywhere else
|
|
286
|
-
* the publisher names its consumers and a name that resolves to nobody is the
|
|
287
|
-
* problem; here the subscriber names a message, and the estate is searched for
|
|
288
|
-
* whoever puts it on the wire. A subscription that resolves is how two
|
|
289
|
-
* repositories that never mention each other are found to be joined.
|
|
290
|
-
*/
|
|
291
|
-
function unresolvedSubscriptions(
|
|
292
|
-
catalog: Catalog,
|
|
293
|
-
index: CatalogIndex,
|
|
294
|
-
): Problem[] {
|
|
295
|
-
// Every name anything in the estate puts on the wire. The index knows the
|
|
296
|
-
// events; a document declaring a send is the other half, and it counts for
|
|
297
|
-
// the same reason it counts as publishing above - the code may simply not
|
|
298
|
-
// have caught up with it yet.
|
|
299
|
-
const published = new Set(index.eventByWireName.keys());
|
|
300
|
-
for (const context of catalog.contexts) {
|
|
301
|
-
for (const service of context.services) {
|
|
302
|
-
for (const channel of service.channels ?? []) {
|
|
303
|
-
for (const message of channel.messages) {
|
|
304
|
-
if (message.direction === "send") published.add(message.name);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const out: Problem[] = [];
|
|
311
|
-
for (const context of catalog.contexts) {
|
|
312
|
-
for (const service of context.services) {
|
|
313
|
-
for (const channel of service.channels ?? []) {
|
|
314
|
-
for (const message of channel.messages) {
|
|
315
|
-
if (message.direction !== "receive") continue;
|
|
316
|
-
if (published.has(message.name)) continue;
|
|
317
|
-
|
|
318
|
-
out.push({
|
|
319
|
-
kind: "subscription-unresolved",
|
|
320
|
-
severity: "warning",
|
|
321
|
-
context: context.id,
|
|
322
|
-
service: service.id,
|
|
323
|
-
id: service.id,
|
|
324
|
-
peer: message.name,
|
|
325
|
-
note: `${service.id} listens on ${channel.address} for ${message.name}, and nothing in the catalog publishes it. Either the publisher is outside the estate, or the name has drifted.`,
|
|
326
|
-
source: channel.source,
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return out;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function sends(messages: { direction: string }[]): boolean {
|
|
337
|
-
return messages.some((m) => m.direction === "send");
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function sourceOf(service: Service, channel: string): string | undefined {
|
|
341
|
-
return service.channels?.find((c) => c.address === channel)?.source;
|
|
342
|
-
}
|