@ethlete/query 6.0.0-next.6 → 6.0.0-next.60

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +939 -0
  2. package/fesm2022/ethlete-query-devtools-contract.mjs +1 -0
  3. package/fesm2022/ethlete-query-ngrx-toolkit.mjs +411 -0
  4. package/fesm2022/ethlete-query-ngrx-toolkit.mjs.map +1 -0
  5. package/fesm2022/ethlete-query-testing.mjs +548 -169
  6. package/fesm2022/ethlete-query-testing.mjs.map +1 -1
  7. package/fesm2022/ethlete-query.mjs +14680 -7172
  8. package/fesm2022/ethlete-query.mjs.map +1 -1
  9. package/generators/deprecate-legacy-queries/migration.js +85 -0
  10. package/generators/deprecate-legacy-queries/schema.json +26 -0
  11. package/generators/generators.json +30 -0
  12. package/generators/migrate-from-ngrx-toolkit/args-assignability.js +140 -0
  13. package/generators/migrate-from-ngrx-toolkit/consumers.js +521 -0
  14. package/generators/migrate-from-ngrx-toolkit/creators.js +301 -0
  15. package/generators/migrate-from-ngrx-toolkit/facade.js +295 -0
  16. package/generators/migrate-from-ngrx-toolkit/feature.js +513 -0
  17. package/generators/migrate-from-ngrx-toolkit/migration.js +140 -0
  18. package/generators/migrate-from-ngrx-toolkit/report.js +154 -0
  19. package/generators/migrate-from-ngrx-toolkit/schema.json +42 -0
  20. package/generators/migrate-from-ngrx-toolkit/store-removal.js +633 -0
  21. package/generators/migrate-from-ngrx-toolkit/ts-edits.js +128 -0
  22. package/generators/migrate-query-client-features/migration.js +144 -0
  23. package/generators/migrate-query-client-features/report.js +136 -0
  24. package/generators/migrate-query-client-features/schema.json +26 -0
  25. package/generators/migrate-query-opt-in-features/migration.js +195 -0
  26. package/generators/migrate-query-opt-in-features/report.js +143 -0
  27. package/generators/migrate-query-opt-in-features/schema.json +31 -0
  28. package/generators/migrate-to-query-v3/auth-provider-scaffold.js +160 -0
  29. package/generators/migrate-to-query-v3/cleanup-migration.js +247 -125
  30. package/generators/migrate-to-query-v3/http-client-check.js +93 -0
  31. package/generators/migrate-to-query-v3/legacy-prepare-migration.js +129 -40
  32. package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +274 -152
  33. package/generators/migrate-to-query-v3/migration-scope.js +40 -0
  34. package/generators/migrate-to-query-v3/migration.js +19 -10
  35. package/generators/migrate-to-query-v3/module-graph.js +165 -0
  36. package/generators/migrate-to-query-v3/query-client-migration.js +342 -160
  37. package/generators/migrate-to-query-v3/rename-symbols.js +233 -0
  38. package/generators/migrate-to-query-v3/report.js +40 -7
  39. package/generators/migrate-to-query-v3/schema.json +12 -0
  40. package/generators/migrate-to-query-v3/shared.js +97 -3
  41. package/generators/move-devtools-contract-imports/migration.js +155 -0
  42. package/generators/move-devtools-contract-imports/moved-names.js +153 -0
  43. package/generators/move-devtools-contract-imports/schema.json +26 -0
  44. package/generators/prep-for-query-v3/migration.js +465 -65
  45. package/generators/report-legacy-query-apis/migration.js +256 -0
  46. package/generators/report-legacy-query-apis/schema.json +25 -0
  47. package/migrations/query-field-default-types.md +43 -0
  48. package/migrations/search-query-field-string.md +43 -0
  49. package/migrations.json +87 -0
  50. package/package.json +34 -14
  51. package/types/ethlete-query-devtools-contract.d.ts +2 -0
  52. package/types/ethlete-query-ngrx-toolkit.d.ts +131 -0
  53. package/types/ethlete-query-testing.d.ts +295 -160
  54. package/types/ethlete-query.d.ts +9755 -3682
@@ -0,0 +1,131 @@
1
+ import * as i0 from "@angular/core";
2
+ import { Injector, PipeTransform } from "@angular/core";
3
+ import { Observable, Subject } from "rxjs";
4
+ import { AnyQueryCreator, QueryArgsOf, QueryHeaders, ResponseType } from "@ethlete/query";
5
+ /** The call state of a toolkit handle, as `@tomtomb/ngrx-toolkit` names it. */
6
+ export declare const CallState: {
7
+ readonly INIT: "INIT";
8
+ readonly LOADING: "LOADING";
9
+ readonly SUCCESS: "SUCCESS";
10
+ readonly ERROR: "ERROR";
11
+ };
12
+ export type CallState = (typeof CallState)[keyof typeof CallState];
13
+ /** The error shape of `@tomtomb/ngrx-toolkit`: the HTTP status, the error message and the response body. */
14
+ export type ToolkitError<T = unknown> = {
15
+ status: number | string;
16
+ message: string;
17
+ data: T | null;
18
+ };
19
+ export type ToolkitActionOptions = {
20
+ headers?: QueryHeaders;
21
+ extras?: {
22
+ skipCache?: boolean;
23
+ };
24
+ };
25
+ /**
26
+ * The toolkit-shaped args of a query creator: `queryParams` holds the path params, `params` the query string and
27
+ * `body` the body, the way `@tomtomb/ngrx-toolkit` action groups named them.
28
+ */
29
+ export type ActionCallArgs<TCreator extends AnyQueryCreator> = { [K in keyof QueryArgsOf<TCreator> as K extends 'pathParams' ? 'queryParams' : K extends 'queryParams' ? 'params' : K extends 'body' ? 'body' : never]: QueryArgsOf<TCreator>[K]; } & {
30
+ actionOptions?: ToolkitActionOptions;
31
+ };
32
+ export type ToolkitPollingOptions = {
33
+ intervalDuration: number;
34
+ killSwitch: Subject<boolean>;
35
+ };
36
+ /** The handle `toolkitCall` returns, shaped like the `MappedEntityState` of `@tomtomb/ngrx-toolkit`. */
37
+ export type MappedEntityState<TCreator extends AnyQueryCreator> = {
38
+ response$: Observable<ResponseType<QueryArgsOf<TCreator>> | null>;
39
+ cachedResponse$: Observable<ResponseType<QueryArgsOf<TCreator>>>;
40
+ error$: Observable<ToolkitError | null>;
41
+ args$: Observable<ActionCallArgs<TCreator> | null>;
42
+ isInit$: Observable<boolean | null>;
43
+ isLoading$: Observable<boolean | null>;
44
+ isSuccess$: Observable<boolean | null>;
45
+ isError$: Observable<boolean | null>;
46
+ timestamp$: Observable<number | null>;
47
+ callState$: Observable<CallState | null>;
48
+ refresh: () => void;
49
+ remove: () => void;
50
+ startPolling: (options: ToolkitPollingOptions) => void;
51
+ stopPolling: () => void;
52
+ };
53
+ export type AnyMappedEntityState = MappedEntityState<any>;
54
+ type ExtractFrom<T> = T extends Observable<infer U> ? U : never;
55
+ /** What `suspense` and `suspenseMulti` render for a handle: every stream's current value, plus the handle's methods. */
56
+ type ToolkitSuspenseState<T extends AnyMappedEntityState | null | undefined> = T extends AnyMappedEntityState ? {
57
+ args: ExtractFrom<T['args$']>;
58
+ cachedResponse: ExtractFrom<T['cachedResponse$']> | null;
59
+ callState: ExtractFrom<T['callState$']>;
60
+ error: ExtractFrom<T['error$']>;
61
+ isError: ExtractFrom<T['isError$']>;
62
+ isInit: ExtractFrom<T['isInit$']>;
63
+ isLoading: ExtractFrom<T['isLoading$']>;
64
+ isSuccess: ExtractFrom<T['isSuccess$']>;
65
+ response: ExtractFrom<T['response$']>;
66
+ timestamp: ExtractFrom<T['timestamp$']>;
67
+ refresh: () => void;
68
+ remove: () => void;
69
+ startPolling: (options: ToolkitPollingOptions) => void;
70
+ stopPolling: () => void;
71
+ } : T;
72
+ /** Subscribes to a toolkit handle and renders its current state, like the `suspense` pipe of `@tomtomb/ngrx-toolkit`. */
73
+ export declare class SuspensePipe implements PipeTransform {
74
+ transform: <T extends AnyMappedEntityState>(value: T | null | undefined) => ToolkitSuspenseState<T>;
75
+ static ɵfac: i0.ɵɵFactoryDeclaration<SuspensePipe, never>;
76
+ static ɵpipe: i0.ɵɵPipeDeclaration<SuspensePipe, "suspense", true>;
77
+ }
78
+ type AnyHandleRecord = Record<string, AnyMappedEntityState | null | undefined>;
79
+ export type ToolkitSuspenseMultiState<T extends AnyHandleRecord> = { [K in keyof T]: ToolkitSuspenseState<T[K]>; };
80
+ /**
81
+ * Subscribes to a record of toolkit handles and renders each one's current state, like the `suspenseMulti` pipe of
82
+ * `@tomtomb/ngrx-toolkit`. Nothing renders until every non-null handle has emitted.
83
+ */
84
+ export declare class SuspenseMultiPipe implements PipeTransform {
85
+ transform: <T extends AnyHandleRecord>(value: T | null | undefined) => ToolkitSuspenseMultiState<T>;
86
+ static ɵfac: i0.ɵɵFactoryDeclaration<SuspenseMultiPipe, never>;
87
+ static ɵpipe: i0.ɵɵPipeDeclaration<SuspenseMultiPipe, "suspenseMulti", true>;
88
+ }
89
+ /** Exports the `suspense` and `suspenseMulti` pipes, like the `NgRxToolkitModule` of `@tomtomb/ngrx-toolkit`. */
90
+ export declare class NgRxToolkitModule {
91
+ static ɵfac: i0.ɵɵFactoryDeclaration<NgRxToolkitModule, never>;
92
+ static ɵmod: i0.ɵɵNgModuleDeclaration<NgRxToolkitModule, never, [typeof SuspensePipe, typeof SuspenseMultiPipe], [typeof SuspensePipe, typeof SuspenseMultiPipe]>;
93
+ static ɵinj: i0.ɵɵInjectorDeclaration<NgRxToolkitModule>;
94
+ }
95
+ export type ToolkitCallOptions = {
96
+ /**
97
+ * The injector the handle's query lives in. Required outside an injection context, which is where a
98
+ * `providedIn: 'root'` facade calls from; pass the facade's `inject(Injector)`.
99
+ */
100
+ injector?: Injector;
101
+ };
102
+ /**
103
+ * Returns the handle for a creator and args without executing it - the `FacadeBase.select(group, actionId)` of
104
+ * `@tomtomb/ngrx-toolkit`. Args that build the same request (in any key order, other keys ignored) give the same
105
+ * handle `toolkitCall` returns, so `toolkitSelect(getTeam, args, { injector }).refresh()` re-runs a request another
106
+ * component started.
107
+ */
108
+ export declare const toolkitSelect: <TCreator extends AnyQueryCreator>(creator: TCreator, args: ActionCallArgs<TCreator>, options?: ToolkitCallOptions) => MappedEntityState<TCreator>;
109
+ /**
110
+ * Executes a query creator with toolkit-shaped args and returns its handle - the `FacadeBase.call(group, args)` of
111
+ * `@tomtomb/ngrx-toolkit`. Every call sends the request again, unless one with equal args is still in flight, which it
112
+ * joins; equal args return the same handle, which lives as long as the injector.
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * @Injectable({ providedIn: 'root' })
117
+ * export class TeamFacade {
118
+ * private injector = inject(Injector);
119
+ *
120
+ * getTeam(args: ActionCallArgs<typeof getTeam>) {
121
+ * return toolkitCall(getTeam, args, { injector: this.injector });
122
+ * }
123
+ * }
124
+ * ```
125
+ */
126
+ export declare const toolkitCall: <TCreator extends AnyQueryCreator>(creator: TCreator, args: ActionCallArgs<TCreator>, options?: ToolkitCallOptions) => MappedEntityState<TCreator>;
127
+ /** Emits `true` while any of the given loading streams is `true`. Nullish entries are skipped. */
128
+ export declare const joinLoading: (observables: (Observable<boolean | null> | null | undefined)[]) => Observable<boolean>;
129
+ /** Emits every non-null error of the given error streams, or `null` when there is none. Nullish entries are skipped. */
130
+ export declare const joinErrors: (observables: (Observable<ToolkitError | null> | null | undefined)[]) => Observable<ToolkitError[] | null>;
131
+ export type { ToolkitSuspenseState };
@@ -1,167 +1,302 @@
1
- import { AnyCreateQueryClientResult, QueryClientRef, createGetQuery, createPostQuery, createPutQuery, createPatchQuery, createDeleteQuery, BearerAuthProviderFeatureContext, AnyQueryBuilder, BearerAuthProvider, AuthQueryBuilder, TokenRefreshQueryBuilder, TokenRefreshQueryConfig } from '@ethlete/query';
2
- import { HttpClient } from '@angular/common/http';
3
- import { HttpTestingController } from '@angular/common/http/testing';
4
- import { Injector } from '@angular/core';
5
-
6
- type QueryTestSetup = {
7
- queryClient: NonNullable<ReturnType<AnyCreateQueryClientResult[1]>>;
8
- queryClientRef: QueryClientRef;
9
- httpClient: HttpClient;
10
- httpTesting: HttpTestingController;
11
- injector: Injector;
12
- baseUrl: string;
13
- createGet: ReturnType<typeof createGetQuery>;
14
- createPost: ReturnType<typeof createPostQuery>;
15
- createPut: ReturnType<typeof createPutQuery>;
16
- createPatch: ReturnType<typeof createPatchQuery>;
17
- createDelete: ReturnType<typeof createDeleteQuery>;
1
+ import { AnyCreateQueryClientResult, AnyQueryBuilder, AuthQueryBuilder, BearerAuthProvider, BearerAuthProviderFeatureContext, FeatureRegistry, PersistedQueryEntry, QueryClientRef, QueryPersistenceAdapter, SocketMessageView, TokenRefreshQueryBuilder, TokenRefreshQueryConfig, WebSocketClientIo, createDeleteQuery, createGetQuery, createPatchQuery, createPostQuery, createPutQuery } from "@ethlete/query";
2
+ import { HttpClient } from "@angular/common/http";
3
+ import { HttpTestingController } from "@angular/common/http/testing";
4
+ import { Injector } from "@angular/core";
5
+ export type QueryTestSetup = {
6
+ queryClient: NonNullable<ReturnType<AnyCreateQueryClientResult['inject']>>;
7
+ queryClientRef: QueryClientRef;
8
+ httpClient: HttpClient;
9
+ httpTesting: HttpTestingController;
10
+ injector: Injector;
11
+ baseUrl: string;
12
+ createGet: ReturnType<typeof createGetQuery>;
13
+ createPost: ReturnType<typeof createPostQuery>;
14
+ createPut: ReturnType<typeof createPutQuery>;
15
+ createPatch: ReturnType<typeof createPatchQuery>;
16
+ createDelete: ReturnType<typeof createDeleteQuery>;
17
+ /**
18
+ * Puts back the `console.error` that was installed before the first {@link setupQueryTest} call. Runs
19
+ * on its own when the `TestBed` module is reset; idempotent, and safe to call from any setup.
20
+ */
21
+ restoreConsole: () => void;
18
22
  };
19
- type QueryTestSetupConfig = {
20
- baseUrl?: string;
21
- name?: string;
22
- mockErrorHandler?: boolean;
23
+ export type QueryTestSetupConfig = {
24
+ baseUrl?: string;
25
+ name?: string;
26
+ /**
27
+ * `true` swallows the `ErrorHandler` reports of failed requests and rethrows every other error, `'all'`
28
+ * swallows everything, `false` keeps Angular's own handler.
29
+ * @default true
30
+ */
31
+ mockErrorHandler?: boolean | 'all';
23
32
  };
24
- declare const setupQueryTest: (config?: QueryTestSetupConfig) => QueryTestSetup;
25
-
26
- type AnyFeatureBuilder = (context: BearerAuthProviderFeatureContext<any, readonly AnyQueryBuilder[]>) => {
27
- type: string;
28
- instance: unknown;
33
+ export declare const setupQueryTest: (config?: QueryTestSetupConfig) => QueryTestSetup;
34
+ export type AnyFeatureBuilder = (context: BearerAuthProviderFeatureContext<any, readonly AnyQueryBuilder[]>) => {
35
+ type: string;
36
+ instance: unknown;
29
37
  };
30
- type AuthTestSetupConfig<TLoginArgs extends {
31
- body: Record<string, unknown>;
32
- response: {
33
- accessToken: string;
34
- refreshToken: string;
35
- };
36
- } = {
37
- body: Record<string, unknown>;
38
- response: {
39
- accessToken: string;
40
- refreshToken: string;
41
- };
42
- }, TRefreshArgs extends {
43
- body: Record<string, unknown>;
44
- response: {
45
- accessToken: string;
46
- refreshToken: string;
47
- };
48
- } = {
49
- body: Record<string, unknown>;
50
- response: {
51
- accessToken: string;
52
- refreshToken: string;
53
- };
54
- }, TFeatures extends readonly AnyFeatureBuilder[] = [], TBearerData = unknown> = {
55
- /** The query test setup instance */
56
- querySetup: QueryTestSetup;
57
- /** Login endpoint path. Default: '/auth/login' */
58
- loginPath?: string;
59
- /** Refresh endpoint path. Default: '/auth/refresh' */
60
- refreshPath?: string;
61
- /** Whether to enable auto-retry on 401. Default: false */
62
- autoRetryOn401?: boolean;
63
- /** Function to extract tokens from login response */
64
- extractLoginTokens?: (response: TLoginArgs['response']) => {
65
- accessToken: string;
66
- refreshToken: string;
67
- };
68
- /** Function to extract tokens from refresh response */
69
- extractRefreshTokens?: (response: TRefreshArgs['response']) => {
70
- accessToken: string;
71
- refreshToken: string;
72
- };
73
- /** Feature builders for additional auth functionality */
74
- features?: [...TFeatures];
75
- /** Custom bearer decrypt function for testing */
76
- bearerDecryptFn?: (token: string) => TBearerData;
77
- /** Whether to disable multi-tab sync. Default: false */
78
- multiTabSync?: false | {
79
- enabled?: boolean;
80
- channelName?: string;
81
- syncTokens?: boolean;
82
- syncLogout?: boolean;
83
- leaderElection?: boolean;
84
- };
85
- /** Refresh strategy configuration. See TokenRefreshQueryConfig for details */
86
- refreshStrategy?: TokenRefreshQueryConfig<TRefreshArgs>['refreshStrategy'];
87
- /** Minimum refresh interval in milliseconds */
88
- minRefreshInterval?: number;
89
- /** Whether to refresh if token is already expired */
90
- refreshIfExpired?: boolean;
91
- /** Property name for expiration in JWT */
92
- expiresInPropertyName?: string;
38
+ type AuthTestArgs = {
39
+ body: Record<string, unknown>;
40
+ response: {
41
+ accessToken: string;
42
+ refreshToken: string;
43
+ };
93
44
  };
94
- type AuthTestSetup<TLoginArgs extends {
95
- body: Record<string, unknown>;
96
- response: {
97
- accessToken: string;
98
- refreshToken: string;
99
- };
100
- } = {
101
- body: Record<string, unknown>;
102
- response: {
103
- accessToken: string;
104
- refreshToken: string;
105
- };
106
- }, TRefreshArgs extends {
107
- body: Record<string, unknown>;
108
- response: {
109
- accessToken: string;
110
- refreshToken: string;
111
- };
112
- } = {
113
- body: Record<string, unknown>;
114
- response: {
115
- accessToken: string;
116
- refreshToken: string;
117
- };
118
- }, TFeatures extends readonly AnyFeatureBuilder[] = [], TBearerData = unknown> = {
119
- /** The bearer auth provider instance */
120
- auth: BearerAuthProvider<[
121
- AuthQueryBuilder<'login', TLoginArgs>,
122
- TokenRefreshQueryBuilder<'refresh', TRefreshArgs>
123
- ], TFeatures, TBearerData>;
124
- /** Helper to login a user and flush the HTTP request */
125
- login: (credentials: TLoginArgs['body'], response: TLoginArgs['response']) => void;
126
- /** Helper to trigger a refresh and flush the HTTP request */
127
- refresh: (token: string, response: TRefreshArgs['response']) => void;
128
- /** Helper to make a secure request that requires authentication */
129
- makeSecureRequest: (route: string) => void;
45
+ /** The query builders of the provider {@link setupAuthTest} builds: a `login` and a `refresh` query. */
46
+ export type AuthTestQueryBuilders<TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs> = [AuthQueryBuilder<'login', TLoginArgs>, TokenRefreshQueryBuilder<'refresh', TRefreshArgs>];
47
+ /** A feature for the provider {@link setupAuthTest} builds, typed against its `login`/`refresh` queries. */
48
+ export type AuthTestFeatureBuilder = (context: BearerAuthProviderFeatureContext<any, AuthTestQueryBuilders>) => unknown;
49
+ export type AuthTestSetupConfig<TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs, TFeatures extends readonly AuthTestFeatureBuilder[] = [], TBearerData = unknown> = {
50
+ /** The query test setup instance */
51
+ querySetup: QueryTestSetup;
52
+ /** Login endpoint path. Default: '/auth/login' */
53
+ loginPath?: string;
54
+ /** Refresh endpoint path. Default: '/auth/refresh' */
55
+ refreshPath?: string;
56
+ /** Whether to refresh and retry a secure request that answers 401. Default: true, as in production */
57
+ autoRetryOn401?: boolean;
58
+ /** Function to extract tokens from login response */
59
+ extractLoginTokens?: (response: TLoginArgs['response']) => {
60
+ accessToken: string;
61
+ refreshToken: string;
62
+ };
63
+ /** Function to extract tokens from refresh response */
64
+ extractRefreshTokens?: (response: TRefreshArgs['response']) => {
65
+ accessToken: string;
66
+ refreshToken: string;
67
+ };
68
+ /** Builds the refresh request from the refresh token. See TokenRefreshQueryConfig for details */
69
+ buildRefreshArgs?: TokenRefreshQueryConfig<TRefreshArgs>['buildArgs'];
70
+ /** Feature builders for additional auth functionality */
71
+ features?: TFeatures;
72
+ /** Custom bearer decrypt function for testing */
73
+ bearerDecryptFn?: (token: string) => TBearerData;
74
+ /** Refresh strategy configuration. See TokenRefreshQueryConfig for details */
75
+ refreshStrategy?: TokenRefreshQueryConfig<TRefreshArgs>['refreshStrategy'];
76
+ /** Minimum refresh interval in milliseconds */
77
+ minRefreshInterval?: number;
78
+ /** Whether to refresh if token is already expired */
79
+ refreshIfExpired?: boolean;
80
+ /** Property name for expiration in JWT */
81
+ expiresInPropertyName?: string;
82
+ /** What to do when the refresh query fails for good. Defaults to the provider's own policy */
83
+ onRefreshFailure?: TokenRefreshQueryConfig<TRefreshArgs>['onRefreshFailure'];
130
84
  };
131
- declare const setupAuthTest: <TLoginArgs extends {
132
- body: Record<string, unknown>;
133
- response: {
134
- accessToken: string;
135
- refreshToken: string;
136
- };
137
- } = {
138
- body: Record<string, unknown>;
139
- response: {
140
- accessToken: string;
141
- refreshToken: string;
142
- };
143
- }, TRefreshArgs extends {
144
- body: Record<string, unknown>;
145
- response: {
146
- accessToken: string;
147
- refreshToken: string;
148
- };
149
- } = {
150
- body: Record<string, unknown>;
151
- response: {
152
- accessToken: string;
153
- refreshToken: string;
154
- };
155
- }, TFeatures extends readonly AnyFeatureBuilder[] = readonly AnyFeatureBuilder[], TBearerData = unknown>(config: AuthTestSetupConfig<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>) => AuthTestSetup<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>;
156
-
157
- declare const expectAndFlush: <T = unknown>(httpTesting: HttpTestingController, url: string, response: T, statusOpts?: {
158
- status: number;
159
- statusText: string;
85
+ export type AuthTestSetup<TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs, TFeatures extends readonly AuthTestFeatureBuilder[] = [], TBearerData = unknown> = {
86
+ /** The bearer auth provider instance */
87
+ auth: Omit<BearerAuthProvider<AuthTestQueryBuilders<TLoginArgs, TRefreshArgs>, [], TBearerData>, 'features'> & {
88
+ features: FeatureRegistry<TFeatures, AuthTestQueryBuilders<TLoginArgs, TRefreshArgs>>;
89
+ };
90
+ /** Helper to login a user and flush the HTTP request */
91
+ login: (credentials: TLoginArgs['body'], response: TLoginArgs['response']) => void;
92
+ /** Helper to trigger a refresh and flush the HTTP request. Sends `buildRefreshArgs(token)` when configured, else `{ body: { token } }` */
93
+ refresh: (token: string, response: TRefreshArgs['response']) => void;
94
+ /** Helper to make a secure request that requires authentication */
95
+ makeSecureRequest: (route: string) => void;
96
+ };
97
+ export declare const setupAuthTest: <TLoginArgs extends AuthTestArgs = AuthTestArgs, TRefreshArgs extends AuthTestArgs = AuthTestArgs, TFeatures extends readonly AuthTestFeatureBuilder[] = readonly AuthTestFeatureBuilder[], TBearerData = unknown>(config: AuthTestSetupConfig<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>) => AuthTestSetup<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>;
98
+ /**
99
+ * Test doubles for the two browser APIs the query client's multi-tab sync is built on:
100
+ * `BroadcastChannel` and the Web Locks API (`navigator.locks`).
101
+ *
102
+ * Both fakes are process-wide and shared between every consumer created while they are installed,
103
+ * which is exactly what makes "two tabs" testable: create two query clients in one test and they
104
+ * talk to each other over the fake bus and contend for the same fake locks, going through the real
105
+ * production code path on both sides.
106
+ *
107
+ * Neither jsdom nor happy-dom can stand in here - jsdom ships a `BroadcastChannel` that never
108
+ * delivers to other instances, and no `navigator.locks` at all.
109
+ */
110
+ /** Something posted on the fake bus, as seen by a receiver. */
111
+ export type FakeBroadcastMessage = {
112
+ /** The channel name it was posted on. */
113
+ channel: string;
114
+ /** The structured-cloned payload. */
115
+ data: unknown;
116
+ };
117
+ export type FakeBroadcastChannelHandle = {
118
+ /** Every message posted on the bus, in order, across all channel names. */
119
+ posted: FakeBroadcastMessage[];
120
+ /** Uninstalls the fake and restores whatever `BroadcastChannel` was there before. */
121
+ restore: () => void;
122
+ };
123
+ /**
124
+ * Replaces `globalThis.BroadcastChannel` with an in-memory bus.
125
+ *
126
+ * Messages are structured-cloned at post time (so a non-cloneable payload throws exactly like the
127
+ * real API does), delivered as a task (never synchronously, and after every pending microtask), and
128
+ * never delivered back to the channel that posted them - all three matching browser behavior. Delivery
129
+ * does not depend on fake timers; `await flushMultiTabSync()` in a spec flushes it.
130
+ */
131
+ export declare const installFakeBroadcastChannel: () => FakeBroadcastChannelHandle;
132
+ export type FakeWebLocksHandle = {
133
+ /** The names currently held, in no particular order. */
134
+ heldNames: () => string[];
135
+ /** The names with at least one request queued behind the holder. */
136
+ pendingNames: () => string[];
137
+ /** Uninstalls the fake and restores whatever `navigator.locks` was there before. */
138
+ restore: () => void;
139
+ };
140
+ /**
141
+ * Installs an in-memory `navigator.locks` implementing exclusive locks: one holder per name, the
142
+ * rest queued FIFO, granted on a microtask (never synchronously), released when the callback's
143
+ * promise settles. `signal` is honored for queued requests and - as in the real API - ignored once a
144
+ * request has been granted.
145
+ *
146
+ * `ifAvailable` is a try-lock: a request that cannot be granted straight away runs its callback with
147
+ * `null` instead of queueing, exactly as the real API does. `steal` releases the current holder -
148
+ * rejecting its request with an `AbortError` - and is granted ahead of everything queued.
149
+ *
150
+ * `mode: 'shared'` is not implemented; nothing in the library uses it.
151
+ */
152
+ export declare const installFakeWebLocks: () => FakeWebLocksHandle;
153
+ /**
154
+ * Waits for the fakes' pending work - message delivery (a task) and lock grants (a microtask) - even
155
+ * under fake timers. Awaiting it once is enough for a single hop; a grant that has to wait for another
156
+ * tab's release, or a reply to a delivered message, needs one await per hop.
157
+ */
158
+ export declare const flushMultiTabSync: () => Promise<void>;
159
+ /**
160
+ * An in-memory stand-in for the query client's persisted response store.
161
+ *
162
+ * jsdom has no IndexedDB at all, so this is how persistence is tested - and because the storage
163
+ * adapter is a documented seam rather than a mock of one, a spec running against this exercises the
164
+ * real engine, repository and request code on both the write and the read side.
165
+ *
166
+ * Everything resolves on a microtask, never synchronously, matching the real thing: `await
167
+ * Promise.resolve()` (or `flushMicrotasks`) is what lets a hydration land in a spec.
168
+ */
169
+ export type FakeQueryPersistenceStoreHandle = {
170
+ /** Pass this as `persistence: { adapter }`. Two clients sharing one handle share one store. */
171
+ adapter: QueryPersistenceAdapter;
172
+ /** Pre-populates the store, as if a previous session had written these entries. */
173
+ seed: (entries: PersistedQueryEntry[]) => void;
174
+ /** Every entry currently held, in insertion order. */
175
+ entries: () => PersistedQueryEntry[];
176
+ /** The entry held under `key`, or `undefined`. */
177
+ entry: (key: string) => PersistedQueryEntry | undefined;
178
+ /** How often each adapter method has been called. */
179
+ calls: () => Record<'loadIndex' | 'read' | 'write' | 'remove' | 'clear', number>;
180
+ /** Makes the next `count` writes reject - a full disk, by default. */
181
+ failNextWrites: (count: number, error?: unknown) => void;
182
+ /** Makes the next `count` removals reject. */
183
+ failNextRemoves: (count: number, error?: unknown) => void;
184
+ /** Makes the next `loadIndex` reject. */
185
+ failNextLoadIndex: (error?: unknown) => void;
186
+ /**
187
+ * Holds every subsequent `read` pending until {@link flushReads} is called, for testing the window
188
+ * between a cold mount and its hydration (a slow disk, or a mount that happened before startup
189
+ * finished). Each held read returns what the store held when it started, as `loadIndex` does.
190
+ */
191
+ deferReads: () => void;
192
+ /** Settles the reads held by {@link deferReads} and stops deferring. */
193
+ flushReads: () => Promise<void>;
194
+ /**
195
+ * Holds every subsequent `loadIndex` pending until {@link flushLoadIndex} is called, for testing
196
+ * what happens to the store while a client's startup is still reading its index (a real disk takes
197
+ * milliseconds). The snapshot each held load returns is the one it would have read when it started.
198
+ */
199
+ deferLoadIndex: () => void;
200
+ /** Settles the index loads held by {@link deferLoadIndex} and stops deferring. */
201
+ flushLoadIndex: () => Promise<void>;
202
+ };
203
+ export declare const createFakeQueryPersistenceStore: () => FakeQueryPersistenceStoreHandle;
204
+ export declare const expectAndFlush: <T = unknown>(httpTesting: HttpTestingController, url: string, response: T, statusOpts?: {
205
+ status: number;
206
+ statusText: string;
160
207
  }) => void;
161
- declare const expectFlushAndWait: <T = unknown>(httpTesting: HttpTestingController, url: string, response: T, statusOpts?: {
162
- status: number;
163
- statusText: string;
208
+ export declare const expectFlushAndWait: <T = unknown>(httpTesting: HttpTestingController, url: string, response: T, statusOpts?: {
209
+ status: number;
210
+ statusText: string;
164
211
  }) => void;
165
-
166
- export { expectAndFlush, expectFlushAndWait, setupAuthTest, setupQueryTest };
167
- export type { AnyFeatureBuilder, AuthTestSetup, AuthTestSetupConfig, QueryTestSetup, QueryTestSetupConfig };
212
+ /** A scripted stand-in for `socket.io-client`'s `io`, driven from a spec. */
213
+ export type WebSocketTestDouble = {
214
+ /** Pass this as `createWebSocketClient({ io })`. */
215
+ io: WebSocketClientIo;
216
+ /** The url and options the client called the factory with, or `null` until it did. */
217
+ connection: () => {
218
+ url: string;
219
+ transports: string[] | undefined;
220
+ } | null;
221
+ /** The `withCredentials` the client asked the factory for, or `null` until it called it. */
222
+ withCredentials: () => boolean | null;
223
+ /** The auth payload of every handshake, oldest first - `null` for one made without `auth`. */
224
+ handshakes: () => (object | null)[];
225
+ /** Every message the client emitted, newest last - room joins and leaves included, buffered ones too. */
226
+ sent: () => {
227
+ event: string;
228
+ data: unknown;
229
+ }[];
230
+ /** Every message that reached the server, newest last. An emit buffered while offline lands here on the next connect. */
231
+ delivered: () => {
232
+ event: string;
233
+ data: unknown;
234
+ }[];
235
+ /**
236
+ * Whether the client asked the socket to connect, and whether it has since disconnected it. `active` is
237
+ * socket.io's own flag - `false` after a server disconnect or a rejected handshake, until the next
238
+ * `connect()`; `connectCalls` counts every `connect()`.
239
+ */
240
+ state: () => {
241
+ connectRequested: boolean;
242
+ disconnected: boolean;
243
+ active: boolean;
244
+ connectCalls: number;
245
+ };
246
+ /**
247
+ * Complete the handshake: flush every buffered emit, then turn the client's `isConnected` true. Pass
248
+ * `recovered: true` for a connection-state-recovered reconnect, in which the server kept the rooms.
249
+ */
250
+ serverConnect: (options?: {
251
+ recovered?: boolean;
252
+ }) => void;
253
+ /**
254
+ * Drop the connection, so `isConnected` turns false. The default reason, `'transport close'`, is one
255
+ * socket.io recovers from itself; `'io server disconnect'` is the server kicking the socket, after which
256
+ * socket.io only reconnects when `connect()` is called again.
257
+ */
258
+ serverDisconnect: (options?: {
259
+ reason?: 'transport close' | 'io server disconnect';
260
+ }) => void;
261
+ /**
262
+ * Reject the handshake the way a server middleware does: the `auth` payload is recorded in
263
+ * {@link handshakes}, `connect_error` fires and the socket stops reconnecting on its own.
264
+ */
265
+ serverRejectHandshake: (message?: string) => void;
266
+ /**
267
+ * Let the ping expire: the socket still reports itself connected, but buffers every emit like socket.io
268
+ * does until it notices and closes. Follow it with {@link serverDisconnect}.
269
+ */
270
+ serverPingExpire: () => void;
271
+ /** Deliver a message the way the server would. */
272
+ serverSend: (message: SocketMessageView) => void;
273
+ /** Deliver a raw frame, for the malformed-payload path. */
274
+ serverSendRaw: (frame: string) => void;
275
+ };
276
+ /**
277
+ * A socket io test double, for specs around `createWebSocketClient` - which takes its `io` factory as
278
+ * an option precisely so a spec (or an app that never opens a socket) needs no `socket.io-client`.
279
+ *
280
+ * Nothing here connects on its own: the client's own `connect()` only flips
281
+ * {@link WebSocketTestDouble.state}, and it is {@link WebSocketTestDouble.serverConnect} that fires
282
+ * the `connect` listener. That split is what makes "joined a room while disconnected, and it was
283
+ * re-joined after a reconnect" testable.
284
+ */
285
+ export declare const createWebSocketTestDouble: () => WebSocketTestDouble;
286
+ export type MintTestTokenOptions = {
287
+ /** @default 900000 (15 minutes) */
288
+ expiresInMs?: number;
289
+ /** Extra payload claims, e.g. the user id or roles `bearerData()` and the auth guards read. */
290
+ claims?: Record<string, unknown>;
291
+ };
292
+ /**
293
+ * Mints an unsigned JWT the bearer auth provider can decode, with `iat`, `exp` and a unique `jti`.
294
+ * Hand it to `setTokens()` to put the app's own provider into a session.
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * TestBed.runInInjectionContext(() => authProviderRef.inject().setTokens(mintTestToken({ claims: { sub: '1' } }), 'refresh'));
299
+ * ```
300
+ */
301
+ export declare const mintTestToken: (options?: MintTestTokenOptions) => string;
302
+ export {};