immutable-class
Version:
A template for creating immutable classes
187 lines (164 loc) • 4.5 kB
text/typescript
/*
* Copyright 2019 Imply Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { BackCompat, Property } from './base-immutable';
import { BaseImmutable, PropertyType } from './base-immutable';
export interface DriverValue {
name: string;
}
export interface DriverJS {
name: string;
}
export class Driver extends BaseImmutable<DriverValue, DriverJS> {
static POPE: Driver;
static PROPERTIES: Property[] = [{ name: 'name', defaultValue: null }];
static fromJS(params: DriverValue) {
return new Driver(BaseImmutable.jsToValue(Driver.PROPERTIES, params));
}
constructor(params: DriverValue) {
super(params);
}
}
BaseImmutable.finalize(Driver);
Driver.POPE = Driver.fromJS({ name: 'the pope' });
export interface CarValue {
name: string;
fuel: string;
subCar: Car;
range?: number;
relatedCars?: Car[];
createdOn?: Date;
owners?: string[];
driver?: Driver;
passengers?: string[];
}
export interface CarJS {
name: string;
fuel?: string;
subCar?: CarJS;
range?: number;
relatedCars?: CarJS[];
createdOn?: Date | string;
owners?: string[];
driver?: DriverJS;
passengers?: string[];
}
function ensureNonNegative(n: any): void {
if (n < 0) throw new Error('must non negative positive');
}
export class Car extends BaseImmutable<CarValue, CarJS> {
static PROPERTIES: Property<CarValue>[] = [
{
name: 'name',
validate: (n: string) => {
if (n.toLowerCase() !== n) throw new Error('must be lowercase');
},
},
{
name: 'fuel',
defaultValue: 'electric',
possibleValues: ['gas', 'diesel', 'electric'],
},
{
name: 'subCar',
defaultValue: null,
immutableClass: Car,
},
{
name: 'range',
defaultValue: 100,
validate: [BaseImmutable.ensure.number, ensureNonNegative],
},
{
name: 'relatedCars',
defaultValue: [],
immutableClassArray: Car,
},
{
name: 'createdOn',
defaultValue: null,
type: PropertyType.DATE,
},
{
name: 'owners',
defaultValue: null,
emptyArrayIsOk: true,
},
{
name: 'driver',
defaultValue: Driver.POPE,
immutableClass: Driver,
},
{
name: 'passengers',
type: PropertyType.ARRAY,
},
];
static BACK_COMPATS: BackCompat[] = [
{
condition: (js: any) => {
return js.fuelType;
},
action: (js: any) => {
js.fuel = js.fuelType;
},
},
];
static isCar(car: Car) {
return car instanceof Car;
}
static fromJS(properties: CarJS) {
return new Car(BaseImmutable.jsToValue(Car.PROPERTIES, properties, Car.BACK_COMPATS));
}
public declare name: string;
public declare fuel: string;
public declare subCar: Car;
public readonly range: number | undefined;
public declare createdOn: Date;
constructor(properties: CarValue) {
super(properties);
if (properties.range != null) this.range = properties.range;
if (this.fuel === 'electric' && this.range && this.range < 200) {
this.range = 200;
}
}
public declare changeFuel: (fuel: string) => this;
public declare getFuel: () => string;
public declare getRange: () => number;
public declare getName: () => string;
public declare getRelatedCars: () => Car[];
public getSubCar() {
const { name, subCar } = this;
if (subCar) return subCar;
if (name === 'ford') return Car.fromJS({ name: 'pinto', fuel: 'gas' });
if (name === 'toyota') return Car.fromJS({ name: 'prius', fuel: 'electric' });
return null;
}
public changeRange(n: number) {
const value = this.valueOf();
const { fuel } = value;
if (fuel === 'electric') {
value.range = n > 400 ? 400 : n;
return new Car(value);
}
if (fuel === 'diesel') {
value.range = n > 2000 ? 2000 : n;
return new Car(value);
}
value.range = n;
return new Car(value);
}
}
BaseImmutable.finalize(Car);