@ic-reactor/core 3.0.3-beta.4 → 3.0.3

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 (65) hide show
  1. package/README.md +6 -4
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +2 -3
  4. package/dist/client.js.map +1 -1
  5. package/dist/display/types.d.ts +4 -2
  6. package/dist/display/types.d.ts.map +1 -1
  7. package/dist/display/visitor.d.ts +2 -1
  8. package/dist/display/visitor.d.ts.map +1 -1
  9. package/dist/display/visitor.js +146 -121
  10. package/dist/display/visitor.js.map +1 -1
  11. package/dist/display-reactor.d.ts +9 -41
  12. package/dist/display-reactor.d.ts.map +1 -1
  13. package/dist/display-reactor.js +5 -41
  14. package/dist/display-reactor.js.map +1 -1
  15. package/dist/reactor.d.ts +17 -1
  16. package/dist/reactor.d.ts.map +1 -1
  17. package/dist/reactor.js +60 -44
  18. package/dist/reactor.js.map +1 -1
  19. package/dist/types/display-reactor.d.ts +2 -2
  20. package/dist/types/display-reactor.d.ts.map +1 -1
  21. package/dist/types/reactor.d.ts +8 -9
  22. package/dist/types/reactor.d.ts.map +1 -1
  23. package/dist/types/transform.d.ts +1 -1
  24. package/dist/types/transform.d.ts.map +1 -1
  25. package/dist/utils/helper.d.ts +20 -1
  26. package/dist/utils/helper.d.ts.map +1 -1
  27. package/dist/utils/helper.js +37 -6
  28. package/dist/utils/helper.js.map +1 -1
  29. package/dist/utils/index.d.ts +1 -0
  30. package/dist/utils/index.d.ts.map +1 -1
  31. package/dist/utils/index.js +1 -0
  32. package/dist/utils/index.js.map +1 -1
  33. package/dist/utils/zod.d.ts +34 -0
  34. package/dist/utils/zod.d.ts.map +1 -0
  35. package/dist/utils/zod.js +39 -0
  36. package/dist/utils/zod.js.map +1 -0
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.d.ts.map +1 -1
  39. package/dist/version.js +2 -1
  40. package/dist/version.js.map +1 -1
  41. package/package.json +7 -6
  42. package/src/client.ts +571 -0
  43. package/src/display/helper.ts +92 -0
  44. package/src/display/index.ts +3 -0
  45. package/src/display/types.ts +91 -0
  46. package/src/display/visitor.ts +415 -0
  47. package/src/display-reactor.ts +361 -0
  48. package/src/errors/index.ts +246 -0
  49. package/src/index.ts +8 -0
  50. package/src/reactor.ts +461 -0
  51. package/src/types/client.ts +110 -0
  52. package/src/types/display-reactor.ts +73 -0
  53. package/src/types/index.ts +6 -0
  54. package/src/types/reactor.ts +188 -0
  55. package/src/types/result.ts +50 -0
  56. package/src/types/transform.ts +29 -0
  57. package/src/types/variant.ts +39 -0
  58. package/src/utils/agent.ts +201 -0
  59. package/src/utils/candid.ts +112 -0
  60. package/src/utils/constants.ts +12 -0
  61. package/src/utils/helper.ts +155 -0
  62. package/src/utils/index.ts +5 -0
  63. package/src/utils/polling.ts +330 -0
  64. package/src/utils/zod.ts +56 -0
  65. package/src/version.ts +5 -0
@@ -0,0 +1,188 @@
1
+ import type {
2
+ ActorMethod,
3
+ ActorSubclass,
4
+ CallConfig,
5
+ PollingOptions,
6
+ } from "@icp-sdk/core/agent"
7
+ import type { Principal } from "@icp-sdk/core/principal"
8
+ import type { QueryKey } from "@tanstack/query-core"
9
+ import type { ClientManager } from "../client"
10
+ import type { CallError, CanisterError } from "../errors"
11
+ import type { OkResult, ErrResult } from "./result"
12
+ import type { DisplayOf, ActorDisplayCodec } from "../display"
13
+
14
+ export interface DefaultActorType {
15
+ [key: string]: ActorMethod<any, any>
16
+ }
17
+
18
+ export type BaseActor<T = DefaultActorType> = ActorSubclass<T>
19
+
20
+ export type FunctionName<A = BaseActor> = Extract<keyof A, string>
21
+
22
+ export type FunctionType = "query" | "update"
23
+
24
+ export type CanisterId = string | Principal
25
+
26
+ // Extracts the argument types of an ActorMethod
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ export type ActorMethodParameters<T> =
29
+ T extends ActorMethod<infer Args, any> ? Args : never
30
+
31
+ // Extracts the return type of an ActorMethod
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ export type ActorMethodReturnType<T> =
34
+ T extends ActorMethod<any, infer Ret> ? Ret : never
35
+
36
+ export interface ReactorParameters {
37
+ clientManager: ClientManager
38
+ name: string
39
+ idlFactory: (IDL: any) => any
40
+ canisterId?: CanisterId
41
+ pollingOptions?: PollingOptions
42
+ }
43
+
44
+ export type ActorMethodType<A, M extends keyof A> = {
45
+ (...args: ActorMethodParameters<A[M]>): Promise<ActorMethodReturnType<A[M]>>
46
+ withOptions: (
47
+ options?: CallConfig
48
+ ) => (
49
+ ...args: ActorMethodParameters<A[M]>
50
+ ) => Promise<ActorMethodReturnType<A[M]>>
51
+ }
52
+
53
+ /**
54
+ * Registry for argument transformations.
55
+ * Users can augment this interface to add custom transforms:
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // In your code, augment the module
60
+ * declare module '@ic-reactor/core' {
61
+ * interface TransformArgsRegistry<T> {
62
+ * myCustom: MyCustomArgTransform<T>
63
+ * }
64
+ * }
65
+ * ```
66
+ */
67
+ /**
68
+ * Helper to extract arguments type for codecs (unwraps single argument tuples).
69
+ */
70
+ export type ArgsType<T> = T extends readonly [infer U]
71
+ ? U
72
+ : T extends readonly []
73
+ ? null
74
+ : T
75
+
76
+ export interface TransformArgsRegistry<T> {
77
+ candid: T
78
+ display: AsDisplayArgs<T>
79
+ }
80
+
81
+ /**
82
+ * Registry for return type transformations.
83
+ * Users can augment this interface to add custom transforms:
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * declare module '@ic-reactor/core' {
88
+ * interface TransformReturnRegistry<T> {
89
+ * myCustom: MyCustomReturnTransform<T>
90
+ * }
91
+ * }
92
+ * ```
93
+ */
94
+ // @ts-expect-error - A is used in module augmentation
95
+ export interface TransformReturnRegistry<T, A = BaseActor> {
96
+ candid: T
97
+ display: DisplayOf<T>
98
+ }
99
+
100
+ /**
101
+ * Helper type to transform args array elements using ToDisplay
102
+ */
103
+ export type AsDisplayArgs<T> = T extends readonly unknown[]
104
+ ? { [K in keyof T]: DisplayOf<T[K]> }
105
+ : DisplayOf<T>
106
+
107
+ /**
108
+ * Union of all available transform keys.
109
+ * Automatically includes any user-defined transforms via module augmentation.
110
+ */
111
+ export type TransformKey = keyof TransformArgsRegistry<unknown>
112
+
113
+ /**
114
+ * Apply argument transformation based on the transform key.
115
+ * Looks up the transform in TransformArgsRegistry.
116
+ */
117
+ export type ReactorArgs<
118
+ A,
119
+ M extends FunctionName<A>,
120
+ Transform extends TransformKey = "candid",
121
+ > = TransformArgsRegistry<ActorMethodParameters<A[M]>>[Transform]
122
+
123
+ /**
124
+ * Apply return type transformation based on the transform key.
125
+ * Looks up the transform in TransformReturnRegistry.
126
+ */
127
+ export type ReactorReturnOk<
128
+ A,
129
+ M extends FunctionName<A>,
130
+ Transform extends TransformKey = "candid",
131
+ > = TransformReturnRegistry<OkResult<ActorMethodReturnType<A[M]>>, A>[Transform]
132
+
133
+ export type ReactorReturnErr<
134
+ A,
135
+ M extends FunctionName<A>,
136
+ Transform extends TransformKey = "candid",
137
+ > =
138
+ | CanisterError<
139
+ TransformReturnRegistry<
140
+ ErrResult<ActorMethodReturnType<A[M]>>,
141
+ A
142
+ >[Transform]
143
+ >
144
+ | CallError
145
+
146
+ /**
147
+ * Helper type for actor method codecs returend by getCodec
148
+ */
149
+ export interface ActorMethodCodecs<A, M extends FunctionName<A>> {
150
+ args: ActorDisplayCodec<
151
+ ArgsType<ActorMethodParameters<A[M]>>,
152
+ DisplayOf<ArgsType<ActorMethodParameters<A[M]>>>
153
+ >
154
+ result: ActorDisplayCodec<
155
+ ActorMethodReturnType<A[M]>,
156
+ DisplayOf<ActorMethodReturnType<A[M]>>
157
+ >
158
+ }
159
+
160
+ // ══════════════════════════════════════════════════════════════════════════
161
+ // REACTOR QUERY PARAMS - Reusable parameter types for reactor methods
162
+ // ══════════════════════════════════════════════════════════════════════════
163
+
164
+ /**
165
+ * Basic query parameters for reactor cache operations.
166
+ * Used by: generateQueryKey, getQueryData
167
+ */
168
+ export interface ReactorQueryParams<
169
+ A,
170
+ M extends FunctionName<A>,
171
+ T extends TransformKey = "candid",
172
+ > {
173
+ functionName: M
174
+ args?: ReactorArgs<A, M, T>
175
+ queryKey?: QueryKey
176
+ }
177
+
178
+ /**
179
+ * Query parameters with optional call configuration.
180
+ * Used by: getQueryOptions, fetchQuery, callMethod
181
+ */
182
+ export interface ReactorCallParams<
183
+ A,
184
+ M extends FunctionName<A>,
185
+ T extends TransformKey = "candid",
186
+ > extends ReactorQueryParams<A, M, T> {
187
+ callConfig?: CallConfig
188
+ }
@@ -0,0 +1,50 @@
1
+ export type UnwrapOkErrResult<T> = T extends { Ok: infer U }
2
+ ? U
3
+ : T extends { ok: infer U }
4
+ ? U
5
+ : T extends { Err: infer E }
6
+ ? E
7
+ : T extends { err: infer E }
8
+ ? E
9
+ : T
10
+
11
+ /**
12
+ * Extract the Ok value from a Result type.
13
+ * Supports both uppercase (Ok/Err - Rust) and lowercase (ok/err - Motoko).
14
+ * - If T is { Ok: U } or { ok: U }, returns U
15
+ * - If T is { Err: E } or { err: E }, returns never (filters it out from unions)
16
+ * - If T is { Ok: U } | { Err: E }, returns U (the Err variant is filtered out)
17
+ * - Otherwise, returns T as-is
18
+ */
19
+ export type OkResult<T> = T extends { Err: unknown }
20
+ ? never
21
+ : T extends { err: unknown }
22
+ ? never
23
+ : T extends { Ok: infer U }
24
+ ? U
25
+ : T extends { ok: infer U }
26
+ ? U
27
+ : T
28
+
29
+ export type ErrResult<T> = T extends { Ok: unknown }
30
+ ? never
31
+ : T extends { ok: unknown }
32
+ ? never
33
+ : T extends { Err: infer E }
34
+ ? E
35
+ : T extends { err: infer E }
36
+ ? E
37
+ : never
38
+
39
+ /**
40
+ * Check if T is a Result type ({ Ok: U } | { Err: E } or { ok: U } | { err: E })
41
+ */
42
+ export type IsOkErrResultType<T> = T extends { Ok: unknown }
43
+ ? true
44
+ : T extends { ok: unknown }
45
+ ? true
46
+ : T extends { Err: unknown }
47
+ ? true
48
+ : T extends { err: unknown }
49
+ ? true
50
+ : false
@@ -0,0 +1,29 @@
1
+ type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
2
+ x: infer I
3
+ ) => void
4
+ ? I
5
+ : never
6
+
7
+ // The "Last" type extracts the last member of a union type
8
+ type Last<T> =
9
+ UnionToIntersection<T extends any ? (x: T) => void : never> extends (
10
+ x: infer L
11
+ ) => void
12
+ ? L
13
+ : never
14
+
15
+ /// Convert a union type to a tuple type (order is not guaranteed)
16
+ /// (Note: there are several variants of this trick – choose one that suits your needs.)
17
+ export type UnionToTuple<T, L = Last<T>> = [T] extends [never]
18
+ ? []
19
+ : [...UnionToTuple<Exclude<T, L>>, L]
20
+
21
+ export type IsBlobType<T> = T extends Uint8Array
22
+ ? true
23
+ : T extends number[]
24
+ ? number[] extends T
25
+ ? true
26
+ : false
27
+ : false
28
+
29
+ export type IsOptionalType<T> = [T] extends [[] | [any]] ? true : false
@@ -0,0 +1,39 @@
1
+ export type IsCandidVariant<T> = [T] extends [CandidVariantToIntersection<T>]
2
+ ? false
3
+ : true
4
+
5
+ export type CandidVariantToIntersection<U> = (
6
+ U extends object ? (k: U) => void : never
7
+ ) extends (k: infer I) => void
8
+ ? I
9
+ : never
10
+
11
+ export type CandidVariantKey<T> = T extends any ? keyof T : never
12
+
13
+ export type CandidVariantValue<T, K extends CandidVariantKey<T>> =
14
+ T extends Record<K, infer U> ? U : never
15
+
16
+ export type CandidVariant<T> =
17
+ IsCandidVariant<T> extends true
18
+ ? {
19
+ _type: CandidVariantKey<T> & string
20
+ } & {
21
+ [K in CandidVariantKey<T> & string as CandidVariantValue<
22
+ T,
23
+ K
24
+ > extends null
25
+ ? never
26
+ : K]: CandidVariantValue<T, K>
27
+ }
28
+ : T
29
+
30
+ /**
31
+ * A type that represents the extracted key and value from a variant
32
+ * Designed to be used with the extractVariant function
33
+ */
34
+ export type CandidKeyValue<T> = T extends infer U
35
+ ? [
36
+ CandidVariantKey<U> & string,
37
+ CandidVariantValue<U, CandidVariantKey<U> & string>,
38
+ ]
39
+ : never
@@ -0,0 +1,201 @@
1
+ import type {
2
+ ApiQueryResponse,
3
+ HttpAgent,
4
+ HttpDetailsResponse,
5
+ PollingOptions,
6
+ SubmitResponse,
7
+ } from "@icp-sdk/core/agent"
8
+ import { Principal } from "@icp-sdk/core/principal"
9
+
10
+ import {
11
+ isV2ResponseBody,
12
+ isV4ResponseBody,
13
+ Certificate,
14
+ lookupResultToBuffer,
15
+ pollForResponse,
16
+ QueryResponseStatus,
17
+ UncertifiedRejectErrorCode,
18
+ RejectError,
19
+ UncertifiedRejectUpdateErrorCode,
20
+ CertifiedRejectErrorCode,
21
+ MissingRootKeyErrorCode,
22
+ ExternalError,
23
+ UnknownError,
24
+ UnexpectedErrorCode,
25
+ } from "@icp-sdk/core/agent"
26
+
27
+ // ══════════════════════════════════════════════════════════════════════
28
+ // QUERY CALL RESPONSE PROCESSING
29
+ // ══════════════════════════════════════════════════════════════════════
30
+
31
+ /**
32
+ * Process a query call response following the exact logic from @icp-sdk/core/agent Actor.
33
+ *
34
+ * @param response - The query call response options
35
+ * @returns The raw reply bytes
36
+ * @throws CallError if the query was rejected
37
+ */
38
+ export function processQueryCallResponse(
39
+ response: ApiQueryResponse,
40
+ canisterId: Principal,
41
+ methodName: string
42
+ ): Uint8Array {
43
+ switch (response.status) {
44
+ case QueryResponseStatus.Rejected: {
45
+ const uncertifiedRejectErrorCode = new UncertifiedRejectErrorCode(
46
+ response.requestId,
47
+ response.reject_code,
48
+ response.reject_message,
49
+ response.error_code,
50
+ response.signatures
51
+ )
52
+ uncertifiedRejectErrorCode.callContext = {
53
+ canisterId,
54
+ methodName,
55
+ httpDetails: response.httpDetails,
56
+ }
57
+ throw RejectError.fromCode(uncertifiedRejectErrorCode)
58
+ }
59
+
60
+ case QueryResponseStatus.Replied:
61
+ return response.reply.arg
62
+ }
63
+ }
64
+
65
+ // ══════════════════════════════════════════════════════════════════════
66
+ // UPDATE CALL RESPONSE PROCESSING
67
+ // ══════════════════════════════════════════════════════════════════════
68
+
69
+ /**
70
+ * Process an update call response following the exact logic from @icp-sdk/core/agent Actor.
71
+ *
72
+ * This handles:
73
+ * - V4 responses with embedded certificate (sync call response)
74
+ * - V2 responses with immediate rejection
75
+ * - 202 responses that require polling
76
+ *
77
+ * @param result - The submit response from agent.call()
78
+ * @param canisterId - The target canister ID
79
+ * @param methodName - The method name being called
80
+ * @param agent - The HTTP agent
81
+ * @param pollingOptions - Options for polling
82
+ * @param blsVerify - Optional BLS verification function
83
+ * @returns The raw reply bytes
84
+ * @throws RejectError if the call was rejected
85
+ * @throws UnknownError if the response format is unexpected
86
+ */
87
+ export async function processUpdateCallResponse(
88
+ result: SubmitResponse,
89
+ canisterId: Principal,
90
+ methodName: string,
91
+ agent: HttpAgent,
92
+ pollingOptions: PollingOptions
93
+ ): Promise<Uint8Array> {
94
+ let reply: Uint8Array | undefined
95
+ let certificate: Certificate | undefined
96
+
97
+ if (isV4ResponseBody(result.response.body)) {
98
+ if (agent.rootKey == null) {
99
+ throw ExternalError.fromCode(new MissingRootKeyErrorCode())
100
+ }
101
+ const cert = result.response.body.certificate
102
+ certificate = await Certificate.create({
103
+ certificate: cert,
104
+ rootKey: agent.rootKey,
105
+ principal: { canisterId },
106
+ agent,
107
+ })
108
+
109
+ const path = [new TextEncoder().encode("request_status"), result.requestId]
110
+ const status = new TextDecoder().decode(
111
+ lookupResultToBuffer(certificate.lookup_path([...path, "status"]))
112
+ )
113
+
114
+ switch (status) {
115
+ case "replied":
116
+ reply = lookupResultToBuffer(
117
+ certificate.lookup_path([...path, "reply"])
118
+ )
119
+ break
120
+ case "rejected": {
121
+ // Find rejection details in the certificate
122
+ const rejectCode = new Uint8Array(
123
+ lookupResultToBuffer(
124
+ certificate.lookup_path([...path, "reject_code"])
125
+ )!
126
+ )[0]
127
+ const rejectMessage = new TextDecoder().decode(
128
+ lookupResultToBuffer(
129
+ certificate.lookup_path([...path, "reject_message"])
130
+ )!
131
+ )
132
+
133
+ const error_code_buf = lookupResultToBuffer(
134
+ certificate.lookup_path([...path, "error_code"])
135
+ )
136
+ const error_code = error_code_buf
137
+ ? new TextDecoder().decode(error_code_buf)
138
+ : undefined
139
+
140
+ const certifiedRejectErrorCode = new CertifiedRejectErrorCode(
141
+ result.requestId,
142
+ rejectCode,
143
+ rejectMessage,
144
+ error_code
145
+ )
146
+ certifiedRejectErrorCode.callContext = {
147
+ canisterId,
148
+ methodName,
149
+ httpDetails: result.response,
150
+ }
151
+ throw RejectError.fromCode(certifiedRejectErrorCode)
152
+ }
153
+ }
154
+ } else if (isV2ResponseBody(result.response.body)) {
155
+ const { reject_code, reject_message, error_code } = result.response.body
156
+ const errorCode = new UncertifiedRejectUpdateErrorCode(
157
+ result.requestId,
158
+ reject_code,
159
+ reject_message,
160
+ error_code
161
+ )
162
+ errorCode.callContext = {
163
+ canisterId,
164
+ methodName,
165
+ httpDetails: result.response,
166
+ }
167
+ throw RejectError.fromCode(errorCode)
168
+ }
169
+
170
+ // Fall back to polling if we receive an Accepted response code
171
+ if (result.response.status === 202) {
172
+ // Contains the certificate and the reply from the boundary node
173
+ const response = await pollForResponse(
174
+ agent,
175
+ canisterId,
176
+ result.requestId,
177
+ pollingOptions
178
+ )
179
+ certificate = response.certificate
180
+ reply = response.reply
181
+ }
182
+
183
+ if (reply !== undefined) {
184
+ return reply
185
+ }
186
+
187
+ // Unexpected response format
188
+ const httpDetails = {
189
+ ...result.response,
190
+ requestDetails: result.requestDetails,
191
+ } as HttpDetailsResponse
192
+ const errorCode = new UnexpectedErrorCode(
193
+ `Call was returned undefined. We cannot determine if the call was successful or not.`
194
+ )
195
+ errorCode.callContext = {
196
+ canisterId,
197
+ methodName,
198
+ httpDetails,
199
+ }
200
+ throw UnknownError.fromCode(errorCode)
201
+ }
@@ -0,0 +1,112 @@
1
+ import {
2
+ CandidVariant,
3
+ CandidVariantKey,
4
+ CandidVariantValue,
5
+ CandidKeyValue,
6
+ } from "../types"
7
+
8
+ /**
9
+ * Creates a Candid variant from a string value.
10
+ * @param str - The string to convert into a variant
11
+ * @returns An object representing the Candid variant
12
+ */
13
+ export function createNullVariant<T extends string>(str: T): Record<T, null> {
14
+ return { [str]: null } as Record<T, null>
15
+ }
16
+
17
+ /**
18
+ * Creates a Candid variant from a record.
19
+ * @param variant - The record to convert into a variant
20
+ * @returns An object representing the Candid variant
21
+ */
22
+ export function createVariant<T extends Record<string, any>>(
23
+ variant: T
24
+ ): CandidVariant<T> {
25
+ const keys = Object.keys(variant)
26
+ if (keys.length !== 1) {
27
+ throw new Error(`
28
+ Invalid variant: must have exactly one key but found ${keys.length} keys: ${keys.map(
29
+ (key) => `${key}: ${variant[key]}`
30
+ )}
31
+ `)
32
+ }
33
+ const key = keys[0] as keyof T
34
+ const value = variant[key]
35
+
36
+ return {
37
+ _type: key,
38
+ ...(value !== null ? { [key]: value } : {}),
39
+ } as CandidVariant<T>
40
+ }
41
+
42
+ /**
43
+ * Extract variant key and value from a variant type
44
+ * Works with types like:
45
+ * type User = { 'Business': BusinessUser } | { 'Individual': IndividualUser }
46
+ *
47
+ * @template T - The variant type
48
+ * @returns A tuple containing the key and value of the variant
49
+ * @throws Error if the variant object does not have exactly one key
50
+ */
51
+ export function getVariantKeyValue<T extends Record<string, any>>(
52
+ variant: T
53
+ ): CandidKeyValue<T> {
54
+ const keys = Object.keys(variant)
55
+ if (keys.length !== 1) {
56
+ const msg = `Invalid variant: must have exactly one key but found ${keys.length} keys: ${keys}`
57
+ throw new Error(msg)
58
+ }
59
+ const key = keys[0] as CandidKeyValue<T>[0]
60
+ const value = variant[key] as CandidKeyValue<T>[1]
61
+
62
+ return [key, value] as CandidKeyValue<T>
63
+ }
64
+
65
+ /**
66
+ * Extracts the key from a Candid variant type.
67
+ * Variants in Candid are represented as objects with a single key-value pair.
68
+ * @param variant - The variant object
69
+ * @returns The key of the variant
70
+ */
71
+ export function getVariantKey<T extends Record<string, any>>(
72
+ variant: T
73
+ ): CandidVariantKey<T> {
74
+ const keys = Object.keys(variant)
75
+ if (keys.length !== 1) {
76
+ throw new Error(
77
+ `Invalid variant: must have exactly one key but found ${keys}`
78
+ )
79
+ }
80
+ return keys[0] as CandidVariantKey<T>
81
+ }
82
+
83
+ /**
84
+ * Extracts the value from a Candid variant type.
85
+ * @param variant - The variant object
86
+ * @returns The value associated with the variant's key
87
+ */
88
+ export function getVariantValue<
89
+ T extends Record<string, any>,
90
+ K extends CandidVariantKey<T> = CandidVariantKey<T>,
91
+ >(variant: T): CandidVariantValue<T, K> {
92
+ return variant[getVariantKey(variant)]
93
+ }
94
+
95
+ export function getVariantValueByKey<
96
+ T extends Record<string, any>,
97
+ K extends CandidVariantKey<T>,
98
+ >(variant: T, key: K): CandidVariantValue<T, K> {
99
+ if (getVariantKey(variant) !== key) {
100
+ throw new Error(
101
+ `Variant key mismatch: expected ${key}, got ${getVariantKey(variant)}`
102
+ )
103
+ }
104
+ return variant[key]
105
+ }
106
+
107
+ export function isKeyMatchVariant<
108
+ T extends Record<string, any>,
109
+ K extends CandidVariantKey<T>,
110
+ >(variant: T, key: K): variant is T & Record<K, unknown> {
111
+ return getVariantKey(variant) === key
112
+ }
@@ -0,0 +1,12 @@
1
+ export const REMOTE_HOSTS = [".github.dev", ".gitpod.io"]
2
+
3
+ export const LOCAL_HOSTS = ["localhost", "127.0.0.1"]
4
+
5
+ export const IC_HOST_NETWORK_URI = "https://ic0.app"
6
+
7
+ export const LOCAL_HOST_NETWORK_URI = "http://127.0.0.1:4943"
8
+
9
+ export const IC_INTERNET_IDENTITY_PROVIDER = "https://id.ai"
10
+
11
+ export const LOCAL_INTERNET_IDENTITY_PROVIDER =
12
+ "http://rdmx6-jaaaa-aaaaa-aaadq-cai.localhost:4943"