Skip to content

build(deps): bump @antelopejs/tooling-configs to 0.5.0 - #152

Merged
Upd4ting merged 3 commits into
mainfrom
build/bump-tooling-configs
Nov 10, 2026
Merged

Upd4ting merged 3 commits into
mainfrom
build/bump-tooling-configs

Conversation

@Upd4ting

Copy link
Copy Markdown
Member

Why

Put dms-database on the latest published @antelopejs/tooling-configs (0.5.0) and make it pass cleanly. It also clears the two notes that tooling-configs#282 turns into sync --check failures in the next release: skipLibCheck and a release that skips typecheck. ANT-1760, split from ANT-1694.

What changes

  • @antelopejs/tooling-configs goes from ^0.3.3 to ^0.5.0, and pnpm run sync updated package.json (prepare), .agents/setup and AGENTS.md. antelope-lock/@antelopejs/dms@0.19.1 is what pnpm test recorded.
  • 0.5.0 adds antelopejs/no-unsafe-object-assign. It flagged the Object.assign that wrote decoded stages over the protected stages field of a staged value in src/service/stagedSerialization.ts. The decoder now passes each stage through the StagedObject constructor instead.
    • Before, the query console accepted { "__cls": "ValueProxy", "stages": [] } as the predicate of a delete or update. It now refuses that body with a 400. The new FORGED_BODIES entry in src/test/query-console.test.ts fails on main with Missing expected rejection and passes here.
    • For every non-empty stages, the decoded value has the same prototype, own keys and stages as before. A reviewer checked this with a probe over all 9 classes.
  • Two JSDoc casts in the verify skill's journeys (extending.mjs, parts/query-wire.mjs) became a narrowed const and a @type annotation. Before, anti-slop/require-safety-comment-for-type-assertion flagged them, once more through the .claude/skills symlink.
  • skipLibCheck is removed from frontend-vue/tsconfig.json and from the verify playground's tsconfig.build.json. Both still type-check.
  • .release-it.json runs pnpm typecheck first in before:init.

No suppression was added.

Proof

Before: main with only the bump to 0.5.0

antelopejs-sync --check:

Note: package.json: script "release" does not run "typecheck", so a release can publish a build that skipped the lib check. Add `pnpm run typecheck` to "release" before it builds or publishes, or to a release-it `before:init` hook.
Drift: package.json; run antelopejs-sync
Drift: .agents/setup; run antelopejs-sync
Drift: AGENTS.md; run antelopejs-sync

pnpm run checks (node module-type warnings removed):

> @antelopejs/dms-database@0.4.3 checks /home/debian/.t3/worktrees/dms-database/build-bump-tooling-configs
> antelopejs-checks
[lint]
> @antelopejs/dms-database@0.4.3 lint /home/debian/.t3/worktrees/dms-database/build-bump-tooling-configs
> oxlint && pnpm --dir frontend-vue lint && node scripts/check-sources.mts && antelopejs-check-interface-ranges && node node_modules/@antelopejs/dms/frontend-vue/scripts/check-api-error-presentation.mjs frontend-vue/app
.agents/skills/verify-dms-database/journeys/parts/query-wire.mjs:10:24: error anti-slop(require-safety-comment-for-type-assertion): This type assertion has no `SAFETY:` justification. State the checked invariant immediately before the assertion or its containing statement.
.agents/skills/verify-dms-database/journeys/extending.mjs:32:174: error anti-slop(require-safety-comment-for-type-assertion): This type assertion has no `SAFETY:` justification. State the checked invariant immediately before the assertion or its containing statement.
.claude/skills/verify-dms-database/journeys/extending.mjs:32:174: error anti-slop(require-safety-comment-for-type-assertion): This type assertion has no `SAFETY:` justification. State the checked invariant immediately before the assertion or its containing statement.
.claude/skills/verify-dms-database/journeys/parts/query-wire.mjs:10:24: error anti-slop(require-safety-comment-for-type-assertion): This type assertion has no `SAFETY:` justification. State the checked invariant immediately before the assertion or its containing statement.
src/service/stagedSerialization.ts:107:36: error antelopejs(no-unsafe-object-assign): Object.assign writes a value over `stages` that the target's `stages` type does not accept, and TypeScript never compares the two: `Object.assign<T, U>` returns `T & U`. Assign the field directly so the compiler checks it.
 ELIFECYCLE  Command failed with exit code 1.
[sync:check]
> @antelopejs/dms-database@0.4.3 sync:check /home/debian/.t3/worktrees/dms-database/build-bump-tooling-configs
> antelopejs-sync --check
Note: package.json: script "release" does not run "typecheck", so a release can publish a build that skipped the lib check. Add `pnpm run typecheck` to "release" before it builds or publishes, or to a release-it `before:init` hook.
Drift: package.json; run antelopejs-sync
Drift: .agents/setup; run antelopejs-sync
Drift: AGENTS.md; run antelopejs-sync
 ELIFECYCLE  Command failed with exit code 1.
Checks: 9 passed, 2 failed.
 ELIFECYCLE  Command failed with exit code 1.

After: this branch

antelopejs-sync --check:

Guardrails are in sync.

pnpm run checks:

> @antelopejs/dms-database@0.4.3 checks /home/debian/.t3/worktrees/dms-database/build-bump-tooling-configs
> antelopejs-checks
Checks: 11 passed, 0 failed.

Vue jobs, run separately:

pnpm test:layer      Test Files 19 passed (19), Tests 166 passed (166)
pnpm test:frontend   Verified 2 modules

The new forged body on main's decoder (ajs module test . -f dist/test/query-console.test.js):

  1) [integration] query console: malformed bodies
       refuses a malformed predicate instead of writing every row:
     AssertionError [ERR_ASSERTION]: Missing expected rejection.

and on this branch:

  [integration] query console: malformed bodies
    ✔ refuses a malformed predicate instead of writing every row
  4 passing

Verify layer

dms-control plan --base origin/main maps the diff to smoke, editing and package, with nothing unmapped. All 22 journeys passed: smoke 1/1, editing 18/18, package 3/3.

flowchart LR
  B[query body JSON] --> G{__cls known and every stage valid?}
  G -- no --> R[400 StagedDecodeError]
  G -- yes --> S[decode each stage]
  S --> C["reduce: Reflect.construct(StagedObject, [stage, previous], cls)"]
  C --> I{instance of StagedObject?}
  I -- "no (zero stages)" --> R
  I -- yes --> D[driver]
Loading

tooling-configs 0.5.0 adds antelopejs/no-unsafe-object-assign, which flagged the Object.assign over the protected stages field. Each stage now goes through the StagedObject constructor, so a staged value with no stage is refused with a 400 instead of reaching the driver. The verify journeys drop their two type assertions.
@Upd4ting
Upd4ting merged commit 274dd93 into main Nov 10, 2026
10 checks passed
@Upd4ting
Upd4ting deleted the build/bump-tooling-configs branch November 10, 2026 15:40
Upd4ting added a commit that referenced this pull request Nov 10, 2026
…#153)

## Why

The DMS database console should stay fast on schemas with one instance
per tenant. The Schemas page, the Overview, the health card, the data
browser's table list and the row references all read
`listSchemaSummaries()`. It infers each table's columns from a 5-row
sample, cached for 30 s. When a table's default instance held no row,
`sampleAnyInstance` probed the named instances one query at a time until
one answered rows. A table empty in every instance paid one round trip
per tenant, on every cache miss.

## Change

`src/service/introspect.ts`: when the default instance holds no row,
read the 5-row sample once through `CROSS_INSTANCE` instead of looping
over `listSchemaInstances()`. On MongoDB a cross-instance read adds no
`$match`, so it is one `aggregate` with `$limit: 5`.

## Workload

Two isolated DMS runs (`dms-control up`), each with its own `mongod`: A
on `origin/main`, B on this branch. The playground's `links` schema has
five tables, empty in every instance. Its named instances were raised to
3, 50 and 500 by rows in `__antelope_instances`. Each sample is one cold
`GET /api/database/schemas/` after the 30 s caches expired. 7 samples
per side, interleaved, with the order alternating each round. mongod's
profiler counted the queries on `links__*` in each request.

## Evidence

Confirmed in an end-to-end benchmark.

| Named instances | Before (median, range) | After (median, range) |
Queries on `links` |
| --- | --- | --- | --- |
| 3 | 39.7 ms (37.0 to 56.5) | 44.5 ms (38.1 to 97.9) | 25 → 15 |
| 50 | 70.9 ms (59.9 to 128.2) | 33.7 ms (29.6 to 45.3) | 260 → 15 |
| 500 | 340.5 ms (335.3 to 533.4) | 34.9 ms (30.9 to 52.6) | 1,241 → 15
|

Primary number: a cold Schemas request at 500 named instances goes from
340.5 ms to 34.9 ms, 90% faster. At 3 instances the two sides overlap,
so normal-size schemas see no measurable difference.

- **Limiter.** Node was idle. The cost is serial round trips: 1,226
fewer queries at about 0.25 ms each comes to about 306 ms, and the
measured saving is 305 ms.
- **Errors.** 0 errors on both sides. The inferred fields of all 19
playground schemas match between A and B, `shop` included, whose rows
live only in its `eu` and `us` instances.

## Impact

One cold request every 30 s for whoever opens a Schemas, Overview or
data-browser page, per schema with named instances. On a 500-tenant
schema that is about 0.3 s saved each time, and 1,226 fewer queries
against the database. The impact is low, but it scales with the tenant
count.

## Behaviour note

The sample now spans tenants instead of coming from the first tenant, in
creation order, that holds rows. Both are 5-row samples. When tenants
store different types in one column, or a column only some tenants fill,
the inferred type can differ. One case is a column typed `number` by
tenant A's rows and `string` by tenant B's. The design note
(`docs/design/redesign-v2-grill.md`) says only that a named instance is
sampled when the default one is empty. Keeping the exact old rule would
take an ordered cross-instance read, which sorts the whole table on
every cold request.

## Test

`src/test/schemas-page.test.ts` adds a tenanted schema whose only row is
in a named instance. The test asserts its `amount` column is typed
`number`. It fails when sampling reads only the default instance:

```
1) types the columns of a table whose rows are all in a named instance
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
```

It passes on this branch, and `pnpm run checks` passes (`Checks: 8
passed, 0 failed.`).

```mermaid
flowchart LR
  S[sampleAnyInstance] --> D{default instance has rows?}
  D -- yes --> R[use them]
  D -- no, before --> L[for each named instance: one query] --> R
  D -- no, after --> X[one CROSS_INSTANCE slice 0..5] --> R
```

## Readability

The function loses its loop and its call to `listSchemaInstances`. The
diff is 5 lines added and 9 removed.

No overlap with #152, which changes
`src/service/stagedSerialization.ts`.

## Verify skill

`verify-dms-database` on `ae8afb3`, with the features `plan` lists. The
verdict is `PASS+NOTES`.

| Feature | Passed | Failed |
| --- | --- | --- |
| smoke | 1 | 0 |
| extending | 3 | 0 |
| schemas | 22 | 0 |

The schemas check that a table whose rows live in named instances
(`shop.order_items`) lists its sampled columns still passes. Its wording
described the old one-instance rule, so `a50a4b0` rewords it.
Upd4ting added a commit that referenced this pull request Nov 10, 2026
)

## Why

The DMS database console should show a query's result without freezing
the page. A query with no `slice` on a table of more than 1,000 rows,
such as `schemas.metrics.instance().table("readings")`, returns the
console's 1,000-row cap. When the result is browsable, each row renders
its "Open in data browser" link as a Nuxt UI `UButton`. Each of those
buttons resolves its own variant classes and mounts a `ULink`, a
`ULinkBase` and a `UIcon`. Mounting 1,000 of them took most of the
render.

## Change

`frontend-vue/app/components/QueryResultPanel.vue`: the row link is now
a `<ULink raw>` with the classes the `UButton` resolved to, wrapping one
`<UIcon>`. It keeps the same role, accessible name, title and `href`. It
still navigates in-app through `ULink`, and still shows only on row
hover or keyboard focus.

## Workload

The production dashboard build of an isolated DMS run (`dms-control
up`). Chromium is headless at 1280×900 with 4× CPU throttling
(`Emulation.setCPUThrottlingRate`). It runs
`schemas.metrics.instance().table("readings").slice(0, 1000)` with
Ctrl+Enter, after a 1-row query, so the table mounts fresh each time.
Timing runs from the keypress to the paint after 1,000 rows exist, with
long tasks read from a `PerformanceObserver`. Base and change ran as two
separate DMS runs: 3 rounds of 5 runs per side, interleaved.

## Evidence

Confirmed in an end-to-end browser benchmark.

| 1,000-row result, 4× CPU | Before (median, range, n=15) | After
(median, range, n=15) |
| --- | --- | --- |
| Result visible | 5,255 ms (4,546 to 6,740) | 2,252 ms (2,030 to 5,893)
|
| Long tasks | 5,136 ms | 2,232 ms |
| Longest task | 3,657 ms | 1,195 ms |

Primary number: a 1,000-row result is visible after 2.25 s instead of
5.26 s, 57% faster on a mid-range laptop profile.

- **Limiter.** The same 1,000 rows with `.without("_id")`, which renders
no row links, show in about 1.1 s. The rest of the remaining time is the
per-row `ULink` and the table cells.
- **Variance.** Every round's medians are apart: before 5,488, 5,822 and
4,725 ms, after 2,979, 2,234 and 2,175 ms.
- **When this was measured.** The timed commit was `ce0901f`. After
review, the change added two classes to the same element (`text-xs
active:scale-[0.98]`), with no new component or node.

## Impact

Anyone who runs a broad query in the console. The longest frozen stretch
drops from 3.7 s to 1.2 s at 4× throttling, about 0.9 s to 0.3 s on an
unthrottled machine. The impact is medium. It happens on every query
that hits the 1,000-row cap.

## Proof the link is unchanged

The `UButton` link (base `9260525`) and this branch (`be8f22b`) were
compared in a real browser on `shop.products` and `shop.customers`
results:

```
rest      24×24, radius 6px, icon 14×14, font 12px, opacity 0
row hover opacity 1
hover     rgb(11,17,20) on rgb(238,241,243)
pressed   23.52×23.52 (scale 0.98)
focus     outline 3px solid, opacity 1, reached with Tab
href      /modules/database/data?schema=shop&table=products&match=_id%3Asku_lamp
Enter     reaches the data browser with no full reload (window marker kept)
```

Rows with no `_id`, and a result that is not browsable, render no link
in either build.

- `pnpm run checks`: `Checks: 8 passed, 0 failed.`
- `pnpm test:layer`: `19 passed (19)` files, `166 passed (166)` tests.
- `pnpm test:frontend`: `Verified 2 modules`.

```mermaid
flowchart LR
  R[result row] --> B{browsable and has _id?}
  B -- before --> U[UButton: ULink + ULinkBase + UIcon + own variant classes]
  B -- after --> L[ULink raw + UIcon, fixed classes]
```

## Readability

The template names the link's classes instead of the button props
`color`, `variant` and `size`. They are the classes the DMS button theme
resolved to for a ghost, neutral, xs, square button, so a theme change
to that button no longer reaches this link.

No overlap with #152.

## Verify skill

`verify-dms-database` on `be8f22b`, with the features `plan` lists:

| Feature | Passed | Failed |
| --- | --- | --- |
| smoke | 1 | 0 |
| query | 15 | 0 |
| guard | 6 | 1 |

`open-in-data-browser-opens-the-target-table-or-the-row` passes. It
covers a result row's own link opening the data browser filtered on that
row.

The verdict reads `FAIL` because of one guard journey,
`a-replace-on-a-selection-changes-every-row-the-dry-run-announced`. It
is the known product finding in
`.agents/skills/verify-dms-database/features/guard.md`:
`@antelopejs/mongodb` 1.6.2 runs a selection replace as
`findOneAndReplace`. The journey fails on `main` as well. It counts
against this PR only because `QueryResultPanel.vue` is under the guard
feature's globs. This diff does not touch the replace path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant