@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.
Files changed (31) hide show
  1. package/CHANGELOG.md +454 -0
  2. package/fesm2022/ethlete-query-testing.mjs +282 -5
  3. package/fesm2022/ethlete-query-testing.mjs.map +1 -1
  4. package/fesm2022/ethlete-query.mjs +9926 -5013
  5. package/fesm2022/ethlete-query.mjs.map +1 -1
  6. package/generators/deprecate-legacy-queries/migration.js +77 -0
  7. package/generators/deprecate-legacy-queries/schema.json +26 -0
  8. package/generators/generators.json +15 -0
  9. package/generators/migrate-query-client-features/migration.js +142 -0
  10. package/generators/migrate-query-client-features/report.js +136 -0
  11. package/generators/migrate-query-client-features/schema.json +26 -0
  12. package/generators/migrate-query-opt-in-features/migration.js +182 -0
  13. package/generators/migrate-query-opt-in-features/report.js +143 -0
  14. package/generators/migrate-query-opt-in-features/schema.json +31 -0
  15. package/generators/migrate-to-query-v3/auth-provider-scaffold.js +131 -0
  16. package/generators/migrate-to-query-v3/cleanup-migration.js +293 -0
  17. package/generators/migrate-to-query-v3/http-client-check.js +93 -0
  18. package/generators/migrate-to-query-v3/legacy-prepare-migration.js +865 -0
  19. package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +608 -0
  20. package/generators/migrate-to-query-v3/migration-scope.js +41 -0
  21. package/generators/migrate-to-query-v3/migration.js +28 -2932
  22. package/generators/migrate-to-query-v3/module-graph.js +153 -0
  23. package/generators/migrate-to-query-v3/query-client-migration.js +526 -0
  24. package/generators/migrate-to-query-v3/rename-symbols.js +193 -0
  25. package/generators/migrate-to-query-v3/report.js +136 -0
  26. package/generators/migrate-to-query-v3/schema.json +12 -0
  27. package/generators/migrate-to-query-v3/shared.js +142 -0
  28. package/generators/prep-for-query-v3/migration.js +3 -3
  29. package/package.json +26 -13
  30. package/types/ethlete-query-testing.d.ts +141 -12
  31. 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[1]>>;
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
- export { expectAndFlush, expectFlushAndWait, setupAuthTest, setupQueryTest };
167
- export type { AnyFeatureBuilder, AuthTestSetup, AuthTestSetupConfig, QueryTestSetup, QueryTestSetupConfig };
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 };