Skip to content

Commit 0ec129b

Browse files
claude: Replace Proxy-based quartoAPI with getQuartoAPI() function and dynamic engine registration
Refactored quartoAPI from Proxy-based lazy initialization to explicit getQuartoAPI() function. This eliminates Proxy overhead on every property access while maintaining lazy initialization semantics. Key changes: - src/core/api/index.ts: Replaced Proxy with memoized getQuartoAPI() function - src/execute/engine.ts: Made engine registration dynamic - Removed module-scope registerExecutionEngine() calls - Added kStandardEngines array and enginesRegistered flag - Renamed reorderEngines() to resolveEngines() - Standard engines now registered on first resolveEngines() call - src/command/command-utils.ts: Updated to use resolveEngines() Benefits: - Better performance: No Proxy overhead on property access - Clearer semantics: Explicit function call for API initialization - Simpler mental model: Standard memoization pattern - Solves module initialization order issues - Better naming: resolveEngines reflects dynamic loading 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent c18559b commit 0ec129b

File tree

10 files changed

+226
-199
lines changed

10 files changed

+226
-199
lines changed

src/command/command-utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { initYamlIntelligenceResourcesFromFilesystem } from "../core/schema/utils.ts";
88
import { projectContext } from "../project/project-context.ts";
99
import { notebookContext } from "../render/notebook/notebook-context.ts";
10-
import { reorderEngines } from "../execute/engine.ts";
10+
import { resolveEngines } from "../execute/engine.ts";
1111
import type { ProjectContext } from "../project/types.ts";
1212

1313
/**
@@ -68,5 +68,5 @@ export async function initializeProjectContextAndEngines(
6868
await zeroFileProjectContext(dir);
6969

7070
// Register external engines from project config
71-
await reorderEngines(context);
71+
await resolveEngines(context);
7272
}

src/core/api/index.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,22 @@ export {
2929
} from "./registry.ts";
3030

3131
/**
32-
* The global QuartoAPI instance
32+
* The global QuartoAPI instance (cached after first call)
33+
*/
34+
let _quartoAPI: QuartoAPI | null = null;
35+
36+
/**
37+
* Get the global QuartoAPI instance
3338
*
34-
* This is created lazily on first access via a getter.
39+
* This function returns the QuartoAPI instance, creating it on first call.
3540
* The register.ts module (imported in src/quarto.ts) ensures all
3641
* namespaces are registered before any code accesses the API.
42+
*
43+
* @returns {QuartoAPI} The complete API object with all namespaces
3744
*/
38-
let _quartoAPI: QuartoAPI | null = null;
39-
40-
export const quartoAPI = new Proxy({} as QuartoAPI, {
41-
get(_target, prop) {
42-
// Create API on first access
43-
if (_quartoAPI === null) {
44-
_quartoAPI = globalRegistry.createAPI();
45-
}
46-
return _quartoAPI[prop as keyof QuartoAPI];
47-
},
48-
});
45+
export function getQuartoAPI(): QuartoAPI {
46+
if (_quartoAPI === null) {
47+
_quartoAPI = globalRegistry.createAPI();
48+
}
49+
return _quartoAPI;
50+
}

src/execute/engine.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,21 @@ import { pandocBuiltInFormats } from "../core/pandoc/pandoc-formats.ts";
3636
import { gitignoreEntries } from "../project/project-gitignore.ts";
3737
import { ensureFileInformationCache } from "../project/project-shared.ts";
3838
import { engineProjectContext } from "../project/engine-project-context.ts";
39-
import { quartoAPI } from "../core/api/index.ts";
39+
import { getQuartoAPI } from "../core/api/index.ts";
4040
import { satisfies } from "semver/mod.ts";
4141
import { quartoConfig } from "../core/quarto.ts";
4242

4343
const kEngines: Map<string, ExecutionEngineDiscovery> = new Map();
4444

45+
// Standard engines to register on first resolveEngines() call
46+
const kStandardEngines: ExecutionEngineDiscovery[] = [
47+
knitrEngineDiscovery,
48+
jupyterEngineDiscovery,
49+
markdownEngineDiscovery,
50+
];
51+
52+
let enginesRegistered = false;
53+
4554
/**
4655
* Check if an engine's Quarto version requirement is satisfied
4756
* @param engine The engine to check
@@ -76,15 +85,6 @@ export function executionEngine(name: string) {
7685
return kEngines.get(name);
7786
}
7887

79-
// Register the standard engines with discovery interface
80-
registerExecutionEngine(knitrEngineDiscovery);
81-
82-
// Register jupyter engine with discovery interface
83-
registerExecutionEngine(jupyterEngineDiscovery);
84-
85-
// Register markdown engine with discovery interface
86-
registerExecutionEngine(markdownEngineDiscovery);
87-
8888
export function registerExecutionEngine(engine: ExecutionEngineDiscovery) {
8989
if (kEngines.has(engine.name)) {
9090
throw new Error(`Execution engine ${engine.name} already registered`);
@@ -95,7 +95,7 @@ export function registerExecutionEngine(engine: ExecutionEngineDiscovery) {
9595

9696
kEngines.set(engine.name, engine);
9797
if (engine.init) {
98-
engine.init(quartoAPI);
98+
engine.init(getQuartoAPI());
9999
}
100100
}
101101

@@ -192,7 +192,15 @@ export function markdownExecutionEngine(
192192
return markdownEngineDiscovery.launch(engineProjectContext(project));
193193
}
194194

195-
export async function reorderEngines(project: ProjectContext) {
195+
export async function resolveEngines(project: ProjectContext) {
196+
// Register standard engines on first call
197+
if (!enginesRegistered) {
198+
enginesRegistered = true;
199+
for (const engine of kStandardEngines) {
200+
registerExecutionEngine(engine);
201+
}
202+
}
203+
196204
const userSpecifiedOrder: string[] = [];
197205
const projectEngines = project.config?.engines as
198206
| (string | ExternalEngine)[]
@@ -231,7 +239,7 @@ export async function reorderEngines(project: ProjectContext) {
231239
userSpecifiedOrder.push(extEngine.name);
232240
kEngines.set(extEngine.name, extEngine);
233241
if (extEngine.init) {
234-
extEngine.init(quartoAPI);
242+
extEngine.init(getQuartoAPI());
235243
}
236244
} catch (err: any) {
237245
// Throw error for engine import failures as this is a serious configuration issue
@@ -278,6 +286,9 @@ export async function fileExecutionEngine(
278286
flags: RenderFlags | undefined,
279287
project: ProjectContext,
280288
): Promise<ExecutionEngineInstance | undefined> {
289+
// Resolve engines first (registers standard engines on first call)
290+
const engines = await resolveEngines(project);
291+
281292
// get the extension and validate that it can be handled by at least one of our engines
282293
const ext = extname(file).toLowerCase();
283294
if (
@@ -288,10 +299,8 @@ export async function fileExecutionEngine(
288299
return undefined;
289300
}
290301

291-
const reorderedEngines = await reorderEngines(project);
292-
293302
// try to find an engine that claims this extension outright
294-
for (const [_, engine] of reorderedEngines) {
303+
for (const [_, engine] of engines) {
295304
if (engine.claimsFile(file, ext)) {
296305
return engine.launch(engineProjectContext(project));
297306
}
@@ -308,7 +317,7 @@ export async function fileExecutionEngine(
308317
return markdownExecutionEngine(
309318
project,
310319
markdown ? markdown.value : Deno.readTextFileSync(file),
311-
reorderedEngines,
320+
engines,
312321
flags,
313322
);
314323
} catch (error) {

src/execute/jupyter/jupyter-kernel.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
JupyterCapabilities,
1414
JupyterKernelspec,
1515
} from "../../core/jupyter/types.ts";
16-
import { quartoAPI as quarto } from "../../core/api/index.ts";
16+
import { getQuartoAPI } from "../../core/api/index.ts";
1717
import type { ProcessResult } from "../../core/process-types.ts";
1818

1919
import {
@@ -175,6 +175,7 @@ async function execJupyter(
175175
options: Record<string, unknown>,
176176
kernelspec: JupyterKernelspec,
177177
): Promise<ProcessResult> {
178+
const quarto = getQuartoAPI();
178179
try {
179180
const cmd = await quarto.jupyter.pythonExec(kernelspec);
180181
const result = await quarto.system.execProcess(
@@ -219,6 +220,7 @@ export async function printExecDiagnostics(
219220
kernelspec: JupyterKernelspec,
220221
stderr?: string,
221222
) {
223+
const quarto = getQuartoAPI();
222224
const caps = await quarto.jupyter.capabilities(kernelspec);
223225
if (caps && !caps.jupyter_core) {
224226
info("Python 3 installation:");
@@ -247,6 +249,7 @@ function pythonVersionMessage() {
247249
}
248250

249251
function maybePrintUnactivatedEnvMessage(caps: JupyterCapabilities) {
252+
const quarto = getQuartoAPI();
250253
const envMessage = quarto.jupyter.unactivatedEnvMessage(caps);
251254
if (envMessage) {
252255
info(envMessage);
@@ -298,6 +301,7 @@ interface KernelTransport {
298301
}
299302

300303
function kernelTransportFile(target: string) {
304+
const quarto = getQuartoAPI();
301305
let transportsDir: string;
302306

303307
try {
@@ -321,6 +325,7 @@ function kernelTransportFile(target: string) {
321325
}
322326

323327
function kernelLogFile() {
328+
const quarto = getQuartoAPI();
324329
const logsDir = quarto.path.dataDir("logs");
325330
const kernelLog = join(logsDir, "jupyter-kernel.log");
326331
if (!existsSync(kernelLog)) {

src/execute/jupyter/jupyter.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,18 @@ interface JupyterTargetData {
6767
}
6868

6969
// Import quartoAPI directly since we're in core codebase
70-
import { quartoAPI as quarto } from "../../core/api/index.ts";
70+
import type { QuartoAPI } from "../../core/api/index.ts";
71+
72+
let quarto: QuartoAPI;
7173
import { MappedString } from "../../core/mapped-text.ts";
7274
import { kJupyterPercentScriptExtensions } from "../../core/jupyter/percent.ts";
7375
import type { CheckConfiguration } from "../../command/check/check.ts";
7476

7577
export const jupyterEngineDiscovery: ExecutionEngineDiscovery = {
76-
// we don't need init() because we use Quarto API directly
78+
init: (quartoAPI) => {
79+
quarto = quartoAPI;
80+
},
81+
7782
name: kJupyterEngine,
7883
defaultExt: ".qmd",
7984
defaultYaml: (kernel?: string) => [

src/execute/rmd.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import { basename, extname } from "../deno_ral/path.ts";
1111
import * as colors from "fmt/colors";
1212

1313
// Import quartoAPI directly since we're in core codebase
14-
import { quartoAPI as quarto } from "../core/api/index.ts";
14+
import type { QuartoAPI } from "../core/api/index.ts";
15+
16+
let quarto: QuartoAPI;
1517

1618
import { rBinaryPath } from "../core/resources.ts";
1719

@@ -48,6 +50,10 @@ import {
4850
const kRmdExtensions = [".rmd", ".rmarkdown"];
4951

5052
export const knitrEngineDiscovery: ExecutionEngineDiscovery = {
53+
init: (quartoAPI) => {
54+
quarto = quartoAPI;
55+
},
56+
5157
// Discovery methods
5258
name: kKnitrEngine,
5359

0 commit comments

Comments
 (0)