Skip to content

Releases: C9up/eon

v0.2.11

Choose a tag to compare

@github-actions github-actions released this 19 Sep 08:40

Release 0.2.11: the runtime dependency range changed

reflect-metadata moved from ^0.2 to ^0.2.2 in dependencies, not
devDependencies. A consumer of 0.2.10 can still resolve 0.2.0, and the
tightening exists because that is not enough -- so the published range
is wrong until a version carries the corrected one.

The other work sitting on this branch does not need a release of its
own: devDependencies are not installed by a consumer, the CI is not
published, and scripts/copy-napi.mjs is referenced only by build:napi
with no postinstall hook, so it never runs on a consumer's machine --
its CARGO_TARGET_DIR fix changes where CI finds the binary, not what
ships.

Read the artifact where cargo actually wrote it

The NAPI copy script looked under <package>/target unconditionally. Cargo
writes elsewhere whenever CARGO_TARGET_DIR is set — a shared cache, a CI mount,
a read-only external directory — so the build produced the library and then
failed to find it, or silently copied a stale one from a previous run.

CARGO_TARGET_DIR is honoured now, resolved against the package root when it
is relative, as cargo resolves it. All fourteen scripts had the same line; an
audit reported it in ream-mcp alone.

Verified end to end, not by reading: a real cargo build redirected to a
temporary directory, the artifact copied out of it, and the package suite green
on that binary.

Keep the dev-dependency alignment, drop the workspace: protocol

The internal ranges had been rewritten to workspace:^. That resolves inside
this monorepo and nowhere else: every package CI checks out its own repository
alone and runs pnpm install, where the protocol has no workspace to point at
and fails with ERR_PNPM_WORKSPACE_PKG_NOT_FOUND before a single test runs. The
concrete ranges are back; the dev-dependency bumps that came with the same edit
are kept, and now match what the lockfile already resolved.


Changes since v0.2.10.

v0.2.10

Choose a tag to compare

@github-actions github-actions released this 09 Sep 16:26

fix(provider): bind the named connections in register(), and finish the rollback

eon:<name> was created in boot(), so the container had a different surface
depending on whether the application was assembled to run or to be inspected —
and a boot that failed late left those factories behind, pointing at connections
it had just closed. The names come from the config, which register() can read:
nothing about eon:primary needs a socket to exist. Resolved before boot opens
one, it now says what is missing instead of answering undefined.

The rollback also destroys the connector's process-global handles. shutdown()
already says why closing the individual connections is not enough to let Node
exit; a failed boot skipped that step and the process hung.

fix(provider): give the migration name back on shutdown

MigrationRegistry.register refuses a duplicate name on purpose — two
providers claiming one means one migrates nothing while the run reports success.
Nothing ever removed a source, so a provider that stopped left its name taken
and its runner holding a connection it had already closed: a second boot in the
same process (a hot reload, a test that restarts the app) failed on 'already
registered', and the CLI would have driven a runner pointing at a dead pool.

Released on shutdown and on a failed boot, and only while it is still ours —
two applications can share a process, and the one stopping must not unregister
the survivor's runner because the name matches. A host without unregister
degrades silently rather than failing a shutdown over it.

fix(provider): roll back a boot that failed after the sockets opened

The connect step already rolled back. Everything after it did not: the container
bindings, the module singleton, the migration source. A failure there — a host
whose migration registry refused the source, a services module that would not
import — left the connections open, possibly the singleton published, and
#booted false, so the next attempt opened another set on top of the ones
nobody could reach.

The whole post-connection sequence is now one transaction with a single
rollback. It releases only what THIS attempt created: the module singleton goes
only while it is still the one we published, because two applications can share
a process and the survivor's connection has to stay.

fix(provider): register bindings in register(), and stop a flaky publication gate

register() was empty and every binding was made in boot(), after the sockets
were open. That is not where upstream puts them, and the reason is concrete: a
provider that booted before this one could not resolve eon.compiler at all,
and the container had a different surface depending on the mode the application
was assembled in.

The compiler is pure -- a call into the native compiler, no socket, no database
-- so nothing about it needed to wait for boot. The connection tokens move too,
so the surface no longer depends on the mode; what they RESOLVE to still needs
boot to have opened something, and reading one too early now says what to do
instead of answering undefined and failing several frames away.

Separately: the one test that pulls the whole testing graph through a dynamic
import gets a longer timeout. Under coverage every module in that graph is
instrumented on the way in, which on a loaded machine crossed a 5-second default
the assertion itself is nowhere near -- and made the publication gate fail at
random on a test that has nothing slow about it.


Changes since v0.2.9.

v0.2.9

Choose a tag to compare

@github-actions github-actions released this 06 Sep 15:33

Keep the vendored copies out of this package's coverage floor

src/vendor/** is generated and identical in every package that carries it, so
measuring it here counts the same lines N times and holds this package to a
floor for code it cannot change — which is what pushed several suites under
their thresholds the moment the copies landed.

The behaviour is not left unmeasured: it is pinned where it broke, in bay's
quasar-bridge suite, which now covers both manager shapes the loader has to
accept.

Run cargo with --locked in CI

Without it, cargo rewrites Cargo.lock in place when it has drifted from the
manifests — so CI resolves dependencies fresh and tests a graph nobody
committed, then the release is built from it. The workspace lock had drifted
by 382 lines before the same flag caught it locally.

Every package here commits a Cargo.lock, so --locked is meaningful: it fails
loudly instead of silently updating. Verified against the current lock before
the flag went in.

The Node side is deliberately left alone: these repositories ship no
pnpm-lock.yaml, so --frozen-lockfile has nothing to freeze against, and
resolving from the registry is what a consumer gets anyway.

Take the NAPI platform table from the vendored copy

The map from Node's platform/arch to napi-rs's suffix was repeated here as
well. Adding a target means adding it everywhere, and a package that misses
the edit fails only on that platform.

The table, the path and the require come from scripts/vendor/nativeBinary.ts.
The policy does not: what this package does when the binary is absent, and the
error it raises, stay here — they carry this package's code and its build
instruction, which a shared helper has no business inventing.

Open no socket while the app is only being inspected

warmUp() runs register, boot and start — it is how a route listing, a codegen
pass and inspect assemble an application without running it. boot() opened
every WebSocket there and could CREATE a database through ensureDatabase, so a
read-only command needed TDengine reachable and could change it; shutdown()
never fires on that path, so the sockets stayed open.

The context gained an optional getMode(), and an inspection now stops after
the compiler binding — which is pure, and is exactly what a codegen pass
needs.

Lint this package the way its own repository will

biome's configuration lived only at the workspace root. This package is
built from its own repository, where that file does not exist and biome
falls back to its defaults — so lint in CI has been checking a different
set of rules from lint here, and the bans this project actually cares
about were never enforced where it counts.

The config is now the package's own, and says the same thing the root one
did.

Declare what CI has to install

Each package is its own repository: pnpm install there sees only this
file, so a dependency the workspace happened to hoist locally is simply
absent in CI. --coverage needs @vitest/coverage-v8 named here, and an
optional peer a test imports has to be a devDependency as well — optional
is exactly what keeps it from being installed.

Run the gates the package already declared, and declare the missing one

Three guard-rails were configured and never reached CI:

  • tsconfig.json includes tests, but CI typechecked only
    tsconfig.build.json — every type a test relied on went unchecked.
  • coverage was measured and reported, with no thresholds at all, so
    test:coverage could not fail whatever the suite stopped covering.
  • lint pointed at src/ alone, so no test file was ever linted.

CI now runs pnpm typecheck, coverage, and a lint covering tests/.
The thresholds are a floor set just under what the suite covers today.


Changes since v0.2.8.

v0.2.8

Choose a tag to compare

@github-actions github-actions released this 04 Sep 15:30

Release 0.2.8

Say what container.make() returns for the tokens this package binds

ream declares ContainerBindings open on purpose: it registers its own
entries and expects each package to contribute the ones it owns — its
comment on the interface names auth (warden), logger (spectrum) and db
(atlas) as exactly this. None of them did, and every other package that
binds a string token was in the same state, so container.make('cache'),
make('mail'), make('hash') and the rest all answered unknown and
every call site had to assert a type it could not prove.

Loaded from the barrel AND from the provider, the second of which is where
AdonisJS puts its own (providers/redis_provider.ts carries the
declare module for redis, database_provider.ts for lucid.db).

Verified live rather than assumed: a declare module naming a specifier
that does not resolve is silently inert, so renaming the member has to
break the compile. It does.

Move the NAPI bindings to napi 3

The Rust needed no change; the toolchain did. napi-derive 3 writes one type-def
file per crate into NAPI_TYPE_DEF_TMP_FOLDER and panics outright when it sees
the old single-file TYPE_DEF_TMP_PATH — that variable is how it detects an
out-of-date toolchain, so the failure reads as "upgrade @napi-rs/cli" even
though the generator here is our own.

It also emits a function as a bare function name(...) where 2 emitted the
signature alone, so concatenating the name onto it produced
function xfunction x(...). The generator handles all three shapes now.

napi-build stays at 2 — there is no 3 on crates.io.

Verified by what the migration could break rather than by it compiling: the
generated src/native/generated.ts comes out byte-identical to the napi 2 one,
and the native binary is rebuilt and exercised by the JS suite.

Update the Rust dependencies within their ranges

Everything the existing semver ranges allow, so no manifest changes and no API
surface moves. fmt, strict clippy, tests and advisories all pass.

Turn on noUnusedLocals/noUnusedParameters

Print the test run, not just its JSON

The workflow ran vitest with --reporter=json alone, so a failing job wrote
its report to a file and nothing to the log: an exit code, and not one word
about which test failed. Twice today that meant finding a failure by deduction
instead of reading it.

The default reporter runs alongside; the JSON one still feeds the smoke gate.

Format the Rust crates, and gate it so they stay formatted

Twelve of the thirteen crates had drifted — 949 differences in all, atlas
alone 345, and build.rs files that had never been through the formatter.
None of their workflows checked, so nothing ever said so; the drift only
surfaced when it took a publish job down.

cargo fmt applied throughout, and a cargo fmt --check step added to each
workflow so this cannot happen again. Formatting only: the Rust tests pass
unchanged in every crate.

One spot in atlas needed a real edit rather than the formatter: cargo fmt
rewrote a return Err(format!(…)) arm back to the inline form on every run
while --check kept asking for the block form, so the file could never
converge. The message is bound to a name, which fits the line budget and
settles it.

Ignore a relocated cargo target

target/ matches a directory only. When the build output is moved elsewhere
and a symlink named target is left in its place, that pattern does not catch
it — it shows up untracked, and a stray git add -A commits a path that only
resolves on one machine.


Changes since v0.2.7.

v0.2.7

Choose a tag to compare

@github-actions github-actions released this 31 Aug 13:14

Release 0.2.7

Turn the connector's "NULL" string back into null

@tdengine/websocket 3.5.0 pushes the four characters "NULL" for an empty cell
whatever the column type — taosResult.js:186 for fixed-width columns and :200
for variable-length ones, both row.push("NULL") where the value should be null.
Reproduced against a live server with no eon code in the path; the server's own
REST endpoint returns null on the same rows.

The type is therefore right when the value is present and wrong when it is
absent: a BIGINT reads back as bigint on every row until the first null one,
where it becomes string. A typeof check written against the happy path holds
until the first empty cell, often months later. Through the ORM it was worse
than a wrong type — reviveBigInt could not revive "NULL", so one null cell made
a whole super-table unreadable, and a nullable decimal threw on construction.

Converted back for every type that cannot hold the string. Text and binary
columns are left alone and said so in the docs: there the bitmap that knew the
difference is already consumed, and converting would corrupt a real value to
repair an absent one.


Changes since v0.2.6.

v0.2.6

Choose a tag to compare

@github-actions github-actions released this 31 Aug 12:30

Release 0.2.6

Refuse a decimal on the ingest path that silently corrupts it

ingestMany binds DECIMAL through @tdengine/websocket's stmt2, which stores
unrelated memory instead of the value: '61.99' into DECIMAL(20,10) read back as
-4268664320557975669762473403.2597208778, and a different number on every run.
Measured against a live server, then reproduced through the connector's own API
with no eon code in the path — so there is nothing here to fix, and 3.5.0 is
the newest release there is.

A wrong price that inserts without complaint is the worst outcome available.
That path now refuses, naming the column and pointing at ingestSql, which was
measured to round-trip the digits exactly. ingestSchemaless already refused a
decimal on its own. A super-table without a decimal is untouched.

The live suite pins all three.


Changes since v0.2.5.

v0.2.5

Choose a tag to compare

@github-actions github-actions released this 28 Aug 15:20

Name the test runner helix, and tighten the runner-import guard

/testing must stay runner-agnostic, and the guard listed a foreign
runner's package alongside helix's. There is no foreign runner in this
universe to import, so the alternative goes and the guard keeps saying
what it means: nothing here imports a test runner.

Release the work that landed after the last published version

The registry now carries the version this package.json was still on, so
everything committed since ships under the next one rather than
retroactively changing what a published version means.

Build the cross-platform matrix only when the version moves

The five-runner matrix exists to produce the prebuilt binaries a release
ships. It ran on every push to main, rebuilding artefacts nobody
downloads — five runners, every time, for a comment fix.

A version-gate job now compares the package version at HEAD^ with
the one at HEAD and the matrix runs only when they differ. Anything
that is not a push passes the gate unconditionally, so workflow_dispatch
— how a release is actually cut — is unaffected, and so is publish,
which still waits on the full matrix. A missing HEAD^ reads as a bump:
erring towards building is the safe direction.

The test signal deliberately does NOT move with it. quality and the
cargo/integration jobs were already independent of the matrix, but
vitest ran INSIDE it, so gating the matrix alone would have quietly
taken the TypeScript suite off every ordinary push. A ts-tests job now
runs it on ubuntu, building its own napi binary rather than waiting on a
gated artefact. On a push without a bump that leaves typecheck, lint,
cargo and vitest — one runner instead of five.

Accept secondary TIMESTAMP columns — TDengine does

validate_stable and compile_create_table rejected a second TIMESTAMP
column with E_TS_DUPLICATE. TDengine has no such rule. Its parser
checks the TYPE of column 0 (TSDB_CODE_PAR_INVALID_FIRST_COLUMN) and,
past that, rejects only JSON — there is no counter and no error code for
a second timestamp. Its own test suite creates
create table stb_ts (ts timestamp, c0 timestamp, c1 timestamp).

The rule the check was reaching for is "the first column is a
TIMESTAMP", which is the row key, and that check was already there and
stays. What it actually enforced was "at most one", which blocks the
normal shape of a dated fact carrying secondary dates: a dividend keyed
on its ex-date also has declaration, record and payment dates. Those had
to be stored as VARCHAR(10) — lossless for a calendar date, but the type
and every date function on the column are gone.

Not modelled, and worth knowing before someone reaches for it: TDengine
3.3's COMPOSITE KEY marks a SECOND primary-key column, which must sit
at index 1 and be an integer or VARCHAR — never a TIMESTAMP.


Changes since v0.2.4.

v0.2.4

Choose a tag to compare

@github-actions github-actions released this 28 Aug 12:50

Derive the native TypeScript surface from the Rust

The hand-written interface describing the .node binary was a second
description of the same thing, and nothing on this side noticed when the
first one changed: a pub fn could gain a parameter, stop being async
or change its return with the declaration still claiming otherwise.

napi-derive can emit the declarations itself. Its type-def feature
writes one JSON line per #[napi] item while cargo compiles;
scripts/build-napi-types.mjs collects them and
scripts/generate-napi-types.mjs turns them into
src/native/generated.ts. build:napi regenerates it, and the
TypeScript side consumes it instead of restating it.

Three things the generation had to handle:

  • type-def APPENDS to its output file, so a parallel cargo build
    interleaves the writes and definitions go missing, silently, leaving
    the generated file short. Crates are built one at a time, and a crate
    that emits nothing fails the script rather than producing a partial
    surface.
  • A Rust doc example holding a cron expression (0 */5 * * *) closes
    the generated comment early. Every */ is escaped except the one
    that legitimately closes the block — escaping that one breaks the
    file just as thoroughly.
  • The driver is Node rather than bash, because build:napi also runs
    on the Windows prebuild runner: a Git Bash mktemp path is not
    something the native proc-macro can write to, and the type-def file
    would come back empty with nothing to explain why.

The generated file is a .ts holding only ambient declarations rather
than a .d.ts, so tsc carries it into dist/native/ and the
reference from the emitted declarations still resolves for consumers.

Create the GitHub release from the publish workflow

A published version arrived with no notes: npm showed a number, GitHub showed
nothing, and the only way to learn what changed was to read a diff. The commit
messages already carry the reasoning, so the release is built from the commits
the tag contains rather than written twice.

Skips a pure version bump, leaves an existing release alone, and does nothing
when the run was not built from a tag. The job takes contents:write for this;
the workflow default stays read.


Changes since v0.2.3.

eon v0.2.3

Choose a tag to compare

@kaen25 kaen25 released this 28 Aug 08:39

eon 0.2.3

Say what KEEP does where the database is created

TDengine drops rows older than the database's KEEP window, and the server
default is 3650 days. Backfill more history than that and the rows are refused
one by one with "Timestamp data out of range" — at write time, long after the
database was created, with nothing pointing back at KEEP. It reads like a data
problem and is not.

ensureDatabase is where KEEP is set, so it is where that belongs; the option
itself and the config stub say it too.

34b2a83


Changes since v0.2.2.