Qowaiv is a (Single) Value Object library. It aims to model reusable (Single) Value Objects that can be used in a wide variety of modeling scenarios, both inside and outside a Domain-driven context.
Supported scenarios include parsing, formatting, validation, (de)serialization, and domain specific logic.
As developement tool, we adopted TypeScript.
A Value Object that can be represented by a single scalar.
In Qowaiv.NET - for non continuous - SVO's there is a not-null state: The
default state of the struct. Such concept does not exist in JavaScript. As a
result, for empty states, undefined is used. So for example:
const svo = PostalCode.parse(''); // undefined.To not have to rely on try-catch, every SVO has a tryParse alternative.
This returns an Unparsable object, containing an error message and the
attempted value once a value can not be parsed. This allows clean code to
handle the unparsable state:
const svo = Guid.tryParse('not-a-guid'); // Unparsable object.
if (svo instanceof (Unparsable)) {
// handle the unparsable state
}The clock provides a testable set of functions that return the current time/date.
const now = Clock.now();
const today = Clock.today();
// updates the generator.
Clock.generator = () => new Date(2025, 09, 10);Represents a date (only) within the range of 0001-01-01 and 9999-12-31.
Contrary to JavaScript's Date, the month component is 1-based, just like the
year and the day (of month).
const date = DateOnly.parse('2017-06-11');
const epoch = date.unixEpoch; // total seconds since 1970-01-01
const dayOfMonth = date.day; // 11
const dayOfWeek = date.dayOfWeek; // 0, Sunday
const dayOfYear = date.dayOfYear; // 162
const next = date.addYears(10); // 2027-06-11
const next = date.addMonths(-3); // 2017-03-11
const next = date.addDays(40); // 2017-07-21
const leap = DateOnly.isLeapYear(1988); // true
const days = DateOnly.daysPerMonth(1988, 2); // 29
const dateTime = date.toDateTime(); // 2017-06-11T:00:00:00Z
const format = date.format('nl', { dateStyle: 'full' }); // zondag 11 juni 2017Represents a Globally Unique Identifier (GUID).
const next = Guid.newGuid(); // 123E4567-E89B-12D3-A456-426655440000
const str = next.format("B"); // {123E4567-E89B-12D3-A456-426655440000}Represents an email address. It supports
- local part with quotes (
"Can contain anything"@qowaiv.org) - comments (
in(some comment)fo@qowaiv.org) - Display Names
John Smith <info@qowaiv.org>"John Smith" info@qowaiv.orginfo@qowaiv.org (John Smith)
- Mailto prefix (
mailto:info@qowaiv.org) - IP-based domains (
info@[127.0.0.1])
Comments, display names, and the mailto prefix are stripped.
const email = Email.parse('info@qowaiv.org');
const ip = Email.parse('info@[127.0.0.1]');
const isIP = ip.isIpBased; // trueRepresents an IBAN.
const iban = InternationalBankAccountNumber.parse('NL20INGB0001234567');
const country = iban.country; // 'NL';
const formatted = iban.format(); // 'NL20 INGB 0001 2345 67' with nbsp.
const lower = iban.format('h'); // 'nl20 ingb 0001 2345 67' with nbsp.Represents a percentage.
const p = Percentage.parse("3.14"); // Parse: 3.14%;
const p = Percentage.parse("3.14%"); // Parse: 3.14%;
const p = Percentage.parse("31.4‰"); // Parse: 3.14%;
const p = Percentage.new(3.14); // 3.14%
const r = p.round(1); // 3.1%
const s = p.toJson(); // '3.14%'Represents a postal code. It supports validation for all countries.
When formatted, non-breaking whitespace is used.
const dutch = PostalCode.parse('2624DP');
dutch.isValid('NL'); // true
dutch.isValid('BE'); // false
const argentina = PostalCode.Parse('Z1230ABC');
argentina.format('AR'); // Z 1230 ABC
argentina.format('NL'); // Z1230ABCFor schema validation, we extend on [Zod](https://zod.dev} schema validation.
Qowaiv.js uses the q constant:
const Model = z.object({
name: z.string(), // Zod defined type
email: q.email(), // Qowaiv-Zod defined type
});For email validation, the following features are available:
q.email(); // required email address
q.email().ipBased(); // required email address including IP=based
q.email().optional(); // optional email addressFor IBAN validation, the following features are available:
q.iban(); // required IBAN
q.iban().optional(); // optional IBANAs JavaScript does not support method overloading, this interface makes explicit
by including toString() and format(f?: TOption) that the format method
should be the overloaded version of toString.
interface IFormattable<string> {
toString(): string;
format(f?: string): string;
}As JavaScript does not have a way to support equals overloading as a lot of other languages do, but it gives something.
interface IEquatable {
equals(other: any): boolean;
}Compared with the following snippet, you could work around it, although using a
function eq(l, r) over l === r is obvious not trivial, or close to ideal.
function eq(l, r) {
if (arguments.length !== 2) { throw new Error('Invalid number of arguments.'); }
if (l !== null && l !== undefined && typeof (l.equals) === 'function') {
return l.equals(r);
}
if (r !== null && r !== undefined && typeof (r.equals) === 'function') {
return r.equals(l);
}
return l === r;
} To support JSON.stringify() this interface was introduced.
interface IJsonStringifyable {
toJSON(): any;
}