Skip to content
Merged
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
31 changes: 18 additions & 13 deletions recipes/compiler.tsx → packages/patterns/compiler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,28 @@ const updateCode = handler<
);

const visit = handler<
{ detail: { value: string } },
{ code: string }
unknown,
{ result: Cell<any> }
>(
(_, state) => {
const { result } = compileAndRun({
files: [{ name: "/main.tsx", contents: state.code }],
main: "/main.tsx",
});

console.log("result", result);

(_, { result }) => {
console.log("visit: navigating to compiled result", result);
return navigateTo(result);
},
);

const handleEditContent = handler<
{ code: string },
{ code: Cell<string> }
>(
(event, { code }) => {
code.set(event.code);
},
);

export default recipe<Input>(
"Compiler",
({ code }) => {
const { error, errors } = compileAndRun({
const { result, error, errors } = compileAndRun({
files: [{ name: "/main.tsx", contents: code }],
main: "/main.tsx",
});
Expand All @@ -63,16 +66,18 @@ export default recipe<Input>(
/>
{ifElse(
error,
<b>fix the errors</b>,
<b>fix the error: {error}</b>,
<ct-button
onClick={visit({ code })}
onClick={visit({ result })}
>
Navigate To Charm
</ct-button>,
)}
</div>
),
code,
updateCode: handleEditContent({ code }),
visit: visit({ result }),
};
},
);
34 changes: 28 additions & 6 deletions packages/runner/src/builtins/compile-and-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ import { CompilerError } from "@commontools/js-runtime/typescript";
export function compileAndRun(
inputsCell: Cell<BuiltInCompileAndRunParams<any>>,
sendResult: (tx: IExtendedStorageTransaction, result: any) => void,
_addCancel: (cancel: () => void) => void,
addCancel: (cancel: () => void) => void,
cause: any,
parentCell: Cell<any>,
runtime: IRuntime,
): Action {
let currentRun = 0;
let requestId: string | undefined = undefined;
let abortController: AbortController | undefined = undefined;
let previousCallHash: string | undefined = undefined;
let cellsInitialized = false;
let pending: Cell<boolean>;
Expand All @@ -52,6 +53,12 @@ export function compileAndRun(
| undefined
>;

// This is called when the recipe containing this node is being stopped.
addCancel(() => {
// Abort any in-flight compilation if it's still pending.
abortController?.abort("Recipe stopped");
});

return (tx: IExtendedStorageTransaction) => {
if (!cellsInitialized) {
pending = runtime.getCell<boolean>(
Expand Down Expand Up @@ -97,7 +104,7 @@ export function compileAndRun(
sendResult(tx, { pending, result, error, errors });
cellsInitialized = true;
}
const thisRun = ++currentRun;

const pendingWithLog = pending.withTx(tx);
const resultWithLog = result.withTx(tx);
const errorWithLog = error.withTx(tx);
Expand Down Expand Up @@ -135,6 +142,11 @@ export function compileAndRun(
if (hash === previousCallHash) return;
previousCallHash = hash;

// Abort any in-flight compilation before starting a new one
abortController?.abort("New compilation started");
abortController = new AbortController();
requestId = crypto.randomUUID();

runtime.runner.stop(result);
resultWithLog.set(undefined);
errorWithLog.set(undefined);
Expand All @@ -156,10 +168,15 @@ export function compileAndRun(
// Now we're sure that we have a new file to compile
pendingWithLog.set(true);

// Capture requestId for this compilation run
const thisRequestId = requestId;

const compilePromise = runtime.harness.run(program)
.catch(
(err) => {
if (thisRun !== currentRun) return;
// Only process this error if the request hasn't been superseded
if (requestId !== thisRequestId) return;
if (abortController?.signal.aborted) return;

runtime.editWithRetry((asyncTx) => {
// Extract structured errors if this is a CompilerError
Expand All @@ -180,15 +197,20 @@ export function compileAndRun(
});
},
).finally(() => {
if (thisRun !== currentRun) return;
// Only update pending if this is still the current request
if (requestId !== thisRequestId) return;
// Always clear pending state, even if cancelled, to avoid stuck state

runtime.editWithRetry((asyncTx) => {
pending.withTx(asyncTx).set(false);
});
});

compilePromise.then((recipe) => {
if (thisRun !== currentRun) return;
// Only run the result if this is still the current request
if (requestId !== thisRequestId) return;
if (abortController?.signal.aborted) return;

if (recipe) {
// TODO(ja): to support editting of existing charms / running with
// inputs from other charms, we will need to think more about
Expand Down