Release notes v0.0.103

Another big release, another set of release notes. Usually that means there are some breaking changes, as is the case with v0.0.103. This release notes are a bit different tho, we renamed to Compas! Due to that we have a bit more breaking changes than planned. Find a migration guide near the bottom of this doc. Buckle up, and let's get started.

TL;DR

First time this is needed, let's go:

  • Stable generated validators
  • Any type custom validators
  • Default validator errors, removed validatorSetError
  • Add dumpApiStructure
  • Generate sql query builders
Lint config changes

We did some version bumps on the dependencies of @compas/lint-config. This brought a new ESLint rule no-unsafe-optional-chaining. This is now enabled by default.

Stable validator output

Previously, if you introduced a new type somewhere, all anonymous validators would get a different name. This was simply based on an increasing number, where a unique type would get a new number and thus created function names like anonymousValidator3. The new anonymous validator names will look something like anonymousValidator781180217. These names are based on a hash generated from te type. This results in git diffs being easier to read.

Any Validators

We now have support for custom validators for T.any() types. These should return a boolean value, and don't have to worry about nullability. A simple example:

// generate.js

const T = new TypeCreator();
app.add(
  T.any().raw("{ myType: true }").validator("myValidator", {
    javaScript: 'import { myValidator } from "./dist/custom-validators.js";',
    typeScript: 'import { myValidator } from "./src/custom-validators.ts";',
  }),
);
// src/custom-validator.js

type MyType = { myType: true };

function isMyType(value): value is MyType {
  return value?.myType === true;
}

This way you can even reuse you custom validator as a TypeScript type-guard.

Inline validators

To improve performance and reduce call stacks, we introduce inline validators. This is a fully backwards compatible change. For now only a few cases will be inlined, for example any types, booleans, string oneOf and references. There is a trade-off here between re-usability and decreasing callstacks. So we are not sure yet how far this will go and if we should find a better way of doing it.

Simplified strict object validation

In your anonymous-validator file, you will now find all static keys of the objects that are validated in strict mode. Instead of allocating a Set every validation call, we now reuse this set, and do the strict check first. This is inspired by the key checks done in the generated UPDATE and INSERT query partials.

Simplified validation errors

We do not generate validationSetError any more. Instead we have defaults depending on the throwingValidators option. When true, as is the default for isNodeServer, this will throw via AppError.validationError and thus result in a 400 status code in a HTTP request context. When throwingValidators is false, the default for isNode and isBrowser, The returned errors array, will contain plain JavaScript objects.

Removed default of unsafe dumpStructure option

Dump structure served us well as the only way of exposing a structure to the outside world. However, it has some downsides. The outside world does not need to know what the object relations are, or other internal types. So we added dumpApiStructure. This only exposes the routes and types referenced by the routes. As a side effect, this breaks for generated api clients that depend on these types.

When isNodeServer is true, this new settings are in effect as defaults. For package maintainers, you would need to add dumpStructure to the generator options, if it is part of your api surface.

SQL Query builders

The biggest feature of this release is nested sql query builders. It solves a few things in one go:

  • Get nested results from Postgres
  • Transform results in usable JavaScript objects, converting Dates back and removing nulls
  • Includes the old traverse builder

There are still lots of missing features and some limitations:

  • Offset and limit are not supported in nested builders yet
  • Generated transformers don't work with self referencing types, either direct or indirect.
  • Transformers only work correctly nested with a valid builder object. This will most likely always be the case.

Note that we have enabled the transformers for the basic queries as well, and thus you'd be able to directly benefit from it without having to use a nested query.

SQL Where and query builder argument validation

We have removed the custom key check from generated Where partials, and now use full validator features. This may not be completely obvious, but as noted above will result in a AppError.validationError.

SQL Where or support

The last small, but darn useful feature is OR support in the generated Where partials. The new where-objects now accept an $or key with an array of nested where-objects. This allows for any recursing OR and AND combination necessary.

Migration guide

This will be a collection of steps and settings to overcome the breaking changes. Make sure to update the dependencies, that depend on Compas, first. So first any packages, then private services and then the final project that depends on these packages and services. Projects that only use the api client generators, should wait on the respective api's to update.

The first thing is to clean up the current installation:

yarn lbu docker clean

This will remove the Docker containers and volumes created by LBU. Next install the new packages, by replacing @lbu with @compas and changing the versions to 0.0.103. At this point it is a good time tho rename all your imports and script references. For imports, the following replace may work:

  • Replace search: "@lbu/([\w-]+)"
  • Replace value: "@compas/$1"

There are no default generate settings for packages anymore. A good starting point is:

/** @type {GenerateOpts} */
const generateOpts = {
  enabledGroups: ["store"],
  enabledGenerators: ["type", "sql", "validator"],
  throwingValidators: true,
  isNode: true,
  dumpStructure: true,
  dumpPostgres: true,
};

For projects already running in production (not advisable...), recurring jobs should be automatically upgraded. This will be removed in the next feature release. Migrations are a bit trickier and require a manual query:

BEGIN;
UPDATE "migration"
SET
  namespace = '@compas/store'
WHERE
  namespace = '@lbu/store';
COMMIT;

Other than the breaking changes mentioned earlier in this doc, this should be it.

In closing

A big release since a while. This was mostly due to time, features piling up and renaming to Compas. One of the last ones before a v0.1.0. Much work is still needed on writing the docs. And prefer to have this release tested in the wild, before going in beta.