diff --git a/packages/hub/src/app/api/runSquiggle/route.ts b/packages/hub/src/app/api/runSquiggle/route.ts new file mode 100644 index 0000000000..54280244e3 --- /dev/null +++ b/packages/hub/src/app/api/runSquiggle/route.ts @@ -0,0 +1,53 @@ +import { NextRequest, NextResponse } from "next/server"; + +import { runSquiggle } from "@/graphql/queries/runSquiggle"; + +export async function POST(req: NextRequest) { + // Assuming 'code' is sent in the request body and is a string + try { + const body = await req.json(); + if (body.code) { + let response = await runSquiggle(body.code); + if (response.isOk) { + return new NextResponse( + JSON.stringify({ + result: response.resultJSON, + bindings: response.bindingsJSON, + }), + { + status: 200, + statusText: "OK", + headers: { "Content-Type": "application/json" }, + } + ); + } else { + return new NextResponse( + JSON.stringify({ error: response.errorString }), + { + status: 400, + statusText: "ERROR", + headers: { "Content-Type": "application/json" }, + } + ); + } + } else { + return new NextResponse(JSON.stringify({ error: "No code provided" }), { + status: 400, + statusText: "ERROR", + headers: { "Content-Type": "application/json" }, + }); + } + } catch (error) { + // Catch any errors, including JSON parsing errors + console.error("Error in POST request:", error); + return new NextResponse( + //We could give more information here, but we don't want to leak any information + JSON.stringify({ error: "An internal error occurred" }), + { + status: 500, + statusText: "Internal Server Error", + headers: { "Content-Type": "application/json" }, + } + ); + } +} diff --git a/packages/hub/src/graphql/queries/runSquiggle.ts b/packages/hub/src/graphql/queries/runSquiggle.ts index 70e464125f..8742e54b2e 100644 --- a/packages/hub/src/graphql/queries/runSquiggle.ts +++ b/packages/hub/src/graphql/queries/runSquiggle.ts @@ -1,11 +1,7 @@ import { Prisma } from "@prisma/client"; import crypto from "crypto"; -import { - SqAbstractDistribution, - SqProject, - SqValue, -} from "@quri/squiggle-lang"; +import { SqProject, SqValue } from "@quri/squiggle-lang"; import { builder } from "@/graphql/builder"; import { prisma } from "@/prisma"; @@ -14,19 +10,8 @@ function getKey(code: string): string { return crypto.createHash("md5").update(code).digest("base64"); } -export const squiggleValueToJSON = (value: SqValue) => { - // this is a lazy shortcut to traverse the value tree; should be reimplemented without parse/stringify - return JSON.parse( - JSON.stringify(value.asJS(), (key, value) => { - if (value instanceof Map) { - return Object.fromEntries(value.entries()); - } - if (value instanceof SqAbstractDistribution) { - return value.toString(); - } - return value; - }) - ); +export const squiggleValueToJSON = (value: SqValue): any => { + return value.asJS(); }; type SquiggleOutput = { @@ -91,10 +76,15 @@ builder.objectType( } ); -async function runSquiggle(code: string): Promise { +export async function runSquiggle(code: string): Promise { const MAIN = "main"; - const project = SqProject.create(); + const env = { + sampleCount: 1000, // int + xyPointLength: 1000, // int + }; + + const project = SqProject.create({ environment: env }); project.setSource(MAIN, code); await project.run(MAIN); diff --git a/packages/hub/src/public/openapi-schema.json b/packages/hub/src/public/openapi-schema.json new file mode 100644 index 0000000000..924befdc5b --- /dev/null +++ b/packages/hub/src/public/openapi-schema.json @@ -0,0 +1,91 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Squiggle Code Processing API", + "version": "0.0.1" + }, + "servers": [ + { + "url": "https://squigglehub.org/api" + } + ], + "paths": { + "/runSquiggle": { + "post": { + "summary": "Run the given Squiggle code string.", + "operationId": "runSquiggle", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The code to be processed." + } + }, + "required": ["code"] + } + } + } + }, + "responses": { + "200": { + "description": "Code processed successfully.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "result": { + "type": "string", + "description": "The result of the code processing." + }, + "bindings": { + "type": "string", + "description": "Additional bindings from the code processing." + } + } + } + } + } + }, + "400": { + "description": "Error in processing or no code provided.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error message explaining the failure." + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "A generic error message indicating a server problem." + } + } + } + } + } + } + } + } + } + } +} diff --git a/packages/squiggle-lang/__tests__/public/SqValue_test.ts b/packages/squiggle-lang/__tests__/public/SqValue_test.ts index 68930a2880..b9f1f553fa 100644 --- a/packages/squiggle-lang/__tests__/public/SqValue_test.ts +++ b/packages/squiggle-lang/__tests__/public/SqValue_test.ts @@ -1,5 +1,3 @@ -import { OrderedMap } from "immutable"; - import { run, sq } from "../../src/index.js"; import { testRun } from "../helpers/helpers.js"; @@ -9,13 +7,13 @@ describe("SqValue.asJS", () => { await testRun('{ x: 5, y: [3, "foo", { dist: normal(5,2) } ] }') ).asJS(); - expect(value).toBeInstanceOf(OrderedMap); + expect(value).toBeInstanceOf(Object); }); test("Dict fields", async () => { const value = (await testRun("{ x: 5 }")).asJS(); - expect((value as any).get("value").get("x")).toBe(5); + expect((value as any).value.x).toBe(5); }); test("Deeply nested dist", async () => { @@ -23,9 +21,7 @@ describe("SqValue.asJS", () => { await testRun('{ x: 5, y: [3, "foo", { dist: normal(5,2) } ] }') ).asJS(); - expect( - (value as any).get("value").get("y")[2].get("value").get("dist") - ).toBeInstanceOf(Array); + expect((value as any).value.y[2].value.dist).toBeInstanceOf(Array); }); }); diff --git a/packages/squiggle-lang/src/public/SqValue/index.ts b/packages/squiggle-lang/src/public/SqValue/index.ts index 33684533c2..1629114c07 100644 --- a/packages/squiggle-lang/src/public/SqValue/index.ts +++ b/packages/squiggle-lang/src/public/SqValue/index.ts @@ -8,7 +8,11 @@ import { vNumber, vString, } from "../../value/index.js"; -import { SimpleValue, simpleValueFromValue } from "../../value/simpleValue.js"; +import { + removeLambdas, + simpleValueFromValue, + simpleValueToJson, +} from "../../value/simpleValue.js"; import { SqError } from "../SqError.js"; import { SqValueContext } from "../SqValueContext.js"; import { SqArray } from "./SqArray.js"; @@ -23,6 +27,10 @@ import { wrapScale } from "./SqScale.js"; import { SqTableChart } from "./SqTableChart.js"; import { SqTags } from "./SqTags.js"; +function valueToJSON(value: Value): unknown { + return simpleValueToJson(removeLambdas(simpleValueFromValue(value))); +} + export function wrapValue(value: Value, context?: SqValueContext) { switch (value.type) { case "Array": @@ -89,15 +97,15 @@ export abstract class SqAbstractValue { abstract asJS(): JSType; } -export class SqArrayValue extends SqAbstractValue<"Array", SimpleValue[]> { +export class SqArrayValue extends SqAbstractValue<"Array", unknown[]> { tag = "Array" as const; get value() { return new SqArray(this._value.value, this.context); } - asJS(): SimpleValue[] { - return this._value.value.map(simpleValueFromValue); + asJS(): unknown[] { + return this._value.value.map(valueToJSON); } } @@ -113,7 +121,7 @@ export class SqBoolValue extends SqAbstractValue<"Bool", boolean> { } } -export class SqDateValue extends SqAbstractValue<"Date", Date> { +export class SqDateValue extends SqAbstractValue<"Date", unknown> { tag = "Date" as const; static create(value: SDate) { @@ -128,13 +136,12 @@ export class SqDateValue extends SqAbstractValue<"Date", Date> { return this._value.value; } - //Note: This reveals the underlying Date object, but we might prefer to keep it hidden asJS() { - return this.value.toDate(); + return valueToJSON(this._value); } } -export class SqDistributionValue extends SqAbstractValue<"Dist", SimpleValue> { +export class SqDistributionValue extends SqAbstractValue<"Dist", unknown> { tag = "Dist" as const; get value() { @@ -151,11 +158,11 @@ export class SqDistributionValue extends SqAbstractValue<"Dist", SimpleValue> { } asJS() { - return simpleValueFromValue(this._value); + return valueToJSON(this._value); } } -export class SqLambdaValue extends SqAbstractValue<"Lambda", SimpleValue> { +export class SqLambdaValue extends SqAbstractValue<"Lambda", unknown> { tag = "Lambda" as const; static create(value: SqLambda) { @@ -194,15 +201,15 @@ export class SqNumberValue extends SqAbstractValue<"Number", number> { } } -export class SqDictValue extends SqAbstractValue<"Dict", SimpleValue> { +export class SqDictValue extends SqAbstractValue<"Dict", unknown> { tag = "Dict" as const; get value() { return new SqDict(this._value.value, this.context); } - asJS(): SimpleValue { - return simpleValueFromValue(this._value); + asJS() { + return valueToJSON(this._value); } } @@ -238,7 +245,7 @@ export class SqDurationValue extends SqAbstractValue<"Duration", number> { } } -export class SqPlotValue extends SqAbstractValue<"Plot", SimpleValue> { +export class SqPlotValue extends SqAbstractValue<"Plot", unknown> { tag = "Plot" as const; get value() { @@ -250,13 +257,10 @@ export class SqPlotValue extends SqAbstractValue<"Plot", SimpleValue> { } asJS() { - return simpleValueFromValue(this._value); + return valueToJSON(this._value); } } -export class SqTableChartValue extends SqAbstractValue< - "TableChart", - SimpleValue -> { +export class SqTableChartValue extends SqAbstractValue<"TableChart", unknown> { tag = "TableChart" as const; get value() { @@ -264,13 +268,10 @@ export class SqTableChartValue extends SqAbstractValue< } asJS() { - return simpleValueFromValue(this._value); + return valueToJSON(this._value); } } -export class SqCalculatorValue extends SqAbstractValue< - "Calculator", - SimpleValue -> { +export class SqCalculatorValue extends SqAbstractValue<"Calculator", unknown> { tag = "Calculator" as const; get value() { @@ -286,7 +287,7 @@ export class SqCalculatorValue extends SqAbstractValue< } } -export class SqScaleValue extends SqAbstractValue<"Scale", SimpleValue> { +export class SqScaleValue extends SqAbstractValue<"Scale", unknown> { tag = "Scale" as const; get value() { @@ -294,11 +295,11 @@ export class SqScaleValue extends SqAbstractValue<"Scale", SimpleValue> { } asJS() { - return simpleValueFromValue(this._value); + return valueToJSON(this._value); } } -export class SqInputValue extends SqAbstractValue<"Input", SimpleValue> { +export class SqInputValue extends SqAbstractValue<"Input", unknown> { tag = "Input" as const; get value() { @@ -306,7 +307,7 @@ export class SqInputValue extends SqAbstractValue<"Input", SimpleValue> { } asJS() { - return simpleValueFromValue(this._value); + return valueToJSON(this._value); } } @@ -322,7 +323,7 @@ export class SqVoidValue extends SqAbstractValue<"Void", null> { } } -export class SqDomainValue extends SqAbstractValue<"Domain", SimpleValue> { +export class SqDomainValue extends SqAbstractValue<"Domain", unknown> { tag = "Domain" as const; get value() { @@ -330,7 +331,7 @@ export class SqDomainValue extends SqAbstractValue<"Domain", SimpleValue> { } asJS() { - return simpleValueFromValue(this._value); + return valueToJSON(this._value); } }