hbsig 0.3.1 → 0.3.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 (94) hide show
  1. package/.babelrc-cjs +5 -0
  2. package/.babelrc-esm +5 -0
  3. package/README.md +1 -0
  4. package/dist/package.json +39 -0
  5. package/make.js +36 -0
  6. package/package.json +16 -16
  7. package/src/bin_to_str.js +46 -0
  8. package/src/collect-body-keys.js +436 -0
  9. package/src/commit.js +219 -0
  10. package/src/encode-array-item.js +112 -0
  11. package/src/encode-utils.js +191 -0
  12. package/src/encode.js +1256 -0
  13. package/src/erl_json.js +292 -0
  14. package/src/erl_str.js +1144 -0
  15. package/src/flat.js +250 -0
  16. package/src/http-message-signatures/httpbis.js +438 -0
  17. package/src/http-message-signatures/index.js +4 -0
  18. package/src/http-message-signatures/structured-header.js +105 -0
  19. package/src/httpsig.js +866 -0
  20. package/src/id.js +459 -0
  21. package/src/index.js +13 -0
  22. package/src/nocrypto.js +4 -0
  23. package/src/parser.js +171 -0
  24. package/src/send-utils.js +1132 -0
  25. package/src/send.js +142 -0
  26. package/src/signer-utils.js +375 -0
  27. package/src/signer.js +312 -0
  28. package/src/structured.js +496 -0
  29. package/src/test.js +2 -0
  30. package/src/utils.js +29 -0
  31. package/test/commit.test.js +41 -0
  32. package/test/erl_json.test.js +8 -0
  33. package/test/flat.test.js +27 -0
  34. package/test/httpsig.test.js +31 -0
  35. package/test/id.test.js +114 -0
  36. package/test/lib/all_cases.js +408 -0
  37. package/test/lib/cases.js +408 -0
  38. package/test/lib/erl_json_cases.js +161 -0
  39. package/test/lib/flat_cases.js +189 -0
  40. package/test/lib/gen.js +528 -0
  41. package/test/lib/httpsig_cases.js +313 -0
  42. package/test/lib/structured_cases.js +222 -0
  43. package/test/lib/test-utils.js +399 -0
  44. package/test/signer.test.js +48 -0
  45. package/test/structured.test.js +35 -0
  46. /package/{cjs → dist/cjs}/bin_to_str.js +0 -0
  47. /package/{cjs → dist/cjs}/collect-body-keys.js +0 -0
  48. /package/{cjs → dist/cjs}/commit.js +0 -0
  49. /package/{cjs → dist/cjs}/encode-array-item.js +0 -0
  50. /package/{cjs → dist/cjs}/encode-utils.js +0 -0
  51. /package/{cjs → dist/cjs}/encode.js +0 -0
  52. /package/{cjs → dist/cjs}/erl_json.js +0 -0
  53. /package/{cjs → dist/cjs}/erl_str.js +0 -0
  54. /package/{cjs → dist/cjs}/flat.js +0 -0
  55. /package/{cjs → dist/cjs}/http-message-signatures/httpbis.js +0 -0
  56. /package/{cjs → dist/cjs}/http-message-signatures/index.js +0 -0
  57. /package/{cjs → dist/cjs}/http-message-signatures/structured-header.js +0 -0
  58. /package/{cjs → dist/cjs}/httpsig.js +0 -0
  59. /package/{cjs → dist/cjs}/id.js +0 -0
  60. /package/{cjs → dist/cjs}/index.js +0 -0
  61. /package/{cjs → dist/cjs}/nocrypto.js +0 -0
  62. /package/{cjs → dist/cjs}/parser.js +0 -0
  63. /package/{cjs → dist/cjs}/send-utils.js +0 -0
  64. /package/{cjs → dist/cjs}/send.js +0 -0
  65. /package/{cjs → dist/cjs}/signer-utils.js +0 -0
  66. /package/{cjs → dist/cjs}/signer.js +0 -0
  67. /package/{cjs → dist/cjs}/structured.js +0 -0
  68. /package/{cjs → dist/cjs}/test.js +0 -0
  69. /package/{cjs → dist/cjs}/utils.js +0 -0
  70. /package/{esm → dist/esm}/bin_to_str.js +0 -0
  71. /package/{esm → dist/esm}/collect-body-keys.js +0 -0
  72. /package/{esm → dist/esm}/commit.js +0 -0
  73. /package/{esm → dist/esm}/encode-array-item.js +0 -0
  74. /package/{esm → dist/esm}/encode-utils.js +0 -0
  75. /package/{esm → dist/esm}/encode.js +0 -0
  76. /package/{esm → dist/esm}/erl_json.js +0 -0
  77. /package/{esm → dist/esm}/erl_str.js +0 -0
  78. /package/{esm → dist/esm}/flat.js +0 -0
  79. /package/{esm → dist/esm}/http-message-signatures/httpbis.js +0 -0
  80. /package/{esm → dist/esm}/http-message-signatures/index.js +0 -0
  81. /package/{esm → dist/esm}/http-message-signatures/structured-header.js +0 -0
  82. /package/{esm → dist/esm}/httpsig.js +0 -0
  83. /package/{esm → dist/esm}/id.js +0 -0
  84. /package/{esm → dist/esm}/index.js +0 -0
  85. /package/{esm → dist/esm}/nocrypto.js +0 -0
  86. /package/{esm → dist/esm}/package.json +0 -0
  87. /package/{esm → dist/esm}/parser.js +0 -0
  88. /package/{esm → dist/esm}/send-utils.js +0 -0
  89. /package/{esm → dist/esm}/send.js +0 -0
  90. /package/{esm → dist/esm}/signer-utils.js +0 -0
  91. /package/{esm → dist/esm}/signer.js +0 -0
  92. /package/{esm → dist/esm}/structured.js +0 -0
  93. /package/{esm → dist/esm}/test.js +0 -0
  94. /package/{esm → dist/esm}/utils.js +0 -0
@@ -0,0 +1,399 @@
1
+ import { send } from "../../src/send.js"
2
+ import { erl_json_to, normalize } from "../../src/erl_json.js"
3
+ import { erl_str_from } from "../../src/erl_str.js"
4
+ import assert from "assert"
5
+ import { describe, it, before, after } from "node:test"
6
+ import { HyperBEAM } from "../../../src/test.js"
7
+ import { createSigner } from "../../src/signer.js"
8
+
9
+ function mod(obj) {
10
+ // Handle undefined - convert to string "undefined"
11
+ if (obj === undefined) return "undefined"
12
+
13
+ // Handle symbols - convert to their description or "symbol"
14
+ if (typeof obj === "symbol") {
15
+ const desc = obj.description || "symbol"
16
+ // Special handling for symbols with descriptions that match special values
17
+ if (desc === "null") return null
18
+ if (desc === "undefined") return undefined
19
+ if (desc === "true") return true
20
+ if (desc === "false") return false
21
+ return desc
22
+ }
23
+
24
+ // Handle arrays
25
+ if (Array.isArray(obj)) {
26
+ return obj.map(item => mod(item))
27
+ }
28
+
29
+ // Handle binary data (Buffer, Uint8Array, etc.)
30
+ if (
31
+ obj instanceof Uint8Array ||
32
+ obj instanceof ArrayBuffer ||
33
+ Buffer.isBuffer(obj)
34
+ ) {
35
+ // Convert to empty string for empty buffers
36
+ const buffer = Buffer.isBuffer(obj) ? obj : Buffer.from(obj)
37
+ return buffer.length === 0 ? "" : buffer.toString("base64")
38
+ }
39
+
40
+ // Handle objects
41
+ if (typeof obj === "object" && obj !== null) {
42
+ const result = {}
43
+ for (const [key, value] of Object.entries(obj)) {
44
+ // Lowercase the key when creating the result object
45
+ result[key.toLowerCase()] = mod(value)
46
+ }
47
+ return result
48
+ }
49
+
50
+ // Handle strings - check if it's already an atom-like string
51
+ if (typeof obj === "string" && obj.match(/^[a-z_][a-zA-Z0-9_]*$/)) {
52
+ // This looks like an atom value, keep as is
53
+ return obj
54
+ }
55
+
56
+ // Return primitive values as-is (strings, numbers, booleans, null)
57
+ return obj
58
+ }
59
+
60
+ // Recursively transform values to match expected format, removing undefined values
61
+ function mod2(obj) {
62
+ // Handle undefined - return undefined to signal removal
63
+ if (obj === undefined) return undefined
64
+
65
+ // Handle symbols - convert to their description or "symbol"
66
+ if (typeof obj === "symbol") {
67
+ const desc = obj.description || "symbol"
68
+ // Special handling for symbols with descriptions that match special values
69
+ if (desc === "null") return null
70
+ if (desc === "undefined") return undefined // This will be removed
71
+ if (desc === "true") return true
72
+ if (desc === "false") return false
73
+ return desc
74
+ }
75
+
76
+ // Handle arrays - filter out undefined values
77
+ if (Array.isArray(obj)) {
78
+ return obj.map(item => mod2(item)).filter(item => item !== undefined)
79
+ }
80
+
81
+ // Handle binary data (Buffer, Uint8Array, etc.)
82
+ if (
83
+ obj instanceof Uint8Array ||
84
+ obj instanceof ArrayBuffer ||
85
+ Buffer.isBuffer(obj)
86
+ ) {
87
+ // Convert to empty string for empty buffers
88
+ const buffer = Buffer.isBuffer(obj) ? obj : Buffer.from(obj)
89
+ return buffer.length === 0 ? "" : buffer.toString("base64")
90
+ }
91
+
92
+ // Handle objects - remove undefined properties
93
+ if (typeof obj === "object" && obj !== null) {
94
+ const result = {}
95
+ for (const [key, value] of Object.entries(obj)) {
96
+ const modifiedValue = mod2(value)
97
+ // Only add the property if the value is not undefined
98
+ if (modifiedValue !== undefined) {
99
+ // Lowercase the key when creating the result object
100
+ result[key.toLowerCase()] = modifiedValue
101
+ }
102
+ }
103
+ return result
104
+ }
105
+
106
+ // Handle strings - check if it's already an atom-like string
107
+ if (typeof obj === "string" && obj.match(/^[a-z_][a-zA-Z0-9_]*$/)) {
108
+ // This looks like an atom value, keep as is
109
+ return obj
110
+ }
111
+
112
+ // Return primitive values as-is (strings, numbers, booleans, null)
113
+ return obj
114
+ }
115
+
116
+ // Helper to recursively remove ao-types fields without applying type conversions
117
+ const removeAoTypesField = obj => {
118
+ if (obj === null || obj === undefined) return obj
119
+ if (typeof obj !== "object") return obj
120
+ if (Buffer.isBuffer(obj)) return obj
121
+ if (Array.isArray(obj)) return obj.map(removeAoTypesField)
122
+
123
+ const result = {}
124
+ for (const [key, value] of Object.entries(obj)) {
125
+ if (key !== "ao-types") {
126
+ result[key] = removeAoTypesField(value)
127
+ }
128
+ }
129
+ return result
130
+ }
131
+
132
+ const test = async (sign, cases, path, mod = v => v, pmod = v => v, skipAoTypes = false, removeAoTypes = false) => {
133
+ let err = []
134
+ let success = []
135
+ let i = 0
136
+ for (const v of cases) {
137
+ console.log(`[${++i}]...........................................`, v)
138
+ try {
139
+ const _pmod = pmod(v)
140
+ const json = erl_json_to(_pmod)
141
+ const signed = await sign({ path, body: JSON.stringify(json) })
142
+ const { out } = await send(signed)
143
+ const input = normalize(_pmod)
144
+ const output = erl_str_from(out)
145
+ let expected = normalize(mod(_pmod), true)
146
+ // Apply ao-types conversions to output (unless skipAoTypes is true for flat/structured codec tests)
147
+ let output_b = skipAoTypes ? erl_str_from(out, true) : applyAoTypes(erl_str_from(out, true))
148
+ // For tests that need ao-types removed but not converted (e.g., flat codec)
149
+ // Apply the same transformation to both expected and actual for fair comparison
150
+ if (removeAoTypes) {
151
+ output_b = removeAoTypesField(output_b)
152
+ expected = removeAoTypesField(expected)
153
+ }
154
+ // DEBUG: Print comparison on failure
155
+ try {
156
+ assert.deepEqual(expected, output_b)
157
+ } catch (assertErr) {
158
+ const stringify = (o) => JSON.stringify(o, (k,v) => {
159
+ if (Buffer.isBuffer(v)) return v.toString('utf-8')
160
+ if (v?.type === 'Buffer' && Array.isArray(v?.data)) return Buffer.from(v.data).toString('utf-8')
161
+ if (typeof v === 'symbol') return `Symbol(${v.description})`
162
+ return v
163
+ }, 2)
164
+ console.log("DEBUG expected:", stringify(expected))
165
+ console.log("DEBUG actual:", stringify(output_b))
166
+ throw assertErr
167
+ }
168
+ success.push(v)
169
+ } catch (e) {
170
+ console.log(e)
171
+ err.push(v)
172
+ }
173
+ }
174
+ console.log(`${err.length} / ${cases.length} failed!`)
175
+ if (err.length > 0) {
176
+ for (let v of err) console.log(v)
177
+ throw new Error(`${err.length} test case(s) failed`)
178
+ }
179
+ }
180
+
181
+ const genTest = ({ desc = "HyperBEAM", its = [] }) => {
182
+ describe(desc, function () {
183
+ let hbeam, sign
184
+ before(async () => {
185
+ hbeam = await new HyperBEAM({ reset: true, linkify_mode: false }).ready()
186
+ sign = createSigner(hbeam.jwk, hbeam.url)
187
+ })
188
+ after(async () => hbeam.kill())
189
+ for (const v of its) {
190
+ const testFn = v.skip ? it.skip : it
191
+ testFn(
192
+ v.it ?? "should run",
193
+ async () =>
194
+ await test(
195
+ sign,
196
+ v.cases,
197
+ v.path ?? "/~hbsig@1.0/json_to_erl",
198
+ v.mod,
199
+ v.pmod,
200
+ v.skipAoTypes ?? false,
201
+ v.removeAoTypes ?? false
202
+ )
203
+ )
204
+ }
205
+ })
206
+ }
207
+
208
+ // Recursive helper to apply ao-types conversions
209
+ const applyAoTypes = obj => {
210
+ if (obj === null || obj === undefined) return obj
211
+ if (typeof obj !== "object") return obj
212
+ if (Buffer.isBuffer(obj)) return obj
213
+ if (Array.isArray(obj)) return obj.map(applyAoTypes)
214
+
215
+ // First, recursively process nested objects (to handle their ao-types)
216
+ for (const key of Object.keys(obj)) {
217
+ if (key !== "ao-types") {
218
+ obj[key] = applyAoTypes(obj[key])
219
+ }
220
+ }
221
+
222
+
223
+ // Process ao-types in this object
224
+ let aoTypesRaw = obj["ao-types"]
225
+ // Convert Buffer to string if needed
226
+ const aoTypes =
227
+ Buffer.isBuffer(aoTypesRaw) ? aoTypesRaw.toString() : aoTypesRaw
228
+
229
+ // Check if ao-types looks like a structured field dictionary (contains key="value" patterns)
230
+ // If it's just a user value like "test", we should preserve it
231
+ const isAoTypesDictionary =
232
+ aoTypes &&
233
+ typeof aoTypes === "string" &&
234
+ /[^=,\s]+="[^"]+"/g.test(aoTypes)
235
+
236
+ if (isAoTypesDictionary) {
237
+ const typeMatches = aoTypes.matchAll(/([^=,\s]+)="([^"]+)"/g)
238
+ for (const match of typeMatches) {
239
+ let key = match[1]
240
+ // Handle dot (.) which means the object itself is a list
241
+ if (key === ".") continue
242
+
243
+ // URL-decode the key (e.g., data%46ield -> dataField)
244
+ const decodedKey = decodeURIComponent(key)
245
+
246
+ // Find the actual key in the object (case-insensitive match)
247
+ const lowerKey = decodedKey.toLowerCase()
248
+ const actualKey =
249
+ Object.keys(obj).find(k => k.toLowerCase() === lowerKey) || lowerKey
250
+ const type = match[2]
251
+ const value = obj[actualKey]
252
+
253
+ // Handle empty types (for keys that don't exist)
254
+ if (!(actualKey in obj)) {
255
+ if (type === "empty-binary") {
256
+ obj[actualKey] = Buffer.from([])
257
+ } else if (type === "empty-list") {
258
+ obj[actualKey] = []
259
+ } else if (type === "empty-message") {
260
+ obj[actualKey] = {}
261
+ }
262
+ } else {
263
+ // Convert existing values to their proper types
264
+ // Handle both strings and Buffers
265
+ const strValue = Buffer.isBuffer(value) ? value.toString() : value
266
+ if (type === "integer" && typeof strValue === "string") {
267
+ obj[actualKey] = parseInt(strValue, 10)
268
+ } else if (type === "float" && typeof strValue === "string") {
269
+ obj[actualKey] = parseFloat(strValue)
270
+ } else if (type === "atom" && typeof strValue === "string") {
271
+ if (strValue === "true") {
272
+ obj[actualKey] = true
273
+ } else if (strValue === "false") {
274
+ obj[actualKey] = false
275
+ } else if (strValue === "null") {
276
+ obj[actualKey] = null
277
+ } else if (strValue === "undefined") {
278
+ obj[actualKey] = undefined
279
+ } else {
280
+ // General atoms (like "ok") become global Symbols
281
+ obj[actualKey] = Symbol.for(strValue)
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ // Check if the whole object should be converted to an array (. = "list")
288
+ if (aoTypes.includes('.="list"')) {
289
+ const keys = Object.keys(obj).filter(k => k !== "ao-types")
290
+ const isArrayLike = keys.every(k => /^\d+$/.test(k))
291
+ if (isArrayLike && keys.length > 0) {
292
+ const arr = []
293
+ const sortedKeys = keys.sort((a, b) => parseInt(a, 10) - parseInt(b, 10))
294
+ for (const k of sortedKeys) {
295
+ arr.push(obj[k])
296
+ }
297
+ return arr
298
+ }
299
+ }
300
+
301
+ // Only delete ao-types if it was a real type annotation dictionary
302
+ delete obj["ao-types"]
303
+ }
304
+
305
+ return obj
306
+ }
307
+
308
+ const modOut = out => {
309
+ let output = erl_str_from(out)
310
+
311
+ // Handle inline-body-key: rename 'body' to the key specified by inline-body-key
312
+ // But only if the key is different from 'body' (otherwise we'd delete it)
313
+ const inlineBodyKey = output["inline-body-key"]
314
+ if (inlineBodyKey && inlineBodyKey !== "body" && output.body !== undefined) {
315
+ output[inlineBodyKey] = output.body
316
+ delete output.body
317
+ }
318
+ // Special case: inline-body-key: body with undefined body means empty body
319
+ // This is how encode.js signals an empty body field
320
+ if (inlineBodyKey === "body" && output.body === undefined) {
321
+ output.body = Buffer.from([])
322
+ }
323
+
324
+ // Handle ao-body-key similarly
325
+ const aoBodyKey = output["ao-body-key"]
326
+ if (aoBodyKey && output.body !== undefined && !inlineBodyKey) {
327
+ output[aoBodyKey] = output.body
328
+ delete output.body
329
+ }
330
+
331
+ // Apply ao-types conversions recursively (handles type conversions and array reconstruction)
332
+ output = applyAoTypes(output)
333
+
334
+ // Delete TABM metadata fields
335
+ delete output.commitments
336
+ delete output.path
337
+ delete output.method
338
+ delete output["content-length"]
339
+ delete output["content-type"]
340
+ delete output["inline-body-key"]
341
+ delete output["ao-body-key"]
342
+ delete output["body-keys"]
343
+ // Delete HTTP request headers that Erlang includes in the response
344
+ delete output.accept
345
+ delete output["accept-bundle"]
346
+ delete output["accept-encoding"]
347
+ delete output["accept-language"]
348
+ delete output["sec-fetch-mode"]
349
+ delete output["user-agent"]
350
+ delete output.connection
351
+ delete output.host
352
+ delete output["content-digest"]
353
+ delete output.signature
354
+ delete output["signature-input"]
355
+ // Note: We no longer delete empty body buffers because they may be intentional
356
+ // (e.g., test case { body: Buffer.from([]) })
357
+ return output
358
+ }
359
+ const modIn = inp => {
360
+ let inp2 = normalize(inp)
361
+
362
+ // Recursive function to lowercase all object keys and convert empty strings to Buffer
363
+ const lowercaseKeys = (obj, isTopLevel = true) => {
364
+ // Handle null/undefined
365
+ if (obj === null || obj === undefined) {
366
+ return obj
367
+ }
368
+
369
+ // Handle empty strings - convert to empty Buffer
370
+ if (obj === "") {
371
+ return Buffer.from([])
372
+ }
373
+
374
+ // Handle arrays - recurse on each element
375
+ if (Array.isArray(obj)) {
376
+ return obj.map(item => lowercaseKeys(item, false))
377
+ }
378
+
379
+ // Handle objects - lowercase keys and recurse on values
380
+ if (typeof obj === "object" && obj.constructor === Object) {
381
+ const result = {}
382
+ for (const [key, value] of Object.entries(obj)) {
383
+ // Lowercase the key and recurse on the value
384
+ result[key.toLowerCase()] = lowercaseKeys(value, false)
385
+ }
386
+
387
+ // Note: The old data→body transformation is removed because with inline-body-key
388
+ // support, the signer now preserves original key names correctly.
389
+
390
+ return result
391
+ }
392
+
393
+ // Return other primitive values as-is
394
+ return obj
395
+ }
396
+
397
+ return lowercaseKeys(inp2, true)
398
+ }
399
+ export { mod, mod2, test, genTest, modOut, modIn }
@@ -0,0 +1,48 @@
1
+ import assert from "assert"
2
+ import { after, describe, it, before, beforeEach } from "node:test"
3
+ import { modIn, modOut } from "./lib/test-utils.js"
4
+ import { verify } from "../src/signer-utils.js"
5
+ import { HyperBEAM } from "../../src/test.js"
6
+ import cases from "./lib/cases.js"
7
+ import { normalize } from "../src/erl_json.js"
8
+ import { createSigner } from "../src/signer.js"
9
+ import { send } from "../src/send.js"
10
+ import { erl_str_from, erl_str_to } from "../src/erl_str.js"
11
+
12
+ describe("Hyperbeam Signer", function () {
13
+ let hbeam, sign
14
+ before(async () => {
15
+ hbeam = await new HyperBEAM({ reset: true, linkify_mode: false }).ready()
16
+ sign = createSigner(hbeam.jwk, hbeam.url)
17
+ })
18
+ after(async () => hbeam.kill())
19
+
20
+ it("should test signer", async () => {
21
+ let err = []
22
+ let success = []
23
+ let i = 0
24
+ let _cases = cases
25
+ for (const v of _cases) {
26
+ console.log(`[${++i}]...........................................`, v)
27
+ try {
28
+ const signed = await sign(
29
+ { path: "/~hbsig@1.0/msg2", ...v },
30
+ { path: false }
31
+ )
32
+ const { out } = await send(signed)
33
+ const output = modOut(out)
34
+ const exp = modIn(normalize(v))
35
+ assert.deepEqual(exp, output)
36
+ success.push(v)
37
+ } catch (e) {
38
+ console.log(e)
39
+ err.push(v)
40
+ }
41
+ }
42
+ console.log(`${err.length} / ${_cases.length} failed!`)
43
+ if (err.length > 0) {
44
+ for (let v of err) console.log(v)
45
+ throw new Error(`${err.length} test case(s) failed`)
46
+ }
47
+ })
48
+ })
@@ -0,0 +1,35 @@
1
+ import { structured_from, structured_to } from "../src/structured.js"
2
+ import { cases_from, cases_to } from "./lib/structured_cases.js"
3
+ import { ok } from "./lib/cases.js"
4
+ import { normalize } from "../src/erl_json.js"
5
+ import { genTest } from "./lib/test-utils.js"
6
+
7
+ genTest({
8
+ its: [
9
+ {
10
+ it: "should test structured_from (cases_from)",
11
+ path: "/~hbsig@1.0/structured_from",
12
+ cases: cases_from,
13
+ // For simple string-only cases, the output should match the input
14
+ // HB's structured codec doesn't add ao-types for pure string values
15
+ mod: v => normalize(v),
16
+ skipAoTypes: true,
17
+ removeAoTypes: true,
18
+ },
19
+ {
20
+ it: "should test structured_from (ok cases)",
21
+ path: "/~hbsig@1.0/structured_from",
22
+ cases: ok,
23
+ // For complex cases, use structured_from to get expected TABM format
24
+ mod: v => structured_from(normalize(v)),
25
+ skipAoTypes: true,
26
+ removeAoTypes: true,
27
+ },
28
+ {
29
+ it: "should test structured_to (cases_to)",
30
+ path: "/~hbsig@1.0/structured_to",
31
+ cases: cases_to,
32
+ mod: v => structured_to(normalize(v)),
33
+ },
34
+ ],
35
+ })
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes