Skip to content

Commit 75cada0

Browse files
test(runtime): keep module loads out of three slow test files' clocked windows (runtime row of #23181) (#23204)
Part of #23181 Clause-②: no The `@objectstack/runtime` row of #23181 (slice 1): three test files the hourly full battery read over 10 s of wall. This PR edits those three test files only: no runtime source, no vitest config, no timeout value, no test removed or moved. ## Rows | file | card's CI reading | this box, isolated, median of 5 (before to after) | this box, whole-package run (before to after) | |:--|--:|--:|--:| | `src/declared-capability-absence-warn-once.test.ts` | 19.4 s (`38325245250`) | 23145 ms to **101 ms** | 10990 ms to **91 ms** | | `src/stored-metadata-reader-contexts.pin.test.ts` | 20.7 s (`38314183560`) | 5756 ms to **5085 ms** | 7241 ms to 8207 ms (one run each, see notes) | | `src/stored-metadata-body-boundary.pin.test.ts` | 13.7 s (`38210849670`) | 4976 ms to **4149 ms** | 6012 ms to 7085 ms (one run each, see notes) | - **warn-once: this PR completes that file's row.** Its clocked work goes from seven module-graph evaluations to none (margin to 10 s: about 9.9 s on this box, isolated and in the whole-package run). - **The two pin files: this PR removes the one test-side cost found and does not claim the rows.** Both were already under 10 s on this box before the change (isolated and in the whole-package run); their CI readings are the slowest of 20 contended hourly runs. What remains in their clocked windows is CPU-bound boot and sandbox work (measured below), not a sleep, retry or poll. The next hourly full battery's timing refresh (`shard-timings-refresh.yml`, `measure-test-shard-timings --diff`) reads all three; if it names a pin file again, the remaining cost is outside the test (Acceptance notes). "Isolated" is the file run alone, five times, with the agent-detection variables unset so vitest picks the `default` reporter CI reads; the number is the file line's `result.duration` (hooks plus tests, collection excluded). The pin files' before and after runs were interleaved (base, after, base, after), with the base bytes run from an untracked copy byte-identical to the merge base's blob (`9423d04d44` for body-boundary). Every after run sat below every before run. This is a shared 4-core box, so read the ratios as well as the absolutes. ## Where the time went, and the change ### `declared-capability-absence-warn-once.test.ts` - **Before:** each of the 7 tests called `vi.resetModules()` and re-imported the dispatcher and `sendError`, re-evaluating the same **733 modules** (`spec/src` 422, `core/src` 93, `platform-objects/src` 71, `runtime/src` 64, `rest/src` 24, `types/src` 22, plus the inlined `objectql` and `metadata-protocol` dist bundles): about 0.9 s per test isolated, 1.1 to 1.4 s per test in the whole-package run. In isolation the first test also paid the graph's cold transform inside its clock (17.0 s; vitest `transform` 15.5 to 17.0 s). - **Change:** one module graph, imported at module top so the transform is paid during collection (the clocked-window rule, `scripts/check-test-source-alias.mjs`). The per-test isolation the reset bought is kept by the route key instead. The dedupe registry is keyed on `METHOD` plus the mounted route pattern (parked by `observability/instrument.ts`), so each `boot()` mounts the dispatcher under its own prefix (`/t1/api/v1`, `/t2/api/v1`, and so on). Every key a test touches is one the registry has never reported, the same precondition a fresh process gives those keys. Handler lookups go through `route('GET /notifications')`; asserted paths through `path('/notifications')`. - Whole-file wall with collection included also fell: vitest `Duration` 22.2 to 23.5 s before, 17.4 to 19.7 s after (isolated). ### `stored-metadata-reader-contexts.pin.test.ts` and `stored-metadata-body-boundary.pin.test.ts` - **Where it goes:** one boot per file in `beforeAll` (already shared), then the tests. On the base bytes of the reader file (3 runs), `beforeAll` took 2.7 to 3.1 s wall against 3.1 to 3.4 s CPU, and the tests 2.6 to 2.9 s wall against 5.5 to 6.2 s CPU. CPU at or above wall in both windows means no idle wait to remove. `bootstrap()` is about 2.0 s, of which the objectql plugin's `start()` is 0.54 to 0.58 s and init/ready the rest. The two `waitFor` polls (100 ms interval, for a `/meta`-authored action or hook to bind through the event-driven resync) settle in 153 to 274 ms per test, and in 826 ms for the body-boundary file's hook bind, whose probe runs three hook bodies per attempt. - **The test-side cost found:** `bootstrap()` first-evaluates two workspace modules inside `beforeAll`, `driver-sqlite-wasm/dist` and `driver-sql/dist`, reached through `service-datasource`'s lazy `import()`. These are the transform-queue-exposed kind of load the clocked-window rule is about. Every other first load in the boot is a native external (better-auth, knex, sql.js, nanoid), and better-auth is not a runtime dependency, so a module-top import of it would be a phantom dependency. - **Change:** module-top `import '@objectstack/driver-sqlite-wasm'` and `import '@objectstack/driver-sql'`, the shape `datasource-visibility.test.ts` already carries. Both pairs were already registered for runtime in `KNOWN_UNALIASED_TEST_IMPORTS`. Measured: `bootstrap()` 2.0 to 2.1 s became 1.5 to 1.8 s in a probe; the files went 12% and 17% faster isolated. ## Every assertion kept - All three files: `it`/`describe` title lines are hash-identical at the merge base and head (reader 21 `it` sites / 27 tests, body-boundary 5 / 7, warn-once 7 / 7). The two pin files gained two import lines and a comment; no test body changed. - warn-once: 25 `expect(` calls before and after, and each test proves the same thing: - `reports a declared 501 ONCE across N requests, at warn, naming the missing service`: five requests to a route the registry never saw, one `warn`, zero `error`. The asserted path is this boot's mounted pattern. - `a SECOND, DIFFERENT route still reports — the dedupe key is the route`: two never-seen routes, one line each, same code and prose. - `an UNDECLARED 500 on the same door is still loud, once per request`: three faults, three `error` lines, no `warn`. - `a declared absence and a fault coexist in one process without either changing the other`: one `warn` and two `error` on one mount. - `the envelope the dispatcher answers is classified identically by the REST envelope writer`: the dispatcher and `sendError` still share one `@objectstack/types` registry (one module graph), and both lines name the service and the suppression. - `answers the same bytes it answered before this change, on every request`: request 1 is still the reported occurrence on a never-seen route, so the first-versus-suppressed byte comparison still holds. - `answers the same bytes for a fault, too`: unchanged. ## One-shot proofs (run from the committed state, not kept as tests) Both went through `scripts/ablation-replace.mjs` (anchor hit counted, blob hash moved, restore proven by the head blob hash and an empty `git diff HEAD`), against the warn-once file at `8d5fab4bdc`: 1. **Isolation removed** (every boot on the shared `/api/v1` prefix): 3 failed, 4 passed. `a SECOND, DIFFERENT route…` got 1 line where 2 were expected, `…coexist…` 0 where 1 was, and `the envelope…` (door 1) 0 where 1 was. That is the order-dependence the reset used to prevent, so the prefix is load-bearing. Blob `a643630be43d` to `e35018939f4d`, restored to `a643630be43d`. 2. **Dedupe removed** (`packages/types/src/server-fault-log.ts`, the `has(key)` early return; aliased to source for this suite, so no build leg): 3 failed. `reports … ONCE…` got 5 lines where 1 was expected, `a SECOND…` 4 where 2 were, and `…coexist…` 2 where 1 was. The file still pins the dedupe itself. Blob `7ed86cc374e8` to `758f68960025`, restored. ## Verification at `8d5fab4bdc` (branch merged with `main` at `d4aaeaf0e2`) - `pnpm --filter @objectstack/runtime test`: `Test Files 364 passed (364)`, `Tests 5178 passed | 19 skipped (5197)`, lock verdict `command-exit 0`. - `pnpm --filter @objectstack/runtime typecheck`: exit 0. `tsc --noEmit` is clean, and `check:test-typecheck: OK`. The three files are in the `tsconfig.test.json` program (3 of 367 runtime test files) and carry no entry in `test-typecheck-debt.json`. - Lint, narrowed to the touched files with its proof: - eslint, invoked as `pnpm lint` invokes it with `--no-inline-config --format json`, over the 3 files: 3 files, 0 errors, 0 warnings, 0 fatal. - `--print-config` resolves each file to an active config (not ignored). - `eslint.config.mjs` enables no type-aware linting (no `parserOptions.project`, no typed rules; its own comment says so), so this diff cannot move any untouched file's verdict. - `node scripts/pm/dispatch-gates.mjs --commands`: 50 commands, run as printed with exit codes captured before any pipe. 49 exit 0 with their own pass lines. `pnpm check:dual-build-cjs-loads` first answered `PREREQUISITE NOT MET` (exit 3: 38 packages had no `dist/`). After a full build (74/74 tasks), the same command answered exit 0: 108 require entry points across 67 packages load. `--ran`: 50 derived, 50 run, 0 NOT-MEASURED, 0 UNRUN. *(Corrected by the seat from the dev's report; the PR body had been written before that run.)* - No changeset: runtime's `files` is `dist`, `README.md`, `CHANGELOG.md`. A string unique to this change has 0 hits in them; a control string from `dispatcher-plugin.ts` has 1 hit in `dist/index.js`. Label `skip-changeset`. ## Acceptance notes - **The pin files' residual, if a refresh names them again.** Their clocked work is the composed boot (about 1.5 s after this change) plus one sandboxed body per assertion. The sandbox instantiates a fresh WASM module per invocation by design (ADR-0102 D2), which shows up as about 2x CPU over wall in the test window. A 4-vCPU shard running `turbo --concurrency=4` multiplies exactly that. The card's readings sit 2.3x to 2.9x over this box's whole-package run. Neither lever is a fixed delay or poll the test waits on, so neither is in this slice: a cheaper boot, or reusing compiled WASM across invocations while keeping separate instances. Observation only; carrier: none. - **Whole-package before/after for the pin files is not an A/B.** It is one run each, on different trees (merge base versus merged `main`), on a shared box (the after run queued 187 s behind sibling work), and with a changed file order (vitest schedules by previous duration, and warn-once is no longer the slowest file). The module-top imports cannot add clocked time, since they move evaluation into collection. The interleaved isolated runs are the comparison. - **warn-once's `vi.setConfig({ testTimeout: 30_000 })`** was raised for the per-test reload this PR removes. It is left as it stands, because this slice's claim excludes timeout values. Its comment now says why it was raised, so whoever next owns the file can drop it. - `@objectstack/runtime` CI reads (`Test Core`) will be the first contended reading of the new shapes. The hourly timing refresh is the pin. --- _Generated by [Claude Code](https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1912af4 commit 75cada0

3 files changed

Lines changed: 92 additions & 48 deletions

File tree

‎packages/runtime/src/declared-capability-absence-warn-once.test.ts‎

Lines changed: 74 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@
5252
*/
5353

5454
import { describe, it, expect, vi } from 'vitest';
55+
// [#23181] Both doors come from ONE module graph, loaded here at module top so
56+
// the transform is paid during collection, never inside a clocked test (the
57+
// clocked-window rule, `scripts/check-test-source-alias.mjs`). One graph is
58+
// also what makes the two-door block below a pin: the dispatcher and
59+
// `sendError` read the same `@objectstack/types` registry.
60+
import { createDispatcherPlugin } from './dispatcher-plugin.js';
61+
import { sendError } from '@objectstack/types';
5562

5663
// [#21061] The analytics domain refuses an anonymous caller first (ADR-0056 D2),
5764
// so every analytics boot below also registers an `auth` slot in the shape
@@ -60,12 +67,11 @@ import { describe, it, expect, vi } from 'vitest';
6067
// unchanged. Anonymity is pinned in `domains/analytics-anonymous-deny.test.ts`.
6168
const SIGNED_IN_AUTH = { api: { getSession: async () => ({ user: { id: 'usr_analytics_caller' } }) } };
6269

63-
// Each test re-executes the dispatcher's module graph (see `boot` below), which
64-
// the default 5s budget does not cover on a shared box — measured: the FIRST
65-
// test paid 5s+ and timed out while the rest ran in ~1.4s each off vitest's
66-
// transform cache. The cost is the price of the per-test isolation the ruling's
67-
// "per process" key needs, so the budget is raised rather than the isolation
68-
// dropped.
70+
// This budget was raised when every test re-executed the dispatcher's module
71+
// graph (the FIRST test paid the cold transform, 5s+ on a shared box). `boot`
72+
// below no longer reloads anything (#23181), so no test here pays a load; the
73+
// value is left as it stands because this change is about the wall a test
74+
// takes, not about its budget.
6975
vi.setConfig({ testTimeout: 30_000 });
7076

7177
function makeFakeServer() {
@@ -97,30 +103,43 @@ function makeRes() {
97103
return res;
98104
}
99105

106+
/** Mounts made so far in this process: each `boot` takes the next prefix. */
107+
let mounts = 0;
108+
100109
/**
101-
* Boot the real plugin over a fake transport, with a spied kernel logger —
102-
* in a FRESH module graph.
110+
* Boot the real plugin over a fake transport, with a spied kernel logger,
111+
* mounted under a route prefix no other test in this file mounts.
112+
*
113+
* ⚠️ The prefix is the load-bearing line, not boilerplate. The dedupe registry
114+
* is module state in `@objectstack/types` (that IS the ruling's "per process"
115+
* half), keyed `METHOD path` on the mounted route PATTERN, which the route
116+
* instrument parks (`observability/instrument.ts`). Under a shared prefix the
117+
* first test to touch a route would silence it for every later test in this
118+
* file, and their colour would depend on execution order. Under its own
119+
* prefix, every route key a test touches is one this process has never
120+
* reported, which is the precondition a fresh process gives those keys, so
121+
* each test still observes the FIRST occurrence of its routes.
103122
*
104-
* ⚠️ `vi.resetModules()` is the load-bearing line, not boilerplate. The dedupe
105-
* registry is module state in `@objectstack/types` (that IS the ruling's "per
106-
* process" half), so without a reset the first test to touch a route would
107-
* silence it for every later test in this file, and their colour would depend
108-
* on execution order. Resetting gives each test its own process-equivalent,
109-
* which is also the only honest way to pin a per-process rule.
123+
* [#23181] This used to be bought with `vi.resetModules()` and a fresh import
124+
* of both doors per test: a re-evaluation of the dispatcher's ~730-module
125+
* graph seven times (measured ~0.9s each idle, 1.1-1.4s each inside the full
126+
* package run, plus a 17s cold transform inside the first test), which made
127+
* this file the slowest in the package. Disjoint route keys give each test the
128+
* same untouched registry at none of that cost. No assertion here reads a
129+
* route another test mounts, and the registry's ceiling (512 routes) is far
130+
* above the 14 keys this file can create.
110131
*
111132
* `@objectstack/types` is aliased to its SOURCE for this package
112-
* (`packages/runtime/vitest.config.ts`), so the reset reaches the registry and
113-
* an edit to `packages/types/src` is visible here without a rebuild.
133+
* (`packages/runtime/vitest.config.ts`), so an edit to `packages/types/src` is
134+
* visible here without a rebuild.
114135
*
115-
* Both doors are imported INSIDE this window so the dispatcher and `sendError`
116-
* share one registry — a two-door pin over two registries would prove nothing.
136+
* `route('GET /notifications')` answers the handler the dispatcher mounted for
137+
* that route under THIS boot's prefix; `path('/notifications')` is the route
138+
* pattern the log line must name.
117139
*/
118140
async function boot(services: Record<string, any>) {
119-
vi.resetModules();
120-
const [{ createDispatcherPlugin }, { sendError }] = await Promise.all([
121-
import('./dispatcher-plugin.js'),
122-
import('@objectstack/types'),
123-
]);
141+
mounts += 1;
142+
const prefix = `/t${mounts}/api/v1`;
124143
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
125144
const kernel = {
126145
getService: (n: string) => services[n],
@@ -135,9 +154,16 @@ async function boot(services: Record<string, any>) {
135154
hook: () => { },
136155
on: () => { },
137156
};
138-
const plugin = createDispatcherPlugin({ prefix: '/api/v1', securityHeaders: false });
157+
const plugin = createDispatcherPlugin({ prefix, securityHeaders: false });
139158
await plugin.start?.(ctx);
140-
return { handlers, logger, sendError };
159+
const path = (route: string) => `${prefix}${route}`;
160+
const route = (verbAndRoute: string) => {
161+
const [verb, routePath] = verbAndRoute.split(' ');
162+
const handler = handlers[`${verb} ${path(routePath)}`];
163+
if (!handler) throw new Error(`no handler mounted for ${verb} ${path(routePath)}`);
164+
return handler;
165+
};
166+
return { route, path, logger, sendError };
141167
}
142168

143169
const FAULT_PREFIX = '[5xx]';
@@ -158,10 +184,10 @@ const throwingAnalytics = (message: string) => ({
158184

159185
describe('#14656 — the dispatcher door', () => {
160186
it('reports a declared 501 ONCE across N requests, at warn, naming the missing service', async () => {
161-
const { handlers, logger } = await boot({});
187+
const { route, path, logger } = await boot({});
162188

163189
for (const _ of [0, 1, 2, 3, 4]) {
164-
await handlers['GET /api/v1/notifications']({ ...REQ }, makeRes());
190+
await route('GET /notifications')({ ...REQ }, makeRes());
165191
}
166192

167193
expect(lines(logger.error), 'a configuration fact is not a fault').toHaveLength(0);
@@ -178,23 +204,23 @@ describe('#14656 — the dispatcher door', () => {
178204
status: 501,
179205
code: 'NOT_IMPLEMENTED',
180206
method: 'GET',
181-
path: '/api/v1/notifications',
207+
path: path('/notifications'),
182208
});
183209
});
184210

185211
it('a SECOND, DIFFERENT route still reports — the dedupe key is the route', async () => {
186-
const { handlers, logger } = await boot({});
212+
const { route, path, logger } = await boot({});
187213

188-
await handlers['GET /api/v1/notifications']({ ...REQ }, makeRes());
189-
await handlers['GET /api/v1/notifications']({ ...REQ }, makeRes());
190-
await handlers['POST /api/v1/notifications/read']({ ...REQ, body: { ids: ['n1'] } }, makeRes());
191-
await handlers['POST /api/v1/notifications/read']({ ...REQ, body: { ids: ['n1'] } }, makeRes());
214+
await route('GET /notifications')({ ...REQ }, makeRes());
215+
await route('GET /notifications')({ ...REQ }, makeRes());
216+
await route('POST /notifications/read')({ ...REQ, body: { ids: ['n1'] } }, makeRes());
217+
await route('POST /notifications/read')({ ...REQ, body: { ids: ['n1'] } }, makeRes());
192218

193219
const warned = lines(logger.warn);
194220
expect(warned, 'one line per route, not one line per process').toHaveLength(2);
195221
expect(warned.map((c) => `${(c[1] as any).method} ${(c[1] as any).path}`)).toEqual([
196-
'GET /api/v1/notifications',
197-
'POST /api/v1/notifications/read',
222+
`GET ${path('/notifications')}`,
223+
`POST ${path('/notifications/read')}`,
198224
]);
199225
// Same slot, same code, same prose — so nothing but the route could
200226
// have told the two apart.
@@ -204,10 +230,10 @@ describe('#14656 — the dispatcher door', () => {
204230
});
205231

206232
it('an UNDECLARED 500 on the same door is still loud, once per request', async () => {
207-
const { handlers, logger } = await boot(throwingAnalytics('still-loud-per-request'));
233+
const { route, logger } = await boot(throwingAnalytics('still-loud-per-request'));
208234

209235
for (const _ of [0, 1, 2]) {
210-
await handlers['POST /api/v1/analytics/query'](
236+
await route('POST /analytics/query')(
211237
{ body: { cube: 'x', measures: ['count'] }, query: {} },
212238
makeRes(),
213239
);
@@ -218,12 +244,12 @@ describe('#14656 — the dispatcher door', () => {
218244
});
219245

220246
it('a declared absence and a fault coexist in one process without either changing the other', async () => {
221-
const { handlers, logger } = await boot(throwingAnalytics('coexist'));
247+
const { route, logger } = await boot(throwingAnalytics('coexist'));
222248

223-
await handlers['GET /api/v1/notifications']({ ...REQ }, makeRes());
224-
await handlers['POST /api/v1/analytics/query']({ body: { cube: 'x', measures: ['count'] }, query: {} }, makeRes());
225-
await handlers['GET /api/v1/notifications']({ ...REQ }, makeRes());
226-
await handlers['POST /api/v1/analytics/query']({ body: { cube: 'x', measures: ['count'] }, query: {} }, makeRes());
249+
await route('GET /notifications')({ ...REQ }, makeRes());
250+
await route('POST /analytics/query')({ body: { cube: 'x', measures: ['count'] }, query: {} }, makeRes());
251+
await route('GET /notifications')({ ...REQ }, makeRes());
252+
await route('POST /analytics/query')({ body: { cube: 'x', measures: ['count'] }, query: {} }, makeRes());
227253

228254
expect(lines(logger.warn)).toHaveLength(1);
229255
expect(lines(logger.error)).toHaveLength(2);
@@ -232,11 +258,11 @@ describe('#14656 — the dispatcher door', () => {
232258

233259
describe('#14656 — both doors read ONE predicate, on one fixture envelope', () => {
234260
it('the envelope the dispatcher answers is classified identically by the REST envelope writer', async () => {
235-
const { handlers, logger, sendError } = await boot({});
261+
const { route, logger, sendError } = await boot({});
236262

237263
// ── Door 1: the runtime dispatcher, on the real route ──────────────
238264
const res = makeRes();
239-
await handlers['GET /api/v1/notifications']({ ...REQ }, res);
265+
await route('GET /notifications')({ ...REQ }, res);
240266

241267
const fixture = {
242268
status: res.statusCode as number,
@@ -285,12 +311,12 @@ describe('#14656 — the wire does not move', () => {
285311
* report» — is a measurement, and this is the instrument.
286312
*/
287313
it('answers the same bytes it answered before this change, on every request', async () => {
288-
const { handlers } = await boot({});
314+
const { route } = await boot({});
289315

290316
const seen: string[] = [];
291317
for (const _ of [0, 1, 2]) {
292318
const res = makeRes();
293-
await handlers['GET /api/v1/notifications']({ ...REQ }, res);
319+
await route('GET /notifications')({ ...REQ }, res);
294320
seen.push(`${res.statusCode} ${JSON.stringify(res.body)}`);
295321
}
296322

@@ -306,10 +332,10 @@ describe('#14656 — the wire does not move', () => {
306332
});
307333

308334
it('answers the same bytes for a fault, too', async () => {
309-
const { handlers } = await boot(throwingAnalytics('wire-unchanged-for-faults'));
335+
const { route } = await boot(throwingAnalytics('wire-unchanged-for-faults'));
310336

311337
const res = makeRes();
312-
await handlers['POST /api/v1/analytics/query'](
338+
await route('POST /analytics/query')(
313339
{ body: { cube: 'x', measures: ['count'] }, query: {} },
314340
res,
315341
);

‎packages/runtime/src/stored-metadata-body-boundary.pin.test.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ import { AppPlugin } from './app-plugin.js';
4545
import { DefaultDatasourcePlugin } from './default-datasource-plugin.js';
4646
import { createDispatcherPlugin } from './dispatcher-plugin.js';
4747

48+
// [#23181] Pay the first transform of the default datasource's drivers at MODULE
49+
// LOAD. `kernel.bootstrap()` below reaches both through `service-datasource`'s
50+
// lazy `import()` (measured: they are the two workspace modules this boot first
51+
// evaluates), and `beforeAll` is clocked while collection is clocked against
52+
// nothing. Same shape and reason as `datasource-visibility.test.ts` (the
53+
// clocked-window rule, `scripts/check-test-source-alias.mjs`).
54+
import '@objectstack/driver-sqlite-wasm';
55+
import '@objectstack/driver-sql';
56+
4857
const BOOT_TIMEOUT = 180_000;
4958
const ORIGIN = 'http://localhost:3000';
5059
const API = '/api/v1';

‎packages/runtime/src/stored-metadata-reader-contexts.pin.test.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ import { AppPlugin } from './app-plugin.js';
7070
import { DefaultDatasourcePlugin } from './default-datasource-plugin.js';
7171
import { createDispatcherPlugin } from './dispatcher-plugin.js';
7272

73+
// [#23181] Pay the first transform of the default datasource's drivers at MODULE
74+
// LOAD. `kernel.bootstrap()` below reaches both through `service-datasource`'s
75+
// lazy `import()` (measured: they are the two workspace modules this boot first
76+
// evaluates), and `beforeAll` is clocked while collection is clocked against
77+
// nothing. Same shape and reason as `datasource-visibility.test.ts` (the
78+
// clocked-window rule, `scripts/check-test-source-alias.mjs`).
79+
import '@objectstack/driver-sqlite-wasm';
80+
import '@objectstack/driver-sql';
81+
7382
const BOOT_TIMEOUT = 180_000;
7483
const ORIGIN = 'http://localhost:3000';
7584
const API = '/api/v1';

0 commit comments

Comments
 (0)