@ethlete/query 6.0.0-next.3 → 6.0.0-next.31
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 +454 -0
- package/fesm2022/ethlete-query-testing.mjs +282 -5
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +9926 -5013
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/deprecate-legacy-queries/migration.js +77 -0
- package/generators/deprecate-legacy-queries/schema.json +26 -0
- package/generators/generators.json +15 -0
- package/generators/migrate-query-client-features/migration.js +142 -0
- package/generators/migrate-query-client-features/report.js +136 -0
- package/generators/migrate-query-client-features/schema.json +26 -0
- package/generators/migrate-query-opt-in-features/migration.js +182 -0
- package/generators/migrate-query-opt-in-features/report.js +143 -0
- package/generators/migrate-query-opt-in-features/schema.json +31 -0
- package/generators/migrate-to-query-v3/auth-provider-scaffold.js +131 -0
- package/generators/migrate-to-query-v3/cleanup-migration.js +293 -0
- package/generators/migrate-to-query-v3/http-client-check.js +93 -0
- package/generators/migrate-to-query-v3/legacy-prepare-migration.js +865 -0
- package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +608 -0
- package/generators/migrate-to-query-v3/migration-scope.js +41 -0
- package/generators/migrate-to-query-v3/migration.js +28 -2932
- package/generators/migrate-to-query-v3/module-graph.js +153 -0
- package/generators/migrate-to-query-v3/query-client-migration.js +526 -0
- package/generators/migrate-to-query-v3/rename-symbols.js +193 -0
- package/generators/migrate-to-query-v3/report.js +136 -0
- package/generators/migrate-to-query-v3/schema.json +12 -0
- package/generators/migrate-to-query-v3/shared.js +142 -0
- package/generators/prep-for-query-v3/migration.js +3 -3
- package/package.json +26 -13
- package/types/ethlete-query-testing.d.ts +141 -12
- package/types/ethlete-query.d.ts +6964 -3128
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { AnyCreateQueryClientResult, QueryClientRef, createGetQuery, createPostQuery, createPutQuery, createPatchQuery, createDeleteQuery, BearerAuthProviderFeatureContext, AnyQueryBuilder, BearerAuthProvider, AuthQueryBuilder, TokenRefreshQueryBuilder, TokenRefreshQueryConfig } from '@ethlete/query';
|
|
1
|
+
import { AnyCreateQueryClientResult, QueryClientRef, createGetQuery, createPostQuery, createPutQuery, createPatchQuery, createDeleteQuery, BearerAuthProviderFeatureContext, AnyQueryBuilder, BearerAuthProvider, AuthQueryBuilder, TokenRefreshQueryBuilder, TokenRefreshQueryConfig, QueryPersistenceAdapter, PersistedQueryEntry, WebSocketClientIo, SocketMessageView } from '@ethlete/query';
|
|
2
2
|
import { HttpClient } from '@angular/common/http';
|
|
3
3
|
import { HttpTestingController } from '@angular/common/http/testing';
|
|
4
4
|
import { Injector } from '@angular/core';
|
|
5
5
|
|
|
6
6
|
type QueryTestSetup = {
|
|
7
|
-
queryClient: NonNullable<ReturnType<AnyCreateQueryClientResult[
|
|
7
|
+
queryClient: NonNullable<ReturnType<AnyCreateQueryClientResult['inject']>>;
|
|
8
8
|
queryClientRef: QueryClientRef;
|
|
9
9
|
httpClient: HttpClient;
|
|
10
10
|
httpTesting: HttpTestingController;
|
|
@@ -70,18 +70,12 @@ type AuthTestSetupConfig<TLoginArgs extends {
|
|
|
70
70
|
accessToken: string;
|
|
71
71
|
refreshToken: string;
|
|
72
72
|
};
|
|
73
|
+
/** Builds the refresh request from the refresh token. See TokenRefreshQueryConfig for details */
|
|
74
|
+
buildRefreshArgs?: TokenRefreshQueryConfig<TRefreshArgs>['buildArgs'];
|
|
73
75
|
/** Feature builders for additional auth functionality */
|
|
74
76
|
features?: [...TFeatures];
|
|
75
77
|
/** Custom bearer decrypt function for testing */
|
|
76
78
|
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
79
|
/** Refresh strategy configuration. See TokenRefreshQueryConfig for details */
|
|
86
80
|
refreshStrategy?: TokenRefreshQueryConfig<TRefreshArgs>['refreshStrategy'];
|
|
87
81
|
/** Minimum refresh interval in milliseconds */
|
|
@@ -90,6 +84,8 @@ type AuthTestSetupConfig<TLoginArgs extends {
|
|
|
90
84
|
refreshIfExpired?: boolean;
|
|
91
85
|
/** Property name for expiration in JWT */
|
|
92
86
|
expiresInPropertyName?: string;
|
|
87
|
+
/** What to do when the refresh query fails for good. Defaults to the provider's own policy */
|
|
88
|
+
onRefreshFailure?: TokenRefreshQueryConfig<TRefreshArgs>['onRefreshFailure'];
|
|
93
89
|
};
|
|
94
90
|
type AuthTestSetup<TLoginArgs extends {
|
|
95
91
|
body: Record<string, unknown>;
|
|
@@ -154,6 +150,100 @@ declare const setupAuthTest: <TLoginArgs extends {
|
|
|
154
150
|
};
|
|
155
151
|
}, TFeatures extends readonly AnyFeatureBuilder[] = readonly AnyFeatureBuilder[], TBearerData = unknown>(config: AuthTestSetupConfig<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>) => AuthTestSetup<TLoginArgs, TRefreshArgs, TFeatures, TBearerData>;
|
|
156
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Test doubles for the two browser APIs the query client's multi-tab sync is built on:
|
|
155
|
+
* `BroadcastChannel` and the Web Locks API (`navigator.locks`).
|
|
156
|
+
*
|
|
157
|
+
* Both fakes are process-wide and shared between every consumer created while they are installed,
|
|
158
|
+
* which is exactly what makes "two tabs" testable: create two query clients in one test and they
|
|
159
|
+
* talk to each other over the fake bus and contend for the same fake locks, going through the real
|
|
160
|
+
* production code path on both sides.
|
|
161
|
+
*
|
|
162
|
+
* Neither jsdom nor happy-dom can stand in here - jsdom ships a `BroadcastChannel` that never
|
|
163
|
+
* delivers to other instances, and no `navigator.locks` at all.
|
|
164
|
+
*/
|
|
165
|
+
/** Something posted on the fake bus, as seen by a receiver. */
|
|
166
|
+
type FakeBroadcastMessage = {
|
|
167
|
+
/** The channel name it was posted on. */
|
|
168
|
+
channel: string;
|
|
169
|
+
/** The structured-cloned payload. */
|
|
170
|
+
data: unknown;
|
|
171
|
+
};
|
|
172
|
+
type FakeBroadcastChannelHandle = {
|
|
173
|
+
/** Every message posted on the bus, in order, across all channel names. */
|
|
174
|
+
posted: FakeBroadcastMessage[];
|
|
175
|
+
/** Uninstalls the fake and restores whatever `BroadcastChannel` was there before. */
|
|
176
|
+
restore: () => void;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Replaces `globalThis.BroadcastChannel` with an in-memory bus.
|
|
180
|
+
*
|
|
181
|
+
* Messages are structured-cloned at post time (so a non-cloneable payload throws exactly like the
|
|
182
|
+
* real API does), delivered on a microtask (never synchronously), and never delivered back to the
|
|
183
|
+
* channel that posted them - all three matching browser behavior. `await Promise.resolve()` in a
|
|
184
|
+
* spec is enough to flush delivery.
|
|
185
|
+
*/
|
|
186
|
+
declare const installFakeBroadcastChannel: () => FakeBroadcastChannelHandle;
|
|
187
|
+
type FakeWebLocksHandle = {
|
|
188
|
+
/** The names currently held, in no particular order. */
|
|
189
|
+
heldNames: () => string[];
|
|
190
|
+
/** The names with at least one request queued behind the holder. */
|
|
191
|
+
pendingNames: () => string[];
|
|
192
|
+
/** Uninstalls the fake and restores whatever `navigator.locks` was there before. */
|
|
193
|
+
restore: () => void;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Installs an in-memory `navigator.locks` implementing exclusive locks: one holder per name, the
|
|
197
|
+
* rest queued FIFO, granted on a microtask (never synchronously), released when the callback's
|
|
198
|
+
* promise settles. `signal` is honored for queued requests and - as in the real API - ignored once a
|
|
199
|
+
* request has been granted.
|
|
200
|
+
*
|
|
201
|
+
* `mode: 'shared'`, `ifAvailable` and `steal` are not implemented; nothing in the library uses them.
|
|
202
|
+
*/
|
|
203
|
+
declare const installFakeWebLocks: () => FakeWebLocksHandle;
|
|
204
|
+
/**
|
|
205
|
+
* Flushes the microtask queue, which is where both fakes schedule their work - message delivery and
|
|
206
|
+
* lock grants. Awaiting it once is enough for a single hop; a grant that has to wait for another
|
|
207
|
+
* tab's release needs one await per hop.
|
|
208
|
+
*/
|
|
209
|
+
declare const flushMultiTabSync: () => Promise<void>;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* An in-memory stand-in for the query client's persisted response store.
|
|
213
|
+
*
|
|
214
|
+
* jsdom has no IndexedDB at all, so this is how persistence is tested - and because the storage
|
|
215
|
+
* adapter is a documented seam rather than a mock of one, a spec running against this exercises the
|
|
216
|
+
* real engine, repository and request code on both the write and the read side.
|
|
217
|
+
*
|
|
218
|
+
* Everything resolves on a microtask, never synchronously, matching the real thing: `await
|
|
219
|
+
* Promise.resolve()` (or `flushMicrotasks`) is what lets a hydration land in a spec.
|
|
220
|
+
*/
|
|
221
|
+
type FakeQueryPersistenceStoreHandle = {
|
|
222
|
+
/** Pass this as `persistence: { adapter }`. Two clients sharing one handle share one store. */
|
|
223
|
+
adapter: QueryPersistenceAdapter;
|
|
224
|
+
/** Pre-populates the store, as if a previous session had written these entries. */
|
|
225
|
+
seed: (entries: PersistedQueryEntry[]) => void;
|
|
226
|
+
/** Every entry currently held, in insertion order. */
|
|
227
|
+
entries: () => PersistedQueryEntry[];
|
|
228
|
+
/** The entry held under `key`, or `undefined`. */
|
|
229
|
+
entry: (key: string) => PersistedQueryEntry | undefined;
|
|
230
|
+
/** How often each adapter method has been called. */
|
|
231
|
+
calls: () => Record<'loadIndex' | 'read' | 'write' | 'remove' | 'clear', number>;
|
|
232
|
+
/** Makes the next `count` writes reject - a full disk, by default. */
|
|
233
|
+
failNextWrites: (count: number, error?: unknown) => void;
|
|
234
|
+
/** Makes the next `loadIndex` reject. */
|
|
235
|
+
failNextLoadIndex: (error?: unknown) => void;
|
|
236
|
+
/**
|
|
237
|
+
* Holds every subsequent `read` pending until {@link flushReads} is called, for testing the window
|
|
238
|
+
* between a cold mount and its hydration (a slow disk, or a mount that happened before startup
|
|
239
|
+
* finished).
|
|
240
|
+
*/
|
|
241
|
+
deferReads: () => void;
|
|
242
|
+
/** Settles the reads held by {@link deferReads} and stops deferring. */
|
|
243
|
+
flushReads: () => Promise<void>;
|
|
244
|
+
};
|
|
245
|
+
declare const createFakeQueryPersistenceStore: () => FakeQueryPersistenceStoreHandle;
|
|
246
|
+
|
|
157
247
|
declare const expectAndFlush: <T = unknown>(httpTesting: HttpTestingController, url: string, response: T, statusOpts?: {
|
|
158
248
|
status: number;
|
|
159
249
|
statusText: string;
|
|
@@ -163,5 +253,44 @@ declare const expectFlushAndWait: <T = unknown>(httpTesting: HttpTestingControll
|
|
|
163
253
|
statusText: string;
|
|
164
254
|
}) => void;
|
|
165
255
|
|
|
166
|
-
|
|
167
|
-
|
|
256
|
+
/** A scripted stand-in for `socket.io-client`'s `io`, driven from a spec. */
|
|
257
|
+
type WebSocketTestDouble = {
|
|
258
|
+
/** Pass this as `createWebSocketClient({ io })`. */
|
|
259
|
+
io: WebSocketClientIo;
|
|
260
|
+
/** The url and options the client called the factory with, or `null` until it did. */
|
|
261
|
+
connection: () => {
|
|
262
|
+
url: string;
|
|
263
|
+
transports: string[] | undefined;
|
|
264
|
+
} | null;
|
|
265
|
+
/** Every message the client sent, newest last - room joins and leaves included. */
|
|
266
|
+
sent: () => {
|
|
267
|
+
event: string;
|
|
268
|
+
data: unknown;
|
|
269
|
+
}[];
|
|
270
|
+
/** Whether the client asked the socket to connect, and whether it has since disconnected it. */
|
|
271
|
+
state: () => {
|
|
272
|
+
connectRequested: boolean;
|
|
273
|
+
disconnected: boolean;
|
|
274
|
+
};
|
|
275
|
+
/** Complete the handshake, so the client's `isConnected` turns true and queued rooms re-join. */
|
|
276
|
+
serverConnect: () => void;
|
|
277
|
+
/** Drop the connection, so `isConnected` turns false. */
|
|
278
|
+
serverDisconnect: () => void;
|
|
279
|
+
/** Deliver a message the way the server would. */
|
|
280
|
+
serverSend: (message: SocketMessageView) => void;
|
|
281
|
+
/** Deliver a raw frame, for the malformed-payload path. */
|
|
282
|
+
serverSendRaw: (frame: string) => void;
|
|
283
|
+
};
|
|
284
|
+
/**
|
|
285
|
+
* A socket io test double, for specs around `createWebSocketClient` - which takes its `io` factory as
|
|
286
|
+
* an option precisely so a spec (or an app that never opens a socket) needs no `socket.io-client`.
|
|
287
|
+
*
|
|
288
|
+
* Nothing here connects on its own: the client's own `connect()` only flips
|
|
289
|
+
* {@link WebSocketTestDouble.state}, and it is {@link WebSocketTestDouble.serverConnect} that fires
|
|
290
|
+
* the `connect` listener. That split is what makes "joined a room while disconnected, and the join
|
|
291
|
+
* was re-sent on connect" testable.
|
|
292
|
+
*/
|
|
293
|
+
declare const createWebSocketTestDouble: () => WebSocketTestDouble;
|
|
294
|
+
|
|
295
|
+
export { createFakeQueryPersistenceStore, createWebSocketTestDouble, expectAndFlush, expectFlushAndWait, flushMultiTabSync, installFakeBroadcastChannel, installFakeWebLocks, setupAuthTest, setupQueryTest };
|
|
296
|
+
export type { AnyFeatureBuilder, AuthTestSetup, AuthTestSetupConfig, FakeBroadcastChannelHandle, FakeBroadcastMessage, FakeQueryPersistenceStoreHandle, FakeWebLocksHandle, QueryTestSetup, QueryTestSetupConfig, WebSocketTestDouble };
|