Skip to content

Commit 2440ba5

Browse files
@daml/react: support for multi-{key,query} streams
This follows up on #7066 and exposes the new underlying multi-key and multi-query stream functions through the React bindings. Following the same reasoning as in #7066, we therefore deprecate the existing functions (with no intention of removing them) as they become redundant. CHANGELOG_BEGIN * JavaScript Client Libraries: Updated React bindings to expose the recent addition of multi-key and multi-query streams in @daml/ledger. The singular versions are marked as deprecated as they have become redundant. The upgrade path for `useStreamQuery` is very straightforward: the query factory remains optional, but if specified it should return an array of queries instead of a single query. The array may be empty, which will return all contracts for that template (similar as not passing in a query factory). The return values of `useStreamQuery` and `useStreamQueries` are the same type. ``` useStreamQuery(T) --> useStreamQueries(T) useStreamQuery(T, () => query, ...) --> useStreamQueries(T, () => [query], ...) ``` The upgrade path for `useStreamFetchByKey` is only slightly more involved as the return type of `useStreamFetchByKeys` is a new type called `FetchByKeysResult` instead of the existing `FetchResult`. `FetchByKeysResult` differs from `FetchResult` in that it contains a `contracts` field with an array of contracts instead of a singular `contract` field. (It differs from `QueryResult` in that each element of the returned array can also be `null`, if there is no corresponding active contract.) `QueryResult` instead of a `FetchResult`, i.e. it contains a list of contracts rather than a single contract. Call sites can be updated as follows: ``` const {loading, contract} = useStreamFetchByKey(T, () => k, ...); --> const {loading, contracts} = useStreamFetchByKeys(T, () => [k], ...)); const contract = contracts[0]; ``` CHANGELOG_END
1 parent 51a97d4 commit 2440ba5

File tree

8 files changed

+399
-233
lines changed

8 files changed

+399
-233
lines changed

docs/source/getting-started/app-architecture.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ It uses DAML React hooks to query and update ledger state.
134134

135135
The ``useParty`` hook simply returns the current user as stored in the ``DamlLedger`` context.
136136
A more interesting example is the ``allUsers`` line.
137-
This uses the ``useStreamQuery`` hook to get all ``User`` contracts on the ledger.
137+
This uses the ``useStreamQueries`` hook to get all ``User`` contracts on the ledger.
138138
(``User.User`` here is an object generated by ``daml codegen js`` - it stores metadata of the ``User`` template defined in ``User.daml``.)
139139
Note however that this query preserves privacy: only users that follow the current user have their contracts revealed.
140140
This behaviour is due to the observers on the ``User`` contract being exactly in the list of users that the current user is following.

language-support/ts/daml-react/README.md

+49-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ const onClick = reload;
8888

8989
`useStreamQuery`
9090
----------------
91+
92+
> Deprecated: prefer `useStreamQueries`
93+
9194
`useStreamQuery` has the same signature as `useQuery`, but it constantly refreshes the results.
9295

9396
```typescript
@@ -101,6 +104,27 @@ If the query is omitted, all visible contracts of the given template are returne
101104
const {contracts, loading} = useStreamQuery(ContractTemplate);
102105
```
103106

107+
`useStreamQueries`
108+
------------------
109+
110+
`useStreamQueries` is similar to `useQuery`, except that:
111+
- It constantly refreshes the results.
112+
- The factory function is expected to return a list of queries, and the
113+
resulting set of contracts is the union of all the contracts that match at
114+
least one query.
115+
- Like `useQuery`, if no factory function is provided, or if the provided
116+
function returns an empty array, the set will contain all contracts of that
117+
template.
118+
119+
```typescript
120+
const {contracts, loading} = useStreamQueries(ContractTemplate,
121+
() => [{field: value}, ...],
122+
[dependency1, dependency2, ...]);
123+
```
124+
125+
You can additionally pass in an extra function to handle WebSocket connection
126+
failures.
127+
104128
`useFetchByKey`
105129
---------------
106130
`useFetchByKey` returns the unique contract of a given template and a given contract key.
@@ -111,13 +135,37 @@ const {contract, loading} = useFetchByKey(ContractTemplate, () => key, [dependen
111135

112136
`useStreamFetchByKey`
113137
---------------------
114-
`useStreamFetchByKey` has the same signature as `useFetchByKey`, but it constantly keeps refreshes
138+
139+
> Deprecated: prefer `useStreamFetchByKeys`
140+
141+
`useStreamFetchByKey` has the same signature as `useFetchByKey`, but it constantly keeps refreshing
115142
the result.
116143

117144
```typescript
118145
const {contract, loading} = useStreamFetchByKey(ContractTemplate, () => key, [dependency1, dependency2, ...]);
119146
```
120147

148+
`useStreamFetchByKeys`
149+
---------------------
150+
151+
`useStreamFetchByKeys` takes a template and a factory that returns a list of
152+
keys, and returns a list of contracts that correspond to those keys (or null if
153+
no contract matches the corresponding key). This hook will keep an open
154+
WebSocket connection and listen for any change to the corresponding contracts.
155+
156+
If the factory function returns an empty array, the hook will similarly produce
157+
an empty array of contracts.
158+
159+
160+
```typescript
161+
const {contracts, loading} = useStreamFetchByKeys(ContractTemplate,
162+
() => [key1, key2, ...],
163+
[dependency1, dependency2, ...]);
164+
```
165+
166+
You can additionally pass in an extra function to handle WebSocket connection
167+
failures.
168+
121169
## Advanced Usage
122170

123171
In order to interact as multiple parties or to connect to several ledgers, one needs to create an extra

language-support/ts/daml-react/createLedgerContext.ts

+51-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ export type FetchResult<T extends object, K, I extends string> = {
5454
loading: boolean;
5555
}
5656

57+
/**
58+
* The result of a streaming ``fetchByKeys`` against the ledger.
59+
*
60+
* @typeparam T The contract template type of the query.
61+
* @typeparam K The contract key type of the query.
62+
* @typeparam I The template id type.
63+
*/
64+
export type FetchByKeysResult<T extends object, K, I extends string> = {
65+
/** Contracts of the given contract template and key. */
66+
contracts: (CreateEvent<T, K, I> | null)[];
67+
/** Indicator for whether the fetch is executing. */
68+
loading: boolean;
69+
}
70+
5771
/**
5872
* A LedgerContext is a React context that stores information about a DAML Ledger
5973
* and hooks necessary to use it.
@@ -66,7 +80,9 @@ export type LedgerContext = {
6680
useFetch: <T extends object, K, I extends string>(template: Template<T, K, I>, contractId: ContractId<T>) => FetchResult<T, K, I>;
6781
useFetchByKey: <T extends object, K, I extends string>(template: Template<T, K, I>, keyFactory: () => K, keyDeps: readonly unknown[]) => FetchResult<T, K, I>;
6882
useStreamQuery: <T extends object, K, I extends string>(template: Template<T, K, I>, queryFactory?: () => Query<T>, queryDeps?: readonly unknown[], closeHandler?: (e: StreamCloseEvent) => void) => QueryResult<T, K, I>;
83+
useStreamQueries: <T extends object, K, I extends string>(template: Template<T, K, I>, queryFactory?: () => Query<T>[], queryDeps?: readonly unknown[], closeHandler?: (e: StreamCloseEvent) => void) => QueryResult<T, K, I>;
6984
useStreamFetchByKey: <T extends object, K, I extends string>(template: Template<T, K, I>, keyFactory: () => K, keyDeps: readonly unknown[], closeHandler?: (e: StreamCloseEvent) => void) => FetchResult<T, K, I>;
85+
useStreamFetchByKeys: <T extends object, K, I extends string>(template: Template<T, K, I>, keyFactory: () => K[], keyDeps: readonly unknown[], closeHandler?: (e: StreamCloseEvent) => void) => FetchByKeysResult<T, K, I>;
7086
useReload: () => () => void;
7187
}
7288

@@ -220,6 +236,23 @@ export function createLedgerContext(contextName="DamlLedgerContext"): LedgerCont
220236
});
221237
}
222238

239+
function useStreamQueries<T extends object, K, I extends string>(template: Template<T, K, I>, queryFactory?: () => Query<T>[], queryDeps?: readonly unknown[], closeHandler?: (e: StreamCloseEvent) => void): QueryResult<T, K, I> {
240+
return useStream<T, K, I, readonly CreateEvent<T, K, I>[], QueryResult<T, K, I>>({
241+
name: "useStreamQueries",
242+
template,
243+
init: {loading: true, contracts: []},
244+
mkStream: (state) => {
245+
const query = queryFactory ? queryFactory() : [];
246+
const stream = state.ledger.streamQueries(template, query);
247+
return [stream, query];
248+
},
249+
setLoading: (r, b) => ({...r, loading: b}),
250+
setData: (r, d) => ({...r, contracts: d}),
251+
deps: queryDeps ?? [],
252+
closeHandler
253+
});
254+
}
255+
223256
function useStreamFetchByKey<T extends object, K, I extends string>(template: Template<T, K, I>, keyFactory: () => K, keyDeps: readonly unknown[], closeHandler?: (e: StreamCloseEvent) => void): FetchResult<T, K, I> {
224257
return useStream<T, K, I, (CreateEvent<T, K, I> | null)[], FetchResult<T, K, I>>({
225258
name: "useStreamFetchByKey",
@@ -237,10 +270,27 @@ export function createLedgerContext(contextName="DamlLedgerContext"): LedgerCont
237270
});
238271
}
239272

273+
function useStreamFetchByKeys<T extends object, K, I extends string>(template: Template<T, K, I>, keyFactory: () => K[], keyDeps: readonly unknown[], closeHandler?: (e: StreamCloseEvent) => void): FetchByKeysResult<T, K, I> {
274+
return useStream<T, K, I, (CreateEvent<T, K, I> | null)[], FetchByKeysResult<T, K, I>>({
275+
name: "useStreamFetchByKeys",
276+
template,
277+
init: {loading: true, contracts: []},
278+
mkStream: (state) => {
279+
const keys = keyFactory();
280+
const stream = state.ledger.streamFetchByKeys(template, keys);
281+
return [stream, keys as unknown as object];
282+
},
283+
setLoading: (r, b) => ({...r, loading: b}),
284+
setData: (r, d) => ({...r, contracts: d}),
285+
deps: keyDeps,
286+
closeHandler
287+
});
288+
}
289+
240290
const useReload = (): () => void => {
241291
const state = useDamlState();
242292
return (): void => state.triggerReload();
243293
}
244294

245-
return { DamlLedger, useParty, useLedger, useQuery, useFetch, useFetchByKey, useStreamQuery, useStreamFetchByKey, useReload };
295+
return { DamlLedger, useParty, useLedger, useQuery, useFetch, useFetchByKey, useStreamQuery, useStreamQueries, useStreamFetchByKey, useStreamFetchByKeys, useReload };
246296
}

language-support/ts/daml-react/defaultLedgerContext.ts

+49-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { createLedgerContext, FetchResult, QueryResult, LedgerProps } from "./createLedgerContext";
4+
import { createLedgerContext, FetchResult, QueryResult, LedgerProps, FetchByKeysResult } from "./createLedgerContext";
55
import { ContractId, Party, Template } from '@daml/types';
66
import Ledger, { Query, StreamCloseEvent } from '@daml/ledger';
77

@@ -84,6 +84,8 @@ export function useFetchByKey<T extends object, K, I extends string>(template: T
8484
/**
8585
* React Hook to query the ledger, the returned result is updated as the ledger state changes.
8686
*
87+
* @deprecated prefer useStreamQueries
88+
*
8789
* @typeparam T The contract template type of the query.
8890
* @typeparam K The contract key type of the query.
8991
* @typeparam I The template id type.
@@ -99,9 +101,29 @@ export function useStreamQuery<T extends object, K, I extends string>(template:
99101
return ledgerContext.useStreamQuery(template, queryFactory, queryDeps, closeHandler);
100102
}
101103

104+
/**
105+
* React Hook to query the ledger, the returned result is updated as the ledger state changes.
106+
*
107+
* @typeparam T The contract template type of the query.
108+
* @typeparam K The contract key type of the query.
109+
* @typeparam I The template id type.
110+
*
111+
* @param template The template of the contracts to match.
112+
* @param queryFactory A function returning an array of queries. If no queryFactory is given, or if the given factory returns an empty array, all visible contracts of the given template are returned. Otherwise, returns a union of all the contracts that match at least one of the given queries.
113+
* @param queryDeps The dependencies of the query (for which a change triggers an update of the result).
114+
* @param closeHandler A callback that will be called if the underlying WebSocket connection fails in an unrecoverable way.
115+
*
116+
* @return The matching contracts.
117+
*/
118+
export function useStreamQueries<T extends object, K, I extends string>(template: Template<T, K, I>, queryFactory?: () => Query<T>[], queryDeps?: readonly unknown[], closeHandler?: (e: StreamCloseEvent) => void): QueryResult<T, K, I> {
119+
return ledgerContext.useStreamQueries(template, queryFactory, queryDeps, closeHandler);
120+
}
121+
102122
/**
103123
* React Hook to query the ledger. Same as useStreamQuery, but query by contract key instead.
104124
*
125+
* @deprecated prefer useStreamFetchByKeys
126+
*
105127
* @typeparam T The contract template type of the query.
106128
* @typeparam K The contract key type of the query.
107129
* @typeparam I The template id type.
@@ -111,12 +133,37 @@ export function useStreamQuery<T extends object, K, I extends string>(template:
111133
* @param queryDeps The dependencies of the query (for which a change triggers an update of the result).
112134
* @param closeHandler A callback that will be called if the underlying WebSocket connection fails in an unrecoverable way.
113135
*
114-
* @return The matching (unique) contract.
136+
* @return The matching (unique) contract, or null.
115137
*/
116138
export function useStreamFetchByKey<T extends object, K, I extends string>(template: Template<T, K, I>, keyFactory: () => K, keyDeps: readonly unknown[]): FetchResult<T, K, I> {
117139
return ledgerContext.useStreamFetchByKey(template, keyFactory, keyDeps);
118140
}
119141

142+
/**
143+
* React Hook to query the ledger. Same as useStreamQueries, but query by contract keys instead.
144+
*
145+
* @typeparam T The contract template type of the query.
146+
* @typeparam K The contract key type of the query.
147+
* @typeparam I The template id type.
148+
*
149+
* @param template The template of the contracts to match.
150+
* @param queryFactory A function returning an array of contract keys. Contract
151+
* keys must be in "output" format as defined in the [JSON API
152+
* docs](https://docs.daml.com/json-api/lf-value-specification.html),
153+
* i.e., have to match the types generated by the daml-types library.
154+
* @param queryDeps The dependencies of the query (for which a change triggers an update of the result).
155+
* @param closeHandler A callback that will be called if the underlying
156+
* WebSocket connection fails in an unrecoverable way.
157+
*
158+
* @return An array of the same length as the given array of keys, where each
159+
* element is either the currently active contract that matches the
160+
* corresponding key, or null if no active contract matches the key in
161+
* the same position.
162+
*/
163+
export function useStreamFetchByKeys<T extends object, K, I extends string>(template: Template<T, K, I>, keyFactory: () => K[], keyDeps: readonly unknown[]): FetchByKeysResult<T, K, I> {
164+
return ledgerContext.useStreamFetchByKeys(template, keyFactory, keyDeps);
165+
}
166+
120167
/**
121168
* React Hook to reload all active queries.
122169
*/

0 commit comments

Comments
 (0)