@velajs/testing 0.5.0 → 0.5.1

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/CHANGELOG.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 647fd5f: Modernize the package build, validation, and release toolchain.
8
+
3
9
  ## 0.3.0 (2026-07-04)
4
10
 
5
11
  - `ComponentManager.init` call removed; provider overrides use the supported `Container.replaceProvider`; registers `DiscoveryService` mirroring bootstrap. Requires `@velajs/vela >=1.11.0`.
6
12
 
7
-
8
13
  ## 0.2.1 (2026-05-14)
9
14
 
10
15
  ### Fixes
@@ -25,7 +30,7 @@ Bugs fixed, rebuilt on `@velajs/vela/internal`, no more `reflect-metadata` dep.
25
30
  - **Provider override shape was wrong.** `overrideProvider(token).useValue(v)` constructed `{ token, useValue }` but vela's container reads `{ provide, useValue }`. The misleading `as ProviderOptions` cast hid this; nothing actually overrode. Fixed.
26
31
  - **`useClass` now uses the container's native `useClass` path** instead of the bogus `useFactory: () => new cls()` workaround. The previous comment claimed `Container.registerOptions` doesn't read `useClass` — it does (`vela/src/container/container.ts`).
27
32
  - **`loader.resolveAllInstances()` is now awaited.** Was called synchronously even though vela's signature is async; lifecycle hooks fired before instance resolution finished.
28
- - **APP_* token wiring uses vela's canonical `bindAppProviders` helper.** Replaces the five duplicated `useGlobalGuards/Pipes/Interceptors/Filters/Middleware(container.resolve(APP_*))` blocks that handled only the single-value case (vela switched to array form months ago). Now stays in lockstep with `VelaFactory.create` automatically.
33
+ - **APP\_\* token wiring uses vela's canonical `bindAppProviders` helper.** Replaces the five duplicated `useGlobalGuards/Pipes/Interceptors/Filters/Middleware(container.resolve(APP_*))` blocks that handled only the single-value case (vela switched to array form months ago). Now stays in lockstep with `VelaFactory.create` automatically.
29
34
 
30
35
  ### Breaking changes
31
36
 
package/dist/index.d.ts CHANGED
@@ -1,15 +1,484 @@
1
- export { Test } from './test.js';
2
- export { TestingModule } from './testing-module.js';
3
- export type { ActingAsResolver, TestPrincipal } from './testing-module.js';
4
- export { TestingModuleBuilder, OverrideBy } from './testing-module.builder.js';
5
- export { TestHttpClient } from './http/test-http-client.js';
6
- export { TestHttpRequest } from './http/test-http-request.js';
7
- export { TestResponse } from './http/test-response.js';
8
- export { getValueAtPath, hasValueAtPath } from './http/path-utils.js';
9
- export { TestSseConnection } from './sse/test-sse-connection.js';
10
- export type { TestSseEvent } from './sse/test-sse-connection.js';
11
- export { TestSseRequest } from './sse/test-sse-request.js';
12
- export { TestWsConnection } from './ws/test-ws-connection.js';
13
- export { TestWsRequest, registerWsConnector, getWsConnector } from './ws/test-ws-request.js';
14
- export type { WsConnector } from './ws/test-ws-request.js';
15
- export type { TestDatabase } from './db/test-database.js';
1
+ import { ModuleOptions, Token, Type, VelaApplication } from "@velajs/vela";
2
+ import { Container } from "@velajs/vela/internal";
3
+ import { ISeeder } from "@velajs/vela/seeder";
4
+ //#region src/db/test-database.d.ts
5
+ /**
6
+ * TestDatabase
7
+ *
8
+ * A minimal driver contract for database assertions in tests. Vela has no ORM
9
+ * in core, so the harness ships only this interface plus thin assertion
10
+ * wrappers (`module.assertDatabaseHas/Missing/Count`). A consumer — or a future
11
+ * `@velajs/crud` driver provides the concrete implementation; the harness
12
+ * never couples to a specific database library.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * class MyTestDb implements TestDatabase {
17
+ * async truncate() { ... }
18
+ * async has(table, where) { ... }
19
+ * async count(table) { ... }
20
+ * }
21
+ * await module.assertDatabaseHas(new MyTestDb(), 'user', { email: 'a@b.com' });
22
+ * ```
23
+ */
24
+ interface TestDatabase {
25
+ /** Remove all rows from every table (reset between tests). */
26
+ truncate(): Promise<void>;
27
+ /** Whether a row matching `where` exists in `table`. */
28
+ has(table: string, where: Record<string, unknown>): Promise<boolean>;
29
+ /** The number of rows in `table`. */
30
+ count(table: string): Promise<number>;
31
+ }
32
+ //#endregion
33
+ //#region src/http/test-response.d.ts
34
+ /**
35
+ * TestResponse
36
+ *
37
+ * Wraps a `Response` with fluent, chainable assertions. Synchronous status /
38
+ * header assertions return `this`; JSON assertions (which must read the body)
39
+ * return `Promise<this>`.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const res = await module.http.get('/users/1').send();
44
+ * res.assertOk();
45
+ * await res.assertJsonPath('data.id', 1);
46
+ * ```
47
+ */
48
+ declare class TestResponse {
49
+ private readonly response;
50
+ private jsonData;
51
+ private textData;
52
+ constructor(response: Response);
53
+ /** The raw `Response`. */
54
+ get raw(): Response;
55
+ /** The response status code. */
56
+ get status(): number;
57
+ /** The response headers. */
58
+ get headers(): Headers;
59
+ /** Parse (and cache) the response body as JSON. */
60
+ json<T = unknown>(): Promise<T>;
61
+ /** Read (and cache) the response body as text. */
62
+ text(): Promise<string>;
63
+ /** Assert status is 200 OK. */
64
+ assertOk(): this;
65
+ /** Assert status is 201 Created. */
66
+ assertCreated(): this;
67
+ /** Assert status is 204 No Content. */
68
+ assertNoContent(): this;
69
+ /** Assert status is 400 Bad Request. */
70
+ assertBadRequest(): this;
71
+ /** Assert status is 401 Unauthorized. */
72
+ assertUnauthorized(): this;
73
+ /** Assert status is 403 Forbidden. */
74
+ assertForbidden(): this;
75
+ /** Assert status is 404 Not Found. */
76
+ assertNotFound(): this;
77
+ /** Assert status is 422 Unprocessable Entity. */
78
+ assertUnprocessable(): this;
79
+ /** Assert status is 500 Internal Server Error. */
80
+ assertServerError(): this;
81
+ /** Assert the response has the given status code. */
82
+ assertStatus(expected: number): this;
83
+ /** Assert the status is in the 2xx range. */
84
+ assertSuccessful(): this;
85
+ /** Assert each key in `expected` equals the corresponding top-level value. */
86
+ assertJson(expected: Record<string, unknown>): Promise<this>;
87
+ /** Assert the value at a dot-notation path equals `expected`. */
88
+ assertJsonPath(path: string, expected: unknown): Promise<this>;
89
+ /** Assert every path/value pair in `expectations` matches (batch assert). */
90
+ assertJsonPaths(expectations: Record<string, unknown>): Promise<this>;
91
+ /** Assert the top-level JSON object has every key in `structure`. */
92
+ assertJsonStructure(structure: string[]): Promise<this>;
93
+ /** Assert a path exists (value may be anything, including `null`). */
94
+ assertJsonPathExists(path: string): Promise<this>;
95
+ /** Assert a path does not exist. */
96
+ assertJsonPathMissing(path: string): Promise<this>;
97
+ /** Assert the value at a path satisfies a predicate. */
98
+ assertJsonPathMatches(path: string, matcher: (value: unknown) => boolean): Promise<this>;
99
+ /** Assert the string value at a path contains `substring`. */
100
+ assertJsonPathContains(path: string, substring: string): Promise<this>;
101
+ /** Assert the array value at a path includes `item`. */
102
+ assertJsonPathIncludes(path: string, item: unknown): Promise<this>;
103
+ /** Assert the array value at a path has `count` items. */
104
+ assertJsonPathCount(path: string, count: number): Promise<this>;
105
+ /** Assert a header is present, optionally equal to `expected`. */
106
+ assertHeader(name: string, expected?: string): this;
107
+ /** Assert a header is absent. */
108
+ assertHeaderMissing(name: string): this;
109
+ }
110
+ //#endregion
111
+ //#region src/http/test-http-request.d.ts
112
+ /**
113
+ * TestHttpRequest
114
+ *
115
+ * Fluent builder for a single test HTTP request. `send()` builds a `Request`
116
+ * and drives it through `module.fetch()` (the full Hono pipeline).
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const res = await module.http
121
+ * .post('/users')
122
+ * .withBody({ name: 'A' })
123
+ * .withHeaders({ 'X-Trace': '1' })
124
+ * .send();
125
+ * res.assertCreated();
126
+ * ```
127
+ */
128
+ declare class TestHttpRequest {
129
+ private readonly method;
130
+ private readonly path;
131
+ private readonly module;
132
+ private readonly host;
133
+ private body;
134
+ private readonly requestHeaders;
135
+ private principal;
136
+ private resolver;
137
+ constructor(method: string, path: string, headers: Headers, module: TestingModule, host?: string | null);
138
+ /** Set the request body (JSON-serialized on send). */
139
+ withBody(data: unknown): this;
140
+ /** Merge additional headers. */
141
+ withHeaders(headers: Record<string, string>): this;
142
+ /** Set `Content-Type: application/json`. */
143
+ asJson(): this;
144
+ /**
145
+ * Authenticate the request as `principal`. The `resolver` (or a default one
146
+ * registered via `module.setAuthResolver`) turns the principal into request
147
+ * headers. The resolver signature `(module, principal) => Promise<Headers>`
148
+ * is the cross-package contract sibling packages (e.g. `@velajs/better-auth`)
149
+ * build against.
150
+ */
151
+ actingAs(principal: TestPrincipal, resolver?: ActingAsResolver): this;
152
+ /** Build the `Request` and send it through `module.fetch()`. */
153
+ send(): Promise<TestResponse>;
154
+ private applyAuthentication;
155
+ }
156
+ //#endregion
157
+ //#region src/http/test-http-client.d.ts
158
+ /**
159
+ * TestHttpClient
160
+ *
161
+ * Fluent entry point for test HTTP requests. `forHost`/`withHeaders` return a
162
+ * new immutable client; the verb methods start a {@link TestHttpRequest}.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * const res = await module.http
167
+ * .forHost('example.com')
168
+ * .post('/users')
169
+ * .withBody({ name: 'A' })
170
+ * .send();
171
+ * res.assertCreated();
172
+ * ```
173
+ */
174
+ declare class TestHttpClient {
175
+ private readonly module;
176
+ private readonly host;
177
+ private readonly defaultHeaders;
178
+ constructor(module: TestingModule, host?: string | null, defaultHeaders?: Headers);
179
+ /**
180
+ * Return a new client bound to `host`. Also sets the `Host` header so domain
181
+ * routing works even when the runtime reads the header rather than the URL.
182
+ */
183
+ forHost(host: string): TestHttpClient;
184
+ /** Return a new client with additional default headers on every request. */
185
+ withHeaders(headers: Record<string, string>): TestHttpClient;
186
+ get(path: string): TestHttpRequest;
187
+ post(path: string): TestHttpRequest;
188
+ put(path: string): TestHttpRequest;
189
+ patch(path: string): TestHttpRequest;
190
+ delete(path: string): TestHttpRequest;
191
+ private createRequest;
192
+ }
193
+ //#endregion
194
+ //#region src/sse/test-sse-connection.d.ts
195
+ /** A parsed Server-Sent Event. */
196
+ interface TestSseEvent {
197
+ data: string;
198
+ event?: string;
199
+ id?: string;
200
+ retry?: number;
201
+ }
202
+ /**
203
+ * TestSseConnection
204
+ *
205
+ * Reads a streaming `text/event-stream` response body and exposes queue-based
206
+ * wait/assert helpers over the parsed events.
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * const sse = await module.sse('/stream/events').connect();
211
+ * await sse.assertEventData('ping');
212
+ * await sse.waitForEnd();
213
+ * ```
214
+ */
215
+ declare class TestSseConnection {
216
+ private readonly response;
217
+ private readonly eventQueue;
218
+ private eventWaiters;
219
+ private streamEnded;
220
+ private endWaiters;
221
+ constructor(response: Response);
222
+ /** The raw `Response`. */
223
+ get raw(): Response;
224
+ /** Wait for the next event (rejects after `timeout` ms). */
225
+ waitForEvent(timeout?: number): Promise<TestSseEvent>;
226
+ /** Wait for the stream to end (rejects after `timeout` ms). */
227
+ waitForEnd(timeout?: number): Promise<void>;
228
+ /** Collect all remaining events until the stream ends. */
229
+ collectEvents(timeout?: number): Promise<TestSseEvent[]>;
230
+ /** Assert the next event matches the expected partial shape. */
231
+ assertEvent(expected: Partial<TestSseEvent>, timeout?: number): Promise<void>;
232
+ /** Assert the next event's `data` equals `expected`. */
233
+ assertEventData(expected: string, timeout?: number): Promise<void>;
234
+ /** Assert the next event's `data` is JSON equal to `expected`. */
235
+ assertJsonEventData<T>(expected: T, timeout?: number): Promise<void>;
236
+ private startReading;
237
+ private endStream;
238
+ private parseEvent;
239
+ private dispatchEvent;
240
+ }
241
+ //#endregion
242
+ //#region src/sse/test-sse-request.d.ts
243
+ /**
244
+ * TestSseRequest
245
+ *
246
+ * Builder for a Server-Sent Events connection. `connect()` issues a GET through
247
+ * `module.fetch()`, asserts a `text/event-stream` 200, and wraps the streaming
248
+ * body in a {@link TestSseConnection}.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * const sse = await module.sse('/stream/events').connect();
253
+ * await sse.assertEvent({ event: 'message', data: 'hello' });
254
+ * ```
255
+ */
256
+ declare class TestSseRequest {
257
+ private readonly path;
258
+ private readonly module;
259
+ private readonly requestHeaders;
260
+ private principal;
261
+ private resolver;
262
+ constructor(path: string, module: TestingModule);
263
+ /** Merge additional headers onto the SSE request. */
264
+ withHeaders(headers: Record<string, string>): this;
265
+ /** Authenticate the connection (see {@link TestHttpRequest.actingAs}). */
266
+ actingAs(principal: TestPrincipal, resolver?: ActingAsResolver): this;
267
+ /** Open the stream and return a live {@link TestSseConnection}. */
268
+ connect(): Promise<TestSseConnection>;
269
+ private applyAuthentication;
270
+ }
271
+ //#endregion
272
+ //#region src/ws/test-ws-connection.d.ts
273
+ /**
274
+ * TestWsConnection
275
+ *
276
+ * Wraps a live `WebSocket` with queue-based wait/assert helpers. Transport is
277
+ * supplied by the caller — the core harness never opens a socket itself (see
278
+ * `@velajs/testing/websocket-node`).
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * const ws = await module.ws('/ws/chat').connect();
283
+ * ws.send('hi');
284
+ * await ws.assertMessage('echo:hi');
285
+ * ws.close();
286
+ * ```
287
+ */
288
+ declare class TestWsConnection {
289
+ private readonly ws;
290
+ private readonly cleanup?;
291
+ private readonly messageQueue;
292
+ private messageWaiters;
293
+ private closeEvent;
294
+ private closeWaiters;
295
+ constructor(ws: WebSocket, cleanup?: (() => void | Promise<void>) | undefined);
296
+ /** The raw `WebSocket`. */
297
+ get raw(): WebSocket;
298
+ /** Send a message. */
299
+ send(data: string | ArrayBufferLike | ArrayBufferView): void;
300
+ /** Close the connection. */
301
+ close(code?: number, reason?: string): void;
302
+ /** Wait for the next message (rejects after `timeout` ms). */
303
+ waitForMessage(timeout?: number): Promise<string | ArrayBuffer>;
304
+ /** Wait for the connection to close (rejects after `timeout` ms). */
305
+ waitForClose(timeout?: number): Promise<{
306
+ code?: number;
307
+ reason?: string;
308
+ }>;
309
+ /** Assert the next message equals `expected`. */
310
+ assertMessage(expected: string, timeout?: number): Promise<void>;
311
+ /** Assert the connection closes, optionally with `expectedCode`. */
312
+ assertClosed(expectedCode?: number, timeout?: number): Promise<void>;
313
+ }
314
+ //#endregion
315
+ //#region src/ws/test-ws-request.d.ts
316
+ /**
317
+ * A transport adapter that performs the WebSocket upgrade and returns a live
318
+ * connection. Registered by a platform package (e.g. websocket-node).
319
+ */
320
+ type WsConnector = (module: TestingModule, path: string, headers: Headers) => Promise<TestWsConnection>;
321
+ /**
322
+ * Register the transport that {@link TestWsRequest.connect} uses. Called by a
323
+ * platform adapter's side-effect import (e.g. `@velajs/testing/websocket-node`).
324
+ */
325
+ declare function registerWsConnector(connector: WsConnector): void;
326
+ /** The currently registered WebSocket connector, if any. */
327
+ declare function getWsConnector(): WsConnector | null;
328
+ /**
329
+ * TestWsRequest
330
+ *
331
+ * Builder for a WebSocket connection. `connect()` requires a transport adapter
332
+ * to be registered; without one it throws a clear, actionable error.
333
+ *
334
+ * @example
335
+ * ```ts
336
+ * import '@velajs/testing/websocket-node';
337
+ * const ws = await module.ws('/ws/chat').connect();
338
+ * ws.send('hi');
339
+ * await ws.assertMessage('echo:hi');
340
+ * ```
341
+ */
342
+ declare class TestWsRequest {
343
+ private readonly path;
344
+ private readonly module;
345
+ private readonly requestHeaders;
346
+ private principal;
347
+ private resolver;
348
+ constructor(path: string, module: TestingModule);
349
+ /** Merge additional headers onto the upgrade request. */
350
+ withHeaders(headers: Record<string, string>): this;
351
+ /** Authenticate the connection (see {@link TestHttpRequest.actingAs}). */
352
+ actingAs(principal: TestPrincipal, resolver?: ActingAsResolver): this;
353
+ /** Open the socket via the registered transport adapter. */
354
+ connect(): Promise<TestWsConnection>;
355
+ private applyAuthentication;
356
+ }
357
+ //#endregion
358
+ //#region src/testing-module.d.ts
359
+ /** A test principal — an opaque object the auth resolver turns into headers. */
360
+ type TestPrincipal = Record<string, unknown>;
361
+ /**
362
+ * Turns a principal into request headers (session cookie, bearer token, …).
363
+ * The signature `(module, principal) => Promise<Headers>` is a cross-package
364
+ * contract: sibling packages (e.g. `@velajs/better-auth/testing`) build a
365
+ * resolver against it. Kept generic so `@velajs/testing` needs no auth deps.
366
+ */
367
+ type ActingAsResolver = (module: TestingModule, principal: TestPrincipal) => Promise<Headers>;
368
+ /**
369
+ * TestingModule
370
+ *
371
+ * The compiled test harness. Beyond `get`/`createApplication`/`close`, it adds
372
+ * Laravel-flavored ergonomics: a fluent HTTP client, SSE/WS builders, request-
373
+ * scope execution, seeding, and database assertion wrappers.
374
+ *
375
+ * @example
376
+ * ```ts
377
+ * const module = await Test.createTestingModule({ imports: [AppModule] }).compile();
378
+ * await module.http.post('/users').withBody({ name: 'A' }).send()
379
+ * .then((r) => r.assertCreated());
380
+ * ```
381
+ */
382
+ declare class TestingModule {
383
+ private readonly app;
384
+ private readonly container;
385
+ private _http;
386
+ private honoApp;
387
+ private authResolver;
388
+ constructor(app: VelaApplication, container: Container);
389
+ /** Resolve a provider from the root container. */
390
+ get<T>(token: Token<T>): T;
391
+ /** Build (once) and return the underlying application. */
392
+ createApplication(): Promise<VelaApplication>;
393
+ /** Lazy fluent HTTP client bound to this module. */
394
+ get http(): TestHttpClient;
395
+ /** Start an SSE connection builder for `path`. */
396
+ sse(path: string): TestSseRequest;
397
+ /** Start a WebSocket connection builder for `path` (needs a transport adapter). */
398
+ ws(path: string): TestWsRequest;
399
+ /**
400
+ * Drive a `Request` through the full Hono pipeline. The Hono app is built
401
+ * once and reused across requests.
402
+ */
403
+ fetch(request: Request, env?: unknown, ctx?: unknown): Promise<Response>;
404
+ /**
405
+ * Register a default auth resolver used by `actingAs(principal)` when no
406
+ * resolver is passed explicitly.
407
+ */
408
+ setAuthResolver(resolver: ActingAsResolver): this;
409
+ /** The default auth resolver, if one was registered. */
410
+ getAuthResolver(): ActingAsResolver | null;
411
+ /**
412
+ * Run `callback` inside a request-scoped child container seeded with a mock
413
+ * {@link RequestContext}, so REQUEST-scoped providers (and anything injecting
414
+ * `REQUEST_CONTEXT`) resolve. The child is disposed afterwards.
415
+ */
416
+ runInRequestScope<T>(callback: (container: Container) => T | Promise<T>): Promise<T>;
417
+ /**
418
+ * Run the given `@Seeder()` classes, each in its own request scope. Throws if
419
+ * a class is not a registered seeder. Requires `SeederModule` (or the seeders
420
+ * themselves) to be present in the module graph.
421
+ */
422
+ seed(...SeederClasses: Type<ISeeder>[]): Promise<void>;
423
+ /** Assert a row matching `where` exists in `table` (via a {@link TestDatabase}). */
424
+ assertDatabaseHas(db: TestDatabase, table: string, where: Record<string, unknown>): Promise<void>;
425
+ /** Assert no row matching `where` exists in `table`. */
426
+ assertDatabaseMissing(db: TestDatabase, table: string, where: Record<string, unknown>): Promise<void>;
427
+ /** Assert `table` has exactly `expected` rows. */
428
+ assertDatabaseCount(db: TestDatabase, table: string, expected: number): Promise<void>;
429
+ /** Dispose the application. */
430
+ close(signal?: string): Promise<void>;
431
+ private ensureHono;
432
+ /**
433
+ * Build a minimal, functional {@link RequestContext} for out-of-band request
434
+ * scopes. Vela has no `createMockRouterContext`; a real (empty) Hono `Context`
435
+ * backs the `hono` field so nothing dangles.
436
+ */
437
+ private createMockRequestContext;
438
+ }
439
+ //#endregion
440
+ //#region src/testing-module.builder.d.ts
441
+ declare class OverrideBy {
442
+ private readonly builder;
443
+ private readonly token;
444
+ constructor(builder: TestingModuleBuilder, token: Token);
445
+ useValue(value: unknown): TestingModuleBuilder;
446
+ useClass(cls: Type): TestingModuleBuilder;
447
+ useFactory(options: {
448
+ factory: (...args: unknown[]) => unknown;
449
+ inject?: Token[];
450
+ }): TestingModuleBuilder;
451
+ }
452
+ declare class TestingModuleBuilder {
453
+ private readonly metadata;
454
+ private overrides;
455
+ constructor(metadata: ModuleOptions);
456
+ overrideProvider(token: Token): OverrideBy;
457
+ overrideGuard(guard: Type): OverrideBy;
458
+ overridePipe(pipe: Type): OverrideBy;
459
+ overrideInterceptor(interceptor: Type): OverrideBy;
460
+ overrideFilter(filter: Type): OverrideBy;
461
+ private addOverride;
462
+ compile(): Promise<TestingModule>;
463
+ }
464
+ //#endregion
465
+ //#region src/test.d.ts
466
+ declare const Test: {
467
+ createTestingModule(metadata: ModuleOptions): TestingModuleBuilder;
468
+ };
469
+ //#endregion
470
+ //#region src/http/path-utils.d.ts
471
+ /**
472
+ * Read the value at a dot-notation path (e.g. `data.user.id`).
473
+ * Returns `undefined` when any segment along the way is null/undefined.
474
+ */
475
+ declare function getValueAtPath(obj: unknown, path: string): unknown;
476
+ /**
477
+ * Whether a dot-notation path exists on the object, even when the value at the
478
+ * path is `null`/`undefined`. Distinguishes "key present but null" from "key
479
+ * absent".
480
+ */
481
+ declare function hasValueAtPath(obj: unknown, path: string): boolean;
482
+ //#endregion
483
+ export { type ActingAsResolver, OverrideBy, Test, type TestDatabase, TestHttpClient, TestHttpRequest, type TestPrincipal, TestResponse, TestSseConnection, type TestSseEvent, TestSseRequest, TestWsConnection, TestWsRequest, TestingModule, TestingModuleBuilder, type WsConnector, getValueAtPath, getWsConnector, hasValueAtPath, registerWsConnector };
484
+ //# sourceMappingURL=index.d.ts.map