Release notes v0.0.158

This release shifts the development experience for TS language server users (and sort of forces others to become one). As far as I know, this (#1055) has been the longest open PR that got merged, and even has a predecessor, #461, back in November 2020. So let's get started.

Cli

The Compas cli got a new command: yarn compas init --jscconfig. It (over)writes the jsconfig.json in the project directory. This file will become stricter over time, so keep it updated with Compas releases. This command and file is not necessary when only using the frontend code generators, because they imply an existing Typescript setup. To control the Typescript version used, you should add 'typescript' as a 'devDependency' in you project.

Lint config

Since we are adding Typescript definition files to the project, disable automatic fixing of ESLint in your IDE for .ts and .d.ts files. This can be skipped if your ESLint config supports TypeScript files, the @compas/lint-config ones don't.

Code gen

Types

The first change in the code generators is that we now always create a structure.js file with the used generator options. This is used is 'multi-generate' projects to combine all types in to a single types.d.ts file to avoid type conflicts down the road.

In a single generate project, e.g. only a single app.generate call, only a single addition is necessary. Add the following snippet after your app.generate:

await app.generateTypes({
  outputDirectory: "./types/generated",
  inputPaths: [],
  dumpCompasTypes: true,
});

The dumpCompasTypes option tells Compas to create a type definition file that makes all major exported types from Compas global in the project. By doing it this way you don't have to import types like App like

/**
 * @typedef {import("@compas/stdlib").App} App
 */

in your project.

In a multi generate project things are a bit more complicated. We have to prevent type conflicts of global types. To ensure this we use the dumpStructure option of app.generate and use app.generateTypes to combine all 'structures' in to a single type definition file.

  • Explicitly mention the enabledGenerators in each app.generate call, but don't add the type generator. When type is the only generator, enabledGenerators may be an empty array.
  • Add dupmStructure: true to each app.generate call.
  • Add the above app.generateTypes call, but in inputPaths specify each outputDirectory of your app.generate calls.

This should give you a single type definition file specifying all necessary types.

Validators

Another generator that got a big change is the validator generator. It now collects as many errors as possible before returning an { error } or { value } object. This is combined with a few breaking changes:

  • No TypeScript file support anymore, so the app.generate call will throw if used in combination with useTypescript: true.
  • The throwingValidator option is removed, as validators will never throw but instead return an { error: AppError } object if any validation error occurred.
  • The top-level error key is always validator.error. All other validation keys are now nested in the 'info' object, with keys representing the 'property path' and values containing the specific validation error and any information. A full example looks something like:
    • Type definition:
    const T = new TypeCreator();
    const type = {
      foo: T.bool(),
      bar: T.anyOf().values({ type: "car" }, { type: "bicycle" }),
    };
    
    • Input:
    {
      "bar": {
        "type": "scooter"
      }
    }
    
    • Resulting error:
    {
      "key": "validator.error",
      "info": {
        "$.foo": {
          "key": "validation.boolean.undefined",
          "info": {}
        },
        "$.bar": {
          "key": "validator.anyOf"
        },
        "$.bar.type": [
          {
            "key": "validator.string.oneOf",
            "info": {
              "oneOf": ["car"]
            }
          },
          {
            "key": "validator.string.oneOf",
            "info": {
              "oneOf": ["bicycle"]
            }
          }
        ]
      }
    }
    

Note that for anyOf validators the result may contain an array, since the property path may be the same.

In closing

There is a bunch of improvements & automation to be added, like auto generating documentation from the generated type definitions, writing tests for the type definitions and making the public api surface more strict by adding @private tags. But that's stuff for the future, for now I am back focussing on feature stability and some improvements & additions to existing features.

As always please report missing type definitions and report bugs, thanks!