diff --git a/typescript/packages/common-memory/deno.json b/typescript/packages/common-memory/deno.json index d0666ce7b..5255f76c4 100644 --- a/typescript/packages/common-memory/deno.json +++ b/typescript/packages/common-memory/deno.json @@ -4,9 +4,7 @@ "test": "deno test --allow-read --allow-write --allow-net --allow-ffi --allow-env --no-check" }, "test": { - "include": [ - "test/*-test.ts" - ] + "include": ["test/*-test.ts"] }, "exports": { ".": "./lib.ts" diff --git a/typescript/packages/common-memory/lib.ts b/typescript/packages/common-memory/lib.ts index 07e4d8cbd..11febcfe9 100644 --- a/typescript/packages/common-memory/lib.ts +++ b/typescript/packages/common-memory/lib.ts @@ -32,10 +32,12 @@ export interface MemoryService { close(): AsyncResult<{}, SystemError>; subscribe(socket: WebSocket): AsyncResult<{}, Error>; patch(request: { json(): Promise }): Promise; + patchJson(json: In): Promise>; query( request: { json(): Promise }, selector: In<{ the?: string; of?: string }>, ): Promise; + queryJson(selector: object): Promise>; } interface MemoryServiceSession { @@ -50,9 +52,15 @@ class Service implements MemoryService { patch(request: { json(): Promise }): Promise { return patch(this, request); } + patchJson(json: In): Promise> { + return patchJson(this.router, json); + } query(request: { json(): Promise }): Promise { return query(this, request); } + queryJson(selector: object): Promise> { + return queryJson(this.router, selector as In>); + } transact(transaction: In) { return this.router.transact(transaction); } @@ -154,6 +162,9 @@ export const query = async (session: MemoryServiceSession, request: { json(): Pr const parseCommand = (source: string) => JSON.parse(source) as Command; +/** + * Converts a raw JSON transaction object into a router transaction + */ const asRouterTransaction = (json: In): In => Object.fromEntries(Object.entries(json).map(([key, value]) => [key, asTransaction(value)])); @@ -166,14 +177,36 @@ const asStatement = (statement: S): S => { if (statement.cause && typeof statement.cause["/"] === "string") { statement.cause = Reference.fromJSON(statement.cause as unknown as { "/": string }); } - if (statement.is && (statement.is as { "/"?: string })["/"]) { statement.is = Reference.fromJSON(statement.is as { "/": string }); } - return statement; }; +/** + * New library-level patch function that accepts already-parsed JSON + * and returns a plain result for consumers to build HTTP responses as needed. + */ +export const patchJson = async ( + session: Router.Router, + json: In, +): Promise> => { + try { + const transaction = asRouterTransaction(json); + const result = await session.transact(transaction); + return result; + } catch (cause) { + const error = cause as Partial; + return { + error: { + name: error?.name ?? "Error", + message: error?.message ?? "Unable to process transaction", + stack: error?.stack ?? "", + }, + }; + } +}; + const pipeToSocket = async ( stream: ReadableStream, socket: WebSocket, @@ -188,3 +221,23 @@ const pipeToSocket = async ( return { error: error as Error }; } }; + +export const queryJson = async ( + session: Router.Router, + selector: object, +): Promise> => { + try { + const result = await session.query(selector as In>); + return result; + } catch (cause) { + console.error(cause); + const error = cause as Partial; + return { + error: { + name: error?.name ?? "Error", + message: error?.message ?? "Unable to process query", + stack: error?.stack ?? "", + }, + }; + } +}; diff --git a/typescript/packages/toolshed/routes/storage/memory/memory.handlers.ts b/typescript/packages/toolshed/routes/storage/memory/memory.handlers.ts index 027655a4a..c1430313a 100644 --- a/typescript/packages/toolshed/routes/storage/memory/memory.handlers.ts +++ b/typescript/packages/toolshed/routes/storage/memory/memory.handlers.ts @@ -11,10 +11,21 @@ if (error) { throw error; } -export const transact: AppRouteHandler = (c) => - // @ts-expect-error - AppRouteHandler does not like Promise here as - // it wants to know status code and not sure how to use my own response - memory.patch(c.req); +export const transact: AppRouteHandler = async (c) => { + try { + const parsedBody = await c.req.json(); + const result = await memory.patchJson(parsedBody); + + if ("error" in result) { + const status = result.error?.name === "ConflictError" ? 409 : 500; + return c.json({ error: result.error?.message }, status); + } + + return c.json({ ok: result.ok }, 200); + } catch (err) { + return c.json({ error: (err as Error).message }, 500); + } +}; export const subscribe: AppRouteHandler = (c) => { const { socket, response } = Deno.upgradeWebSocket(c.req.raw); @@ -22,6 +33,15 @@ export const subscribe: AppRouteHandler = (c) => { return response; }; -export const query: AppRouteHandler = (c) => - // @ts-expect-error - Same reason as transact handler - memory.query(c.req); +export const query: AppRouteHandler = async (c) => { + try { + const selector = await c.req.json(); + const result = await memory.queryJson(selector); + if ("error" in result) { + return c.json({ error: result.error?.message || "Unknown error" }, 500); + } + return c.json({ ok: result.ok }, 200); + } catch (err) { + return c.json({ error: (err as Error).message }, 500); + } +};