@techspokes/typescript-wsdl-client 0.1.7

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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +245 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +94 -0
  6. package/dist/compiler/schemaCompiler.d.ts +50 -0
  7. package/dist/compiler/schemaCompiler.d.ts.map +1 -0
  8. package/dist/compiler/schemaCompiler.js +372 -0
  9. package/dist/config.d.ts +13 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +7 -0
  12. package/dist/emit/clientEmitter.d.ts +6 -0
  13. package/dist/emit/clientEmitter.d.ts.map +1 -0
  14. package/dist/emit/clientEmitter.js +31 -0
  15. package/dist/emit/metaEmitter.d.ts +4 -0
  16. package/dist/emit/metaEmitter.d.ts.map +1 -0
  17. package/dist/emit/metaEmitter.js +9 -0
  18. package/dist/emit/opsEmitter.d.ts +4 -0
  19. package/dist/emit/opsEmitter.d.ts.map +1 -0
  20. package/dist/emit/opsEmitter.js +13 -0
  21. package/dist/emit/runtimeEmitter.d.ts +2 -0
  22. package/dist/emit/runtimeEmitter.d.ts.map +1 -0
  23. package/dist/emit/runtimeEmitter.js +90 -0
  24. package/dist/emit/typesEmitter.d.ts +11 -0
  25. package/dist/emit/typesEmitter.d.ts.map +1 -0
  26. package/dist/emit/typesEmitter.js +91 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +46 -0
  30. package/dist/loader/fetch.d.ts +5 -0
  31. package/dist/loader/fetch.d.ts.map +1 -0
  32. package/dist/loader/fetch.js +19 -0
  33. package/dist/loader/wsdlLoader.d.ts +14 -0
  34. package/dist/loader/wsdlLoader.d.ts.map +1 -0
  35. package/dist/loader/wsdlLoader.js +88 -0
  36. package/dist/util/xml.d.ts +13 -0
  37. package/dist/util/xml.d.ts.map +1 -0
  38. package/dist/util/xml.js +40 -0
  39. package/dist/xsd/primitives.d.ts +8 -0
  40. package/dist/xsd/primitives.d.ts.map +1 -0
  41. package/dist/xsd/primitives.js +116 -0
  42. package/package.json +74 -0
@@ -0,0 +1,91 @@
1
+ import fs from "node:fs";
2
+ /**
3
+ * Emit TypeScript types from a compiled XSD catalog.
4
+ *
5
+ * - Text node is modeled as "$value" (not "value") to avoid collisions with real elements.
6
+ * - xs:complexType + xs:simpleContent/extension is flattened to `extends <BaseType>`.
7
+ * - All properties (attributes and elements) include @xsd JSDoc.
8
+ * - If a "$value" element is present, it is emitted last.
9
+ */
10
+ export function emitTypes(outFile, compiled) {
11
+ const lines = [];
12
+ // Convenience lookups
13
+ const typeNames = new Set(compiled.types.map((t) => t.name));
14
+ //
15
+ // 1) Named simple types (aliases) first
16
+ //
17
+ for (const a of compiled.aliases) {
18
+ const ann = a.jsdoc ? ` /** @xsd ${a.jsdoc} */\n` : "";
19
+ lines.push(`${ann}export type ${a.name} = ${a.tsType};`);
20
+ lines.push("");
21
+ }
22
+ //
23
+ // 2) Complex types as interfaces
24
+ //
25
+ for (const t of compiled.types) {
26
+ // Detect mis-mapped simpleContent extension:
27
+ // single "$value" whose tsType is another named interface ⇒ extend that interface
28
+ const valueElems = (t.elems || []).filter((e) => e.name === "$value" &&
29
+ (e.max === 1 || e.max === undefined) &&
30
+ typeof e.tsType === "string" &&
31
+ typeNames.has(e.tsType));
32
+ const isSimpleContentExtension = (t.elems?.length || 0) === 1 && valueElems.length === 1;
33
+ const baseName = isSimpleContentExtension ? valueElems[0].tsType : undefined;
34
+ // Header
35
+ if (baseName) {
36
+ lines.push(`export interface ${t.name} extends ${baseName} {`);
37
+ }
38
+ else {
39
+ lines.push(`export interface ${t.name} {`);
40
+ }
41
+ //
42
+ // Attributes — with JSDoc on every attribute
43
+ //
44
+ for (const a of t.attrs || []) {
45
+ const opt = a.use === "required" ? "" : "?";
46
+ const annObj = {
47
+ kind: "attribute",
48
+ type: a.declaredType,
49
+ use: a.use || "optional",
50
+ };
51
+ const ann = ` /** @xsd ${JSON.stringify(annObj)} */`;
52
+ lines.push(ann);
53
+ lines.push(` ${a.name}${opt}: ${a.tsType};`);
54
+ }
55
+ //
56
+ // Elements — with JSDoc on every element
57
+ //
58
+ const elementsToEmit = [...(t.elems || [])];
59
+ // If we detected simpleContent extension, drop the synthetic $value (we're extending instead).
60
+ if (isSimpleContentExtension) {
61
+ const idx = elementsToEmit.findIndex((e) => e.name === "$value");
62
+ if (idx >= 0)
63
+ elementsToEmit.splice(idx, 1);
64
+ }
65
+ // Ensure "$value" is last if present
66
+ elementsToEmit.sort((a, b) => {
67
+ if (a.name === "$value" && b.name !== "$value")
68
+ return 1;
69
+ if (a.name !== "$value" && b.name === "$value")
70
+ return -1;
71
+ return 0;
72
+ });
73
+ for (const e of elementsToEmit) {
74
+ const isArray = e.max === "unbounded" ||
75
+ (typeof e.max === "number" && e.max > 1);
76
+ const arr = isArray ? "[]" : "";
77
+ const opt = (e.min ?? 0) === 0 ? "?" : "";
78
+ const annObj = {
79
+ kind: "element",
80
+ type: e.declaredType,
81
+ occurs: { min: e.min, max: e.max, nillable: e.nillable ?? false },
82
+ };
83
+ const ann = ` /** @xsd ${JSON.stringify(annObj)} */`;
84
+ lines.push(ann);
85
+ lines.push(` ${e.name}${opt}: ${e.tsType}${arr};`);
86
+ }
87
+ lines.push("}");
88
+ lines.push("");
89
+ }
90
+ fs.writeFileSync(outFile, lines.join("\n"), "utf8");
91
+ }
@@ -0,0 +1,11 @@
1
+ import type { CompilerOptions } from "./config.js";
2
+ export declare function compileWsdlToProject(input: {
3
+ wsdl: string;
4
+ outDir: string;
5
+ options?: CompilerOptions;
6
+ }): Promise<void>;
7
+ export { compileCatalog } from "./compiler/schemaCompiler.js";
8
+ export type { PrimitiveOptions } from "./xsd/primitives.js";
9
+ export { xsdToTsPrimitive } from "./xsd/primitives.js";
10
+ export type { CompilerOptions } from "./config.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAYnD,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,eAAe,CAAA;CAAE,iBAiC5G;AAID,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ import { defaultOptions } from "./config.js";
2
+ import { loadWsdl } from "./loader/wsdlLoader.js";
3
+ import { compileCatalog } from "./compiler/schemaCompiler.js";
4
+ import { emitTypes } from "./emit/typesEmitter.js";
5
+ import { emitMeta } from "./emit/metaEmitter.js";
6
+ import { emitOperations } from "./emit/opsEmitter.js";
7
+ import { emitClient } from "./emit/clientEmitter.js";
8
+ import { emitRuntime } from "./emit/runtimeEmitter.js";
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ // noinspection JSUnusedGlobalSymbols
12
+ export async function compileWsdlToProject(input) {
13
+ const opts = { ...defaultOptions, ...(input.options || {}) };
14
+ // Backward-compat: map legacy flags to primitive preferences unless explicitly provided
15
+ const primitive = { ...(opts.primitive || {}) };
16
+ if (opts.dateAs && primitive.dateAs == null)
17
+ primitive.dateAs = opts.dateAs === "date" ? "Date" : "string";
18
+ if (opts.intAs && (primitive.int64As == null || primitive.bigIntegerAs == null)) {
19
+ const as = opts.intAs; // "number" | "string"
20
+ if (primitive.int64As == null)
21
+ primitive.int64As = as;
22
+ if (primitive.bigIntegerAs == null)
23
+ primitive.bigIntegerAs = as;
24
+ }
25
+ const mergedOpts = { ...opts, primitive };
26
+ const catalog = await loadWsdl(input.wsdl);
27
+ console.log(`Loaded WSDL: ${catalog.wsdlUri}`);
28
+ const compiled = compileCatalog(catalog, mergedOpts);
29
+ console.log(`Schemas discovered: ${catalog.schemas.length}`);
30
+ console.log(`Compiled types: ${compiled.types.length}`);
31
+ console.log(`Operations: ${compiled.operations.length}`);
32
+ fs.mkdirSync(input.outDir, { recursive: true });
33
+ const typesPath = path.join(input.outDir, "types.ts");
34
+ const metaPath = path.join(input.outDir, "meta.ts");
35
+ const opsPath = path.join(input.outDir, "operations.json");
36
+ const clientPath = path.join(input.outDir, "client.ts");
37
+ const runtimePath = path.join(input.outDir, "runtime.ts");
38
+ emitTypes(typesPath, compiled);
39
+ emitMeta(metaPath, compiled, mergedOpts);
40
+ emitOperations(opsPath, compiled);
41
+ emitClient(clientPath, compiled, mergedOpts);
42
+ emitRuntime(path.join(runtimePath, "runtime.ts"));
43
+ }
44
+ // Public API re-exports for library users
45
+ export { compileCatalog } from "./compiler/schemaCompiler.js";
46
+ export { xsdToTsPrimitive } from "./xsd/primitives.js";
@@ -0,0 +1,5 @@
1
+ export declare function fetchText(urlOrPath: string, base?: string): Promise<{
2
+ uri: string;
3
+ text: string;
4
+ }>;
5
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/loader/fetch.ts"],"names":[],"mappings":"AAGA,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAcxG"}
@@ -0,0 +1,19 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export async function fetchText(urlOrPath, base) {
4
+ let uri = urlOrPath;
5
+ if (base && !/^https?:/i.test(urlOrPath) && !path.isAbsolute(urlOrPath)) {
6
+ uri = path.resolve(base, urlOrPath);
7
+ }
8
+ if (/^https?:/i.test(uri)) {
9
+ const res = await fetch(uri, { headers: { Accept: "application/xml,text/xml,*/*" } });
10
+ if (!res.ok)
11
+ throw new Error(`HTTP ${res.status} ${res.statusText} for ${uri}`);
12
+ const text = await res.text();
13
+ return { uri, text };
14
+ }
15
+ else {
16
+ const text = fs.readFileSync(uri, "utf8");
17
+ return { uri, text };
18
+ }
19
+ }
@@ -0,0 +1,14 @@
1
+ export type SchemaDoc = {
2
+ uri: string;
3
+ xml: any;
4
+ targetNS: string;
5
+ prefixes: Record<string, string>;
6
+ };
7
+ export type WsdlCatalog = {
8
+ wsdlUri: string;
9
+ wsdlXml: any;
10
+ schemas: SchemaDoc[];
11
+ prefixMap: Record<string, string>;
12
+ };
13
+ export declare function loadWsdl(wsdlUrlOrPath: string): Promise<WsdlCatalog>;
14
+ //# sourceMappingURL=wsdlLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wsdlLoader.d.ts","sourceRoot":"","sources":["../../src/loader/wsdlLoader.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,SAAS,GAAG;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC;IACb,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC,CAAC;AAIF,wBAAsB,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA6B1E"}
@@ -0,0 +1,88 @@
1
+ import { XMLParser } from "fast-xml-parser";
2
+ import { fetchText } from "./fetch.js";
3
+ import path from "node:path";
4
+ // noinspection ES6UnusedImports
5
+ import { getChildrenWithLocalName, normalizeArray } from "../util/xml.js";
6
+ const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_" });
7
+ export async function loadWsdl(wsdlUrlOrPath) {
8
+ const { uri: wsdlUri, text } = await fetchText(wsdlUrlOrPath);
9
+ const wsdlXml = parser.parse(text);
10
+ const defs = wsdlXml["wsdl:definitions"] || wsdlXml["definitions"];
11
+ if (!defs)
12
+ throw new Error("Not a WSDL 1.1 file: missing wsdl:definitions");
13
+ // WSDL-level prefixes
14
+ const prefixMap = {};
15
+ for (const [k, v] of Object.entries(defs)) {
16
+ if (k.startsWith("@_xmlns:"))
17
+ prefixMap[k.slice(8)] = String(v);
18
+ }
19
+ const tns = defs["@_targetNamespace"] || "";
20
+ const types = defs["wsdl:types"] || defs["types"];
21
+ const schemas = [];
22
+ const visited = new Set(); // de-dupe fetched schema docs by absolute URL/path
23
+ if (types) {
24
+ // Find any <xsd:schema> (prefix-agnostic) directly under wsdl:types
25
+ const rawSchemas = getChildrenWithLocalName(types, "schema");
26
+ const baseDir = path.dirname(wsdlUri);
27
+ for (const s of rawSchemas) {
28
+ const discovered = await resolveSchema(s, baseDir, visited);
29
+ schemas.push(...discovered);
30
+ }
31
+ }
32
+ console.log(`[wsdl] types->schemas: ${schemas.length}`);
33
+ return { wsdlUri, wsdlXml, schemas, prefixMap: { ...prefixMap, tns } };
34
+ }
35
+ async function resolveSchema(schemaNode, baseDir, visited) {
36
+ const out = [];
37
+ const targetNS = schemaNode["@_targetNamespace"] || "";
38
+ const prefixes = {};
39
+ for (const [k, v] of Object.entries(schemaNode)) {
40
+ if (k.startsWith("@_xmlns:"))
41
+ prefixes[k.slice(8)] = String(v);
42
+ }
43
+ // Record the inlined schema from the WSDL (no own URI; inherit baseDir)
44
+ out.push({ uri: baseDir, xml: schemaNode, targetNS, prefixes });
45
+ // includes/imports (prefix-agnostic)
46
+ for (const inc of getChildrenWithLocalName(schemaNode, "include")) {
47
+ const loc = inc?.["@_schemaLocation"];
48
+ if (!loc)
49
+ continue;
50
+ const more = await fetchAndResolveSchemaDoc(loc, baseDir, visited);
51
+ out.push(...more);
52
+ }
53
+ for (const imp of getChildrenWithLocalName(schemaNode, "import")) {
54
+ const loc = imp?.["@_schemaLocation"];
55
+ if (!loc)
56
+ continue;
57
+ const more = await fetchAndResolveSchemaDoc(loc, baseDir, visited);
58
+ out.push(...more);
59
+ }
60
+ return out;
61
+ }
62
+ async function fetchAndResolveSchemaDoc(schemaLocation, baseDir, visited) {
63
+ const { uri, text } = await fetchText(schemaLocation, baseDir);
64
+ const docKey = uri;
65
+ if (visited.has(docKey))
66
+ return [];
67
+ visited.add(docKey);
68
+ const sx = parser.parse(text);
69
+ // The fetched document may contain one or more <schema> nodes at the root (prefix-agnostic).
70
+ const roots = getChildrenWithLocalName(sx, "schema");
71
+ const docs = [];
72
+ for (const sn of roots) {
73
+ const tns = sn["@_targetNamespace"] || "";
74
+ const prefixes = {};
75
+ for (const [k, v] of Object.entries(sn)) {
76
+ if (k.startsWith("@_xmlns:"))
77
+ prefixes[k.slice(8)] = String(v);
78
+ }
79
+ const thisDir = path.dirname(uri);
80
+ docs.push({ uri: thisDir, xml: sn, targetNS: tns, prefixes });
81
+ // Recurse into nested includes/imports of this external schema
82
+ const nested = await resolveSchema(sn, thisDir, visited);
83
+ // resolveSchema already pushed the 'sn'; but here we’ve just pushed it ourselves.
84
+ // Merge only the *children* discovered by resolveSchema to avoid duplicate heads.
85
+ docs.push(...nested.slice(1));
86
+ }
87
+ return docs;
88
+ }
@@ -0,0 +1,13 @@
1
+ /** Normalize a possibly-single value into an array. */
2
+ export declare function normalizeArray<T>(x: T | T[] | undefined | null): T[];
3
+ /** Collect direct children whose LOCAL name matches (prefix-agnostic). */
4
+ export declare function getChildrenWithLocalName(node: any, local: string): any[];
5
+ /** Return the first direct child whose LOCAL name matches (prefix-agnostic). */
6
+ export declare function getFirstWithLocalName(node: any, local: string): any | undefined;
7
+ /** Simple PascalCase helper used across emitters/parsers. */
8
+ export declare function pascal(s: string): string;
9
+ export declare function resolveQName(qname: string, defaultNS: string, prefixes: Record<string, string>): {
10
+ ns: string;
11
+ local: string;
12
+ };
13
+ //# sourceMappingURL=xml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../../src/util/xml.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,GAAG,IAAI,GAAG,CAAC,EAAE,CAGpE;AAED,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,CASxE;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAK/E;AAED,6DAA6D;AAC7D,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAS/B"}
@@ -0,0 +1,40 @@
1
+ /** Normalize a possibly-single value into an array. */
2
+ export function normalizeArray(x) {
3
+ if (x == null)
4
+ return [];
5
+ return Array.isArray(x) ? x : [x];
6
+ }
7
+ /** Collect direct children whose LOCAL name matches (prefix-agnostic). */
8
+ export function getChildrenWithLocalName(node, local) {
9
+ const out = [];
10
+ for (const [k, v] of Object.entries(node || {})) {
11
+ if (k === local || k.endsWith(`:${local}`)) {
12
+ const arr = Array.isArray(v) ? v : [v];
13
+ out.push(...arr.filter(Boolean));
14
+ }
15
+ }
16
+ return out;
17
+ }
18
+ /** Return the first direct child whose LOCAL name matches (prefix-agnostic). */
19
+ export function getFirstWithLocalName(node, local) {
20
+ for (const [k, v] of Object.entries(node || {})) {
21
+ if (k === local || k.endsWith(`:${local}`))
22
+ return v;
23
+ }
24
+ return undefined;
25
+ }
26
+ /** Simple PascalCase helper used across emitters/parsers. */
27
+ export function pascal(s) {
28
+ return s.replace(/(^|[_\-\s])(\w)/g, (_, __, c) => c.toUpperCase()).replace(/[^A-Za-z0-9]/g, "");
29
+ }
30
+ export function resolveQName(qname, defaultNS, prefixes) {
31
+ if (!qname)
32
+ return { ns: defaultNS, local: "" };
33
+ const parts = qname.split(":");
34
+ if (parts.length === 2) {
35
+ const [prefix, local] = parts;
36
+ const ns = prefixes[prefix] || defaultNS;
37
+ return { ns, local };
38
+ }
39
+ return { ns: defaultNS, local: qname };
40
+ }
@@ -0,0 +1,8 @@
1
+ export type PrimitiveOptions = {
2
+ int64As?: "string" | "number" | "bigint";
3
+ bigIntegerAs?: "string" | "number";
4
+ decimalAs?: "string" | "number";
5
+ dateAs?: "string" | "Date";
6
+ };
7
+ export declare function xsdToTsPrimitive(xsdQName: string, options?: PrimitiveOptions): string;
8
+ //# sourceMappingURL=primitives.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../src/xsd/primitives.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,gBAAgB,GAAG;IAC3B,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACzC,YAAY,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACnC,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAChC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAC9B,CAAC;AA+EF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAwCrF"}
@@ -0,0 +1,116 @@
1
+ // Centralized XSD → TypeScript primitive mapping with safe defaults.
2
+ // Rationale:
3
+ // - 64-bit integers (long/unsignedLong) can overflow JS number → default to string
4
+ // - Arbitrary-precision decimal (money) may lose precision in JS number → default to string
5
+ // - Dates/times stay as string by default (no runtime Date parsing in a generator)
6
+ const DEFAULTS = {
7
+ int64As: "string",
8
+ bigIntegerAs: "string",
9
+ decimalAs: "string",
10
+ dateAs: "string",
11
+ };
12
+ // Helper to choose ts type for integer families depending on magnitude
13
+ function intFamily(local, opts) {
14
+ // 64-bit or unbounded families → configurable
15
+ const int64 = new Set(["long", "unsignedLong"]);
16
+ const big = new Set([
17
+ "integer",
18
+ "nonPositiveInteger",
19
+ "negativeInteger",
20
+ "nonNegativeInteger",
21
+ "positiveInteger",
22
+ ]);
23
+ if (int64.has(local)) {
24
+ return opts.int64As === "bigint" ? "bigint" : opts.int64As;
25
+ }
26
+ if (big.has(local)) {
27
+ return opts.bigIntegerAs;
28
+ }
29
+ // Safe 32-bit families → number
30
+ const int32 = new Set(["int", "unsignedInt"]);
31
+ const int16 = new Set(["short", "unsignedShort"]);
32
+ const int8 = new Set(["byte", "unsignedByte"]);
33
+ if (int32.has(local) || int16.has(local) || int8.has(local))
34
+ return "number";
35
+ return "number";
36
+ }
37
+ function decimalFamily(local, opts) {
38
+ // xs:decimal and derived decimals (if any) → configurable
39
+ if (local === "decimal")
40
+ return opts.decimalAs;
41
+ return "number";
42
+ }
43
+ function dateFamily(local, opts) {
44
+ // You can choose "Date", but generator won’t parse at runtime; it’s just type-level.
45
+ const s = opts.dateAs;
46
+ switch (local) {
47
+ case "date":
48
+ case "dateTime":
49
+ case "time":
50
+ case "gYear":
51
+ case "gYearMonth":
52
+ case "gMonth":
53
+ case "gMonthDay":
54
+ case "gDay":
55
+ case "dateTimeStamp": // XSD 1.1
56
+ case "duration":
57
+ case "dayTimeDuration": // XSD 1.1
58
+ case "yearMonthDuration": // XSD 1.1
59
+ return s;
60
+ default:
61
+ return "string";
62
+ }
63
+ }
64
+ const STRING_LIKE = new Set([
65
+ "string",
66
+ "normalizedString",
67
+ "token",
68
+ "language",
69
+ "Name",
70
+ "NCName",
71
+ "NMTOKEN", "NMTOKENS",
72
+ "ID", "IDREF", "IDREFS",
73
+ "ENTITY", "ENTITIES",
74
+ "anyURI",
75
+ "QName",
76
+ "NOTATION",
77
+ "hexBinary",
78
+ "base64Binary", // could be "string" or a branded type
79
+ ]);
80
+ export function xsdToTsPrimitive(xsdQName, options) {
81
+ const opts = { ...DEFAULTS, ...(options || {}) };
82
+ // Expect formats like "xs:int". Fall back to string if unknown.
83
+ const m = /^([a-zA-Z0-9_]+:)?([A-Za-z0-9_]+)$/.exec(xsdQName);
84
+ const local = m ? m[2] : xsdQName;
85
+ if (STRING_LIKE.has(local))
86
+ return "string";
87
+ if (local === "boolean")
88
+ return "boolean";
89
+ // Numerics
90
+ if (local === "byte" || local === "unsignedByte" ||
91
+ local === "short" || local === "unsignedShort" ||
92
+ local === "int" || local === "unsignedInt" ||
93
+ local === "long" || local === "unsignedLong" ||
94
+ local === "integer" ||
95
+ local === "nonNegativeInteger" || local === "positiveInteger" ||
96
+ local === "nonPositiveInteger" || local === "negativeInteger") {
97
+ return intFamily(local, opts);
98
+ }
99
+ if (local === "decimal")
100
+ return decimalFamily(local, opts);
101
+ if (local === "float" || local === "double")
102
+ return "number";
103
+ // Dates/times & durations
104
+ if (local === "date" || local === "dateTime" || local === "time" ||
105
+ local === "gYear" || local === "gYearMonth" || local === "gMonth" ||
106
+ local === "gMonthDay" || local === "gDay" ||
107
+ local === "dateTimeStamp" || local === "duration" ||
108
+ local === "dayTimeDuration" || local === "yearMonthDuration") {
109
+ return dateFamily(local, opts);
110
+ }
111
+ // anyType/anySimpleType → unknown (or 'any' if you prefer)
112
+ if (local === "anyType" || local === "anySimpleType")
113
+ return "unknown";
114
+ // Default fallback
115
+ return "string";
116
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@techspokes/typescript-wsdl-client",
3
+ "version": "0.1.7",
4
+ "description": "TypeScript WSDL → SOAP client generator with full xs:attribute support, complex types, sequences, inheritance, and namespace-collision merging.",
5
+ "keywords": [
6
+ "wsdl",
7
+ "soap",
8
+ "typescript",
9
+ "codegen",
10
+ "generator",
11
+ "xml",
12
+ "xsd"
13
+ ],
14
+ "homepage": "https://github.com/techspokes/typescript-wsdl-client#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/techspokes/typescript-wsdl-client/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/techspokes/typescript-wsdl-client.git"
21
+ },
22
+ "license": "MIT",
23
+ "author": "TechSpokes <contact@techspokes.com> (https://www.techspokes.com)",
24
+ "contributors": [
25
+ "Serge Liatko <contact@techspokes.com> (https://github.com/sergeliatko)"
26
+ ],
27
+ "type": "module",
28
+ "engines": {
29
+ "node": ">=20.0.0"
30
+ },
31
+ "bin": {
32
+ "wsdl-tsc": "dist/cli.js"
33
+ },
34
+ "main": "dist/index.js",
35
+ "types": "dist/index.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "default": "./dist/index.js"
40
+ }
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "scripts": {
48
+ "build": "tsc -p tsconfig.json",
49
+ "clean": "rimraf dist",
50
+ "dev": "tsx src/cli.ts",
51
+ "watch": "tsx watch src/cli.ts",
52
+ "typecheck": "tsc --noEmit",
53
+ "prepublishOnly": "npm run clean && npm run build",
54
+ "test": "exit 0",
55
+ "smoke": "tsx src/cli.ts --help",
56
+ "smoke:gen": "rimraf tmp && tsx src/cli.ts --wsdl examples/minimal/echo.wsdl --out tmp && tsc --noEmit --module NodeNext --moduleResolution NodeNext --target ES2022 tmp/types.ts tmp/meta.ts tmp/operations.ts tmp/runtime.ts tmp/client.ts src/types/soap.d.ts",
57
+ "ci": "npm run build && npm run typecheck && npm run smoke && npm run smoke:gen"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^24.3.0",
61
+ "@types/yargs": "^17.0.33",
62
+ "rimraf": "^6.0.0",
63
+ "tsx": "^4.20.0",
64
+ "typescript": "^5.6.3"
65
+ },
66
+ "dependencies": {
67
+ "fast-xml-parser": "^5.2.5",
68
+ "yargs": "^18.0.0"
69
+ },
70
+ "funding": {
71
+ "type": "github",
72
+ "url": "https://github.com/techspokes"
73
+ }
74
+ }