@ttt-productions/query-core 0.14.21 → 0.14.23

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 (153) hide show
  1. package/README.md +6 -6
  2. package/dist/defaults.d.ts +0 -22
  3. package/dist/defaults.d.ts.map +1 -1
  4. package/dist/defaults.js +0 -35
  5. package/dist/defaults.js.map +1 -1
  6. package/dist/firestore/types.d.ts +1 -191
  7. package/dist/firestore/types.d.ts.map +1 -1
  8. package/dist/firestore/types.js +1 -6
  9. package/dist/firestore/types.js.map +1 -1
  10. package/dist/index.d.ts +2 -9
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -8
  13. package/dist/index.js.map +1 -1
  14. package/dist/keys.d.ts +0 -22
  15. package/dist/keys.d.ts.map +1 -1
  16. package/dist/keys.js +0 -4
  17. package/dist/keys.js.map +1 -1
  18. package/dist/react/cache-helpers.d.ts +16 -0
  19. package/dist/react/cache-helpers.d.ts.map +1 -0
  20. package/dist/{cache-helpers.js → react/cache-helpers.js} +0 -28
  21. package/dist/react/cache-helpers.js.map +1 -0
  22. package/dist/react/defaults.d.ts +4 -0
  23. package/dist/react/defaults.d.ts.map +1 -0
  24. package/dist/react/defaults.js +17 -0
  25. package/dist/react/defaults.js.map +1 -0
  26. package/dist/react/domain-events/cache-invalidation.d.ts +33 -0
  27. package/dist/react/domain-events/cache-invalidation.d.ts.map +1 -0
  28. package/dist/react/domain-events/cache-invalidation.js +74 -0
  29. package/dist/react/domain-events/cache-invalidation.js.map +1 -0
  30. package/dist/react/domain-events/create-domain-event-invalidator.d.ts +28 -0
  31. package/dist/react/domain-events/create-domain-event-invalidator.d.ts.map +1 -0
  32. package/dist/{domain-events → react/domain-events}/create-domain-event-invalidator.js +1 -6
  33. package/dist/react/domain-events/create-domain-event-invalidator.js.map +1 -0
  34. package/dist/react/firestore/absence-scheduler.d.ts +0 -28
  35. package/dist/react/firestore/absence-scheduler.d.ts.map +1 -1
  36. package/dist/react/firestore/absence-scheduler.js +1 -39
  37. package/dist/react/firestore/absence-scheduler.js.map +1 -1
  38. package/dist/react/firestore/batch-doc-loader.d.ts +0 -30
  39. package/dist/react/firestore/batch-doc-loader.d.ts.map +1 -1
  40. package/dist/react/firestore/batch-doc-loader.js +0 -34
  41. package/dist/react/firestore/batch-doc-loader.js.map +1 -1
  42. package/dist/react/firestore/context.d.ts +0 -19
  43. package/dist/react/firestore/context.d.ts.map +1 -1
  44. package/dist/react/firestore/context.js +1 -20
  45. package/dist/react/firestore/context.js.map +1 -1
  46. package/dist/react/firestore/doc-subscription-registry.d.ts +0 -23
  47. package/dist/react/firestore/doc-subscription-registry.d.ts.map +1 -1
  48. package/dist/react/firestore/doc-subscription-registry.js +5 -26
  49. package/dist/react/firestore/doc-subscription-registry.js.map +1 -1
  50. package/dist/react/firestore/infinite-helpers.d.ts +9 -0
  51. package/dist/react/firestore/infinite-helpers.d.ts.map +1 -0
  52. package/dist/react/firestore/infinite-helpers.js +11 -0
  53. package/dist/react/firestore/infinite-helpers.js.map +1 -0
  54. package/dist/react/firestore/resubscribe.d.ts +0 -20
  55. package/dist/react/firestore/resubscribe.d.ts.map +1 -1
  56. package/dist/react/firestore/resubscribe.js +0 -20
  57. package/dist/react/firestore/resubscribe.js.map +1 -1
  58. package/dist/react/firestore/subscription-status.d.ts +0 -29
  59. package/dist/react/firestore/subscription-status.d.ts.map +1 -1
  60. package/dist/react/firestore/subscription-status.js +1 -34
  61. package/dist/react/firestore/subscription-status.js.map +1 -1
  62. package/dist/react/firestore/types.d.ts +80 -0
  63. package/dist/react/firestore/types.d.ts.map +1 -0
  64. package/dist/react/firestore/types.js +4 -0
  65. package/dist/react/firestore/types.js.map +1 -0
  66. package/dist/react/firestore/use-firestore-collection.d.ts +2 -21
  67. package/dist/react/firestore/use-firestore-collection.d.ts.map +1 -1
  68. package/dist/react/firestore/use-firestore-collection.js +23 -44
  69. package/dist/react/firestore/use-firestore-collection.js.map +1 -1
  70. package/dist/react/firestore/use-firestore-count.d.ts +1 -16
  71. package/dist/react/firestore/use-firestore-count.d.ts.map +1 -1
  72. package/dist/react/firestore/use-firestore-count.js +0 -15
  73. package/dist/react/firestore/use-firestore-count.js.map +1 -1
  74. package/dist/react/firestore/use-firestore-doc.d.ts +2 -24
  75. package/dist/react/firestore/use-firestore-doc.d.ts.map +1 -1
  76. package/dist/react/firestore/use-firestore-doc.js +21 -42
  77. package/dist/react/firestore/use-firestore-doc.js.map +1 -1
  78. package/dist/react/firestore/use-firestore-infinite.d.ts +1 -30
  79. package/dist/react/firestore/use-firestore-infinite.d.ts.map +1 -1
  80. package/dist/react/firestore/use-firestore-infinite.js +0 -32
  81. package/dist/react/firestore/use-firestore-infinite.js.map +1 -1
  82. package/dist/react/firestore/use-firestore-live-infinite.d.ts +2 -35
  83. package/dist/react/firestore/use-firestore-live-infinite.d.ts.map +1 -1
  84. package/dist/react/firestore/use-firestore-live-infinite.js +10 -36
  85. package/dist/react/firestore/use-firestore-live-infinite.js.map +1 -1
  86. package/dist/react/firestore/use-firestore-mutations.d.ts +2 -1
  87. package/dist/react/firestore/use-firestore-mutations.d.ts.map +1 -1
  88. package/dist/react/firestore/use-firestore-mutations.js +3 -3
  89. package/dist/react/firestore/use-firestore-mutations.js.map +1 -1
  90. package/dist/react/firestore/use-firestore-paginated.d.ts +2 -31
  91. package/dist/react/firestore/use-firestore-paginated.d.ts.map +1 -1
  92. package/dist/react/firestore/use-firestore-paginated.js +8 -36
  93. package/dist/react/firestore/use-firestore-paginated.js.map +1 -1
  94. package/dist/react/firestore/useBatchFirestoreDocs.d.ts +1 -123
  95. package/dist/react/firestore/useBatchFirestoreDocs.d.ts.map +1 -1
  96. package/dist/react/firestore/useBatchFirestoreDocs.js +12 -61
  97. package/dist/react/firestore/useBatchFirestoreDocs.js.map +1 -1
  98. package/dist/react/index.d.ts +13 -4
  99. package/dist/react/index.d.ts.map +1 -1
  100. package/dist/react/index.js +8 -13
  101. package/dist/react/index.js.map +1 -1
  102. package/dist/react/pending-mutations.d.ts +2 -22
  103. package/dist/react/pending-mutations.d.ts.map +1 -1
  104. package/dist/react/pending-mutations.js +7 -38
  105. package/dist/react/pending-mutations.js.map +1 -1
  106. package/dist/react/provider.d.ts +1 -6
  107. package/dist/react/provider.d.ts.map +1 -1
  108. package/dist/react/provider.js +1 -5
  109. package/dist/react/provider.js.map +1 -1
  110. package/dist/{query-client.d.ts → react/query-client.d.ts} +0 -4
  111. package/dist/react/query-client.d.ts.map +1 -0
  112. package/dist/{query-client.js → react/query-client.js} +0 -4
  113. package/dist/react/query-client.js.map +1 -0
  114. package/dist/react/search/types.d.ts +16 -0
  115. package/dist/react/search/types.d.ts.map +1 -0
  116. package/dist/react/search/types.js +2 -0
  117. package/dist/react/search/types.js.map +1 -0
  118. package/dist/react/search/use-firestore-search.d.ts +1 -30
  119. package/dist/react/search/use-firestore-search.d.ts.map +1 -1
  120. package/dist/react/search/use-firestore-search.js +3 -41
  121. package/dist/react/search/use-firestore-search.js.map +1 -1
  122. package/dist/react/use-read-retry-state.d.ts +1 -21
  123. package/dist/react/use-read-retry-state.d.ts.map +1 -1
  124. package/dist/react/use-read-retry-state.js +3 -22
  125. package/dist/react/use-read-retry-state.js.map +1 -1
  126. package/dist/search/rule.d.ts +0 -6
  127. package/dist/search/rule.d.ts.map +1 -1
  128. package/dist/search/rule.js +0 -6
  129. package/dist/search/rule.js.map +1 -1
  130. package/dist/search/types.d.ts +1 -39
  131. package/dist/search/types.d.ts.map +1 -1
  132. package/dist/search/types.js.map +1 -1
  133. package/package.json +2 -2
  134. package/dist/cache-helpers.d.ts +0 -48
  135. package/dist/cache-helpers.d.ts.map +0 -1
  136. package/dist/cache-helpers.js.map +0 -1
  137. package/dist/domain-events/cache-invalidation.d.ts +0 -70
  138. package/dist/domain-events/cache-invalidation.d.ts.map +0 -1
  139. package/dist/domain-events/cache-invalidation.js +0 -123
  140. package/dist/domain-events/cache-invalidation.js.map +0 -1
  141. package/dist/domain-events/create-domain-event-invalidator.d.ts +0 -46
  142. package/dist/domain-events/create-domain-event-invalidator.d.ts.map +0 -1
  143. package/dist/domain-events/create-domain-event-invalidator.js.map +0 -1
  144. package/dist/domain-events/index.d.ts +0 -5
  145. package/dist/domain-events/index.d.ts.map +0 -1
  146. package/dist/domain-events/index.js +0 -3
  147. package/dist/domain-events/index.js.map +0 -1
  148. package/dist/firestore/infinite-helpers.d.ts +0 -20
  149. package/dist/firestore/infinite-helpers.d.ts.map +0 -1
  150. package/dist/firestore/infinite-helpers.js +0 -23
  151. package/dist/firestore/infinite-helpers.js.map +0 -1
  152. package/dist/query-client.d.ts.map +0 -1
  153. package/dist/query-client.js.map +0 -1
@@ -1,36 +1,18 @@
1
- "use client";
2
- // Authoritative pending for interactive mutations. A mutation registers in React Query's shared
3
- // MutationCache under a stable `mutationKey` the app declares (one key per action), and its target
4
- // travels in the mutation's variables. Pending is read from that cache, never from one mounted
5
- // control's `isPending`, which gives three guarantees:
6
- //
7
- // 1. an in-flight request survives its originating control unmounting;
8
- // 2. a remounted control still renders pending for that target;
9
- // 3. a second submission for the same target is refused while the first is pending.
10
- //
11
- // The app owns its keys, its variable shapes and what counts as the same target; this module owns
12
- // only the mechanics.
13
- import { useCallback } from "react";
14
- import { useMutationState, useQueryClient, } from "@tanstack/react-query";
15
- /** The variables of every pending mutation under `mutationKey` (prefix-matched), live. */
1
+ 'use client';
2
+ import { useCallback } from 'react';
3
+ import { useMutationState, useQueryClient, } from '@tanstack/react-query';
16
4
  export function usePendingMutationVariables(mutationKey) {
17
5
  return useMutationState({
18
- filters: { mutationKey, status: "pending" },
6
+ filters: { mutationKey, status: 'pending' },
19
7
  select: (mutation) => mutation.state.variables,
20
8
  });
21
9
  }
22
- /** The variables of every pending mutation under `mutationKey`, read once — for a duplicate guard. */
23
10
  export function pendingMutationVariables(queryClient, mutationKey) {
24
11
  return queryClient
25
12
  .getMutationCache()
26
- .findAll({ mutationKey, status: "pending" })
13
+ .findAll({ mutationKey, status: 'pending' })
27
14
  .map((mutation) => mutation.state.variables);
28
15
  }
29
- /**
30
- * The targets of every pending mutation under `mutationKey`, live — for a list whose rows each
31
- * ask "is mine pending?" through one hook call. `targetOf` names a variables' target, or returns
32
- * nothing for variables without one.
33
- */
34
16
  export function usePendingMutationTargets(mutationKey, targetOf) {
35
17
  const targets = new Set();
36
18
  for (const variables of usePendingMutationVariables(mutationKey)) {
@@ -42,31 +24,18 @@ export function usePendingMutationTargets(mutationKey, targetOf) {
42
24
  }
43
25
  return targets;
44
26
  }
45
- /**
46
- * True when a mutation under `mutationKey` is pending — any one, or with `matches`, one whose
47
- * variables are for the same target.
48
- */
49
27
  export function hasPendingMutation(queryClient, mutationKey, matches) {
50
28
  const pending = pendingMutationVariables(queryClient, mutationKey);
51
29
  if (!matches)
52
30
  return pending.length > 0;
53
31
  return pending.some((variables) => variables !== undefined && matches(variables));
54
32
  }
55
- /**
56
- * The rejection a guarded `mutateAsync` throws for a duplicate submission: an `AbortError`, the
57
- * shape a caller already treats as "nothing happened" rather than a failure to report.
58
- */
59
33
  export function duplicateSubmissionError(what) {
60
34
  const duplicate = new Error(`${what} is already in flight.`);
61
- duplicate.name = "AbortError";
35
+ duplicate.name = 'AbortError';
62
36
  return duplicate;
63
37
  }
64
- /**
65
- * Wrap a keyed mutation's `mutate` / `mutateAsync` in the cache-consulting duplicate guard:
66
- * `mutate` drops a duplicate silently, and `mutateAsync` rejects it with
67
- * {@link duplicateSubmissionError}. `isDuplicate` must have a stable identity (declare it at module
68
- * level) so the guarded callbacks keep theirs.
69
- */
38
+ /** `isDuplicate` must have a stable identity (declare it at module level) so the guarded callbacks keep theirs. */
70
39
  export function useDuplicateGuardedMutation(mutation, isDuplicate, what) {
71
40
  const queryClient = useQueryClient();
72
41
  const { mutate: rawMutate, mutateAsync: rawMutateAsync } = mutation;
@@ -1 +1 @@
1
- {"version":3,"file":"pending-mutations.js","sourceRoot":"","sources":["../../src/react/pending-mutations.ts"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n// Authoritative pending for interactive mutations. A mutation registers in React Query's shared\n// MutationCache under a stable `mutationKey` the app declares (one key per action), and its target\n// travels in the mutation's variables. Pending is read from that cache, never from one mounted\n// control's `isPending`, which gives three guarantees:\n//\n// 1. an in-flight request survives its originating control unmounting;\n// 2. a remounted control still renders pending for that target;\n// 3. a second submission for the same target is refused while the first is pending.\n//\n// The app owns its keys, its variable shapes and what counts as the same target; this module owns\n// only the mechanics.\n\nimport { useCallback } from \"react\";\nimport {\n useMutationState,\n useQueryClient,\n type MutateOptions,\n type MutationKey,\n type QueryClient,\n type UseMutationResult,\n} from \"@tanstack/react-query\";\n\n/** The variables of every pending mutation under `mutationKey` (prefix-matched), live. */\nexport function usePendingMutationVariables<V>(mutationKey: MutationKey): ReadonlyArray<V | undefined> {\n return useMutationState({\n filters: { mutationKey, status: \"pending\" },\n select: (mutation) => mutation.state.variables as V | undefined,\n });\n}\n\n/** The variables of every pending mutation under `mutationKey`, read once — for a duplicate guard. */\nexport function pendingMutationVariables<V>(\n queryClient: QueryClient,\n mutationKey: MutationKey,\n): Array<V | undefined> {\n return queryClient\n .getMutationCache()\n .findAll({ mutationKey, status: \"pending\" })\n .map((mutation) => mutation.state.variables as V | undefined);\n}\n\n/**\n * The targets of every pending mutation under `mutationKey`, live — for a list whose rows each\n * ask \"is mine pending?\" through one hook call. `targetOf` names a variables' target, or returns\n * nothing for variables without one.\n */\nexport function usePendingMutationTargets<V>(\n mutationKey: MutationKey,\n targetOf: (variables: V) => string | null | undefined,\n): ReadonlySet<string> {\n const targets = new Set<string>();\n for (const variables of usePendingMutationVariables<V>(mutationKey)) {\n if (variables === undefined) continue;\n const target = targetOf(variables);\n if (target) targets.add(target);\n }\n return targets;\n}\n\n/**\n * True when a mutation under `mutationKey` is pending — any one, or with `matches`, one whose\n * variables are for the same target.\n */\nexport function hasPendingMutation<V>(\n queryClient: QueryClient,\n mutationKey: MutationKey,\n matches?: (variables: V) => boolean,\n): boolean {\n const pending = pendingMutationVariables<V>(queryClient, mutationKey);\n if (!matches) return pending.length > 0;\n return pending.some((variables) => variables !== undefined && matches(variables));\n}\n\n/**\n * The rejection a guarded `mutateAsync` throws for a duplicate submission: an `AbortError`, the\n * shape a caller already treats as \"nothing happened\" rather than a failure to report.\n */\nexport function duplicateSubmissionError(what: string): Error {\n const duplicate = new Error(`${what} is already in flight.`);\n duplicate.name = \"AbortError\";\n return duplicate;\n}\n\n/**\n * Wrap a keyed mutation's `mutate` / `mutateAsync` in the cache-consulting duplicate guard:\n * `mutate` drops a duplicate silently, and `mutateAsync` rejects it with\n * {@link duplicateSubmissionError}. `isDuplicate` must have a stable identity (declare it at module\n * level) so the guarded callbacks keep theirs.\n */\nexport function useDuplicateGuardedMutation<TData, TError, TVariables, TContext>(\n mutation: UseMutationResult<TData, TError, TVariables, TContext>,\n isDuplicate: (queryClient: QueryClient, variables: TVariables) => boolean,\n what: string,\n): UseMutationResult<TData, TError, TVariables, TContext> {\n const queryClient = useQueryClient();\n const { mutate: rawMutate, mutateAsync: rawMutateAsync } = mutation;\n const mutate = useCallback(\n (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => {\n if (isDuplicate(queryClient, variables)) return;\n rawMutate(variables, options);\n },\n [queryClient, rawMutate, isDuplicate],\n );\n const mutateAsync = useCallback(\n async (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => {\n if (isDuplicate(queryClient, variables)) throw duplicateSubmissionError(what);\n return rawMutateAsync(variables, options);\n },\n [queryClient, rawMutateAsync, isDuplicate, what],\n );\n return { ...mutation, mutate, mutateAsync } as UseMutationResult<TData, TError, TVariables, TContext>;\n}\n"]}
1
+ {"version":3,"file":"pending-mutations.js","sourceRoot":"","sources":["../../src/react/pending-mutations.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback } from 'react';\nimport {\n useMutationState,\n useQueryClient,\n type MutateOptions,\n type MutationKey,\n type QueryClient,\n type UseMutationResult,\n} from '@tanstack/react-query';\n\nexport function usePendingMutationVariables<V>(\n mutationKey: MutationKey,\n): ReadonlyArray<V | undefined> {\n return useMutationState({\n filters: { mutationKey, status: 'pending' },\n select: (mutation) => mutation.state.variables as V | undefined,\n });\n}\n\nexport function pendingMutationVariables<V>(\n queryClient: QueryClient,\n mutationKey: MutationKey,\n): Array<V | undefined> {\n return queryClient\n .getMutationCache()\n .findAll({ mutationKey, status: 'pending' })\n .map((mutation) => mutation.state.variables as V | undefined);\n}\n\nexport function usePendingMutationTargets<V>(\n mutationKey: MutationKey,\n targetOf: (variables: V) => string | null | undefined,\n): ReadonlySet<string> {\n const targets = new Set<string>();\n for (const variables of usePendingMutationVariables<V>(mutationKey)) {\n if (variables === undefined) continue;\n const target = targetOf(variables);\n if (target) targets.add(target);\n }\n return targets;\n}\n\nexport function hasPendingMutation<V>(\n queryClient: QueryClient,\n mutationKey: MutationKey,\n matches?: (variables: V) => boolean,\n): boolean {\n const pending = pendingMutationVariables<V>(queryClient, mutationKey);\n if (!matches) return pending.length > 0;\n return pending.some((variables) => variables !== undefined && matches(variables));\n}\n\nexport function duplicateSubmissionError(what: string): Error {\n const duplicate = new Error(`${what} is already in flight.`);\n duplicate.name = 'AbortError';\n return duplicate;\n}\n\n/** `isDuplicate` must have a stable identity (declare it at module level) so the guarded callbacks keep theirs. */\nexport function useDuplicateGuardedMutation<TData, TError, TVariables, TContext>(\n mutation: UseMutationResult<TData, TError, TVariables, TContext>,\n isDuplicate: (queryClient: QueryClient, variables: TVariables) => boolean,\n what: string,\n): UseMutationResult<TData, TError, TVariables, TContext> {\n const queryClient = useQueryClient();\n const { mutate: rawMutate, mutateAsync: rawMutateAsync } = mutation;\n const mutate = useCallback(\n (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => {\n if (isDuplicate(queryClient, variables)) return;\n rawMutate(variables, options);\n },\n [queryClient, rawMutate, isDuplicate],\n );\n const mutateAsync = useCallback(\n async (variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>) => {\n if (isDuplicate(queryClient, variables)) throw duplicateSubmissionError(what);\n return rawMutateAsync(variables, options);\n },\n [queryClient, rawMutateAsync, isDuplicate, what],\n );\n return { ...mutation, mutate, mutateAsync } as UseMutationResult<\n TData,\n TError,\n TVariables,\n TContext\n >;\n}\n"]}
@@ -1,12 +1,7 @@
1
1
  import React from 'react';
2
- import { type CreateQueryClientOptions } from '../query-client.js';
2
+ import { type CreateQueryClientOptions } from './query-client.js';
3
3
  export type QueryProviderProps = React.PropsWithChildren<{
4
- /** Optional overrides applied when the client is first created. */
5
4
  clientOptions?: CreateQueryClientOptions;
6
5
  }>;
7
- /**
8
- * Client component wrapper for Next.js / React apps.
9
- * Creates a single QueryClient per browser session.
10
- */
11
6
  export declare function QueryProvider({ children, clientOptions }: QueryProviderProps): React.JSX.Element;
12
7
  //# sourceMappingURL=provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAqB,KAAK,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAEtF,MAAM,MAAM,kBAAkB,GAAG,KAAK,CAAC,iBAAiB,CAAC;IACvD,mEAAmE;IACnE,aAAa,CAAC,EAAE,wBAAwB,CAAC;CAC1C,CAAC,CAAC;AAEH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,kBAAkB,qBAG5E"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAqB,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAErF,MAAM,MAAM,kBAAkB,GAAG,KAAK,CAAC,iBAAiB,CAAC;IACvD,aAAa,CAAC,EAAE,wBAAwB,CAAC;CAC1C,CAAC,CAAC;AAEH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,kBAAkB,qBAG5E"}
@@ -2,11 +2,7 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import React from 'react';
4
4
  import { QueryClientProvider } from '@tanstack/react-query';
5
- import { createQueryClient } from '../query-client.js';
6
- /**
7
- * Client component wrapper for Next.js / React apps.
8
- * Creates a single QueryClient per browser session.
9
- */
5
+ import { createQueryClient } from './query-client.js';
10
6
  export function QueryProvider({ children, clientOptions }) {
11
7
  const [client] = React.useState(() => createQueryClient(clientOptions));
12
8
  return _jsx(QueryClientProvider, { client: client, children: children });
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAiC,MAAM,oBAAoB,CAAC;AAOtF;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAsB;IAC3E,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAAC;IACxE,OAAO,KAAC,mBAAmB,IAAC,MAAM,EAAE,MAAM,YAAG,QAAQ,GAAuB,CAAC;AAC/E,CAAC","sourcesContent":["'use client';\n\nimport React from 'react';\nimport { QueryClientProvider } from '@tanstack/react-query';\nimport { createQueryClient, type CreateQueryClientOptions } from '../query-client.js';\n\nexport type QueryProviderProps = React.PropsWithChildren<{\n /** Optional overrides applied when the client is first created. */\n clientOptions?: CreateQueryClientOptions;\n}>;\n\n/**\n * Client component wrapper for Next.js / React apps.\n * Creates a single QueryClient per browser session.\n */\nexport function QueryProvider({ children, clientOptions }: QueryProviderProps) {\n const [client] = React.useState(() => createQueryClient(clientOptions));\n return <QueryClientProvider client={client}>{children}</QueryClientProvider>;\n}\n"]}
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/react/provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAiC,MAAM,mBAAmB,CAAC;AAMrF,MAAM,UAAU,aAAa,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAsB;IAC3E,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAAC;IACxE,OAAO,KAAC,mBAAmB,IAAC,MAAM,EAAE,MAAM,YAAG,QAAQ,GAAuB,CAAC;AAC/E,CAAC","sourcesContent":["'use client';\n\nimport React from 'react';\nimport { QueryClientProvider } from '@tanstack/react-query';\nimport { createQueryClient, type CreateQueryClientOptions } from './query-client.js';\n\nexport type QueryProviderProps = React.PropsWithChildren<{\n clientOptions?: CreateQueryClientOptions;\n}>;\n\nexport function QueryProvider({ children, clientOptions }: QueryProviderProps) {\n const [client] = React.useState(() => createQueryClient(clientOptions));\n return <QueryClientProvider client={client}>{children}</QueryClientProvider>;\n}\n"]}
@@ -1,8 +1,4 @@
1
1
  import { QueryClient, type QueryClientConfig } from '@tanstack/react-query';
2
2
  export type CreateQueryClientOptions = QueryClientConfig;
3
- /**
4
- * Factory so every app uses the same baseline behavior.
5
- * Pass overrides to adjust defaults without forking.
6
- */
7
3
  export declare function createQueryClient(overrides?: CreateQueryClientOptions): QueryClient;
8
4
  //# sourceMappingURL=query-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-client.d.ts","sourceRoot":"","sources":["../../src/react/query-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG5E,MAAM,MAAM,wBAAwB,GAAG,iBAAiB,CAAC;AAEzD,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,wBAA6B,eAiBzE"}
@@ -1,9 +1,5 @@
1
1
  import { QueryClient } from '@tanstack/react-query';
2
2
  import { defaultQueryClientConfig } from './defaults.js';
3
- /**
4
- * Factory so every app uses the same baseline behavior.
5
- * Pass overrides to adjust defaults without forking.
6
- */
7
3
  export function createQueryClient(overrides = {}) {
8
4
  return new QueryClient({
9
5
  ...defaultQueryClientConfig,
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-client.js","sourceRoot":"","sources":["../../src/react/query-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA0B,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAIzD,MAAM,UAAU,iBAAiB,CAAC,YAAsC,EAAE;IACxE,OAAO,IAAI,WAAW,CAAC;QACrB,GAAG,wBAAwB;QAC3B,GAAG,SAAS;QACZ,cAAc,EAAE;YACd,GAAG,wBAAwB,CAAC,cAAc;YAC1C,GAAG,SAAS,CAAC,cAAc;YAC3B,OAAO,EAAE;gBACP,GAAG,wBAAwB,CAAC,cAAc,EAAE,OAAO;gBACnD,GAAG,SAAS,CAAC,cAAc,EAAE,OAAO;aACrC;YACD,SAAS,EAAE;gBACT,GAAG,wBAAwB,CAAC,cAAc,EAAE,SAAS;gBACrD,GAAG,SAAS,CAAC,cAAc,EAAE,SAAS;aACvC;SACF;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { QueryClient, type QueryClientConfig } from '@tanstack/react-query';\nimport { defaultQueryClientConfig } from './defaults.js';\n\nexport type CreateQueryClientOptions = QueryClientConfig;\n\nexport function createQueryClient(overrides: CreateQueryClientOptions = {}) {\n return new QueryClient({\n ...defaultQueryClientConfig,\n ...overrides,\n defaultOptions: {\n ...defaultQueryClientConfig.defaultOptions,\n ...overrides.defaultOptions,\n queries: {\n ...defaultQueryClientConfig.defaultOptions?.queries,\n ...overrides.defaultOptions?.queries,\n },\n mutations: {\n ...defaultQueryClientConfig.defaultOptions?.mutations,\n ...overrides.defaultOptions?.mutations,\n },\n },\n });\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { DocumentData } from 'firebase/firestore';
2
+ import type { SearchEqualityFilter } from '../../search/types.js';
3
+ export interface FirestoreSearchOptions<T extends DocumentData = DocumentData> {
4
+ collectionPath: string;
5
+ searchField: string;
6
+ queryText: string;
7
+ limit?: number;
8
+ enabled?: boolean;
9
+ select?: (data: DocumentData & {
10
+ id: string;
11
+ }) => T;
12
+ debounceMs?: number;
13
+ staleTime?: number;
14
+ equalityFilters?: SearchEqualityFilter[];
15
+ }
16
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/search/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,oBAAoB,EAAE,CAAC;CAC1C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/react/search/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { DocumentData } from 'firebase/firestore';\nimport type { SearchEqualityFilter } from '../../search/types.js';\n\nexport interface FirestoreSearchOptions<T extends DocumentData = DocumentData> {\n collectionPath: string;\n searchField: string;\n queryText: string;\n limit?: number;\n enabled?: boolean;\n select?: (data: DocumentData & { id: string }) => T;\n debounceMs?: number;\n staleTime?: number;\n equalityFilters?: SearchEqualityFilter[];\n}\n"]}
@@ -1,35 +1,6 @@
1
1
  import { type UseQueryResult } from '@tanstack/react-query';
2
2
  import { type DocumentData } from 'firebase/firestore';
3
- import type { FirestoreSearchOptions } from '../../search/types.js';
3
+ import type { FirestoreSearchOptions } from './types.js';
4
4
  import type { WithId } from '../../firestore/types.js';
5
- /**
6
- * Generic Firestore search hook with debouncing and case-insensitive prefix matching.
7
- * Automatically normalizes search text to lowercase and uses Firestore range queries.
8
- *
9
- * Features:
10
- * - Debounced input (default 300ms)
11
- * - Case-insensitive search
12
- * - Searches only a text `isSearchableText` accepts (`FIRESTORE_SEARCH_MIN_LENGTH`, trimmed)
13
- * - No answer while the debounce lags the typed text: `data` is undefined and `isLoading` is true
14
- * (idle instead when the typed text is too short or the search is disabled)
15
- * - Prefix matching with '\uf8ff' suffix
16
- * - Configurable result limit
17
- *
18
- * @example
19
- * ```typescript
20
- * const { data, isLoading } = useFirestoreSearch<UserProfile>({
21
- * collectionPath: 'userProfiles',
22
- * searchField: 'username',
23
- * queryText: searchValue,
24
- * });
25
- *
26
- * const { data } = useFirestoreSearch<Team>({
27
- * collectionPath: 'teams',
28
- * searchField: 'name_lowercase',
29
- * queryText: searchValue,
30
- * limit: 10,
31
- * });
32
- * ```
33
- */
34
5
  export declare function useFirestoreSearch<T extends DocumentData = DocumentData>({ collectionPath, searchField, queryText, limit, enabled, select, debounceMs, staleTime, equalityFilters, }: FirestoreSearchOptions<T>): UseQueryResult<WithId<T>[], Error>;
35
6
  //# sourceMappingURL=use-firestore-search.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-search.d.ts","sourceRoot":"","sources":["../../../src/react/search/use-firestore-search.ts"],"names":[],"mappings":"AAGA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAOL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAOvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACxE,cAAc,EACd,WAAW,EACX,SAAS,EACT,KAAqB,EACrB,OAAc,EACd,MAAM,EACN,UAAgC,EAChC,SAA8B,EAC9B,eAAoB,GACrB,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAwDhE"}
1
+ {"version":3,"file":"use-firestore-search.d.ts","sourceRoot":"","sources":["../../../src/react/search/use-firestore-search.ts"],"names":[],"mappings":"AAGA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAOL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAOvD,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACxE,cAAc,EACd,WAAW,EACX,SAAS,EACT,KAAqB,EACrB,OAAc,EACd,MAAM,EACN,UAAgC,EAChC,SAA8B,EAC9B,eAAoB,GACrB,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAyDhE"}
@@ -7,40 +7,10 @@ import { keys } from '../../keys.js';
7
7
  import { isSearchableText, normalizeSearchText } from '../../search/rule.js';
8
8
  const DEFAULT_LIMIT = 5;
9
9
  const DEFAULT_DEBOUNCE_MS = 300;
10
- const DEFAULT_STALE_TIME = 60 * 1000; // 1 minute
11
- /**
12
- * Generic Firestore search hook with debouncing and case-insensitive prefix matching.
13
- * Automatically normalizes search text to lowercase and uses Firestore range queries.
14
- *
15
- * Features:
16
- * - Debounced input (default 300ms)
17
- * - Case-insensitive search
18
- * - Searches only a text `isSearchableText` accepts (`FIRESTORE_SEARCH_MIN_LENGTH`, trimmed)
19
- * - No answer while the debounce lags the typed text: `data` is undefined and `isLoading` is true
20
- * (idle instead when the typed text is too short or the search is disabled)
21
- * - Prefix matching with '\uf8ff' suffix
22
- * - Configurable result limit
23
- *
24
- * @example
25
- * ```typescript
26
- * const { data, isLoading } = useFirestoreSearch<UserProfile>({
27
- * collectionPath: 'userProfiles',
28
- * searchField: 'username',
29
- * queryText: searchValue,
30
- * });
31
- *
32
- * const { data } = useFirestoreSearch<Team>({
33
- * collectionPath: 'teams',
34
- * searchField: 'name_lowercase',
35
- * queryText: searchValue,
36
- * limit: 10,
37
- * });
38
- * ```
39
- */
10
+ const DEFAULT_STALE_TIME = 60 * 1000;
40
11
  export function useFirestoreSearch({ collectionPath, searchField, queryText, limit = DEFAULT_LIMIT, enabled = true, select, debounceMs = DEFAULT_DEBOUNCE_MS, staleTime = DEFAULT_STALE_TIME, equalityFilters = [], }) {
41
12
  const db = useFirestoreDb();
42
13
  const [debouncedQuery, setDebouncedQuery] = useState(queryText);
43
- // Debounce the search query
44
14
  useEffect(() => {
45
15
  const timer = setTimeout(() => {
46
16
  setDebouncedQuery(queryText);
@@ -56,8 +26,7 @@ export function useFirestoreSearch({ collectionPath, searchField, queryText, lim
56
26
  return [];
57
27
  }
58
28
  const collectionRef = collection(db, collectionPath);
59
- // Build range query for prefix matching
60
- // Using '\uf8ff' as upper bound creates a range that matches all strings starting with the query
29
+ // '\uf8ff' is the highest code point Firestore orders, so the range covers every string with the prefix.
61
30
  const constraints = [
62
31
  ...equalityFilters.map((f) => where(f.field, '==', f.value)),
63
32
  where(searchField, '>=', normalizedQuery),
@@ -77,20 +46,13 @@ export function useFirestoreSearch({ collectionPath, searchField, queryText, lim
77
46
  enabled: shouldSearch,
78
47
  staleTime,
79
48
  });
80
- // Until the debounce catches up, the query's key holds an earlier text, so its result (or its
81
- // absence) is not an answer for the text typed now (FRONTEND-206): report no answer instead —
82
- // still searching when the typed text will be searched, idle when it will not.
83
49
  const typedQuery = normalizeSearchText(queryText);
84
50
  if (typedQuery === normalizedQuery)
85
51
  return searchQuery;
86
52
  const willSearch = enabled && isSearchableText(queryText);
87
53
  return withoutAnswer(searchQuery, willSearch);
88
54
  }
89
- /**
90
- * The query result with every answer field replaced. The other fields stay getters onto the
91
- * original result: TanStack tracks which fields a component reads to decide when it re-renders,
92
- * and reading them all here (a spread) would opt every consumer out of that.
93
- */
55
+ /** Other fields stay getters onto the original result: TanStack re-renders on the fields a component reads, and a spread reads them all. */
94
56
  function withoutAnswer(result, searching) {
95
57
  const answerFields = {
96
58
  data: undefined,
@@ -1 +1 @@
1
- {"version":3,"file":"use-firestore-search.js","sourceRoot":"","sources":["../../../src/react/search/use-firestore-search.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useEffect } from 'react';\nimport { useQuery, type UseQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n getDocs,\n query,\n where,\n orderBy,\n limit as firestoreLimit,\n type DocumentData,\n type QueryConstraint,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from '../firestore/context.js';\nimport { keys } from '../../keys.js';\nimport type { FirestoreSearchOptions } from '../../search/types.js';\nimport type { WithId } from '../../firestore/types.js';\nimport { isSearchableText, normalizeSearchText } from '../../search/rule.js';\n\nconst DEFAULT_LIMIT = 5;\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst DEFAULT_STALE_TIME = 60 * 1000; // 1 minute\n\n/**\n * Generic Firestore search hook with debouncing and case-insensitive prefix matching.\n * Automatically normalizes search text to lowercase and uses Firestore range queries.\n *\n * Features:\n * - Debounced input (default 300ms)\n * - Case-insensitive search\n * - Searches only a text `isSearchableText` accepts (`FIRESTORE_SEARCH_MIN_LENGTH`, trimmed)\n * - No answer while the debounce lags the typed text: `data` is undefined and `isLoading` is true\n * (idle instead when the typed text is too short or the search is disabled)\n * - Prefix matching with '\\uf8ff' suffix\n * - Configurable result limit\n *\n * @example\n * ```typescript\n * const { data, isLoading } = useFirestoreSearch<UserProfile>({\n * collectionPath: 'userProfiles',\n * searchField: 'username',\n * queryText: searchValue,\n * });\n *\n * const { data } = useFirestoreSearch<Team>({\n * collectionPath: 'teams',\n * searchField: 'name_lowercase',\n * queryText: searchValue,\n * limit: 10,\n * });\n * ```\n */\nexport function useFirestoreSearch<T extends DocumentData = DocumentData>({\n collectionPath,\n searchField,\n queryText,\n limit = DEFAULT_LIMIT,\n enabled = true,\n select,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n staleTime = DEFAULT_STALE_TIME,\n equalityFilters = [],\n}: FirestoreSearchOptions<T>): UseQueryResult<WithId<T>[], Error> {\n const db = useFirestoreDb();\n const [debouncedQuery, setDebouncedQuery] = useState(queryText);\n\n // Debounce the search query\n useEffect(() => {\n const timer = setTimeout(() => {\n setDebouncedQuery(queryText);\n }, debounceMs);\n\n return () => clearTimeout(timer);\n }, [queryText, debounceMs]);\n\n const normalizedQuery = normalizeSearchText(debouncedQuery);\n const shouldSearch = enabled && isSearchableText(debouncedQuery);\n\n const searchQuery = useQuery({\n queryKey: keys.custom('search', collectionPath, searchField, normalizedQuery, JSON.stringify(equalityFilters)),\n queryFn: async (): Promise<WithId<T>[]> => {\n if (!shouldSearch) {\n return [];\n }\n\n const collectionRef = collection(db, collectionPath);\n\n // Build range query for prefix matching\n // Using '\\uf8ff' as upper bound creates a range that matches all strings starting with the query\n const constraints: QueryConstraint[] = [\n ...equalityFilters.map((f) => where(f.field, '==', f.value)),\n where(searchField, '>=', normalizedQuery),\n where(searchField, '<=', normalizedQuery + '\\uf8ff'),\n orderBy(searchField, 'asc'),\n firestoreLimit(limit),\n ];\n\n const q = query(collectionRef, ...constraints);\n const snapshot = await getDocs(q);\n\n return snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n },\n enabled: shouldSearch,\n staleTime,\n });\n\n // Until the debounce catches up, the query's key holds an earlier text, so its result (or its\n // absence) is not an answer for the text typed now (FRONTEND-206): report no answer instead —\n // still searching when the typed text will be searched, idle when it will not.\n const typedQuery = normalizeSearchText(queryText);\n if (typedQuery === normalizedQuery) return searchQuery;\n const willSearch = enabled && isSearchableText(queryText);\n return withoutAnswer(searchQuery, willSearch);\n}\n\n/**\n * The query result with every answer field replaced. The other fields stay getters onto the\n * original result: TanStack tracks which fields a component reads to decide when it re-renders,\n * and reading them all here (a spread) would opt every consumer out of that.\n */\nfunction withoutAnswer<R extends UseQueryResult<unknown, Error>>(result: R, searching: boolean): R {\n const answerFields: Record<string, unknown> = {\n data: undefined,\n error: null,\n isError: false,\n isLoadingError: false,\n isRefetchError: false,\n isSuccess: false,\n isPending: true,\n isPlaceholderData: false,\n isFetched: false,\n isFetchedAfterMount: false,\n isRefetching: false,\n isLoading: searching,\n isFetching: searching,\n status: 'pending',\n fetchStatus: searching ? 'fetching' : 'idle',\n dataUpdatedAt: 0,\n errorUpdatedAt: 0,\n };\n const view = {} as R;\n for (const key of Object.keys(result)) {\n Object.defineProperty(view, key, {\n enumerable: true,\n configurable: true,\n get: key in answerFields ? () => answerFields[key] : () => result[key as keyof R],\n });\n }\n return view;\n}\n\n"]}
1
+ {"version":3,"file":"use-firestore-search.js","sourceRoot":"","sources":["../../../src/react/search/use-firestore-search.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState, useEffect } from 'react';\nimport { useQuery, type UseQueryResult } from '@tanstack/react-query';\nimport {\n collection,\n getDocs,\n query,\n where,\n orderBy,\n limit as firestoreLimit,\n type DocumentData,\n type QueryConstraint,\n} from 'firebase/firestore';\nimport { useFirestoreDb } from '../firestore/context.js';\nimport { keys } from '../../keys.js';\nimport type { FirestoreSearchOptions } from './types.js';\nimport type { WithId } from '../../firestore/types.js';\nimport { isSearchableText, normalizeSearchText } from '../../search/rule.js';\n\nconst DEFAULT_LIMIT = 5;\nconst DEFAULT_DEBOUNCE_MS = 300;\nconst DEFAULT_STALE_TIME = 60 * 1000;\n\nexport function useFirestoreSearch<T extends DocumentData = DocumentData>({\n collectionPath,\n searchField,\n queryText,\n limit = DEFAULT_LIMIT,\n enabled = true,\n select,\n debounceMs = DEFAULT_DEBOUNCE_MS,\n staleTime = DEFAULT_STALE_TIME,\n equalityFilters = [],\n}: FirestoreSearchOptions<T>): UseQueryResult<WithId<T>[], Error> {\n const db = useFirestoreDb();\n const [debouncedQuery, setDebouncedQuery] = useState(queryText);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n setDebouncedQuery(queryText);\n }, debounceMs);\n\n return () => clearTimeout(timer);\n }, [queryText, debounceMs]);\n\n const normalizedQuery = normalizeSearchText(debouncedQuery);\n const shouldSearch = enabled && isSearchableText(debouncedQuery);\n\n const searchQuery = useQuery({\n queryKey: keys.custom(\n 'search',\n collectionPath,\n searchField,\n normalizedQuery,\n JSON.stringify(equalityFilters),\n ),\n queryFn: async (): Promise<WithId<T>[]> => {\n if (!shouldSearch) {\n return [];\n }\n\n const collectionRef = collection(db, collectionPath);\n\n // '\\uf8ff' is the highest code point Firestore orders, so the range covers every string with the prefix.\n const constraints: QueryConstraint[] = [\n ...equalityFilters.map((f) => where(f.field, '==', f.value)),\n where(searchField, '>=', normalizedQuery),\n where(searchField, '<=', normalizedQuery + '\\uf8ff'),\n orderBy(searchField, 'asc'),\n firestoreLimit(limit),\n ];\n\n const q = query(collectionRef, ...constraints);\n const snapshot = await getDocs(q);\n\n return snapshot.docs.map((docSnap) => {\n const rawData = docSnap.data();\n const dataWithId = { id: docSnap.id, ...rawData };\n const data = select ? select(dataWithId) : dataWithId;\n return data as WithId<T>;\n });\n },\n enabled: shouldSearch,\n staleTime,\n });\n\n const typedQuery = normalizeSearchText(queryText);\n if (typedQuery === normalizedQuery) return searchQuery;\n const willSearch = enabled && isSearchableText(queryText);\n return withoutAnswer(searchQuery, willSearch);\n}\n\n/** Other fields stay getters onto the original result: TanStack re-renders on the fields a component reads, and a spread reads them all. */\nfunction withoutAnswer<R extends UseQueryResult<unknown, Error>>(result: R, searching: boolean): R {\n const answerFields: Record<string, unknown> = {\n data: undefined,\n error: null,\n isError: false,\n isLoadingError: false,\n isRefetchError: false,\n isSuccess: false,\n isPending: true,\n isPlaceholderData: false,\n isFetched: false,\n isFetchedAfterMount: false,\n isRefetching: false,\n isLoading: searching,\n isFetching: searching,\n status: 'pending',\n fetchStatus: searching ? 'fetching' : 'idle',\n dataUpdatedAt: 0,\n errorUpdatedAt: 0,\n };\n const view = {} as R;\n for (const key of Object.keys(result)) {\n Object.defineProperty(view, key, {\n enumerable: true,\n configurable: true,\n get: key in answerFields ? () => answerFields[key] : () => result[key as keyof R],\n });\n }\n return view;\n}\n"]}
@@ -1,40 +1,20 @@
1
1
  import { type QueryKey } from '@tanstack/react-query';
2
- /** The parts of a read's result the retry state is decided from. */
3
2
  export interface ReadRetrySource<TError = Error> {
4
3
  error: TError | null | undefined;
5
4
  isFetching: boolean;
6
5
  isLoading: boolean;
7
- /** A fetch waiting for the network counts as still running. */
8
6
  isPaused?: boolean;
9
7
  }
10
- /** A read's failure as its surface shows it, held on screen while a retry of it runs. */
11
8
  export interface ReadRetryState<TError = Error> {
12
- /** The read's failure: its current one, or the last one while a retry of it is running. */
13
9
  error: TError | null;
14
- /** A retry of the failed read is running. */
15
10
  isRetrying: boolean;
16
- /** The read has not answered yet and is not a retry of a failure. */
17
11
  isLoading: boolean;
18
12
  }
19
- /** One read a keyed retry state follows: its result and its identity. */
20
13
  export interface ReadRetryEntry<TError = Error> {
21
14
  source: ReadRetrySource<TError>;
22
15
  queryKey: QueryKey;
23
16
  }
24
- /**
25
- * The failure and retry state of each of several reads, in entry order — the keyed form of
26
- * `useReadRetryState` for a set whose size changes between renders (a `useQueries` list, a batch
27
- * of per-id reads). Each read's failure is held under its own `queryKey`, so one read's retry
28
- * never shows another's failure, and a read that leaves the set drops what it held.
29
- */
30
17
  export declare function useReadRetryStates<TError = Error>(entries: readonly ReadRetryEntry<TError>[]): ReadRetryState<TError>[];
31
- /**
32
- * The failure and retry state of one read. TanStack Query clears the `error` of a query that never
33
- * succeeded and puts it back to `pending` the moment a refetch of it starts, so a retry state built
34
- * from `isFetching && error` is never true for a failed first read, and its failure panel turns into
35
- * a loading state under the retry the person pressed. This holds the read's last failure until the
36
- * retry settles — an answer clears it, a new failure replaces it — and reports the retry as running
37
- * meanwhile. The failure belongs to `queryKey`: a read under another key never shows it.
38
- */
18
+ /** TanStack clears a never-succeeded query's `error` as its refetch starts, so the failure is held until the retry settles. */
39
19
  export declare function useReadRetryState<TError = Error>(source: ReadRetrySource<TError>, queryKey: QueryKey): ReadRetryState<TError>;
40
20
  //# sourceMappingURL=use-read-retry-state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-read-retry-state.d.ts","sourceRoot":"","sources":["../../src/react/use-read-retry-state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE/D,oEAAoE;AACpE,MAAM,WAAW,eAAe,CAAC,MAAM,GAAG,KAAK;IAC7C,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,yFAAyF;AACzF,MAAM,WAAW,cAAc,CAAC,MAAM,GAAG,KAAK;IAC5C,2FAA2F;IAC3F,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,6CAA6C;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,qEAAqE;IACrE,SAAS,EAAE,OAAO,CAAC;CACpB;AAmBD,yEAAyE;AACzE,MAAM,WAAW,cAAc,CAAC,MAAM,GAAG,KAAK;IAC5C,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAsBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAG,KAAK,EAC/C,OAAO,EAAE,SAAS,cAAc,CAAC,MAAM,CAAC,EAAE,GACzC,cAAc,CAAC,MAAM,CAAC,EAAE,CAa1B;AAUD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAG,KAAK,EAC9C,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,EAC/B,QAAQ,EAAE,QAAQ,GACjB,cAAc,CAAC,MAAM,CAAC,CAExB"}
1
+ {"version":3,"file":"use-read-retry-state.d.ts","sourceRoot":"","sources":["../../src/react/use-read-retry-state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,WAAW,eAAe,CAAC,MAAM,GAAG,KAAK;IAC7C,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc,CAAC,MAAM,GAAG,KAAK;IAC5C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAeD,MAAM,WAAW,cAAc,CAAC,MAAM,GAAG,KAAK;IAC5C,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAyBD,wBAAgB,kBAAkB,CAAC,MAAM,GAAG,KAAK,EAC/C,OAAO,EAAE,SAAS,cAAc,CAAC,MAAM,CAAC,EAAE,GACzC,cAAc,CAAC,MAAM,CAAC,EAAE,CAY1B;AAUD,+HAA+H;AAC/H,wBAAgB,iBAAiB,CAAC,MAAM,GAAG,KAAK,EAC9C,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,EAC/B,QAAQ,EAAE,QAAQ,GACjB,cAAc,CAAC,MAAM,CAAC,CAExB"}
@@ -1,11 +1,7 @@
1
- "use client";
1
+ 'use client';
2
2
  import { useState } from 'react';
3
3
  import { hashKey } from '@tanstack/react-query';
4
- /**
5
- * One failure is its name, message, and `code`, not its object: a composite source that builds its
6
- * error in render hands back a new object for the same failure every render, and holding each one
7
- * as new would set state on every render without end.
8
- */
4
+ /** A composite source builds a new error object every render, so a failure is compared by name, message, and `code`. */
9
5
  function sameFailure(a, b) {
10
6
  if (a === b)
11
7
  return true;
@@ -15,7 +11,6 @@ function sameFailure(a, b) {
15
11
  const y = b;
16
12
  return x.name === y.name && x.message === y.message && x.code === y.code;
17
13
  }
18
- /** The failure a read holds next, given what it held: its live failure, or the held one while it runs. */
19
14
  function nextHeldFailure(held, source) {
20
15
  const live = source.error ?? null;
21
16
  if (live !== null)
@@ -31,12 +26,6 @@ function toRetryState(source, held) {
31
26
  isLoading: error === null && source.isLoading,
32
27
  };
33
28
  }
34
- /**
35
- * The failure and retry state of each of several reads, in entry order — the keyed form of
36
- * `useReadRetryState` for a set whose size changes between renders (a `useQueries` list, a batch
37
- * of per-id reads). Each read's failure is held under its own `queryKey`, so one read's retry
38
- * never shows another's failure, and a read that leaves the set drops what it held.
39
- */
40
29
  export function useReadRetryStates(entries) {
41
30
  const [held, setHeld] = useState({});
42
31
  const next = {};
@@ -47,7 +36,6 @@ export function useReadRetryStates(entries) {
47
36
  next[identity] = failure;
48
37
  return toRetryState(source, failure);
49
38
  });
50
- // Render-phase adjustment: the held failures follow the reads without an effect's extra render.
51
39
  if (!sameHeld(held, next))
52
40
  setHeld(next);
53
41
  return states;
@@ -56,14 +44,7 @@ function sameHeld(a, b) {
56
44
  const keys = Object.keys(a);
57
45
  return keys.length === Object.keys(b).length && keys.every((key) => a[key] === b[key]);
58
46
  }
59
- /**
60
- * The failure and retry state of one read. TanStack Query clears the `error` of a query that never
61
- * succeeded and puts it back to `pending` the moment a refetch of it starts, so a retry state built
62
- * from `isFetching && error` is never true for a failed first read, and its failure panel turns into
63
- * a loading state under the retry the person pressed. This holds the read's last failure until the
64
- * retry settles — an answer clears it, a new failure replaces it — and reports the retry as running
65
- * meanwhile. The failure belongs to `queryKey`: a read under another key never shows it.
66
- */
47
+ /** TanStack clears a never-succeeded query's `error` as its refetch starts, so the failure is held until the retry settles. */
67
48
  export function useReadRetryState(source, queryKey) {
68
49
  return useReadRetryStates([{ source, queryKey }])[0];
69
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-read-retry-state.js","sourceRoot":"","sources":["../../src/react/use-read-retry-state.ts"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport { useState } from 'react';\nimport { hashKey, type QueryKey } from '@tanstack/react-query';\n\n/** The parts of a read's result the retry state is decided from. */\nexport interface ReadRetrySource<TError = Error> {\n error: TError | null | undefined;\n isFetching: boolean;\n isLoading: boolean;\n /** A fetch waiting for the network counts as still running. */\n isPaused?: boolean;\n}\n\n/** A read's failure as its surface shows it, held on screen while a retry of it runs. */\nexport interface ReadRetryState<TError = Error> {\n /** The read's failure: its current one, or the last one while a retry of it is running. */\n error: TError | null;\n /** A retry of the failed read is running. */\n isRetrying: boolean;\n /** The read has not answered yet and is not a retry of a failure. */\n isLoading: boolean;\n}\n\ninterface HeldFailure<TError> {\n error: TError;\n}\n\n/**\n * One failure is its name, message, and `code`, not its object: a composite source that builds its\n * error in render hands back a new object for the same failure every render, and holding each one\n * as new would set state on every render without end.\n */\nfunction sameFailure(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false;\n const x = a as { name?: unknown; message?: unknown; code?: unknown };\n const y = b as { name?: unknown; message?: unknown; code?: unknown };\n return x.name === y.name && x.message === y.message && x.code === y.code;\n}\n\n/** One read a keyed retry state follows: its result and its identity. */\nexport interface ReadRetryEntry<TError = Error> {\n source: ReadRetrySource<TError>;\n queryKey: QueryKey;\n}\n\n/** The failure a read holds next, given what it held: its live failure, or the held one while it runs. */\nfunction nextHeldFailure<TError>(\n held: HeldFailure<TError> | undefined,\n source: ReadRetrySource<TError>,\n): HeldFailure<TError> | undefined {\n const live = source.error ?? null;\n if (live !== null) return held !== undefined && sameFailure(held.error, live) ? held : { error: live };\n const running = source.isFetching || source.isPaused === true;\n return running ? held : undefined;\n}\n\nfunction toRetryState<TError>(source: ReadRetrySource<TError>, held: HeldFailure<TError> | undefined): ReadRetryState<TError> {\n const error = source.error ?? held?.error ?? null;\n return {\n error,\n isRetrying: error !== null && (source.isFetching || source.isPaused === true),\n isLoading: error === null && source.isLoading,\n };\n}\n\n/**\n * The failure and retry state of each of several reads, in entry order — the keyed form of\n * `useReadRetryState` for a set whose size changes between renders (a `useQueries` list, a batch\n * of per-id reads). Each read's failure is held under its own `queryKey`, so one read's retry\n * never shows another's failure, and a read that leaves the set drops what it held.\n */\nexport function useReadRetryStates<TError = Error>(\n entries: readonly ReadRetryEntry<TError>[],\n): ReadRetryState<TError>[] {\n const [held, setHeld] = useState<Readonly<Record<string, HeldFailure<TError>>>>({});\n\n const next: Record<string, HeldFailure<TError>> = {};\n const states = entries.map(({ source, queryKey }) => {\n const identity = hashKey(queryKey);\n const failure = nextHeldFailure(held[identity], source);\n if (failure !== undefined) next[identity] = failure;\n return toRetryState(source, failure);\n });\n // Render-phase adjustment: the held failures follow the reads without an effect's extra render.\n if (!sameHeld(held, next)) setHeld(next);\n return states;\n}\n\nfunction sameHeld<TError>(\n a: Readonly<Record<string, HeldFailure<TError>>>,\n b: Readonly<Record<string, HeldFailure<TError>>>,\n): boolean {\n const keys = Object.keys(a);\n return keys.length === Object.keys(b).length && keys.every((key) => a[key] === b[key]);\n}\n\n/**\n * The failure and retry state of one read. TanStack Query clears the `error` of a query that never\n * succeeded and puts it back to `pending` the moment a refetch of it starts, so a retry state built\n * from `isFetching && error` is never true for a failed first read, and its failure panel turns into\n * a loading state under the retry the person pressed. This holds the read's last failure until the\n * retry settles — an answer clears it, a new failure replaces it — and reports the retry as running\n * meanwhile. The failure belongs to `queryKey`: a read under another key never shows it.\n */\nexport function useReadRetryState<TError = Error>(\n source: ReadRetrySource<TError>,\n queryKey: QueryKey,\n): ReadRetryState<TError> {\n return useReadRetryStates([{ source, queryKey }])[0]!;\n}\n"]}
1
+ {"version":3,"file":"use-read-retry-state.js","sourceRoot":"","sources":["../../src/react/use-read-retry-state.ts"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { hashKey, type QueryKey } from '@tanstack/react-query';\n\nexport interface ReadRetrySource<TError = Error> {\n error: TError | null | undefined;\n isFetching: boolean;\n isLoading: boolean;\n isPaused?: boolean;\n}\n\nexport interface ReadRetryState<TError = Error> {\n error: TError | null;\n isRetrying: boolean;\n isLoading: boolean;\n}\n\ninterface HeldFailure<TError> {\n error: TError;\n}\n\n/** A composite source builds a new error object every render, so a failure is compared by name, message, and `code`. */\nfunction sameFailure(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false;\n const x = a as { name?: unknown; message?: unknown; code?: unknown };\n const y = b as { name?: unknown; message?: unknown; code?: unknown };\n return x.name === y.name && x.message === y.message && x.code === y.code;\n}\n\nexport interface ReadRetryEntry<TError = Error> {\n source: ReadRetrySource<TError>;\n queryKey: QueryKey;\n}\n\nfunction nextHeldFailure<TError>(\n held: HeldFailure<TError> | undefined,\n source: ReadRetrySource<TError>,\n): HeldFailure<TError> | undefined {\n const live = source.error ?? null;\n if (live !== null)\n return held !== undefined && sameFailure(held.error, live) ? held : { error: live };\n const running = source.isFetching || source.isPaused === true;\n return running ? held : undefined;\n}\n\nfunction toRetryState<TError>(\n source: ReadRetrySource<TError>,\n held: HeldFailure<TError> | undefined,\n): ReadRetryState<TError> {\n const error = source.error ?? held?.error ?? null;\n return {\n error,\n isRetrying: error !== null && (source.isFetching || source.isPaused === true),\n isLoading: error === null && source.isLoading,\n };\n}\n\nexport function useReadRetryStates<TError = Error>(\n entries: readonly ReadRetryEntry<TError>[],\n): ReadRetryState<TError>[] {\n const [held, setHeld] = useState<Readonly<Record<string, HeldFailure<TError>>>>({});\n\n const next: Record<string, HeldFailure<TError>> = {};\n const states = entries.map(({ source, queryKey }) => {\n const identity = hashKey(queryKey);\n const failure = nextHeldFailure(held[identity], source);\n if (failure !== undefined) next[identity] = failure;\n return toRetryState(source, failure);\n });\n if (!sameHeld(held, next)) setHeld(next);\n return states;\n}\n\nfunction sameHeld<TError>(\n a: Readonly<Record<string, HeldFailure<TError>>>,\n b: Readonly<Record<string, HeldFailure<TError>>>,\n): boolean {\n const keys = Object.keys(a);\n return keys.length === Object.keys(b).length && keys.every((key) => a[key] === b[key]);\n}\n\n/** TanStack clears a never-succeeded query's `error` as its refetch starts, so the failure is held until the retry settles. */\nexport function useReadRetryState<TError = Error>(\n source: ReadRetrySource<TError>,\n queryKey: QueryKey,\n): ReadRetryState<TError> {\n return useReadRetryStates([{ source, queryKey }])[0]!;\n}\n"]}
@@ -1,10 +1,4 @@
1
- /** The fewest characters, counted on the normalized text, that `useFirestoreSearch` searches. */
2
1
  export declare const FIRESTORE_SEARCH_MIN_LENGTH = 3;
3
- /**
4
- * The text `useFirestoreSearch` reads for a typed text: lowercased, surrounding whitespace dropped.
5
- * Two typed texts with the same normalized text are the same search.
6
- */
7
2
  export declare function normalizeSearchText(text: string): string;
8
- /** Whether `useFirestoreSearch` searches this typed text (its normalized length reaches the minimum). */
9
3
  export declare function isSearchableText(text: string): boolean;
10
4
  //# sourceMappingURL=rule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../../src/search/rule.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,yGAAyG;AACzG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD"}
1
+ {"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../../src/search/rule.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD"}
@@ -1,13 +1,7 @@
1
- /** The fewest characters, counted on the normalized text, that `useFirestoreSearch` searches. */
2
1
  export const FIRESTORE_SEARCH_MIN_LENGTH = 3;
3
- /**
4
- * The text `useFirestoreSearch` reads for a typed text: lowercased, surrounding whitespace dropped.
5
- * Two typed texts with the same normalized text are the same search.
6
- */
7
2
  export function normalizeSearchText(text) {
8
3
  return text.toLowerCase().trim();
9
4
  }
10
- /** Whether `useFirestoreSearch` searches this typed text (its normalized length reaches the minimum). */
11
5
  export function isSearchableText(text) {
12
6
  return normalizeSearchText(text).length >= FIRESTORE_SEARCH_MIN_LENGTH;
13
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rule.js","sourceRoot":"","sources":["../../src/search/rule.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAE7C;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;AACnC,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,2BAA2B,CAAC;AACzE,CAAC","sourcesContent":["/** The fewest characters, counted on the normalized text, that `useFirestoreSearch` searches. */\nexport const FIRESTORE_SEARCH_MIN_LENGTH = 3;\n\n/**\n * The text `useFirestoreSearch` reads for a typed text: lowercased, surrounding whitespace dropped.\n * Two typed texts with the same normalized text are the same search.\n */\nexport function normalizeSearchText(text: string): string {\n return text.toLowerCase().trim();\n}\n\n/** Whether `useFirestoreSearch` searches this typed text (its normalized length reaches the minimum). */\nexport function isSearchableText(text: string): boolean {\n return normalizeSearchText(text).length >= FIRESTORE_SEARCH_MIN_LENGTH;\n}\n"]}
1
+ {"version":3,"file":"rule.js","sourceRoot":"","sources":["../../src/search/rule.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAE7C,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,2BAA2B,CAAC;AACzE,CAAC","sourcesContent":["export const FIRESTORE_SEARCH_MIN_LENGTH = 3;\n\nexport function normalizeSearchText(text: string): string {\n return text.toLowerCase().trim();\n}\n\nexport function isSearchableText(text: string): boolean {\n return normalizeSearchText(text).length >= FIRESTORE_SEARCH_MIN_LENGTH;\n}\n"]}
@@ -1,50 +1,12 @@
1
- import type { DocumentData } from 'firebase/firestore';
2
- /**
3
- * Equality filter applied before the prefix range in a Firestore search.
4
- * Each becomes a `where(field, '==', value)` constraint, ordered ahead of the
5
- * search field. The composite index must list these equality fields (ASC)
6
- * before the search field (ASC).
7
- */
1
+ /** The composite index must list these equality fields (ASC) before the search field (ASC). */
8
2
  export interface SearchEqualityFilter {
9
3
  field: string;
10
4
  value: unknown;
11
5
  }
12
- /**
13
- * Configuration for a Firestore search query
14
- */
15
6
  export interface FirestoreSearchConfig {
16
- /** Firestore collection path to search */
17
7
  collectionPath: string;
18
- /** Field name to search on (must be indexed and lowercase) */
19
8
  searchField: string;
20
- /** Maximum number of results to return */
21
9
  limit: number;
22
- /** Optional equality filters applied before the prefix range. */
23
- equalityFilters?: SearchEqualityFilter[];
24
- }
25
- /**
26
- * Options for useFirestoreSearch hook
27
- */
28
- export interface FirestoreSearchOptions<T extends DocumentData = DocumentData> {
29
- /** Firestore collection path to search */
30
- collectionPath: string;
31
- /** Field name to search on (must be indexed and lowercase) */
32
- searchField: string;
33
- /** Search query text */
34
- queryText: string;
35
- /** Maximum number of results (default: 5) */
36
- limit?: number;
37
- /** Enable/disable the search (default: true) */
38
- enabled?: boolean;
39
- /** Transform function to convert raw Firestore data to typed result */
40
- select?: (data: DocumentData & {
41
- id: string;
42
- }) => T;
43
- /** Debounce delay in milliseconds (default: 300) */
44
- debounceMs?: number;
45
- /** React Query stale time (default: 60000ms) */
46
- staleTime?: number;
47
- /** Optional equality filters applied before the prefix range. */
48
10
  equalityFilters?: SearchEqualityFilter[];
49
11
  }
50
12
  //# sourceMappingURL=types.d.ts.map