Skip to content

Commit 7311afb

Browse files
Merge pull request #68 from IntersectMBO/feat/tschema-improvements
feat/tschema improvements
2 parents dd7a730 + 5450de7 commit 7311afb

File tree

4 files changed

+432
-194
lines changed

4 files changed

+432
-194
lines changed

.changeset/beige-donuts-wish.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
"@evolution-sdk/evolution": patch
3+
---
4+
5+
## TSchema Code Simplifications and Test Coverage
6+
7+
### Summary
8+
Added Literal options (index, flatInUnion) for better Union control. Simplified TSchema implementation by removing redundant code, extracting helpers, and optimizing algorithms. Added 7 missing round-trip tests for comprehensive coverage.
9+
10+
### New Features
11+
12+
**Literal options for custom indices and flat unions:**
13+
```typescript
14+
// Custom index for positioning in unions
15+
const Action = TSchema.Literal("withdraw", { index: 100 })
16+
17+
// Flat in union - unwraps the Literal at the Union level
18+
const FlatUnion = TSchema.Union(
19+
TSchema.Literal("OptionA", { flatInUnion: true }),
20+
TSchema.Literal("OptionB", { flatInUnion: true })
21+
)
22+
23+
// Before: Union wraps each literal
24+
// Constr(0, [Constr(0, [])]) for OptionA
25+
// Constr(1, [Constr(1, [])]) for OptionB
26+
27+
// After: Literals are unwrapped at Union level
28+
// Constr(0, []) for OptionA
29+
// Constr(1, []) for OptionB
30+
31+
// Note: TSchema.Literal("OptionA", "OptionB") creates a single schema
32+
// with multiple literal values, which is different from a Union of
33+
// separate Literal schemas. Use Union + flatInUnion for explicit control.
34+
```
35+
36+
**LiteralOptions interface:**
37+
```typescript
38+
interface LiteralOptions {
39+
index?: number // Custom Constr index (default: auto-increment)
40+
flatInUnion?: boolean // Unwrap when used in Union (default: false)
41+
}
42+
43+
// Overloaded signatures
44+
function Literal(...values: Literals): Literal<Literals>
45+
function Literal(...args: [...Literals, LiteralOptions]): Literal<Literals>
46+
```
47+
48+
### Code Simplifications
49+
50+
**Removed redundant OneLiteral function:**
51+
```typescript
52+
// Before: Separate function for single literals
53+
const Action = TSchema.OneLiteral("withdraw")
54+
55+
// After: Use Literal directly
56+
const Action = TSchema.Literal("withdraw")
57+
```
58+
59+
**Simplified Boolean validation:**
60+
```typescript
61+
// Before: Two separate checks
62+
decode: ({ fields, index }) => {
63+
if (index !== 0n && index !== 1n) {
64+
throw new Error(`Expected constructor index to be 0 or 1, got ${index}`)
65+
}
66+
if (fields.length !== 0) {
67+
throw new Error("Expected a constructor with no fields")
68+
}
69+
return index === 1n
70+
}
71+
72+
// After: Combined check with better error message
73+
decode: ({ fields, index }) => {
74+
if ((index !== 0n && index !== 1n) || fields.length !== 0) {
75+
throw new Error(`Expected constructor with index 0 or 1 and no fields, got index ${index} with ${fields.length} fields`)
76+
}
77+
return index === 1n
78+
}
79+
```
80+
81+
**Optimized collision detection (O(n²) → O(n)):**
82+
```typescript
83+
// Before: Nested loops
84+
for (let i = 0; i < flatMembers.length; i++) {
85+
for (let j = i + 1; j < flatMembers.length; j++) {
86+
if (flatMembers[i].index === flatMembers[j].index) {
87+
// collision detected
88+
}
89+
}
90+
}
91+
92+
// After: Map-based tracking
93+
const indexMap = new globalThis.Map<number, number>()
94+
for (const member of flatMembers) {
95+
if (indexMap.has(member.index)) {
96+
// collision detected
97+
}
98+
indexMap.set(member.index, member.position)
99+
}
100+
```
101+
102+
**Extracted helper functions:**
103+
- `getTypeName(value)` - Centralized type name logic for error messages
104+
- Simplified `getLiteralFieldValue` with ternary operators
105+
- Simplified tag field detection logic
106+
107+
### New Round-Trip Tests
108+
109+
Added comprehensive test coverage for previously untested features:
110+
111+
1. **UndefinedOr** - Both defined and undefined value encoding/decoding
112+
2. **Struct with custom index** - Validates custom Constr index is preserved
113+
3. **Struct with flatFields** - Verifies field merging into parent struct
114+
4. **Variant** - Multi-option tagged unions (Mint, Burn, Transfer)
115+
5. **TaggedStruct** - Default "_tag" field and custom tagField names
116+
6. **flatInUnion Literals in Union** - Validates flat Literals with Structs
117+
7. **flatInUnion mixed types** - Literals and Structs with flatFields
118+

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

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,11 @@ parent: Modules
2727
- [Integer (interface)](#integer-interface)
2828
- [Literal](#literal)
2929
- [Literal (interface)](#literal-interface)
30+
- [LiteralOptions (interface)](#literaloptions-interface)
3031
- [Map](#map)
3132
- [Map (interface)](#map-interface)
3233
- [NullOr](#nullor)
3334
- [NullOr (interface)](#nullor-interface)
34-
- [OneLiteral](#oneliteral)
35-
- [OneLiteral (interface)](#oneliteral-interface)
3635
- [Struct](#struct)
3736
- [Struct (interface)](#struct-interface)
3837
- [StructOptions (interface)](#structoptions-interface)
@@ -85,7 +84,7 @@ export declare const TaggedStruct: <
8584
tagValue: TagValue,
8685
fields: Fields,
8786
options?: StructOptions & { tagField?: TagField }
88-
) => Struct<{ [K in TagField]: OneLiteral<TagValue> } & Fields>
87+
) => Struct<{ [K in TagField]: Literal<[TagValue]> } & Fields>
8988
```
9089
9190
Added in v2.0.0
@@ -203,9 +202,14 @@ Creates a schema for literal types with Plutus Data Constructor transformation
203202
**Signature**
204203

205204
```ts
206-
export declare const Literal: <Literals extends NonEmptyReadonlyArray<Exclude<SchemaAST.LiteralValue, null | bigint>>>(
207-
...self: Literals
208-
) => Literal<Literals>
205+
export declare const Literal: {
206+
<Literals extends NonEmptyReadonlyArray<Exclude<SchemaAST.LiteralValue, null | bigint>>>(
207+
...self: Literals
208+
): Literal<Literals>
209+
<Literals extends NonEmptyReadonlyArray<Exclude<SchemaAST.LiteralValue, null | bigint>>>(
210+
...args: [...Literals, LiteralOptions]
211+
): Literal<Literals>
212+
}
209213
```
210214
211215
Added in v2.0.0
@@ -219,6 +223,30 @@ export interface Literal<Literals extends NonEmptyReadonlyArray<SchemaAST.Litera
219223
extends Schema.transform<Schema.SchemaClass<Data.Constr, Data.Constr, never>, Schema.Literal<[...Literals]>> {}
220224
```
221225

226+
## LiteralOptions (interface)
227+
228+
Options for Literal schema
229+
230+
**Signature**
231+
232+
```ts
233+
export interface LiteralOptions {
234+
/**
235+
* Custom Constr index for this literal (default: auto-incremented from 0)
236+
* Useful when matching Plutus contract constructor indices
237+
*/
238+
index?: number
239+
/**
240+
* When used in a Union, controls whether this Literal should be "flattened" (unwrapped).
241+
* - true: Encodes as Constr(index, []) directly
242+
* - false: Encodes as Constr(unionPos, [Constr(index, [])]) (nested)
243+
*
244+
* Default: true when index is specified, false otherwise
245+
*/
246+
flatInUnion?: boolean
247+
}
248+
```
249+
222250
## Map
223251

224252
Creates a schema for maps with Plutus Map type annotation
@@ -270,25 +298,6 @@ export interface NullOr<S extends Schema.Schema.All>
270298
extends Schema.transform<Schema.SchemaClass<Data.Constr, Data.Constr, never>, Schema.NullOr<S>> {}
271299
```
272300

273-
## OneLiteral
274-
275-
**Signature**
276-
277-
```ts
278-
export declare const OneLiteral: <Single extends Exclude<SchemaAST.LiteralValue, null | bigint>>(
279-
self: Single
280-
) => OneLiteral<Single>
281-
```
282-
283-
## OneLiteral (interface)
284-
285-
**Signature**
286-
287-
```ts
288-
export interface OneLiteral<Single extends Exclude<SchemaAST.LiteralValue, null | bigint>>
289-
extends Schema.transform<Schema.SchemaClass<Data.Constr, Data.Constr, never>, Schema.Literal<[Single]>> {}
290-
```
291-
292301
## Struct
293302

294303
Creates a schema for struct types using Plutus Data Constructor

0 commit comments

Comments
 (0)