From a75bc707a43b73bd8f83d0c8e8d4751161aec816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Van=20Keisbelck?= Date: Tue, 3 Nov 2020 11:10:24 +0100 Subject: [PATCH 01/18] changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e3fcd7..3d5a87a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## v1.2.0 (03/11/2020) + +#### closed + +- [**closed**] Run .ts tests only [#36](https://github.com/vankeisb/react-tea-cup/pull/36) +- [**closed**] parallel tasks [#34](https://github.com/vankeisb/react-tea-cup/pull/34) + +--- + ## v1.1.2 (19/10/2020) #### closed From de22567fbcd6dab28754643c6037898e2383c741 Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Thu, 5 Nov 2020 20:38:33 +0100 Subject: [PATCH 02/18] add orNull decoder --- core/src/TeaCup/Decode.test.ts | 34 +++++++++++++++++++++++++++++++++- core/src/TeaCup/Decode.ts | 8 ++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index 2a27e4a..72f71da 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -24,7 +24,7 @@ */ import { Decode, Decoder } from './Decode'; -import { err, ok } from './Result'; +import { err, ok, Result } from './Result'; import { just, nothing } from './Maybe'; const num = Decode.num; const field = Decode.field; @@ -292,3 +292,35 @@ describe('optional field', () => { ).toEqual(ok(expected)); }) }); + +describe('null types', () => { + test("non null value", () => { + const value = { foo: 'bar' }; + const result: Result = Decode.orNull(Decode.field('foo', Decode.str)).decodeValue(value) + expect(result).toEqual(ok('bar')); + }) + + test("null value", () => { + const value = { foo: null }; + const result: Result = (Decode.field('foo', Decode.orNull(Decode.str))).decodeValue(value) + expect(result).toEqual(ok(null)); + }) + + test("typical use case", () => { + type MyType = { + gnu: number | null; + foo: string | null + } + const value = { foo: null, gnu: null }; + const expected: MyType = { + foo: null, + gnu: null + }; + expect(Decode.map2( + (foo, gnu) => { return { foo, gnu } }, + Decode.field('foo', Decode.orNull(Decode.str)), + Decode.field('gnu', Decode.orNull(Decode.num))) + .decodeValue(value) + ).toEqual(ok(expected)); + }) +}) \ No newline at end of file diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index 666f7e5..8b8f223 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -133,6 +133,14 @@ export class Decode { }); } + /** + * Decoder for nullable types + * @param d the decoder to be used if the value is not null + */ + static orNull(d: Decoder): Decoder { + return this.map(v => v.map(v => v).withDefault(null), this.nullable(d)); + } + /** * Decoder for lists * @param d the decoder for elements in the list From 0b878aeaaa7d60c54fc36af02e79bfb96c53f149 Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Thu, 5 Nov 2020 21:25:24 +0100 Subject: [PATCH 03/18] introduce mapObject --- core/src/TeaCup/Decode.test.ts | 36 ++++++++++++++++++++++++++++++++++ core/src/TeaCup/Decode.ts | 20 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index 2a27e4a..985c2db 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -154,8 +154,44 @@ test('map8', () => { field('h', num), ).decodeValue(o), ).toEqual(ok(o)); + }); +describe('mapObject', () => { + type MyType = { + foo: string, + bar: number + }; + const expected: MyType = { + foo: 'a foo', + bar: 13 + } + + test('simple', () => { + const value = { foo: 'a foo', bar: 13 } + expect(Decode.mapObject({ + foo: Decode.str, + bar: Decode.num + }).decodeValue(value)).toEqual(ok(expected)); + }) + + test('missing field', () => { + const value = { foo: 'a foo' } + expect(Decode.mapObject({ + foo: Decode.str, + bar: Decode.num + }).decodeValue(value)).toEqual(err('path not found [bar] on {"foo":"a foo"}')); + }) + + test('superfluous field', () => { + const value = { foo: 'a foo', bar: 13, toto: true } + expect(Decode.mapObject({ + foo: Decode.str, + bar: Decode.num + }).decodeValue(value)).toEqual(ok(expected)); + }) +}) + test('andThen', () => { type Stuff = { readonly tag: 'stuff1'; readonly foo: string } | { readonly tag: 'stuff2'; readonly bar: string }; diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index 666f7e5..142a241 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -436,6 +436,22 @@ export class Decode { ); } + /** + * Decoder for big objects, where map8() is not enough. + * @param dobject an object with decoders + */ + static mapObject(dobject: { [P in keyof T]: Decoder }): Decoder { + const keys = Object.keys(dobject) as Array + const partialDecoder: Decoder> = keys.reduce((dacc, key) => Decode.andThen(object => { + const propertyDecoder = getProperty(dobject, key); + return Decode.map(property => { + object[key] = property; + return object; + }, Decode.field(key as string, propertyDecoder)); + }, dacc), Decode.succeed({} as Partial)) + return Decode.map(v => v as T, partialDecoder); + } + // Fancy Decoding /** @@ -506,3 +522,7 @@ export class Decode { }); } } + +function getProperty(o: T, key: K): T[K] { + return o[key]; +} \ No newline at end of file From 4f77616200f066cc87286fba81be0f12a02ec2c9 Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Thu, 5 Nov 2020 22:59:12 +0100 Subject: [PATCH 04/18] attempt mapArray --- core/src/TeaCup/Decode.test.ts | 46 ++++++++++++++++++++++++++++++++++ core/src/TeaCup/Decode.ts | 13 +++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index 985c2db..f93febb 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -192,6 +192,52 @@ describe('mapObject', () => { }) }) +describe('mapArray', () => { + type MyType = [ + string, + number + ] + const expected: MyType = [ + 'a foo', + 13 + ] + + test('simple', () => { + const value = ['a foo', 13] + expect(Decode.mapArray([ + Decode.str, + Decode.num + ]).decodeValue(value)).toEqual(ok(expected)); + }) + + test('type mismatch', () => { + // TODO help the type system compile fail? + const value = ['a foo', 13] + expect(Decode.mapArray([ + Decode.str, + Decode.str + ]).decodeValue(value)).toEqual(err('ran into decoder error at [1] : value is not a string : 13')); + }) + + test('missing item', () => { + // TODO help the type system compile fail? + const value = ['a foo'] + expect(Decode.mapArray([ + Decode.str, + Decode.num + ]).decodeValue(value)).toEqual(err('path not found [1] on [\"a foo\"]')); + }) + + test('too many items', () => { + // TODO help the type system compile fail? + const value = ['a foo', 13, true] + expect(Decode.mapArray([ + Decode.str, + Decode.num + ]).decodeValue(value)).toEqual(ok(expected)); + }) +}) + test('andThen', () => { type Stuff = { readonly tag: 'stuff1'; readonly foo: string } | { readonly tag: 'stuff2'; readonly bar: string }; diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index 142a241..a05d35c 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -452,6 +452,14 @@ export class Decode { return Decode.map(v => v as T, partialDecoder); } + /** + * Decoder for big objects, where map8() is not enough. + * @param darray an array with decoders + */ + static mapArray(darray: DecoderArray): Decoder { + return Decode.map(v => Object.values(v) as T, this.mapObject(darray)); + } + // Fancy Decoding /** @@ -523,6 +531,9 @@ export class Decode { } } + function getProperty(o: T, key: K): T[K] { return o[key]; -} \ No newline at end of file +} + +type DecoderArray = { [P in keyof A]: A[P] extends A[number] ? Decoder : never } From 9cfda7925e636bc20f8fa1354aac28786e7c07fe Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Sun, 8 Nov 2020 11:07:11 +0100 Subject: [PATCH 05/18] tests show how to use typesystem to enforce more --- core/src/TeaCup/Decode.test.ts | 37 +++++++++++++++++++++++++--------- core/src/TeaCup/Decode.ts | 10 +++++++-- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index f93febb..79797bc 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -169,7 +169,7 @@ describe('mapObject', () => { test('simple', () => { const value = { foo: 'a foo', bar: 13 } - expect(Decode.mapObject({ + expect(Decode.mapObject({ foo: Decode.str, bar: Decode.num }).decodeValue(value)).toEqual(ok(expected)); @@ -177,7 +177,7 @@ describe('mapObject', () => { test('missing field', () => { const value = { foo: 'a foo' } - expect(Decode.mapObject({ + expect(Decode.mapObject({ foo: Decode.str, bar: Decode.num }).decodeValue(value)).toEqual(err('path not found [bar] on {"foo":"a foo"}')); @@ -185,11 +185,13 @@ describe('mapObject', () => { test('superfluous field', () => { const value = { foo: 'a foo', bar: 13, toto: true } - expect(Decode.mapObject({ + expect(Decode.mapObject({ foo: Decode.str, bar: Decode.num }).decodeValue(value)).toEqual(ok(expected)); }) + + }) describe('mapArray', () => { @@ -203,16 +205,25 @@ describe('mapArray', () => { ] test('simple', () => { - const value = ['a foo', 13] - expect(Decode.mapArray([ + type ValueType = [string, number] + const value: ValueType = ['a foo', 13] + expect(Decode.mapArray([ Decode.str, Decode.num ]).decodeValue(value)).toEqual(ok(expected)); }) test('type mismatch', () => { - // TODO help the type system compile fail? - const value = ['a foo', 13] + type ValueType = [string, number] + const value: ValueType = ['a foo', 13] + + // the type system will compile fail this test: + // expect(Decode.mapArray([ + // Decode.str, + // Decode.str + // ]).decodeValue(value)).toEqual(err('ran into decoder error at [1] : value is not a string : 13')); + + // the type system will let though to runtime: expect(Decode.mapArray([ Decode.str, Decode.str @@ -220,7 +231,11 @@ describe('mapArray', () => { }) test('missing item', () => { - // TODO help the type system compile fail? + type ValueType = [string, number] + // the type system will compile fail this test: + // const value: ValueType = ['a foo'] + + // the type system will let though to runtime: const value = ['a foo'] expect(Decode.mapArray([ Decode.str, @@ -229,7 +244,11 @@ describe('mapArray', () => { }) test('too many items', () => { - // TODO help the type system compile fail? + type ValueType = [string, number] + // the type system will compile fail this test: + // const value: ValueType = ['a foo', 13, true] + + // the type system will let though to runtime: const value = ['a foo', 13, true] expect(Decode.mapArray([ Decode.str, diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index a05d35c..b06ac2c 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -440,7 +440,7 @@ export class Decode { * Decoder for big objects, where map8() is not enough. * @param dobject an object with decoders */ - static mapObject(dobject: { [P in keyof T]: Decoder }): Decoder { + static mapObject(dobject: DecoderObject): Decoder { const keys = Object.keys(dobject) as Array const partialDecoder: Decoder> = keys.reduce((dacc, key) => Decode.andThen(object => { const propertyDecoder = getProperty(dobject, key); @@ -457,7 +457,7 @@ export class Decode { * @param darray an array with decoders */ static mapArray(darray: DecoderArray): Decoder { - return Decode.map(v => Object.values(v) as T, this.mapObject(darray)); + return Decode.map(v => Object.values(v) as T, this.mapObject(darray)); } // Fancy Decoding @@ -536,4 +536,10 @@ function getProperty(o: T, key: K): T[K] { return o[key]; } + +type DecoderObject = { [P in keyof T]: Decoder } type DecoderArray = { [P in keyof A]: A[P] extends A[number] ? Decoder : never } + +// TODO need required? +// type DecoderObject = Required<{ [P in keyof T]: Decoder }> +// type DecoderArray = Required<{ [P in keyof A]: A[P] extends A[number] ? Decoder : never }> From 2784293f5a9b21755cd188e84f3b1077b73ecd4a Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Sun, 8 Nov 2020 16:19:49 +0100 Subject: [PATCH 06/18] map object supports optional fields --- core/src/TeaCup/Decode.test.ts | 52 ++++++++++++++++++++++++++++++---- core/src/TeaCup/Decode.ts | 24 ++++++++++++---- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index 79797bc..bbfcfdc 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -170,28 +170,52 @@ describe('mapObject', () => { test('simple', () => { const value = { foo: 'a foo', bar: 13 } expect(Decode.mapObject({ + foo: Decode.field('foo', Decode.str), + bar: Decode.field('bar', Decode.num) + }).decodeValue(value)).toEqual(ok(expected)); + }) + + + test('simpler', () => { + const value = { foo: 'a foo', bar: 13 } + expect(Decode.mapObject(Decode.mapFields({ foo: Decode.str, bar: Decode.num - }).decodeValue(value)).toEqual(ok(expected)); + })).decodeValue(value)).toEqual(ok(expected)); }) + test('missing field', () => { const value = { foo: 'a foo' } expect(Decode.mapObject({ - foo: Decode.str, - bar: Decode.num + foo: Decode.field('foo', Decode.str), + bar: Decode.field('bar', Decode.num) }).decodeValue(value)).toEqual(err('path not found [bar] on {"foo":"a foo"}')); }) test('superfluous field', () => { const value = { foo: 'a foo', bar: 13, toto: true } expect(Decode.mapObject({ - foo: Decode.str, - bar: Decode.num + foo: Decode.field('foo', Decode.str), + bar: Decode.field('bar', Decode.num) }).decodeValue(value)).toEqual(ok(expected)); }) + test('optional field', () => { + type MyType2 = { + foo: string, + bar?: number + }; + const expected: MyType2 = { + foo: 'a foo', + } + const value = { foo: 'a foo', toto: true } + expect(Decode.mapObject({ + foo: Decode.field('foo', Decode.str), + bar: Decode.optionalField('bar', Decode.num) + }).decodeValue(value)).toEqual(ok(expected)); + }) }) describe('mapArray', () => { @@ -392,4 +416,22 @@ describe('optional field', () => { Decode.optionalField('gnu', Decode.num)).decodeValue(value) ).toEqual(ok(expected)); }) + + test('simpler optional field', () => { + type MyType2 = { + foo: string, + bar?: number + }; + const expected: MyType2 = { + foo: 'a foo', + } + + const value = { foo: 'a foo', toto: true } + expect(Decode.mapObject({ + ...Decode.mapFields({ + foo: Decode.str, + }), + bar: Decode.optionalField('bar', Decode.num) + }).decodeValue(value)).toEqual(ok(expected)); + }) }); diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index b06ac2c..08088db 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -447,17 +447,31 @@ export class Decode { return Decode.map(property => { object[key] = property; return object; - }, Decode.field(key as string, propertyDecoder)); + }, propertyDecoder); }, dacc), Decode.succeed({} as Partial)) return Decode.map(v => v as T, partialDecoder); } /** - * Decoder for big objects, where map8() is not enough. - * @param darray an array with decoders - */ + * Convenience, map docoders to field decoders + * @param dobject an object with decoders + */ + static mapFields(decoders: DecoderObject): DecoderObject { + const keys = Object.keys(decoders) as Array + const partial: Partial> = keys.reduce((acc, key) => { + const propertyDecoder = getProperty(decoders, key); + acc[key] = Decode.field(key as string, propertyDecoder); + return acc; + }, {} as Partial>) + return partial as DecoderObject; + } + + /** + * Decoder for big objects, where map8() is not enough. + * @param darray an array with decoders + */ static mapArray(darray: DecoderArray): Decoder { - return Decode.map(v => Object.values(v) as T, this.mapObject(darray)); + return Decode.map(v => Object.values(v) as T, this.mapObject(this.mapFields(darray))); } // Fancy Decoding From b7b00da1858027ef6930514f0895bb4a3aec7d36 Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Sun, 8 Nov 2020 16:23:55 +0100 Subject: [PATCH 07/18] stricter typing --- core/src/TeaCup/Decode.test.ts | 5 +++++ core/src/TeaCup/Decode.ts | 9 ++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index bbfcfdc..2a0b21f 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -215,6 +215,11 @@ describe('mapObject', () => { foo: Decode.field('foo', Decode.str), bar: Decode.optionalField('bar', Decode.num) }).decodeValue(value)).toEqual(ok(expected)); + + // the type system will compile fail this test: + expect(Decode.mapObject({ + foo: Decode.field('foo', Decode.str), + }).decodeValue(value)).toEqual(ok(expected)); }) }) diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index 08088db..d69ac43 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -550,10 +550,5 @@ function getProperty(o: T, key: K): T[K] { return o[key]; } - -type DecoderObject = { [P in keyof T]: Decoder } -type DecoderArray = { [P in keyof A]: A[P] extends A[number] ? Decoder : never } - -// TODO need required? -// type DecoderObject = Required<{ [P in keyof T]: Decoder }> -// type DecoderArray = Required<{ [P in keyof A]: A[P] extends A[number] ? Decoder : never }> +type DecoderObject = Required<{ [P in keyof T]: Decoder }> +type DecoderArray = Required<{ [P in keyof A]: A[P] extends A[number] ? Decoder : never }> From 07ddb6233ae92ef4e32b01d957d0095f4615a8d7 Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Sun, 8 Nov 2020 20:00:09 +0100 Subject: [PATCH 08/18] re-comment compile test --- core/src/TeaCup/Decode.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index 2a0b21f..f3827eb 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -217,9 +217,9 @@ describe('mapObject', () => { }).decodeValue(value)).toEqual(ok(expected)); // the type system will compile fail this test: - expect(Decode.mapObject({ - foo: Decode.field('foo', Decode.str), - }).decodeValue(value)).toEqual(ok(expected)); + // expect(Decode.mapObject({ + // foo: Decode.field('foo', Decode.str), + // }).decodeValue(value)).toEqual(ok(expected)); }) }) From 6a6c1ff667f55242a73eb7a5fab0ef3af05d7a6c Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Sun, 8 Nov 2020 20:11:08 +0100 Subject: [PATCH 09/18] simpler api for decoding fields --- core/src/TeaCup/Decode.test.ts | 22 ++++++++++++++++++++++ core/src/TeaCup/Decode.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index f3827eb..344ca23 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -221,6 +221,28 @@ describe('mapObject', () => { // foo: Decode.field('foo', Decode.str), // }).decodeValue(value)).toEqual(ok(expected)); }) + + test('simpler optional field', () => { + type MyType2 = { + foo: string, + bar?: number + }; + const expected: MyType2 = { + foo: 'a foo', + } + + const decoder = { + ...Decode.mapRequiredFields({ + foo: Decode.str, + }), + ...Decode.mapOptionalFields({ + bar: Decode.num + }) + } + + const value = { foo: 'a foo', toto: true } + expect(Decode.mapObject(decoder).decodeValue(value)).toEqual(ok(expected)); + }) }) describe('mapArray', () => { diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index d69ac43..5849a76 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -466,6 +466,34 @@ export class Decode { return partial as DecoderObject; } + /** + * Convenience, map docoders to required field decoders + * @param dobject an object with decoders + */ + static mapRequiredFields(decoders: DecoderObject): DecoderObject { + const keys = Object.keys(decoders) as Array + const partial: Partial> = keys.reduce((acc, key) => { + const propertyDecoder = getProperty(decoders, key); + acc[key] = Decode.field(key as string, propertyDecoder); + return acc; + }, {} as Partial>) + return partial as DecoderObject; + } + + /** + * Convenience, map docoders to optional field decoders + * @param dobject an object with decoders + */ + static mapOptionalFields(decoders: DecoderObject): DecoderObjectWithOptionals { + const keys = Object.keys(decoders) as Array + const partial: Partial> = keys.reduce((acc, key) => { + const propertyDecoder = getProperty(decoders, key); + acc[key] = Decode.optionalField(key as string, propertyDecoder); + return acc; + }, {} as Partial>) + return partial as DecoderObjectWithOptionals; + } + /** * Decoder for big objects, where map8() is not enough. * @param darray an array with decoders @@ -551,4 +579,5 @@ function getProperty(o: T, key: K): T[K] { } type DecoderObject = Required<{ [P in keyof T]: Decoder }> +type DecoderObjectWithOptionals = Required<{ [P in keyof T]: Decoder }> type DecoderArray = Required<{ [P in keyof A]: A[P] extends A[number] ? Decoder : never }> From 01b0309674c1a980fe8086b5e2ae9c45cc882001 Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Sun, 8 Nov 2020 22:22:29 +0100 Subject: [PATCH 10/18] remove code duplication, genaralize mapFields --- core/src/TeaCup/Decode.test.ts | 10 +++---- core/src/TeaCup/Decode.ts | 49 +++++++++++++++++----------------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index 344ca23..924ad61 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -23,7 +23,7 @@ * */ -import { Decode, Decoder } from './Decode'; +import { Decode, Decoder, DecoderObject } from './Decode'; import { err, ok } from './Result'; import { just, nothing } from './Maybe'; const num = Decode.num; @@ -178,7 +178,7 @@ describe('mapObject', () => { test('simpler', () => { const value = { foo: 'a foo', bar: 13 } - expect(Decode.mapObject(Decode.mapFields({ + expect(Decode.mapObject(Decode.mapRequiredFields({ foo: Decode.str, bar: Decode.num })).decodeValue(value)).toEqual(ok(expected)); @@ -231,12 +231,12 @@ describe('mapObject', () => { foo: 'a foo', } - const decoder = { + const decoder: DecoderObject = { ...Decode.mapRequiredFields({ foo: Decode.str, }), ...Decode.mapOptionalFields({ - bar: Decode.num + bar: Decode.num, }) } @@ -455,7 +455,7 @@ describe('optional field', () => { const value = { foo: 'a foo', toto: true } expect(Decode.mapObject({ - ...Decode.mapFields({ + ...Decode.mapRequiredFields({ foo: Decode.str, }), bar: Decode.optionalField('bar', Decode.num) diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index 5849a76..9783eff 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -453,17 +453,19 @@ export class Decode { } /** - * Convenience, map docoders to field decoders + * Convenience, map decoder object to another decoder object * @param dobject an object with decoders + * @param fun the mapper function */ - static mapFields(decoders: DecoderObject): DecoderObject { + static mapFields(decoders: DecoderObject, fun: DecoderObjectMapper): DecoderObject { const keys = Object.keys(decoders) as Array - const partial: Partial> = keys.reduce((acc, key) => { + const partial: Partial> = keys.reduce((acc, key) => { const propertyDecoder = getProperty(decoders, key); - acc[key] = Decode.field(key as string, propertyDecoder); + const [key2, propertyDecoder2] = fun(key, propertyDecoder); + acc[key2] = propertyDecoder2; return acc; - }, {} as Partial>) - return partial as DecoderObject; + }, {} as Partial>) + return partial as DecoderObject; } /** @@ -471,27 +473,17 @@ export class Decode { * @param dobject an object with decoders */ static mapRequiredFields(decoders: DecoderObject): DecoderObject { - const keys = Object.keys(decoders) as Array - const partial: Partial> = keys.reduce((acc, key) => { - const propertyDecoder = getProperty(decoders, key); - acc[key] = Decode.field(key as string, propertyDecoder); - return acc; - }, {} as Partial>) - return partial as DecoderObject; + return this.mapFields(decoders, (k: keyof T, d: Decoder) => [k, Decode.field(k as string, d)]); } /** * Convenience, map docoders to optional field decoders * @param dobject an object with decoders */ - static mapOptionalFields(decoders: DecoderObject): DecoderObjectWithOptionals { - const keys = Object.keys(decoders) as Array - const partial: Partial> = keys.reduce((acc, key) => { - const propertyDecoder = getProperty(decoders, key); - acc[key] = Decode.optionalField(key as string, propertyDecoder); - return acc; - }, {} as Partial>) - return partial as DecoderObjectWithOptionals; + static mapOptionalFields(decoders: DecoderObject): DecoderObject> { + const mapper: DecoderObjectMapper> = + (k: keyof T, d: Decoder) => [k, Decode.optionalField(k as string, d)] + return this.mapFields(decoders, mapper); } /** @@ -499,7 +491,7 @@ export class Decode { * @param darray an array with decoders */ static mapArray(darray: DecoderArray): Decoder { - return Decode.map(v => Object.values(v) as T, this.mapObject(this.mapFields(darray))); + return Decode.map(v => Object.values(v) as T, this.mapObject(this.mapRequiredFields(darray))); } // Fancy Decoding @@ -578,6 +570,13 @@ function getProperty(o: T, key: K): T[K] { return o[key]; } -type DecoderObject = Required<{ [P in keyof T]: Decoder }> -type DecoderObjectWithOptionals = Required<{ [P in keyof T]: Decoder }> -type DecoderArray = Required<{ [P in keyof A]: A[P] extends A[number] ? Decoder : never }> +export type DecoderObject = Required<{ [P in keyof T]: Decoder }> +export type DecoderArray = Required<{ [P in keyof A]: A[P] extends A[number] ? Decoder : never }> + +export type OptionalFields = { + [P in keyof T]: (T[P] | undefined); +}; + +export type DecoderObjectMapper = + (k: keyof T, d: Decoder) => [keyof T2, Decoder] + From 15cfc4e5a7857fb4caf6f82ded0f0a61d372ccbd Mon Sep 17 00:00:00 2001 From: Frank Wagner Date: Mon, 9 Nov 2020 20:02:47 +0100 Subject: [PATCH 11/18] fixes after merge --- core/src/TeaCup/Decode.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index c9d8e1f..5fb6e85 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -24,8 +24,6 @@ */ import { Decode, Decoder, DecoderObject } from './Decode'; -import { err, ok } from './Result'; -import { Decode, Decoder } from './Decode'; import { err, ok, Result } from './Result'; import { just, nothing } from './Maybe'; const num = Decode.num; @@ -495,3 +493,4 @@ describe('null types', () => { .decodeValue(value) ).toEqual(ok(expected)); }) +}) From c527cd8b760f87f4db2682316aabfad5b8646ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Van=20Keisbelck?= Date: Mon, 16 Nov 2020 16:16:58 +0100 Subject: [PATCH 12/18] rename mapArray to mapTuple --- core/src/TeaCup/Decode.test.ts | 14 +++++++------- core/src/TeaCup/Decode.ts | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/TeaCup/Decode.test.ts b/core/src/TeaCup/Decode.test.ts index 5fb6e85..4252dc8 100644 --- a/core/src/TeaCup/Decode.test.ts +++ b/core/src/TeaCup/Decode.test.ts @@ -258,7 +258,7 @@ describe('mapArray', () => { test('simple', () => { type ValueType = [string, number] const value: ValueType = ['a foo', 13] - expect(Decode.mapArray([ + expect(Decode.mapTuple([ Decode.str, Decode.num ]).decodeValue(value)).toEqual(ok(expected)); @@ -274,8 +274,8 @@ describe('mapArray', () => { // Decode.str // ]).decodeValue(value)).toEqual(err('ran into decoder error at [1] : value is not a string : 13')); - // the type system will let though to runtime: - expect(Decode.mapArray([ + // the type system will let though to runtime: + expect(Decode.mapTuple([ Decode.str, Decode.str ]).decodeValue(value)).toEqual(err('ran into decoder error at [1] : value is not a string : 13')); @@ -286,9 +286,9 @@ describe('mapArray', () => { // the type system will compile fail this test: // const value: ValueType = ['a foo'] - // the type system will let though to runtime: + // the type system will let though to runtime: const value = ['a foo'] - expect(Decode.mapArray([ + expect(Decode.mapTuple([ Decode.str, Decode.num ]).decodeValue(value)).toEqual(err('path not found [1] on [\"a foo\"]')); @@ -299,9 +299,9 @@ describe('mapArray', () => { // the type system will compile fail this test: // const value: ValueType = ['a foo', 13, true] - // the type system will let though to runtime: + // the type system will let though to runtime: const value = ['a foo', 13, true] - expect(Decode.mapArray([ + expect(Decode.mapTuple([ Decode.str, Decode.num ]).decodeValue(value)).toEqual(ok(expected)); diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index 8067d20..82b80c8 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -462,7 +462,7 @@ export class Decode { /** * Convenience, map decoder object to another decoder object - * @param dobject an object with decoders + * @param dobject an object with decoders * @param fun the mapper function */ static mapFields(decoders: DecoderObject, fun: DecoderObjectMapper): DecoderObject { @@ -485,7 +485,7 @@ export class Decode { } /** - * Convenience, map docoders to optional field decoders + * Convenience, map decoders to optional field decoders * @param dobject an object with decoders */ static mapOptionalFields(decoders: DecoderObject): DecoderObject> { @@ -495,11 +495,11 @@ export class Decode { } /** - * Decoder for big objects, where map8() is not enough. + * Decoder for fixed-length tuples. * @param darray an array with decoders */ - static mapArray(darray: DecoderArray): Decoder { - return Decode.map(v => Object.values(v) as T, this.mapObject(this.mapRequiredFields(darray))); + static mapTuple(decoders: DecoderArray): Decoder { + return Decode.map(v => Object.values(v) as T, this.mapObject(this.mapRequiredFields(decoders))); } // Fancy Decoding From 5893301a4c3c6c1e97b07dc23791c103831ab09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Van=20Keisbelck?= Date: Mon, 16 Nov 2020 16:17:52 +0100 Subject: [PATCH 13/18] cleanup --- core/src/TeaCup/Decode.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/TeaCup/Decode.ts b/core/src/TeaCup/Decode.ts index 82b80c8..5f2a581 100644 --- a/core/src/TeaCup/Decode.ts +++ b/core/src/TeaCup/Decode.ts @@ -462,7 +462,7 @@ export class Decode { /** * Convenience, map decoder object to another decoder object - * @param dobject an object with decoders + * @param decoders an object with decoders * @param fun the mapper function */ static mapFields(decoders: DecoderObject, fun: DecoderObjectMapper): DecoderObject { @@ -478,7 +478,7 @@ export class Decode { /** * Convenience, map docoders to required field decoders - * @param dobject an object with decoders + * @param decoders an object with decoders */ static mapRequiredFields(decoders: DecoderObject): DecoderObject { return this.mapFields(decoders, (k: keyof T, d: Decoder) => [k, Decode.field(k as string, d)]); @@ -486,7 +486,7 @@ export class Decode { /** * Convenience, map decoders to optional field decoders - * @param dobject an object with decoders + * @param decoders an object with decoders */ static mapOptionalFields(decoders: DecoderObject): DecoderObject> { const mapper: DecoderObjectMapper> = @@ -496,7 +496,7 @@ export class Decode { /** * Decoder for fixed-length tuples. - * @param darray an array with decoders + * @param decoders an array with decoders */ static mapTuple(decoders: DecoderArray): Decoder { return Decode.map(v => Object.values(v) as T, this.mapObject(this.mapRequiredFields(decoders))); @@ -521,7 +521,7 @@ export class Decode { /** * Decoder for null - * @param the result to yield in case the decoded value is null + * @param t the result to yield in case the decoded value is null */ static null(t: T): Decoder { return new Decoder((o: any) => { From 2f2dfb415a4d9647cab714ac01ecb3e9ad1d03a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Van=20Keisbelck?= Date: Mon, 23 Nov 2020 12:32:35 +0100 Subject: [PATCH 14/18] max events in devtools --- tea-cup/src/TeaCup/DevTools.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tea-cup/src/TeaCup/DevTools.ts b/tea-cup/src/TeaCup/DevTools.ts index 943f3b6..0ac2862 100644 --- a/tea-cup/src/TeaCup/DevTools.ts +++ b/tea-cup/src/TeaCup/DevTools.ts @@ -60,6 +60,7 @@ export class DevTools { private pausedOnEvent: number = -1; private listeners: DevToolsListener[] = []; private objectSerializer: ObjectSerializer; + private maxEvents: number = -1; constructor(objectSerializer: ObjectSerializer) { this.objectSerializer = objectSerializer; @@ -82,6 +83,7 @@ export class DevTools { onEvent(e: DevToolsEvent): void { this.events.push(e); + this.removeEventsIfNeeded(); this.listeners.forEach((l) => l(e)); } @@ -192,4 +194,29 @@ export class DevTools { '***********************************************************************', ); } + + clear() { + if (this.isPaused()) { + this.resume(); + } + this.events = []; + console.log("All events cleared") + } + + setMaxEvents(maxEvents: number): DevTools { + this.maxEvents = maxEvents; + this.removeEventsIfNeeded(); + return this; + } + + private removeEventsIfNeeded(): DevTools { + if (this.maxEvents > 0) { + const delta = this.events.length - this.maxEvents; + if (delta > 0) { + const newEvents = this.events.slice(delta, this.events.length); + this.events = newEvents; + } + } + return this; + } } From f1d9b5393d4ad7226633bd4acf5465851672654c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Van=20Keisbelck?= Date: Mon, 23 Nov 2020 14:30:14 +0100 Subject: [PATCH 15/18] left/right accessors on Either --- core/src/TeaCup/Either.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/src/TeaCup/Either.ts b/core/src/TeaCup/Either.ts index ae82c6b..b90eefe 100644 --- a/core/src/TeaCup/Either.ts +++ b/core/src/TeaCup/Either.ts @@ -23,6 +23,8 @@ * */ +import {just, Maybe, nothing} from "./Maybe"; + /** * Either left, or right. */ @@ -55,6 +57,14 @@ export class Left { match(onLeft: (a: A) => R, onRight: (b: B) => R): R { return onLeft(this.value); } + + get left(): Maybe { + return just(this.value); + } + + get right(): Maybe { + return nothing; + } } export class Right { @@ -84,6 +94,14 @@ export class Right { match(onLeft: (a: A) => R, onRight: (b: B) => R): R { return onRight(this.value); } + + get left(): Maybe { + return nothing; + } + + get right(): Maybe { + return just(this.value); + } } export function left(a: A): Either { From 5ca9d8d985dfcc6239befda03bc93d3deaaa224b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Van=20Keisbelck?= Date: Mon, 23 Nov 2020 14:33:31 +0100 Subject: [PATCH 16/18] either tests --- core/src/TeaCup/Either.test.ts | 4 ++++ core/tsconfig.json | 27 +++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/core/src/TeaCup/Either.test.ts b/core/src/TeaCup/Either.test.ts index 53e7426..d23225d 100644 --- a/core/src/TeaCup/Either.test.ts +++ b/core/src/TeaCup/Either.test.ts @@ -29,12 +29,16 @@ test('left', () => { const e: Either = left('yeah'); expect(e.isLeft()).toBe(true); expect(e.isRight()).toBe(false); + expect(e.left.map(s => s + '!').withDefault('')).toBe('yeah!'); + expect(e.right.withDefault(123)).toBe(123); }); test('right', () => { const e: Either = right(123); expect(e.isLeft()).toBe(false); expect(e.isRight()).toBe(true); + expect(e.left.withDefault('!!!')).toBe('!!!'); + expect(e.right.map(x => x + 1).withDefault(456)).toBe(124); }); test('mapLeft', () => { diff --git a/core/tsconfig.json b/core/tsconfig.json index e66c8b6..50b8112 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -1,15 +1,34 @@ { - "include": ["src"], + "include": [ + "src" + ], "compilerOptions": { "target": "es6", - "module": "commonjs", + "module": "esnext", "declaration": true, "outDir": "./dist", "strict": true, "inlineSourceMap": true, "inlineSources": true, - "esModuleInterop": false + "esModuleInterop": false, + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" }, "compileOnSave": true, - "exclude": ["node_modules", "dist"] + "exclude": [ + "node_modules", + "dist" + ] } From 45555e528627c00c2760c2cd46cf80e4be6ec6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Van=20Keisbelck?= Date: Mon, 23 Nov 2020 15:24:22 +0100 Subject: [PATCH 17/18] fix tsconfig --- core/tsconfig.json | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/core/tsconfig.json b/core/tsconfig.json index 50b8112..e66c8b6 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -1,34 +1,15 @@ { - "include": [ - "src" - ], + "include": ["src"], "compilerOptions": { "target": "es6", - "module": "esnext", + "module": "commonjs", "declaration": true, "outDir": "./dist", "strict": true, "inlineSourceMap": true, "inlineSources": true, - "esModuleInterop": false, - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react" + "esModuleInterop": false }, "compileOnSave": true, - "exclude": [ - "node_modules", - "dist" - ] + "exclude": ["node_modules", "dist"] } From 05ef1a3cfb1b561d4734956dffae606ec750f1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Van=20Keisbelck?= Date: Mon, 23 Nov 2020 16:46:29 +0100 Subject: [PATCH 18/18] bump 1.3.0 --- core/package.json | 2 +- samples/package.json | 2 +- tea-cup/package.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/package.json b/core/package.json index 6fb09f0..1e4ad3e 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "tea-cup-core", - "version": "1.2.0", + "version": "1.3.0", "description": "react-tea-cup core classes and utilities (Maybe etc)", "author": "Rémi Van Keisbelck ", "license": "MIT", diff --git a/samples/package.json b/samples/package.json index d0c5abf..c6eefae 100644 --- a/samples/package.json +++ b/samples/package.json @@ -11,7 +11,7 @@ "react": "^16.7.0", "react-dom": "^16.7.0", "react-scripts": "3.4.3", - "react-tea-cup": "1.2.0" + "react-tea-cup": "1.3.0" }, "scripts": { "start": "react-scripts start", diff --git a/tea-cup/package.json b/tea-cup/package.json index f7b5d5e..8a9546b 100644 --- a/tea-cup/package.json +++ b/tea-cup/package.json @@ -1,6 +1,6 @@ { "name": "react-tea-cup", - "version": "1.2.0", + "version": "1.3.0", "description": "Put some TEA in your React.", "author": "Rémi Van Keisbelck ", "license": "MIT", @@ -19,7 +19,7 @@ "samples": "tsc --outFile ./dist/Samples/index.js && cp ./src/Samples/index.html ./dist/Samples" }, "dependencies": { - "tea-cup-core": "1.2.0", + "tea-cup-core": "1.3.0", "react": "^16.7.0" }, "devDependencies": {