daml2ts: Support lookupByKey (#3987)

* daml2ts: Support lookupByKey

We'll add `fetchByKey` and `exerciseByKey` in a separate PR. We'll remove
the `pseudo*` methods in another PR after that one.

CHANGELOG_BEGIN
CHANGELOG_END

* daml2ts: make `lookupByKey` inaccessible for templates without key
This commit is contained in:
Martin Huschenbett 2020-01-09 11:51:44 +01:00 committed by mergify[bot]
parent 49b90bfb36
commit 20ad5263d4
5 changed files with 42 additions and 13 deletions

View File

@ -168,12 +168,19 @@ genDefDataType curModName tpls def = case unTypeConName (dataTypeCon def) of
, let (rtyp, rser) = genType curModName rLf
, let argRefs = Set.setOf typeModuleRef tLf
]
(keyTypeTs, keySer) = case tplKey tpl of
Nothing -> ("undefined", "() => jtv.constant(undefined)")
Just key ->
let (keyTypeTs, keySer) = genType curModName (tplKeyType key)
in
(keyTypeTs, "() => " <> keySer <> ".decoder()")
dict =
["export const " <> conName <> ": daml.Template<" <> conName <> "> & {"] ++
["export const " <> conName <> ": daml.Template<" <> conName <> ", " <> keyTypeTs <> "> & {"] ++
[" " <> x <> ": daml.Choice<" <> conName <> ", " <> t <> ", " <> rtyp <> " >;" | (x, t, rtyp, _) <- chcs] ++
["} = {"
] ++
[" templateId: templateId('" <> conName <> "'),"
," keyDecoder: " <> keySer <> ","
] ++
map (" " <>) (onHead ("decoder: " <>) serDesc) ++
concat

View File

@ -38,6 +38,9 @@ template Person with
where
signatory party
key (party, age): (Party, Int)
maintainer key._1
choice Birthday: ContractId Person
controller party
do

View File

@ -32,8 +32,9 @@ export type TemplateId = {
* Interface for objects representing DAML templates. It is similar to the
* `Template` type class in DAML.
*/
export interface Template<T extends object> extends Serializable<T> {
export interface Template<T extends object, K = unknown> extends Serializable<T> {
templateId: TemplateId;
keyDecoder: () => jtv.Decoder<K>;
Archive: Choice<T, {}, {}>;
}

View File

@ -4,13 +4,13 @@ import { Choice, ContractId, List, Party, Template, TemplateId, Text, lookupTemp
import * as jtv from '@mojotech/json-type-validation';
import fetch from 'cross-fetch';
export type CreateEvent<T extends object> = {
export type CreateEvent<T extends object, K = unknown> = {
templateId: TemplateId;
contractId: ContractId<T>;
signatories: List<Party>;
observers: List<Party>;
agreementText: Text;
key: unknown;
key: K;
payload: T;
}
@ -19,8 +19,8 @@ export type ArchiveEvent<T extends object> = {
contractId: ContractId<T>;
}
export type Event<T extends object> =
| { created: CreateEvent<T> }
export type Event<T extends object, K = unknown> =
| { created: CreateEvent<T, K> }
| { archived: ArchiveEvent<T> }
const decodeTemplateId: jtv.Decoder<TemplateId> = jtv.object({
@ -29,13 +29,13 @@ const decodeTemplateId: jtv.Decoder<TemplateId> = jtv.object({
entityName: jtv.string(),
});
const decodeCreateEvent = <T extends object>(template: Template<T>): jtv.Decoder<CreateEvent<T>> => jtv.object({
const decodeCreateEvent = <T extends object, K>(template: Template<T, K>): jtv.Decoder<CreateEvent<T, K>> => jtv.object({
templateId: decodeTemplateId,
contractId: ContractId(template).decoder(),
signatories: List(Party).decoder(),
observers: List(Party).decoder(),
agreementText: Text.decoder(),
key: jtv.unknownJson(),
key: template.keyDecoder(),
payload: template.decoder(),
});
@ -132,7 +132,7 @@ class Ledger {
* https://github.com/digital-asset/daml/blob/master/docs/source/json-api/search-query-language.rst
* for a description of the query language.
*/
async query<T extends object>(template: Template<T>, query: Query<T>): Promise<CreateEvent<T>[]> {
async query<T extends object, K>(template: Template<T, K>, query: Query<T>): Promise<CreateEvent<T, K>[]> {
const payload = {"%templates": [template.templateId], ...query};
const json = await this.submit('contracts/search', payload);
return jtv.Result.withException(jtv.array(decodeCreateEvent(template)).run(json));
@ -141,15 +141,24 @@ class Ledger {
/**
* Retrieve all contracts for a given template.
*/
async fetchAll<T extends object>(template: Template<T>): Promise<CreateEvent<T>[]> {
async fetchAll<T extends object, K>(template: Template<T, K>): Promise<CreateEvent<T, K>[]> {
return this.query(template, {} as Query<T>);
}
async lookupByKey<T extends object, K>(template: Template<T, K>, key: K extends undefined ? never : K): Promise<CreateEvent<T, K> | null> {
const payload = {
templateId: template.templateId,
key,
};
const json = await this.submit('contracts/lookup', payload);
return jtv.Result.withException(jtv.oneOf(jtv.constant(null), decodeCreateEvent(template)).run(json));
}
/**
* Mimic DAML's `lookupByKey`. The `key` must be a formulation of the
* contract key as a query.
*/
async pseudoLookupByKey<T extends object>(template: Template<T>, key: Query<T>): Promise<CreateEvent<T> | undefined> {
async pseudoLookupByKey<T extends object, K>(template: Template<T, K>, key: Query<T>): Promise<CreateEvent<T, K> | undefined> {
const contracts = await this.query(template, key);
if (contracts.length > 1) {
throw Error("pseudoLookupByKey: query returned multiple contracts");
@ -161,7 +170,7 @@ class Ledger {
* Mimic DAML's `fetchByKey`. The `key` must be a formulation of the
* contract key as a query.
*/
async pseudoFetchByKey<T extends object>(template: Template<T>, key: Query<T>): Promise<CreateEvent<T>> {
async pseudoFetchByKey<T extends object, K>(template: Template<T, K>, key: Query<T>): Promise<CreateEvent<T, K>> {
const contract = await this.pseudoLookupByKey(template, key);
if (contract === undefined) {
throw Error("pseudoFetchByKey: query returned no contract");
@ -172,7 +181,7 @@ class Ledger {
/**
* Create a contract for a given template.
*/
async create<T extends object>(template: Template<T>, argument: T): Promise<CreateEvent<T>> {
async create<T extends object, K>(template: Template<T, K>, argument: T): Promise<CreateEvent<T, K>> {
const payload = {
templateId: template.templateId,
argument,

View File

@ -72,13 +72,21 @@ test('create + fetch & exercise', async () => {
party: ALICE_PARTY,
age: '5',
};
const aliceKey = {_1: alice.party, _2: alice.age};
const aliceContract = await ledger.create(Main.Person, alice);
expect(aliceContract.payload).toEqual(alice);
expect(aliceContract.key).toEqual(aliceKey);
const personContracts = await ledger.fetchAll(Main.Person);
expect(personContracts).toHaveLength(1);
expect(personContracts[0]).toEqual(aliceContract);
const aliceContractByKey = await ledger.lookupByKey(Main.Person, aliceKey);
expect(aliceContractByKey).toEqual(aliceContract);
const bobByKey = await ledger.lookupByKey(Main.Person, {_1: 'Bob', _2: '4'});
expect(bobByKey).toBeNull();
// Alice has a birthday.
const [er, es] = await ledger.exercise(Main.Person.Birthday, aliceContract.contractId, {});
// Resulting in her old record being archived and replaced with a new one.
@ -114,6 +122,7 @@ test('create + fetch & exercise', async () => {
};
const allTypesContract = await ledger.create(Main.AllTypes, allTypes);
expect(allTypesContract.payload).toEqual(allTypes);
expect(allTypesContract.key).toBeUndefined();
const allTypesContracts = await ledger.fetchAll(Main.AllTypes);
expect(allTypesContracts).toHaveLength(1);