Sunday, August 25, 2024

5.0.0 - Runtime is Funtime

What's Changed

This release is the continutation of the 4.x goal of aligning closer to NodeJS.  Under the cover much of the changes are nominal, but there have been a few substantial sets of features and improvments to the core platform. Beyond NodeJS alignment, there is also a push to defer to ecosystem standard libraries where possible.

Path

The path implementation in the manifest module ensures POSIX-like behavior for all join/resolve/etc. operations.  The challenge was to remember to pull in the proper module, and some of the slight differences beteween the module and what a full path implementation looked like.  This release sees the shift to asking the user to import path from the node standard library, and the framework will swap it out behind the scenes.  This ensures the code looks and feels like it should, while ensuring consistency at the platform level.

Runtime

Previously known as Base, this represented the foundation for which all modules build apon.  Base unfortunately wasn't a clear name for what it should contain, and ended up being a general catch-all for various functionalities.  Going forward, Runtime will act as the layer between the operating system and the various modules.  The goal is to have this module become as small as possible, specifically as NodeJS evolves.  In addition to general renames, quite a few functions were moved closer to where they belong, and a handful of functionality was removed as it was no longer needed. 

Model + Blob - Asset

Moving towards standard behavior, the Asset service acted as a thin wrapper around the Model + Stream services.  Essentialy it was just NodeJS streams + metadata.  That recipe is basically what blobs are.  By moving the Model contract from streams to blobs, the Asset service was no longer necessary.  This work also touched upload, as upload is no longer tied to assets, but just a standard blob/file upload interceptor that is usable by the Rest framework.  Blobs are the new stanard for streams + metadata, and have reduced the coupling between various modules.

Test

The test framework was given an internal overhaul, allowing for some neat new features, and also shoring up quite a few bugs in the process.  The primary new feature is the ability to tag tests via decorators, and to target or exclude certain tests during invocation.  This is helpful for tests that shouldn't be run in CI/CD for example.  Outstanding bugs were also fixed in the unresolved promise detection.  

As an extra feature, suites are now randomized as well when executing.  This helps to normalize resource usage (and speed up processes), as well as uncover any bugs that may have been hidden due to specific ordering of test runs.

Tooling (Compiler + VSCode)

Additional love and attention has been provided to the compiler, resolving a few outstanding bugs, specifically where a kill -1 was able to be invoked (this kills all userland processes).  Additional love was given to how many open connections/processes VSCode has at once.  Other fixes included ensuring a workspace reload properly kills the compiler (we were ending up with multiple).

Pack

The final enhancement was to allow for marking certain dependencies as external to the bundle process which allows for them to be installed via npm post bundling.  The docker support already picks this up and will install them.  This helps to minimize the amount of boilerplate needed for the most trivial of configurations.

Whats Next?

The ecosystem is still waiting on ES Decorators being fully ratified (including parameter decorators), including support for standalone functions. Once all the necessary features are available within Typescript, the framework will overhaul decorator behavior, and allow for isolated functions where it make sense (e.g. DI factory functions, standalone rest routes, caching, etc).

Thursday, February 22, 2024

4.0.0 - Spring Cleaning

What's Changed

This release is aimed at re-evaluating some of the core assumptions for the base of the framework.

The primary modules impacted in this endeavor are, Manifest, Compiler, Base, Pack, and Terminal. In this new world, Base and Terminal have had their scope's reduced, and some core functionality either removed, or migrated to a location that is closer to its primary use case.

NodeJS Alignment

In addition to shuffling code, there was a big push to lean into native NodeJS functionality when applicable. Some of the native functionality was 1-1 (or nearly so), while other areas required rethinking and reworking. As NodeJS has matured, there is more and more of an opportunity to shift ownership of some common patterns into core NodeJS libraries.

Terminal

In theory the role terminal had been playing was aimed at increasing the usability of the command line interface. The implementation relied on some of complexity of terminals that had issues in a cross-platform environment in addition to being really easy to get to an inconsistent state with SIGKILL occurring in the middle of an execution.

To that end terminal has been removed as a core module, but moved to a more specialized module, that has had a dramatic reduction in scope. This reduction comes in the form of only a few methods being available for rendering (and now constrained and better verified), along with outsourcing all color support to chalkjs. The code is smaller, more focused, more reliable, and now ultimately providing a superior user experience.

Manifest

The manifest structure has been overhauled to provide a more logical grouping of common values, and providing increased clarity of which fields belong together. This clarity, also coupled with some stronger assumptions about how the compiler should be working, allowed for a vast reduction in the number of times we need to produce a novel manifest context, which is the core unit of truth for every compilation/execution.

Beyond the context cleanup, internal work was done on module visiting/painting for determining runtime roles, and prod designations for all modules. This once nuanced and delicate piece of code is now far simplified and far more robust. The goal here is simplification of edge cases, and erring on the side of clarity over flexibility.

Compiler

The compiler has received an incredible amount of tlc in this release. Compiler client/server logic has been completely rewritten to centralize ownership to the compiler and to minimize the amount of information needed to integrate at runtime. Additionally types are now being shared appropriately which helps to mitigate the gap between client/server entirely.

The server has also been updated to control the logic around stop/restart/terminate/kill signals to have a repeatable and reliable method for handling shutdown cleanly and being able to recover cleanly when a process is killed immediately. The net result here is a more reliable process that is more resilient against unexpected behaviors.

File Watching

File watching, once again has been updated. Many odd bugs (some were logical, and some were tied to file write behavior of VS Code and how that translates into @parcel/watcher's event stream) have been fixed. Additionally in lieu of watching at each folder to mitigate the number of watchers (but also increased the watch complexity), all watching has been reduced to a single watcher at the repo root. This may have some impact for linux with the inotify limitations, a new field has been added to the `travetto.build` field of the package.json to allow for setting exclusions for watching.

The net-result here is a far more reliable watch experience, and the errant hangs on buffered file writes or git checkouts should be a thing of the past.

VSCode / Tooling Overhaul

In the course of stabilizing and ensuring consistency/repeatability/reliability of all the command line tools and the VS Code functionality, some of the patterns were entirely re-written. There was a non-trivial amount of logic geared towards restarting, checking for healthiness of the underlying tools, capturing odd error states, and the like.

This has all been rewritten to the point of pushing it all of the complexity into the command line tools. The net result here is that every tool invocation within VSCode is far simpler, and no longer has the possibility of runaway process spawning in edge cases. This also provides the benefit that other uses of the tools will now have access to a more robust functional interface allowing all callers to be simpler.

Base

This was a labor of love as base had grown into a hodgepodge of shared utilities that was a little too convenient. This is to say that more focused was needed here along with expectation of logic living where it is being used.

Process execution, environment variable interaction, time utilities, data utilities, stream interactions, shutdown behavior, and startup behavior were all on the chopping block. As stated in the first section, the primary goal here was alignment with standard NodeJS behavior along with migrating code to where it needed to be. The result was a significant change to the core functionality that represented simplicity and clarity, but is definitely not backwards compatible.

Out of all the changes, the Base module rewrite was the primary motivator for the major version release as this impacts far more than just the framework itself.

Pack

Last but not least, Pack was revisited with a unification between the CommonJS and ESM output formats, to allow for a consistent compilation experience, and help to resolve some unexpected bugs that rose during compilation in ESM. Everything is now consistent but this did introduce a behavior change in CommonJS around optional dependencies.

To that end, a new field in the package.json is exposed for defining ignorable modules during pack. This will help to minimize the need of creating a custom rollup entrypoint.

Whats Next?

The ecosystem is still waiting on ES Decorators being fully ratified (including parameter decorators), including support for standalone functions. Once all the necessary features are available within Typescript, the framework will overhaul decorator behavior, and allow for isolated functions where it make sense (e.g. DI factory functions, standalone rest routes, caching, etc).