From 32ceed2ba991b58ea16fb812b60f780d8869a09d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 16 May 2026 07:17:31 +0000 Subject: [PATCH] fix: draft() can't resolve TypeScript helper functions defined in logic/logic.ts (#147) TemplateArchiveProcessor.draft() compiled inline formulas (`{{% expr %}}`) with a TypeScript context that did not include the template's user code from `logic/logic.ts`. Helper functions like `monthlyPaymentFormula(...)` therefore produced `Cannot find name 'monthlyPaymentFormula'` (TS2304). trigger() and init() already loaded logic/logic.ts via the TypeScriptToJavaScriptCompiler. This change: - Extracts that compilation into a shared `compileUserLogic` helper (src/UserLogic.ts) which returns the compiled JS along with the top-level identifiers it declares. - Adds `declare const : any;` stubs to the formula compile context for each of those identifiers, so user helpers resolve at compile time. - Splices the stripped logic.ts JS into the formula function body at runtime, so the same identifiers are real values at evaluation time. - Refactors trigger() and init() to use the new shared helper, so draft() and trigger()/init() now use the same logic-loading path. Adds a fixture (test/archives/formula-uses-logic-helper) and a unit test that confirms draft() can call helpers from logic/logic.ts. Signed-off-by: Claude --- src/TemplateArchiveProcessor.ts | 150 ++++++++---------- src/TemplateMarkInterpreter.ts | 43 +++-- src/TemplateMarkToJavaScriptCompiler.ts | 4 +- src/TypeScriptCompilationContext.ts | 14 +- src/TypeScriptToJavaScriptCompiler.ts | 4 +- src/UserLogic.ts | 104 ++++++++++++ test/TemplateArchiveProcessor.test.ts | 27 ++++ .../formula-uses-logic-helper/README.md | 7 + .../formula-uses-logic-helper/logic/README.md | 25 +++ .../generated/concerto.decorator@1.0.0.ts | 13 ++ .../logic/generated/concerto.ts | 23 +++ .../logic/generated/concerto@1.0.0.ts | 75 +++++++++ .../io.clause.latedeliveryandpenalty@0.1.0.ts | 39 +++++ .../org.accordproject.contract@0.2.0.ts | 21 +++ .../org.accordproject.runtime@0.2.0.ts | 38 +++++ .../generated/org.accordproject.time@0.3.0.ts | 57 +++++++ .../formula-uses-logic-helper/logic/logic.ts | 54 +++++++ ...oject.org.accordproject.contract@0.2.0.cto | 32 ++++ ...roject.org.accordproject.runtime@0.2.0.cto | 51 ++++++ .../@models.accordproject.org.time@0.3.0.cto | 85 ++++++++++ .../formula-uses-logic-helper/model/model.cto | 99 ++++++++++++ .../formula-uses-logic-helper/package.json | 10 ++ .../formula-uses-logic-helper/request.json | 7 + .../text/grammar.tem.md | 7 + 24 files changed, 883 insertions(+), 106 deletions(-) create mode 100644 src/UserLogic.ts create mode 100644 test/archives/formula-uses-logic-helper/README.md create mode 100644 test/archives/formula-uses-logic-helper/logic/README.md create mode 100644 test/archives/formula-uses-logic-helper/logic/generated/concerto.decorator@1.0.0.ts create mode 100644 test/archives/formula-uses-logic-helper/logic/generated/concerto.ts create mode 100644 test/archives/formula-uses-logic-helper/logic/generated/concerto@1.0.0.ts create mode 100644 test/archives/formula-uses-logic-helper/logic/generated/io.clause.latedeliveryandpenalty@0.1.0.ts create mode 100644 test/archives/formula-uses-logic-helper/logic/generated/org.accordproject.contract@0.2.0.ts create mode 100644 test/archives/formula-uses-logic-helper/logic/generated/org.accordproject.runtime@0.2.0.ts create mode 100644 test/archives/formula-uses-logic-helper/logic/generated/org.accordproject.time@0.3.0.ts create mode 100644 test/archives/formula-uses-logic-helper/logic/logic.ts create mode 100644 test/archives/formula-uses-logic-helper/model/@models.accordproject.org.accordproject.contract@0.2.0.cto create mode 100644 test/archives/formula-uses-logic-helper/model/@models.accordproject.org.accordproject.runtime@0.2.0.cto create mode 100644 test/archives/formula-uses-logic-helper/model/@models.accordproject.org.time@0.3.0.cto create mode 100644 test/archives/formula-uses-logic-helper/model/model.cto create mode 100644 test/archives/formula-uses-logic-helper/package.json create mode 100644 test/archives/formula-uses-logic-helper/request.json create mode 100644 test/archives/formula-uses-logic-helper/text/grammar.tem.md diff --git a/src/TemplateArchiveProcessor.ts b/src/TemplateArchiveProcessor.ts index 5708205..b17b0a7 100644 --- a/src/TemplateArchiveProcessor.ts +++ b/src/TemplateArchiveProcessor.ts @@ -18,11 +18,9 @@ import { Template } from '@accordproject/cicero-core'; import { TemplateMarkInterpreter } from './TemplateMarkInterpreter'; import { TemplateMarkTransformer } from '@accordproject/markdown-template'; import { transform } from '@accordproject/markdown-transform'; -import { TypeScriptToJavaScriptCompiler } from './TypeScriptToJavaScriptCompiler'; import Script from '@accordproject/cicero-core/types/src/script'; -import { TwoSlashReturn } from '@typescript/twoslash'; import { JavaScriptEvaluator } from './JavaScriptEvaluator'; -import { SMART_LEGAL_CONTRACT_BASE64 } from './runtime/declarations'; +import { compileUserLogic, CompiledUserLogic } from './UserLogic'; export type State = object; export type Response = object; @@ -66,9 +64,14 @@ export class TemplateArchiveProcessor { const metadata = this.template.getMetadata(); const templateKind = metadata.getTemplateType() !== 0 ? 'clause' : 'contract'; + // Compile the template's logic/logic.ts (when present) so that + // inline formulas {{% expr %}} can call helpers like + // monthlyPaymentFormula(...) declared in user code (issue #147). + const userLogic = await this.compileLogic(); + // Get the data const modelManager = this.template.getModelManager(); - const engine = new TemplateMarkInterpreter(modelManager, {}); + const engine = new TemplateMarkInterpreter(modelManager, {}, undefined, userLogic); const templateMarkTransformer = new TemplateMarkTransformer(); const templateMarkDom = templateMarkTransformer.fromMarkdownTemplate( { content: this.template.getTemplate() }, modelManager, templateKind, {options}); @@ -82,6 +85,31 @@ export class TemplateArchiveProcessor { } + /** + * Compiles the template's `logic/logic.ts` using the same TypeScript + * compilation pipeline that {@link trigger} and {@link init} use, but + * returns the result via {@link CompiledUserLogic} so it can also be + * consumed by {@link draft} (so inline formulas can call helpers from + * logic.ts — see issue #147). Returns `undefined` for non-TypeScript + * runtimes or when the template ships no logic. + */ + private async compileLogic(): Promise { + const logicManager = this.template.getLogicManager(); + if (logicManager.getLanguage() !== 'typescript') { + return undefined; + } + const tsFiles: Array