-
-
Save micimize/9964338e0e052d1b15c821835c75db1d to your computer and use it in GitHub Desktop.
import * as I from 'immutable' | |
import { l, t, validater } from './t' | |
function IOMap<K extends t.Any, V extends t.Any>(keyType: K, valueType: V) { | |
type Key = t.TypeOf<K> | |
type Value = t.TypeOf<V> | |
type M = I.Map<Key, Value> | |
const Entries = t.array(t.tuple([ keyType, valueType ])) | |
type Entries = t.TypeOf<typeof Entries> | |
type Type = t.Type<M> & { | |
of: (value: Entries) => M | |
} | |
const Type: Type = { | |
_A: t._A, | |
name: `Map<${keyType.name}, ${valueType.name}>'`, | |
validate: (value: object, context) => | |
Entries.validate(Object.entries(value), context) | |
.chain(entries => t.success(I.Map<Key, Value>(entries))), | |
of: (entries: Entries): M => I.Map<Key, Value>(entries) | |
} | |
return Type //{ Map, Type } | |
} | |
namespace IOMap { | |
export const of = I.Map.of | |
export const isMap = I.Map.of | |
} | |
export default IOMap |
import * as I from 'immutable' | |
import { l, t, validater } from './t' | |
type Defaults<P extends t.Props> = t.PartialOf<P> | |
function emptyDefaults<P extends t.Props>(props: P): Defaults<P> { | |
let o = {} | |
Object.keys(props).forEach(k => | |
o[k] = undefined) | |
return o as Defaults<P> | |
} | |
// Prefilled Records don't require full or any arguments, as the defaults already satisfy them | |
function PrefilledRecord<P extends t.Props>(props: P, defaults: t.TypeOf<typeof FullInterface>){ | |
let FullInterface = t.interface(props) | |
type Interface = t.TypeOf<typeof FullInterface> | |
let R = I.Record(defaults as Object) | |
class PrefilledRecord extends R { | |
constructor(record: Partial<Interface> = defaults) { | |
super(record) | |
} | |
static of(record: Partial<Interface> = defaults): PrefilledRecord { | |
return new PrefilledRecord(record) | |
} | |
} | |
return PrefilledRecord | |
} | |
function Record<P extends t.Props>(props: P, defaults: Defaults<P> = emptyDefaults(props)){ | |
let FullInterface = t.readonly(t.interface(props)) | |
type Interface = t.TypeOf<typeof FullInterface> | |
let D = t.partial(props) | |
let validate = validater<Interface>(FullInterface) | |
class StrictRecord extends I.Record(defaults as object) { | |
constructor(record: Interface) { | |
super(validate(record)) | |
} | |
static of(record: Interface): StrictRecord { | |
return new StrictRecord(record) | |
} | |
public of = (record: Interface) => StrictRecord.of(record) | |
} | |
return StrictRecord | |
} | |
export { Record, PrefilledRecord } |
Hey @micimize, these are pretty awesome. I was just thinking of creating an abstraction similar to this, so thank you for the inspiration. Care to show an example or 2 on how did you end up using them?
Also, do you think it's valuable/required to use Immutable and io-ts together?
Thank you
@GabrielCTroia glad to hear its of some use! After some excavation, Here's what I believe is a usage example:
const Context = Record({
innovation: t.Integer,
activations: InnovationMap(ActivationRef),
nodes: InnovationMap(PotentialNode),
connections: InnovationMap(PotentialConnection)
}, {
innovation: 2,
activations: { 0: 'INPUT', 1: 'BIAS' },
nodes: {},
connections: {}
})
let context = t.validate({ foo: 'bar' }, Context)
I abandoned this approach on live-neat
, but there might be other usage examples in the tree at that commit. I think I just felt like I was getting distracted from the main focus of the project
That's pretty cool! Thank you!
I ended up with something a bit different – also I ditched Immutable for the simplicity of the Readonly Type, b/c I believe compile time immutable should be good enough.
Inspired by your idea, this is what I ended up with – still a work in progress as there are some things that are a bit quarky, like the exported types (RecordType, CollectionType), but I thing this API is a bit friendlier than the io-ts default.
/**
* Factory method for creating Opinioated Payload Records
* - Readonly by default
* - Exact Types - all the extra props are removed
* - io is provided in the callback to avoid import pollution
* - Out of the box support for Records & Collections
*
* @param name
* @param requiredFn
* @param optionalsFn
*/
export const RecordFactory = <TRequired extends io.Props, TOptional extends io.Props>(
name: string,
requiredFn: (t: typeof enhancedIO) => io.TypeC<TRequired>,
optionalsFn: (t: typeof enhancedIO) => io.PartialC<TOptional> = () => io.partial({} as TOptional),
) => {
const required = requiredFn(enhancedIO);
const optional = optionalsFn(enhancedIO);
const recordValidator = io.exact(io.intersection([required, optional]), name);
type RecordType = DeepReadonly<io.TypeOf<typeof recordValidator>>;
function recordOf(payload: unknown) {
const decoded = recordValidator.decode(payload);
if (isLeft(decoded)) {
const messages = reporter(decoded);
console.error(messages.join('\n'));
console.log('Faulty Payload', payload);
throw new Error('RecordOf Validation Error!');
}
return decoded.right as RecordType;
}
const collectionValidator = io.array(recordValidator, `${name}Collection`);
type CollectionType = DeepReadonly<io.TypeOf<typeof collectionValidator>>;
function collectionOf(payload: unknown) {
const decoded = collectionValidator.decode(payload);
if (isLeft(decoded)) {
const messages = reporter(decoded);
console.error(messages.join('\n'));
console.log('Faulty Payload', payload);
throw new Error('CollectionOf Validation Error!');
}
return decoded.right as CollectionType;
}
return {
// Use these to create records from paloads
recordOf,
collectionOf,
// Use these to create composite records
recordValidator,
collectionValidator,
// DO NOT USE THESE AS VALUES! But only to get the Type, not as a Value
// type MyRecord = typeof Record.RecordType
get RecordType(): RecordType {
throw new Error('RecordFactory - attempted to use RecordType as Value. Did you mean `typeof RecordType`?');
},
get CollectionType(): CollectionType {
throw new Error('RecordFactory - attempted to use CollectionType as Value. Did you mean `typeof CollectionType`?');
},
};
};
And you use it like:
import { RecordFactory } from 'src/lib/recordFactory';
export const Vehicle = RecordFactory(
'Vehicle',
(t) => t.type({
id: t.StringFromNumber,
userId: t.StringFromNumber,
make: t.string,
images: t.array(
t.type({
imageUrl: t.string,
mobileUrl: t.string,
}),
),
averageRating: t.NumberFromAny,
miles: t.NumberFromAny,
}),
);
export type VehicleRecord = typeof Vehicle.RecordType;
export type VehicleCollection = typeof Vehicle.CollectionType;
Your opinion would be greatly appreciated! Thanks
@GabrielCTroia Looks like a nice API to me! I think the main thing I would miss from my API would be the default object param
I found these in some corner of my fs and thought I'd throw them in a gist. They're probably broken and I wouldn't recommend using them.