-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
es6 class compatibility #247
Comments
This is an excerpt of how we're wrapping them for use with ES6 (the primary motivation was compatibility with Flow, and I couldn't get the
// @flow
import AmpersandState from 'ampersand-state';
import _ from 'lodash';
export default class StateModel<Props: GenericMap<mixed>> extends AmpersandState<Props, *, *> {
// By default set to 'reject' to throw errors when unknown properties are added in development
// 'ignore' in production to squelch errors.
static extraProperties = 'ignore';
// Keys helper
keys() {
return _.keys(this.all);
}
rawKeys() {
return _.keys(this._values);
}
// Simple values getter
values(): Object {
return this.serialize();
}
// getType as-is is useless, let's make it useful
getType(key: string): ?string {
return this._definition[key] && this._definition[key].type;
}
}
// ES6 Class Compatibility
export function setupModel<T: Class<$Subtype<StateModel<any>>>>(AModel: T): T {
// Move ampersand-specific statics over to prototype for compatibility
AModel.prototype.typeAttribute = AModel.typeAttribute;
AModel.prototype.idAttribute = AModel.idAttribute;
AModel.prototype.props = AModel.props;
AModel.prototype.derived = AModel.derived;
AModel.prototype.children = AModel.children;
AModel.prototype.extraProperties = AModel.extraProperties || AModel.prototype.extraProperties;
return AModel.extend(AModel.prototype);
}
// DataTypes
// Get existing datatypes to extend
const dataTypes = AmpersandState.extend({props: {}}).prototype._dataTypes;
AmpersandState.prototype._dataTypes = {
...dataTypes,
number: {
set(newVal) {
// This prevents a coercion to 0 (Number(null)), allowing defaults to work properly
if (newVal === null) return {val: null, type: null};
return {
val: Number(newVal),
type: 'number'
};
},
get(val) {
return val;
},
default() {
return NaN;
}
},
integer: {
set(newVal) {
if (typeof newVal === 'number' || typeof newVal === 'string') {
return {
val: parseInt(newVal, 10),
type: 'integer'
};
}
return {
val: newVal,
type: typeof newVal
};
},
get(val) {
if (typeof val === 'number') {
return parseInt(val, 10);
}
return null;
},
default() {
return NaN;
}
}
};
declare module 'ampersand-state' {
declare type AmpersandType = 'integer' | 'number' | 'string' | 'date' | 'moment' | 'object' | 'currency';
declare type DerivedDef<Props, DerivedProps> = {
deps?: Array<$Keys<Props> | $Keys<DerivedProps>>,
fn: () => any
};
declare type ExtraPropDef = {
type?: AmpersandType,
default?: any
} | string;
declare type CreateOptions = {
foo?: string
};
declare type GenericMap<T, U = any> = {[key: $Keys<U>]: T};
declare type DataType<T> = {
get: (val: T) => T;
default: () => T;
set: (newVal: any) => {val: T, type: ?string};
};
declare type GetAttributesOpts = {
props?: boolean,
session?: boolean,
derived?: boolean
};
declare class AmpersandState<Props, ExtraProps: GenericMap<ExtraPropDef>, DerivedProps: GenericMap<DerivedDef<*, *>>> {
static idAttribute?: string | false;
static typeAttribute?: string | false;
static extraProperties?: 'ignore' | 'reject';
// static extend<T1>(def: T1): Class<AmpersandState<Props, DerivedProps> & T1>;
// static extend<T1, T2>(def: T1, def2: T2): Class<AmpersandState<Props, DerivedProps> & T1 & T2>;
// static extend<T1, T2, T3>(def: T1, def2: T2, def3: T3): Class<AmpersandState<Props, DerivedProps> & T1 & T2 & T3>;
// static extend<T1, T2, T3, T4>(def: T1, def2: T2, def3: T3, def4: T4): Class<AmpersandState<Props, DerivedProps> & T1 & T2 & T3 & T4>;
static derived: ?DerivedProps;
static props: ?ExtraProps;
static extend(...protos: Array<Object>): typeof AmpersandState;
// Added statics
static schemaProps: Props;
static allProps: Props & ExtraProps & DerivedProps;
// Make these have an indexable signature
[key: $Keys<Props> | $Keys<DerivedProps>]: *;
collection: ?Object; // NOTE: If you try to import Collection here, it will silently coerce it to any
indexes: ?Array<string>;
isCollection: (item: any) => boolean;
length: number;
mainIndex: string;
isModel: (item: any) => boolean;
// Methods
initialize(attributes: ?Object, options: ?Object): void;
getType(key: string): ?string;
get(key: string): any;
getAttributes(options: GetAttributesOpts, raw?: boolean): GenericMap<mixed, Props>; // JSON
serialize(): GenericMap<mixed, Props>; // JSON
toJSON(): GenericMap<mixed, Props>; // JSON
on(event: string, handler: Function): void;
off(event: ?string, handler: ?Function): void;
// Added methods
keys(): Array<$Keys<Props>>;
rawKeys(): Array<$Keys<Props>>;
values(): GenericMap<mixed, Props>;
// Internals
all: {[key: $Keys<Props> | $Keys<DerivedProps>]: mixed};
// TODO internal definition
_definition: {[key: $Keys<Props> | $Keys<DerivedProps>]: Object};
_dataTypes: GenericMap<DataType<any>>;
_derived: {[key: $Keys<DerivedProps>]: DerivedDef<Props, DerivedProps>};
_values: Props;
}
declare var exports: typeof AmpersandState;
} We then have a number of helpful Flow type getters: export type ModelSchemaProps<T: Model> = $PropertyType<Class<T>, 'schemaProps'>; // extracts Props from Model
export type ModelShape<T: Model> = $Shape<T & ModelSchemaProps<T>>;
export type ModelKeys<T: Model> = $Keys<T> | $Keys<ModelSchemaProps<T>>; |
Yea, my motivation is similar. Lots of tooling seems work quite well with the new class syntax, but doesn't infer anything from |
(Note: that PR i linked is huge because it includes a copy of the ampersand-state test suite - if I ever get it to where I think it's actually usable, I planned on seeing about getting it into ampersand-state proper) |
ampersand-state has been immensely useful for backing my models in a consistent fashion, but I'm finding more and more that es6 classes would solve a category of problems that ampersand-state does not.
I'd like to do some work on making es6 classes and ampersand play nicely together, but before I begin, I'm curious what (if any) prior work has been done on this and if the maintainers have any thoughts on the best way to go about it.
A naive solution may be to simply reimplement ampersand-state as a class (possibly relying on decorators for defining prop/session/computed), but that'll force end users into a precompiler step. Can anyone share solutions that have already been considered?
The text was updated successfully, but these errors were encountered: