UNPKG

@json-render/core

Version:

JSON becomes real things. Define your catalog, register your components, let AI generate.

753 lines (751 loc) • 21 kB
// src/types.ts import { z } from "zod"; var DynamicValueSchema = z.union([ z.string(), z.number(), z.boolean(), z.null(), z.object({ $state: z.string() }) ]); var DynamicStringSchema = z.union([ z.string(), z.object({ $state: z.string() }) ]); var DynamicNumberSchema = z.union([ z.number(), z.object({ $state: z.string() }) ]); var DynamicBooleanSchema = z.union([ z.boolean(), z.object({ $state: z.string() }) ]); function resolveDynamicValue(value, stateModel) { if (value === null || value === void 0) { return void 0; } if (typeof value === "object" && "$state" in value) { return getByPath(stateModel, value.$state); } return value; } function unescapeJsonPointer(token) { return token.replace(/~1/g, "/").replace(/~0/g, "~"); } function parseJsonPointer(path) { const raw = path.startsWith("/") ? path.slice(1).split("/") : path.split("/"); return raw.map(unescapeJsonPointer); } function getByPath(obj, path) { if (!path || path === "/") { return obj; } const segments = parseJsonPointer(path); let current = obj; for (const segment of segments) { if (current === null || current === void 0) { return void 0; } if (Array.isArray(current)) { const index = parseInt(segment, 10); current = current[index]; } else if (typeof current === "object" && Object.hasOwn(current, segment)) { current = current[segment]; } else { return void 0; } } return current; } function resolveRepeatStatePath(statePath, repeatBasePath) { if (typeof statePath === "string") { return statePath; } if (repeatBasePath == null) { return void 0; } if (statePath.$item === "" || statePath.$item === "/") { return repeatBasePath; } return joinStatePath(repeatBasePath, statePath.$item); } function resolveRepeatItemStatePath(statePath, index) { return joinStatePath(statePath, String(index)); } function joinStatePath(basePath, childPath) { const child = childPath.startsWith("/") ? childPath.slice(1) : childPath; if (basePath === "" || basePath === "/") { return `/${child}`; } return `${basePath}/${child}`; } function isNumericIndex(str) { return /^\d+$/.test(str); } function denseIndex(arr, segment) { return isNumericIndex(segment) && Number(segment) <= arr.length ? Number(segment) : void 0; } function setByPath(obj, path, value) { const segments = parseJsonPointer(path); if (segments.length === 0 || segments.includes("__proto__")) return; let current = obj; for (let i = 0; i < segments.length - 1; i++) { const segment = segments[i]; const nextSegment = segments[i + 1]; const nextIsNumeric = nextSegment !== void 0 && (isNumericIndex(nextSegment) || nextSegment === "-"); if (Array.isArray(current)) { const index = denseIndex(current, segment); if (index === void 0) return; if (current[index] === void 0 || typeof current[index] !== "object") { current[index] = nextIsNumeric ? [] : {}; } current = current[index]; } else { if (!Object.hasOwn(current, segment) || typeof current[segment] !== "object") { current[segment] = nextIsNumeric ? [] : {}; } current = current[segment]; } } const lastSegment = segments[segments.length - 1]; if (Array.isArray(current)) { if (lastSegment === "-") { current.push(value); } else { const index = denseIndex(current, lastSegment); if (index !== void 0) current[index] = value; } } else { current[lastSegment] = value; } } function addByPath(obj, path, value) { const segments = parseJsonPointer(path); if (segments.length === 0 || segments.includes("__proto__")) return; let current = obj; for (let i = 0; i < segments.length - 1; i++) { const segment = segments[i]; const nextSegment = segments[i + 1]; const nextIsNumeric = nextSegment !== void 0 && (isNumericIndex(nextSegment) || nextSegment === "-"); if (Array.isArray(current)) { const index = denseIndex(current, segment); if (index === void 0) return; if (current[index] === void 0 || typeof current[index] !== "object") { current[index] = nextIsNumeric ? [] : {}; } current = current[index]; } else { if (!Object.hasOwn(current, segment) || typeof current[segment] !== "object") { current[segment] = nextIsNumeric ? [] : {}; } current = current[segment]; } } const lastSegment = segments[segments.length - 1]; if (Array.isArray(current)) { if (lastSegment === "-") { current.push(value); } else { const index = denseIndex(current, lastSegment); if (index !== void 0) current.splice(index, 0, value); } } else { current[lastSegment] = value; } } function removeByPath(obj, path) { const segments = parseJsonPointer(path); if (segments.length === 0 || segments.includes("__proto__")) return; let current = obj; for (let i = 0; i < segments.length - 1; i++) { const segment = segments[i]; if (Array.isArray(current)) { const index = parseInt(segment, 10); if (current[index] === void 0 || typeof current[index] !== "object") { return; } current = current[index]; } else { if (!Object.hasOwn(current, segment) || typeof current[segment] !== "object") { return; } current = current[segment]; } } const lastSegment = segments[segments.length - 1]; if (Array.isArray(current)) { const index = parseInt(lastSegment, 10); if (index >= 0 && index < current.length) { current.splice(index, 1); } } else { delete current[lastSegment]; } } function deepEqual(a, b) { if (a === b) return true; if (a === null || b === null) return false; if (typeof a !== typeof b) return false; if (typeof a !== "object") return false; if (Array.isArray(a)) { if (!Array.isArray(b)) return false; if (a.length !== b.length) return false; return a.every((item, i) => deepEqual(item, b[i])); } const aObj = a; const bObj = b; const aKeys = Object.keys(aObj); const bKeys = Object.keys(bObj); if (aKeys.length !== bKeys.length) return false; return aKeys.every((key) => deepEqual(aObj[key], bObj[key])); } function findFormValue(fieldName, params, state) { if (params?.[fieldName] !== void 0) { return params[fieldName]; } if (params) { for (const key of Object.keys(params)) { if (key.endsWith(`.${fieldName}`)) { return params[key]; } } } if (state) { for (const key of Object.keys(state)) { if (key === fieldName || key.endsWith(`.${fieldName}`)) { return state[key]; } } const val = getByPath(state, fieldName); if (val !== void 0) { return val; } } return void 0; } function parseSpecStreamLine(line) { const trimmed = line.trim(); if (!trimmed || !trimmed.startsWith("{")) return null; try { const patch = JSON.parse(trimmed); if (patch.op && patch.path !== void 0) { return patch; } return null; } catch { return null; } } function applySpecStreamPatch(obj, patch) { switch (patch.op) { case "add": addByPath(obj, patch.path, patch.value); break; case "replace": setByPath(obj, patch.path, patch.value); break; case "remove": removeByPath(obj, patch.path); break; case "move": { if (!patch.from) break; const moveValue = getByPath(obj, patch.from); removeByPath(obj, patch.from); addByPath(obj, patch.path, moveValue); break; } case "copy": { if (!patch.from) break; const copyValue = getByPath(obj, patch.from); addByPath(obj, patch.path, copyValue); break; } case "test": { const actual = getByPath(obj, patch.path); if (!deepEqual(actual, patch.value)) { throw new Error( `Test operation failed: value at "${patch.path}" does not match` ); } break; } } return obj; } function applySpecPatch(spec, patch) { applySpecStreamPatch(spec, patch); return spec; } function nestedToFlat(nested) { const elements = {}; let counter = 0; function walk(node) { const key = `el-${counter++}`; const { type, props, children: rawChildren, slots: rawSlots, ...rest } = node; const childKeys = []; if (Array.isArray(rawChildren)) { for (const child of rawChildren) { if (child && typeof child === "object" && "type" in child) { childKeys.push(walk(child)); } } } const slots = {}; if (rawSlots && typeof rawSlots === "object") { for (const [slotName, slotChildren] of Object.entries(rawSlots)) { if (!Array.isArray(slotChildren)) continue; slots[slotName] = slotChildren.flatMap( (child) => child && typeof child === "object" && "type" in child ? [walk(child)] : [] ); } } const element = { type: type ?? "unknown", props: props ?? {}, children: childKeys, ...Object.keys(slots).length > 0 ? { slots } : {} }; for (const [k, v] of Object.entries(rest)) { if (k !== "state" && v !== void 0) { element[k] = v; } } elements[key] = element; return key; } const root = walk(nested); const spec = { root, elements }; if (nested.state && typeof nested.state === "object" && !Array.isArray(nested.state)) { spec.state = nested.state; } return spec; } function compileSpecStream(stream, initial = {}) { const lines = stream.split("\n"); const result = { ...initial }; for (const line of lines) { const patch = parseSpecStreamLine(line); if (patch) { applySpecStreamPatch(result, patch); } } return result; } function createSpecStreamCompiler(initial = {}) { let result = { ...initial }; let buffer = ""; const appliedPatches = []; return { push(chunk) { buffer += chunk; const newPatches = []; const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { const trimmed = line.trim(); if (!trimmed) continue; const patch = parseSpecStreamLine(trimmed); if (patch) { applySpecStreamPatch(result, patch); appliedPatches.push(patch); newPatches.push(patch); } } if (newPatches.length > 0) { result = { ...result }; } return { result, newPatches }; }, getResult() { if (buffer.trim()) { const patch = parseSpecStreamLine(buffer); if (patch) { applySpecStreamPatch(result, patch); appliedPatches.push(patch); result = { ...result }; } buffer = ""; } return result; }, getPatches() { return [...appliedPatches]; }, reset(newInitial = {}) { result = { ...newInitial }; buffer = ""; appliedPatches.length = 0; } }; } function createMixedStreamParser(callbacks) { let buffer = ""; let inSpecFence = false; function processLine(line) { const trimmed = line.trim(); if (!inSpecFence && trimmed.startsWith("```spec")) { inSpecFence = true; return; } if (inSpecFence && trimmed === "```") { inSpecFence = false; return; } if (!trimmed) return; if (inSpecFence) { const patch2 = parseSpecStreamLine(trimmed); if (patch2) { callbacks.onPatch(patch2); } return; } const patch = parseSpecStreamLine(trimmed); if (patch) { callbacks.onPatch(patch); } else { callbacks.onText(line); } } return { push(chunk) { buffer += chunk; const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { processLine(line); } }, flush() { if (buffer.trim()) { processLine(buffer); } buffer = ""; } }; } var SPEC_FENCE_OPEN = "```spec"; var SPEC_FENCE_CLOSE = "```"; function createJsonRenderTransform() { let lineBuffer = ""; let currentTextId = ""; let buffering = false; let inSpecFence = false; let inTextBlock = false; let textIdCounter = 0; function closeTextBlock(controller) { if (inTextBlock) { controller.enqueue({ type: "text-end", id: currentTextId }); inTextBlock = false; } } function ensureTextBlock(controller) { if (!inTextBlock) { textIdCounter++; currentTextId = String(textIdCounter); controller.enqueue({ type: "text-start", id: currentTextId }); inTextBlock = true; } } function emitTextDelta(delta, controller) { ensureTextBlock(controller); controller.enqueue({ type: "text-delta", id: currentTextId, delta }); } function emitPatch(patch, controller) { closeTextBlock(controller); controller.enqueue({ type: SPEC_DATA_PART_TYPE, data: { type: "patch", patch } }); } function flushBuffer(controller) { if (!lineBuffer) return; const trimmed = lineBuffer.trim(); if (inSpecFence) { if (trimmed) { const patch = parseSpecStreamLine(trimmed); if (patch) emitPatch(patch, controller); } lineBuffer = ""; buffering = false; return; } if (trimmed) { const patch = parseSpecStreamLine(trimmed); if (patch) { emitPatch(patch, controller); } else { emitTextDelta(lineBuffer, controller); } } else { emitTextDelta(lineBuffer, controller); } lineBuffer = ""; buffering = false; } function processCompleteLine(line, controller) { const trimmed = line.trim(); if (!inSpecFence && trimmed.startsWith(SPEC_FENCE_OPEN)) { inSpecFence = true; return; } if (inSpecFence && trimmed === SPEC_FENCE_CLOSE) { inSpecFence = false; return; } if (inSpecFence) { if (trimmed) { const patch2 = parseSpecStreamLine(trimmed); if (patch2) emitPatch(patch2, controller); } return; } if (!trimmed) { emitTextDelta("\n", controller); return; } const patch = parseSpecStreamLine(trimmed); if (patch) { emitPatch(patch, controller); } else { emitTextDelta(line + "\n", controller); } } return new TransformStream({ transform(chunk, controller) { switch (chunk.type) { case "text-start": { const id = chunk.id; const idNum = parseInt(id, 10); if (!isNaN(idNum) && idNum >= textIdCounter) { textIdCounter = idNum; } currentTextId = id; inTextBlock = true; controller.enqueue(chunk); break; } case "text-delta": { const delta = chunk; const text = delta.delta; for (let i = 0; i < text.length; i++) { const ch = text.charAt(i); if (ch === "\n") { if (buffering) { processCompleteLine(lineBuffer, controller); lineBuffer = ""; buffering = false; } else { if (!inSpecFence) { emitTextDelta("\n", controller); } } } else if (lineBuffer.length === 0 && !buffering) { if (inSpecFence || ch === "{" || ch === "`") { buffering = true; lineBuffer += ch; } else { emitTextDelta(ch, controller); } } else if (buffering) { lineBuffer += ch; } else { emitTextDelta(ch, controller); } } break; } case "text-end": { flushBuffer(controller); if (inTextBlock) { controller.enqueue({ type: "text-end", id: currentTextId }); inTextBlock = false; } break; } default: { controller.enqueue(chunk); break; } } }, flush(controller) { flushBuffer(controller); closeTextBlock(controller); } }); } var SPEC_DATA_PART = "spec"; var SPEC_DATA_PART_TYPE = `data-${SPEC_DATA_PART}`; function pipeJsonRender(stream) { return stream.pipeThrough( createJsonRenderTransform() ); } // src/state-store.ts function immutableSetByPath(root, path, value) { const segments = parseJsonPointer(path); if (segments.length === 0) return root; const result = { ...root }; let current = result; for (let i = 0; i < segments.length - 1; i++) { const seg = segments[i]; const child = current[seg]; if (Array.isArray(child)) { current[seg] = [...child]; } else if (child !== null && typeof child === "object") { current[seg] = { ...child }; } else { const nextSeg = segments[i + 1]; current[seg] = nextSeg !== void 0 && /^\d+$/.test(nextSeg) ? [] : {}; } current = current[seg]; } const lastSeg = segments[segments.length - 1]; if (Array.isArray(current)) { if (lastSeg === "-") { current.push(value); } else { current[parseInt(lastSeg, 10)] = value; } } else { current[lastSeg] = value; } return result; } function createStateStore(initialState = {}) { let state = { ...initialState }; const listeners = /* @__PURE__ */ new Set(); function notify() { for (const listener of listeners) { listener(); } } return { get(path) { return getByPath(state, path); }, set(path, value) { if (getByPath(state, path) === value) return; state = immutableSetByPath(state, path, value); notify(); }, update(updates) { let changed = false; let next = state; for (const [path, value] of Object.entries(updates)) { if (getByPath(next, path) !== value) { next = immutableSetByPath(next, path, value); changed = true; } } if (!changed) return; state = next; notify(); }, getSnapshot() { return state; }, getServerSnapshot() { return state; }, subscribe(listener) { listeners.add(listener); return () => { listeners.delete(listener); }; } }; } function createStoreAdapter(config) { return { get(path) { return getByPath(config.getSnapshot(), path); }, set(path, value) { const current = config.getSnapshot(); if (getByPath(current, path) === value) return; config.setSnapshot(immutableSetByPath(current, path, value)); }, update(updates) { let next = config.getSnapshot(); let changed = false; for (const [path, value] of Object.entries(updates)) { if (getByPath(next, path) !== value) { next = immutableSetByPath(next, path, value); changed = true; } } if (!changed) return; config.setSnapshot(next); }, getSnapshot: config.getSnapshot, getServerSnapshot: config.getSnapshot, subscribe: config.subscribe }; } var MAX_FLATTEN_DEPTH = 20; function flattenToPointers(obj, prefix = "", _depth = 0, _seen, _warned) { const seen = _seen ?? /* @__PURE__ */ new Set(); const warned = _warned ?? { current: false }; const result = {}; for (const [key, value] of Object.entries(obj)) { const pointer = `${prefix}/${key}`; if (_depth < MAX_FLATTEN_DEPTH && value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype && !seen.has(value)) { seen.add(value); Object.assign( result, flattenToPointers( value, pointer, _depth + 1, seen, warned ) ); } else { if (process.env.NODE_ENV !== "production" && !warned.current && _depth >= MAX_FLATTEN_DEPTH && value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype && !seen.has(value)) { warned.current = true; console.warn( `flattenToPointers: depth limit (${MAX_FLATTEN_DEPTH}) reached. Nested state beyond this depth will be treated as a leaf value.` ); } result[pointer] = value; } } return result; } export { DynamicValueSchema, DynamicStringSchema, DynamicNumberSchema, DynamicBooleanSchema, resolveDynamicValue, getByPath, resolveRepeatStatePath, resolveRepeatItemStatePath, setByPath, addByPath, removeByPath, findFormValue, parseSpecStreamLine, applySpecStreamPatch, applySpecPatch, nestedToFlat, compileSpecStream, createSpecStreamCompiler, createMixedStreamParser, createJsonRenderTransform, SPEC_DATA_PART, SPEC_DATA_PART_TYPE, pipeJsonRender, immutableSetByPath, createStateStore, createStoreAdapter, flattenToPointers }; //# sourceMappingURL=chunk-FVP4K5MD.mjs.map