@ultimat3/cli 27.6.1 → 27.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/i18n-index.ts CHANGED
@@ -9,7 +9,10 @@
9
9
  // why: Bun has no synchronous existence check — `Bun.file(p).exists()` is async, and this decides
10
10
  // whether to write at all, before any await the caller could interleave with.
11
11
  import { existsSync } from 'node:fs';
12
+ import { stripComments } from '@ultimat3/core';
12
13
  import { APP_CATALOGS_PATH } from '@ultimat3/i18n/app-catalogs';
14
+ import type { GenerateDisk } from './generate-disk';
15
+ import { appDisk } from './generate-disk';
13
16
  import { containedPath } from './generate-write';
14
17
  import type { Finding } from './output';
15
18
  import { CATALOG_ROOT, i18nIndex, localeEntry, localeImport } from './templates';
@@ -30,6 +33,8 @@ export interface IndexSync {
30
33
  /** False for an app with no i18n package, or an index this writer refused to edit. */
31
34
  readonly registered: boolean;
32
35
  readonly findings: readonly Finding[];
36
+ /** The index, when this changed its bytes — a run's file list names every file it touched. */
37
+ readonly edited?: readonly string[];
33
38
  }
34
39
 
35
40
  const CATALOG_IMPORT = /^import (\w+) from '\.\.\/catalogs\/([^']+)\.json';$/gm;
@@ -48,31 +53,42 @@ const importedLocales = (source: string): readonly string[] =>
48
53
  * stay. A shape with no `locales: { … }` object to add to is refused with the edit named, never
49
54
  * overwritten: it was, on every `x g`, with a template hard-coding `default: 'en'`.
50
55
  */
51
- export async function syncI18nIndex(root: string): Promise<IndexSync> {
52
- const indexAbsolute = containedPath(root, APP_CATALOGS_PATH);
53
- if (!existsSync(indexAbsolute)) return { registered: false, findings: [] };
54
- const current = await Bun.file(indexAbsolute).text();
55
- const onDisk = await catalogLocales(root);
56
+ export async function syncI18nIndex(
57
+ root: string,
58
+ disk: GenerateDisk = appDisk(root),
59
+ /** Locales whose catalog a dry run PLANS: on the real disk only once the run has written. */
60
+ planned: readonly string[] = [],
61
+ ): Promise<IndexSync> {
62
+ const current = await disk.read(APP_CATALOGS_PATH);
63
+ if (current === undefined) return { registered: false, findings: [] };
64
+ const onDisk = [...new Set([...(await catalogLocales(root)), ...planned])].sort();
56
65
  const imported = importedLocales(current);
57
- if (current === i18nIndex(imported)) {
58
- await Bun.write(indexAbsolute, i18nIndex(onDisk));
59
- return { registered: true, findings: [] };
60
- }
66
+ const write = async (next: string): Promise<IndexSync> => {
67
+ if (next === current) return { registered: true, findings: [] };
68
+ await disk.write(APP_CATALOGS_PATH, next);
69
+ return { registered: true, findings: [], edited: [APP_CATALOGS_PATH] };
70
+ };
71
+ if (current === i18nIndex(imported)) return write(i18nIndex(onDisk));
61
72
  const missing = onDisk.filter((locale) => !imported.includes(locale));
62
73
  if (missing.length === 0) return { registered: true, findings: [] };
63
74
  const edited = withLocales(current, missing);
64
75
  if (edited === undefined) return { registered: false, findings: [refusal(missing)] };
65
- await Bun.write(indexAbsolute, edited);
66
- return { registered: true, findings: [] };
76
+ return write(edited);
67
77
  }
68
78
 
69
79
  /** Each import after the last catalog import (or at the top), each entry at the object's end. */
70
80
  function withLocales(source: string, locales: readonly string[]): string | undefined {
71
- const object = LOCALES_OBJECT.exec(source);
81
+ // Found on the comment-stripped text (same offsets): a `locales: { … }` in a comment above the
82
+ // call is prose, and it was the one this edited.
83
+ const object = LOCALES_OBJECT.exec(stripComments(source));
72
84
  if (object === null) return undefined;
73
- const body = (object[2] ?? '').trim().replace(/,$/, '');
85
+ const [whole, head = '', inner = ''] = object;
86
+ // A comment INSIDE the object cannot be carried through a one-line rewrite: refused, with the
87
+ // edit named, rather than dropped.
88
+ if (source.slice(object.index, object.index + whole.length) !== whole) return undefined;
89
+ const body = inner.trim().replace(/,$/, '');
74
90
  const entries = [body, ...locales.map(localeEntry)].filter((part) => part !== '').join(', ');
75
- const withEntries = source.replace(LOCALES_OBJECT, `$1 ${entries} $3`);
91
+ const withEntries = `${source.slice(0, object.index)}${head} ${entries} }${source.slice(object.index + whole.length)}`;
76
92
  const imports = locales.map(localeImport).join('\n');
77
93
  const lastImport = [...withEntries.matchAll(CATALOG_IMPORT)].at(-1);
78
94
  if (lastImport === undefined) return `${imports}\n${withEntries}`;
@@ -0,0 +1,63 @@
1
+ // What `x verify` says about the query an `isr` route keys on. Advice and not a finding: the scan
2
+ // reads source text, so it can miss a read made through a helper and can name one that is not a
3
+ // query read at all — and a route that declared nothing works as it always did. Both are exactly
4
+ // what a reader should hear about before a visitor finds it.
5
+
6
+ // why: Bun exposes no path-join primitive; Bun.file takes one already joined.
7
+ import { join } from 'node:path';
8
+ import { stripComments } from '@ultimat3/core';
9
+ import { describePages } from '@ultimat3/render';
10
+ import { msg } from './messages';
11
+
12
+ const MEMBER_READ = /\bquery\s*\??\.\s*([A-Za-z_$][\w$]*)\b(?!\s*\()/g;
13
+ const BRACKET_READ = /\bquery\s*(?:\?\.)?\[\s*['"]([^'"]+)['"]\s*\]/g;
14
+ const SEARCH_PARAM_READ = /\bsearchParams\s*\??\.\s*(?:get|getAll|has)\(\s*['"]([^'"]+)['"]/g;
15
+
16
+ /**
17
+ * The query parameter names a route module reads, as far as its text says: `query.name`,
18
+ * `query?.name`, `query['name']` and `searchParams.get('name')` (also `getAll`, `has`). A call
19
+ * (`query.where(…)`) is a method, not a parameter. Sorted, each once.
20
+ */
21
+ export function queryReads(source: string): readonly string[] {
22
+ const code = stripComments(source);
23
+ const names = new Set<string>();
24
+ for (const pattern of [MEMBER_READ, BRACKET_READ, SEARCH_PARAM_READ]) {
25
+ for (const match of code.matchAll(pattern)) {
26
+ if (match[1] !== undefined) names.add(match[1]);
27
+ }
28
+ }
29
+ return [...names].sort();
30
+ }
31
+
32
+ const listed = (names: readonly string[]): string => names.map((name) => `'${name}'`).join(', ');
33
+
34
+ /**
35
+ * One line per `isr` route with no `revalidate.query` — it renders and stores a page for every
36
+ * distinct query string, so `?x=1`, `?x=2`, … evict its real pages and cost a render each — and
37
+ * one per parameter a route's module reads and its declaration omits: the page is stored under a
38
+ * key without it, so every visitor gets the document rendered for whichever value arrived first.
39
+ */
40
+ export async function isrQueryAdvice(root: string): Promise<readonly string[]> {
41
+ const advice: string[] = [];
42
+ for (const route of describePages()) {
43
+ if (route.mode !== 'isr') continue;
44
+ const declared = route.revalidateQuery ?? null;
45
+ if (declared === null) {
46
+ advice.push(msg('cli.verify.isrQueryUndeclared', { file: route.file }));
47
+ continue;
48
+ }
49
+ const module = Bun.file(join(root, route.file));
50
+ if (!(await module.exists())) continue;
51
+ for (const name of queryReads(await module.text())) {
52
+ if (declared.includes(name)) continue;
53
+ advice.push(
54
+ msg('cli.verify.isrQueryOmitted', {
55
+ file: route.file,
56
+ name,
57
+ declared: listed([...declared, name].sort()),
58
+ }),
59
+ );
60
+ }
61
+ }
62
+ return advice;
63
+ }
@@ -0,0 +1,49 @@
1
+ // The `query` object a page component is handed. For an `isr` route that declared
2
+ // `revalidate.query` it holds the declared parameters only — and, outside production, it says so
3
+ // once when the page reads any other one: that read is a document varying on something its stored
4
+ // key does not carry, which is one visitor's page answered to every visitor after.
5
+
6
+ import type { ResolveEnvironmentOptions } from '@ultimat3/core';
7
+ import { logger, tryResolveEnvironment } from '@ultimat3/core';
8
+ import type { RouteEntry } from '@ultimat3/render';
9
+
10
+ type Query = Readonly<Record<string, string>>;
11
+
12
+ /** `<route file>\u0000<parameter>` already told — bounded by what the app's source can read. */
13
+ const told = new Set<string>();
14
+
15
+ /** Names every object answers and a renderer or a serializer may ask for: never a parameter. */
16
+ const isProbe = (name: string): boolean =>
17
+ name in Object.prototype || name === 'then' || name === 'toJSON';
18
+
19
+ export function routeQuery(
20
+ entry: RouteEntry,
21
+ url: URL,
22
+ env?: ResolveEnvironmentOptions['env'],
23
+ ): Query {
24
+ const query: Query = Object.fromEntries(url.searchParams);
25
+ const declared = entry.config.render === 'isr' ? entry.config.revalidate?.query : undefined;
26
+ if (declared === undefined) return query;
27
+ // Production pays nothing: the plain object, no trap. (`undefined`: an unreadable environment,
28
+ // which is not a development one either.)
29
+ const environment = tryResolveEnvironment(env === undefined ? undefined : { env });
30
+ if (environment === 'production' || environment === undefined) return query;
31
+ return new Proxy(query, {
32
+ get(target, name, receiver) {
33
+ if (typeof name === 'string' && !declared.includes(name) && !isProbe(name)) {
34
+ const key = `${entry.file}\u0000${name}`;
35
+ if (!told.has(key)) {
36
+ told.add(key);
37
+ const widened = [...declared, name].sort().map((one) => `'${one}'`);
38
+ logger.warn('isr.query.unkeyed-read', {
39
+ route: entry.file,
40
+ param: name,
41
+ cause: `the page read query.${name}, and revalidate.query does not list it — the stored page is keyed without it, so every visitor gets the document rendered for whichever value arrived first`,
42
+ fix: `query: [${widened.join(', ')}] // in revalidate, in ${entry.file}`,
43
+ });
44
+ }
45
+ }
46
+ return Reflect.get(target, name, receiver);
47
+ },
48
+ });
49
+ }
package/src/mcp-host.ts CHANGED
@@ -105,7 +105,8 @@ export function lazyServices(input: DevHostInput): LazyServices {
105
105
  // one connection wedged every later tool call in the session with that first error, and the only
106
106
  // way out was restarting the host — `startServices` unwinds everything it started before it
107
107
  // rejects, so there is nothing left over for a second attempt to collide with.
108
- const boot = retryMemo(() => startServices(services, input.env));
108
+ // `[]`: this host starts no role, so it only ever publishes to the bus and never waits for it.
109
+ const boot = retryMemo(() => startServices(services, input.env, undefined, 'apply', []));
109
110
  let closed = false;
110
111
  return {
111
112
  services,
package/src/messages.ts CHANGED
@@ -328,6 +328,10 @@ const CATALOG = {
328
328
  // counts — so the line says how many and `x test <type>` prints the names.
329
329
  'cli.verify.allSkipped': 'found {skipped} test(s) and every one skipped itself',
330
330
  'cli.verify.warnings': '{count} warning(s) — --verbose or --json lists them',
331
+ 'cli.verify.isrQueryUndeclared':
332
+ "{file}: render 'isr' with no revalidate.query — its stored pages are keyed on the whole query string, so any visitor can mint one per request. Declare revalidate: { query: [] } (or the parameters the page varies on)",
333
+ 'cli.verify.isrQueryOmitted':
334
+ '{file}: reads the query parameter "{name}", which revalidate.query does not list — the page is stored under a key without it, so every visitor gets the document rendered for whichever value arrived first. Declare query: [{declared}], or stop reading it',
331
335
  // The other shape the same counts can take: a suite that reported no test at all, skipped ones
332
336
  // included. Its own line and not `allSkipped` with a zero, because the repair differs — there is
333
337
  // no skipped test to read a reason off, so the files the step selected hold no test.
@@ -0,0 +1,66 @@
1
+ // Single responsibility: what the `sync` role does when its node cannot start because the bus is
2
+ // away. A `ROLE=sync` container refuses its boot — it has nothing to serve without the bus.
3
+ // `x dev` runs every role in one process, so there the node stays mounted and NOT ready, and is
4
+ // started when the bus answers: the pages, the worker and the scheduler never went down with it.
5
+
6
+ import { isUltimateError, logger } from '@ultimat3/core';
7
+ import type { SyncNode, Transport } from '@ultimat3/realtime/server';
8
+
9
+ export interface BusWaitRuntime {
10
+ readonly transport: Pick<Transport, 'onReconnect'>;
11
+ /** `RunningServices.busTolerated`: the boot is `x dev`, and a bus that is away is waited for. */
12
+ readonly busTolerated?: boolean | undefined;
13
+ }
14
+
15
+ /**
16
+ * Start the node, or — tolerated, with the bus away — leave it unstarted and start it on the
17
+ * transport's next recovery. Until then an upgrade is shed with a 503 and a retry delay
18
+ * (`sync-upgrade.ts`), so an open page keeps asking and goes live with no restart. Any other
19
+ * refusal, and every refusal outside `x dev`, is the boot's. Returns what stops the wait.
20
+ */
21
+ export async function startOrWaitForBus(
22
+ node: Pick<SyncNode, 'start'>,
23
+ runtime: BusWaitRuntime,
24
+ ): Promise<() => void> {
25
+ const refused = await node.start().then(
26
+ () => undefined,
27
+ (error: unknown) => ({ error }),
28
+ );
29
+ if (refused === undefined) return () => undefined;
30
+ const { error } = refused;
31
+ if (
32
+ runtime.busTolerated !== true ||
33
+ !isUltimateError(error) ||
34
+ error.code !== 'X_TRANSPORT_UNAVAILABLE'
35
+ ) {
36
+ throw error;
37
+ }
38
+ // Once, with the refusal a container would have exited on.
39
+ logger.warn('sync node is waiting for the bus: sockets are refused until it answers', {
40
+ code: error.code,
41
+ cause: error.cause,
42
+ fix: error.fix,
43
+ });
44
+ let waiting = true;
45
+ let off = (): void => undefined;
46
+ const attempt = (): void => {
47
+ if (!waiting) return;
48
+ void node.start().then(
49
+ () => {
50
+ if (!waiting) return;
51
+ waiting = false;
52
+ off();
53
+ logger.info('sync node started: the bus answered');
54
+ },
55
+ // Still away, or away again: the next recovery asks once more.
56
+ () => undefined,
57
+ );
58
+ };
59
+ off = runtime.transport.onReconnect(attempt);
60
+ // The dial may have landed between the refusal and the line above; asking again costs nothing.
61
+ attempt();
62
+ return () => {
63
+ waiting = false;
64
+ off();
65
+ };
66
+ }
package/src/role-sync.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  import { neighbouringPort, PORT_RANGE, portPairAfter } from './flag-number';
27
27
  import { portFree } from './port-probe';
28
28
  import type { StartRolesOptions } from './role-start';
29
+ import { startOrWaitForBus } from './role-sync-bus-wait';
29
30
  import type { Env } from './runtime-bindings';
30
31
  import { syncAuthenticator } from './sync-authenticator';
31
32
  import { syncOriginsFrom } from './sync-url';
@@ -327,14 +328,17 @@ export async function prepareSync(options: StartRolesOptions): Promise<PreparedS
327
328
  ttlMs: options.runtime.presenceTtlMs,
328
329
  }),
329
330
  });
330
- await node.start();
331
+ const stopWaiting = await startOrWaitForBus(node, options.runtime);
331
332
  return {
332
333
  node,
333
334
  registry,
334
335
  // The node's OWN path, asked rather than restated: `SyncNodeOptions.path` is settable and a
335
336
  // second copy of `/_x/sync` here is the copy that stays behind when it moves.
336
337
  mount: { path: node.path, fetch: node.fetch, websocket: node.websocket },
337
- stop: () => node.stop(),
338
+ stop: async () => {
339
+ stopWaiting();
340
+ await node.stop();
341
+ },
338
342
  listen: async (appUrl) => {
339
343
  origins.push(...devOrigins(options, origins, appUrl));
340
344
  return await listen(options, node, { registry, hub }, appUrl);
@@ -0,0 +1,198 @@
1
+ // Single responsibility: the fanout bus as ONE boot holds it — what the roles it starts do with
2
+ // the bus, the dial that calls for, the readiness check, and the label the boot line prints.
3
+ //
4
+ // The rule is the role's, not the transport's. A `sync` node serves sockets from the bus and a
5
+ // replicator feeds them: neither can work without it, so each awaits the dial and refuses its
6
+ // boot, coded, inside a bounded wait. `web`, `worker` and `scheduler` only PUBLISH — channel
7
+ // events and cache invalidations, both of which mean "re-read" — so for them the bus is dialled in
8
+ // the background and a dead one is `degraded`: the pod still boots, serves and runs jobs. Until
9
+ // 2026-10 every role awaited the dial and the JetStream bucket, so a web pod that restarted while
10
+ // NATS was down took the app's pages, webhooks and send jobs down with a feature none of them use.
11
+ //
12
+ // `x dev` is the one boot that TOLERATES a missing bus for a role that serves from it: it runs
13
+ // every role in one process, so the `sync` role's refusal took the pages, the worker and the
14
+ // scheduler down with it — the same outage, on the developer's own machine. There the refusal is
15
+ // logged once, coded, the dial goes on in the background, and the pages run without live until it
16
+ // lands. A `ROLE=sync` container keeps the hard refusal: it has nothing else to serve.
17
+
18
+ import type { RealtimeConfig, Role } from '@ultimat3/core';
19
+ import { isUltimateError, logger, registerReadinessCheck } from '@ultimat3/core';
20
+ import type { BusUse, SelectTransportOptions, Transport } from '@ultimat3/realtime/server';
21
+ import { selectTransport, setChannelTransport } from '@ultimat3/realtime/server';
22
+ import type { Env } from './runtime-bindings';
23
+
24
+ /**
25
+ * What a process running `roles` does with the bus. `undefined` — a boot that names no roles — is
26
+ * held to the strictest reading, the one every boot had. `realtime.enabled: false` starts neither
27
+ * realtime role (`role-realtime.ts`), so nothing in such a process waits for the bus.
28
+ */
29
+ export function busUseFor(
30
+ roles: readonly Role[] | undefined,
31
+ realtime: Pick<RealtimeConfig, 'enabled'>,
32
+ ): BusUse {
33
+ if (roles === undefined) return 'sockets';
34
+ if (!realtime.enabled) return 'publish';
35
+ if (roles.includes('sync')) return 'sockets';
36
+ return roles.includes('replicator') ? 'feed' : 'publish';
37
+ }
38
+
39
+ /** A transport that says whether it is connected. NATS does; the in-process bus has nothing to be. */
40
+ interface ConnectableTransport {
41
+ readonly connected: boolean;
42
+ }
43
+
44
+ const saysConnected = (transport: Transport): transport is Transport & ConnectableTransport =>
45
+ typeof (transport as Partial<ConnectableTransport>).connected === 'boolean';
46
+
47
+ /**
48
+ * The boot line's `bus=` value: `nats(up)`, `nats(connecting)`, or the bare name of a bus with no
49
+ * connection to lose. A fixed vocabulary a script may parse, never a catalog lookup.
50
+ */
51
+ export function busLabel(transport: Transport): string {
52
+ if (!saysConnected(transport)) return transport.name;
53
+ return `${transport.name}(${transport.connected ? 'up' : 'connecting'})`;
54
+ }
55
+
56
+ export interface RunningBus {
57
+ readonly transport: Transport;
58
+ /** The env key that selected the bus — never the url behind it — or `runtime override`. */
59
+ readonly detail: string;
60
+ /** Everything `start()` acquired, newest first; the first failure is rethrown, every step runs. */
61
+ stop(): Promise<void>;
62
+ }
63
+
64
+ /**
65
+ * How long `x dev` waits for a bus its realtime roles need before it boots without one. Short on
66
+ * purpose: a NATS started beside it is reachable by then, and one that is not is dialled in the
67
+ * background anyway.
68
+ */
69
+ export const DEV_BUS_WAIT_MS = 3_000;
70
+
71
+ export interface SelectedBus {
72
+ readonly use: BusUse;
73
+ /**
74
+ * `x dev` with a role that serves from the bus: a bus that is away is waited for in the
75
+ * background rather than refused (`RunningServices.busTolerated`).
76
+ */
77
+ readonly tolerant: boolean;
78
+ /** What the sync role must give `PresenceRegistry` (`RunningServices.presenceTtlMs`). */
79
+ readonly presenceTtlMs: number;
80
+ /**
81
+ * Dial as `use` calls for, install the transport as the process's channel bus, and register its
82
+ * readiness check. Rejects — with nothing left registered or open — only for a use that cannot
83
+ * work without the bus.
84
+ */
85
+ start(): Promise<RunningBus>;
86
+ }
87
+
88
+ export interface SelectBusInput {
89
+ readonly env: Env;
90
+ readonly realtime: RealtimeConfig;
91
+ /** The roles this process will start; `undefined` when the caller does not know. */
92
+ readonly roles: readonly Role[] | undefined;
93
+ /** `RuntimeOverrides.transport`: already connected, and its owner's to close. */
94
+ readonly override?: Transport | undefined;
95
+ /** Injected by a test: a fake dial, a short wait. */
96
+ readonly select?: SelectTransportOptions | undefined;
97
+ /** The boot is `x dev`: every role in one process, on the developer's machine. */
98
+ readonly dev?: boolean | undefined;
99
+ }
100
+
101
+ /**
102
+ * Selection is pure — it parses env and constructs, it dials nothing — so a config and an
103
+ * environment that disagree refuse here, before any service starts (`selectTransport`).
104
+ */
105
+ export function selectBus(input: SelectBusInput): SelectedBus {
106
+ const use = busUseFor(input.roles, input.realtime);
107
+ const tolerant = input.dev === true && use !== 'publish';
108
+ const selection = selectTransport(input.env, input.realtime, {
109
+ ...(tolerant ? { connectWithinMs: DEV_BUS_WAIT_MS } : {}),
110
+ ...input.select,
111
+ use,
112
+ });
113
+ return {
114
+ use,
115
+ tolerant,
116
+ presenceTtlMs: selection.presenceTtlMs,
117
+ async start(): Promise<RunningBus> {
118
+ const started: (() => void | Promise<void>)[] = [];
119
+ const stop = async (): Promise<void> => {
120
+ let failure: { readonly error: unknown } | undefined;
121
+ for (const step of started.splice(0).reverse()) {
122
+ try {
123
+ await step();
124
+ } catch (error) {
125
+ failure ??= { error };
126
+ }
127
+ }
128
+ if (failure !== undefined) throw failure.error;
129
+ };
130
+ try {
131
+ // A supplied transport is already connected and is NOT closed here: whoever built it owns
132
+ // its socket, which is why the override skips both halves rather than only the dial.
133
+ let transport = input.override;
134
+ if (transport === undefined) {
135
+ // Before the dial, not after: a dial refused for being late is still in flight inside
136
+ // the client library, and closing the transport is what releases whatever it lands as.
137
+ started.push(() => selection.transport.close());
138
+ try {
139
+ await selection.connect();
140
+ } catch (error) {
141
+ if (!tolerant || !isUltimateError(error) || error.code !== 'X_TRANSPORT_UNAVAILABLE') {
142
+ throw error;
143
+ }
144
+ // Said ONCE, with the refusal a container would have exited on. The transport is not
145
+ // closed: its dial loop goes on, and `ultimate bus` is the line that says it landed.
146
+ logger.warn('ultimate bus is away: x dev serves without live until it answers', {
147
+ code: error.code,
148
+ cause: error.cause,
149
+ fix: error.fix,
150
+ });
151
+ }
152
+ transport = selection.transport;
153
+ }
154
+ // The bus an app's own code publishes a channel event on (`publishChannelEvent`), in EVERY
155
+ // role: the hub is the `sync` role's, so a job or an action reached none, and an
156
+ // events-only channel — the one kind that needs no replicator — could be fed by nothing
157
+ // but a socket.
158
+ started.push(setChannelTransport(transport));
159
+ // Only a transport that can be disconnected gets a check. `NatsTransport.connected` is a
160
+ // synchronous getter over the client's own state, so no probe is needed; the in-process
161
+ // bus has nothing to lose a connection to, and a check that can only answer `true` is a
162
+ // number in `registered` that means nothing.
163
+ if (saysConnected(transport)) {
164
+ const connectable = transport;
165
+ started.push(
166
+ registerReadinessCheck(
167
+ 'transport',
168
+ () => connectable.connected,
169
+ // A publisher serves without the bus, so losing it must not pull the pod from the
170
+ // ingress — it is reported, and `/readyz?deep=1` is where a monitor reads it.
171
+ // `x dev` serves its pages from the same process: its bus is a degradation too.
172
+ { onFailure: use === 'publish' || tolerant ? 'degraded' : 'failing' },
173
+ ),
174
+ );
175
+ // Each time the bus comes (back) up, on one line: the boot line may have said
176
+ // `connecting`, and this is the other half of it.
177
+ started.push(
178
+ connectable.onReconnect(() => {
179
+ logger.info('ultimate bus', { bus: busLabel(connectable), use });
180
+ }),
181
+ );
182
+ }
183
+ return {
184
+ transport,
185
+ // The env key that selected the bus — or the honest answer that no env key did, because
186
+ // a boot line reading `NATS_URL` over a transport the host handed in is a lie a script
187
+ // parses.
188
+ detail: input.override === undefined ? selection.detail : 'runtime override',
189
+ stop,
190
+ };
191
+ } catch (error) {
192
+ // The refusal is the failure worth reporting, not a release on the way out.
193
+ await stop().catch(() => undefined);
194
+ throw error;
195
+ }
196
+ },
197
+ };
198
+ }