@wishknish/knishio-client-ts 0.9.4 → 0.9.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishknish/knishio-client-ts",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "productName": "Knish.IO TypeScript SDK Client",
5
5
  "description": "TypeScript implementation of the Knish.IO SDK to consume Knish.IO GraphQL APIs with enhanced type safety.",
6
6
  "license": "GPL-3.0-or-later",
@@ -58,32 +58,30 @@
58
58
  "fips-203"
59
59
  ],
60
60
  "dependencies": {
61
- "@noble/post-quantum": "^0.5.4",
62
- "@thumbmarkjs/thumbmarkjs": "^0.19.1",
63
- "@urql/core": "^5.2.0",
64
- "graphql": "^16.11.0",
65
- "graphql-ws": "^6.0.7",
66
- "isomorphic-fetch": "^3.0.0",
67
- "jssha": "^3.3.1",
61
+ "@noble/post-quantum": "^0.7.0",
62
+ "@urql/core": "^6.0.3",
63
+ "graphql": "^16.14.2",
64
+ "graphql-ws": "^6.2.1",
65
+ "jssha": "^3.3.2",
68
66
  "tslib": "^2.8.1",
69
- "wonka": "^6.3.5",
70
- "zod": "^3.23.8"
67
+ "wonka": "^6.3.6",
68
+ "zod": "^4.5.4"
71
69
  },
72
70
  "devDependencies": {
73
- "@types/isomorphic-fetch": "^0.0.39",
74
71
  "@types/jssha": "^3.0.0",
75
- "@types/node": "^22",
76
- "@typescript-eslint/eslint-plugin": "^8.63.0",
77
- "@typescript-eslint/parser": "^8.63.0",
78
- "@vitest/coverage-v8": "^4.1.10",
79
- "eslint": "^9",
80
- "prettier": "^3",
72
+ "@types/node": "^26",
73
+ "@typescript-eslint/eslint-plugin": "^8.67.0",
74
+ "@typescript-eslint/parser": "^8.67.0",
75
+ "@vitest/coverage-v8": "^4.1.11",
76
+ "eslint": "^10",
77
+ "prettier": "^3.9.6",
81
78
  "tsup": "^8.5.1",
82
- "tsx": "^4.23.0",
79
+ "tsx": "^4.23.12",
83
80
  "typescript": "^5",
84
- "vite": "^7",
85
- "vite-plugin-dts": "^5.0.3",
86
- "vitest": "^4.1.10"
81
+ "vitest": "^4.1.11"
82
+ },
83
+ "resolutions": {
84
+ "wonka": "6.3.6"
87
85
  },
88
86
  "browserslist": [
89
87
  "> 1%",
@@ -103,7 +101,6 @@
103
101
  "test:watch": "vitest",
104
102
  "test:coverage": "vitest run --coverage",
105
103
  "test:ui": "vitest --ui",
106
- "dev": "vite",
107
104
  "prepublishOnly": "npm run build",
108
105
  "selftest": "npm run build && node self-test.cjs"
109
106
  },
@@ -113,7 +110,7 @@
113
110
  "README.md",
114
111
  "LICENSE"
115
112
  ],
116
- "packageManager": "yarn@4.6.0",
113
+ "packageManager": "yarn@4.15.0",
117
114
  "engines": {
118
115
  "node": ">=18.0.0"
119
116
  }
@@ -104,6 +104,7 @@ import MutationLinkIdentifier from '@/mutation/MutationLinkIdentifier'
104
104
  import MutationPeering from '@/mutation/MutationPeering'
105
105
  import MutationAppendRequest from '@/mutation/MutationAppendRequest'
106
106
  import MutationProposeMolecule from '@/mutation/MutationProposeMolecule'
107
+ import MutationReplenishToken from '@/mutation/MutationReplenishToken'
107
108
 
108
109
  // Response imports
109
110
  import ResponseProposeMolecule from '@/response/ResponseProposeMolecule'
@@ -1836,27 +1837,55 @@ export default class KnishIOClient {
1836
1837
  }
1837
1838
 
1838
1839
  /**
1839
- * Replenish tokens
1840
+ * Replenish a non-finite token supply.
1841
+ * Matches JS SDK KnishIOClient.replenishToken (KnishIOClient.js:2195-2231).
1840
1842
  */
1841
1843
  async replenishToken({
1842
1844
  token,
1843
1845
  amount = null,
1844
1846
  units = null,
1845
- sourceWallet: _sourceWallet = null
1847
+ sourceWallet = null
1846
1848
  }: {
1847
1849
  token: TokenSlug | string
1848
1850
  amount?: number | string | null
1849
- units?: string[] | null
1851
+ units?: Array<[string, string, Record<string, any>?]> | null
1850
1852
  sourceWallet?: Wallet | null
1851
1853
  }): Promise<Response> {
1852
1854
  this.log('info', `KnishIOClient::replenishToken() - Replenishing ${amount || 'units'} of ${token}...`)
1853
1855
 
1854
- // Similar to requestTokens but with specific replenishment logic
1855
- return this.requestTokens({
1856
- token,
1857
- amount,
1858
- units
1856
+ if (!sourceWallet) {
1857
+ sourceWallet = (await this.queryBalance({ token }))?.payload() as Wallet | null
1858
+ }
1859
+ if (!sourceWallet) {
1860
+ throw new TransferBalanceException('Source wallet is missing or invalid.')
1861
+ }
1862
+
1863
+ // Remainder wallet (same token, so the V-isotope pair conserves)
1864
+ const remainderWallet = sourceWallet.createRemainder(this.getSecret())
1865
+
1866
+ const molecule = await this.createMolecule({
1867
+ sourceWallet,
1868
+ remainderWallet
1869
+ })
1870
+ molecule.replenishToken({
1871
+ amount: Number(amount ?? 0),
1872
+ units: units ?? []
1859
1873
  })
1874
+ molecule.sign({ bundle: this.getBundle() })
1875
+ molecule.check()
1876
+
1877
+ const mutation = await this.createMoleculeMutation({
1878
+ mutationClass: MutationReplenishToken,
1879
+ molecule
1880
+ })
1881
+
1882
+ const response = await this.executeQuery(mutation)
1883
+
1884
+ if (!response) {
1885
+ throw new CodeException('Token replenishment failed')
1886
+ }
1887
+
1888
+ return response
1860
1889
  }
1861
1890
 
1862
1891
  /**
@@ -47,6 +47,7 @@ License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
47
47
  */
48
48
 
49
49
  import Meta, { type NormalizedMeta } from './Meta'
50
+ import PolicyMeta from './PolicyMeta'
50
51
 
51
52
  // Configuration constants matching JS SDK
52
53
  const USE_META_CONTEXT = false
@@ -172,12 +173,19 @@ export default class AtomMeta {
172
173
  * @return This instance for chaining
173
174
  */
174
175
  addPolicy(policy: Record<string, any>): AtomMeta {
175
- // For now, just stringify the policy
176
- // TODO: Implement PolicyMeta class if needed
176
+ // Normalise through PolicyMeta before serialising. PolicyMeta.fillDefault adds a default
177
+ // entry for every meta key not already covered by the caller's policy, so the stored string
178
+ // carries materially more than the input — and this string is hashed into the atom, so a
179
+ // raw JSON.stringify(policy) diverges from every other SDK.
180
+ // Matches JS SDK AtomMeta.addPolicy (AtomMeta.js:170-175), including passing
181
+ // Object.keys(this.meta): `meta` is a NormalizedMeta[] in both SDKs, so these are array
182
+ // indices rather than meta key names. Faithful to the JS behaviour — do not "correct" it.
183
+ const policyMeta = new PolicyMeta(policy, Object.keys(this.meta))
184
+
177
185
  this.merge({
178
- policy: JSON.stringify(policy)
186
+ policy: policyMeta.toJson()
179
187
  })
180
-
188
+
181
189
  return this
182
190
  }
183
191
 
@@ -755,6 +755,67 @@ export default class Molecule {
755
755
  return this
756
756
  }
757
757
 
758
+ /**
759
+ * Replenishes non-finite token supplies.
760
+ * Matches JS SDK Molecule.replenishToken (Molecule.js:521-566) exactly.
761
+ *
762
+ * Two orderings here are load-bearing for the molecular hash and must not be reordered:
763
+ * the remainder balance is computed BEFORE the source balance is overwritten, and the
764
+ * source V-atom is added BEFORE the remainder V-atom.
765
+ */
766
+ replenishToken({
767
+ amount,
768
+ units = []
769
+ }: {
770
+ amount: number
771
+ units?: Array<[string, string, Record<string, any>?]>
772
+ }): Molecule {
773
+ if (amount < 0) {
774
+ throw new NegativeAmountException('Molecule::replenishToken() - Amount to replenish must be positive!')
775
+ }
776
+
777
+ if (!this.sourceWallet || !this.remainderWallet) {
778
+ throw new Error('Source and remainder wallets required for token replenishment')
779
+ }
780
+
781
+ if (units.length) {
782
+ // Prepare token units to formatted style
783
+ const formatted = Wallet.getTokenUnits(units)
784
+
785
+ // Merge token units with source wallet & new items
786
+ this.remainderWallet.tokenUnits = this.sourceWallet.tokenUnits
787
+ for (const unit of formatted) {
788
+ this.remainderWallet.tokenUnits.push(unit)
789
+ }
790
+ this.remainderWallet.balance = String(this.remainderWallet.tokenUnits.length)
791
+
792
+ // Override first atom's token units to replenish values
793
+ this.sourceWallet.tokenUnits = formatted
794
+ this.sourceWallet.balance = String(this.sourceWallet.tokenUnits.length)
795
+ } else {
796
+ // Update wallet's balances
797
+ this.remainderWallet.balance = String(Number(this.sourceWallet.balance) + amount)
798
+ this.sourceWallet.balance = String(amount)
799
+ }
800
+
801
+ // Initializing a new Atom to remove tokens from source
802
+ this.addAtom(Atom.create({
803
+ isotope: 'V',
804
+ wallet: this.sourceWallet,
805
+ value: Number(this.sourceWallet.balance)
806
+ }))
807
+
808
+ this.addAtom(Atom.create({
809
+ isotope: 'V',
810
+ wallet: this.remainderWallet,
811
+ value: Number(this.remainderWallet.balance),
812
+ metaType: 'walletBundle',
813
+ metaId: this.remainderWallet.bundle!
814
+ }))
815
+
816
+ return this
817
+ }
818
+
758
819
  /**
759
820
  * Initialize authorization request
760
821
  * Creates U-isotope (authorization) atom for requesting auth token
@@ -51,6 +51,7 @@ import { randomString, chunkSubstr, isHex } from '@/libraries/strings'
51
51
  import { generateBundleHash, generateSecret, shake256, generateBatchId } from '@/libraries/crypto'
52
52
  import WalletCredentialException from '@/exception/WalletCredentialException'
53
53
  import { isBundleHash } from '@/types'
54
+ import TokenUnit from '@/core/TokenUnit'
54
55
  // Post-quantum cryptography for ML-KEM768 key encapsulation
55
56
  import { ml_kem768 } from '@noble/post-quantum/ml-kem.js'
56
57
 
@@ -271,12 +272,13 @@ export default class Wallet {
271
272
  }
272
273
 
273
274
  /**
274
- * Get formatted token units from raw data
275
- * Stub implementation for now
275
+ * Map raw token-unit tuples to TokenUnit instances.
276
+ * Matches JS SDK Wallet.getTokenUnits (Wallet.js:190-196). The serialised shape reaches hashed
277
+ * atom meta via AtomMeta.setAtomWallet -> JSON.stringify(getTokenUnitsData()), so returning raw
278
+ * tuples here would diverge from every other SDK.
276
279
  */
277
- static getTokenUnits(unitsData: any[]): any[] {
278
- // TODO: Implement TokenUnit class if needed
279
- return unitsData
280
+ static getTokenUnits(unitsData: Array<[string, string, Record<string, any>?]>): TokenUnit[] {
281
+ return unitsData.map(unitData => TokenUnit.createFromDB(unitData))
280
282
  }
281
283
 
282
284
  /**
package/src/index.ts CHANGED
@@ -359,7 +359,10 @@ export {
359
359
  // VERSION AND SDK INFORMATION
360
360
  // =============================================================================
361
361
 
362
- export const SDK_VERSION = '1.0.0'
362
+ // MUST equal package.json's "version". The CI `version consistency` job enforces this via
363
+ // .github/scripts/check-version.sh, which parses this exact line — keep the literal form
364
+ // `export const SDK_VERSION = '<semver>'` intact so the gate can read it.
365
+ export const SDK_VERSION = '0.9.6'
363
366
  export const SDK_NAME = 'KnishIO-Client-TS'
364
367
  export const COMPATIBLE_SERVER_VERSIONS = [4, 5]
365
368
 
@@ -69,7 +69,7 @@ const CallbackParamsSchema = z.object({
69
69
  action: z.string().min(1, 'Action cannot be empty'),
70
70
  metaType: z.union([z.string(), z.null()]).optional(),
71
71
  metaId: z.union([z.string(), z.null()]).optional(),
72
- meta: z.union([z.instanceof(Meta), z.record(z.unknown()), z.null()]).optional(),
72
+ meta: z.union([z.instanceof(Meta), z.record(z.string(), z.unknown()), z.null()]).optional(),
73
73
  address: z.union([z.string(), z.null()]).optional(),
74
74
  token: z.union([z.string(), z.null()]).optional(),
75
75
  amount: z.union([z.string(), z.null()]).optional(),
@@ -141,16 +141,30 @@ export default class GraphQLClient implements IGraphQLClient {
141
141
  return createClient({
142
142
  url: serverUri,
143
143
  exchanges,
144
- // PQ-transport Phase E: when encryption is on, route fetch through the CipherHash wrapper
145
- // (encrypt the request body to the validator's ML-KEM pubkey, decrypt the response).
146
- // Omitted → urql uses the global fetch (plaintext).
147
- ...(this.cipherLink ? { fetch: ((input: any, init: any) => this.cipherFetch(input, init)) as typeof fetch } : {}),
144
+ // urql 5 had no default and always POSTed; urql 6 defaults to 'within-url-limit', which
145
+ // URL-encodes short queries and sends NO body. That would silently disable the CipherHash
146
+ // envelope below — cipherFetch's `typeof init.body === 'string'` guard fails on a GET, so
147
+ // the query would leave as plaintext URL parameters with no error. Pin POST explicitly.
148
+ preferGetMethod: false,
149
+ // Always route through our own fetch. Two reasons: (1) PQ-transport Phase E — when
150
+ // encryption is on, cipherFetch wraps the request body in the CipherHash envelope and
151
+ // decrypts the response; (2) the 60s timeout. urql's makeFetchSource unconditionally
152
+ // overwrites init.signal with its own AbortController, so a signal returned from
153
+ // fetchOptions() is discarded and never fired. Combining it here is the only place it
154
+ // survives.
155
+ fetch: ((input: RequestInfo | URL, init?: RequestInit) => {
156
+ const timeoutSignal = AbortSignal.timeout(60000)
157
+ // Keep urql's own signal so teardown still aborts; AbortSignal.any needs Node >= 18.17.
158
+ const signal = init?.signal && typeof AbortSignal.any === 'function'
159
+ ? AbortSignal.any([init.signal, timeoutSignal])
160
+ : (init?.signal ?? timeoutSignal)
161
+ const timedInit: RequestInit = { ...init, signal }
162
+ return this.cipherLink ? this.cipherFetch(input, timedInit) : fetch(input, timedInit)
163
+ }) as typeof fetch,
148
164
  fetchOptions: () => ({
149
165
  headers: {
150
166
  'X-Auth-Token': this.$__authToken
151
- },
152
- // Add 60 second timeout
153
- signal: AbortSignal.timeout(60000)
167
+ }
154
168
  })
155
169
  })
156
170
  }
@@ -155,12 +155,17 @@ class UrqlClientWrapper {
155
155
  return createClient({
156
156
  url: serverUri,
157
157
  exchanges,
158
+ // urql 6 defaults preferGetMethod to 'within-url-limit', sending short queries as GET with
159
+ // no body. Keep the urql-5 behaviour (always POST) so this client matches GraphQLClient and
160
+ // cannot silently change the wire format if it is ever put back into use.
161
+ preferGetMethod: false,
162
+ // No `signal` here: urql's makeFetchSource overwrites init.signal with its own
163
+ // AbortController, so a timeout passed through fetchOptions() is silently discarded and
164
+ // never fires. GraphQLClient applies its 60s timeout inside a wrapping `fetch` instead.
158
165
  fetchOptions: () => ({
159
166
  headers: {
160
167
  'X-Auth-Token': this.$__authToken
161
- },
162
- // Add 60 second timeout for debugging
163
- signal: AbortSignal.timeout(60000)
168
+ }
164
169
  })
165
170
  })
166
171
  }
@@ -659,13 +659,11 @@ export function verifyOTSSignature(
659
659
  const enumerated = enumerateMolecularHash(base17Hash)
660
660
  const normalized = normalizeMolecularHash(enumerated)
661
661
 
662
- // Step 2: Process OTS fragments (handle compression if needed)
663
- let ots = otsFragments
662
+ // Step 2: Fragments must be the uncompressed 2048-character form. Base64-compressed
663
+ // signatures are not supported and are rejected here rather than expanded.
664
+ const ots = otsFragments
664
665
  if (ots.length !== 2048) {
665
- // If not 2048 chars, it might be base64 compressed - for now, assume uncompressed
666
- if (ots.length !== 2048) {
667
- return false // Invalid signature length
668
- }
666
+ return false
669
667
  }
670
668
 
671
669
  // Step 3: Subdivide OTS into 16 segments of 128 characters each
@@ -0,0 +1,28 @@
1
+ /*
2
+ Powered by Knish.IO: Connecting a Decentralized World
3
+
4
+ Please visit https://github.com/WishKnish/KnishIO-Client-TS for information.
5
+
6
+ License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
7
+ */
8
+
9
+ import MutationProposeMolecule from './MutationProposeMolecule'
10
+
11
+ /**
12
+ * Mutation for replenishing a non-finite token supply.
13
+ *
14
+ * The JS SDK submits replenishment through `MutationProposeMolecule` directly
15
+ * (KnishIOClient.js:2226-2230), but that class is abstract here because it declares
16
+ * `fillMolecule`. This subclass exists to provide the concrete type; it inherits the identical
17
+ * `ProposeMolecule(molecule: $molecule)` document from the base, so the request on the wire is
18
+ * the same one the JS SDK sends.
19
+ *
20
+ * `fillMolecule` is intentionally a no-op: KnishIOClient.replenishToken builds the V-atom pair
21
+ * via Molecule.replenishToken, then signs and checks the molecule, before this mutation is
22
+ * constructed. There is nothing left to fill.
23
+ */
24
+ export default class MutationReplenishToken extends MutationProposeMolecule {
25
+ override fillMolecule(): void {
26
+ // Intentionally empty — the molecule arrives fully built, signed and checked.
27
+ }
28
+ }
@@ -62,6 +62,21 @@ import { z } from 'zod'
62
62
  const hexStringRegex = /^[0-9a-fA-F]+$/
63
63
  const base17HashRegex = /^[0-9a-g]+$/
64
64
 
65
+ // Zod 3's `.url()` was `try { new URL(v) } catch` and returned the input UNCHANGED.
66
+ // Zod 4's `z.url()` returns the normalized `URL.href` instead — it strips default ports and
67
+ // lowercases the host — and there is no opt-out. The parsed `uri` is the endpoint the client
68
+ // actually talks to (KnishIOClient destructures it out of the validated config), so a
69
+ // refinement is used here: it validates exactly as v3 did without rewriting the value.
70
+ const urlString = (message = 'Invalid URL') =>
71
+ z.string().refine((value) => {
72
+ try {
73
+ new URL(value)
74
+ return true
75
+ } catch {
76
+ return false
77
+ }
78
+ }, message)
79
+
65
80
  export const HexStringSchema = z.string()
66
81
  .regex(hexStringRegex, 'Must be a valid hexadecimal string')
67
82
  .brand<'HexString'>()
@@ -102,7 +117,7 @@ export const MetaIdSchema = z.string()
102
117
  .brand<'MetaId'>()
103
118
 
104
119
  export const BatchIdSchema = z.string()
105
- .min(1, 'Batch ID cannot be empty')
120
+ .regex(/^[0-9a-fA-F]{64}$/, 'Batch ID must be 64 hexadecimal characters')
106
121
  .brand<'BatchId'>()
107
122
 
108
123
  export const CellSlugSchema = z.string()
@@ -115,7 +130,7 @@ export const CellSlugSchema = z.string()
115
130
  // =============================================================================
116
131
 
117
132
  export const AtomIsotopeSchema = z.enum(['C', 'V', 'U', 'T', 'M', 'I', 'R', 'B', 'F'], {
118
- errorMap: () => ({ message: 'Invalid atom isotope. Must be one of: C, V, U, T, M, I, R, B, F' })
133
+ error: 'Invalid atom isotope. Must be one of: C, V, U, T, M, I, R, B, F'
119
134
  })
120
135
 
121
136
  // =============================================================================
@@ -135,7 +150,7 @@ export const AtomMetaDataSchema = z.object({
135
150
  }).strict()
136
151
 
137
152
  export const MetaDataSchema: z.ZodType<Record<string, any>> = z.lazy(() =>
138
- z.record(z.union([
153
+ z.record(z.string(), z.union([
139
154
  MetaDataValueSchema,
140
155
  MetaDataSchema,
141
156
  z.array(MetaDataSchema)
@@ -186,7 +201,7 @@ export const MoleculeParamsSchema = z.object({
186
201
  // =============================================================================
187
202
 
188
203
  export const KnishIOClientConfigSchema = z.object({
189
- uri: z.union([z.string().url(), z.array(z.string().url())]).optional(),
204
+ uri: z.union([urlString(), z.array(urlString())]).optional(),
190
205
  cellSlug: z.union([CellSlugSchema, z.string(), z.null()]).optional(),
191
206
  client: z.unknown().optional(),
192
207
  socket: z.unknown().optional(),
@@ -209,7 +224,7 @@ export const AuthTokenParamsSchema = z.object({
209
224
  export const AuthParamsSchema = z.object({
210
225
  cellSlug: z.union([CellSlugSchema, z.string(), z.null()]).optional(),
211
226
  encrypt: z.boolean().optional(),
212
- callback: z.function().args(z.unknown()).returns(z.void()).optional()
227
+ callback: z.custom<(arg: unknown) => void>((value) => typeof value === 'function').optional()
213
228
  }).strict()
214
229
 
215
230
  export const GuestAuthParamsSchema = z.object({
@@ -224,7 +239,7 @@ export const TransferParamsSchema = z.object({
224
239
  recipient: z.union([WalletAddressSchema, z.string()]),
225
240
  amount: z.union([z.number().positive(), z.string().min(1)]),
226
241
  token: z.union([TokenSlugSchema, z.string()]).optional(),
227
- callbackUrl: z.union([z.string().url(), z.null()]).optional(),
242
+ callbackUrl: z.union([urlString(), z.null()]).optional(),
228
243
  metaType: z.union([MetaTypeSchema, z.string(), z.null()]).optional(),
229
244
  metaId: z.union([MetaIdSchema, z.string(), z.null()]).optional(),
230
245
  meta: z.union([MetaDataSchema, z.null()]).optional()
@@ -278,7 +293,7 @@ export const MetaQueryParamsSchema = z.object({
278
293
  value: z.union([z.string(), z.null()]).optional(),
279
294
  latest: z.union([z.boolean(), z.null()]).optional(),
280
295
  filter: z.union([z.array(MetaFilterSchema), z.null()]).optional(),
281
- queryArgs: z.union([z.record(z.unknown()), z.null()]).optional(),
296
+ queryArgs: z.union([z.record(z.string(), z.unknown()), z.null()]).optional(),
282
297
  count: z.union([z.number().int().min(1), z.null()]).optional(),
283
298
  countBy: z.union([z.string(), z.null()]).optional(),
284
299
  cellSlug: z.union([CellSlugSchema, z.string(), z.null()]).optional()
@@ -316,7 +331,7 @@ export const SignatureResultSchema = z.object({
316
331
 
317
332
  export const GraphQLRequestSchema = z.object({
318
333
  query: z.string().min(1, 'GraphQL query cannot be empty'),
319
- variables: z.record(z.unknown()).optional(),
334
+ variables: z.record(z.string(), z.unknown()).optional(),
320
335
  operationName: z.union([z.string(), z.null()]).optional()
321
336
  }).strict()
322
337
 
@@ -327,13 +342,13 @@ export const GraphQLErrorSchema = z.object({
327
342
  column: z.number().int().min(1)
328
343
  })).optional(),
329
344
  path: z.array(z.union([z.string(), z.number()])).optional(),
330
- extensions: z.record(z.unknown()).optional()
345
+ extensions: z.record(z.string(), z.unknown()).optional()
331
346
  }).strict()
332
347
 
333
348
  export const GraphQLResponseSchema = z.object({
334
349
  data: z.unknown().optional(),
335
350
  errors: z.array(GraphQLErrorSchema).nullable().optional(),
336
- extensions: z.record(z.unknown()).optional()
351
+ extensions: z.record(z.string(), z.unknown()).optional()
337
352
  }).strict()
338
353
 
339
354
  // =============================================================================
@@ -343,8 +358,8 @@ export const GraphQLResponseSchema = z.object({
343
358
  export const SubscriptionOptionsSchema = z.object({
344
359
  operationName: z.string().optional(),
345
360
  query: z.string().optional(),
346
- variables: z.record(z.unknown()).optional(),
347
- callback: z.function().args(z.unknown()).returns(z.void()).optional()
361
+ variables: z.record(z.string(), z.unknown()).optional(),
362
+ callback: z.custom<(arg: unknown) => void>((value) => typeof value === 'function').optional()
348
363
  }).strict()
349
364
 
350
365
  // =============================================================================
@@ -364,7 +379,7 @@ export const ValidationResultSchema = z.object({
364
379
 
365
380
  export const ErrorContextSchema = z.object({
366
381
  operation: z.string().optional(),
367
- parameters: z.record(z.unknown()).optional(),
382
+ parameters: z.record(z.string(), z.unknown()).optional(),
368
383
  timestamp: z.number().int().min(0).optional(),
369
384
  stack: z.string().optional()
370
385
  }).strict()
@@ -407,7 +422,7 @@ export const KnishIOErrorTypeSchema = z.enum([
407
422
  * Validates and parses data using a Zod schema with enhanced error messages
408
423
  */
409
424
  export function parseWithSchema<T>(
410
- schema: z.ZodSchema<T>,
425
+ schema: z.ZodType<T>,
411
426
  data: unknown,
412
427
  context?: string
413
428
  ): T {
@@ -428,7 +443,7 @@ export function parseWithSchema<T>(
428
443
  * Type-safe validation that returns a result object instead of throwing
429
444
  */
430
445
  export function validateWithSchema<T>(
431
- schema: z.ZodSchema<T>,
446
+ schema: z.ZodType<T>,
432
447
  data: unknown
433
448
  ): { success: true; data: T } | { success: false; error: string } {
434
449
  const result = schema.safeParse(data)
@@ -444,7 +459,7 @@ export function validateWithSchema<T>(
444
459
  * Validates multiple values with their respective schemas
445
460
  */
446
461
  export function validateMultiple<T extends Record<string, any>>(
447
- validations: { [K in keyof T]: { schema: z.ZodSchema<T[K]>; data: unknown } }
462
+ validations: { [K in keyof T]: { schema: z.ZodType<T[K]>; data: unknown } }
448
463
  ): T {
449
464
  const result = {} as T
450
465
 
@@ -399,12 +399,12 @@ export function assertBatchId(
399
399
  ): asserts value is BatchId {
400
400
  if (!isBatchId(value)) {
401
401
  throw new AssertionError(
402
- createAssertionMessage('BatchId (UUID format)', value, context),
402
+ createAssertionMessage('BatchId (64-character hex)', value, context),
403
403
  'BatchId',
404
404
  value,
405
405
  {
406
406
  operation: 'batch_id_assertion',
407
- expected: 'UUID v4 format',
407
+ expected: '64-character hexadecimal string',
408
408
  actual: value,
409
409
  field: context
410
410
  }
@@ -182,9 +182,7 @@ export class ValidationService {
182
182
  KNISHIO_SERVER_SDK_VERSION: process.env.KNISHIO_SERVER_SDK_VERSION
183
183
  }
184
184
 
185
- // cast: EnvironmentConfig uses .default()/.optional() so its inferred input≠output type and
186
- // doesn't structurally match safeParse's ZodType<T> param; ZodTypeAny sidesteps the mismatch.
187
- const parseResult = safeParse(Schemas.EnvironmentConfig as z.ZodTypeAny, env)
185
+ const parseResult = safeParse(Schemas.EnvironmentConfig, env)
188
186
 
189
187
  if (!parseResult.success) {
190
188
  return {