settler-delta-parser.ts 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import {
  2. RuntimeStateDeltaSchema,
  3. type RuntimeStateDelta,
  4. } from "../models/runtime-state.js";
  5. export interface SettlerDeltaOutput {
  6. readonly postSettlement: string;
  7. readonly runtimeStateDelta: RuntimeStateDelta;
  8. }
  9. function sanitizeJSON(str: string): string {
  10. return str
  11. .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "")
  12. .replace(/,\s*([}\]])/g, "$1");
  13. }
  14. export function parseSettlerDeltaOutput(content: string): SettlerDeltaOutput {
  15. const extract = (tag: string): string => {
  16. const regex = new RegExp(
  17. `=== ${tag} ===\\s*([\\s\\S]*?)(?==== [A-Z_]+ ===|$)`,
  18. );
  19. const match = content.match(regex);
  20. return match?.[1]?.trim() ?? "";
  21. };
  22. const rawDelta = extract("RUNTIME_STATE_DELTA");
  23. if (!rawDelta) {
  24. throw new Error("runtime state delta block is missing");
  25. }
  26. const jsonPayload = stripCodeFence(rawDelta);
  27. let parsed: unknown;
  28. try {
  29. parsed = JSON.parse(sanitizeJSON(jsonPayload));
  30. } catch (error) {
  31. throw new Error(`runtime state delta is not valid JSON: ${String(error)}`);
  32. }
  33. try {
  34. return {
  35. postSettlement: extract("POST_SETTLEMENT"),
  36. runtimeStateDelta: RuntimeStateDeltaSchema.parse(parsed),
  37. };
  38. } catch (error) {
  39. throw new Error(`runtime state delta failed schema validation: ${String(error)}`);
  40. }
  41. }
  42. function stripCodeFence(value: string): string {
  43. const trimmed = value.trim();
  44. const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
  45. return fenced?.[1]?.trim() ?? trimmed;
  46. }