@smartsoft001/utils

A set of static helper services with no framework attached: identifiers, Polish document validation, array and object handling, slugs and in-memory specification checks.


Install

npm install @smartsoft001/utils

Everything it needs comes with it: lodash, guid-typescript, md5, flatted and tslib are declared as dependencies. Nothing else is required, and the package imports neither Angular nor NestJS, so it runs the same in a browser bundle, in a Node process and in a test. The specification example below also uses @smartsoft001/domain-core to build the specification it evaluates.

What it is

This is the bottom of the internal dependency graph. Every other @smartsoft001 package is free to depend on it, and it depends on none of them, which is why the helpers are written as static methods on classes rather than as injectable services. There is nothing to register and nothing to construct: import the class and call the method.

The contents fall into four groups. Identifier and text helpers cover GUID creation, capitalisation, HTML stripping and slug generation. Validators cover the Polish tax number, the national identity number and the postal code, each exposing a matching isValid and isInvalid so a caller can read the condition in whichever direction suits the code. Data helpers cover arrays and the conversion of plain data into class instances, which is what makes the classType option of @Field in @smartsoft001/models work. Finally SpecificationService evaluates a specification against an object in memory, which is how the same business rule can be checked on the client and used as a query on the server.

Two behaviours are easy to get wrong from the signatures alone. ArrayService.addItem and removeItem return a new array, but they reach that result by mutating the array they were given first, so the argument changes too. And PasswordService is md5-based, which the warning below spells out.

Usage

Identifiers are generated without a database round trip, which is what lets the client set the id of a record it is about to create.

import { GuidService } from '@smartsoft001/utils';

export function createId(): string {
  return GuidService.create();
}

The spec asserts two properties of the result: it matches the canonical GUID layout of five hex groups, and two consecutive calls never return the same value.

Validators take the identifier as written by a person, not as stored.

import { NipService } from '@smartsoft001/utils';

/**
 * Validates the checksum of a Polish tax identification number (NIP).
 * Spaces and dashes are ignored, so "537-252-70-48" is accepted as well.
 */
export function isValidNip(nip: string): boolean {
  return NipService.isValid(nip);
}

The spec runs one number three times. The bare ten digits pass, the same number typed with dashes passes because the service strips spaces and hyphens before weighing the digits, and a copy with the last digit altered fails the checksum. PeselService and ZipCodeService follow the same shape for the national identity number and the postal code.

A specification built for a repository can also be evaluated against an object in memory.

import { BasicSpecification } from '@smartsoft001/domain-core';
import { SpecificationService } from '@smartsoft001/utils';

interface Account {
  id: string;
  status: string;
}

/** The same specification a repository would receive as query criteria. */
export const activeAccounts = new BasicSpecification({ status: 'active' });

/** Evaluates the specification in memory, without touching a repository. */
export function isActive(account: Account): boolean {
  return SpecificationService.valid(account, activeAccounts);
}

The spec checks both directions: an account whose status matches the criteria returns true, an account with another status returns false. No repository is involved, which is the point. The rule is defined once as a value and can be used as a query on the server and as a predicate on the client.

Only flat criteria are evaluated

SpecificationService.valid walks the keys of the criteria object and compares each one with the matching property of the value, treating an array property as a match when any element equals the criteria. It does not interpret the $and and $or keys that AndSpecification and OrSpecification produce, so composed specifications are for repositories. A key prefixed with $root. is resolved against the custom.$root object passed as the third argument instead of against the value.

PasswordService is not a password hash

PasswordService.hash returns an unsalted md5 digest, and compare simply hashes the candidate again and compares the two strings. md5 is fast and broken for this purpose, and without a salt identical passwords produce identical digests, so it must not be treated as a modern password hash. Use a memory-hard algorithm such as argon2 or bcrypt for credentials that matter, and keep this service for legacy digests you still have to read.

API

Identifiers and text

ExportKindDescription
GuidService.create()Static methodA new GUID as a lowercase hyphenated string.
capitalize(val)FunctionThe string with its first character uppercased. Returns '' for an empty or missing value.
RemoveHtmlService.create(val)Static methodPlain text from an HTML fragment. Strips tags, decodes the common named entities and the numeric &#..; forms, and drops any entity it does not recognise. Returns '' for null or undefined.
SlugService.create(text)Static methodA URL slug. Strips HTML first, maps Polish diacritics onto their ASCII letters, lowercases, replaces every run of other characters with a single hyphen and trims hyphens from both ends. Returns '' for null or undefined.

Validators

ExportKindDescription
NipService.isValid(nip)Static methodWhether the Polish tax number passes its weighted checksum. Ignores spaces and hyphens, and answers false for anything that is not a string.
NipService.isInvalid(nip)Static methodThe negation of isValid.
PeselService.isValid(pesel)Static methodWhether the national identity number is eleven digits, carries a month no greater than 12 and a day no greater than 31, and passes its checksum.
PeselService.isInvalid(pesel)Static methodThe negation of isValid.
ZipCodeService.isValid(code)Static methodWhether the postal code has the Polish NN-NNN form.
ZipCodeService.isInvalid(code)Static methodThe negation of isValid.

Arrays and objects

ExportKindDescription
ArrayService.addItem(array, item)Static methodPushes the item onto the array and returns a copy of the result. The argument is modified as well.
ArrayService.removeItem(array, item)Static methodRemoves the first occurrence of the item from the array and returns a copy. The argument is modified as well.
ArrayService.sort(array, by)Static methodA new array sorted by the key the callback returns, using lodash sortBy. Leaves the argument alone.
ObjectService.createByType(data, type)Static methodAn instance of type carrying the own properties of data. Returns data unchanged when it is falsy or already an instance of that type. This is what rehydrates the classType fields of a model.
ObjectService.removeTypes(obj)Static methodA copy whose property values have been round-tripped through JSON, so class instances become plain objects. Date values are kept as they are, and a circular value falls back to flatted.

Specifications and passwords

ExportKindDescription
SpecificationService.valid(value, spec, custom?)Static methodWhether the value satisfies every key of spec.criteria, as described in the note above. Answers false for a missing value.
SpecificationService.invalid(value, spec, custom?)Static methodThe negation of valid.
SpecificationService.getSqlCriteria(spec)Static methodThe criteria as a SQL where body, one key = value per criteria joined with and, quoting everything that is not a number. Values are interpolated as they are, with no escaping, so it must not be fed values that came from a user.
SPECIFICATION_ROOT_KEYConstantThe '$root.' prefix that sends a criteria key to the custom root object.
ISpecificationCustomInterfaceThe third argument of valid and invalid: an optional $root object.
PasswordService.hash(p)Static methodA promise of the unsalted md5 digest of the text. See the warning above.
PasswordService.compare(p, h)Static methodA promise of whether hashing the text yields exactly the given digest.
  • @smartsoft001/models calls the object service from its field decorator to rehydrate classType fields.
  • @smartsoft001/domain-core builds the specifications this package evaluates.
  • @smartsoft001/auth-domain calls PasswordService.compare when it issues a token for the password grant, which is what the warning above is about.
  • @smartsoft001/angular wraps the slug and HTML-stripping services as template pipes, and its form factory turns the identity and postal code validators into form validators for the matching field types.