@genkit-ai/ai
Version:
Genkit AI framework generative AI APIs.
256 lines • 6.9 kB
JavaScript
function escapeToken(token) {
return token.replace(/~/g, "~0").replace(/\//g, "~1");
}
function unescapeToken(token) {
return token.replace(/~1/g, "/").replace(/~0/g, "~");
}
const FORBIDDEN_TOKENS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
function parsePointer(pointer) {
if (pointer === "") return [];
if (pointer[0] !== "/") {
throw new Error(`Invalid JSON Pointer: "${pointer}" must start with "/".`);
}
const tokens = pointer.slice(1).split("/").map(unescapeToken);
for (const token of tokens) {
if (FORBIDDEN_TOKENS.has(token)) {
throw new Error(
`Invalid JSON Pointer: "${pointer}" contains forbidden token "${token}".`
);
}
}
return tokens;
}
function isObject(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function definedKeys(obj) {
return Object.keys(obj).filter((key) => obj[key] !== void 0);
}
function deepEqual(a, b) {
if (a === b) return true;
if (typeof a !== typeof b) return false;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i])) return false;
}
return true;
}
if (isObject(a) && isObject(b)) {
const aKeys = definedKeys(a);
const bKeys = definedKeys(b);
if (aKeys.length !== bKeys.length) return false;
for (const key of aKeys) {
if (b[key] === void 0) return false;
if (!deepEqual(a[key], b[key])) return false;
}
return true;
}
return false;
}
function clone(value) {
if (value === void 0) return value;
if (typeof structuredClone === "function") {
return structuredClone(value);
}
return JSON.parse(JSON.stringify(value));
}
function diff(from, to) {
const patch = [];
diffRecursive(from, to, "", patch);
return patch;
}
function diffRecursive(from, to, pointer, patch) {
if (deepEqual(from, to)) return;
if (isObject(from) && isObject(to)) {
const keys = /* @__PURE__ */ new Set([...Object.keys(from), ...Object.keys(to)]);
for (const key of keys) {
const childPointer = `${pointer}/${escapeToken(key)}`;
const inFrom = from[key] !== void 0;
const inTo = to[key] !== void 0;
if (inFrom && !inTo) {
patch.push({ op: "remove", path: childPointer });
} else if (!inFrom && inTo) {
patch.push({ op: "add", path: childPointer, value: clone(to[key]) });
} else if (inFrom && inTo) {
diffRecursive(from[key], to[key], childPointer, patch);
}
}
return;
}
if (Array.isArray(from) && Array.isArray(to)) {
const min = Math.min(from.length, to.length);
for (let i = 0; i < min; i++) {
diffRecursive(from[i], to[i], `${pointer}/${i}`, patch);
}
if (to.length > from.length) {
for (let i = from.length; i < to.length; i++) {
patch.push({ op: "add", path: `${pointer}/-`, value: clone(to[i]) });
}
} else if (from.length > to.length) {
for (let i = from.length - 1; i >= to.length; i--) {
patch.push({ op: "remove", path: `${pointer}/${i}` });
}
}
return;
}
patch.push({ op: "replace", path: pointer, value: clone(to) });
}
function applyPatch(document, patch) {
let doc = clone(document);
for (const op of patch) {
doc = applyOperation(doc, op);
}
return doc;
}
function applyOperation(doc, op) {
const tokens = parsePointer(op.path);
if (tokens.length === 0) {
switch (op.op) {
case "add":
case "replace":
return clone(op.value);
case "remove":
return void 0;
case "test":
if (!deepEqual(doc, op.value)) {
throw new Error(`JSON Patch 'test' failed at root.`);
}
return doc;
case "move":
case "copy": {
const value = clone(getValue(doc, parsePointer(op.from)));
return value;
}
default:
throw new Error(`Unsupported JSON Patch op: ${op.op}`);
}
}
if (doc == null && (op.op === "add" || op.op === "replace")) {
doc = {};
}
switch (op.op) {
case "add":
setValue(
doc,
tokens,
clone(op.value),
/* isAdd= */
true
);
return doc;
case "replace":
setValue(
doc,
tokens,
clone(op.value),
/* isAdd= */
false
);
return doc;
case "remove":
removeValue(doc, tokens);
return doc;
case "test": {
const actual = getValue(doc, tokens);
if (!deepEqual(actual, op.value)) {
throw new Error(`JSON Patch 'test' failed at "${op.path}".`);
}
return doc;
}
case "move": {
const fromTokens = parsePointer(op.from);
const value = clone(getValue(doc, fromTokens));
removeValue(doc, fromTokens);
setValue(
doc,
tokens,
value,
/* isAdd= */
true
);
return doc;
}
case "copy": {
const value = clone(getValue(doc, parsePointer(op.from)));
setValue(
doc,
tokens,
value,
/* isAdd= */
true
);
return doc;
}
default:
throw new Error(`Unsupported JSON Patch op: ${op.op}`);
}
}
function getValue(doc, tokens) {
let cur = doc;
for (const token of tokens) {
if (cur == null) return void 0;
cur = Array.isArray(cur) ? cur[Number(token)] : cur[token];
}
return cur;
}
function setValue(doc, tokens, value, isAdd) {
const parent = ensureParent(doc, tokens);
if (parent == null) return;
const last = tokens[tokens.length - 1];
if (Array.isArray(parent)) {
if (last === "-") {
parent.push(value);
return;
}
const idx = Number(last);
if (Number.isNaN(idx)) return;
if (isAdd) {
parent.splice(idx, 0, value);
} else {
parent[idx] = value;
}
return;
}
parent[last] = value;
}
function removeValue(doc, tokens) {
const parent = getValue(doc, tokens.slice(0, -1));
if (parent == null) return;
const last = tokens[tokens.length - 1];
if (Array.isArray(parent)) {
const idx = Number(last);
if (!Number.isNaN(idx) && idx >= 0 && idx < parent.length) {
parent.splice(idx, 1);
}
return;
}
if (isObject(parent)) {
delete parent[last];
}
}
function ensureParent(doc, tokens) {
let cur = doc;
for (let i = 0; i < tokens.length - 1; i++) {
const token = tokens[i];
if (cur == null) return void 0;
const next = Array.isArray(cur) ? cur[Number(token)] : cur[token];
if (next == null || typeof next !== "object") {
const created = {};
if (Array.isArray(cur)) {
cur[Number(token)] = created;
} else {
cur[token] = created;
}
cur = created;
} else {
cur = next;
}
}
return cur;
}
export {
applyPatch,
diff
};
//# sourceMappingURL=json-patch.mjs.map