@ic-reactor/candid 3.0.2-beta.0 → 3.0.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.
Files changed (83) hide show
  1. package/README.md +33 -1
  2. package/dist/adapter.js +2 -1
  3. package/dist/adapter.js.map +1 -1
  4. package/dist/display-reactor.d.ts +4 -13
  5. package/dist/display-reactor.d.ts.map +1 -1
  6. package/dist/display-reactor.js +22 -8
  7. package/dist/display-reactor.js.map +1 -1
  8. package/dist/index.d.ts +3 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +3 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/metadata-display-reactor.d.ts +108 -0
  13. package/dist/metadata-display-reactor.d.ts.map +1 -0
  14. package/dist/metadata-display-reactor.js +141 -0
  15. package/dist/metadata-display-reactor.js.map +1 -0
  16. package/dist/reactor.d.ts +1 -1
  17. package/dist/reactor.d.ts.map +1 -1
  18. package/dist/reactor.js +10 -6
  19. package/dist/reactor.js.map +1 -1
  20. package/dist/types.d.ts +38 -7
  21. package/dist/types.d.ts.map +1 -1
  22. package/dist/utils.d.ts +4 -4
  23. package/dist/utils.d.ts.map +1 -1
  24. package/dist/utils.js +33 -10
  25. package/dist/utils.js.map +1 -1
  26. package/dist/visitor/arguments/helpers.d.ts +55 -0
  27. package/dist/visitor/arguments/helpers.d.ts.map +1 -0
  28. package/dist/visitor/arguments/helpers.js +123 -0
  29. package/dist/visitor/arguments/helpers.js.map +1 -0
  30. package/dist/visitor/arguments/index.d.ts +101 -0
  31. package/dist/visitor/arguments/index.d.ts.map +1 -0
  32. package/dist/visitor/arguments/index.js +780 -0
  33. package/dist/visitor/arguments/index.js.map +1 -0
  34. package/dist/visitor/arguments/types.d.ts +270 -0
  35. package/dist/visitor/arguments/types.d.ts.map +1 -0
  36. package/dist/visitor/arguments/types.js +26 -0
  37. package/dist/visitor/arguments/types.js.map +1 -0
  38. package/dist/visitor/constants.d.ts +4 -0
  39. package/dist/visitor/constants.d.ts.map +1 -0
  40. package/dist/visitor/constants.js +73 -0
  41. package/dist/visitor/constants.js.map +1 -0
  42. package/dist/visitor/helpers.d.ts +30 -0
  43. package/dist/visitor/helpers.d.ts.map +1 -0
  44. package/dist/visitor/helpers.js +204 -0
  45. package/dist/visitor/helpers.js.map +1 -0
  46. package/dist/visitor/index.d.ts +5 -0
  47. package/dist/visitor/index.d.ts.map +1 -0
  48. package/dist/visitor/index.js +5 -0
  49. package/dist/visitor/index.js.map +1 -0
  50. package/dist/visitor/returns/index.d.ts +38 -0
  51. package/dist/visitor/returns/index.d.ts.map +1 -0
  52. package/dist/visitor/returns/index.js +460 -0
  53. package/dist/visitor/returns/index.js.map +1 -0
  54. package/dist/visitor/returns/types.d.ts +202 -0
  55. package/dist/visitor/returns/types.d.ts.map +1 -0
  56. package/dist/visitor/returns/types.js +2 -0
  57. package/dist/visitor/returns/types.js.map +1 -0
  58. package/dist/visitor/types.d.ts +19 -0
  59. package/dist/visitor/types.d.ts.map +1 -0
  60. package/dist/visitor/types.js +2 -0
  61. package/dist/visitor/types.js.map +1 -0
  62. package/package.json +16 -7
  63. package/src/adapter.ts +446 -0
  64. package/src/constants.ts +11 -0
  65. package/src/display-reactor.ts +337 -0
  66. package/src/index.ts +8 -0
  67. package/src/metadata-display-reactor.ts +230 -0
  68. package/src/reactor.ts +199 -0
  69. package/src/types.ts +127 -0
  70. package/src/utils.ts +60 -0
  71. package/src/visitor/arguments/helpers.ts +153 -0
  72. package/src/visitor/arguments/index.test.ts +1439 -0
  73. package/src/visitor/arguments/index.ts +981 -0
  74. package/src/visitor/arguments/schema.test.ts +324 -0
  75. package/src/visitor/arguments/types.ts +387 -0
  76. package/src/visitor/constants.ts +76 -0
  77. package/src/visitor/helpers.test.ts +274 -0
  78. package/src/visitor/helpers.ts +223 -0
  79. package/src/visitor/index.ts +4 -0
  80. package/src/visitor/returns/index.test.ts +2377 -0
  81. package/src/visitor/returns/index.ts +658 -0
  82. package/src/visitor/returns/types.ts +302 -0
  83. package/src/visitor/types.ts +75 -0
@@ -0,0 +1,274 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import {
3
+ isPrincipalId,
4
+ isCanisterId,
5
+ isBtcAddress,
6
+ isEthAddress,
7
+ isAccountIdentifier,
8
+ isIsoDate,
9
+ isUrl,
10
+ isImage,
11
+ isUuid,
12
+ } from "./helpers"
13
+
14
+ describe("Helpers", () => {
15
+ describe("isPrincipalId", () => {
16
+ it("should return true for valid principal IDs", () => {
17
+ expect(isPrincipalId("aaaaa-aa")).toBe(true)
18
+ expect(isPrincipalId("2vxsx-fae")).toBe(true)
19
+ expect(isPrincipalId("rrkah-fqaaa-aaaaa-aaaaq-cai")).toBe(true)
20
+ })
21
+
22
+ it("should return false for invalid principal IDs", () => {
23
+ expect(isPrincipalId("")).toBe(false) // empty string
24
+ expect(isPrincipalId("invalid-principal")).toBe(false) // contains invalid characters
25
+ expect(isPrincipalId("123456")).toBe(false) // numbers only
26
+ expect(
27
+ isPrincipalId("a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z")
28
+ ).toBe(false) // too long
29
+ expect(isPrincipalId("not-a-valid-principal")).toBe(false) // malformed
30
+ })
31
+
32
+ it("should return false for non-string inputs", () => {
33
+ expect(isPrincipalId(123 as any)).toBe(false)
34
+ expect(isPrincipalId(null as any)).toBe(false)
35
+ expect(isPrincipalId(undefined as any)).toBe(false)
36
+ expect(isPrincipalId({} as any)).toBe(false)
37
+ })
38
+ })
39
+
40
+ describe("isCanisterId", () => {
41
+ it("should return true for valid canister IDs", () => {
42
+ expect(isCanisterId("rrkah-fqaaa-aaaaa-aaaaq-cai")).toBe(true)
43
+ expect(isCanisterId("ryjl3-tyaaa-aaaaa-aaaba-cai")).toBe(true)
44
+ })
45
+
46
+ it("should return false for principal IDs that are not canister IDs", () => {
47
+ expect(isCanisterId("aaaaa-aa")).toBe(false) // too short
48
+ expect(isCanisterId("2vxsx-fae")).toBe(false) // too short
49
+ })
50
+
51
+ it("should return false for invalid strings", () => {
52
+ expect(isCanisterId("invalid-canister-id")).toBe(false)
53
+ expect(isCanisterId("rrkah-fqaaa-aaaaa-aaaaq")).toBe(false) // missing -cai
54
+ expect(isCanisterId("rrkah-fqaaa-aaaaa-aaaaq-ca")).toBe(false) // wrong ending
55
+ expect(isCanisterId("")).toBe(false)
56
+ })
57
+
58
+ it("should return false for non-string inputs", () => {
59
+ expect(isCanisterId(123 as any)).toBe(false)
60
+ expect(isCanisterId(null as any)).toBe(false)
61
+ })
62
+ })
63
+
64
+ describe("isBtcAddress", () => {
65
+ it("should return true for valid Bitcoin addresses", () => {
66
+ // Bech32 mainnet
67
+ expect(isBtcAddress("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")).toBe(
68
+ true
69
+ )
70
+ // Bech32 testnet
71
+ expect(isBtcAddress("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx")).toBe(
72
+ true
73
+ )
74
+ // Base58 mainnet
75
+ expect(isBtcAddress("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2")).toBe(true)
76
+ // Base58 testnet
77
+ expect(isBtcAddress("mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn")).toBe(true)
78
+ })
79
+
80
+ it("should return false for invalid Bitcoin addresses", () => {
81
+ expect(isBtcAddress("")).toBe(false)
82
+ expect(isBtcAddress("invalid")).toBe(false)
83
+ expect(isBtcAddress("bc1short")).toBe(false) // too short
84
+ expect(
85
+ isBtcAddress(
86
+ "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
87
+ )
88
+ ).toBe(false) // too long
89
+ })
90
+
91
+ it("should return false for non-string inputs", () => {
92
+ expect(isBtcAddress(123 as any)).toBe(false)
93
+ expect(isBtcAddress(null as any)).toBe(false)
94
+ })
95
+ })
96
+
97
+ describe("isEthAddress", () => {
98
+ it("should return true for valid Ethereum addresses", () => {
99
+ expect(isEthAddress("0x742d35Cc6634C0532925a3b844Bc454e4438f44e")).toBe(
100
+ true
101
+ )
102
+ expect(isEthAddress("0x742d35cc6634c0532925a3b844bc454e4438f44e")).toBe(
103
+ true
104
+ ) // lowercase
105
+ expect(isEthAddress("0x742D35CC6634C0532925A3B844BC454E4438F44E")).toBe(
106
+ true
107
+ ) // uppercase
108
+ })
109
+
110
+ it("should return false for invalid Ethereum addresses", () => {
111
+ expect(isEthAddress("")).toBe(false)
112
+ expect(isEthAddress("0x742d35Cc6634C0532925a3b844Bc454e4438f44")).toBe(
113
+ false
114
+ ) // too short
115
+ expect(isEthAddress("0x742d35Cc6634C0532925a3b844Bc454e4438f44ee")).toBe(
116
+ false
117
+ ) // too long
118
+ expect(isEthAddress("742d35Cc6634C0532925a3b844Bc454e4438f44e")).toBe(
119
+ false
120
+ ) // missing 0x
121
+ expect(isEthAddress("0x742d35Cc6634C0532925a3b844Bc454e4438f44g")).toBe(
122
+ false
123
+ ) // invalid char
124
+ })
125
+
126
+ it("should return false for non-string inputs", () => {
127
+ expect(isEthAddress(123 as any)).toBe(false)
128
+ expect(isEthAddress(null as any)).toBe(false)
129
+ })
130
+ })
131
+
132
+ describe("isAccountIdentifier", () => {
133
+ it("should return true for valid account identifiers", () => {
134
+ expect(
135
+ isAccountIdentifier(
136
+ "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
137
+ )
138
+ ).toBe(true)
139
+ expect(
140
+ isAccountIdentifier(
141
+ "0000000000000000000000000000000000000000000000000000000000000000"
142
+ )
143
+ ).toBe(true)
144
+ expect(
145
+ isAccountIdentifier(
146
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
147
+ )
148
+ ).toBe(true)
149
+ })
150
+
151
+ it("should return false for invalid account identifiers", () => {
152
+ expect(isAccountIdentifier("")).toBe(false)
153
+ expect(
154
+ isAccountIdentifier(
155
+ "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
156
+ )
157
+ ).toBe(false) // 63 chars
158
+ expect(
159
+ isAccountIdentifier(
160
+ "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a"
161
+ )
162
+ ).toBe(false) // 65 chars
163
+ expect(
164
+ isAccountIdentifier(
165
+ "gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg"
166
+ )
167
+ ).toBe(false) // invalid chars
168
+ })
169
+
170
+ it("should return false for non-string inputs", () => {
171
+ expect(isAccountIdentifier(123 as any)).toBe(false)
172
+ expect(isAccountIdentifier(null as any)).toBe(false)
173
+ })
174
+ })
175
+
176
+ describe("isIsoDate", () => {
177
+ it("should return true for valid ISO dates", () => {
178
+ expect(isIsoDate("2023-12-25T10:30:00Z")).toBe(true)
179
+ expect(isIsoDate("2023-12-25T10:30:00.123Z")).toBe(true)
180
+ expect(isIsoDate("2023-12-25T10:30:00+05:30")).toBe(true)
181
+ expect(isIsoDate("2023-12-25T10:30:00-08:00")).toBe(true)
182
+ })
183
+
184
+ it("should return false for invalid ISO dates", () => {
185
+ expect(isIsoDate("")).toBe(false)
186
+ expect(isIsoDate("2023-12-25")).toBe(false) // missing time
187
+ expect(isIsoDate("2023-12-25T10:30:00")).toBe(false) // missing timezone
188
+ expect(isIsoDate("invalid-date")).toBe(false)
189
+ expect(isIsoDate("2023-12-25T10:30:00Zextra")).toBe(false) // extra characters
190
+ })
191
+
192
+ it("should return false for non-string inputs", () => {
193
+ expect(isIsoDate(123 as any)).toBe(false)
194
+ expect(isIsoDate(null as any)).toBe(false)
195
+ })
196
+ })
197
+
198
+ describe("isUrl", () => {
199
+ it("should return true for valid URLs", () => {
200
+ expect(isUrl("http://example.com")).toBe(true)
201
+ expect(isUrl("https://example.com")).toBe(true)
202
+ expect(isUrl("https://example.com/path")).toBe(true)
203
+ expect(isUrl("https://example.com/path?query=value")).toBe(true)
204
+ })
205
+
206
+ it("should return false for invalid URLs", () => {
207
+ expect(isUrl("")).toBe(false)
208
+ expect(isUrl("example.com")).toBe(false) // missing protocol
209
+ expect(isUrl("ftp://example.com")).toBe(false) // unsupported protocol
210
+ expect(isUrl("not-a-url")).toBe(false)
211
+ })
212
+
213
+ it("should return false for non-string inputs", () => {
214
+ expect(isUrl(123 as any)).toBe(false)
215
+ expect(isUrl(null as any)).toBe(false)
216
+ })
217
+ })
218
+
219
+ describe("isImage", () => {
220
+ it("should return true for valid image URLs", () => {
221
+ expect(isImage("https://example.com/image.jpg")).toBe(true)
222
+ expect(isImage("https://example.com/image.jpeg")).toBe(true)
223
+ expect(isImage("https://example.com/image.png")).toBe(true)
224
+ expect(isImage("https://example.com/image.gif")).toBe(true)
225
+ expect(isImage("https://example.com/image.svg")).toBe(true)
226
+ })
227
+
228
+ it("should return true for base64 image data", () => {
229
+ expect(
230
+ isImage(
231
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
232
+ )
233
+ ).toBe(true)
234
+ expect(
235
+ isImage(
236
+ "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/vAA="
237
+ )
238
+ ).toBe(true)
239
+ })
240
+
241
+ it("should return false for non-image URLs", () => {
242
+ expect(isImage("https://example.com/document.pdf")).toBe(false)
243
+ expect(isImage("https://example.com/video.mp4")).toBe(false)
244
+ expect(isImage("not-an-image")).toBe(false)
245
+ expect(isImage("")).toBe(false)
246
+ })
247
+
248
+ it("should return false for non-string inputs", () => {
249
+ expect(isImage(123 as any)).toBe(false)
250
+ expect(isImage(null as any)).toBe(false)
251
+ })
252
+ })
253
+
254
+ describe("isUuid", () => {
255
+ it("should return true for valid UUIDs", () => {
256
+ expect(isUuid("123e4567-e89b-12d3-a456-426614174000")).toBe(true)
257
+ expect(isUuid("123E4567-E89B-12D3-A456-426614174000")).toBe(true) // uppercase
258
+ expect(isUuid("123e4567-e89b-12d3-a456-426614174000")).toBe(true) // lowercase
259
+ })
260
+
261
+ it("should return false for invalid UUIDs", () => {
262
+ expect(isUuid("")).toBe(false)
263
+ expect(isUuid("123e4567-e89b-12d3-a456-42661417400")).toBe(false) // too short
264
+ expect(isUuid("123e4567-e89b-12d3-a456-4266141740000")).toBe(false) // too long
265
+ expect(isUuid("123e4567-e89b-12d3-a456-42661417400g")).toBe(false) // invalid char
266
+ expect(isUuid("not-a-uuid")).toBe(false)
267
+ })
268
+
269
+ it("should return false for non-string inputs", () => {
270
+ expect(isUuid(123 as any)).toBe(false)
271
+ expect(isUuid(null as any)).toBe(false)
272
+ })
273
+ })
274
+ })
@@ -0,0 +1,223 @@
1
+ import { IDL } from "@icp-sdk/core/candid"
2
+ import { Principal } from "@icp-sdk/core/principal"
3
+
4
+ export const extractAndSortArgs = <T extends Record<string, unknown>>(
5
+ argsObject: T
6
+ ): Array<T[keyof T]> => {
7
+ if (!argsObject || typeof argsObject !== "object") return []
8
+
9
+ const args: Array<T[keyof T]> = []
10
+ let index = 0
11
+
12
+ while (Object.prototype.hasOwnProperty.call(argsObject, `arg${index}`)) {
13
+ args.push(argsObject[`arg${index}`] as T[keyof T])
14
+ index++
15
+ }
16
+
17
+ return args
18
+ }
19
+
20
+ /**
21
+ * Normalize form state by converting objects with numeric string keys to arrays.
22
+ * TanStack Form sometimes creates { "0": value, "1": value } instead of [value, value]
23
+ * when using indexed field paths like "parent.0.child".
24
+ *
25
+ * This function also handles the variant cleanup case where objects have mixed
26
+ * numeric and named keys (e.g., { "0": old, "Add": current }) by removing
27
+ * orphaned numeric keys.
28
+ *
29
+ * This function recursively processes the form state and fixes these issues.
30
+ */
31
+ export const normalizeFormState = <T>(value: T): T => {
32
+ // Handle null/undefined
33
+ if (value === null || value === undefined) {
34
+ return value
35
+ }
36
+
37
+ // Handle arrays - recursively normalize each element
38
+ if (Array.isArray(value)) {
39
+ return value.map(normalizeFormState) as T
40
+ }
41
+
42
+ // Handle objects
43
+ if (typeof value === "object") {
44
+ const obj = value as Record<string, unknown>
45
+ const keys = Object.keys(obj)
46
+
47
+ // Separate numeric and non-numeric keys
48
+ const numericKeys = keys.filter((k) => /^\d+$/.test(k))
49
+ const namedKeys = keys.filter((k) => !/^\d+$/.test(k))
50
+
51
+ // Case 1: All keys are numeric - convert to array
52
+ if (numericKeys.length > 0 && namedKeys.length === 0) {
53
+ const sortedNums = numericKeys.map(Number).sort((a, b) => a - b)
54
+ // Check if keys are consecutive starting from 0
55
+ if (sortedNums.every((num, idx) => num === idx)) {
56
+ return sortedNums.map((k) => normalizeFormState(obj[String(k)])) as T
57
+ }
58
+ }
59
+
60
+ // Case 2: Mixed keys (variant case) - keep only named keys, remove orphan numeric keys
61
+ if (numericKeys.length > 0 && namedKeys.length > 0) {
62
+ const result: Record<string, unknown> = {}
63
+ for (const key of namedKeys) {
64
+ result[key] = normalizeFormState(obj[key])
65
+ }
66
+ return result as T
67
+ }
68
+
69
+ // Case 3: All named keys - recursively normalize all values
70
+ const result: Record<string, unknown> = {}
71
+ for (const [key, val] of Object.entries(obj)) {
72
+ result[key] = normalizeFormState(val)
73
+ }
74
+ return result as T
75
+ }
76
+
77
+ // Return primitives as-is
78
+ return value
79
+ }
80
+
81
+ export const convertNanoToDate = (nano: bigint) => {
82
+ return new Date(Number(nano) / 1000000)
83
+ }
84
+
85
+ export const convertToCycle = (cycles: bigint) => {
86
+ const mcycles = cycles / BigInt(1_000_000)
87
+ if (mcycles >= BigInt(1_000_000)) {
88
+ const tcycles = mcycles / BigInt(1_000_000)
89
+ return `${tcycles.toLocaleString()} T`
90
+ }
91
+ return `${mcycles.toLocaleString()} M`
92
+ }
93
+
94
+ export const convertStringToNumber = (value: string) => {
95
+ const bits = value.length
96
+ if (bits >= 16) {
97
+ return BigInt(value)
98
+ } else {
99
+ return Number(value)
100
+ }
101
+ }
102
+
103
+ export const validateNumberError = (t: IDL.Type) => {
104
+ return function validate(value: string) {
105
+ if (value === "") {
106
+ return true
107
+ }
108
+
109
+ const bits = value.length
110
+ if (bits >= 16) {
111
+ try {
112
+ const valueAsBigInt = BigInt(value)
113
+ t.covariant(valueAsBigInt)
114
+ return true
115
+ } catch (error) {
116
+ return (error as Error).message || "Failed to convert to BigInt"
117
+ }
118
+ } else {
119
+ try {
120
+ const valueAsNumber = Number(value)
121
+ t.covariant(valueAsNumber)
122
+ return true
123
+ } catch (error) {
124
+ return (error as Error).message || "Failed to convert to number"
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ export const validateError = (t: IDL.Type) => {
131
+ return function validate(value: unknown) {
132
+ try {
133
+ t.covariant(value)
134
+ return true
135
+ } catch (error) {
136
+ return (error as Error).message || "An error occurred"
137
+ }
138
+ }
139
+ }
140
+
141
+ export function isQuery(func: IDL.FuncClass): boolean {
142
+ return (
143
+ func.annotations.includes("query") ||
144
+ func.annotations.includes("composite_query")
145
+ )
146
+ }
147
+
148
+ export function isUrl(str: string): boolean {
149
+ if (typeof str !== "string") return false
150
+ return str.startsWith("http") || str.startsWith("https")
151
+ }
152
+
153
+ export function isImage(str: string): boolean {
154
+ if (typeof str !== "string") return false
155
+ // Check if the string starts with 'data:image' (indicating base64-encoded image)
156
+ if (str.startsWith("data:image")) {
157
+ return true
158
+ }
159
+
160
+ // List of common image file extensions
161
+ const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".svg"]
162
+
163
+ // Check if the string ends with any of the image extensions (indicating image URL)
164
+ if (imageExtensions.some((ext) => str.endsWith(ext))) {
165
+ return true
166
+ }
167
+
168
+ return false
169
+ }
170
+
171
+ export function isUuid(str: string): boolean {
172
+ if (typeof str !== "string") return false
173
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
174
+ str
175
+ )
176
+ }
177
+
178
+ export function isPrincipalId(str: string): boolean {
179
+ if (typeof str !== "string") return false
180
+ try {
181
+ Principal.fromText(str)
182
+ return true
183
+ } catch {
184
+ return false
185
+ }
186
+ }
187
+
188
+ export function isCanisterId(str: string): boolean {
189
+ if (typeof str !== "string") return false
190
+ if (!isPrincipalId(str)) return false
191
+ if (str.length !== 27) return false
192
+ // All canister IDs end with "-cai"
193
+ return str.endsWith("-cai")
194
+ }
195
+
196
+ export function isBtcAddress(str: string): boolean {
197
+ if (typeof str !== "string") return false
198
+ // Bech32 (Mainnet: bc1, Testnet: tb1, Regtest: bcrt1)
199
+ if (/^(bc1|tb1|bcrt1)[a-zA-HJ-NP-Z0-9]{25,60}$/.test(str)) return true
200
+ // Base58 (Mainnet: 1/3, Testnet: m/n/2)
201
+ if (/^[13mn2][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(str)) return true
202
+ return false
203
+ }
204
+
205
+ export function isEthAddress(str: string): boolean {
206
+ if (typeof str !== "string") return false
207
+ return /^0x[a-fA-F0-9]{40}$/.test(str)
208
+ }
209
+
210
+ export function isAccountIdentifier(str: string): boolean {
211
+ if (typeof str !== "string") return false
212
+ // 64 characters hex string (standard for ICP account ids and hashes)
213
+ return /^[a-fA-F0-9]{64}$/.test(str)
214
+ }
215
+
216
+ export function isIsoDate(str: string): boolean {
217
+ if (typeof str !== "string") return false
218
+ // Basic ISO 8601 / RFC 3339 format check
219
+ // YYYY-MM-DDTHH:mm:ss.sssZ (optional milliseconds/nanoseconds and timezone)
220
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/.test(
221
+ str
222
+ )
223
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./returns"
2
+ export * from "./arguments"
3
+ export * from "./types"
4
+ export * from "./helpers"