@ic-reactor/core 3.0.3-beta.5 → 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 +7 -7
  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,155 @@
1
+ import { LOCAL_HOSTS, REMOTE_HOSTS } from "./constants"
2
+ import { CanisterError } from "../errors"
3
+ import { OkResult } from "../types"
4
+
5
+ export const generateKey = (args: any[]) => {
6
+ return JSON.stringify(args, (_, v) =>
7
+ typeof v === "bigint" ? v.toString() : v
8
+ )
9
+ }
10
+
11
+ /**
12
+ * Checks if the current environment is local or development.
13
+ *
14
+ * @returns `true` if running in a local or development environment, otherwise `false`.
15
+ */
16
+ export const isInLocalOrDevelopment = () => {
17
+ return typeof process !== "undefined" && process.env.DFX_NETWORK === "local"
18
+ }
19
+
20
+ /**
21
+ * Retrieves the network from the process environment variables.
22
+ *
23
+ * @returns The network name, defaulting to "ic" if not specified.
24
+ */
25
+ export const getProcessEnvNetwork = () => {
26
+ if (typeof process === "undefined") return "ic"
27
+ else return process.env.DFX_NETWORK ?? "ic"
28
+ }
29
+
30
+ /**
31
+ * Detect whether the runtime should be considered *development*.
32
+ *
33
+ * Checks in order:
34
+ * - `import.meta.env?.DEV` (Vite / ESM environments)
35
+ * - `process.env.NODE_ENV === 'development'` (Node)
36
+ * - `process.env.DFX_NETWORK === 'local'` (local IC replica)
37
+ */
38
+ export const isDev = (): boolean => {
39
+ const importMetaDev =
40
+ typeof import.meta !== "undefined" && (import.meta as any).env?.DEV
41
+ const nodeDev =
42
+ typeof process !== "undefined" &&
43
+ (process.env.NODE_ENV === "development" ||
44
+ process.env.DFX_NETWORK === "local")
45
+
46
+ return Boolean(importMetaDev || nodeDev)
47
+ }
48
+
49
+ /**
50
+ * Determines the network type based on the provided hostname.
51
+ *
52
+ * @param hostname - The hostname to evaluate.
53
+ * @returns A string indicating the network type: "local", "remote", or "ic".
54
+ */
55
+ export function getNetworkByHostname(
56
+ hostname: string
57
+ ): "local" | "remote" | "ic" {
58
+ if (LOCAL_HOSTS.some((host) => hostname.endsWith(host))) {
59
+ return "local"
60
+ } else if (REMOTE_HOSTS.some((host) => hostname.endsWith(host))) {
61
+ return "remote"
62
+ } else {
63
+ return "ic"
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Helper function for extracting the value from a compiled result { Ok: T } or throw a CanisterError if { Err: E }
69
+ * Supports both uppercase (Ok/Err - Rust) and lowercase (ok/err - Motoko) conventions.
70
+ *
71
+ * @param result - The compiled result to extract from.
72
+ * @returns The extracted value from the compiled result.
73
+ * @throws CanisterError with the typed error value if result is { Err: E } or { err: E }
74
+ */
75
+ export function extractOkResult<T>(result: T): OkResult<T> {
76
+ if (!result || typeof result !== "object") {
77
+ // Non-object, return as-is
78
+ return result as OkResult<T>
79
+ }
80
+
81
+ // Handle { Ok: T } (Rust convention)
82
+ if ("Ok" in result) {
83
+ return result.Ok as OkResult<T>
84
+ }
85
+ // Handle { ok: T } (Motoko convention)
86
+ if ("ok" in result) {
87
+ return result.ok as OkResult<T>
88
+ }
89
+
90
+ // Handle { Err: E } (Rust convention) - throw CanisterError
91
+ if ("Err" in result) {
92
+ throw new CanisterError(result.Err)
93
+ }
94
+ // Handle { err: E } (Motoko convention) - throw CanisterError
95
+ if ("err" in result) {
96
+ throw new CanisterError(result.err)
97
+ }
98
+
99
+ // Non-Result type, return as-is
100
+ return result as OkResult<T>
101
+ }
102
+
103
+ export const isNullish = (value: unknown): value is null | undefined =>
104
+ value === null || value === undefined
105
+
106
+ export const nonNullish = <T>(value: T | null | undefined): value is T =>
107
+ value !== null && value !== undefined
108
+
109
+ /**
110
+ * Converts a Uint8Array or number array to a hex string (without 0x prefix)
111
+ */
112
+ export const uint8ArrayToHex = (bytes: Uint8Array | number[]): string => {
113
+ return Array.from(bytes)
114
+ .map((b) => b.toString(16).padStart(2, "0"))
115
+ .join("")
116
+ }
117
+
118
+ /**
119
+ * Converts a hex string to Uint8Array (accepts with or without 0x prefix)
120
+ */
121
+ export const hexToUint8Array = (hex: string): Uint8Array<ArrayBuffer> => {
122
+ // Normalize: remove 0x prefix if present and filter invalid chars
123
+ const normalized = hex
124
+ .replace(/^0x/i, "")
125
+ .replace(/[^0-9a-f]/gi, "")
126
+ .toLowerCase()
127
+
128
+ // Handle odd-length hex strings by padding with leading zero
129
+ const paddedHex = normalized.length % 2 ? `0${normalized}` : normalized
130
+
131
+ return new Uint8Array(
132
+ paddedHex.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) ?? []
133
+ )
134
+ }
135
+
136
+ /**
137
+ * Formats hex string with 0x prefix for display purposes
138
+ */
139
+ export const formatHexDisplay = (hex: string): `0x${string}` => {
140
+ const normalized = hex.replace(/^0x/i, "")
141
+ return `0x${normalized}`
142
+ }
143
+
144
+ /**
145
+ * Converts a JSON-serializable value to a string, handling BigInt values.
146
+ * @param value - The value to convert
147
+ * @returns A string representation of the value
148
+ */
149
+ export const jsonToString = (value: any): string => {
150
+ return JSON.stringify(
151
+ value,
152
+ (_, v) => (typeof v === "bigint" ? v.toString() : v),
153
+ 2
154
+ )
155
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./candid"
2
+ export * from "./constants"
3
+ export * from "./helper"
4
+ export * from "./polling"
5
+ export * from "./zod"
@@ -0,0 +1,330 @@
1
+ /**
2
+ * @module polling
3
+ * @description
4
+ * Polling strategy for Internet Computer (Dfinity) agent update calls.
5
+ *
6
+ * This module provides a configurable, intelligent polling mechanism that:
7
+ * - Starts with rapid polling for quick responses (fast phase)
8
+ * - Gradually increases delay intervals (ramp phase)
9
+ * - Settles into steady-state polling (plateau phase)
10
+ * - Adds jitter to prevent thundering herd problems
11
+ * - Provides structured logging with elapsed time and status
12
+ * - Supports graceful cancellation via AbortSignal
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const strategy = createPollingStrategy({
17
+ * context: "signer-creation",
18
+ * fastAttempts: 10,
19
+ * plateauDelayMs: 5000
20
+ * });
21
+ *
22
+ * const result = await actor.createSigner(params, {
23
+ * pollingOptions: { strategy }
24
+ * });
25
+ * ```
26
+ */
27
+
28
+ import {
29
+ PollStrategy,
30
+ RequestId,
31
+ RequestStatusResponseStatus,
32
+ } from "@icp-sdk/core/agent"
33
+ import { Principal } from "@icp-sdk/core/principal"
34
+
35
+ export interface PollingConfig {
36
+ /**
37
+ * Logical operation name used in log messages for identifying the polling context.
38
+ *
39
+ * Used to distinguish between different polling operations in console output.
40
+ * Choose descriptive names that help with debugging and monitoring.
41
+ *
42
+ * @default "operation"
43
+ * @example "sign-transaction", "document-upload"
44
+ */
45
+ context?: string
46
+
47
+ /**
48
+ * Custom prefix prepended to all log messages from this polling strategy.
49
+ *
50
+ * Allows filtering and identifying logs specific to this polling instance.
51
+ * Useful when running multiple polling operations concurrently.
52
+ *
53
+ * @default "[Polling]"
54
+ * @example "[PaymentPolling]", "[TransactionPolling]", "[SignerPolling]"
55
+ */
56
+ logPrefix?: string
57
+
58
+ /**
59
+ * Number of initial rapid polling attempts before transitioning to ramp phase.
60
+ *
61
+ * During fast phase, polls occur at `fastDelayMs` intervals (plus jitter).
62
+ * Higher values = more aggressive initial polling for fast responses.
63
+ * Lower values = faster transition to exponential backoff.
64
+ *
65
+ * @default 10
66
+ * @example 5 (for slower operations), 15 (for very fast operations)
67
+ */
68
+ fastAttempts?: number
69
+
70
+ /**
71
+ * Base delay in milliseconds between polls during the fast phase.
72
+ *
73
+ * This is the baseline interval before jitter is applied.
74
+ * Actual delay will vary by ±`jitterRatio` percentage.
75
+ * Lower values = more aggressive polling, higher network load.
76
+ *
77
+ * @default 100
78
+ * @example 50 (aggressive), 200 (conservative)
79
+ */
80
+ fastDelayMs?: number
81
+
82
+ /**
83
+ * Duration threshold in milliseconds for the ramp phase.
84
+ *
85
+ * While elapsed time < rampUntilMs, delay grows exponentially from
86
+ * `fastDelayMs` up to `plateauDelayMs` using a power curve (0.7 exponent).
87
+ * After this duration, polling enters plateau phase with constant delays.
88
+ *
89
+ * @default 20000 (20 seconds)
90
+ * @example 10_000 (faster transition), 30_000 (longer ramp for slow ops)
91
+ */
92
+ rampUntilMs?: number
93
+
94
+ /**
95
+ * Steady-state polling delay in milliseconds during plateau phase.
96
+ *
97
+ * Once ramp phase completes, all subsequent polls use this interval (plus jitter).
98
+ * This is the "cruise speed" for long-running operations.
99
+ * Balance between responsiveness and network/resource efficiency.
100
+ *
101
+ * @default 5000 (5 seconds)
102
+ * @example 2_000 (more responsive), 10_000 (more efficient)
103
+ */
104
+ plateauDelayMs?: number
105
+
106
+ /**
107
+ * Randomization ratio (0-1) for adding jitter to prevent thundering herd.
108
+ *
109
+ * Jitter adds ±(ratio * delay) randomness to each polling interval.
110
+ * Higher values = more randomization, better distribution across time.
111
+ * Lower values = more predictable intervals, less variance.
112
+ * Prevents synchronized polling when multiple clients start simultaneously.
113
+ *
114
+ * @default 0.4 (±40% randomization)
115
+ * @example 0.2 (±20%, less jitter), 0.5 (±50%, more jitter)
116
+ */
117
+ jitterRatio?: number
118
+
119
+ /**
120
+ * Maximum time in milliseconds between log outputs (heartbeat interval).
121
+ *
122
+ * Forces a log message if this much time passes without logging,
123
+ * even if normal log throttling would suppress it.
124
+ * Prevents "silent" long-running operations; ensures monitoring visibility.
125
+ *
126
+ * @default 15000 (15 seconds)
127
+ * @example 10_000 (more frequent heartbeats), 30_000 (less verbose)
128
+ */
129
+ maxLogIntervalMs?: number
130
+
131
+ /**
132
+ * External abort signal for graceful cancellation of polling operation.
133
+ *
134
+ * When the signal is aborted, the polling strategy throws an error
135
+ * on the next poll attempt. Use AbortController to create signals.
136
+ * Allows coordinated cancellation across multiple async operations.
137
+ *
138
+ * @default undefined (no external cancellation)
139
+ * @example
140
+ * ```typescript
141
+ * const controller = new AbortController();
142
+ * const strategy = createPollingStrategy({
143
+ * abortSignal: controller.signal
144
+ * });
145
+ * // Later: controller.abort();
146
+ * ```
147
+ */
148
+ abortSignal?: AbortSignal
149
+ }
150
+
151
+ /**
152
+ * Creates an polling strategy for Internet Computer agent update calls.
153
+ *
154
+ * The strategy implements three phases:
155
+ * 1. **Fast Phase**: Initial rapid polling (default: 10 attempts @ 100ms intervals)
156
+ * 2. **Ramp Phase**: Exponential backoff growth (default: up to 20s elapsed)
157
+ * 3. **Plateau Phase**: Steady-state polling (default: 5s intervals)
158
+ *
159
+ * The strategy continues polling while request status is RECEIVED/PROCESSING,
160
+ * and only terminates on REPLIED/REJECTED/DONE status or when aborted.
161
+ *
162
+ * @param {PollingConfig} [cfg={}] - Configuration options
163
+ * @returns {PollStrategy} - Async strategy function compatible with agent pollingOptions.strategy
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * // Basic usage
168
+ * const strategy = createPollingStrategy();
169
+ *
170
+ * // Custom configuration for long-running operations
171
+ * const strategy = createPollingStrategy({
172
+ * context: "blockchain-sync",
173
+ * fastAttempts: 5,
174
+ * fastDelayMs: 200,
175
+ * rampUntilMs: 30_000,
176
+ * plateauDelayMs: 10_000,
177
+ * jitterRatio: 0.3
178
+ * });
179
+ *
180
+ * // With abort signal
181
+ * const controller = new AbortController();
182
+ * const strategy = createPollingStrategy({
183
+ * context: "transaction-signing",
184
+ * abortSignal: controller.signal
185
+ * });
186
+ * // Later: controller.abort();
187
+ * ```
188
+ *
189
+ * @throws {Error} When abortSignal is triggered during polling
190
+ */
191
+ export function createPollingStrategy(cfg: PollingConfig = {}): PollStrategy {
192
+ const {
193
+ context = "operation",
194
+ logPrefix = "[Polling]",
195
+ fastAttempts = 10,
196
+ fastDelayMs = 100,
197
+ rampUntilMs = 20_000,
198
+ plateauDelayMs = 5_000,
199
+ jitterRatio = 0.4,
200
+ maxLogIntervalMs = 15_000,
201
+ abortSignal,
202
+ } = cfg
203
+
204
+ let attempt = 0
205
+ const start = Date.now()
206
+ let lastLog = 0
207
+
208
+ /**
209
+ * Structured logging function that outputs polling state information.
210
+ * Implements intelligent log throttling to prevent console spam while ensuring
211
+ * periodic heartbeat logs for long-running operations.
212
+ *
213
+ * @param {string} phase - Current polling phase ("fast", "ramp", or "plateau")
214
+ * @param {string | undefined} statusKind - Request status from IC agent
215
+ * @param {number} nextDelay - Calculated delay until next poll (ms)
216
+ */
217
+ const log = (
218
+ phase: string,
219
+ statusKind: string | undefined,
220
+ nextDelay: number
221
+ ) => {
222
+ const now = Date.now()
223
+ // Suppress ultra-noisy logs: skip if < 1s since last log and not in fast phase
224
+ if (now - lastLog < 1_000 && phase !== "fast" && nextDelay < 1_000) return
225
+
226
+ // Force a heartbeat log if too much time has passed (prevents silent operations)
227
+ if (now - lastLog > maxLogIntervalMs) {
228
+ phase += "+heartbeat"
229
+ }
230
+
231
+ lastLog = now
232
+ // eslint-disable-next-line no-console
233
+ console.info(
234
+ `${logPrefix} ${context} attempt=${attempt} elapsed=${now - start}ms status=${statusKind} phase=${phase} nextDelay=${Math.round(nextDelay)}ms`
235
+ )
236
+ }
237
+
238
+ /**
239
+ * Computes the next polling delay based on elapsed time and attempt count.
240
+ * Implements three-phase strategy:
241
+ * - Fast: Initial rapid polling
242
+ * - Ramp: Exponential growth with power curve
243
+ * - Plateau: Steady-state polling
244
+ *
245
+ * @param {number} elapsed - Milliseconds elapsed since start
246
+ * @param {number} a - Current attempt number
247
+ * @returns {{ delay: number; phase: string }} - Calculated delay and phase name
248
+ */
249
+ const computeDelay = (
250
+ elapsed: number,
251
+ a: number
252
+ ): { delay: number; phase: string } => {
253
+ // Phase 1: Fast initial polling
254
+ if (a < fastAttempts) {
255
+ return { delay: withJitter(fastDelayMs), phase: "fast" }
256
+ }
257
+
258
+ // Phase 2: Ramp with exponential growth (power curve 0.7 for smooth acceleration)
259
+ if (elapsed < rampUntilMs) {
260
+ const progress = elapsed / rampUntilMs // Normalized progress: 0..1
261
+ const base =
262
+ fastDelayMs + (plateauDelayMs - fastDelayMs) * Math.pow(progress, 0.7)
263
+ return { delay: withJitter(base), phase: "ramp" }
264
+ }
265
+
266
+ // Phase 3: Plateau - steady-state polling
267
+ return { delay: withJitter(plateauDelayMs), phase: "plateau" }
268
+ }
269
+
270
+ /**
271
+ * Applies random jitter to prevent synchronized polling across multiple clients.
272
+ * Uses configured jitterRatio to add ±N% randomness to the base delay.
273
+ *
274
+ * @param {number} base - Base delay in milliseconds
275
+ * @returns {number} - Jittered delay, minimum 50ms
276
+ */
277
+ const withJitter = (base: number): number => {
278
+ const spread = base * jitterRatio
279
+ return Math.max(50, base - spread + Math.random() * spread * 2)
280
+ }
281
+
282
+ /**
283
+ * Async strategy function invoked by the IC agent on each polling cycle.
284
+ * Determines whether to continue waiting based on request status.
285
+ *
286
+ * @param {Principal} _canisterId - Target canister principal (unused but required by interface)
287
+ * @param {RequestId} _requestId - Request identifier (unused but required by interface)
288
+ * @param {RequestStatusResponseStatus} [rawStatus] - Current request status from IC
289
+ * @returns {Promise<void>} - Resolves after calculated delay, or immediately for terminal states
290
+ * @throws {Error} - If abortSignal is triggered
291
+ */
292
+ return async function strategy(
293
+ _canisterId: Principal,
294
+ _requestId: RequestId,
295
+ rawStatus?: RequestStatusResponseStatus
296
+ ): Promise<void> {
297
+ // Check for external cancellation
298
+ if (abortSignal?.aborted) {
299
+ throw new Error(`${context} polling aborted`)
300
+ }
301
+
302
+ attempt++
303
+ const statusKind = rawStatus
304
+
305
+ // Terminal states: Stop polling immediately
306
+ // - Replied: Request completed successfully
307
+ // - Rejected: Request was rejected by canister
308
+ // - Done: Request processing complete
309
+ // Note: Agent typically stops before we reach here, but we handle defensively
310
+ if (
311
+ statusKind === RequestStatusResponseStatus.Replied ||
312
+ statusKind === RequestStatusResponseStatus.Rejected ||
313
+ statusKind === RequestStatusResponseStatus.Done
314
+ ) {
315
+ return
316
+ }
317
+
318
+ // Continue polling for:
319
+ // - Received: Request received but not yet processed
320
+ // - Processing: Request is being processed
321
+ // - Unknown: Status not yet available
322
+ // - undefined: No status information yet
323
+ const elapsed = Date.now() - start
324
+ const { delay, phase } = computeDelay(elapsed, attempt)
325
+ log(phase, statusKind, delay)
326
+
327
+ // Sleep for calculated delay before next poll
328
+ await new Promise((r) => setTimeout(r, delay))
329
+ }
330
+ }
@@ -0,0 +1,56 @@
1
+ // ============================================================================
2
+ // Zod Integration Helper
3
+ // ============================================================================
4
+
5
+ import { ValidationIssue } from "../errors"
6
+ import { ValidationResult, Validator } from "../types"
7
+
8
+ /**
9
+ * Create a validator from a Zod schema.
10
+ * This is a utility function to easily integrate Zod schemas as validators.
11
+ *
12
+ * @param schema - A Zod schema to validate against
13
+ * @returns A Validator function compatible with DisplayReactor
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { z } from "zod"
18
+ * import { fromZodSchema } from "@ic-reactor/core"
19
+ *
20
+ * const transferSchema = z.object({
21
+ * to: z.string().min(1, "Recipient is required"),
22
+ * amount: z.string().regex(/^\d+$/, "Must be a valid number"),
23
+ * })
24
+ *
25
+ * reactor.registerValidator("transfer", fromZodSchema(transferSchema))
26
+ * ```
27
+ */
28
+ export function fromZodSchema<T>(schema: {
29
+ safeParse: (data: unknown) => {
30
+ success: boolean
31
+ error?: {
32
+ issues: Array<{
33
+ path: (string | number)[]
34
+ message: string
35
+ code?: string
36
+ }>
37
+ }
38
+ }
39
+ }): Validator<T[]> {
40
+ return (args: T[]): ValidationResult => {
41
+ // Validate the first argument (common IC pattern)
42
+ const result = schema.safeParse(args[0])
43
+
44
+ if (result.success) {
45
+ return { success: true }
46
+ }
47
+
48
+ const issues: ValidationIssue[] = result.error!.issues.map((issue) => ({
49
+ path: issue.path,
50
+ message: issue.message,
51
+ code: issue.code,
52
+ }))
53
+
54
+ return { success: false, issues }
55
+ }
56
+ }
package/src/version.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { version } from "../package.json"
2
+ /**
3
+ * Library version - automatically synced from package.json.
4
+ */
5
+ export const VERSION = version