@llm4ts/shell 2.11.0 → 2.13.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.
Files changed (61) hide show
  1. package/flows/epic-stories.js +25 -5
  2. package/flows/lib/epic-stories.js +76 -8
  3. package/kits/soap-ace/README.md +313 -0
  4. package/kits/soap-ace/fixtures/demo-bank-soap/RUNBOOK.md +102 -0
  5. package/kits/soap-ace/fixtures/demo-bank-soap/design/api-design.md +620 -0
  6. package/kits/soap-ace/fixtures/demo-bank-soap/responses/cercaConti.xml +3 -0
  7. package/kits/soap-ace/fixtures/demo-bank-soap/responses/cercaMovimenti.xml +3 -0
  8. package/kits/soap-ace/fixtures/demo-bank-soap/responses/confermaBonifico.xml +3 -0
  9. package/kits/soap-ace/fixtures/demo-bank-soap/responses/dettaglioConto.xml +3 -0
  10. package/kits/soap-ace/fixtures/demo-bank-soap/responses/inserisciBonifico.xml +3 -0
  11. package/kits/soap-ace/fixtures/demo-bank-soap/responses/revocaBonifico.xml +3 -0
  12. package/kits/soap-ace/fixtures/demo-bank-soap/soapui/DemoBank-soapui-project.xml +39 -0
  13. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/DemoBank.wsdl +186 -0
  14. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/LegacyRpc.wsdl +29 -0
  15. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/banking.xsd +10 -0
  16. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/bonifici.xsd +107 -0
  17. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/common.xsd +93 -0
  18. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/conti.xsd +71 -0
  19. package/kits/soap-ace/fixtures/demo-bank-soap/wsdl/xsd/movimenti.xsd +55 -0
  20. package/kits/soap-ace/flows/lib/soap/Analysis.js +508 -0
  21. package/kits/soap-ace/flows/lib/soap/Auth.js +179 -0
  22. package/kits/soap-ace/flows/lib/soap/Call.js +186 -0
  23. package/kits/soap-ace/flows/lib/soap/CallPolicy.js +50 -0
  24. package/kits/soap-ace/flows/lib/soap/Catalog.js +157 -0
  25. package/kits/soap-ace/flows/lib/soap/Classification.js +254 -0
  26. package/kits/soap-ace/flows/lib/soap/Design.js +497 -0
  27. package/kits/soap-ace/flows/lib/soap/DesignProposal.js +204 -0
  28. package/kits/soap-ace/flows/lib/soap/Discover.js +139 -0
  29. package/kits/soap-ace/flows/lib/soap/Envelope.js +132 -0
  30. package/kits/soap-ace/flows/lib/soap/Epic.js +239 -0
  31. package/kits/soap-ace/flows/lib/soap/Instance.js +675 -0
  32. package/kits/soap-ace/flows/lib/soap/Mapping.js +130 -0
  33. package/kits/soap-ace/flows/lib/soap/Masking.js +508 -0
  34. package/kits/soap-ace/flows/lib/soap/OpenApi.js +288 -0
  35. package/kits/soap-ace/flows/lib/soap/Samples.js +287 -0
  36. package/kits/soap-ace/flows/lib/soap/Scenarios.js +132 -0
  37. package/kits/soap-ace/flows/lib/soap/SoapUiImport.js +200 -0
  38. package/kits/soap-ace/flows/lib/soap/Stub.js +34 -0
  39. package/kits/soap-ace/flows/lib/soap/Transport.js +167 -0
  40. package/kits/soap-ace/flows/lib/soap/Wsdl.js +533 -0
  41. package/kits/soap-ace/flows/lib/soap/Xml.js +453 -0
  42. package/kits/soap-ace/flows/lib/soap/Xsd.js +471 -0
  43. package/kits/soap-ace/flows/lib/soap/Yaml.js +309 -0
  44. package/kits/soap-ace/flows/soap-design.js +167 -0
  45. package/kits/soap-ace/flows/soap-discover.js +85 -0
  46. package/kits/soap-ace/flows/soap-epic.js +204 -0
  47. package/kits/soap-ace/flows/soap-sample.js +265 -0
  48. package/kits/soap-ace/packs/ace12-rest/api-style.md +63 -0
  49. package/kits/soap-ace/packs/ace12-rest/pack.md +18 -0
  50. package/kits/soap-ace/packs/ace12-rest/prompts/implement.md +8 -0
  51. package/kits/soap-ace/packs/ace12-rest/reviewers/contract.md +5 -0
  52. package/kits/soap-ace/patterns/PAT-ACE-001-esito-to-problem.md +8 -0
  53. package/kits/soap-ace/patterns/PAT-ACE-002-fault-to-problem.md +8 -0
  54. package/kits/soap-ace/patterns/PAT-ACE-003-amounts-and-enums.md +8 -0
  55. package/kits/soap-ace/patterns/PAT-ACE-004-pagination.md +7 -0
  56. package/kits/soap-ace/patterns/PAT-ACE-005-nil-and-absence.md +7 -0
  57. package/kits/soap-ace/scaffolds/ace12-rest-api/CONTRIBUTING.md +56 -0
  58. package/kits/soap-ace/scaffolds/ace12-rest-api/README.md +16 -0
  59. package/kits/soap-ace/scaffolds/ace12-rest-api/scripts/ace-gates-container.sh +8 -0
  60. package/kits/soap-ace/scaffolds/ace12-rest-api/scripts/ace-gates.sh +41 -0
  61. package/package.json +4 -4
@@ -0,0 +1,533 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { posix } from "node:path";
3
+ import * as Context from "effect/Context";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Schema from "effect/Schema";
6
+ import { CatalogVersion, Endpoint, FaultRef, OpenQuestion, Operation, qname, splitClark, WsdlCatalog, XsdNamespace } from "./Catalog.js";
7
+ import { attribute, childrenNamed, firstChild, is, parseXmlBytes, resolveQName, textOf } from "./Xml.js";
8
+ import { collectSchema, finishSchemas, makeSchemaCollector } from "./Xsd.js";
9
+ // WSDL discovery: load the root document, follow `wsdl:import`,
10
+ // `xsd:import`, and `xsd:include` through the `DocumentLoader`, and build the
11
+ // typed `WsdlCatalog`. Only document/literal SOAP bindings become operations
12
+ // (v1 scope). A WSDL whose every SOAP binding is RPC or encoded fails with
13
+ // `WsdlError` naming the bindings; one that also offers a document/literal
14
+ // binding keeps the usable one and records the others as open questions.
15
+ export const Wsdl11Namespace = "http://schemas.xmlsoap.org/wsdl/";
16
+ export const Wsdl20Namespace = "http://www.w3.org/ns/wsdl";
17
+ const Soap11BindingNamespace = "http://schemas.xmlsoap.org/wsdl/soap/";
18
+ const Soap12BindingNamespace = "http://schemas.xmlsoap.org/wsdl/soap12/";
19
+ const Wsdl20SoapNamespace = "http://www.w3.org/ns/wsdl/soap";
20
+ export class DocumentLoadError extends Schema.TaggedError()("DocumentLoadError", {
21
+ location: Schema.String,
22
+ detail: Schema.String
23
+ }) {
24
+ get message() {
25
+ return `cannot load ${this.location}: ${this.detail}`;
26
+ }
27
+ }
28
+ export class WsdlError extends Schema.TaggedError()("WsdlError", {
29
+ reason: Schema.Literals(["not-wsdl", "unsupported-binding", "no-operations"]),
30
+ detail: Schema.String,
31
+ location: Schema.String
32
+ }) {
33
+ get message() {
34
+ return `${this.location}: ${this.reason}: ${this.detail}`;
35
+ }
36
+ }
37
+ export class DocumentLoader extends Context.Service()("@llm4ts/kits/soap-ace/DocumentLoader") {
38
+ }
39
+ /** A loader over an in-memory map of location → text, for tests and fixtures. */
40
+ export const makeMemoryDocumentLoader = (documents) => ({
41
+ load: (location) => {
42
+ const found = documents[location];
43
+ if (found === undefined) {
44
+ return Effect.fail(new DocumentLoadError({ location, detail: "not found" }));
45
+ }
46
+ return Effect.succeed(typeof found === "string" ? new TextEncoder().encode(found) : found);
47
+ }
48
+ });
49
+ /** A loader over local files; URLs are refused (they need a `SoapTransport`). */
50
+ export const makeFileDocumentLoader = () => ({
51
+ load: (location) => isUrl(location)
52
+ ? Effect.fail(new DocumentLoadError({ location, detail: "URLs need the HTTP document loader" }))
53
+ : Effect.tryPromise({
54
+ try: async () => new Uint8Array(await readFile(location)),
55
+ catch: (cause) => new DocumentLoadError({
56
+ location,
57
+ detail: typeof cause === "object" && cause !== null && "code" in cause
58
+ ? String(cause.code)
59
+ : "unreadable"
60
+ })
61
+ })
62
+ });
63
+ /**
64
+ * A location as it may be recorded or shown: URL credentials removed and
65
+ * query parameters named like secrets redacted. The loader still gets the
66
+ * real location; catalogs, questions, and errors only ever see this one.
67
+ */
68
+ export const safeLocation = (location) => {
69
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(location))
70
+ return location;
71
+ try {
72
+ const url = new URL(location);
73
+ url.username = "";
74
+ url.password = "";
75
+ const sensitive = [...url.searchParams.keys()].filter((name) => /token|key|secret|pass|auth|sig|session|code/i.test(name));
76
+ if (sensitive.length === 0)
77
+ return `${url.origin}${url.pathname}${url.search}${url.hash}`;
78
+ for (const name of sensitive)
79
+ url.searchParams.set(name, "REDACTED");
80
+ return url.toString();
81
+ }
82
+ catch {
83
+ return "(unreadable location)";
84
+ }
85
+ };
86
+ const isUrl = (location) => /^[a-z][a-z0-9+.-]*:\/\//i.test(location);
87
+ /** Resolve a relative `location`/`schemaLocation` against the referring document. */
88
+ export const resolveLocation = (base, relative) => {
89
+ if (isUrl(relative))
90
+ return relative;
91
+ if (isUrl(base))
92
+ return new URL(relative, base).toString();
93
+ if (relative.startsWith("/"))
94
+ return posix.normalize(relative);
95
+ return posix.normalize(posix.join(posix.dirname(base), relative));
96
+ };
97
+ const refOf = (element, name) => {
98
+ const value = attribute(element, name);
99
+ if (value === undefined)
100
+ return undefined;
101
+ const resolved = resolveQName(element, value);
102
+ return resolved === undefined ? `{?}${value}` : qname(resolved.namespace, resolved.local);
103
+ };
104
+ const wsdlDocumentation = (element, namespace) => {
105
+ const documentation = firstChild(element, namespace, "documentation");
106
+ const text = documentation === undefined ? "" : textOf(documentation).trim().replace(/\s+/g, " ");
107
+ return text === "" ? undefined : text;
108
+ };
109
+ class Discovery {
110
+ documents = [];
111
+ schemas = makeSchemaCollector();
112
+ questions = [];
113
+ wsdl11 = {
114
+ messages: new Map(),
115
+ portTypes: new Map(),
116
+ bindings: new Map(),
117
+ ports: []
118
+ };
119
+ wsdl20 = { interfaces: new Map(), bindings: new Map(), endpoints: [] };
120
+ visited = new Set();
121
+ loader;
122
+ constructor(loader) {
123
+ this.loader = loader;
124
+ }
125
+ question(code, location, subject, detail) {
126
+ this.questions.push(new OpenQuestion({ code, location, subject, detail }));
127
+ }
128
+ load(location) {
129
+ const shown = safeLocation(location);
130
+ this.documents.push(shown);
131
+ return this.loader.load(location).pipe(Effect.mapError((error) => new DocumentLoadError({ location: shown, detail: error.detail })), Effect.flatMap((bytes) => parseXmlBytes(bytes, { source: shown })));
132
+ }
133
+ /** Read a WSDL document (root or imported) and everything it pulls in. */
134
+ wsdl(location) {
135
+ this.visited.add(location);
136
+ return Effect.flatMap(this.load(location), (root) => this.wsdlFrom(root, location));
137
+ }
138
+ wsdlFrom(root, location) {
139
+ return Effect.gen({ self: this }, function* () {
140
+ const version = is(root, Wsdl11Namespace, "definitions")
141
+ ? "1.1"
142
+ : is(root, Wsdl20Namespace, "description")
143
+ ? "2.0"
144
+ : undefined;
145
+ if (version === undefined) {
146
+ return yield* new WsdlError({
147
+ reason: "not-wsdl",
148
+ detail: `root element {${root.name.namespace}}${root.name.local} is not a WSDL 1.1 definitions or WSDL 2.0 description`,
149
+ location
150
+ });
151
+ }
152
+ const namespace = version === "1.1" ? Wsdl11Namespace : Wsdl20Namespace;
153
+ const targetNamespace = attribute(root, "targetNamespace") ?? "";
154
+ for (const imported of [
155
+ ...childrenNamed(root, namespace, "import"),
156
+ ...childrenNamed(root, namespace, "include")
157
+ ]) {
158
+ const target = attribute(imported, "location");
159
+ if (target === undefined)
160
+ continue;
161
+ const resolved = resolveLocation(location, target);
162
+ if (this.visited.has(resolved))
163
+ continue;
164
+ this.visited.add(resolved);
165
+ const next = yield* this.load(resolved);
166
+ if (is(next, XsdNamespace, "schema")) {
167
+ yield* this.schema(next, resolved, undefined);
168
+ }
169
+ else {
170
+ yield* this.wsdlFrom(next, resolved);
171
+ }
172
+ }
173
+ const types = firstChild(root, namespace, "types");
174
+ if (types !== undefined) {
175
+ for (const schema of childrenNamed(types, XsdNamespace, "schema")) {
176
+ yield* this.schema(schema, location, undefined);
177
+ }
178
+ }
179
+ if (version === "1.1")
180
+ this.read11(root, location, targetNamespace);
181
+ else
182
+ this.read20(root, location, targetNamespace);
183
+ return { version, targetNamespace };
184
+ });
185
+ }
186
+ schema(schema, location, namespaceOverride) {
187
+ return Effect.gen({ self: this }, function* () {
188
+ const read = collectSchema(schema, safeLocation(location), this.schemas, namespaceOverride);
189
+ for (const reference of read.references) {
190
+ const resolved = resolveLocation(location, reference.location);
191
+ const key = reference.kind === "include" ? `${resolved}#${read.targetNamespace}` : resolved;
192
+ if (this.visited.has(key))
193
+ continue;
194
+ this.visited.add(key);
195
+ const next = yield* this.load(resolved);
196
+ if (!is(next, XsdNamespace, "schema"))
197
+ continue;
198
+ yield* this.schema(next, resolved, reference.kind === "include" ? read.targetNamespace : undefined);
199
+ }
200
+ });
201
+ }
202
+ read11(root, location, targetNamespace) {
203
+ const at = (element) => `${safeLocation(location)}:${element.line}`;
204
+ for (const message of childrenNamed(root, Wsdl11Namespace, "message")) {
205
+ const name = attribute(message, "name");
206
+ if (name === undefined)
207
+ continue;
208
+ this.wsdl11.messages.set(qname(targetNamespace, name), childrenNamed(message, Wsdl11Namespace, "part").map((part) => ({
209
+ name: attribute(part, "name") ?? "",
210
+ element: refOf(part, "element"),
211
+ type: refOf(part, "type")
212
+ })));
213
+ }
214
+ for (const portType of childrenNamed(root, Wsdl11Namespace, "portType")) {
215
+ const name = attribute(portType, "name");
216
+ if (name === undefined)
217
+ continue;
218
+ this.wsdl11.portTypes.set(qname(targetNamespace, name), childrenNamed(portType, Wsdl11Namespace, "operation").map((operation) => {
219
+ const input = firstChild(operation, Wsdl11Namespace, "input");
220
+ const output = firstChild(operation, Wsdl11Namespace, "output");
221
+ return {
222
+ name: attribute(operation, "name") ?? "",
223
+ input: input === undefined ? undefined : refOf(input, "message"),
224
+ output: output === undefined ? undefined : refOf(output, "message"),
225
+ faults: childrenNamed(operation, Wsdl11Namespace, "fault").map((fault) => ({
226
+ name: attribute(fault, "name") ?? "",
227
+ message: refOf(fault, "message")
228
+ })),
229
+ documentation: wsdlDocumentation(operation, Wsdl11Namespace)
230
+ };
231
+ }));
232
+ }
233
+ for (const binding of childrenNamed(root, Wsdl11Namespace, "binding")) {
234
+ const name = attribute(binding, "name");
235
+ if (name === undefined)
236
+ continue;
237
+ const soap11 = firstChild(binding, Soap11BindingNamespace, "binding");
238
+ const soap12 = firstChild(binding, Soap12BindingNamespace, "binding");
239
+ const soapNamespace = soap11 !== undefined ? Soap11BindingNamespace : Soap12BindingNamespace;
240
+ const soapBinding = soap11 ?? soap12;
241
+ const style = soapBinding === undefined ? "" : (attribute(soapBinding, "style") ?? "document");
242
+ this.wsdl11.bindings.set(qname(targetNamespace, name), {
243
+ name,
244
+ portType: refOf(binding, "type"),
245
+ soapVersion: soap11 !== undefined ? "1.1" : soap12 !== undefined ? "1.2" : undefined,
246
+ style,
247
+ location: at(binding),
248
+ operations: childrenNamed(binding, Wsdl11Namespace, "operation").map((operation) => {
249
+ const soapOperation = firstChild(operation, soapNamespace, "operation");
250
+ const input = firstChild(operation, Wsdl11Namespace, "input");
251
+ const body = input === undefined ? undefined : firstChild(input, soapNamespace, "body");
252
+ return {
253
+ name: attribute(operation, "name") ?? "",
254
+ soapAction: soapOperation === undefined ? "" : (attribute(soapOperation, "soapAction") ?? ""),
255
+ style: (soapOperation === undefined ? undefined : attribute(soapOperation, "style")) ??
256
+ style,
257
+ use: body === undefined ? "literal" : (attribute(body, "use") ?? "literal"),
258
+ bodyParts: body === undefined ? undefined : attribute(body, "parts")
259
+ };
260
+ })
261
+ });
262
+ }
263
+ for (const service of childrenNamed(root, Wsdl11Namespace, "service")) {
264
+ const serviceName = attribute(service, "name") ?? "";
265
+ for (const port of childrenNamed(service, Wsdl11Namespace, "port")) {
266
+ const address = firstChild(port, Soap11BindingNamespace, "address") ??
267
+ firstChild(port, Soap12BindingNamespace, "address");
268
+ this.wsdl11.ports.push({
269
+ service: serviceName,
270
+ name: attribute(port, "name") ?? "",
271
+ binding: refOf(port, "binding"),
272
+ address: address === undefined ? undefined : attribute(address, "location"),
273
+ location: at(port)
274
+ });
275
+ }
276
+ }
277
+ }
278
+ read20(root, location, targetNamespace) {
279
+ for (const iface of childrenNamed(root, Wsdl20Namespace, "interface")) {
280
+ const name = attribute(iface, "name");
281
+ if (name === undefined)
282
+ continue;
283
+ const faults = new Map();
284
+ for (const fault of childrenNamed(iface, Wsdl20Namespace, "fault")) {
285
+ faults.set(attribute(fault, "name") ?? "", refOf(fault, "element"));
286
+ }
287
+ this.wsdl20.interfaces.set(qname(targetNamespace, name), {
288
+ faults,
289
+ operations: childrenNamed(iface, Wsdl20Namespace, "operation").map((operation) => {
290
+ const input = firstChild(operation, Wsdl20Namespace, "input");
291
+ const output = firstChild(operation, Wsdl20Namespace, "output");
292
+ return {
293
+ name: attribute(operation, "name") ?? "",
294
+ input: input === undefined ? undefined : refOf(input, "element"),
295
+ output: output === undefined ? undefined : refOf(output, "element"),
296
+ faults: childrenNamed(operation, Wsdl20Namespace, "outfault").map((fault) => {
297
+ const ref = refOf(fault, "ref");
298
+ return ref === undefined ? "" : splitClark(ref).local;
299
+ }),
300
+ documentation: wsdlDocumentation(operation, Wsdl20Namespace)
301
+ };
302
+ })
303
+ });
304
+ }
305
+ for (const binding of childrenNamed(root, Wsdl20Namespace, "binding")) {
306
+ const name = attribute(binding, "name");
307
+ if (name === undefined)
308
+ continue;
309
+ const actions = new Map();
310
+ for (const operation of childrenNamed(binding, Wsdl20Namespace, "operation")) {
311
+ const ref = refOf(operation, "ref");
312
+ if (ref !== undefined) {
313
+ actions.set(splitClark(ref).local, attribute(operation, "action", Wsdl20SoapNamespace) ?? "");
314
+ }
315
+ }
316
+ this.wsdl20.bindings.set(qname(targetNamespace, name), {
317
+ name,
318
+ interfaceName: refOf(binding, "interface"),
319
+ soap: attribute(binding, "type") === Wsdl20SoapNamespace,
320
+ soapVersion: attribute(binding, "version", Wsdl20SoapNamespace) === "1.1" ? "1.1" : "1.2",
321
+ actions
322
+ });
323
+ }
324
+ for (const service of childrenNamed(root, Wsdl20Namespace, "service")) {
325
+ const serviceName = attribute(service, "name") ?? "";
326
+ for (const endpoint of childrenNamed(service, Wsdl20Namespace, "endpoint")) {
327
+ this.wsdl20.endpoints.push({
328
+ service: serviceName,
329
+ name: attribute(endpoint, "name") ?? "",
330
+ binding: refOf(endpoint, "binding"),
331
+ address: attribute(endpoint, "address"),
332
+ location: `${safeLocation(location)}:${endpoint.line}`
333
+ });
334
+ }
335
+ }
336
+ }
337
+ }
338
+ const faultElement = (discovery, message) => {
339
+ if (message === undefined)
340
+ return undefined;
341
+ return discovery.wsdl11.messages.get(message)?.find((part) => part.element !== undefined)?.element;
342
+ };
343
+ const operations11 = (discovery, rootLocation) => {
344
+ const { bindings, portTypes, messages, ports } = discovery.wsdl11;
345
+ const operations = new Map();
346
+ const rejected = [];
347
+ // SOAP 1.1 bindings first: when a service offers both, the 1.1 binding
348
+ // names the operation's defaults, and the 1.2 one only adds an endpoint.
349
+ const ordered = [...bindings.values()].sort((left, right) => (left.soapVersion === "1.1" ? 0 : 1) - (right.soapVersion === "1.1" ? 0 : 1));
350
+ for (const binding of ordered) {
351
+ if (binding.soapVersion === undefined) {
352
+ discovery.question("unsupported-binding", binding.location, binding.name, `binding ${binding.name} is not a SOAP binding (HTTP or MIME bindings are out of scope)`);
353
+ continue;
354
+ }
355
+ const unsupported = binding.operations.filter((operation) => operation.style === "rpc" || operation.use === "encoded");
356
+ if (unsupported.length > 0) {
357
+ const styles = [...new Set(unsupported.map((op) => `${op.style}/${op.use}`))].join(", ");
358
+ rejected.push(`${binding.name} (${styles})`);
359
+ discovery.question("unsupported-binding", binding.location, binding.name, `binding ${binding.name} uses ${styles}; only document/literal is supported`);
360
+ continue;
361
+ }
362
+ const portType = binding.portType === undefined ? undefined : portTypes.get(binding.portType);
363
+ if (portType === undefined)
364
+ continue;
365
+ for (const bound of binding.operations) {
366
+ if (operations.has(bound.name))
367
+ continue;
368
+ const abstract = portType.find((operation) => operation.name === bound.name);
369
+ if (abstract === undefined)
370
+ continue;
371
+ const messageElement = (message, direction) => {
372
+ if (message === undefined)
373
+ return undefined;
374
+ const parts = messages.get(message) ?? [];
375
+ const selected = bound.bodyParts === undefined
376
+ ? parts
377
+ : parts.filter((part) => bound.bodyParts?.split(/\s+/).includes(part.name));
378
+ if (selected.length > 1) {
379
+ discovery.question("multi-part-message", binding.location, bound.name, `${direction} message ${splitClark(message).local} has ${selected.length} body parts; the first is used`);
380
+ }
381
+ const first = selected[0];
382
+ if (first !== undefined && first.element === undefined) {
383
+ discovery.question("non-literal-part", binding.location, bound.name, `${direction} part ${first.name} uses type= instead of element=; not document/literal`);
384
+ return undefined;
385
+ }
386
+ return first?.element;
387
+ };
388
+ const input = messageElement(abstract.input, "input");
389
+ if (input === undefined)
390
+ continue;
391
+ const output = messageElement(abstract.output, "output");
392
+ const inputType = discovery.schemas.elements.get(input)?.type;
393
+ const inputComplex = inputType === undefined ? undefined : discovery.schemas.complex.get(inputType);
394
+ operations.set(bound.name, new Operation({
395
+ name: bound.name,
396
+ soapAction: bound.soapAction,
397
+ soapVersion: binding.soapVersion,
398
+ binding: binding.name,
399
+ wrapped: splitClark(input).local === bound.name &&
400
+ inputComplex !== undefined &&
401
+ inputComplex.attributes.length === 0,
402
+ input,
403
+ ...(output === undefined ? {} : { output }),
404
+ faults: abstract.faults.flatMap((fault) => {
405
+ const element = faultElement(discovery, fault.message);
406
+ return element === undefined ? [] : [new FaultRef({ name: fault.name, element })];
407
+ }),
408
+ ...(abstract.documentation === undefined ? {} : { documentation: abstract.documentation })
409
+ }));
410
+ }
411
+ }
412
+ if (operations.size === 0) {
413
+ return Effect.fail(rejected.length > 0
414
+ ? new WsdlError({
415
+ reason: "unsupported-binding",
416
+ detail: `only document/literal bindings are supported; rejected: ${rejected.join("; ")}`,
417
+ location: safeLocation(rootLocation)
418
+ })
419
+ : new WsdlError({
420
+ reason: "no-operations",
421
+ detail: "no SOAP operation could be read from the WSDL",
422
+ location: safeLocation(rootLocation)
423
+ }));
424
+ }
425
+ const endpoints = ports.flatMap((port) => {
426
+ const binding = port.binding === undefined ? undefined : bindings.get(port.binding);
427
+ if (binding?.soapVersion === undefined)
428
+ return [];
429
+ if (port.address === undefined) {
430
+ discovery.question("missing-address", port.location, port.name, `port ${port.name} has no address`);
431
+ return [];
432
+ }
433
+ return [
434
+ new Endpoint({
435
+ service: port.service,
436
+ port: port.name,
437
+ binding: binding.name,
438
+ soapVersion: binding.soapVersion,
439
+ address: port.address
440
+ })
441
+ ];
442
+ });
443
+ return Effect.succeed({ operations: [...operations.values()], endpoints });
444
+ };
445
+ const operations20 = (discovery, rootLocation) => {
446
+ const { interfaces, bindings, endpoints } = discovery.wsdl20;
447
+ const operations = new Map();
448
+ for (const binding of bindings.values()) {
449
+ if (!binding.soap)
450
+ continue;
451
+ const iface = binding.interfaceName === undefined ? undefined : interfaces.get(binding.interfaceName);
452
+ if (iface === undefined)
453
+ continue;
454
+ for (const abstract of iface.operations) {
455
+ if (operations.has(abstract.name) || abstract.input === undefined)
456
+ continue;
457
+ const inputType = discovery.schemas.elements.get(abstract.input)?.type;
458
+ const inputComplex = inputType === undefined ? undefined : discovery.schemas.complex.get(inputType);
459
+ operations.set(abstract.name, new Operation({
460
+ name: abstract.name,
461
+ soapAction: binding.actions.get(abstract.name) ?? "",
462
+ soapVersion: binding.soapVersion,
463
+ binding: binding.name,
464
+ wrapped: splitClark(abstract.input).local === abstract.name &&
465
+ inputComplex !== undefined &&
466
+ inputComplex.attributes.length === 0,
467
+ input: abstract.input,
468
+ ...(abstract.output === undefined ? {} : { output: abstract.output }),
469
+ faults: abstract.faults.flatMap((name) => {
470
+ const element = iface.faults.get(name);
471
+ return element === undefined ? [] : [new FaultRef({ name, element })];
472
+ }),
473
+ ...(abstract.documentation === undefined ? {} : { documentation: abstract.documentation })
474
+ }));
475
+ }
476
+ }
477
+ if (operations.size === 0) {
478
+ return Effect.fail(new WsdlError({
479
+ reason: "no-operations",
480
+ detail: "no SOAP operation could be read from the WSDL 2.0 description",
481
+ location: safeLocation(rootLocation)
482
+ }));
483
+ }
484
+ return Effect.succeed({
485
+ operations: [...operations.values()],
486
+ endpoints: endpoints.flatMap((endpoint) => {
487
+ const binding = endpoint.binding === undefined ? undefined : bindings.get(endpoint.binding);
488
+ if (binding === undefined || !binding.soap || endpoint.address === undefined)
489
+ return [];
490
+ return [
491
+ new Endpoint({
492
+ service: endpoint.service,
493
+ port: endpoint.name,
494
+ binding: binding.name,
495
+ soapVersion: binding.soapVersion,
496
+ address: endpoint.address
497
+ })
498
+ ];
499
+ })
500
+ });
501
+ };
502
+ /**
503
+ * Discover a SOAP service: read the WSDL at `location` (a path or URL) with
504
+ * every document it imports, and return its typed catalog.
505
+ */
506
+ export const readCatalog = (location) => Effect.gen(function* () {
507
+ const loader = yield* DocumentLoader;
508
+ if (/^[a-z][a-z0-9+.-]*:\/\/[^/?#]*@/i.test(location)) {
509
+ return yield* new DocumentLoadError({
510
+ location: safeLocation(location),
511
+ detail: "credentials in the URL are refused; put references in auth.json"
512
+ });
513
+ }
514
+ // Normalized so the root is recognized when an import cycles back to it.
515
+ const rootLocation = isUrl(location) ? location : posix.normalize(location);
516
+ const discovery = new Discovery(loader);
517
+ const root = yield* discovery.wsdl(rootLocation);
518
+ const schemas = finishSchemas(discovery.schemas);
519
+ const read = root.version === "1.1"
520
+ ? yield* operations11(discovery, rootLocation)
521
+ : yield* operations20(discovery, rootLocation);
522
+ return new WsdlCatalog({
523
+ version: CatalogVersion,
524
+ wsdlVersion: root.version,
525
+ targetNamespace: root.targetNamespace,
526
+ documents: discovery.documents,
527
+ endpoints: read.endpoints,
528
+ operations: read.operations,
529
+ elements: schemas.elements,
530
+ types: schemas.types,
531
+ openQuestions: [...schemas.questions, ...discovery.questions]
532
+ });
533
+ });