Release notes v0.0.79

The first time we actually have release notes. I guess it is a start in making LBU a bit more mature. Remember when you had to search through all PR's cause something was broken in a new release? Yea, I do ;P Let's get to the juicy details.

Chores

We have updated Postgres twice to a new minor versions of 2.0.0-beta. Although the package doesn't have clear goal for what the 2.0.0 release should contain, we don't have to step up there or look for alternatives yet. Other than that, some minor things like automatically linking these release notes in the generated changelog, switching the default branch to main and fixing up the dependabot config.

Cli

In our test runner we now warn when a subtest uses an assertion on the parent t. This prevents confusion when a failing assertion is printed under the parent, instead of the subtest.

import { mainTestFn, test } from "@compas/cli";

mainTestFn(import.meta);

test("My test", (t) => {
  t.test("sub test", () => {
    // warning: called 't.ok' on parent 't'. Accept 't' as argument in the callback of 't.test(msg, callback)'.
    t.ok(true);
  });
});

This caught a single usage in the lbu codebase here .

Code gen

Since we actively used the open-api importer for the first time, @tjonger found and fixed various bugs in there. Notably, resolving references in path and query parameters and creating a name from the path if operationId is not available.

Other than that we have two new features, soft deletes for sql generation and cancel token support in apiClient and react-query hook generation. Let's start with the latter.

We added support for request cancellation with Axios. To minimize the differences between the api client generated when isNode is used versus isBrowser, most of the logic is done in the generated hooks. We accept the property cancelToken on the options argument, which is the QueryConfig provided to useQuery. If provided, the cancelToken#token is passed through to the generated api client function, and the cancelToken#cancel function is added to the returned Promise. This is behaviour is mostly in line with react-query documentation.

import { CancelToken, CancelTokenSource } from "axios";

export const MyComponent = () => {
  const [count, setCount] = useState(0);
  // I don't know React, please provide better sample :S
  const cancelToken = useMemo(() => CancelToken.source(), [count]);

  // Expensive operation
  const { data: imageBlob } = useAppConvertCountToImage(
    { count },
    { cancelToken },
  );

  return (
    <div>
      <Button onClick={() => setCount(count + 1)} />
      <img src={URL.createObjectURL(imageBlob)} />
    </div>
  );
};

The other big thing is soft delete support in sql generation. This can be enabled by passing in withSoftDeletes: true in enableQueries calls. This takes over the old withHistory, and immediately removed support. Soft deletes add a deletedAt column. All generated where arguments by default won't return 'deleted' records. This can be enabled by providing { deletedAtInclude: true }. To permanently delete a record, groupQueries.tableDeletePermanent is generated as well.

// In generate
const T = new TypeCreator();
app.add(
  T.object("myValue")
    .keys({
      id: T.uuid().primary(),
      count: T.number(),
    })
    .enableQueries({ withSoftDeletes: true }),
);

// Insert a record
const [myValue] = await appQueries.myValueInsert(sql, { count: 5 });

const firstCount = await appQueries.myValueCount(sql);
// firstCount === 1

// Delete our `myValue`
await appQueries.myValueDelete(sql, { id: myValue.id });

const secondCount = await appQueries.myValueCount(sql);
// secondCount === 0, deleted records are not included

const countWithDeleted = await appQueries.myValueCount(sql, {
  deletedAtInclude: true,
});
// countWithDeleted === 1

// Remove record completely
await appQueries.myValueDeletePermanent(sql, { id: myValue.id });

Note that cascading soft deletes is not included in this release. The generated DDL, enabled by app.generate({ ..., dumpPostgres: true }) does not yet interact with withSoftDeletes. For an example of how to use it manually see the open issue

Store

This package seems to have the most breaking changes. We renamed all table names to use singular nouns:

  • migrations -> migration
  • fileStore -> file -> storeQueries.fileSelect
  • sessionStore -> session -> storeQueries.sessionSelect
  • jobQueue -> job -> storeQueries.jobSelect

File now uses the withSoftDeletes instead of withHistory. This means that the syncDeletedFiles function will only clean up 'permanently' deleted files. We also renamed file#filename to file#name.

getMigrationsToBeApplied from the migration part of @lbu/store, will also return a list of migrations that have changed since they are applied to the database. This allows you to communicate to other developers that some migrations have changed and that they may need to do a full database reset.

In closing

As it is the first time doing this, I did not expect it to take this much time. Any constructive feedback to the writing style, background information provided or other questions are welcome.

That's all!

Only 14 days vacation left 🎃