n8n
Version:
n8n Workflow Automation Tool
223 lines • 9.28 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyProviderShapeNormalizers = applyProviderShapeNormalizers;
exports.findProviderShapeViolation = findProviderShapeViolation;
const PLACEHOLDER_B64 = 'iVBORw0KGgo';
const DEFAULT_HUBSPOT_VID = 3234574;
function isPlainObject(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function nowUnix() {
return Math.floor(Date.now() / 1000);
}
function isOpenAiImagesGeneration(path) {
return path.endsWith('/images/generations');
}
function isGeminiGenerate(host, path) {
return host === 'generativelanguage.googleapis.com' && path.includes('generatecontent');
}
function isRedditWrite(host, path, method) {
if (method.toUpperCase() !== 'POST' || !host.includes('reddit.com'))
return null;
if (path.includes('api/comment'))
return 'comment';
if (path.includes('api/submit'))
return 'submit';
return null;
}
function isHubspotUpsert(host, path) {
return host.includes('hubapi.com') && path.includes('/contacts/v1/contact/createorupdate');
}
function isGoogleDocsBatchUpdate(host, path) {
return (host === 'docs.googleapis.com' && path.includes('/documents/') && path.includes(':batchupdate'));
}
function extractAnswerText(body) {
if (body === null || body === undefined)
return '';
if (typeof body === 'string')
return body;
if (!isPlainObject(body))
return String(body);
if (typeof body.text === 'string')
return body.text;
if (typeof body.content === 'string')
return body.content;
if (typeof body.output_text === 'string')
return body.output_text;
if (typeof body.message === 'string')
return body.message;
return JSON.stringify(body);
}
function normalizeOpenAiImages(spec) {
const body = spec.body;
let base = {};
let rawEntries;
if (isPlainObject(body) && Array.isArray(body.data)) {
base = body;
rawEntries = body.data;
}
else if (Array.isArray(body)) {
rawEntries = body;
}
else if (isPlainObject(body) && ('b64_json' in body || 'url' in body)) {
rawEntries = [body];
}
else if (isPlainObject(body)) {
base = body;
}
const entries = rawEntries && rawEntries.length > 0 ? rawEntries : [{}];
const data = entries.map(coerceImageEntry);
spec.body = { created: nowUnix(), ...base, data };
}
function coerceImageEntry(entry) {
if (typeof entry === 'string')
return { b64_json: entry };
if (!isPlainObject(entry))
return { b64_json: PLACEHOLDER_B64 };
if (typeof entry.b64_json === 'string' && entry.b64_json.length > 0)
return entry;
return { ...entry, b64_json: PLACEHOLDER_B64 };
}
function normalizeGemini(spec) {
if (hasGeminiCandidates(spec.body))
return;
const text = extractAnswerText(spec.body);
spec.body = {
candidates: [{ content: { parts: [{ text }], role: 'model' }, finishReason: 'STOP', index: 0 }],
usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 },
};
}
function hasGeminiCandidates(body) {
if (!isPlainObject(body) || !Array.isArray(body.candidates) || body.candidates.length === 0) {
return false;
}
const first = body.candidates[0];
if (!isPlainObject(first) || !isPlainObject(first.content))
return false;
return Array.isArray(first.content.parts) && first.content.parts.length > 0;
}
function normalizeReddit(spec, kind) {
const inner = extractRedditData(spec.body);
const data = kind === 'comment' ? ensureRedditThings(inner) : inner;
spec.body = { json: { errors: existingRedditErrors(spec.body), data } };
}
function extractRedditData(body) {
if (isPlainObject(body) && isPlainObject(body.json) && isPlainObject(body.json.data)) {
return body.json.data;
}
if (isPlainObject(body) && isPlainObject(body.data))
return body.data;
if (isPlainObject(body))
return body;
return {};
}
function existingRedditErrors(body) {
if (isPlainObject(body) && isPlainObject(body.json) && Array.isArray(body.json.errors)) {
return body.json.errors;
}
return [];
}
function ensureRedditThings(data) {
if (Array.isArray(data.things) && isPlainObject(data.things[0]) && data.things[0].data) {
return data;
}
return { things: [{ kind: 't1', data }] };
}
function normalizeHubspotUpsert(spec) {
const body = isPlainObject(spec.body) ? spec.body : {};
spec.body = {
...body,
vid: resolveHubspotVid(body),
isNew: typeof body.isNew === 'boolean' ? body.isNew : true,
};
}
function resolveHubspotVid(body) {
for (const candidate of [body.vid, body['canonical-vid'], body.id]) {
if (typeof candidate === 'number' && Number.isFinite(candidate))
return candidate;
if (typeof candidate === 'string' && /^\d+$/.test(candidate))
return Number(candidate);
}
return DEFAULT_HUBSPOT_VID;
}
function normalizeGoogleDocsBatchUpdate(spec) {
const body = isPlainObject(spec.body) ? spec.body : {};
const replies = Array.isArray(body.replies) && body.replies.length > 0 ? body.replies : [{}];
spec.body = { ...body, replies };
}
function applyProviderShapeNormalizers(info, spec) {
if (spec.type !== 'json')
return;
const path = info.pathname.toLowerCase();
const host = (info.hostname ?? '').toLowerCase();
if (isOpenAiImagesGeneration(path))
return normalizeOpenAiImages(spec);
if (isGeminiGenerate(host, path))
return normalizeGemini(spec);
const redditKind = isRedditWrite(host, path, info.method);
if (redditKind)
return normalizeReddit(spec, redditKind);
if (isHubspotUpsert(host, path))
return normalizeHubspotUpsert(spec);
if (isGoogleDocsBatchUpdate(host, path))
return normalizeGoogleDocsBatchUpdate(spec);
}
function findProviderShapeViolation(info, body) {
const path = info.pathname.toLowerCase();
const host = (info.hostname ?? '').toLowerCase();
if (isOpenAiImagesGeneration(path))
return openAiImagesViolation(body);
if (isGeminiGenerate(host, path))
return geminiViolation(body);
const redditKind = isRedditWrite(host, path, info.method);
if (redditKind)
return redditViolation(body, redditKind);
if (isHubspotUpsert(host, path))
return hubspotViolation(body);
if (isGoogleDocsBatchUpdate(host, path))
return googleDocsViolation(body);
return undefined;
}
function openAiImagesViolation(body) {
const data = isPlainObject(body) ? body.data : undefined;
if (!Array.isArray(data) || data.length === 0) {
return 'Invalid: OpenAI /v1/images/generations must return a JSON object with a `data` ARRAY of image objects, e.g. `{ "created": <unix>, "data": [{ "b64_json": "..." }] }`. Resubmit with that envelope.';
}
const brokenEntry = data.some((entry) => !isPlainObject(entry) ||
(typeof entry.b64_json !== 'string' && typeof entry.url !== 'string'));
if (brokenEntry) {
return 'Invalid: every OpenAI image `data[]` entry must carry a `b64_json` (base64 string) — the node decodes it. Resubmit with `data: [{ "b64_json": "..." }]`.';
}
return undefined;
}
function geminiViolation(body) {
if (hasGeminiCandidates(body))
return undefined;
return 'Invalid: Google Gemini responses must use the full envelope `{ "candidates": [{ "content": { "parts": [{ "text": "..." }] } }] }` — the node reads candidates[].content.parts[].text. Resubmit wrapped in that shape.';
}
function redditViolation(body, kind) {
const data = isPlainObject(body) && isPlainObject(body.json) ? body.json.data : undefined;
if (!isPlainObject(data)) {
return 'Invalid: Reddit write responses must use the `{ "json": { "errors": [], "data": { ... } } }` envelope — the node reads response.json.data. Resubmit wrapped in that shape.';
}
if (kind === 'comment') {
const things = data.things;
if (!Array.isArray(things) || !isPlainObject(things[0]) || !things[0].data) {
return 'Invalid: Reddit api/comment responses put the comment under `json.data.things[0].data`. Resubmit with `{ "json": { "data": { "things": [{ "kind": "t1", "data": { ... } }] } } }`.';
}
}
return undefined;
}
function hubspotViolation(body) {
const vid = isPlainObject(body) ? body.vid : undefined;
if (typeof vid === 'number' || (typeof vid === 'string' && /^\d+$/.test(vid)))
return undefined;
return 'Invalid: HubSpot contacts/v1 createOrUpdate returns `{ "vid": <number>, "isNew": <bool> }` — the node reads response.vid. Resubmit with a numeric `vid`.';
}
function googleDocsViolation(body) {
const replies = isPlainObject(body) ? body.replies : undefined;
if (Array.isArray(replies) && replies.length > 0)
return undefined;
return 'Invalid: Google Docs batchUpdate returns `{ "documentId": "...", "replies": [ ... ] }` with one reply per request — the node reads response.replies[0]. Resubmit with a non-empty `replies` array.';
}
//# sourceMappingURL=provider-shapes.js.map