Skip to main content
Glama

Storyden

by Southclaws
Mozilla Public License 2.0
229
diff.ts5.68 kB
import { dequal } from "dequal"; import { omit } from "lodash"; import { Identifier, NodeMutableProps, NodeWithChildren, PropertySchemaList, PropertySchemaMutableProps, } from "@/api/openapi-schema"; import { isValidLinkLike, normalizeLink } from "../link/validation"; import { isSlugReady } from "../mark/mark"; type NodeMutablePropsWithChildren = NodeMutableProps & Pick<NodeWithChildren, "children">; function projectNodeToMutableProps( node: NodeWithChildren, ): NodeMutablePropsWithChildren { return { name: node.name, slug: node.slug, content: node.content, tags: node.tags.map((t) => t.name), primary_image_asset_id: node.primary_image?.id, url: normalizeLink(node.link?.url), description: node.description, properties: node.properties.map((p) => { const fid = p.fid.startsWith("new_field") ? undefined : p.fid; return { fid: fid, name: p.name, value: p.value, type: p.type, }; }), hide_child_tree: node.hide_child_tree, meta: node.meta, children: node.children, }; } export type MutationSet = { clean: boolean; nodeMutation: NodeMutableProps; childPropertySchemaMutation?: PropertySchemaMutableProps[]; childMutation: Record<Identifier, NodeMutableProps[]>; }; export const deriveMutationFromDifference = ( current: NodeWithChildren, updated: NodeWithChildren, ): MutationSet => { const mutation: NodeMutableProps = {}; const childMutation: Record<Identifier, NodeMutableProps[]> = {}; const draft = projectNodeToMutableProps(current); const changes = projectNodeToMutableProps(updated); const keys = Object.keys(changes) as (keyof NodeMutablePropsWithChildren)[]; console.debug( "deriveMutationFromDifference", current.id, "keys", keys, "changes", changes, ); keys.forEach((key) => { const draftValue = draft[key]; const updatedValue = changes[key]; if (updatedValue === null) { console.debug(`Skipping mutation for '${key}' because it is null`); return; } const changed = !dequal(draftValue, updatedValue); if (!changed) { console.debug( `Skipping mutation for '${key}' because it has not changed`, { old: draftValue, new: updatedValue, }, ); return; } // Field specific transformations and skipping logic. switch (key) { case "slug": { const slugValue = updatedValue as string | undefined; if (slugValue === undefined) { return; } if (!isSlugReady(slugValue)) { // Slugs must be valid to be added to patch, see mark.ts for details. console.debug("Skipping mutation for 'slug' because it is not ready"); return; } break; } case "url": { const urlValue = updatedValue as string | undefined; if (urlValue === undefined) { Object.assign(mutation, { url: null }); return; } else { if (!isValidLinkLike(urlValue)) { console.debug( "Skipping mutation for 'url' because it is not valid", updatedValue, ); return; } } break; } case "primary_image_asset_id": { const primaryImageAssetId = updatedValue as Identifier | undefined; if (primaryImageAssetId === undefined) { Object.assign(mutation, { primary_image_asset_id: null }); return; } break; } case "children": { const updatedChildren = updatedValue as NodeWithChildren["children"]; if (updatedChildren.length === 0) { console.debug("Skipping mutation for 'children' because it is empty"); return; } // If children have changed we need to create a mutation for each child. updatedChildren.forEach((child) => { const childDraft = draft.children.find( (c) => c.id === child.id, ) as NodeWithChildren; if (!childDraft) { console.warn("Child draft not found for", child.id); return; } // Safe recursion: children do not contain a full tree of descendants. const childChanges = deriveMutationFromDifference(childDraft, child); console.debug("child deriveMutationFromDifference:", childChanges); if (childChanges.clean) { return; } console.debug( "Adding child mutation for", child.id, childChanges.nodeMutation, ); (childMutation[child.id] ??= []).push(childChanges.nodeMutation); }); return; } } Object.assign(mutation, { [key]: updatedValue }); }); // Diff the child property schema const childPropertySchema = diffPropertySchemas( current.child_property_schema, updated.child_property_schema, ); const nodeMutations = Object.keys(mutation).length; // Determine if this mutation even does anything. const clean = nodeMutations === 0 && !childPropertySchema && !Object.keys(childMutation).length; return { clean, nodeMutation: mutation, childPropertySchemaMutation: childPropertySchema, childMutation: childMutation, }; }; function diffPropertySchemas( a: PropertySchemaList, b: PropertySchemaList, ): PropertySchemaMutableProps[] | undefined { if (dequal(a, b)) { return undefined; } return b.map((p) => { if (p.fid.startsWith("new_field")) { return omit(p, "fid") as PropertySchemaMutableProps; } return p; }); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Southclaws/storyden'

If you have feedback or need assistance with the MCP directory API, please join our Discord server