@terrygonguet/utils 0.0.2 → 0.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrygonguet/utils",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -2,7 +2,7 @@ import { identity } from "./index.ts"
2
2
 
3
3
  interface API<T> {
4
4
  isSome(this: Maybe<T>): this is Some<T>
5
- isNone(this: Maybe<T>): this is None
5
+ isNone(this: Maybe<T>): this is None<T>
6
6
  orDefault(this: Maybe<T>, defaultValue: T): T
7
7
  map<U>(this: Maybe<T>, f: (value: T) => U): Maybe<U>
8
8
  flatMap<U>(this: Maybe<T>, f: (value: T) => Maybe<U>): Maybe<U>
@@ -10,51 +10,57 @@ interface API<T> {
10
10
  }
11
11
 
12
12
  export type Some<T> = { value: T } & API<T>
13
- export type None = {} & API<never>
14
- export type Maybe<T> = Some<T> | None
13
+ export type None<T> = API<T>
14
+ export type Maybe<T> = Some<T> | None<T>
15
15
 
16
16
  const $_kind = "@terrygonguet/utils/functional/maybe"
17
17
  const $_variant_Some = "@terrygonguet/utils/functional/maybe/Some"
18
18
  const $_variant_None = "@terrygonguet/utils/functional/maybe/None"
19
19
 
20
20
  export function Some<T>(value: NonNullable<T>): Some<T> {
21
- const SomePrototype: API<T> = {
22
- isSome: () => true,
23
- isNone: () => false,
24
- orDefault(this: Some<T>) {
25
- return this.value
21
+ return Object.create(
22
+ {
23
+ isSome: () => true,
24
+ isNone: () => false,
25
+ orDefault(this: Some<T>) {
26
+ return this.value
27
+ },
28
+ map(this: Some<T>, f) {
29
+ return Some(f(this.value)!)
30
+ },
31
+ flatMap(this: Some<T>, f) {
32
+ return f(this.value)
33
+ },
34
+ toJSON(this: Some<T>) {
35
+ return { $_kind, $_variant: $_variant_Some, value: this.value }
36
+ },
37
+ } as API<T>,
38
+ {
39
+ $_kind: { value: $_kind, enumerable: false, writable: false },
40
+ $_variant: { value: $_variant_Some, enumerable: false, writable: false },
41
+ value: { value, writable: false },
26
42
  },
27
- map(this: Some<T>, f) {
28
- return Some(f(this.value)!)
29
- },
30
- flatMap(this: Some<T>, f) {
31
- return f(this.value)
32
- },
33
- toJSON(this: Some<T>) {
34
- return { $_kind, $_variant: $_variant_Some, value: this.value }
35
- },
36
- }
37
- return Object.create(SomePrototype, {
38
- $_kind: { value: $_kind, enumerable: false, writable: false },
39
- $_variant: { value: $_variant_Some, enumerable: false, writable: false },
40
- value: { value, writable: false },
41
- })
43
+ )
42
44
  }
43
45
 
44
- const NonePrototype: API<never> = {
45
- isSome: () => false,
46
- isNone: () => true,
47
- orDefault: identity,
48
- map: () => None,
49
- flatMap: () => None,
50
- toJSON: () => ({ $_kind, $_variant: $_variant_None }),
51
- }
52
- export const None: None = Object.create(NonePrototype, {
53
- $_kind: { value: $_kind, enumerable: false, writable: false },
54
- $_variant: { value: $_variant_None, enumerable: false, writable: false },
55
- })
46
+ export const None: None<any> = Object.create(
47
+ {
48
+ isSome: () => false,
49
+ isNone: () => true,
50
+ orDefault: identity,
51
+ map: () => None,
52
+ flatMap: () => None,
53
+ toJSON: () => ({ $_kind, $_variant: $_variant_None }),
54
+ } as API<any>,
55
+ {
56
+ $_kind: { value: $_kind, enumerable: false, writable: false },
57
+ $_variant: { value: $_variant_None, enumerable: false, writable: false },
58
+ },
59
+ )
56
60
 
57
61
  export const Maybe = {
62
+ Some,
63
+ None,
58
64
  from<T>(value: T | undefined | null): Maybe<T> {
59
65
  switch (value) {
60
66
  case null:
@@ -17,48 +17,54 @@ const $_variant_Success = "@terrygonguet/utils/functional/result/Success"
17
17
  const $_variant_Failure = "@terrygonguet/utils/functional/result/Failure"
18
18
 
19
19
  export function Success<S, F>(value: S): Success<S, F> {
20
- const SuccessPrototype: API<S, F> = {
21
- isSuccess: () => true,
22
- isFailure: () => false,
23
- map<S2>(this: Success<S, F>, f: (value: S) => S2) {
24
- return Success(f(this.value))
20
+ return Object.create(
21
+ {
22
+ isSuccess: () => true,
23
+ isFailure: () => false,
24
+ map<S2>(this: Success<S, F>, f: (value: S) => S2) {
25
+ return Success(f(this.value))
26
+ },
27
+ flatMap<S2, F2>(this: Success<S, F>, f: (value: S) => Result<S2, F2>) {
28
+ return f(this.value)
29
+ },
30
+ toJSON(this: Success<S, F>) {
31
+ return { $_kind, $_variant: $_variant_Success, value: this.value }
32
+ },
33
+ } as API<S, F>,
34
+ {
35
+ $_kind: { value: $_kind, enumerable: false, writable: false },
36
+ $_variant: { value: $_variant_Success, enumerable: false, writable: false },
37
+ value: { value, writable: false },
25
38
  },
26
- flatMap<S2, F2>(this: Success<S, F>, f: (value: S) => Result<S2, F2>) {
27
- return f(this.value)
28
- },
29
- toJSON(this: Success<S, F>) {
30
- return { $_kind, $_variant: $_variant_Success, value: this.value }
31
- },
32
- }
33
- return Object.create(SuccessPrototype, {
34
- $_kind: { value: $_kind, enumerable: false, writable: false },
35
- $_variant: { value: $_variant_Success, enumerable: false, writable: false },
36
- value: { value, writable: false },
37
- })
39
+ )
38
40
  }
39
41
 
40
42
  export function Failure<S, F>(reason: F): Failure<S, F> {
41
- const FailurePrototype: API<S, F> = {
42
- isSuccess: () => false,
43
- isFailure: () => true,
44
- map<S2>(this: Failure<S, F>) {
45
- return this as unknown as Result<S2, F>
43
+ return Object.create(
44
+ {
45
+ isSuccess: () => false,
46
+ isFailure: () => true,
47
+ map<S2>(this: Failure<S, F>) {
48
+ return this as unknown as Result<S2, F>
49
+ },
50
+ flatMap<S2, F2>(this: Failure<S, F>) {
51
+ return this as unknown as Result<S2, F | F2>
52
+ },
53
+ toJSON(this: Failure<S, F>) {
54
+ return { $_kind, $_variant: $_variant_Failure, reason: this.reason }
55
+ },
56
+ } as API<S, F>,
57
+ {
58
+ $_kind: { value: $_kind, enumerable: false, writable: false },
59
+ $_variant: { value: $_variant_Success, enumerable: false, writable: false },
60
+ reason: { value: reason, writable: false },
46
61
  },
47
- flatMap<S2, F2>(this: Failure<S, F>) {
48
- return this as unknown as Result<S2, F | F2>
49
- },
50
- toJSON(this: Failure<S, F>) {
51
- return { $_kind, $_variant: $_variant_Failure, reason: this.reason }
52
- },
53
- }
54
- return Object.create(FailurePrototype, {
55
- $_kind: { value: $_kind, enumerable: false, writable: false },
56
- $_variant: { value: $_variant_Success, enumerable: false, writable: false },
57
- reason: { value: reason, writable: false },
58
- })
62
+ )
59
63
  }
60
64
 
61
65
  export const Result = {
66
+ Success,
67
+ Failure,
62
68
  try<S, F>(tryFn: () => S) {
63
69
  return new TryCatch<S, F>(tryFn)
64
70
  },
@@ -72,7 +78,7 @@ export const Result = {
72
78
  },
73
79
  }
74
80
 
75
- class TryCatch<S, F> {
81
+ export class TryCatch<S, F> {
76
82
  tryFn: () => S
77
83
  catchFn: (err: unknown) => F
78
84
 
package/src/main.ts CHANGED
@@ -5,14 +5,21 @@ export function clamp(value: number, min: number, max: number) {
5
5
  return Math.min(Math.max(value, min), max)
6
6
  }
7
7
 
8
- export function safeParse<T>(
9
- str: string,
10
- defaultValue: T,
11
- reviver?: (key: string, value: any) => any,
12
- ) {
8
+ type JSONReviver = (key: string, value: any) => any
9
+
10
+ export function safeParse<T>(str: string, defaultValue: T, reviver?: JSONReviver) {
13
11
  try {
14
12
  return JSON.parse(str, reviver)
15
13
  } catch (_) {
16
14
  return defaultValue
17
15
  }
18
16
  }
17
+
18
+ export function composeJSONRevivers(...revivers: JSONReviver[]): JSONReviver {
19
+ return function (key, value) {
20
+ for (const reviver of revivers) {
21
+ value = reviver(key, value)
22
+ }
23
+ return value
24
+ }
25
+ }
@@ -1,6 +1,6 @@
1
1
  interface API<T> {
2
2
  isSome(this: Maybe<T>): this is Some<T>;
3
- isNone(this: Maybe<T>): this is None;
3
+ isNone(this: Maybe<T>): this is None<T>;
4
4
  orDefault(this: Maybe<T>, defaultValue: T): T;
5
5
  map<U>(this: Maybe<T>, f: (value: T) => U): Maybe<U>;
6
6
  flatMap<U>(this: Maybe<T>, f: (value: T) => Maybe<U>): Maybe<U>;
@@ -9,11 +9,13 @@ interface API<T> {
9
9
  export type Some<T> = {
10
10
  value: T;
11
11
  } & API<T>;
12
- export type None = {} & API<never>;
13
- export type Maybe<T> = Some<T> | None;
12
+ export type None<T> = API<T>;
13
+ export type Maybe<T> = Some<T> | None<T>;
14
14
  export declare function Some<T>(value: NonNullable<T>): Some<T>;
15
- export declare const None: None;
15
+ export declare const None: None<any>;
16
16
  export declare const Maybe: {
17
+ Some: typeof Some;
18
+ None: None<any>;
17
19
  from<T>(value: T | null | undefined): Maybe<T>;
18
20
  JSONReviver(_key: string, value: any): any;
19
21
  };
@@ -15,10 +15,12 @@ export type Result<S, F> = Success<S, F> | Failure<S, F>;
15
15
  export declare function Success<S, F>(value: S): Success<S, F>;
16
16
  export declare function Failure<S, F>(reason: F): Failure<S, F>;
17
17
  export declare const Result: {
18
+ Success: typeof Success;
19
+ Failure: typeof Failure;
18
20
  try<S, F>(tryFn: () => S): TryCatch<S, F>;
19
21
  JSONReviver(_key: string, value: any): any;
20
22
  };
21
- declare class TryCatch<S, F> {
23
+ export declare class TryCatch<S, F> {
22
24
  tryFn: () => S;
23
25
  catchFn: (err: unknown) => F;
24
26
  constructor(tryFn: () => S);
package/types/main.d.ts CHANGED
@@ -2,4 +2,7 @@
2
2
  * Behaviour is undefined when max < min
3
3
  */
4
4
  export declare function clamp(value: number, min: number, max: number): number;
5
- export declare function safeParse<T>(str: string, defaultValue: T, reviver?: (key: string, value: any) => any): any;
5
+ type JSONReviver = (key: string, value: any) => any;
6
+ export declare function safeParse<T>(str: string, defaultValue: T, reviver?: JSONReviver): any;
7
+ export declare function composeJSONRevivers(...revivers: JSONReviver[]): JSONReviver;
8
+ export {};
package/src/async.test.js DELETED
@@ -1,9 +0,0 @@
1
- import { describe } from "vitest"
2
- import { wait } from "./async.ts"
3
-
4
- describe("wait", it => {
5
- it("Waits", async ({ expect }) => {
6
- // TODO: figure out how to test that
7
- await expect(wait(25)).resolves
8
- })
9
- })
@@ -1,11 +0,0 @@
1
- import { describe } from "vitest"
2
- import { identity } from "./index.ts"
3
-
4
- describe.concurrent("identity()", it => {
5
- it("does nothing", ({ expect }) => {
6
- expect(identity(5)).to.equal(5)
7
- expect(identity("test")).to.equal("test")
8
- const object = { prop: "value" }
9
- expect(identity(object)).to.equal(object)
10
- })
11
- })
@@ -1,85 +0,0 @@
1
- import { describe } from "vitest"
2
- import { Some, None, Maybe } from "./maybe.ts"
3
-
4
- describe.concurrent("Maybe", it => {
5
- /** @type {Maybe<number>} */
6
- const some = Some(5)
7
- /** @type {Maybe<number>} */
8
- const none = None
9
-
10
- it("isSome()", ({ expect }) => {
11
- expect(some.isSome()).to.be.true
12
- expect(none.isSome()).to.be.false
13
- })
14
-
15
- it("isNone()", ({ expect }) => {
16
- expect(some.isNone()).to.be.false
17
- expect(none.isNone()).to.be.true
18
- })
19
-
20
- it("orDefault()", ({ expect }) => {
21
- expect(some.orDefault(15)).to.equal(5)
22
- expect(none.orDefault(15)).to.equal(15)
23
- })
24
-
25
- it("map()", ({ expect }) => {
26
- const mappedSome = some.map(n => n * 2)
27
- expect(mappedSome.isSome()).to.be.true
28
- expect(mappedSome.value).to.equal(10)
29
- const mappedNone = none.map(n => n * 2)
30
- expect(mappedNone.isNone()).to.be.true
31
- })
32
-
33
- it("flatMap()", ({ expect }) => {
34
- const some1 = some.flatMap(n => Some(n * 2))
35
- expect(some1.isSome()).to.be.true
36
- expect(some1.value).to.equal(10)
37
- const none1 = some.flatMap(n => None)
38
- expect(none1.isNone()).to.be.true
39
- const some2 = none.flatMap(n => Some(n * 2))
40
- expect(some2.isSome()).to.be.false
41
- const none2 = some.flatMap(n => None)
42
- expect(none2.isNone()).to.be.true
43
- })
44
-
45
- it("toJSON()", ({ expect }) => {
46
- expect(some.toJSON()).toMatchInlineSnapshot()
47
- expect(none.toJSON()).toMatchInlineSnapshot()
48
- })
49
-
50
- describe.concurrent("Maybe.JSONReviver()", it => {
51
- const $_kind = "@terrygonguet/utils/functional/maybe"
52
- const $_variant_Some = "@terrygonguet/utils/functional/maybe/Some"
53
- const $_variant_None = "@terrygonguet/utils/functional/maybe/None"
54
-
55
- it("does nothing to normal objects", ({ expect }) => {
56
- const json1 = `{"prop":"value"}`
57
- const obj1 = JSON.parse(json1, Maybe.JSONReviver)
58
- expect(obj1).to.deep.equal({ prop: "value" })
59
- })
60
-
61
- it("revives a Some", ({ expect }) => {
62
- const json2 = `{"prop":"value","some":{"$_kind":"${$_kind}","$_variant":"${$_variant_Some}","value":5}}`
63
- const obj2 = JSON.parse(json2, Maybe.JSONReviver)
64
- expect(obj2.prop).to.equal("value")
65
- expect(obj2.some.isSome()).to.be.true
66
- expect(obj2.some.value).to.equal(5)
67
- })
68
-
69
- it("revives a None", ({ expect }) => {
70
- const json3 = `{"prop":"value","none":{"$_kind":"${$_kind}","$_variant":"${$_variant_None}"}}`
71
- const obj3 = JSON.parse(json3, Maybe.JSONReviver)
72
- expect(obj3.prop).to.equal("value")
73
- expect(obj3.none).to.equal(None)
74
- })
75
-
76
- it("does a round trip", ({ expect }) => {
77
- const json4 = JSON.stringify({ prop: "value", some: Some(5), none: None })
78
- const obj4 = JSON.parse(json4, Maybe.JSONReviver)
79
- expect(obj4.prop).to.equal("value")
80
- expect(obj4.some.isSome()).to.be.true
81
- expect(obj4.some.value).to.equal(5)
82
- expect(obj4.none).to.equal(None)
83
- })
84
- })
85
- })
@@ -1,150 +0,0 @@
1
- import { describe } from "vitest"
2
- import { Success, Failure, Result } from "./result.ts"
3
-
4
- describe.concurrent("Result", it => {
5
- /** @type {Result<number, string>} */
6
- const success = Success(5)
7
- /** @type {Result<number, string>} */
8
- const failure = Failure("reason")
9
-
10
- it("isSuccess()", ({ expect }) => {
11
- expect(success.isSuccess()).to.be.true
12
- expect(failure.isSuccess()).to.be.false
13
- })
14
-
15
- it("isFailure()", ({ expect }) => {
16
- expect(success.isFailure()).to.be.false
17
- expect(failure.isFailure()).to.be.true
18
- })
19
-
20
- it("map()", ({ expect }) => {
21
- const mappedSuccess = success.map(n => n * 2)
22
- expect(mappedSuccess.isSuccess()).to.be.true
23
- expect(mappedSuccess.value).to.equal(10)
24
- const mappedFailure = failure.map(n => n * 2)
25
- expect(mappedFailure.isSuccess()).to.be.false
26
- })
27
-
28
- it("flatMap()", ({ expect }) => {
29
- const succ1 = success.flatMap(n => Success(n * 2))
30
- expect(succ1.isSuccess()).to.be.true
31
- expect(succ1.value).to.equal(10)
32
-
33
- const fail1 = success.flatMap(n => Failure("fail"))
34
- expect(fail1.isSuccess()).to.be.false
35
- expect(fail1.reason).to.equal("fail")
36
-
37
- const fail2 = failure.flatMap(n => Success(n * 2))
38
- expect(fail2.isSuccess()).to.be.false
39
- expect(fail2.reason).to.equal("reason")
40
-
41
- const fail3 = failure.flatMap(n => Failure("fail"))
42
- expect(fail3.isSuccess()).to.be.false
43
- expect(fail3.reason).to.equal("reason")
44
- })
45
-
46
- it("toJSON()", ({ expect }) => {
47
- expect(success.toJSON()).toMatchInlineSnapshot(`
48
- {
49
- "$_kind": "@terrygonguet/utils/functional/result",
50
- "$_variant": "@terrygonguet/utils/functional/result/Success",
51
- "value": 5,
52
- }
53
- `)
54
- expect(failure.toJSON()).toMatchInlineSnapshot(`
55
- {
56
- "$_kind": "@terrygonguet/utils/functional/result",
57
- "$_variant": "@terrygonguet/utils/functional/result/Failure",
58
- "reason": "reason",
59
- }
60
- `)
61
- })
62
-
63
- describe.concurrent("Result.JSONReviver()", it => {
64
- const $_kind = "@terrygonguet/utils/functional/result"
65
- const $_variant_Success = "@terrygonguet/utils/functional/result/Success"
66
- const $_variant_Failure = "@terrygonguet/utils/functional/result/Failure"
67
-
68
- it("does nothing to normal objects", ({ expect }) => {
69
- const json = `{"prop":"value"}`
70
- const obj = JSON.parse(json, Result.JSONReviver)
71
- expect(obj.prop).to.equal("value")
72
- })
73
-
74
- it("revives a Success", ({ expect }) => {
75
- const json = `{
76
- "prop":"value",
77
- "success": {
78
- "$_kind": "@terrygonguet/utils/functional/result",
79
- "$_variant": "@terrygonguet/utils/functional/result/Success",
80
- "value": 5
81
- }
82
- }`
83
- const obj = JSON.parse(json, Result.JSONReviver)
84
- expect(obj.prop).to.equal("value")
85
- expect(obj.success.isSuccess()).to.be.true
86
- expect(obj.success.value).to.equal(5)
87
- })
88
-
89
- it("revives a Failure", ({ expect }) => {
90
- const json = `{
91
- "prop":"value",
92
- "failure": {
93
- "$_kind": "@terrygonguet/utils/functional/result",
94
- "$_variant": "@terrygonguet/utils/functional/result/Failure",
95
- "reason": "reason"
96
- }
97
- }`
98
- const obj = JSON.parse(json, Result.JSONReviver)
99
- expect(obj.prop).to.equal("value")
100
- expect(obj.failure.isSuccess()).to.be.false
101
- expect(obj.failure.reason).to.equal("reason")
102
- })
103
-
104
- it("does a round trip", ({ expect }) => {
105
- const json = JSON.stringify({ prop: "value", success, failure })
106
- const obj = JSON.parse(json, Result.JSONReviver)
107
- expect(obj.prop).to.equal("value")
108
- expect(obj.success.isSuccess()).to.be.true
109
- expect(obj.success.value).to.equal(5)
110
- expect(obj.failure.isSuccess()).to.be.false
111
- expect(obj.failure.reason).to.equal("reason")
112
- })
113
- })
114
-
115
- describe("Result.try()", it => {
116
- it("gets the value from the try function", ({ expect }) => {
117
- const result = Result.try(() => {
118
- const n = 5
119
- return n * 2
120
- }).exec()
121
- expect(result.isSuccess()).to.be.true
122
- expect(result.value).to.equal(10)
123
- })
124
-
125
- it("gets the value from catch on error", ({ expect }) => {
126
- const result = Result.try(() => {
127
- throw new Error("fail")
128
- })
129
- .catch(err => {
130
- return err.message
131
- })
132
- .exec()
133
- expect(result.isSuccess()).to.be.false
134
- expect(result.reason).to.equal("fail")
135
- })
136
-
137
- it("ignores catch if try does not throw", ({ expect }) => {
138
- const result = Result.try(() => {
139
- const n = 5
140
- return n * 2
141
- })
142
- .catch(err => {
143
- throw new Error("Should not run")
144
- })
145
- .exec()
146
- expect(result.isSuccess()).to.be.true
147
- expect(result.value).to.equal(10)
148
- })
149
- })
150
- })
package/src/main.test.js DELETED
@@ -1,43 +0,0 @@
1
- import { describe } from "vitest"
2
- import { clamp, safeParse } from "./main.ts"
3
-
4
- describe.concurrent("clamp()", it => {
5
- it("does nothing to values within range", ({ expect }) => {
6
- expect(clamp(5, 0, 10)).to.equal(5)
7
- expect(clamp(10, 10, 10)).to.equal(10)
8
- expect(clamp(0, 0, 10)).to.equal(0)
9
- expect(clamp(10, 0, 10)).to.equal(10)
10
- })
11
-
12
- it("clamps values outside of range", ({ expect }) => {
13
- expect(clamp(-5, 0, 10)).to.equal(0)
14
- expect(clamp(15, 0, 10)).to.equal(10)
15
- expect(clamp(-5, 10, 10)).to.equal(10)
16
- expect(clamp(15, 10, 10)).to.equal(10)
17
- })
18
- })
19
-
20
- describe.concurrent("safeParse()", it => {
21
- it("works the same as JSON.parse for valid objects", ({ expect }) => {
22
- const json = `{"prop":"value"}`
23
- expect(safeParse(json, {})).to.deep.equal(JSON.parse(json))
24
- })
25
-
26
- it("uses default value for malformed JSON", ({ expect }) => {
27
- const json = `{"prop":}`
28
- expect(safeParse(json, { prop: "default" })).to.deep.equal({ prop: "default" })
29
- })
30
-
31
- it("uses revivers", ({ expect }) => {
32
- const json = `{"prop":"value","other":"ignored"}`
33
- function reviver(key, value) {
34
- if (key == "prop") return "replaced"
35
- else return value
36
- }
37
- const defaultValue = { prop: "value", other: "ignored" }
38
- expect(safeParse(json, defaultValue, reviver)).to.deep.equal({
39
- prop: "replaced",
40
- other: "ignored",
41
- })
42
- })
43
- })