From a70e8195a269984519059a6e3d405c195331eb29 Mon Sep 17 00:00:00 2001 From: Max Richter Date: Fri, 20 Dec 2024 14:06:33 +0100 Subject: [PATCH] feat: add some more versining stuff --- store/openapi.json | 2 +- store/src/routes/node/node.controller.ts | 76 ++++++++++++++++++------ store/src/routes/node/node.service.ts | 57 +++++++++++++++--- 3 files changed, 108 insertions(+), 27 deletions(-) diff --git a/store/openapi.json b/store/openapi.json index 2f7d493..c8dfe94 100644 --- a/store/openapi.json +++ b/store/openapi.json @@ -1 +1 @@ -{"openapi":"3.0.0","info":{"version":"1.0.0","title":"Nodarium API"},"components":{"schemas":{"NodeInput":{"anyOf":[{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["seed"]},"value":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["boolean"]},"value":{"type":"boolean"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["float"]},"element":{"type":"string","enum":["slider"]},"value":{"type":"number"},"min":{"type":"number"},"max":{"type":"number"},"step":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["integer"]},"element":{"type":"string","enum":["slider"]},"value":{"type":"number"},"min":{"type":"number"},"max":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["select"]},"options":{"type":"array","items":{"type":"string"}},"value":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["seed"]},"value":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["vec3"]},"value":{"type":"array","items":{"type":"number"}}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["geometry"]}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["path"]}},"required":["type"]}]},"NodeDefinition":{"type":"object","properties":{"id":{"type":"string","pattern":"^([a-z0-9-]+)\\/([a-z0-9-]+)\\/([a-z0-9-]+)$"},"inputs":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/NodeInput"}},"outputs":{"type":"array","items":{"type":"string"}},"meta":{"type":"object","properties":{"description":{"type":"string"},"title":{"type":"string"}}}},"required":["id"]},"User":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1}},"required":["id","name"]}},"parameters":{}},"paths":{"/nodes/{user}.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"}],"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}}},"/nodes/{user}/{system}.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":false,"name":"system","in":"path"}],"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}}},"/nodes/{user}/{system}/{nodeId}.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"system","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"nodeId","in":"path"}],"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}},"/nodes/{user}/{system}/{nodeId}.wasm":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"system","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"nodeId","in":"path"}],"responses":{"200":{"description":"Retrieve a single node","content":{"application/wasm":{"schema":{"nullable":true}}}}}}},"/nodes/{user}/{system}/{nodeId}@{hash}.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"system","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"nodeId","in":"path"}],"responses":{"200":{"description":"Create a single node","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}},"/nodes/{user}/{system}/{nodeId}/versions.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"system","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"nodeId","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"hash","in":"path"}],"responses":{"200":{"description":"Create a single node","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}},"/nodes":{"post":{"responses":{"200":{"description":"Create a single node","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}},"/users/users.json":{"get":{"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}}}}},"/users/{userId}.json":{"get":{"parameters":[{"schema":{"type":"string"},"required":false,"name":"userId","in":"path"}],"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}}}}}}} \ No newline at end of file +{"openapi":"3.0.0","info":{"version":"1.0.0","title":"Nodarium API"},"components":{"schemas":{"NodeInput":{"anyOf":[{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["seed"]},"value":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["boolean"]},"value":{"type":"boolean"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["float"]},"element":{"type":"string","enum":["slider"]},"value":{"type":"number"},"min":{"type":"number"},"max":{"type":"number"},"step":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["integer"]},"element":{"type":"string","enum":["slider"]},"value":{"type":"number"},"min":{"type":"number"},"max":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["select"]},"options":{"type":"array","items":{"type":"string"}},"value":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["seed"]},"value":{"type":"number"}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["vec3"]},"value":{"type":"array","items":{"type":"number"}}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["geometry"]}},"required":["type"]},{"type":"object","properties":{"internal":{"type":"boolean"},"external":{"type":"boolean"},"setting":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"accepts":{"type":"array","items":{"type":"string"}},"hidden":{"type":"boolean"},"type":{"type":"string","enum":["path"]}},"required":["type"]}]},"NodeDefinition":{"type":"object","properties":{"id":{"type":"string","pattern":"^([a-z0-9-]+)\\/([a-z0-9-]+)\\/([a-z0-9-]+)$"},"inputs":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/NodeInput"}},"outputs":{"type":"array","items":{"type":"string"}},"meta":{"type":"object","properties":{"description":{"type":"string"},"title":{"type":"string"}}}},"required":["id"]},"User":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1}},"required":["id","name"]}},"parameters":{}},"paths":{"/nodes/{user}.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":false,"name":"user","in":"path"}],"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}}},"/nodes/{user}/{system}.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":false,"name":"system","in":"path"}],"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}}},"/nodes/{user}/{system}/{nodeId}.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"system","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":false,"name":"nodeId","in":"path"}],"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}},"/nodes/{user}/{system}/{nodeId}.wasm":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"system","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"nodeId","in":"path"}],"responses":{"200":{"description":"Retrieve a single node","content":{"application/wasm":{"schema":{"nullable":true}}}}}}},"/nodes/{user}/{system}/{nodeId}@{hash}.wasm":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"system","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"nodeId","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"hash","in":"path"}],"responses":{"200":{"description":"Create a single node","content":{"application/wasm":{"schema":{"nullable":true}}}}}}},"/nodes/{user}/{system}/{nodeId}@{hash}.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"system","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"nodeId","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"hash","in":"path"}],"responses":{"200":{"description":"Create a single node","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}},"/nodes/{user}/{system}/{nodeId}/versions.json":{"get":{"parameters":[{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"user","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"system","in":"path"},{"schema":{"type":"string","minLength":3,"maxLength":20},"required":true,"name":"nodeId","in":"path"}],"responses":{"200":{"description":"Create a single node","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}}},"/nodes":{"post":{"responses":{"200":{"description":"Create a single node","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodeDefinition"}}}}}}},"/users/users.json":{"get":{"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}}}}},"/users/{userId}.json":{"get":{"parameters":[{"schema":{"type":"string"},"required":false,"name":"userId","in":"path"}],"responses":{"200":{"description":"Retrieve a single node definition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}}}}}}} \ No newline at end of file diff --git a/store/src/routes/node/node.controller.ts b/store/src/routes/node/node.controller.ts index 8ab1d3a..3494ada 100644 --- a/store/src/routes/node/node.controller.ts +++ b/store/src/routes/node/node.controller.ts @@ -28,7 +28,7 @@ const getUserNodesRoute = createRoute({ path: "/{user}.json", request: { params: z.object({ - user: SingleParam("user"), + user: SingleParam("user").optional(), }), }, responses: { @@ -44,7 +44,9 @@ const getUserNodesRoute = createRoute({ }); nodeRouter.openapi(getUserNodesRoute, async (c) => { const userId = c.req.param("user.json").replace(/\.json$/, ""); - const nodes = await service.getNodeDefinitionsByUser(userId); + const nodes = await service.getNodeDefinitionsByUser( + userId, + ); return c.json(nodes); }); @@ -80,7 +82,11 @@ const getNodeDefinitionRoute = createRoute({ method: "get", path: "/{user}/{system}/{nodeId}.json", request: { - params: ParamsSchema, + params: z.object({ + user: SingleParam("user"), + system: SingleParam("system"), + nodeId: SingleParam("nodeId").optional(), + }), }, responses: { 200: { @@ -94,12 +100,13 @@ const getNodeDefinitionRoute = createRoute({ }, }); nodeRouter.openapi(getNodeDefinitionRoute, async (c) => { - const { user, system, nodeId } = c.req.valid("param"); + const { user, system } = c.req.valid("param"); + const nodeId = c.req.param("nodeId.json").replace(/\.json$/, ""); const node = await service.getNodeDefinitionById( user, system, - nodeId.replace(/\.json$/, ""), + nodeId, ); if (!node) { @@ -140,17 +147,22 @@ nodeRouter.openapi(getNodeWasmRoute, async (c) => { return c.body(wasmContent); }); -const getNodeVersionRoute = createRoute({ +const getNodeVersionWasmRoute = createRoute({ method: "get", - path: "/{user}/{system}/{nodeId}@{hash}.json", + path: "/{user}/{system}/{nodeId}@{hash}.wasm", request: { - params: ParamsSchema, + params: z.object({ + user: SingleParam("user"), + system: SingleParam("system"), + nodeId: SingleParam("nodeId"), + hash: SingleParam("hash"), + }), }, responses: { 200: { content: { - "application/json": { - schema: NodeDefinitionSchema, + "application/wasm": { + schema: z.any(), }, }, description: "Create a single node", @@ -158,17 +170,17 @@ const getNodeVersionRoute = createRoute({ }, }); -nodeRouter.openapi(getNodeVersionRoute, async (c) => { - const { user, system, nodeId } = c.req.valid("param"); +nodeRouter.openapi(getNodeVersionWasmRoute, async (c) => { + const { user, system, nodeId, hash } = c.req.valid("param"); - const nodes = await service.getNodeVersions(user, system, nodeId); + const nodes = await service.getNodeVersionWasm(user, system, nodeId, hash); return c.json(nodes); }); -const getNodeVersionsRoute = createRoute({ +const getNodeVersionRoute = createRoute({ method: "get", - path: "/{user}/{system}/{nodeId}/versions.json", + path: "/{user}/{system}/{nodeId}@{hash}.json", request: { params: z.object({ user: SingleParam("user"), @@ -189,10 +201,40 @@ const getNodeVersionsRoute = createRoute({ }, }); -nodeRouter.openapi(getNodeVersionsRoute, async (c) => { +nodeRouter.openapi(getNodeVersionRoute, async (c) => { const { user, system, nodeId, hash } = c.req.valid("param"); - const node = await service.getNodeVersion(user, system, nodeId, hash); + const nodes = await service.getNodeVersion(user, system, nodeId, hash); + + return c.json(nodes); +}); + +const getNodeVersionsRoute = createRoute({ + method: "get", + path: "/{user}/{system}/{nodeId}/versions.json", + request: { + params: z.object({ + user: SingleParam("user"), + system: SingleParam("system"), + nodeId: SingleParam("nodeId"), + }), + }, + responses: { + 200: { + content: { + "application/json": { + schema: z.array(NodeDefinitionSchema), + }, + }, + description: "Create a single node", + }, + }, +}); + +nodeRouter.openapi(getNodeVersionsRoute, async (c) => { + const { user, system, nodeId } = c.req.valid("param"); + + const node = await service.getNodeVersions(user, system, nodeId); return c.json(node); }); diff --git a/store/src/routes/node/node.service.ts b/store/src/routes/node/node.service.ts index 623c7da..cbe8050 100644 --- a/store/src/routes/node/node.service.ts +++ b/store/src/routes/node/node.service.ts @@ -83,7 +83,10 @@ export async function createNode( } export async function getNodeDefinitionsByUser(userName: string) { - const nodes = await db.select({ definition: nodeTable.definition }).from( + const nodes = await db.select({ + definition: nodeTable.definition, + hash: nodeTable.hash, + }).from( nodeTable, ) .where( @@ -92,7 +95,10 @@ export async function getNodeDefinitionsByUser(userName: string) { ), ); - return nodes.map((n) => n.definition); + return nodes.map((n) => ({ + ...n.definition, + id: n.definition.id + "@" + n.hash, + })); } export async function getNodesBySystem( @@ -102,7 +108,7 @@ export async function getNodesBySystem( const nodes = await db .selectDistinctOn( [nodeTable.userId, nodeTable.systemId, nodeTable.nodeId], - { definition: nodeTable.definition }, + { definition: nodeTable.definition, hash: nodeTable.hash }, ) .from(nodeTable) .where( @@ -110,9 +116,14 @@ export async function getNodesBySystem( ).orderBy(nodeTable.userId, nodeTable.systemId, nodeTable.nodeId); const definitions = nodes - .map((node) => NodeDefinitionSchema.safeParse(node.definition)) - .filter((v) => v.success) - .map((v) => v.data); + .map((node) => + [NodeDefinitionSchema.safeParse(node.definition), node.hash] as const + ) + .filter(([v]) => v.success) + .map(([v, hash]) => ({ + ...v.data, + id: v?.data?.id + "@" + hash, + })); return definitions; } @@ -147,7 +158,10 @@ export async function getNodeDefinitionById( systemId: string, nodeId: string, ) { - const node = await db.select({ definition: nodeTable.definition }).from( + const node = await db.select({ + definition: nodeTable.definition, + hash: nodeTable.hash, + }).from( nodeTable, ).where( and( @@ -169,7 +183,7 @@ export async function getNodeDefinitionById( throw new Error("Invalid definition"); } - return definition.data; + return { ...definition.data, id: definition.data.id + "@" + node[0].hash }; } export async function getNodeVersions( @@ -205,7 +219,6 @@ export async function getNodeVersion( ) { const nodes = await db.select({ definition: nodeTable.definition, - hash: nodeTable.hash, }).from( nodeTable, ).where( @@ -223,3 +236,29 @@ export async function getNodeVersion( return nodes[0].definition; } + +export async function getNodeVersionWasm( + user: string, + system: string, + nodeId: string, + hash: string, +) { + const node = await db.select({ + content: nodeTable.content, + }).from( + nodeTable, + ).where( + and( + eq(nodeTable.userId, user), + eq(nodeTable.systemId, system), + eq(nodeTable.nodeId, nodeId), + eq(nodeTable.hash, hash), + ), + ).limit(1); + + if (node.length === 0) { + throw new Error("Node not found"); + } + + return node[0].content; +}