@ic-reactor/core 3.0.7-beta.0 → 3.0.7-beta.2

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,91 @@
1
+ import * as z from "zod"
2
+ import type { Principal } from "@icp-sdk/core/principal"
3
+ import type {
4
+ IsOptionalType,
5
+ IsBlobType,
6
+ UnionToTuple,
7
+ } from "../types/transform"
8
+ import type {
9
+ CandidVariantKey,
10
+ CandidVariantValue,
11
+ IsCandidVariant,
12
+ } from "../types/variant"
13
+
14
+ type VariantsOf<T> =
15
+ T extends Record<infer K extends CandidVariantKey<T>, any>
16
+ ? { _type: K } & (CandidVariantValue<T, K> extends null
17
+ ? {}
18
+ : { [P in K]: DisplayOf<CandidVariantValue<T, K>> })
19
+ : never
20
+
21
+ type VariantUnionOf<T> =
22
+ UnionToTuple<T> extends infer U
23
+ ? U extends any[]
24
+ ? { [K in keyof U]: VariantsOf<U[K]> }[number]
25
+ : never
26
+ : never
27
+
28
+ type CombineObjects<Required, Optional> = keyof Optional extends never
29
+ ? Required
30
+ : keyof Required extends never
31
+ ? Optional
32
+ : Required & Optional
33
+
34
+ type AsObject<T> = CombineObjects<
35
+ {
36
+ [K in keyof T as IsOptionalType<T[K]> extends true ? never : K]: DisplayOf<
37
+ T[K]
38
+ >
39
+ },
40
+ {
41
+ [K in keyof T as IsOptionalType<T[K]> extends true ? K : never]?: DisplayOf<
42
+ T[K]
43
+ >
44
+ }
45
+ >
46
+
47
+ type AsOptional<T> = T extends [infer U] ? NullishType<DisplayOf<U>> : never
48
+
49
+ export type BlobType = Uint8Array | number[] | string
50
+
51
+ export type NullishType<T> = T | null | undefined
52
+
53
+ export type DisplayCommonType<T> = T extends string
54
+ ? string
55
+ : T extends bigint
56
+ ? string
57
+ : T extends number
58
+ ? number
59
+ : T extends boolean
60
+ ? boolean
61
+ : T
62
+
63
+ export type DisplayOf<T> =
64
+ IsOptionalType<T> extends true
65
+ ? AsOptional<T>
66
+ : IsBlobType<T> extends true
67
+ ? BlobType
68
+ : IsCandidVariant<T> extends true
69
+ ? VariantUnionOf<T>
70
+ : T extends Array<[string, infer B]>
71
+ ? Map<string, DisplayOf<B>>
72
+ : T extends (infer U)[]
73
+ ? DisplayOf<U>[]
74
+ : T extends null
75
+ ? null
76
+ : T extends Principal
77
+ ? string
78
+ : T extends object
79
+ ? AsObject<T>
80
+ : DisplayCommonType<T>
81
+
82
+ export type DisplayCodec<TC = unknown, TD = DisplayOf<TC>> = z.ZodCodec<
83
+ z.ZodType<TC>,
84
+ z.ZodType<TD>
85
+ >
86
+
87
+ export interface ActorDisplayCodec<TC = unknown, TD = DisplayOf<TC>> {
88
+ codec: DisplayCodec<TC, TD>
89
+ asDisplay: (val: TC) => TD
90
+ asCandid: (val: TD) => TC
91
+ }
@@ -0,0 +1,410 @@
1
+ import * as z from "zod"
2
+ import { IDL } from "@icp-sdk/core/candid"
3
+ import { Principal } from "@icp-sdk/core/principal"
4
+ import {
5
+ createVariant,
6
+ nonNullish,
7
+ uint8ArrayToHex,
8
+ hexToUint8Array,
9
+ isNullish,
10
+ } from "../utils"
11
+
12
+ export class DisplayCodecVisitor extends IDL.Visitor<unknown, z.ZodTypeAny> {
13
+ private _recCache = new Map<IDL.RecClass, z.ZodTypeAny>()
14
+
15
+ visitType<T>(t: IDL.Type<T>, data: unknown): z.ZodTypeAny {
16
+ return t.accept(this, data)
17
+ }
18
+
19
+ visitPrimitive<T>(t: IDL.PrimitiveType<T>, data: unknown): z.ZodTypeAny {
20
+ return t.accept(this, data)
21
+ }
22
+
23
+ visitEmpty(_t: IDL.EmptyClass, _data: unknown): z.ZodTypeAny {
24
+ return z.never()
25
+ }
26
+
27
+ visitBool(_t: IDL.BoolClass, _data: unknown): z.ZodTypeAny {
28
+ return z.boolean()
29
+ }
30
+
31
+ visitNull(_t: IDL.NullClass, _data: unknown): z.ZodTypeAny {
32
+ return z.null()
33
+ }
34
+
35
+ visitReserved(_t: IDL.ReservedClass, _data: unknown): z.ZodTypeAny {
36
+ return z.any()
37
+ }
38
+
39
+ visitText(_t: IDL.TextClass, _data: unknown): z.ZodTypeAny {
40
+ return z.string()
41
+ }
42
+
43
+ visitNumber<T>(t: IDL.PrimitiveType<T>, data: unknown): z.ZodTypeAny {
44
+ return t.accept(this, data)
45
+ }
46
+
47
+ visitInt(_t: IDL.IntClass, _data: unknown): z.ZodTypeAny {
48
+ return z.codec(
49
+ z.bigint(), // Candid format
50
+ z.string(), // Display format
51
+ {
52
+ decode: (val) => (typeof val === "bigint" ? val.toString() : val),
53
+ encode: (val) => (typeof val === "string" ? BigInt(val) : val),
54
+ }
55
+ )
56
+ }
57
+
58
+ visitNat(_t: IDL.NatClass, _data: unknown): z.ZodTypeAny {
59
+ return z.codec(
60
+ z.bigint(), // Candid format
61
+ z.string(), // Display format
62
+ {
63
+ decode: (val) => (typeof val === "bigint" ? val.toString() : val),
64
+ encode: (val) => (typeof val === "string" ? BigInt(val) : val),
65
+ }
66
+ )
67
+ }
68
+
69
+ visitFloat(_t: IDL.FloatClass, _data: unknown): z.ZodTypeAny {
70
+ return z.number()
71
+ }
72
+
73
+ visitFixedInt(t: IDL.FixedIntClass, _data: unknown): z.ZodTypeAny {
74
+ const bits = t._bits
75
+
76
+ if (bits <= 32) {
77
+ // 32-bit integers stay as numbers
78
+ return z.number()
79
+ } else {
80
+ // 64-bit integers: bigint ↔ string
81
+ return z.codec(
82
+ z.bigint(), // Candid format
83
+ z.string(), // Display format
84
+ {
85
+ decode: (val) => (typeof val === "bigint" ? val.toString() : val),
86
+ encode: (val) => (typeof val === "string" ? BigInt(val) : val),
87
+ }
88
+ )
89
+ }
90
+ }
91
+
92
+ visitFixedNat(t: IDL.FixedNatClass, _data: unknown): z.ZodTypeAny {
93
+ const bits = t._bits
94
+
95
+ if (bits <= 32) {
96
+ return z.number()
97
+ } else {
98
+ return z.codec(
99
+ z.bigint(), // Candid format
100
+ z.string(), // Display format
101
+ {
102
+ decode: (val) => (typeof val === "bigint" ? val.toString() : val),
103
+ encode: (val) => (typeof val === "string" ? BigInt(val) : val),
104
+ }
105
+ )
106
+ }
107
+ }
108
+
109
+ visitPrincipal(_t: IDL.PrincipalClass, _data: unknown): z.ZodTypeAny {
110
+ const stringOrPrincipalSchema = z.union([z.string(), z.any()])
111
+
112
+ return z.codec(stringOrPrincipalSchema, stringOrPrincipalSchema, {
113
+ decode: (val) => {
114
+ if (val instanceof Principal) return val.toText()
115
+ if (typeof val === "string") return val
116
+ return String(val)
117
+ },
118
+ encode: (val) => {
119
+ if (typeof val === "string") return Principal.fromText(val)
120
+ if (val instanceof Principal) return val
121
+ return Principal.fromText(String(val))
122
+ },
123
+ })
124
+ }
125
+
126
+ visitConstruct<T>(t: IDL.ConstructType<T>, data: unknown): z.ZodTypeAny {
127
+ return t.accept(this, data)
128
+ }
129
+
130
+ visitVec<T>(
131
+ _t: IDL.VecClass<T>,
132
+ elemType: IDL.Type<T>,
133
+ _data: unknown
134
+ ): z.ZodTypeAny {
135
+ // Special case: Vec<Nat8> is a Blob (Uint8Array ↔ hex string)
136
+ if (elemType.name === "nat8") {
137
+ return z.codec(
138
+ z.union([z.instanceof(Uint8Array), z.array(z.number())]),
139
+ z.union([z.string(), z.instanceof(Uint8Array)]),
140
+ {
141
+ decode: (val) => {
142
+ if (!val) return val
143
+ if (val.length <= 512) return uint8ArrayToHex(val)
144
+ return val as Uint8Array<ArrayBuffer>
145
+ },
146
+ encode: (val) => {
147
+ if (typeof val === "string") {
148
+ return hexToUint8Array(val)
149
+ }
150
+ return val
151
+ },
152
+ }
153
+ )
154
+ }
155
+ // Regular array: codec each element
156
+ const elemCodec = elemType.accept(this, null)
157
+
158
+ // Special case: Vec<Tuple(Text, Value)> → Map (for key-value pairs)
159
+ const isTextTuple =
160
+ elemType instanceof IDL.TupleClass && elemType._fields.length === 2
161
+
162
+ if (isTextTuple) {
163
+ return z.codec(z.any(), z.any(), {
164
+ decode: (val) => {
165
+ if (!Array.isArray(val)) return val
166
+ return new Map(
167
+ val.map((elem) => elemCodec.decode(elem)) as [string, string][]
168
+ )
169
+ },
170
+ encode: (val) => {
171
+ const entries = val instanceof Map ? Array.from(val.entries()) : val
172
+ if (!Array.isArray(entries)) return entries
173
+ return entries.map((elem) => elemCodec.encode(elem))
174
+ },
175
+ })
176
+ }
177
+
178
+ return z.codec(z.any(), z.any(), {
179
+ decode: (val) => {
180
+ if (!Array.isArray(val)) return val
181
+ return val.map((elem) => elemCodec.decode(elem))
182
+ },
183
+ encode: (val) => {
184
+ if (!Array.isArray(val)) return val
185
+ return val.map((elem) => elemCodec.encode(elem))
186
+ },
187
+ })
188
+ }
189
+
190
+ visitOpt<T>(
191
+ _t: IDL.OptClass<T>,
192
+ elemType: IDL.Type<T>,
193
+ _data: unknown
194
+ ): z.ZodTypeAny {
195
+ const elemCodec = elemType.accept(this, null)
196
+
197
+ return z.codec(z.any(), z.any(), {
198
+ decode: (val) => {
199
+ if (!Array.isArray(val) || val.length === 0) return undefined
200
+ return elemCodec.decode(val[0])
201
+ },
202
+ encode: (val) => {
203
+ if (isNullish(val)) return [] as []
204
+ return [elemCodec.encode(val)] as [any]
205
+ },
206
+ })
207
+ }
208
+
209
+ visitRecord(
210
+ _t: IDL.RecordClass,
211
+ fields: Array<[string, IDL.Type]>,
212
+ _data: unknown
213
+ ): z.ZodTypeAny {
214
+ const fieldEntries = fields.map(([fieldName, fieldType]) => ({
215
+ fieldName,
216
+ codec: fieldType.accept(this, null),
217
+ }))
218
+
219
+ return z.codec(z.any(), z.any(), {
220
+ decode: (val) => {
221
+ if (!val || typeof val !== "object") return val
222
+ return Object.fromEntries(
223
+ fieldEntries.map(({ fieldName, codec }) => [
224
+ fieldName,
225
+ codec.decode(val[fieldName]),
226
+ ])
227
+ )
228
+ },
229
+ encode: (val) => {
230
+ if (!val || typeof val !== "object") return val
231
+ return Object.fromEntries(
232
+ fieldEntries.map(({ fieldName, codec }) => [
233
+ fieldName,
234
+ codec.encode(val[fieldName]),
235
+ ])
236
+ )
237
+ },
238
+ })
239
+ }
240
+
241
+ visitTuple<T extends any[]>(
242
+ _t: IDL.TupleClass<T>,
243
+ components: IDL.Type[],
244
+ _data: unknown
245
+ ): z.ZodTypeAny {
246
+ const componentCodecs: any = components.map((component) =>
247
+ component.accept(this, null)
248
+ )
249
+
250
+ return z.codec(z.any(), z.any(), {
251
+ decode: (val) => {
252
+ if (!Array.isArray(val)) return val
253
+ return val.map((elem: any, idx: number) =>
254
+ componentCodecs[idx].decode(elem)
255
+ )
256
+ },
257
+ encode: (val) => {
258
+ if (!Array.isArray(val)) return val
259
+ return val.map((elem: any, idx: number) =>
260
+ componentCodecs[idx].encode(elem)
261
+ )
262
+ },
263
+ })
264
+ }
265
+
266
+ visitVariant(
267
+ _t: IDL.VariantClass,
268
+ fields: Array<[string, IDL.Type]>,
269
+ _data: unknown
270
+ ): z.ZodTypeAny {
271
+ const variantCodecs: Record<string, any> = {}
272
+ for (const [variantName, variantType] of fields) {
273
+ variantCodecs[variantName] = variantType.accept(this, null)
274
+ }
275
+
276
+ const decode = (codec: any, val: any) =>
277
+ codec.decode ? codec.decode(val) : val
278
+ const encode = (codec: any, val: any) =>
279
+ codec.encode ? codec.encode(val) : val
280
+
281
+ return z.codec(z.any(), z.any(), {
282
+ decode: (val: any) => {
283
+ if (
284
+ !val ||
285
+ typeof val !== "object" ||
286
+ Array.isArray(val) ||
287
+ val instanceof Principal ||
288
+ "_type" in val
289
+ ) {
290
+ return val
291
+ }
292
+
293
+ const keys = Object.keys(val)
294
+ if (keys.length !== 1) return val
295
+
296
+ try {
297
+ const extracted = createVariant(val)
298
+ const key = extracted._type
299
+ const fieldType = fields.find(([n]) => n === key)?.[1]
300
+ if (fieldType?.name === "null") return { _type: key }
301
+
302
+ if (key in variantCodecs && nonNullish(extracted[key])) {
303
+ return {
304
+ _type: key,
305
+ [key]: decode(variantCodecs[key], extracted[key]),
306
+ }
307
+ }
308
+ return extracted
309
+ } catch {
310
+ return val
311
+ }
312
+ },
313
+ encode: (val: any) => {
314
+ if (
315
+ !val ||
316
+ typeof val !== "object" ||
317
+ Array.isArray(val) ||
318
+ val instanceof Principal
319
+ ) {
320
+ return val
321
+ }
322
+
323
+ try {
324
+ // Format 1: With _type property (from decode output)
325
+ if ("_type" in val) {
326
+ const key = val._type
327
+ const fieldType = fields.find(([n]) => n === key)?.[1]
328
+ if (fieldType?.name === "null") return { [key]: null }
329
+
330
+ if (key in variantCodecs && nonNullish(val[key])) {
331
+ return { [key]: encode(variantCodecs[key], val[key]) }
332
+ }
333
+ return { [key]: null }
334
+ }
335
+
336
+ // Format 2: Without _type (direct variant format from forms: { Add: value })
337
+ const keys = Object.keys(val)
338
+ if (keys.length === 1) {
339
+ const key = keys[0]
340
+ const fieldType = fields.find(([n]) => n === key)?.[1]
341
+ if (fieldType?.name === "null") return { [key]: null }
342
+
343
+ if (key in variantCodecs && nonNullish(val[key])) {
344
+ return { [key]: encode(variantCodecs[key], val[key]) }
345
+ }
346
+ return { [key]: null }
347
+ }
348
+
349
+ // Unknown format - return as-is
350
+ return val
351
+ } catch {
352
+ return val
353
+ }
354
+ },
355
+ })
356
+ }
357
+
358
+ visitRec<T>(
359
+ t: IDL.RecClass<T>,
360
+ ty: IDL.ConstructType<T>,
361
+ data: unknown
362
+ ): z.ZodTypeAny {
363
+ if (this._recCache.has(t)) return this._recCache.get(t)!
364
+
365
+ const lazyCodec = z.codec(z.any(), z.any(), {
366
+ decode: (val: any) => {
367
+ const codec = ty.accept(this, data)
368
+ return codec.decode ? codec.decode(val) : val
369
+ },
370
+ encode: (val: any) => {
371
+ const codec = ty.accept(this, data)
372
+ return codec.encode ? codec.encode(val) : val
373
+ },
374
+ })
375
+
376
+ this._recCache.set(t, lazyCodec)
377
+ return lazyCodec
378
+ }
379
+
380
+ visitFunc(_t: IDL.FuncClass, _data: unknown): z.ZodTypeAny {
381
+ return z.codec(z.any(), z.any(), {
382
+ decode: (val: any) => {
383
+ if (!Array.isArray(val) || val.length !== 2) return val
384
+ const [principal, method] = val
385
+ return [
386
+ principal instanceof Principal ? principal.toText() : principal,
387
+ method,
388
+ ]
389
+ },
390
+ encode: (val: any) => {
391
+ if (!Array.isArray(val) || val.length !== 2) return val
392
+ const [principalStr, method] = val
393
+ return [
394
+ typeof principalStr === "string"
395
+ ? Principal.fromText(principalStr)
396
+ : principalStr,
397
+ method,
398
+ ]
399
+ },
400
+ })
401
+ }
402
+
403
+ visitService(_t: IDL.ServiceClass, _data: unknown): z.ZodTypeAny {
404
+ return z.codec(z.any(), z.any(), {
405
+ decode: (val) => (val instanceof Principal ? val.toText() : val),
406
+ encode: (val) =>
407
+ typeof val === "string" ? Principal.fromText(val) : val,
408
+ })
409
+ }
410
+ }