@hsuite/smart-engines-sdk 3.2.0 → 3.2.1

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.
@@ -0,0 +1,99 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ import { z } from 'zod';
4
+
5
+ export declare const PqcCertV1Schema: z.ZodObject<{
6
+ pqcCertVersion: z.ZodLiteral<1>;
7
+ algorithm: z.ZodLiteral<"ml-dsa-87">;
8
+ consumer: z.ZodString;
9
+ contentHash: z.ZodString;
10
+ payloadHash: z.ZodOptional<z.ZodString>;
11
+ threshold: z.ZodNumber;
12
+ signers: z.ZodArray<z.ZodObject<{
13
+ nodeId: z.ZodString;
14
+ dilithium5PublicKey: z.ZodString;
15
+ signature: z.ZodString;
16
+ }, "strip", z.ZodTypeAny, {
17
+ nodeId: string;
18
+ dilithium5PublicKey: string;
19
+ signature: string;
20
+ }, {
21
+ nodeId: string;
22
+ dilithium5PublicKey: string;
23
+ signature: string;
24
+ }>, "atleastone">;
25
+ ts: z.ZodNumber;
26
+ topicMessageId: z.ZodOptional<z.ZodString>;
27
+ }, "strict", z.ZodTypeAny, {
28
+ pqcCertVersion: 1;
29
+ algorithm: "ml-dsa-87";
30
+ consumer: string;
31
+ contentHash: string;
32
+ threshold: number;
33
+ signers: [
34
+ {
35
+ nodeId: string;
36
+ dilithium5PublicKey: string;
37
+ signature: string;
38
+ },
39
+ ...{
40
+ nodeId: string;
41
+ dilithium5PublicKey: string;
42
+ signature: string;
43
+ }[]
44
+ ];
45
+ ts: number;
46
+ payloadHash?: string | undefined;
47
+ topicMessageId?: string | undefined;
48
+ }, {
49
+ pqcCertVersion: 1;
50
+ algorithm: "ml-dsa-87";
51
+ consumer: string;
52
+ contentHash: string;
53
+ threshold: number;
54
+ signers: [
55
+ {
56
+ nodeId: string;
57
+ dilithium5PublicKey: string;
58
+ signature: string;
59
+ },
60
+ ...{
61
+ nodeId: string;
62
+ dilithium5PublicKey: string;
63
+ signature: string;
64
+ }[]
65
+ ];
66
+ ts: number;
67
+ payloadHash?: string | undefined;
68
+ topicMessageId?: string | undefined;
69
+ }>;
70
+ export type PqcCertV1 = z.infer<typeof PqcCertV1Schema>;
71
+ export type PqcCertV1Signer = PqcCertV1["signers"][number];
72
+ export declare function parsePqcCert(input: unknown): {
73
+ ok: true;
74
+ cert: PqcCertV1;
75
+ } | {
76
+ ok: false;
77
+ error: string;
78
+ };
79
+ export type ValidatorRegistrySnapshot = {
80
+ readonly snapshotTs: number;
81
+ readonly validators: readonly {
82
+ readonly nodeId: string;
83
+ readonly dilithium5PublicKey: string;
84
+ readonly active: boolean;
85
+ }[];
86
+ };
87
+ export type FetchRegistryOptions = {
88
+ readonly timeoutMs?: number;
89
+ };
90
+ export declare function fetchRegistrySnapshot(smartHostUrl: string, opts?: FetchRegistryOptions): Promise<ValidatorRegistrySnapshot | null>;
91
+ export type VerifyResult = {
92
+ readonly valid: boolean;
93
+ readonly reason?: string;
94
+ readonly verifiedSignerCount?: number;
95
+ readonly thresholdMet?: boolean;
96
+ };
97
+ export declare function verifyPqcAttestation(cert: unknown, payload: unknown, registrySnapshot?: ValidatorRegistrySnapshot): Promise<VerifyResult>;
98
+
99
+ export {};
@@ -0,0 +1,167 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+ var mlDsa = require('@noble/post-quantum/ml-dsa');
5
+ var sha256 = require('@noble/hashes/sha256');
6
+
7
+ // src/pqc-verify/cert-schema.ts
8
+ var PqcCertV1Schema = zod.z.object({
9
+ pqcCertVersion: zod.z.literal(1),
10
+ algorithm: zod.z.literal("ml-dsa-87"),
11
+ consumer: zod.z.string().regex(/^[a-z][a-z0-9-]{0,63}$/),
12
+ contentHash: zod.z.string().regex(/^[a-f0-9]{64}$/),
13
+ payloadHash: zod.z.string().regex(/^[a-f0-9]{64}$/).optional(),
14
+ threshold: zod.z.number().int().positive(),
15
+ signers: zod.z.array(
16
+ zod.z.object({
17
+ nodeId: zod.z.string().min(1),
18
+ dilithium5PublicKey: zod.z.string().min(1),
19
+ signature: zod.z.string().min(1)
20
+ })
21
+ ).nonempty(),
22
+ ts: zod.z.number().int().positive(),
23
+ topicMessageId: zod.z.string().optional()
24
+ }).strict();
25
+ function parsePqcCert(input) {
26
+ const result = PqcCertV1Schema.safeParse(input);
27
+ if (!result.success) {
28
+ return { ok: false, error: result.error.issues[0]?.message ?? "schema-invalid" };
29
+ }
30
+ return { ok: true, cert: result.data };
31
+ }
32
+
33
+ // src/pqc-verify/registry-fetch.ts
34
+ async function fetchRegistrySnapshot(smartHostUrl, opts = {}) {
35
+ const controller = new AbortController();
36
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 1e4);
37
+ try {
38
+ const url = `${smartHostUrl.replace(/\/+$/, "")}/api/registry/snapshot`;
39
+ const resp = await fetch(url, { signal: controller.signal });
40
+ if (!resp.ok) return null;
41
+ const data = await resp.json();
42
+ if (typeof data !== "object" || data === null) return null;
43
+ const obj = data;
44
+ if (typeof obj.snapshotTs !== "number" || !Array.isArray(obj.validators)) {
45
+ return null;
46
+ }
47
+ return obj;
48
+ } catch {
49
+ return null;
50
+ } finally {
51
+ clearTimeout(timer);
52
+ }
53
+ }
54
+ function canonicalJsonStringify(value) {
55
+ return _serialize(value);
56
+ }
57
+ function _serialize(value) {
58
+ if (value === null) return "null";
59
+ if (typeof value === "boolean") return value ? "true" : "false";
60
+ if (typeof value === "number") {
61
+ if (!Number.isFinite(value)) {
62
+ throw new Error(`canonicalJsonStringify: non-finite number ${value}`);
63
+ }
64
+ return JSON.stringify(value);
65
+ }
66
+ if (typeof value === "string") return JSON.stringify(value);
67
+ if (typeof value === "undefined") {
68
+ throw new Error("canonicalJsonStringify: undefined is not JSON-representable");
69
+ }
70
+ if (Array.isArray(value)) {
71
+ return "[" + value.map(_serialize).join(",") + "]";
72
+ }
73
+ if (typeof value === "object") {
74
+ const obj = value;
75
+ const keys = Object.keys(obj).sort();
76
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + _serialize(obj[k])).join(",") + "}";
77
+ }
78
+ throw new Error(`canonicalJsonStringify: unsupported type ${typeof value}`);
79
+ }
80
+ function hexToBytes(hex) {
81
+ if (hex.length % 2 !== 0) throw new Error("odd-length hex");
82
+ const out = new Uint8Array(hex.length / 2);
83
+ for (let i = 0; i < out.length; i++) {
84
+ const b = parseInt(hex.substr(i * 2, 2), 16);
85
+ if (Number.isNaN(b)) throw new Error("non-hex char");
86
+ out[i] = b;
87
+ }
88
+ return out;
89
+ }
90
+ function bytesToHex(bytes) {
91
+ let s = "";
92
+ for (let i = 0; i < bytes.length; i++) {
93
+ s += bytes[i].toString(16).padStart(2, "0");
94
+ }
95
+ return s;
96
+ }
97
+ async function verifyPqcAttestation(cert, payload, registrySnapshot) {
98
+ const parsed = parsePqcCert(cert);
99
+ if (!parsed.ok) {
100
+ return { valid: false, reason: `schema-invalid: ${parsed.error}` };
101
+ }
102
+ const c = parsed.cert;
103
+ let computedContentHash;
104
+ try {
105
+ const canonical = canonicalJsonStringify(payload);
106
+ computedContentHash = bytesToHex(sha256.sha256(new TextEncoder().encode(canonical)));
107
+ } catch (err) {
108
+ return { valid: false, reason: `payload-canonicalization-failed: ${err.message}` };
109
+ }
110
+ if (computedContentHash.toLowerCase() !== c.contentHash.toLowerCase()) {
111
+ return {
112
+ valid: false,
113
+ reason: `content-hash-mismatch: computed=${computedContentHash} cert=${c.contentHash}`
114
+ };
115
+ }
116
+ if (registrySnapshot) {
117
+ for (const signer of c.signers) {
118
+ const entry = registrySnapshot.validators.find((v) => v.nodeId === signer.nodeId);
119
+ if (!entry) {
120
+ return { valid: false, reason: `registry-unknown-signer: ${signer.nodeId}` };
121
+ }
122
+ if (!entry.active) {
123
+ return { valid: false, reason: `registry-inactive-signer: ${signer.nodeId}` };
124
+ }
125
+ if (entry.dilithium5PublicKey.toLowerCase() !== signer.dilithium5PublicKey.toLowerCase()) {
126
+ return {
127
+ valid: false,
128
+ reason: `registry-pubkey-mismatch: ${signer.nodeId}`
129
+ };
130
+ }
131
+ }
132
+ }
133
+ const sigDigestInput = new TextEncoder().encode(`${c.consumer}|${c.contentHash}|${c.ts}`);
134
+ const sigDigest = sha256.sha256(sigDigestInput);
135
+ let verifiedSignerCount = 0;
136
+ for (const signer of c.signers) {
137
+ try {
138
+ const pubKey = hexToBytes(signer.dilithium5PublicKey);
139
+ const sigBytes = hexToBytes(signer.signature);
140
+ if (mlDsa.ml_dsa87.verify(pubKey, sigDigest, sigBytes)) {
141
+ verifiedSignerCount++;
142
+ }
143
+ } catch {
144
+ }
145
+ }
146
+ const thresholdMet = verifiedSignerCount >= c.threshold;
147
+ if (!thresholdMet) {
148
+ return {
149
+ valid: false,
150
+ reason: `threshold-not-met: verified=${verifiedSignerCount} required=${c.threshold}`,
151
+ verifiedSignerCount,
152
+ thresholdMet: false
153
+ };
154
+ }
155
+ return {
156
+ valid: true,
157
+ verifiedSignerCount,
158
+ thresholdMet: true
159
+ };
160
+ }
161
+
162
+ exports.PqcCertV1Schema = PqcCertV1Schema;
163
+ exports.fetchRegistrySnapshot = fetchRegistrySnapshot;
164
+ exports.parsePqcCert = parsePqcCert;
165
+ exports.verifyPqcAttestation = verifyPqcAttestation;
166
+ //# sourceMappingURL=index.js.map
167
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pqc-verify/cert-schema.ts","../../src/pqc-verify/registry-fetch.ts","../../src/pqc-verify/verify-pqc-attestation.ts"],"names":["z","sha256","ml_dsa87"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * PQC Arc 11 PR 11.0 — Customer-facing PqcCertV1 Zod schema + parser.\n *\n * Cert schema is locked at `pqcCertVersion: 1` per design §3 — additive-only\n * migration policy keeps existing customer SDKs verifying for the deprecation\n * window (12 months minimum) when v2 ships.\n *\n * @see docs/superpowers/specs/2026-05-27-pqc-arc11-external-verifier-design.md §3\n * @see docs/superpowers/plans/2026-05-27-pqc-arc11-external-verifier-plan.md §11.0.1\n */\nimport { z } from 'zod';\n\nexport const PqcCertV1Schema = z\n .object({\n pqcCertVersion: z.literal(1),\n algorithm: z.literal('ml-dsa-87'),\n consumer: z.string().regex(/^[a-z][a-z0-9-]{0,63}$/),\n contentHash: z.string().regex(/^[a-f0-9]{64}$/),\n payloadHash: z\n .string()\n .regex(/^[a-f0-9]{64}$/)\n .optional(),\n threshold: z.number().int().positive(),\n signers: z\n .array(\n z.object({\n nodeId: z.string().min(1),\n dilithium5PublicKey: z.string().min(1),\n signature: z.string().min(1),\n }),\n )\n .nonempty(),\n ts: z.number().int().positive(),\n topicMessageId: z.string().optional(),\n })\n .strict();\n\nexport type PqcCertV1 = z.infer<typeof PqcCertV1Schema>;\n\nexport type PqcCertV1Signer = PqcCertV1['signers'][number];\n\n/**\n * Fail-soft cert parser. Never throws — returns a discriminated result so the\n * verifier can surface schema errors as `{ valid: false, reason }` to customers.\n */\nexport function parsePqcCert(\n input: unknown,\n): { ok: true; cert: PqcCertV1 } | { ok: false; error: string } {\n const result = PqcCertV1Schema.safeParse(input);\n if (!result.success) {\n return { ok: false, error: result.error.issues[0]?.message ?? 'schema-invalid' };\n }\n return { ok: true, cert: result.data };\n}\n","/**\n * PQC Arc 11 PR 11.0 Phase 11.0.2 — validator-registry snapshot fetcher.\n *\n * Pulls a snapshot from the smart-host proxy (PR #820) at\n * `GET /api/registry/snapshot`. Fail-soft: any non-2xx / network error /\n * malformed body returns `null` so the verifier can fall through to the\n * signature-only validity path without throwing.\n *\n * @see docs/superpowers/specs/2026-05-27-pqc-arc11-external-verifier-design.md §4.1\n */\n\nexport type ValidatorRegistrySnapshot = {\n readonly snapshotTs: number;\n readonly validators: readonly {\n readonly nodeId: string;\n readonly dilithium5PublicKey: string;\n readonly active: boolean;\n }[];\n};\n\nexport type FetchRegistryOptions = {\n /** Abort the fetch after this many ms. Default 10_000. */\n readonly timeoutMs?: number;\n};\n\n/**\n * Fetch a validator-registry snapshot from a smart-host proxy.\n *\n * Returns `null` on any failure (HTTP non-OK, network error, JSON parse error,\n * shape mismatch). Callers can treat `null` as \"registry unavailable; skip\n * pubkey membership check\" and still verify signatures against the pubkeys\n * embedded in the cert itself.\n */\nexport async function fetchRegistrySnapshot(\n smartHostUrl: string,\n opts: FetchRegistryOptions = {},\n): Promise<ValidatorRegistrySnapshot | null> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 10_000);\n try {\n const url = `${smartHostUrl.replace(/\\/+$/, '')}/api/registry/snapshot`;\n const resp = await fetch(url, { signal: controller.signal });\n if (!resp.ok) return null;\n const data: unknown = await resp.json();\n if (typeof data !== 'object' || data === null) return null;\n const obj = data as Record<string, unknown>;\n if (typeof obj.snapshotTs !== 'number' || !Array.isArray(obj.validators)) {\n return null;\n }\n return obj as unknown as ValidatorRegistrySnapshot;\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n","/**\n * PQC Arc 11 PR 11.0 Phase 11.0.3 — verifyPqcAttestation primitive.\n *\n * Pure verification — no platform calls. Pass a `registrySnapshot` to also\n * enforce registry membership; omit it for signature-only verification.\n *\n * Verification steps (per design §4.1):\n * 1. Schema check: pqcCertVersion === 1, algorithm === 'ml-dsa-87'.\n * 2. Content-hash match: sha256(canonical(payload)) === cert.contentHash.\n * 3. (Optional) Registry membership: each signer active in snapshot,\n * pubkey matches.\n * 4. Per-signer signature verification against\n * sha256(`${consumer}|${contentHash}|${ts}`).\n * 5. Threshold check: verified-signer-count >= cert.threshold.\n *\n * Fail-soft at every step — returns `{ valid: false, reason }` instead of\n * throwing.\n *\n * @see docs/superpowers/specs/2026-05-27-pqc-arc11-external-verifier-design.md §4.1\n */\nimport { ml_dsa87 } from '@noble/post-quantum/ml-dsa';\nimport { sha256 } from '@noble/hashes/sha256';\nimport { parsePqcCert, type PqcCertV1 } from './cert-schema';\nimport type { ValidatorRegistrySnapshot } from './registry-fetch';\n\nexport type VerifyResult = {\n readonly valid: boolean;\n readonly reason?: string;\n readonly verifiedSignerCount?: number;\n readonly thresholdMet?: boolean;\n};\n\n/**\n * Canonical JSON serialization — sorted object keys, no whitespace, JSON.stringify\n * for primitives. Mirrors libs/shared/src/schemas/pqc.schema.ts:canonicalJsonStringify\n * so that customer-side `sha256(canonical(payload))` matches platform-side hashes\n * byte-for-byte regardless of language / emitter.\n */\nfunction canonicalJsonStringify(value: unknown): string {\n return _serialize(value);\n}\n\nfunction _serialize(value: unknown): string {\n if (value === null) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error(`canonicalJsonStringify: non-finite number ${value}`);\n }\n return JSON.stringify(value);\n }\n if (typeof value === 'string') return JSON.stringify(value);\n if (typeof value === 'undefined') {\n throw new Error('canonicalJsonStringify: undefined is not JSON-representable');\n }\n if (Array.isArray(value)) {\n return '[' + value.map(_serialize).join(',') + ']';\n }\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return '{' + keys.map((k) => JSON.stringify(k) + ':' + _serialize(obj[k])).join(',') + '}';\n }\n throw new Error(`canonicalJsonStringify: unsupported type ${typeof value}`);\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('odd-length hex');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n const b = parseInt(hex.substr(i * 2, 2), 16);\n if (Number.isNaN(b)) throw new Error('non-hex char');\n out[i] = b;\n }\n return out;\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let s = '';\n for (let i = 0; i < bytes.length; i++) {\n s += bytes[i].toString(16).padStart(2, '0');\n }\n return s;\n}\n\n/**\n * Verify a PQC attestation cert against a payload.\n *\n * @param cert - The PqcCertV1 cert (any shape; will be schema-validated).\n * @param payload - The artifact payload to verify against. Will be canonicalized\n * + sha256-hashed for the content-hash check.\n * @param registrySnapshot - Optional. When supplied, every signer must be\n * present + active in the snapshot, AND the cert's\n * pubkey must match the registry pubkey.\n */\nexport async function verifyPqcAttestation(\n cert: unknown,\n payload: unknown,\n registrySnapshot?: ValidatorRegistrySnapshot,\n): Promise<VerifyResult> {\n // ── Step 1: Schema check ──────────────────────────────────────────────────\n const parsed = parsePqcCert(cert);\n if (!parsed.ok) {\n return { valid: false, reason: `schema-invalid: ${parsed.error}` };\n }\n const c: PqcCertV1 = parsed.cert;\n\n // ── Step 2: Content-hash match ────────────────────────────────────────────\n let computedContentHash: string;\n try {\n const canonical = canonicalJsonStringify(payload);\n computedContentHash = bytesToHex(sha256(new TextEncoder().encode(canonical)));\n } catch (err) {\n return { valid: false, reason: `payload-canonicalization-failed: ${(err as Error).message}` };\n }\n if (computedContentHash.toLowerCase() !== c.contentHash.toLowerCase()) {\n return {\n valid: false,\n reason: `content-hash-mismatch: computed=${computedContentHash} cert=${c.contentHash}`,\n };\n }\n\n // ── Step 3: Registry membership (optional) ────────────────────────────────\n if (registrySnapshot) {\n for (const signer of c.signers) {\n const entry = registrySnapshot.validators.find((v) => v.nodeId === signer.nodeId);\n if (!entry) {\n return { valid: false, reason: `registry-unknown-signer: ${signer.nodeId}` };\n }\n if (!entry.active) {\n return { valid: false, reason: `registry-inactive-signer: ${signer.nodeId}` };\n }\n if (entry.dilithium5PublicKey.toLowerCase() !== signer.dilithium5PublicKey.toLowerCase()) {\n return {\n valid: false,\n reason: `registry-pubkey-mismatch: ${signer.nodeId}`,\n };\n }\n }\n }\n\n // ── Step 4: Per-signer signature verification ─────────────────────────────\n const sigDigestInput = new TextEncoder().encode(`${c.consumer}|${c.contentHash}|${c.ts}`);\n const sigDigest = sha256(sigDigestInput);\n\n let verifiedSignerCount = 0;\n for (const signer of c.signers) {\n try {\n const pubKey = hexToBytes(signer.dilithium5PublicKey);\n const sigBytes = hexToBytes(signer.signature);\n if (ml_dsa87.verify(pubKey, sigDigest, sigBytes)) {\n verifiedSignerCount++;\n }\n } catch {\n // malformed hex / wrong size — treat as invalid sig, continue\n }\n }\n\n // ── Step 5: Threshold ─────────────────────────────────────────────────────\n const thresholdMet = verifiedSignerCount >= c.threshold;\n if (!thresholdMet) {\n return {\n valid: false,\n reason: `threshold-not-met: verified=${verifiedSignerCount} required=${c.threshold}`,\n verifiedSignerCount,\n thresholdMet: false,\n };\n }\n\n return {\n valid: true,\n verifiedSignerCount,\n thresholdMet: true,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hsuite/smart-engines-sdk",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "Simplified client SDK for Smart Engines multi-chain infrastructure",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,6 +12,10 @@
12
12
  "./nestjs": {
13
13
  "types": "./dist/nestjs/index.d.ts",
14
14
  "default": "./dist/nestjs/index.js"
15
+ },
16
+ "./pqc-verify": {
17
+ "types": "./dist/pqc-verify/index.d.ts",
18
+ "default": "./dist/pqc-verify/index.js"
15
19
  }
16
20
  },
17
21
  "scripts": {
@@ -22,9 +26,12 @@
22
26
  "build:tsc": "tsc --skipLibCheck --declaration",
23
27
  "prepublishOnly": "npm run clean && npm run build",
24
28
  "build:js": "tsup",
25
- "build:dts": "dts-bundle-generator --silent --project tsconfig.build.json -o dist/index.d.ts src/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/nestjs/index.d.ts src/nestjs/index.ts"
29
+ "build:dts": "dts-bundle-generator --silent --project tsconfig.build.json -o dist/index.d.ts src/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/nestjs/index.d.ts src/nestjs/index.ts && dts-bundle-generator --silent --project tsconfig.build.json -o dist/pqc-verify/index.d.ts src/pqc-verify/index.ts"
30
+ },
31
+ "dependencies": {
32
+ "@noble/hashes": "^1.4.0",
33
+ "@noble/post-quantum": "^0.4.1"
26
34
  },
27
- "dependencies": {},
28
35
  "devDependencies": {
29
36
  "@types/node": "^20.10.0",
30
37
  "dts-bundle-generator": "^9.5.1",