Skip to content

Commit 29c3e4d

Browse files
committed
docs: add changeset
1 parent 8fa379c commit 29c3e4d

File tree

5 files changed

+87
-121
lines changed

5 files changed

+87
-121
lines changed

.changeset/chilly-brooms-end.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
"@evolution-sdk/evolution": patch
3+
---
4+
5+
Fixed field ordering bug in TSchema.Struct encode function that caused fields to be swapped during CBOR encoding when using NullOr/UndefinedOr.
6+
7+
**Before:**
8+
```typescript
9+
const CredentialSchema = TSchema.Union(
10+
TSchema.Struct({ pubKeyHash: TSchema.ByteArray }, { flatFields: true }),
11+
TSchema.Struct({ scriptHash: TSchema.ByteArray }, { flatFields: true })
12+
)
13+
14+
const AddressSchema = TSchema.Struct({
15+
paymentCredential: CredentialSchema,
16+
stakeCredential: TSchema.NullOr(TSchema.Integer)
17+
})
18+
19+
const Foo = TSchema.Union(
20+
TSchema.Struct({ foo: AddressSchema }, { flatFields: true })
21+
)
22+
23+
const input = {
24+
foo: {
25+
paymentCredential: { pubKeyHash: fromHex("deadbeef") },
26+
stakeCredential: null
27+
}
28+
}
29+
30+
const encoded = Data.withSchema(Foo).toData(input)
31+
// BUG: Fields were swapped in innerStruct!
32+
// innerStruct.fields[0] = Constr(1, []) // stakeCredential (null) - WRONG!
33+
// innerStruct.fields[1] = Constr(0, [...]) // paymentCredential - WRONG!
34+
```
35+
36+
**After:**
37+
```typescript
38+
const CredentialSchema = TSchema.Union(
39+
TSchema.Struct({ pubKeyHash: TSchema.ByteArray }, { flatFields: true }),
40+
TSchema.Struct({ scriptHash: TSchema.ByteArray }, { flatFields: true })
41+
)
42+
43+
const AddressSchema = TSchema.Struct({
44+
paymentCredential: CredentialSchema,
45+
stakeCredential: TSchema.NullOr(TSchema.Integer)
46+
})
47+
48+
const Foo = TSchema.Union(
49+
TSchema.Struct({ foo: AddressSchema }, { flatFields: true })
50+
)
51+
52+
const input = {
53+
foo: {
54+
paymentCredential: { pubKeyHash: fromHex("deadbeef") },
55+
stakeCredential: null
56+
}
57+
}
58+
59+
const encoded = Data.withSchema(Foo).toData(input)
60+
// FIXED: Fields now in correct order matching schema!
61+
// innerStruct.fields[0] = Constr(0, [...]) // paymentCredential - CORRECT!
62+
// innerStruct.fields[1] = Constr(1, []) // stakeCredential (null) - CORRECT!
63+
```
64+

docs/content/docs/modules/core/TSchema.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ while maintaining single-level CBOR encoding compatible with Aiken.
100100
**Signature**
101101
102102
```ts
103-
export declare const Variant: <Variants extends Record<string, Schema.Struct.Fields>>(
103+
export declare const Variant: <const Variants extends Record<PropertyKey, Schema.Struct.Fields>>(
104104
variants: Variants
105-
) => Schema.Schema<VariantType<Variants>, Data.Data, never>
105+
) => Union<ReadonlyArray<{ [K in keyof Variants]: Struct<{ readonly [P in K]: Struct<Variants[K]> }> }[keyof Variants]>>
106106
```
107107
108108
Added in v2.0.0

docs/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./out/dev/types/routes.d.ts";
3+
import "./.next/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

packages/evolution/docs/modules/core/TSchema.ts.md

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,9 @@ while maintaining single-level CBOR encoding compatible with Aiken.
100100
**Signature**
101101
102102
```ts
103-
export declare function Variant<const Variants extends Record<PropertyKey, Schema.Struct.Fields>>(
103+
export declare const Variant: <const Variants extends Record<PropertyKey, Schema.Struct.Fields>>(
104104
variants: Variants
105-
): Union<
106-
ReadonlyArray<
107-
{
108-
[K in keyof Variants]: Struct<{ readonly [P in K]: Struct<Variants[K]> }>
109-
}[keyof Variants]
110-
>
111-
>
105+
) => Union<ReadonlyArray<{ [K in keyof Variants]: Struct<{ readonly [P in K]: Struct<Variants[K]> }> }[keyof Variants]>>
112106
```
113107
114108
Added in v2.0.0
@@ -303,10 +297,10 @@ Objects are represented as a constructor with index (default 0) and fields as an
303297
**Signature**
304298

305299
```ts
306-
export declare function Struct<Fields extends Schema.Struct.Fields>(
300+
export declare const Struct: <Fields extends Schema.Struct.Fields>(
307301
fields: Fields,
308-
options: StructOptions = {}
309-
): Struct<Fields>
302+
options?: StructOptions
303+
) => Struct<Fields>
310304
```
311305
312306
Added in v2.0.0

packages/evolution/test/TSchema.test.ts

Lines changed: 15 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -304,121 +304,29 @@ describe("TypeTaggedSchema Tests", () => {
304304
stakeCredential: TSchema.NullOr(TSchema.Integer)
305305
})
306306

307+
const Foo = TSchema.Union(
308+
TSchema.Struct({ foo: AddressSchema }, { flatFields: true })
309+
)
310+
307311
const input = {
308-
paymentCredential: { pubKeyHash: fromHex("deadbeef") },
309-
stakeCredential: null
312+
foo: {
313+
paymentCredential: { pubKeyHash: fromHex("deadbeef") },
314+
stakeCredential: null
315+
}
310316
}
311317

312-
const encoded = Data.withSchema(AddressSchema).toData(input)
313-
const decoded = Data.withSchema(AddressSchema).fromData(encoded)
318+
const encoded = Data.withSchema(Foo).toData(input)
319+
const decoded = Data.withSchema(Foo).fromData(encoded)
314320

315321
// Verify roundtrip
316322
expect(decoded).toEqual(input)
317323

318324
// Verify field order in CBOR: paymentCredential should be field 0, stakeCredential field 1
319-
expect(encoded.fields.length).toBe(2)
320-
expect(encoded.fields[0]).toBeInstanceOf(Data.Constr) // paymentCredential
321-
expect(encoded.fields[1]).toBeInstanceOf(Data.Constr) // stakeCredential (null)
322-
expect((encoded.fields[1] as Data.Constr).index).toBe(1n) // null is Constr(1, [])
323-
})
324-
325-
it("should preserve field order with multiple NullOr fields", () => {
326-
const TestSchema = TSchema.Struct({
327-
first: TSchema.Integer,
328-
second: TSchema.NullOr(TSchema.ByteArray),
329-
third: TSchema.Boolean,
330-
fourth: TSchema.NullOr(TSchema.Integer)
331-
})
332-
333-
const input = {
334-
first: 100n,
335-
second: fromHex("cafe"),
336-
third: true,
337-
fourth: null
338-
}
339-
340-
const encoded = Data.withSchema(TestSchema).toData(input)
341-
const decoded = Data.withSchema(TestSchema).fromData(encoded)
342-
343-
expect(decoded).toEqual(input)
344-
345-
// Verify field order
346-
expect(encoded.fields[0]).toBe(100n) // first
347-
expect(encoded.fields[1]).toBeInstanceOf(Data.Constr) // second (NullOr with value)
348-
expect(encoded.fields[2]).toBeInstanceOf(Data.Constr) // third (Boolean)
349-
expect(encoded.fields[3]).toBeInstanceOf(Data.Constr) // fourth (null)
350-
expect((encoded.fields[3] as Data.Constr).index).toBe(1n) // null
351-
})
352-
353-
it("should preserve field order with UndefinedOr fields", () => {
354-
const TestSchema = TSchema.Struct({
355-
first: TSchema.ByteArray,
356-
second: TSchema.UndefinedOr(TSchema.Integer),
357-
third: TSchema.Boolean
358-
})
359-
360-
const inputWithValue = {
361-
first: fromHex("deadbeef"),
362-
second: 42n,
363-
third: false
364-
}
365-
366-
const encodedWithValue = Data.withSchema(TestSchema).toData(inputWithValue)
367-
const decodedWithValue = Data.withSchema(TestSchema).fromData(encodedWithValue)
368-
369-
expect(decodedWithValue).toEqual(inputWithValue)
370-
371-
// Verify field order when value is present
372-
expect(encodedWithValue.fields[0]).toBeInstanceOf(Uint8Array) // first
373-
expect(encodedWithValue.fields[1]).toBeInstanceOf(Data.Constr) // second (UndefinedOr with value)
374-
expect(encodedWithValue.fields[2]).toBeInstanceOf(Data.Constr) // third (Boolean)
375-
376-
const inputUndefined = {
377-
first: fromHex("cafebabe"),
378-
second: undefined,
379-
third: true
380-
}
381-
382-
const encodedUndefined = Data.withSchema(TestSchema).toData(inputUndefined)
383-
const decodedUndefined = Data.withSchema(TestSchema).fromData(encodedUndefined)
384-
385-
expect(decodedUndefined).toEqual(inputUndefined)
386-
387-
// Verify field order when value is undefined
388-
expect(encodedUndefined.fields[0]).toBeInstanceOf(Uint8Array) // first
389-
expect(encodedUndefined.fields[1]).toBeInstanceOf(Data.Constr) // second (undefined)
390-
expect((encodedUndefined.fields[1] as Data.Constr).index).toBe(1n) // undefined is Constr(1, [])
391-
expect(encodedUndefined.fields[2]).toBeInstanceOf(Data.Constr) // third (Boolean)
392-
})
393-
394-
it("should preserve field order with mixed NullOr and UndefinedOr fields", () => {
395-
const TestSchema = TSchema.Struct({
396-
a: TSchema.Integer,
397-
b: TSchema.NullOr(TSchema.ByteArray),
398-
c: TSchema.UndefinedOr(TSchema.Integer),
399-
d: TSchema.Boolean
400-
})
401-
402-
const input = {
403-
a: 999n,
404-
b: null,
405-
c: undefined,
406-
d: true
407-
}
408-
409-
const encoded = Data.withSchema(TestSchema).toData(input)
410-
const decoded = Data.withSchema(TestSchema).fromData(encoded)
411-
412-
expect(decoded).toEqual(input)
413-
414-
// Verify all fields are in correct order
415-
expect(encoded.fields[0]).toBe(999n) // a
416-
expect(encoded.fields[1]).toBeInstanceOf(Data.Constr) // b (null)
417-
expect((encoded.fields[1] as Data.Constr).index).toBe(1n) // null
418-
expect(encoded.fields[2]).toBeInstanceOf(Data.Constr) // c (undefined)
419-
expect((encoded.fields[2] as Data.Constr).index).toBe(1n) // undefined
420-
expect(encoded.fields[3]).toBeInstanceOf(Data.Constr) // d (Boolean)
421-
expect((encoded.fields[3] as Data.Constr).index).toBe(1n) // true
325+
const innerStruct = (encoded.fields[0] as Data.Constr).fields[0] as Data.Constr
326+
expect(innerStruct.fields.length).toBe(2)
327+
expect(innerStruct.fields[0]).toBeInstanceOf(Data.Constr) // paymentCredential
328+
expect(innerStruct.fields[1]).toBeInstanceOf(Data.Constr) // stakeCredential (null)
329+
expect((innerStruct.fields[1] as Data.Constr).index).toBe(1n) // null is Constr(1, [])
422330
})
423331
})
424332

0 commit comments

Comments
 (0)