@helios-lang/effect 0.1.0

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 (64) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +3 -0
  3. package/dist/Address.js +13 -0
  4. package/dist/Address.js.map +1 -0
  5. package/dist/Bech32.js +153 -0
  6. package/dist/Bech32.js.map +1 -0
  7. package/dist/Cbor.js +1171 -0
  8. package/dist/Cbor.js.map +1 -0
  9. package/dist/Uplc/Cek.js +3 -0
  10. package/dist/Uplc/Cek.js.map +1 -0
  11. package/dist/Uplc/Data.js +171 -0
  12. package/dist/Uplc/Data.js.map +1 -0
  13. package/dist/Uplc/DataSchema.js +118 -0
  14. package/dist/Uplc/DataSchema.js.map +1 -0
  15. package/dist/Uplc/Primitive.js +23 -0
  16. package/dist/Uplc/Primitive.js.map +1 -0
  17. package/dist/Uplc/index.js +4 -0
  18. package/dist/Uplc/index.js.map +1 -0
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/internal/Base32.js +201 -0
  22. package/dist/internal/Base32.js.map +1 -0
  23. package/dist/internal/BigEndian.js +56 -0
  24. package/dist/internal/BigEndian.js.map +1 -0
  25. package/dist/internal/Bits.js +300 -0
  26. package/dist/internal/Bits.js.map +1 -0
  27. package/dist/internal/Bytes.js +293 -0
  28. package/dist/internal/Bytes.js.map +1 -0
  29. package/dist/internal/Flat.js +298 -0
  30. package/dist/internal/Flat.js.map +1 -0
  31. package/dist/internal/Float.js +154 -0
  32. package/dist/internal/Float.js.map +1 -0
  33. package/dist/internal/Utf8.js +44 -0
  34. package/dist/internal/Utf8.js.map +1 -0
  35. package/eslint.config.mjs +30 -0
  36. package/package.json +36 -0
  37. package/src/Address.ts +20 -0
  38. package/src/Bech32.test.ts +117 -0
  39. package/src/Bech32.ts +198 -0
  40. package/src/Cbor.test.ts +1610 -0
  41. package/src/Cbor.ts +1704 -0
  42. package/src/Uplc/Cek.ts +92 -0
  43. package/src/Uplc/Data.ts +259 -0
  44. package/src/Uplc/DataSchema.test.ts +207 -0
  45. package/src/Uplc/DataSchema.ts +181 -0
  46. package/src/Uplc/Primitive.ts +56 -0
  47. package/src/Uplc/index.ts +3 -0
  48. package/src/index.ts +4 -0
  49. package/src/internal/Base32.test.ts +219 -0
  50. package/src/internal/Base32.ts +341 -0
  51. package/src/internal/BigEndian.test.ts +79 -0
  52. package/src/internal/BigEndian.ts +67 -0
  53. package/src/internal/Bits.test.ts +300 -0
  54. package/src/internal/Bits.ts +398 -0
  55. package/src/internal/Bytes.test.ts +369 -0
  56. package/src/internal/Bytes.ts +343 -0
  57. package/src/internal/Flat.test.ts +29 -0
  58. package/src/internal/Flat.ts +387 -0
  59. package/src/internal/Float.test.ts +51 -0
  60. package/src/internal/Float.ts +190 -0
  61. package/src/internal/Utf8.test.ts +69 -0
  62. package/src/internal/Utf8.ts +58 -0
  63. package/tsconfig.build.json +14 -0
  64. package/tsconfig.json +38 -0
@@ -0,0 +1,181 @@
1
+ import { Effect, Encoding, ParseResult, Schema } from "effect"
2
+ import { decode as decodeUtf8, encode as encodeUtf8 } from "../internal/Utf8.js"
3
+ import { DataUnencoded as Data } from "./Data.js"
4
+
5
+ const BigIntSchema = Schema.transformOrFail(Data, Schema.BigIntFromSelf, {
6
+ strict: true,
7
+ decode: (data) => {
8
+ if ("int" in data) {
9
+ return ParseResult.succeed(data.int)
10
+ } else {
11
+ return ParseResult.fail(
12
+ new ParseResult.Unexpected(data, "expected IntData")
13
+ )
14
+ }
15
+ },
16
+ encode: (value) => ParseResult.succeed({ int: value })
17
+ })
18
+
19
+ export { BigIntSchema as BigInt }
20
+
21
+ export const Int = Schema.transformOrFail(Data, Schema.Int, {
22
+ strict: true,
23
+ decode: (data) => {
24
+ if ("int" in data) {
25
+ return ParseResult.succeed(Number(data.int))
26
+ } else {
27
+ return ParseResult.fail(
28
+ new ParseResult.Unexpected(data, "expected IntData")
29
+ )
30
+ }
31
+ },
32
+ encode: (value) => {
33
+ if (value % 1.0 != 0) {
34
+ return ParseResult.fail(
35
+ new ParseResult.Unexpected(value, "not an integer")
36
+ )
37
+ } else {
38
+ return ParseResult.succeed({ int: BigInt(Math.round(value)) })
39
+ }
40
+ }
41
+ })
42
+
43
+ export const ByteArray = Schema.transformOrFail(
44
+ Data,
45
+ Schema.Uint8ArrayFromHex,
46
+ {
47
+ strict: true,
48
+ decode: (data) => {
49
+ if ("bytes" in data) {
50
+ return ParseResult.succeed(data.bytes)
51
+ } else {
52
+ return ParseResult.fail(
53
+ new ParseResult.Unexpected(data, "expected ByteArrayData")
54
+ )
55
+ }
56
+ },
57
+ encode: (hex) => ParseResult.succeed({ bytes: hex })
58
+ }
59
+ )
60
+
61
+ const StringSchema = Schema.transformOrFail(Data, Schema.String, {
62
+ strict: true,
63
+ decode: (data) => {
64
+ if ("bytes" in data) {
65
+ return decodeUtf8(data.bytes).pipe(
66
+ Effect.mapError((e) => {
67
+ return new ParseResult.Unexpected(data.bytes, e.message)
68
+ })
69
+ )
70
+ } else {
71
+ return ParseResult.fail(
72
+ new ParseResult.Unexpected(data, "expected ByteArrayData")
73
+ )
74
+ }
75
+ },
76
+ encode: (s) =>
77
+ ParseResult.succeed({ bytes: Encoding.encodeHex(encodeUtf8(s)) })
78
+ })
79
+
80
+ export { StringSchema as String }
81
+
82
+ const ArraySchema = <ItemType>(
83
+ itemSchema: Schema.Schema<ItemType, Schema.Schema.Encoded<typeof Data>>
84
+ ) =>
85
+ Schema.transformOrFail(Data, Schema.Array(itemSchema), {
86
+ strict: true,
87
+ decode: (data) => {
88
+ if ("list" in data) {
89
+ return ParseResult.succeed(data.list)
90
+ } else {
91
+ return ParseResult.fail(
92
+ new ParseResult.Unexpected(data, "expected ListData")
93
+ )
94
+ }
95
+ },
96
+ encode: (items) => ParseResult.succeed({ list: items })
97
+ })
98
+
99
+ export { ArraySchema as Array }
100
+
101
+ export const Struct = <
102
+ FieldTypes extends { [key: string]: any }
103
+ >(fieldSchemas: {
104
+ [FieldName in keyof FieldTypes]: Schema.Schema<FieldTypes[FieldName], Data>
105
+ }) =>
106
+ Schema.transformOrFail(Data, Schema.Struct(fieldSchemas), {
107
+ strict: true,
108
+ decode: (data) => {
109
+ if ("list" in data) {
110
+ return Effect.all(
111
+ Object.entries(fieldSchemas).map(([fieldName], i) => {
112
+ if (i >= data.list.length) {
113
+ return Effect.fail(
114
+ new ParseResult.Unexpected(
115
+ data,
116
+ `expected at least ${i + 1} entries in ListData`
117
+ )
118
+ )
119
+ }
120
+
121
+ const itemData = data.list[i]
122
+
123
+ return Effect.succeed([fieldName, itemData] as [string, Data])
124
+ })
125
+ ).pipe(Effect.map(Object.fromEntries))
126
+ } else {
127
+ return ParseResult.fail(
128
+ new ParseResult.Unexpected(data, "expected ListData")
129
+ )
130
+ }
131
+ },
132
+ encode: (fields) => ParseResult.succeed({ list: Object.values(fields) })
133
+ })
134
+
135
+ export const EnumVariant = <FieldTypes extends { [key: string]: any }>(
136
+ tag: number | bigint,
137
+ fieldSchemas: {
138
+ [FieldName in keyof FieldTypes]: Schema.Schema<FieldTypes[FieldName], Data>
139
+ }
140
+ ) =>
141
+ Schema.transformOrFail(Data, Schema.Struct(fieldSchemas), {
142
+ strict: true,
143
+ decode: (data) => {
144
+ if ("fields" in data) {
145
+ if (data.constructor != Number(tag)) {
146
+ return ParseResult.fail(
147
+ new ParseResult.Unexpected(
148
+ data,
149
+ `expected ConstrData with constructor tag ${tag}`
150
+ )
151
+ )
152
+ }
153
+
154
+ return Effect.all(
155
+ Object.entries(fieldSchemas).map(([fieldName], i) => {
156
+ if (i >= data.fields.length) {
157
+ return Effect.fail(
158
+ new ParseResult.Unexpected(
159
+ data,
160
+ `expected at least ${i + 1} entries in ConstrData`
161
+ )
162
+ )
163
+ }
164
+
165
+ const itemData = data.fields[i]
166
+
167
+ return Effect.succeed([fieldName, itemData] as [string, Data])
168
+ })
169
+ ).pipe(Effect.map(Object.fromEntries))
170
+ } else {
171
+ return ParseResult.fail(
172
+ new ParseResult.Unexpected(data, "expected ConstrData")
173
+ )
174
+ }
175
+ },
176
+ encode: (fields) =>
177
+ ParseResult.succeed({
178
+ constructor: Number(tag),
179
+ fields: Object.values(fields)
180
+ })
181
+ })
@@ -0,0 +1,56 @@
1
+ import { Brand, Schema } from "effect"
2
+
3
+ export type Bool = boolean & Brand.Brand<"Bool">
4
+
5
+ export const makeBool = Brand.nominal<Bool>()
6
+
7
+ export type ByteArray = number[] & Brand.Brand<"ByteArray">
8
+
9
+ export const makeByteArray = Brand.nominal<ByteArray>()
10
+
11
+ export const Int = Schema.BigInt.pipe(Schema.brand("Int"))
12
+
13
+ export type Int = bigint & Brand.Brand<"Int">
14
+
15
+ export const makeInt = Brand.nominal<Int>()
16
+
17
+ export type List = {
18
+ readonly _tag: "List"
19
+ readonly itemType: string // needed for empty lists
20
+ readonly items: Primitive[]
21
+ }
22
+
23
+ export function makeList(itemType: string, items: Primitive[]): List {
24
+ return {
25
+ _tag: "List",
26
+ itemType,
27
+ items
28
+ }
29
+ }
30
+
31
+ export type Pair = {
32
+ readonly _tag: "Pair"
33
+ readonly first: Primitive
34
+ readonly second: Primitive
35
+ }
36
+
37
+ export function makePair(first: Primitive, second: Primitive): Pair {
38
+ return {
39
+ _tag: "Pair",
40
+ first,
41
+ second
42
+ }
43
+ }
44
+
45
+ export type String = string & Brand.Brand<"String">
46
+
47
+ export const makeString = Brand.nominal<String>()
48
+
49
+ export type Unit = Brand.Brand<"Unit">
50
+
51
+ const makeUnitInternal = Brand.nominal<Unit>()
52
+
53
+ export const makeUnit = () => makeUnitInternal({})
54
+
55
+ // TODO: add Bls12_381 primitives
56
+ export type Primitive = Bool | ByteArray | Int | List | Pair | String | Unit
@@ -0,0 +1,3 @@
1
+ export * as Data from "./Data.js"
2
+ export * as DataSchema from "./DataSchema.js"
3
+ export * as Primitive from "./Primitive.js"
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * as Address from "./Address.js"
2
+ export * as Bech32 from "./Bech32.js"
3
+ export * as Cbor from "./Cbor.js"
4
+ export * as Uplc from "./Uplc"
@@ -0,0 +1,219 @@
1
+ import { Effect, Either } from "effect"
2
+ import { describe, it, expect } from "bun:test"
3
+ import * as Base32 from "./Base32.js"
4
+ import * as Utf8 from "./Utf8.js"
5
+
6
+ /**
7
+ * Some test vectors taken from https://chromium.googlesource.com/chromium/src/+/lkgr/components/base32/base32_unittest.cc
8
+ */
9
+
10
+ describe(`Base32.make()`, () => {
11
+ it("fails for non-32 char alphabet", () => {
12
+ expect(() => Base32.make({ alphabet: "abcdefg" })).toThrow()
13
+ })
14
+
15
+ it("fails for non-unique 32 char alphabet", () => {
16
+ expect(() =>
17
+ Base32.make({ alphabet: "aacdefghijklmnopqrstuvwxyz234567" })
18
+ ).toThrow()
19
+ })
20
+
21
+ it("fails for non-single char padding (0 chars)", () => {
22
+ expect(() =>
23
+ Base32.make({
24
+ alphabet: Base32.DEFAULT_ALPHABET,
25
+ padChar: ""
26
+ })
27
+ ).toThrow()
28
+ })
29
+
30
+ it("fails for non-single char padding (more than 1 chars)", () => {
31
+ expect(() =>
32
+ Base32.make({
33
+ alphabet: Base32.DEFAULT_ALPHABET,
34
+ padChar: "=="
35
+ })
36
+ ).toThrow()
37
+ })
38
+
39
+ it("fails if padding char is part of alphabet", () => {
40
+ expect(() =>
41
+ Base32.make({
42
+ alphabet: "abcdefghijklmnopqrstuvwxyz23456=",
43
+ padChar: "="
44
+ })
45
+ ).toThrow()
46
+ })
47
+ })
48
+
49
+ describe("Base32.DEFAULT.isValid()", () => {
50
+ it("returns true for an empty string", () => {
51
+ expect(Base32.DEFAULT.isValid("")).toBe(true)
52
+ })
53
+
54
+ it('returns true for "my"', () => {
55
+ expect(Base32.DEFAULT.isValid("my")).toBe(true)
56
+ })
57
+
58
+ it('returns false for "f0" (invalid char)', () => {
59
+ expect(Base32.DEFAULT.isValid("f0")).toBe(false)
60
+ })
61
+
62
+ it('returns false for "fo=" (bad alignment with padding)', () => {
63
+ expect(Base32.DEFAULT.isValid("fo=")).toBe(false)
64
+ })
65
+
66
+ it('returns false for "fo=o====" (interrupted padding)', () => {
67
+ expect(Base32.DEFAULT.isValid("fo=o====")).toBe(false)
68
+ })
69
+
70
+ it('returns false for "foo=====" (invalid padding length)', () => {
71
+ expect(Base32.DEFAULT.isValid("foo=====")).toBe(false)
72
+ })
73
+
74
+ it('returns false for "fooo====" (bad terminating char)', () => {
75
+ expect(Base32.DEFAULT.isValid("fooo====")).toBe(false)
76
+ })
77
+
78
+ it('returns true for "fooa===="', () => {
79
+ expect(Base32.DEFAULT.isValid("fooa====")).toBe(true)
80
+ })
81
+ })
82
+
83
+ describe("Base32.encode() without padding", () => {
84
+ const codec = Base32.make({})
85
+
86
+ it("returns an empty string for []", () => {
87
+ expect(codec.encode([])).toBe("")
88
+ })
89
+
90
+ it('returns "my" for the utf-8 bytes of "f"', () => {
91
+ expect(codec.encode(Utf8.encode("f"))).toBe("my")
92
+ })
93
+
94
+ it('returns "mzxq" for the utf-8 bytes of "fo"', () => {
95
+ expect(codec.encode(Utf8.encode("fo"))).toBe("mzxq")
96
+ })
97
+
98
+ it('returns "mzxw6" for the utf-8 bytes of "foo"', () => {
99
+ expect(codec.encode(Utf8.encode("foo"))).toBe("mzxw6")
100
+ })
101
+
102
+ it('returns "mzxw6yq" for the utf-8 bytes of "foob"', () => {
103
+ expect(codec.encode(Utf8.encode("foob"))).toBe("mzxw6yq")
104
+ })
105
+
106
+ it('returns "mzxw6ytb" for the utf-8 bytes of "fooba"', () => {
107
+ expect(codec.encode(Utf8.encode("fooba"))).toBe("mzxw6ytb")
108
+ })
109
+
110
+ it('returns "mzxw6ytboi" for the utf-8 bytes of "foobar"', () => {
111
+ expect(codec.encode(Utf8.encode("foobar"))).toBe("mzxw6ytboi")
112
+ })
113
+ })
114
+
115
+ describe("Base32.decode()", () => {
116
+ const paddingLessCodec = Base32.make({ alphabet: Base32.DEFAULT_ALPHABET })
117
+ const paddingCodec = Base32.make({
118
+ ...Base32.DEFAULT_PROPS,
119
+ strict: true
120
+ })
121
+
122
+ it("returns [] for an empty string", () => {
123
+ expect(Base32.DEFAULT.decode("")).toEqual(Either.right(new Uint8Array([])))
124
+ })
125
+
126
+ it('returns the utf-8 bytes of "f" for "my"', () => {
127
+ expect(paddingLessCodec.decode("my")).toEqual(
128
+ Either.right(Utf8.encode("f"))
129
+ )
130
+ })
131
+
132
+ it('returns the utf-8 bytes of "fo" for "mzxq"', () => {
133
+ expect(Base32.DEFAULT.decode("mzxq")).toEqual(
134
+ Either.right(Utf8.encode("fo"))
135
+ )
136
+ })
137
+
138
+ it('fails for "mzxq" if strict', () => {
139
+ expect(paddingCodec.decode("mzxq")._tag).toBe("Left")
140
+ })
141
+
142
+ it('returns the utf-8 btyes of "foo" for "mzxw6"', () => {
143
+ expect(Base32.DEFAULT.decode("mzxw6")).toEqual(
144
+ Either.right(Utf8.encode("foo"))
145
+ )
146
+ })
147
+
148
+ it('returns the utf-8 bytes of "foob" for "mzxw6yq"', () => {
149
+ expect(Base32.DEFAULT.decode("mzxw6yq")).toEqual(
150
+ Either.right(Utf8.encode("foob"))
151
+ )
152
+ })
153
+
154
+ it('returns the utf-8 bytes of "fooba" for "mzxw6ytb"', () => {
155
+ expect(Base32.DEFAULT.decode("mzxw6ytb")).toEqual(
156
+ Either.right(Utf8.encode("fooba"))
157
+ )
158
+ })
159
+
160
+ it('returns the utf-8 bytes of "foobar" for "mzxw6ytboi"', () => {
161
+ expect(Base32.DEFAULT.decode("mzxw6ytboi")).toEqual(
162
+ Either.right(Utf8.encode("foobar"))
163
+ )
164
+ })
165
+
166
+ it('fails for "0" (invalid char)', () => {
167
+ expect(Base32.DEFAULT.decode("0")._tag).toBe("Left")
168
+ })
169
+
170
+ it('fails for "1" (invalid char)', () => {
171
+ expect(Base32.DEFAULT.decode("1")._tag).toBe("Left")
172
+ })
173
+
174
+ it('fails for "8" (invalid char)', () => {
175
+ expect(Base32.DEFAULT.decode("8")._tag).toBe("Left")
176
+ })
177
+
178
+ it('fails for "9" (invalid char)', () => {
179
+ expect(Base32.DEFAULT.decode("9")._tag).toBe("Left")
180
+ })
181
+
182
+ it('fails for "$" (invalid char)', () => {
183
+ expect(Base32.DEFAULT.decode("$")._tag).toBe("Left")
184
+ })
185
+
186
+ it('returns the same for "mzxw6ytboi" as for "MZXW6YTBOI" (case insensitive)', () => {
187
+ const s = "mzxw6ytboi"
188
+
189
+ expect(Base32.DEFAULT.decode(s)).toEqual(
190
+ Base32.DEFAULT.decode(s.toUpperCase())
191
+ )
192
+ })
193
+ })
194
+
195
+ describe("Base32.decode()/Base32.encode() roundtrip", () => {
196
+ function roundtrip(encoded: string): string {
197
+ return Base32.DEFAULT.encode(Effect.runSync(Base32.DEFAULT.decode(encoded)))
198
+ }
199
+
200
+ it("fails for foo=====", () => {
201
+ expect(() => roundtrip("foo=====")).toThrow()
202
+ })
203
+
204
+ it("fails for foo====", () => {
205
+ expect(() => roundtrip("foo====")).toThrow()
206
+ })
207
+
208
+ it("fails for foo=b", () => {
209
+ expect(() => roundtrip("foo=b")).toThrow()
210
+ })
211
+
212
+ it("ok for fooa====", () => {
213
+ expect(roundtrip("fooa====")).toBe("fooa====")
214
+ })
215
+
216
+ it("fails for for fooo====", () => {
217
+ expect(() => roundtrip("fooo====")).toThrow()
218
+ })
219
+ })