From 20ad5263d4736b1c2c0d32c1c98a8ebbddd89bf5 Mon Sep 17 00:00:00 2001 From: Martin Huschenbett Date: Thu, 9 Jan 2020 11:51:44 +0100 Subject: [PATCH] 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 --- .../ts/codegen/src/TsCodeGenMain.hs | 9 +++++- .../ts/codegen/tests/daml/Main.daml | 3 ++ .../tests/ts/daml-json-types/src/index.ts | 3 +- .../tests/ts/daml-ledger-fetch/src/index.ts | 31 ++++++++++++------- .../tests/ts/generated/src/__tests__/test.ts | 9 ++++++ 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/language-support/ts/codegen/src/TsCodeGenMain.hs b/language-support/ts/codegen/src/TsCodeGenMain.hs index a926f06dd2..93d2ca5c86 100644 --- a/language-support/ts/codegen/src/TsCodeGenMain.hs +++ b/language-support/ts/codegen/src/TsCodeGenMain.hs @@ -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 diff --git a/language-support/ts/codegen/tests/daml/Main.daml b/language-support/ts/codegen/tests/daml/Main.daml index 59084f6c6b..3b73f66501 100644 --- a/language-support/ts/codegen/tests/daml/Main.daml +++ b/language-support/ts/codegen/tests/daml/Main.daml @@ -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 diff --git a/language-support/ts/codegen/tests/ts/daml-json-types/src/index.ts b/language-support/ts/codegen/tests/ts/daml-json-types/src/index.ts index b2c58e5d58..8b13d05e9e 100644 --- a/language-support/ts/codegen/tests/ts/daml-json-types/src/index.ts +++ b/language-support/ts/codegen/tests/ts/daml-json-types/src/index.ts @@ -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 extends Serializable { +export interface Template extends Serializable { templateId: TemplateId; + keyDecoder: () => jtv.Decoder; Archive: Choice; } diff --git a/language-support/ts/codegen/tests/ts/daml-ledger-fetch/src/index.ts b/language-support/ts/codegen/tests/ts/daml-ledger-fetch/src/index.ts index 350bb4c55d..773fde8f43 100644 --- a/language-support/ts/codegen/tests/ts/daml-ledger-fetch/src/index.ts +++ b/language-support/ts/codegen/tests/ts/daml-ledger-fetch/src/index.ts @@ -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 = { +export type CreateEvent = { templateId: TemplateId; contractId: ContractId; signatories: List; observers: List; agreementText: Text; - key: unknown; + key: K; payload: T; } @@ -19,8 +19,8 @@ export type ArchiveEvent = { contractId: ContractId; } -export type Event = - | { created: CreateEvent } +export type Event = + | { created: CreateEvent } | { archived: ArchiveEvent } const decodeTemplateId: jtv.Decoder = jtv.object({ @@ -29,13 +29,13 @@ const decodeTemplateId: jtv.Decoder = jtv.object({ entityName: jtv.string(), }); -const decodeCreateEvent = (template: Template): jtv.Decoder> => jtv.object({ +const decodeCreateEvent = (template: Template): jtv.Decoder> => 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(template: Template, query: Query): Promise[]> { + async query(template: Template, query: Query): Promise[]> { 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(template: Template): Promise[]> { + async fetchAll(template: Template): Promise[]> { return this.query(template, {} as Query); } + async lookupByKey(template: Template, key: K extends undefined ? never : K): Promise | 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(template: Template, key: Query): Promise | undefined> { + async pseudoLookupByKey(template: Template, key: Query): Promise | 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(template: Template, key: Query): Promise> { + async pseudoFetchByKey(template: Template, key: Query): Promise> { 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(template: Template, argument: T): Promise> { + async create(template: Template, argument: T): Promise> { const payload = { templateId: template.templateId, argument, diff --git a/language-support/ts/codegen/tests/ts/generated/src/__tests__/test.ts b/language-support/ts/codegen/tests/ts/generated/src/__tests__/test.ts index bc111ed4ad..1ed3e7945b 100644 --- a/language-support/ts/codegen/tests/ts/generated/src/__tests__/test.ts +++ b/language-support/ts/codegen/tests/ts/generated/src/__tests__/test.ts @@ -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);