Skip to content

Commit 7052e65

Browse files
add multi-{key,query} stream to daml-ledger
This is an attempt to address #7034 & #7036. Strictly speaking, this does not match their acceptance criteria, as this only supports multiple queries, not mixed templates. Because the two new functions can cover the exact same use cases (and more) as the existing `streamQuery` and `streamFetchByKey`, the latter are deprecated. The deprecation cycle I suggest is to deprecate them immediately by annotating them as such in the documentation (done on this PR). That's it. I do not think we ever need to actually remove them, nor to make them print annoying warnings or anything. There is nothing wrong with the functions as they stand, they just don't fit in the API anymore. We could, at some point, move them to a separate documentation page, or to the boottm of the existing one, but I feel even removing them from the documentation is unnecessary. CHANGELOG_BEGIN - [JavaScript Client Libraries] Two new methods have been added to `daml-ledger`: `streamQueries` and `streamFetchByKeys`. They are similar to the existing singular versions, except they can take multiple queries and multiple keys, respectively, and return a union of what the corresponding individual queries/keys would have. Because these new functions can do everything the existing ones can, we are deprecating the latter, though there is no plan to remove them. Upgrade path is straightforward: ``` streamQuery(t); => streamQueries(t, []); streamQuery(t, undefined); => streamQueries(t, []); streamQuery(t, q); => streamQueries(t, [q]); streamFetchByKey(t, k); => streamFetchByKey(t, [k]); ``` There is one caveat, though: `streamFetchByKeys` is a little bit less lenient in the format in which it expects the key. If your existing code was conforming to the generated TypeScript code we provide, everything should keep working, but if you were using plain JS or bypassing the TS type system, it is possible that you used to construct keys that will no longer be accepted. The new function requires all keys to be given in the _output_ format of the JSON API, which is a little bit more strict than the general JSON <-> LF conversion rules. CHANGELOG_END
1 parent d33e130 commit 7052e65

File tree

12 files changed

+791
-137
lines changed

12 files changed

+791
-137
lines changed

language-support/ts/codegen/tests/daml/Main.daml

+14
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,17 @@ data VoidRecord = VoidRecord with
153153

154154
data VoidEnum = VoidEnum VoidEnum
155155
deriving (Eq, Show)
156+
157+
template Counter with
158+
p: Party
159+
t: Text
160+
c: Int
161+
where
162+
signatory p
163+
key (p, t): (Party, Text)
164+
maintainer key._1
165+
controller p can
166+
preconsuming Change: ContractId Counter
167+
with n: Int
168+
do
169+
create Counter {c = n, ..}

language-support/ts/codegen/tests/ts/build-and-lint-test/src/__tests__/test.ts

+143
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,146 @@ test('create + fetch & exercise', async () => {
312312
const nonTopLevelContracts = await aliceLedger.query(buildAndLint.Lib.Mod.NonTopLevel);
313313
expect(nonTopLevelContracts).toEqual([nonTopLevelContract]);
314314
});
315+
316+
test("multi-{key,query} stream", async () => {
317+
const ledger = new Ledger({token: ALICE_TOKEN, httpBaseUrl: httpBaseUrl()});
318+
319+
function collect<T extends object, K, I extends string, State>(stream: Stream<T, K, I, State>): [State, readonly Event<T, K, I>[]][] {
320+
const res = [] as [State, readonly Event<T, K, I>[]][];
321+
stream.on('change', (state, events) => res.push([state, events]));
322+
return res;
323+
}
324+
async function create(t: string): Promise<void> {
325+
await ledger.create(buildAndLint.Main.Counter, {p: ALICE_PARTY, t, c: "0"});
326+
}
327+
async function update(t: string, c: number): Promise<void> {
328+
await ledger.exerciseByKey(buildAndLint.Main.Counter.Change, {_1: ALICE_PARTY, _2: t}, {n: c.toString()});
329+
}
330+
async function archive(t: string): Promise<void> {
331+
await ledger.archiveByKey(buildAndLint.Main.Counter, {_1: ALICE_PARTY, _2: t});
332+
}
333+
async function close<T extends object, K, I extends string, State>(s: Stream<T, K, I, State>): Promise<void> {
334+
const p = pEvent(s, 'close');
335+
s.close();
336+
await p;
337+
}
338+
// Add support for comparison queries
339+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
340+
const streamQueriesWithComparison = ledger.streamQueries.bind(ledger) as (t: any, qs: any) => any;
341+
const q = streamQueriesWithComparison(
342+
buildAndLint.Main.Counter,
343+
[{p: ALICE_PARTY, t: "included"},
344+
{c: {"%gt": 5}}]);
345+
const queryResult = collect(q);
346+
const ks = ledger.streamFetchByKeys(
347+
buildAndLint.Main.Counter,
348+
[{_1: ALICE_PARTY, _2: "included"},
349+
{_1: ALICE_PARTY, _2: "byKey"},
350+
{_1: ALICE_PARTY, _2: "included"}]);
351+
const byKeysResult = collect(ks);
352+
353+
await create("included");
354+
await create("byKey");
355+
await create("excluded");
356+
357+
await update("excluded", 10);
358+
await update("byKey", 3);
359+
await update("byKey", 6);
360+
await update("excluded", 3);
361+
await update("included", 2);
362+
363+
await archive("included");
364+
await archive("byKey");
365+
366+
await create("included");
367+
368+
369+
await close(q);
370+
await close(ks);
371+
372+
expect(queryResult).toMatchObject(
373+
[[[{"payload": {"c": "0", "t": "included"}}],
374+
[{"created": {"payload": {"c": "0", "t": "included"}}}]],
375+
376+
[[{"payload": {"c": "0", "t": "included"}},
377+
{"payload": {"c": "10", "t": "excluded"}}],
378+
[{"archived": {}},
379+
{"created": {"payload": {"c": "10", "t": "excluded"}}}]],
380+
381+
[[{"payload": {"c": "0", "t": "included"}},
382+
{"payload": {"c": "10", "t": "excluded"}}],
383+
[{"archived": {}}]],
384+
385+
[[{"payload": {"c": "0", "t": "included"}},
386+
{"payload": {"c": "10", "t": "excluded"}},
387+
{"payload": {"c": "6", "t": "byKey"}}],
388+
[{"archived": {}},
389+
{"created": {"payload": {"c": "6", "t": "byKey"}}}]],
390+
391+
[[{"payload": {"c": "0", "t": "included"}},
392+
{"payload": {"c": "6", "t": "byKey"}}],
393+
[{"archived": {}}]],
394+
395+
[[{"payload": {"c": "6", "t": "byKey"}},
396+
{"payload": {"c": "2", "t": "included"}}],
397+
[{"archived": {}},
398+
{"created": {"payload": {"c": "2", "t": "included"}}}]],
399+
400+
[[{"payload": {"c": "6", "t": "byKey"}}],
401+
[{"archived": {}}]],
402+
403+
[[],
404+
[{"archived": {}}]],
405+
406+
[[{"payload": {"c": "0", "t": "included"}}],
407+
[{"created": {"payload": {"c": "0", "t": "included"}}}]]]
408+
409+
);
410+
411+
expect(byKeysResult).toMatchObject(
412+
[[[{"payload": {"c": "0", "t": "included"}},
413+
null,
414+
{"payload": {"c": "0", "t": "included"}}],
415+
[{"created": {"payload": {"c": "0", "t": "included"}}}]],
416+
417+
[[{"payload": {"c": "0", "t": "included"}},
418+
{"payload": {"c": "0", "t": "byKey"}},
419+
{"payload": {"c": "0", "t": "included"}}],
420+
[{"created": {"payload": {"c": "0", "t": "byKey"}}}]],
421+
422+
[[{"payload": {"c": "0", "t": "included"}},
423+
{"payload": {"c": "3", "t": "byKey"}},
424+
{"payload": {"c": "0", "t": "included"}}],
425+
[{"archived": {}},
426+
{"created": {"payload": {"c": "3", "t": "byKey"}}}]],
427+
428+
[[{"payload": {"c": "0", "t": "included"}},
429+
{"payload": {"c": "6", "t": "byKey"}},
430+
{"payload": {"c": "0", "t": "included"}}],
431+
[{"archived": {}},
432+
{"created": {"payload": {"c": "6", "t": "byKey"}}}]],
433+
434+
[[{"payload": {"c": "2", "t": "included"}},
435+
{"payload": {"c": "6", "t": "byKey"}},
436+
{"payload": {"c": "2", "t": "included"}}],
437+
[{"archived": {}},
438+
{"created": {"payload": {"c": "2", "t": "included"}}}]],
439+
440+
[[null,
441+
{"payload": {"c": "6", "t": "byKey"}},
442+
null],
443+
[{"archived": {}}]],
444+
445+
[[null,
446+
null,
447+
null],
448+
[{"archived": {}}]],
449+
450+
[[{"payload": {"c": "0", "t": "included"}},
451+
null,
452+
{"payload": {"c": "0", "t": "included"}}],
453+
[{"created": {"payload": {"c": "0", "t": "included"}}}]]
454+
]
455+
);
456+
457+
});

language-support/ts/daml-ledger/BUILD.bazel

+2
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ da_ts_library(
2323
deps = [
2424
"//language-support/ts/daml-types",
2525
"@language_support_ts_deps//@mojotech/json-type-validation",
26+
"@language_support_ts_deps//@types/deep-equal",
2627
"@language_support_ts_deps//@types/jest",
2728
"@language_support_ts_deps//@types/ws",
2829
"@language_support_ts_deps//cross-fetch",
30+
"@language_support_ts_deps//deep-equal",
2931
"@language_support_ts_deps//events",
3032
"@language_support_ts_deps//isomorphic-ws",
3133
"@language_support_ts_deps//jest-mock-console",

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

+36-5
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,21 @@ of that template visible for the submitting party are returned.
5454

5555
`streamQuery`
5656
-------------
57-
Retrieve a consolidated stream of events for a given template and query. The accumulated state is
58-
the current set of active contracts matching the query. An event can be a `CreateEvent` or an
59-
`ArchiveEvent`. When no `query` argument is given, all events visible to the submitting party are
60-
returned.
57+
58+
> Deprecated: prefer `streamQueries`.
59+
60+
Retrieve a consolidated stream of events for a given template and optional
61+
query. The accumulated state is the current set of active contracts matching
62+
the query if one was given; if the function was called without a query
63+
argument, or the query argument was `undefined`, the accumulated state will
64+
instead contain all of the active contracts for the given template.
65+
66+
`streamQueries`
67+
---------------
68+
Retrieve a consolidated stream of events for a given template and queries. The
69+
accumulated state is the current set of active contracts matching at least one
70+
of the given queries, or all contracts for the given template if no query is
71+
given.
6172

6273
`fetch`
6374
-------
@@ -69,11 +80,31 @@ Fetch a contract identified by its contract key.
6980

7081
`streamFetchByKey`
7182
------------------
72-
Retrieve a consolidated stream of `CreateEvent`'s for a given template and contract key.
7383

84+
> Deprecated: prefer `streamFetchByKeys`.
85+
86+
Retrieve a consolidated stream of `CreateEvent`'s for a given template and
87+
contract key. The accumulated state is either the `CreateEvent` for the active
88+
contract matching the given key, or null if there is no currently-active
89+
contract for the given key.
90+
91+
`streamFetchByKeys`
92+
-------------------
93+
Retrieve a consolidated stream of `CreateEvent`'s for a given template and
94+
contract keys. The accumulated state is a vector of the same length as the
95+
given vector of keys, where each element is the CreateEvent for the current
96+
active contract of the corresponding key (element-wise), or null if there is no
97+
current active contract for that key.
98+
99+
Note: the given `key` objects will be compared for (deep) equality with the
100+
values returned by the API. As such, they have to be given in the "output"
101+
format of the API, including the values of `encodeDecimalAsString` and
102+
`encodeInt64AsString`. See [the JSON API docs for details][0].
74103

75104
## Source
76105
https://github.com/digital-asset/daml/tree/master/language-support/ts/daml-ledger
77106

78107
## License
79108
[Apache-2.0](https://github.com/digital-asset/daml/blob/master/LICENSE)
109+
110+
[0]: https://docs.daml.com/json-api/lf-value-specification.html

0 commit comments

Comments
 (0)