domain-objects
Version:
A simple, convenient way to represent domain objects, leverage domain knowledge, and add runtime validation in your code base.
381 lines • 16.6 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const uni_time_1 = require("@ehmpathy/uni-time");
const joi_1 = __importDefault(require("joi"));
const DomainEntity_1 = require("../../instantiation/DomainEntity");
const DomainLiteral_1 = require("../../instantiation/DomainLiteral");
const deserialize_1 = require("./deserialize");
/* eslint-disable no-useless-escape */
const serialize_1 = require("./serialize");
describe('deserialize', () => {
describe('basic types', () => {
it('should deserialize strings', () => {
const original = 'hello!';
const serial = (0, serialize_1.serialize)(original);
const undone = (0, deserialize_1.deserialize)(serial);
expect(undone).toEqual(original);
});
it('should deserialize numbers', () => {
const original = 821;
const serial = (0, serialize_1.serialize)(original);
const undone = (0, deserialize_1.deserialize)(serial);
expect(undone).toEqual(original);
});
it.todo('should deserialize dates');
it.todo('should deserialize undefined');
it('should deserialize nulls', () => {
const original = null;
const serial = (0, serialize_1.serialize)(original);
const undone = (0, deserialize_1.deserialize)(serial);
expect(undone).toEqual(original);
});
it.todo('should deserialize buffers');
});
describe('arrays', () => {
it('should be able to deserialize arrays', () => {
const original = ['2', 'four', 1, 3];
const serial = (0, serialize_1.serialize)(original);
const undone = (0, deserialize_1.deserialize)(serial);
expect(undone).toEqual(original); // sorted
});
it('should deserialize arrays even if they have objects', () => {
const original = [
'banana',
821,
{ id: 0, meaning: null },
{ id: 1, meaning: 42, value: 821 },
];
const serial = (0, serialize_1.serialize)(original);
const undone = (0, deserialize_1.deserialize)(serial);
expect(undone).toEqual(original);
});
});
describe('objects', () => {
it('should be able to serialize an object with all sorts of types', () => {
const original = {
color: 'blue',
cost: 821,
orders: [
{ id: 0, meaning: null },
{ id: 1, meaning: 42, value: 821 },
],
application: {
type: 'PAINTING',
},
};
const serial = (0, serialize_1.serialize)(original);
const undone = (0, deserialize_1.deserialize)(serial);
expect(undone).toEqual(original);
});
});
describe('domain objects', () => {
class Spaceship extends DomainEntity_1.DomainEntity {
}
Spaceship.unique = ['serialNumber'];
Spaceship.updatable = ['serialNumber'];
Spaceship.schema = joi_1.default.object().keys({
_dobj: joi_1.default.string().optional(),
serialNumber: joi_1.default.string().required(),
fuelQuantity: joi_1.default.number().required(),
passengers: joi_1.default.number().required(),
});
class Address extends DomainLiteral_1.DomainLiteral {
}
class Spaceport extends DomainEntity_1.DomainEntity {
}
Spaceport.unique = ['uuid'];
Spaceport.updatable = ['spaceships'];
Spaceport.nested = { address: Address, spaceships: Spaceship };
class Human extends DomainEntity_1.DomainEntity {
}
Human.unique = ['birthCode'];
Human.updatable = ['name'];
class Robot extends DomainEntity_1.DomainEntity {
}
Robot.unique = ['serialNumber'];
Robot.updatable = ['name'];
class Captain extends DomainEntity_1.DomainEntity {
}
Captain.unique = ['ship', 'agent'];
Captain.nested = {
ship: Spaceship,
agent: [Robot, Human],
};
// run the tests
it('should deserialize domain objects', () => {
const ship = new Spaceship({
serialNumber: '__UUID__',
fuelQuantity: 9001,
passengers: 21,
});
const original = ship;
const serial = (0, serialize_1.serialize)(original);
const undone = (0, deserialize_1.deserialize)(serial, { with: [Spaceship] });
expect(undone).toEqual(original);
expect(undone).toBeInstanceOf(Spaceship);
});
it('should throw a helpful error if attempted to deserialize a domain object without its constructor being provided in the context', () => {
const ship = new Spaceship({
serialNumber: '__UUID__',
fuelQuantity: 9001,
passengers: 21,
});
const original = ship;
const serial = (0, serialize_1.serialize)(original);
try {
(0, deserialize_1.deserialize)(serial, { with: [] });
throw new Error('should not reach here');
}
catch (error) {
if (!(error instanceof Error))
throw error;
expect(error.message).toContain(`DomainObject 'Spaceship' was referenced in the string being deserialized but was missing from the context given to the deserialize method`);
expect(error.message).toMatchSnapshot(); // save an example of the message to snapshot
}
});
it('recursively deserialize domain objects', () => {
const shipA = new Spaceship({
serialNumber: '__SHIP_A__',
fuelQuantity: 7000,
passengers: 42,
});
const shipB = new Spaceship({
serialNumber: '__SHIP_B__',
fuelQuantity: 9001,
passengers: 21,
});
const spaceport = new Spaceport({
uuid: '__SPACEPORT_UUID__',
address: new Address({
galaxy: 'Milky Way',
solarSystem: 'Sun',
planet: 'Earth',
continent: 'North America',
}),
spaceships: [shipA, shipB],
});
const original = spaceport;
const serial = (0, serialize_1.serialize)(original, { lossless: true });
const undone = (0, deserialize_1.deserialize)(serial, {
with: [Spaceport, Spaceship, Address],
});
expect(undone).toEqual(original);
expect(undone).toBeInstanceOf(Spaceport);
expect(undone.address).toBeInstanceOf(Address);
expect(undone.spaceships[0]).toBeInstanceOf(Spaceship);
});
it('recursively deserialize an array of domain objects', () => {
const shipA = new Spaceship({
serialNumber: '__SHIP_A__',
fuelQuantity: 7000,
passengers: 42,
});
const shipB = new Spaceship({
serialNumber: '__SHIP_B__',
fuelQuantity: 9001,
passengers: 21,
});
const original = [shipA, shipB];
const serial = (0, serialize_1.serialize)(original, { lossless: true });
const undone = (0, deserialize_1.deserialize)(serial, {
with: [Spaceport, Spaceship, Address],
});
expect(undone).toEqual(original);
expect(undone[0]).toBeInstanceOf(Spaceship);
});
it('recursively deserialize a domain object which has a nested domain-object property instantiable with several options of domain objects', () => {
const ship = new Spaceship({
serialNumber: '__SHIP_A__',
fuelQuantity: 7000,
passengers: 42,
});
const agent = new Robot({
serialNumber: '821',
name: 'Bender',
});
const captain = new Captain({
ship,
agent,
});
const original = captain;
const serial = (0, serialize_1.serialize)(original, { lossless: true });
const undone = (0, deserialize_1.deserialize)(serial, {
with: [Spaceship, Human, Robot, Captain],
});
expect(undone).toEqual(original);
expect(undone).toBeInstanceOf(Captain);
expect(undone.agent).toBeInstanceOf(Robot);
});
describe('speed', () => {
it.skip('should be faster if schema is skipped', () => __awaiter(void 0, void 0, void 0, function* () {
// define the choices
const shipA = new Spaceship({
serialNumber: '__SHIP_A__',
fuelQuantity: 7000,
passengers: 42,
});
const shipB = new Spaceship({
serialNumber: '__SHIP_B__',
fuelQuantity: 9001,
passengers: 21,
});
const spaceport = new Spaceport({
uuid: '__SPACEPORT_UUID__',
address: new Address({
galaxy: 'Milky Way',
solarSystem: 'Sun',
planet: 'Earth',
continent: 'North America',
}),
spaceships: [shipA, shipB],
});
const agent = new Robot({
serialNumber: '821',
name: 'Bender',
});
const captainA = new Captain({
ship: shipA,
agent,
});
const captainB = new Captain({
ship: shipB,
agent,
});
// define many instances of domain objects
const setSingle = [
shipA,
shipB,
shipA,
shipB,
shipA,
shipB,
spaceport,
agent,
captainA,
captainB,
];
const setMany = Array(100).fill(setSingle).flat(); // 100 copies of set single
// serialize the dobjs into a document
const document = (0, serialize_1.serialize)(setMany, { lossless: true });
// check the deserialize duration with schema
const stopwatchWithSchema = (0, uni_time_1.startDurationStopwatch)({
for: 'deserialize with schema',
log: {
level: 'info',
threshold: { milliseconds: 1 },
},
}, { log: console });
yield (0, deserialize_1.deserialize)(document, {
with: [Spaceship, Spaceport, Robot, Captain],
});
const { duration: durationWithSchema } = stopwatchWithSchema.stop();
// check the deserialize duration without schema
const stopwatchWithoutSchema = (0, uni_time_1.startDurationStopwatch)({
for: 'deserialize without schema',
log: {
level: 'info',
threshold: { milliseconds: 1 },
},
}, { log: console });
yield (0, deserialize_1.deserialize)(document, {
with: [Spaceship, Spaceport, Robot, Captain],
skip: {
schema: true,
},
});
const { duration: durationWithoutSchema } = stopwatchWithoutSchema.stop();
expect(durationWithoutSchema.milliseconds).toBeLessThan(durationWithSchema.milliseconds);
}));
it.skip('should be instant on repeat attempts, due to in memory cache', () => __awaiter(void 0, void 0, void 0, function* () {
// define the choices
const shipA = new Spaceship({
serialNumber: '__SHIP_A__',
fuelQuantity: 7000,
passengers: 42,
});
const shipB = new Spaceship({
serialNumber: '__SHIP_B__',
fuelQuantity: 9001,
passengers: 21,
});
const spaceport = new Spaceport({
uuid: '__SPACEPORT_UUID__',
address: new Address({
galaxy: 'Milky Way',
solarSystem: 'Sun',
planet: 'Earth',
continent: 'North America',
}),
spaceships: [shipA, shipB],
});
const agent = new Robot({
serialNumber: '821',
name: 'Bender',
});
const captainA = new Captain({
ship: shipA,
agent,
});
const captainB = new Captain({
ship: shipB,
agent,
});
// define many instances of domain objects
const setSingle = [
shipA,
shipB,
shipA,
shipB,
shipA,
shipB,
spaceport,
agent,
captainA,
captainB,
];
const setMany = Array(300).fill(setSingle).flat(); // 100 copies of set single
// serialize the dobjs into a document
const document = (0, serialize_1.serialize)(setMany, { lossless: true });
// check the deserialize duration with schema
const stopwatchFirst = (0, uni_time_1.startDurationStopwatch)({
for: 'deserialize with schema',
log: {
level: 'info',
threshold: { milliseconds: 1 },
},
}, { log: console });
yield (0, deserialize_1.deserialize)(document, {
with: [Spaceship, Spaceport, Robot, Captain],
});
const { duration: durationFirst } = stopwatchFirst.stop();
expect(durationFirst.milliseconds).toBeGreaterThan(20);
// check the deserialize duration without schema
const stopwatchSecond = (0, uni_time_1.startDurationStopwatch)({
for: 'deserialize with schema',
log: {
level: 'info',
threshold: { milliseconds: 1 },
},
}, { log: console });
yield (0, deserialize_1.deserialize)(document, {
with: [Spaceship, Spaceport, Robot, Captain],
});
const { duration: durationSecond } = stopwatchSecond.stop();
expect(durationSecond.milliseconds).toBeLessThan(5); // instant
}));
});
});
});
//# sourceMappingURL=deserialize.test.js.map