muschema
Version:
Schemas for mudb
172 lines (145 loc) • 4.66 kB
text/typescript
import { MuWriteStream, MuReadStream } from 'mustreams';
import { MuSchema } from './schema';
// tslint:disable-next-line:class-name
export interface _TypeDataPair<SubTypes extends { [type:string]:MuSchema<any> }> {
type:keyof SubTypes;
data:SubTypes[keyof SubTypes]['identity'];
}
function isUnion (x) {
if (x !== Object(x)) {
return false;
}
if (Object.keys(x).length !== 2) {
return false;
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
if (!hasOwnProperty.call(x, 'type')) {
return false;
}
if (!hasOwnProperty.call(x, 'data')) {
return false;
}
return true;
}
export class MuUnion<SubTypes extends { [type:string]:MuSchema<any> }>
implements MuSchema<_TypeDataPair<SubTypes>> {
private _types:string[];
public readonly identity:_TypeDataPair<SubTypes>;
public readonly muType = 'union';
public readonly muData:SubTypes;
public readonly json:object;
constructor (
schemaSpec:SubTypes,
identityType?:keyof SubTypes,
) {
this._types = Object.keys(schemaSpec);
this.muData = schemaSpec;
if (identityType) {
this.identity = {
type: identityType,
data: schemaSpec[identityType].identity,
};
} else {
this.identity = {
type: '',
data: void 0,
};
}
const result = {};
Object.keys(schemaSpec).forEach((subtype) => {
result[subtype] = this.muData[subtype].json;
});
this.json = {
type: 'union',
identity: this.identity.type,
data: result,
};
}
public alloc () : _TypeDataPair<SubTypes> {
return {
type: '',
data: void 0,
};
}
public free (data:_TypeDataPair<SubTypes>) {
this.muData[data.type].free(data.data);
}
public equal (x:_TypeDataPair<SubTypes>, y:_TypeDataPair<SubTypes>) {
if (!isUnion(x) || !isUnion(y)) {
return false;
}
if (x.type !== y.type) {
return false;
}
if (x.type === '') {
return true;
}
return this.muData[x.type].equal(x.data, y.data);
}
public clone (data:_TypeDataPair<SubTypes>) : _TypeDataPair<SubTypes> {
const schema = this.muData[data.type];
return {
type: data.type,
data: schema.clone(data.data),
};
}
public copy (source:_TypeDataPair<SubTypes>, target:_TypeDataPair<SubTypes>) {
if (source === target) {
return;
}
if (source.type === target.type) {
this.muData[source.type].copy(source.data, target.data);
}
target.type = source.type;
this.muData[target.type].free(target.data);
target.data = this.muData[source.type].clone(source.data);
}
public diff (
base:_TypeDataPair<SubTypes>,
target:_TypeDataPair<SubTypes>,
stream:MuWriteStream,
) : boolean {
stream.grow(8);
const trackerOffset = stream.offset;
++stream.offset;
let tracker = 0;
const dataSchema = this.muData[target.type];
if (base.type === target.type) {
if (dataSchema.diff(base.data, target.data, stream)) {
tracker = 1;
}
} else {
stream.writeUint8(this._types.indexOf(target.type as string));
if (dataSchema.diff(dataSchema.identity, target.data, stream)) {
tracker = 2;
} else {
tracker = 4;
}
}
if (tracker) {
stream.writeUint8At(trackerOffset, tracker);
return true;
}
stream.offset = trackerOffset;
return false;
}
public patch (
base:_TypeDataPair<SubTypes>,
stream:MuReadStream,
) : _TypeDataPair<SubTypes> {
const result = this.clone(base);
const tracker = stream.readUint8();
if (tracker & 1) {
result.data = this.muData[result.type].patch(result.data, stream);
} else if (tracker & 2) {
result.type = this._types[stream.readUint8()];
const schema = this.muData[result.type];
result.data = schema.patch(result.data, stream);
} else {
result.type = this._types[stream.readUint8()];
const schema = this.muData[result.type];
result.data = schema.clone(schema.identity);
}
return result;
}
}