@questpie/tanstack-query 3.14.0 → 3.15.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/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { DefaultError, QueryKey, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
2
2
  import { ApplyQuery, CollectionRelations, CollectionSelect, FindManyOptions, FindOneOptionsBase, FindResult, GetCollection, GetGlobal, GlobalRelations, GlobalSelect, QuestpieApp, QuestpieClient, RealtimeAPI, ResolveRelationsDeep, TopicConfig, With, buildCollectionTopic, buildGlobalTopic } from "questpie/client";
3
+ import { AnyChannelDefinition, ChannelParamsOf, ChannelPresenceOf } from "questpie/channels";
3
4
 
4
5
  //#region src/index.d.ts
5
6
  type QuestpieQueryErrorMap = (error: unknown) => unknown;
@@ -20,8 +21,8 @@ type QueryBuilder<TFn extends AnyAsyncFn> = HasArgs<TFn> extends false ? () => U
20
21
  type RealtimeQueryConfig = {
21
22
  /**
22
23
  * Subscribe the query to live snapshots over the multiplexed SSE stream.
23
- * Initial data comes from a normal fetch; afterwards the server pushes
24
- * full snapshots on every matching change (via `experimental_streamedQuery`).
24
+ * The server supplies both the initial value and later full snapshots through
25
+ * the same stream (via `experimental_streamedQuery`).
25
26
  */
26
27
  realtime?: boolean;
27
28
  };
@@ -37,6 +38,23 @@ type MutationBuilder<TVariables, TData> = () => UseMutationOptions<TData, Defaul
37
38
  type CollectionKeys<TApp extends QuestpieApp> = Extract<keyof QuestpieClient<TApp>["collections"], string>;
38
39
  /** Extract global keys from QuestpieClient */
39
40
  type GlobalKeys<TApp extends QuestpieApp> = Extract<keyof QuestpieClient<TApp>["globals"], string>;
41
+ /** Extract generated channel registry keys without client control properties. */
42
+ type ChannelKeys<TApp extends QuestpieApp> = Extract<keyof NonNullable<TApp["channels"]>, string>;
43
+ type ChannelIter<TApp extends QuestpieApp, K$1 extends ChannelKeys<TApp>> = QuestpieClient<TApp>["channels"][K$1] extends {
44
+ iter: infer TIter;
45
+ } ? TIter extends ((...args: infer _TArgs) => AsyncGenerator<unknown, void, unknown>) ? TIter : never : never;
46
+ type ChannelIterMessage<TIter> = TIter extends ((...args: infer _TArgs) => AsyncGenerator<infer TMessage, void, unknown>) ? TMessage : never;
47
+ type ChannelDefinition<TApp extends QuestpieApp, K$1 extends ChannelKeys<TApp>> = NonNullable<TApp["channels"]>[K$1] extends AnyChannelDefinition ? NonNullable<TApp["channels"]>[K$1] : never;
48
+ type ChannelPresenceOptionsAPI<TApp extends QuestpieApp, K$1 extends ChannelKeys<TApp>> = [ChannelPresenceOf<ChannelDefinition<TApp, K$1>>] extends [never] ? {} : keyof ChannelParamsOf<ChannelDefinition<TApp, K$1>> extends never ? {
49
+ presence: () => UseQueryOptions<readonly ChannelPresenceOf<ChannelDefinition<TApp, K$1>>[]>;
50
+ } : {
51
+ presence: (params: ChannelParamsOf<ChannelDefinition<TApp, K$1>>) => UseQueryOptions<readonly ChannelPresenceOf<ChannelDefinition<TApp, K$1>>[]>;
52
+ };
53
+ type ChannelSubscriptionOptionsAPI<TApp extends QuestpieApp, K$1 extends ChannelKeys<TApp>> = (keyof ChannelParamsOf<ChannelDefinition<TApp, K$1>> extends never ? {
54
+ subscription: () => UseQueryOptions<readonly ChannelIterMessage<ChannelIter<TApp, K$1>>[]>;
55
+ } : {
56
+ subscription: (params: ChannelParamsOf<ChannelDefinition<TApp, K$1>>) => UseQueryOptions<readonly ChannelIterMessage<ChannelIter<TApp, K$1>>[]>;
57
+ }) & ChannelPresenceOptionsAPI<TApp, K$1>;
40
58
  type CollectionSelectOf<TApp extends QuestpieApp, K$1 extends CollectionKeys<TApp>> = CollectionSelect<GetCollection<TApp["collections"], K$1>>;
41
59
  type CollectionRelationsOf<TApp extends QuestpieApp, K$1 extends CollectionKeys<TApp>> = ResolveRelationsDeep<CollectionRelations<GetCollection<TApp["collections"], K$1>>, TApp["collections"]>;
42
60
  type GlobalSelectOf<TApp extends QuestpieApp, K$1 extends GlobalKeys<TApp>> = GlobalSelect<GetGlobal<NonNullable<TApp["globals"]>, K$1>>;
@@ -120,6 +138,7 @@ type GlobalQueryOptionsAPI<TApp extends QuestpieApp, K$1 extends GlobalKeys<TApp
120
138
  type QuestpieQueryOptionsProxy<TApp extends QuestpieApp = QuestpieApp> = {
121
139
  collections: { [K in CollectionKeys<TApp>]: CollectionQueryOptionsAPI<TApp, K> };
122
140
  globals: { [K in GlobalKeys<TApp>]: GlobalQueryOptionsAPI<TApp, K> };
141
+ channels: { [K in ChannelKeys<TApp>]: ChannelSubscriptionOptionsAPI<TApp, K> };
123
142
  routes: RoutesQueryOptionsAPI<QuestpieClient<TApp>["routes"]>;
124
143
  custom: {
125
144
  query: <TData>(config: {
package/dist/index.mjs CHANGED
@@ -39,37 +39,11 @@ const wrapMutationFn = (mutationFn, errorMap) => {
39
39
  };
40
40
  };
41
41
  async function* streamRealtimeQuery(options) {
42
- const { initialFetch, realtime, topic, signal, errorMap } = options;
43
- const queue = [];
44
- let resolveNext = null;
45
- const notify = () => {
46
- resolveNext?.();
47
- resolveNext = null;
48
- };
49
- const handleAbort = () => notify();
50
- const unsubscribe = realtime.subscribe(topic, (data) => {
51
- queue.push(data);
52
- notify();
53
- }, signal);
54
- signal?.addEventListener("abort", handleAbort, { once: true });
42
+ const { realtime, topic, signal, errorMap } = options;
55
43
  try {
56
- try {
57
- yield await initialFetch();
58
- } catch (error) {
59
- throw errorMap(error);
60
- }
61
- while (!signal?.aborted) {
62
- while (queue.length > 0) yield queue.shift();
63
- if (signal?.aborted) break;
64
- await new Promise((resolve) => {
65
- resolveNext = resolve;
66
- });
67
- resolveNext = null;
68
- }
69
- } finally {
70
- signal?.removeEventListener("abort", handleAbort);
71
- unsubscribe();
72
- resolveNext = null;
44
+ for await (const snapshot of realtime.stream(topic, signal)) yield snapshot;
45
+ } catch (error) {
46
+ throw errorMap(error);
73
47
  }
74
48
  }
75
49
  /**
@@ -118,7 +92,6 @@ function createQuestpieQueryOptions(client, config = {}) {
118
92
  queryKey: qKey,
119
93
  queryFn: experimental_streamedQuery({
120
94
  streamFn: ({ signal }) => streamRealtimeQuery({
121
- initialFetch: () => collection.find(options),
122
95
  realtime: client.realtime,
123
96
  topic,
124
97
  signal,
@@ -144,18 +117,17 @@ function createQuestpieQueryOptions(client, config = {}) {
144
117
  normalizeQueryKeyOptions(options)
145
118
  ]);
146
119
  if (queryConfig?.realtime && client.realtime) {
147
- const topic = buildCollectionTopic$1(collectionName, options);
120
+ const topic = buildCollectionTopic$1(collectionName, options, "count");
148
121
  return queryOptions({
149
122
  queryKey: qKey,
150
123
  queryFn: experimental_streamedQuery({
151
124
  streamFn: ({ signal }) => streamRealtimeQuery({
152
- initialFetch: () => collection.count(options),
153
125
  realtime: client.realtime,
154
126
  topic,
155
127
  signal,
156
128
  errorMap
157
129
  }),
158
- reducer: (_, chunk) => typeof chunk?.totalDocs === "number" ? chunk.totalDocs : chunk,
130
+ reducer: (_, chunk) => chunk,
159
131
  initialValue: void 0,
160
132
  refetchMode: "append"
161
133
  })
@@ -306,7 +278,6 @@ function createQuestpieQueryOptions(client, config = {}) {
306
278
  queryKey: qKey,
307
279
  queryFn: experimental_streamedQuery({
308
280
  streamFn: ({ signal }) => streamRealtimeQuery({
309
- initialFetch: () => global.get(options),
310
281
  realtime: client.realtime,
311
282
  topic,
312
283
  signal,
@@ -377,6 +348,44 @@ function createQuestpieQueryOptions(client, config = {}) {
377
348
  })
378
349
  };
379
350
  } });
351
+ const channels = new Proxy({}, { get: (_target, channelName) => {
352
+ if (typeof channelName !== "string") return void 0;
353
+ const channel = Reflect.get(client.channels, channelName);
354
+ return {
355
+ subscription: (...args) => {
356
+ return queryOptions({
357
+ queryKey: buildKey(keyPrefix, [
358
+ "channels",
359
+ channelName,
360
+ "subscription",
361
+ normalizeQueryKeyOptions(args)
362
+ ]),
363
+ queryFn: experimental_streamedQuery({
364
+ streamFn: ({ signal }) => channel.iter(...args, { signal }),
365
+ reducer: (messages, message) => [...messages, message],
366
+ initialValue: [],
367
+ refetchMode: "reset"
368
+ })
369
+ });
370
+ },
371
+ presence: (...args) => {
372
+ return queryOptions({
373
+ queryKey: buildKey(keyPrefix, [
374
+ "channels",
375
+ channelName,
376
+ "presence",
377
+ normalizeQueryKeyOptions(args)
378
+ ]),
379
+ queryFn: experimental_streamedQuery({
380
+ streamFn: ({ signal }) => channel.presenceIter(...args, { signal }),
381
+ reducer: (_roster, snapshot) => snapshot,
382
+ initialValue: [],
383
+ refetchMode: "reset"
384
+ })
385
+ });
386
+ }
387
+ };
388
+ } });
380
389
  /**
381
390
  * Resolve a nested route caller from the client by traversing dot segments.
382
391
  */
@@ -422,6 +431,7 @@ function createQuestpieQueryOptions(client, config = {}) {
422
431
  return {
423
432
  collections,
424
433
  globals,
434
+ channels,
425
435
  routes: new Proxy({}, { get: (_target, prop) => {
426
436
  if (typeof prop !== "string") return void 0;
427
437
  return createRouteNodeProxy([prop]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questpie/tanstack-query",
3
- "version": "3.14.0",
3
+ "version": "3.15.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/questpie/questpie.git",
@@ -36,7 +36,8 @@
36
36
  },
37
37
  "devDependencies": {
38
38
  "bun-types": "latest",
39
- "tsdown": "^0.18.3"
39
+ "tsdown": "^0.18.3",
40
+ "zod": "^4.2.1"
40
41
  },
41
42
  "peerDependencies": {
42
43
  "@tanstack/react-query": "^5.0.0",