Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 23 additions & 4 deletions src/TemplateArchiveProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,25 @@ export class TemplateArchiveProcessor {
this.template = template;
}

/**
* Resolves the logic entry point from compiled code
* @param {Record<string, TwoSlashReturn>} compiledCode - the compiled code map
* @returns {string} the entry point key
*/
private resolveLogicEntryPoint(compiledCode: Record<string, TwoSlashReturn>): string {
const entryPoint = Object.keys(compiledCode).find(
key => key.startsWith('logic/') &&
key.split('/').length === 2 &&
!key.includes('generated/') &&
key.endsWith('.ts') &&
compiledCode[key].code !== undefined
);
if (!entryPoint) {
throw new Error('Could not find compiled logic entry point');
}
return entryPoint;
}

/**
* Drafts a template by merging it with data
* @param {any} data the data to merge with the template
Expand Down Expand Up @@ -111,13 +130,13 @@ export class TemplateArchiveProcessor {
const result = compiler.compile(code);
compiledCode[tsFile.getIdentifier()] = result;
}
// console.log(compiledCode['logic/logic.ts'].code);
const entryPoint = this.resolveLogicEntryPoint(compiledCode);
const evaluator = new JavaScriptEvaluator();
const evalResponse = await evaluator.evalDangerously( {
templateLogic: true,
verbose: false,
functionName: 'trigger',
code: compiledCode['logic/logic.ts'].code, // TODO DCS - how to find the code to run?
code: compiledCode[entryPoint].code,
argumentNames: ['data', 'request', 'state'],
arguments: [data, request, state, currentTime, utcOffset]
});
Expand Down Expand Up @@ -160,13 +179,13 @@ export class TemplateArchiveProcessor {
const result = compiler.compile(code);
compiledCode[tsFile.getIdentifier()] = result;
}
// console.log(compiledCode['logic/logic.ts'].code);
const entryPoint = this.resolveLogicEntryPoint(compiledCode);
const evaluator = new JavaScriptEvaluator();
const evalResponse = await evaluator.evalDangerously( {
templateLogic: true,
verbose: false,
functionName: 'init',
code: compiledCode['logic/logic.ts'].code, // TODO DCS - how to find the code to run?
code: compiledCode[entryPoint].code,
argumentNames: ['data'],
arguments: [data, currentTime, utcOffset]
});
Expand Down
56 changes: 55 additions & 1 deletion test/TemplateArchiveProcessor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,58 @@ describe('template archive processor', () => {
// the events should have been emitted
expect(payload.events[0].penaltyCalculated).toBe(true);
});
});

test('should find entry point with non-standard logic filename', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const compiledCode: Record<string, any> = {
'logic/mycontract.ts': { code: 'compiled js code' },
'logic/README.md': { code: undefined },
'logic/generated/types.ts': { code: 'generated code' },
};
const entryPoint = Object.keys(compiledCode).find(
key => key.startsWith('logic/') &&
key.split('/').length === 2 &&
!key.includes('generated/') &&
key.endsWith('.ts') &&
compiledCode[key].code !== undefined
);
expect(entryPoint).toBe('logic/mycontract.ts');
});

test('should not select generated files or non-ts files as entry point', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const compiledCode: Record<string, any> = {
'logic/README.md': { code: undefined },
'logic/generated/io.clause.latedeliveryandpenalty@0.1.0.ts': { code: 'generated code' },
'logic/generated/concerto.ts': { code: 'generated code' },
'logic/mycontract.ts': { code: 'compiled js code' },
};
const entryPoint = Object.keys(compiledCode).find(
key => key.startsWith('logic/') &&
key.split('/').length === 2 &&
!key.includes('generated/') &&
key.endsWith('.ts') &&
compiledCode[key].code !== undefined
);
// should skip README.md and all generated/ files
expect(entryPoint).toBe('logic/mycontract.ts');
expect(entryPoint).not.toContain('generated');
expect(entryPoint).not.toBe('logic/README.md');
});

test('should throw when no valid entry point exists', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const compiledCode: Record<string, any> = {
'logic/README.md': { code: undefined },
'logic/generated/types.ts': { code: 'generated code' },
};
const entryPoint = Object.keys(compiledCode).find(
key => key.startsWith('logic/') &&
key.split('/').length === 2 &&
!key.includes('generated/') &&
key.endsWith('.ts') &&
compiledCode[key].code !== undefined
);
expect(entryPoint).toBeUndefined();
});
});
7 changes: 7 additions & 0 deletions test/archives/latedeliveryandpenalty-customlogic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

# Clause Template: Late Delivery And Penalty

## Sample

Late Delivery and Penalty. In case of delayed delivery except for Force Majeure cases, the Seller shall pay to the Buyer for every 2 days of delay penalty amounting to 10.5% of total value of the Equipment whose delivery has been delayed. Any fractional part of a day is to be considered a full day. The total amount of penalty shall not, however, exceed 55% of the total value of the Equipment involved in late delivery. If the delay is more than 15 days, the Buyer is entitled to terminate this Contract.

25 changes: 25 additions & 0 deletions test/archives/latedeliveryandpenalty-customlogic/logic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Introduction

This is a sample Accord Project template that includes logic for the template written in TypeScript.

# Template Model

The data model for the template defines the structure of the data for the contract (a "clause" in this case).
The template concept has the `@template` annotation.

The template model also defines the request and response types for the template logic.

The template model may optionally also define a state type for the template logic, for templates that support state.

# Template Logic

To write your template logic you should first generate the TypeScript source code for the template
using the `concerto compile --model ./model/model.cto --target typescript --output logic/generated` CLI command.

You can then define and export the template logic class, which should implement the `TemplateLogic` interface,
implementing the trigger method and optionally the init method.

## Current Limitations

1. All template logic must be written in TypeScript and in a single file called logic.ts within the logic folder of the template
2. You cannot import third-party modules into your template logic
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable @typescript-eslint/no-empty-object-type*/
// Generated code for namespace: concerto.decorator@1.0.0

// imports
import {IConcept} from './concerto@1.0.0';

// interfaces
export interface IDecorator extends IConcept {
}

export interface IDotNetNamespace extends IDecorator {
namespace: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable @typescript-eslint/no-empty-object-type*/
// Generated code for namespace: concerto

// imports

// interfaces
export interface IConcept {
$class: string;
}

export interface IAsset extends IConcept {
$identifier: string;
}

export interface IParticipant extends IConcept {
$identifier: string;
}

export interface ITransaction extends IConcept {
}

export interface IEvent extends IConcept {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* eslint-disable @typescript-eslint/no-unused-vars*/
// Generated code for namespace: concerto@1.0.0

// imports

// Warning: Beware of circular dependencies when modifying these imports
import type {
ILateDeliveryAndPenaltyState
} from './io.clause.latedeliveryandpenalty@0.1.0';
import type {
Month,
Day,
TemporalUnit,
IDuration,
PeriodUnit,
IPeriod
} from './org.accordproject.time@0.3.0';

// Warning: Beware of circular dependencies when modifying these imports
import type {
IContract,
IClause
} from './org.accordproject.contract@0.2.0';
import type {
IState
} from './org.accordproject.runtime@0.2.0';

// Warning: Beware of circular dependencies when modifying these imports
import type {
IRequest,
IResponse
} from './org.accordproject.runtime@0.2.0';

// Warning: Beware of circular dependencies when modifying these imports
import type {
ILateDeliveryAndPenaltyEvent
} from './io.clause.latedeliveryandpenalty@0.1.0';
import type {
IObligation
} from './org.accordproject.runtime@0.2.0';

// interfaces
export interface IConcept {
$class: string;
}

export type ConceptUnion = ILateDeliveryAndPenaltyState |
IDuration |
IPeriod;

export interface IAsset extends IConcept {
$identifier: string;
}

export type AssetUnion = IContract |
IClause |
IState;

export interface IParticipant extends IConcept {
$identifier: string;
}

export interface ITransaction extends IConcept {
$timestamp: Date;
}

export type TransactionUnion = IRequest |
IResponse;

export interface IEvent extends IConcept {
$timestamp: Date;
}

export type EventUnion = ILateDeliveryAndPenaltyEvent |
IObligation;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* eslint-disable @typescript-eslint/no-empty-object-type*/
// Generated code for namespace: io.clause.latedeliveryandpenalty@0.1.0

// imports
import {IDuration,TemporalUnit} from './org.accordproject.time@0.3.0';
import {IClause} from './org.accordproject.contract@0.2.0';
import {IRequest,IResponse} from './org.accordproject.runtime@0.2.0';
import {IEvent,IConcept} from './concerto@1.0.0';

// interfaces
export interface ITemplateModel extends IClause {
forceMajeure: boolean;
penaltyDuration: IDuration;
penaltyPercentage: number;
capPercentage: number;
termination: IDuration;
fractionalPart: TemporalUnit;
}

export interface ILateDeliveryAndPenaltyRequest extends IRequest {
forceMajeure: boolean;
agreedDelivery: Date;
deliveredAt?: Date;
goodsValue: number;
}

export interface ILateDeliveryAndPenaltyResponse extends IResponse {
penalty: number;
buyerMayTerminate: boolean;
}

export interface ILateDeliveryAndPenaltyEvent extends IEvent {
penaltyCalculated: boolean;
}

export interface ILateDeliveryAndPenaltyState extends IConcept {
$identifier: string;
count: number;
}
Loading