Skip to content

Commit e5c47ba

Browse files
github-actions[bot]claude
authored andcommitted
fix: return single object from findOne in angular-db instead of array (issue #1261)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a137fca commit e5c47ba

1 file changed

Lines changed: 72 additions & 17 deletions

File tree

packages/angular-db/src/index.ts

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ import { BaseQueryBuilder, createLiveQueryCollection } from '@tanstack/db'
1010
import type {
1111
ChangeMessage,
1212
Collection,
13+
CollectionConfigSingleRowOption,
1314
CollectionStatus,
1415
Context,
1516
GetResult,
17+
InferResultType,
1618
InitialQueryBuilder,
1719
LiveQueryCollectionConfig,
20+
NonSingleResult,
1821
QueryBuilder,
22+
SingleResult,
1923
} from '@tanstack/db'
2024
import type { Signal } from '@angular/core'
2125

@@ -24,16 +28,14 @@ import type { Signal } from '@angular/core'
2428
* Contains reactive signals for the query state and data.
2529
*/
2630
export interface InjectLiveQueryResult<
27-
TResult extends object = any,
28-
TKey extends string | number = string | number,
29-
TUtils extends Record<string, any> = {},
31+
TContext extends Context,
3032
> {
3133
/** A signal containing the complete state map of results keyed by their ID */
32-
state: Signal<Map<TKey, TResult>>
33-
/** A signal containing the results as an array */
34-
data: Signal<Array<TResult>>
34+
state: Signal<Map<string | number, GetResult<TContext>>>
35+
/** A signal containing the results as an array, or single result for findOne queries */
36+
data: Signal<InferResultType<TContext>>
3537
/** A signal containing the underlying collection instance (null for disabled queries) */
36-
collection: Signal<Collection<TResult, TKey, TUtils> | null>
38+
collection: Signal<Collection<GetResult<TContext>, string | number, {}> | null>
3739
/** A signal containing the current status of the collection */
3840
status: Signal<CollectionStatus | `disabled`>
3941
/** A signal indicating whether the collection is currently loading */
@@ -48,6 +50,38 @@ export interface InjectLiveQueryResult<
4850
isCleanedUp: Signal<boolean>
4951
}
5052

53+
export interface InjectLiveQueryResultWithCollection<
54+
TResult extends object = any,
55+
TKey extends string | number = string | number,
56+
TUtils extends Record<string, any> = {},
57+
> {
58+
state: Signal<Map<TKey, TResult>>
59+
data: Signal<Array<TResult>>
60+
collection: Signal<Collection<TResult, TKey, TUtils> | null>
61+
status: Signal<CollectionStatus | `disabled`>
62+
isLoading: Signal<boolean>
63+
isReady: Signal<boolean>
64+
isIdle: Signal<boolean>
65+
isError: Signal<boolean>
66+
isCleanedUp: Signal<boolean>
67+
}
68+
69+
export interface InjectLiveQueryResultWithSingleResultCollection<
70+
TResult extends object = any,
71+
TKey extends string | number = string | number,
72+
TUtils extends Record<string, any> = {},
73+
> {
74+
state: Signal<Map<TKey, TResult>>
75+
data: Signal<TResult | undefined>
76+
collection: Signal<(Collection<TResult, TKey, TUtils> & SingleResult) | null>
77+
status: Signal<CollectionStatus | `disabled`>
78+
isLoading: Signal<boolean>
79+
isReady: Signal<boolean>
80+
isIdle: Signal<boolean>
81+
isError: Signal<boolean>
82+
isCleanedUp: Signal<boolean>
83+
}
84+
5185
export function injectLiveQuery<
5286
TContext extends Context,
5387
TParams extends any,
@@ -57,7 +91,7 @@ export function injectLiveQuery<
5791
params: TParams
5892
q: InitialQueryBuilder
5993
}) => QueryBuilder<TContext>
60-
}): InjectLiveQueryResult<GetResult<TContext>>
94+
}): InjectLiveQueryResult<TContext>
6195
export function injectLiveQuery<
6296
TContext extends Context,
6397
TParams extends any,
@@ -67,25 +101,34 @@ export function injectLiveQuery<
67101
params: TParams
68102
q: InitialQueryBuilder
69103
}) => QueryBuilder<TContext> | undefined | null
70-
}): InjectLiveQueryResult<GetResult<TContext>>
104+
}): InjectLiveQueryResult<TContext>
71105
export function injectLiveQuery<TContext extends Context>(
72106
queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
73-
): InjectLiveQueryResult<GetResult<TContext>>
107+
): InjectLiveQueryResult<TContext>
74108
export function injectLiveQuery<TContext extends Context>(
75109
queryFn: (
76110
q: InitialQueryBuilder,
77111
) => QueryBuilder<TContext> | undefined | null,
78-
): InjectLiveQueryResult<GetResult<TContext>>
112+
): InjectLiveQueryResult<TContext>
79113
export function injectLiveQuery<TContext extends Context>(
80114
config: LiveQueryCollectionConfig<TContext>,
81-
): InjectLiveQueryResult<GetResult<TContext>>
115+
): InjectLiveQueryResult<TContext>
116+
// Pre-created collection without singleResult
117+
export function injectLiveQuery<
118+
TResult extends object,
119+
TKey extends string | number,
120+
TUtils extends Record<string, any>,
121+
>(
122+
liveQueryCollection: Collection<TResult, TKey, TUtils> & NonSingleResult,
123+
): InjectLiveQueryResultWithCollection<TResult, TKey, TUtils>
124+
// Pre-created collection with singleResult
82125
export function injectLiveQuery<
83126
TResult extends object,
84127
TKey extends string | number,
85128
TUtils extends Record<string, any>,
86129
>(
87-
liveQueryCollection: Collection<TResult, TKey, TUtils>,
88-
): InjectLiveQueryResult<TResult, TKey, TUtils>
130+
liveQueryCollection: Collection<TResult, TKey, TUtils> & SingleResult,
131+
): InjectLiveQueryResultWithSingleResultCollection<TResult, TKey, TUtils>
89132
export function injectLiveQuery(opts: any) {
90133
assertInInjectionContext(injectLiveQuery)
91134
const destroyRef = inject(DestroyRef)
@@ -156,19 +199,31 @@ export function injectLiveQuery(opts: any) {
156199
})
157200

158201
const state = signal(new Map<string | number, any>())
159-
const data = signal<Array<any>>([])
202+
const internalData = signal<Array<any>>([])
160203
const status = signal<CollectionStatus | `disabled`>(
161204
collection() ? `idle` : `disabled`,
162205
)
163206

207+
// Returns single item for singleResult collections, array otherwise
208+
const data = computed(() => {
209+
const currentCollection = collection()
210+
if (!currentCollection) {
211+
return internalData()
212+
}
213+
const config = (currentCollection).config as
214+
| CollectionConfigSingleRowOption<any, any, any>
215+
| undefined
216+
return config?.singleResult ? internalData()[0] : internalData()
217+
})
218+
164219
const syncDataFromCollection = (
165220
currentCollection: Collection<any, any, any>,
166221
) => {
167222
const newState = new Map(currentCollection.entries())
168223
const newData = Array.from(currentCollection.values())
169224

170225
state.set(newState)
171-
data.set(newData)
226+
internalData.set(newData)
172227
status.set(currentCollection.status)
173228
}
174229

@@ -185,7 +240,7 @@ export function injectLiveQuery(opts: any) {
185240
if (!currentCollection) {
186241
status.set(`disabled` as const)
187242
state.set(new Map())
188-
data.set([])
243+
internalData.set([])
189244
cleanup()
190245
return
191246
}

0 commit comments

Comments
 (0)