
Node.js SDK Guide
A complete, runnable example for integrating Zywrap's V1 offline data (including dynamic schemas) into your Node.js (Express) application.
Step 1: Download Your Data Bundle
Download the V1 ZIP file containing the highly compressed Tabular JSON data. Unzip the zywrap-data.json file from the bundle to use in the import script.
You must be logged in to download the SDK data bundle.
Step 2: Database Setup
Run this SQL. It features the relational use_cases table containing the dynamic schema_data.
-- Database Schema for Zywrap Offline SDK v1.0 (PostgreSQL)
CREATE TABLE "ai_models" (
"code" VARCHAR(255) PRIMARY KEY,
"name" VARCHAR(255) NOT NULL,
"status" BOOLEAN DEFAULT TRUE,
"ordering" INT
);
CREATE TABLE "categories" (
"code" VARCHAR(255) PRIMARY KEY,
"name" VARCHAR(255) NOT NULL,
"status" BOOLEAN DEFAULT TRUE,
"ordering" INT
);
CREATE TABLE "languages" (
"code" VARCHAR(10) PRIMARY KEY,
"name" VARCHAR(255) NOT NULL,
"status" BOOLEAN DEFAULT TRUE,
"ordering" INT
);
CREATE TABLE "use_cases" (
"code" VARCHAR(255) PRIMARY KEY,
"name" VARCHAR(255) NOT NULL,
"description" TEXT,
"category_code" VARCHAR(255) REFERENCES categories(code) ON DELETE SET NULL,
"schema_data" JSONB,
"status" BOOLEAN DEFAULT TRUE,
"ordering" BIGINT
);
CREATE TABLE "wrappers" (
"code" VARCHAR(255) PRIMARY KEY,
"name" VARCHAR(255) NOT NULL,
"description" TEXT,
"use_case_code" VARCHAR(255) REFERENCES use_cases(code) ON DELETE SET NULL,
"featured" BOOLEAN DEFAULT FALSE,
"base" BOOLEAN DEFAULT FALSE,
"status" BOOLEAN DEFAULT TRUE,
"ordering" BIGINT
);
CREATE TABLE "block_templates" (
"type" VARCHAR(50) NOT NULL,
"code" VARCHAR(255) NOT NULL,
"name" VARCHAR(255) NOT NULL,
"status" BOOLEAN DEFAULT TRUE,
PRIMARY KEY ("type", "code")
);
CREATE TABLE "settings" (
"setting_key" VARCHAR(255) PRIMARY KEY,
"setting_value" TEXT
);
CREATE TABLE "usage_logs" (
"id" BIGSERIAL PRIMARY KEY,
"trace_id" VARCHAR(255),
"wrapper_code" VARCHAR(255),
"model_code" VARCHAR(255),
"prompt_tokens" INT DEFAULT 0,
"completion_tokens" INT DEFAULT 0,
"total_tokens" INT DEFAULT 0,
"credits_used" BIGINT DEFAULT 0,
"latency_ms" INT DEFAULT 0,
"status" VARCHAR(50) DEFAULT 'success',
"error_message" TEXT,
"created_at" TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX idx_usage_wrapper ON usage_logs(wrapper_code);
CREATE INDEX idx_usage_model ON usage_logs(model_code);
CREATE INDEX idx_use_case_cat ON use_cases(category_code);
CREATE INDEX idx_wrapper_uc ON wrappers(use_case_code);
Step 3: Database Connection
Save this as db.js. We recommend using the pg package for PostgreSQL.
// FILE: db.js
// Uses the popular 'pg' library for PostgreSQL
// npm install pg
const { Pool } = require('pg');
// Replace with your actual database credentials
const pool = new Pool({
user: 'postgres',
host: 'localhost',
database: 'zywrap_db',
password: 'password',
port: 5432,
});
module.exports = pool;
Step 4: Import Tabular Data
This script parses the compressed V1 JSON and securely bulk-inserts it into your database.
// FILE: import.js
// USAGE: node import.js
// This script assumes you have 'zywrap-data.json' in the same directory.
// Tip: To prevent memory limits on massive JSON files, run with: node --max-old-space-size=4096 import.js
const fs = require('fs/promises');
const pool = require('./db');
// Helper to expand tabular JSON data into arrays of objects
function extractTabular(tabularData) {
if (!tabularData || !tabularData.cols || !tabularData.data) return [];
const cols = tabularData.cols;
return tabularData.data.map(row => {
const obj = {};
cols.forEach((col, i) => obj[col] = row[i]);
return obj;
});
}
async function main() {
console.log('Starting lightning-fast v1.0 data import...');
let data;
try {
const jsonFile = await fs.readFile('zywrap-data.json', 'utf8');
data = JSON.parse(jsonFile);
} catch (e) {
console.error('FATAL: zywrap-data.json not found or invalid.', e.message);
process.exit(1);
}
const client = await pool.connect();
try {
await client.query('BEGIN');
console.log('Clearing tables...');
await client.query('TRUNCATE wrappers, use_cases, categories, languages, block_templates, ai_models, settings RESTART IDENTITY CASCADE');
// 1. Import Categories
if (data.categories) {
for (const c of extractTabular(data.categories)) {
await client.query(
'INSERT INTO categories (code, name, status, ordering) VALUES ($1, $2, TRUE, $3)',
[c.code, c.name, c.ordering ?? 99999]
);
}
console.log('Categories imported successfully.');
}
// 2. Import Use Cases
if (data.useCases) {
for (const uc of extractTabular(data.useCases)) {
const schemaJson = uc.schema ? JSON.stringify(uc.schema) : null;
await client.query(
'INSERT INTO use_cases (code, name, description, category_code, schema_data, status, ordering) VALUES ($1, $2, $3, $4, $5, TRUE, $6)',
[uc.code, uc.name, uc.desc, uc.cat, schemaJson, uc.ordering ?? 999999999]
);
}
console.log('Use Cases imported successfully.');
}
// 3. Import Wrappers
if (data.wrappers) {
for (const w of extractTabular(data.wrappers)) {
await client.query(
'INSERT INTO wrappers (code, name, description, use_case_code, featured, base, status, ordering) VALUES ($1, $2, $3, $4, $5, $6, TRUE, $7)',
[w.code, w.name, w.desc, w.usecase, !!w.featured, !!w.base, w.ordering ?? 999999999]
);
}
console.log('Wrappers imported successfully.');
}
// 4. Import Languages
if (data.languages) {
let ord = 1;
for (const l of extractTabular(data.languages)) {
await client.query(
'INSERT INTO languages (code, name, status, ordering) VALUES ($1, $2, TRUE, $3)',
[l.code, l.name, ord++]
);
}
console.log('Languages imported successfully.');
}
// 5. Import AI Models
if (data.aiModels) {
for (const m of extractTabular(data.aiModels)) {
await client.query(
'INSERT INTO ai_models (code, name, status, ordering) VALUES ($1, $2, TRUE, $3)',
[m.code, m.name, m.ordering ?? 99999]
);
}
console.log('AI Models imported successfully.');
}
// 6. Import Block Templates
if (data.templates) {
for (const [type, tabular] of Object.entries(data.templates)) {
for (const tpl of extractTabular(tabular)) {
await client.query(
'INSERT INTO block_templates (type, code, name, status) VALUES ($1, $2, $3, TRUE)',
[type, tpl.code, tpl.name]
);
}
}
console.log('Block templates imported successfully.');
}
// 7. Store Version
if (data.version) {
await client.query(
"INSERT INTO settings (setting_key, setting_value) VALUES ('data_version', $1) ON CONFLICT (setting_key) DO UPDATE SET setting_value = EXCLUDED.setting_value",
[data.version]
);
console.log('Data version saved to settings table.');
}
await client.query('COMMIT');
console.log(`\n✅ v1.0 Import complete! Version: ${data.version || 'N/A'}`);
} catch (e) {
await client.query('ROLLBACK');
console.error('FATAL: Database error during import. Transaction rolled back.', e.message);
process.exit(1);
} finally {
client.release();
}
}
main();
Step 5: The Dynamic API & Playground
This Express server reads from your local DB, handles dynamic schemas, forwards the final request to the Zywrap API, and logs local usage automatically.
// FILE: app.js
// A simple Express server to replicate the 'api.php' V1 playground backend.
//
// REQUIREMENTS:
// npm install express pg axios cors
//
// USAGE:
// 1. Save this as 'app.js'
// 2. Run: node app.js
// 3. Open 'playground.html' in your browser.
const express = require('express');
const cors = require('cors');
const axios = require('axios');
const pool = require('./db');
const app = express();
const port = 3000;
app.use(cors());
app.use(express.json());
const ZYWRAP_API_KEY = "YOUR_ZYWRAP_API_KEY";
const ZYWRAP_PROXY_URL = 'https://api.zywrap.com/v1/proxy';
// --- Database Helper Functions ---
async function getCategories(client) {
const { rows } = await client.query("SELECT code, name FROM categories WHERE status = TRUE ORDER BY ordering ASC");
return rows;
}
async function getLanguages(client) {
const { rows } = await client.query("SELECT code, name FROM languages WHERE status = TRUE ORDER BY ordering ASC");
return rows;
}
async function getAiModels(client) {
const { rows } = await client.query("SELECT code, name FROM ai_models WHERE status = TRUE ORDER BY ordering ASC");
return rows;
}
async function getBlockTemplates(client) {
const { rows } = await client.query("SELECT type, code, name FROM block_templates WHERE status = TRUE ORDER BY type, name ASC");
const grouped = {};
for (const row of rows) {
if (!grouped[row.type]) grouped[row.type] = [];
grouped[row.type].push({ code: row.code, name: row.name });
}
return grouped;
}
async function getWrappersByCategory(client, categoryCode) {
const { rows } = await client.query(
`SELECT w.code, w.name, w.featured, w.base
FROM wrappers w
JOIN use_cases uc ON w.use_case_code = uc.code
WHERE uc.category_code = $1 AND w.status = TRUE AND uc.status = TRUE
ORDER BY w.ordering ASC`,
[categoryCode]
);
return rows;
}
async function getSchemaByWrapper(client, wrapperCode) {
const { rows } = await client.query(
`SELECT uc.schema_data
FROM use_cases uc
JOIN wrappers w ON w.use_case_code = uc.code
WHERE w.code = $1 AND w.status = TRUE AND uc.status = TRUE`,
[wrapperCode]
);
return rows.length > 0 ? rows[0].schema_data : null;
}
// ✅ HYBRID PROXY EXECUTION
async function executeZywrapProxy(apiKey, model, wrapperCode, prompt, language = null, variables = {}, overrides = {}) {
const payloadData = {
model,
wrapperCodes: [wrapperCode],
prompt,
variables,
source: 'node_sdk'
};
if (language) payloadData.language = language;
if (overrides) Object.assign(payloadData, overrides);
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey.trim()}`,
'User-Agent': 'ZywrapNodeSDK/1.1'
};
try {
const response = await axios.post(ZYWRAP_PROXY_URL, payloadData, {
headers,
responseType: 'text',
timeout: 300000
});
const lines = response.data.split('\n');
let finalJson = null;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith('data: ')) {
const jsonStr = trimmed.substring(6);
try {
const data = JSON.parse(jsonStr);
if (data && (data.output || data.error)) {
finalJson = data;
}
} catch (e) { }
}
}
let statusCode = 200;
if (finalJson && finalJson.error) {
statusCode = 400;
}
return { status: statusCode, data: finalJson || { error: 'Failed to parse streaming response from Zywrap.' } };
} catch (error) {
const status = error.response?.status || 500;
let errorData = { error: error.message };
if (error.response?.data) {
try {
errorData = typeof error.response.data === 'string'
? JSON.parse(error.response.data)
: error.response.data;
} catch (e) {
errorData = { error: error.response.data };
}
}
return { status, data: errorData };
}
}
// --- API Router ---
app.all('/api', async (req, res) => {
const client = await pool.connect();
try {
if (req.method === 'GET') {
const { action, category, wrapper } = req.query;
switch (action) {
case 'get_categories': return res.json(await getCategories(client));
case 'get_languages': return res.json(await getLanguages(client));
case 'get_ai_models': return res.json(await getAiModels(client));
case 'get_block_templates': return res.json(await getBlockTemplates(client));
case 'get_wrappers': return res.json(await getWrappersByCategory(client, category));
case 'get_schema': return res.json(await getSchemaByWrapper(client, wrapper));
default: return res.status(400).json({ error: 'Invalid action' });
}
}
if (req.method === 'POST') {
const { model, wrapperCode, prompt, language, variables, overrides } = req.body;
const action = req.query.action || req.body.action;
if (action === 'execute') {
// ⏱️ Start Local Timer
const startTime = Date.now();
const { data, status } = await executeZywrapProxy(
ZYWRAP_API_KEY, model, wrapperCode || '', prompt, language, variables, overrides
);
// ⏱️ End Local Timer
const latencyMs = Date.now() - startTime;
// --- 📝 LOGGING TO LOCAL DATABASE ---
try {
const statusText = status === 200 ? 'success' : 'error';
const traceId = data.id || null;
const pTokens = data.usage?.prompt_tokens || 0;
const cTokens = data.usage?.completion_tokens || 0;
const tTokens = data.usage?.total_tokens || 0;
const creditsUsed = data.cost?.credits_used || 0;
const rawErrorMsg = statusText === 'error' ? (data.error || 'Unknown Error') : null;
const errMsgStr = typeof rawErrorMsg === 'string' ? rawErrorMsg : JSON.stringify(rawErrorMsg);
const errMsg = errMsgStr ? (errMsgStr.length > 255 ? errMsgStr.substring(0, 255) + '...' : errMsgStr) : null;
await client.query(
`INSERT INTO usage_logs
(trace_id, wrapper_code, model_code, prompt_tokens, completion_tokens, total_tokens, credits_used, latency_ms, status, error_message)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
[traceId, wrapperCode, model || 'default', pTokens, cTokens, tTokens, creditsUsed, latencyMs, statusText, errMsg]
);
} catch (logErr) {
console.error('Failed to write to usage_logs:', logErr.message);
}
return res.status(status).json(data);
}
return res.status(400).json({ error: 'Invalid action' });
}
} catch (e) {
console.error(e);
res.status(500).json({ error: e.message });
} finally {
client.release();
}
});
app.listen(port, () => {
console.log(`Zywrap Node.js SDK Playground backend listening at http://localhost:${port}`);
});
Step 6: Synchronize Data
Run this script on a cron job. It features automatic unzipping for FULL resets and ON CONFLICT optimized upserts for DELTA updates.
// FILE: zywrap-sync.js
// USAGE: node zywrap-sync.js
// DEPENDENCIES: npm install axios pg adm-zip
const axios = require('axios');
const fs = require('fs');
const AdmZip = require('adm-zip');
const { execSync } = require('child_process');
const pool = require('./db');
// --- CONFIGURATION ---
const API_KEY = 'YOUR_ZYWRAP_API_KEY_HERE';
const API_URL = 'https://api.zywrap.com/v1/sdk/v1/sync';
// --- HELPER FUNCTIONS ---
async function upsertBatch(client, tableName, rows, columns, pk = 'code') {
if (!rows.length) return;
const BATCH_SIZE = 1000;
const colNames = columns.map(c => `"${c}"`).join(', ');
const updateCols = columns
.filter(c => c !== pk && c !== 'type')
.map(c => `"${c}" = EXCLUDED."${c}"`)
.join(', ');
const conflictTarget = pk === 'compound_template' ? '(type, code)' : `("${pk}")`;
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
const chunk = rows.slice(i, i + BATCH_SIZE);
const values = [];
const rowPlaceholders = [];
let counter = 1;
for (const row of chunk) {
const rowPh = [];
for (const cell of row) {
rowPh.push(`$${counter++}`);
values.push(cell);
}
rowPlaceholders.push(`(${rowPh.join(', ')})`);
}
const query = `
INSERT INTO "${tableName}" (${colNames})
VALUES ${rowPlaceholders.join(', ')}
ON CONFLICT ${conflictTarget}
DO UPDATE SET ${updateCols}
`;
try {
await client.query(query, values);
} catch (e) {
console.error(`\n [!] Error upserting batch in ${tableName}:`, e.message);
throw e;
}
}
console.log(` [+] Upserted ${rows.length} records into '${tableName}'.`);
}
async function deleteBatch(client, tableName, ids, pk = 'code') {
if (!ids.length) return;
const BATCH_SIZE = 2000;
for (let i = 0; i < ids.length; i += BATCH_SIZE) {
const chunk = ids.slice(i, i + BATCH_SIZE);
await client.query(`DELETE FROM "${tableName}" WHERE "${pk}" = ANY($1)`, [chunk]);
}
console.log(` [-] Deleted ${ids.length} records from '${tableName}'.`);
}
// --- MAIN ---
(async () => {
console.log('--- 🚀 Starting Zywrap V1 Sync ---');
const client = await pool.connect();
try {
const verRes = await client.query("SELECT setting_value FROM settings WHERE setting_key = 'data_version'");
const localVersion = verRes.rows[0]?.setting_value || '';
console.log(`🔹 Local Version: ${localVersion || 'None'}`);
const response = await axios.get(API_URL, {
params: { fromVersion: localVersion },
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
const json = response.data;
console.log(`🔹 Sync Mode: ${json.mode}`);
if (json.mode === 'FULL_RESET') {
const zipPath = 'zywrap-data.zip';
const downloadUrl = json.wrappers.downloadUrl;
console.log(`⬇️ Attempting automatic download from Zywrap...`);
try {
const dl = await axios({
url: downloadUrl,
method: 'GET',
responseType: 'arraybuffer',
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
fs.writeFileSync(zipPath, dl.data);
const mbSize = (fs.statSync(zipPath).size / (1024 * 1024)).toFixed(2);
console.log(`✅ Data bundle downloaded successfully (${mbSize} MB).`);
try {
console.log('📦 Attempting auto-unzip...');
const zip = new AdmZip(zipPath);
zip.extractAllTo(__dirname, true);
console.log('✅ Auto-unzip successful. Running import script...');
fs.unlinkSync(zipPath);
// Run the import script automatically
execSync('node import.js', { stdio: 'inherit' });
} catch (zErr) {
console.log("⚠️ Failed to auto-unzip (Check directory permissions).");
console.log("\n👉 ACTION REQUIRED:");
console.log(` 1. Please manually unzip '${zipPath}' in this folder.`);
console.log(" 2. Then run: node import.js");
}
} catch (dlErr) {
if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
console.log(`❌ Automatic download failed. HTTP Status: ${dlErr.response?.status || 'Unknown'}`);
}
} else if (json.mode === 'DELTA_UPDATE') {
await client.query('BEGIN');
const meta = json.metadata || {};
// Categories
if (meta.categories) {
const rows = meta.categories.map(r => [r.code, r.name, r.status ?? true, r.position || r.displayOrder || r.ordering]);
await upsertBatch(client, 'categories', rows, ['code', 'name', 'status', 'ordering']);
}
// Languages
if (meta.languages) {
const rows = meta.languages.map(r => [r.code, r.name, r.status ?? true, r.ordering]);
await upsertBatch(client, 'languages', rows, ['code', 'name', 'status', 'ordering']);
}
// AI Models
if (meta.aiModels) {
const rows = meta.aiModels.map(r => [r.code, r.name, r.status ?? true, r.displayOrder || r.ordering]);
await upsertBatch(client, 'ai_models', rows, ['code', 'name', 'status', 'ordering']);
}
// Templates
if (meta.templates) {
const rows = [];
for (const [type, items] of Object.entries(meta.templates)) {
for (const i of items) rows.push([type, i.code, i.label || i.name, i.status ?? true]);
}
await upsertBatch(client, 'block_templates', rows, ['type', 'code', 'name', 'status'], 'compound_template');
}
// Use Cases
if (json.useCases?.upserts?.length) {
const rows = json.useCases.upserts.map(uc => [
uc.code, uc.name, uc.description, uc.categoryCode,
uc.schema ? JSON.stringify(uc.schema) : null,
uc.status ?? true, uc.displayOrder || uc.ordering
]);
await upsertBatch(client, 'use_cases', rows, ['code', 'name', 'description', 'category_code', 'schema_data', 'status', 'ordering']);
}
// Wrappers
if (json.wrappers?.upserts?.length) {
const rows = json.wrappers.upserts.map(w => [
w.code, w.name, w.description, w.useCaseCode || w.categoryCode,
!!(w.featured || w.isFeatured), !!(w.base || w.isBaseWrapper),
w.status ?? true, w.displayOrder || w.ordering
]);
await upsertBatch(client, 'wrappers', rows, ['code', 'name', 'description', 'use_case_code', 'featured', 'base', 'status', 'ordering']);
}
// Deletes
if (json.wrappers?.deletes?.length) await deleteBatch(client, 'wrappers', json.wrappers.deletes);
if (json.useCases?.deletes?.length) await deleteBatch(client, 'use_cases', json.useCases.deletes);
if (json.newVersion) {
await client.query("INSERT INTO settings (setting_key, setting_value) VALUES ('data_version', $1) ON CONFLICT (setting_key) DO UPDATE SET setting_value = EXCLUDED.setting_value", [json.newVersion]);
}
await client.query('COMMIT');
console.log('✅ Delta Sync Complete.');
} else {
console.log('✅ No updates needed.');
}
} catch (e) {
if (client) await client.query('ROLLBACK');
console.error('\n❌ Sync Error:', e.message);
} finally {
if (client) client.release();
await pool.end();
}
})();
Programmatically Download
Stream the download directly to disk to prevent memory issues.
// FILE: download-bundle.js
const axios = require('axios');
const fs = require('fs');
const ZYWRAP_API_KEY = 'YOUR_API_KEY';
const API_ENDPOINT = 'https://api.zywrap.com/v1/sdk/v1/download';
const OUTPUT_FILE = './zywrap-data.zip';
async function downloadBundle() {
console.log('Downloading latest V1 wrapper data from Zywrap...');
try {
const response = await axios.get(API_ENDPOINT, {
headers: { 'Authorization': `Bearer ${ZYWRAP_API_KEY}` },
responseType: 'stream'
});
const writer = fs.createWriteStream(OUTPUT_FILE);
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on('finish', () => {
console.log(`✅ Sync complete. Data saved to ${OUTPUT_FILE}.`);
console.log("Run 'unzip zywrap-data.zip' to extract the 'zywrap-data.json' file, then run 'node import.js'.");
resolve();
});
writer.on('error', reject);
response.data.on('error', reject);
});
} catch (error) {
if (fs.existsSync(OUTPUT_FILE)) fs.unlinkSync(OUTPUT_FILE);
console.error(`Error downloading bundle: ${error.response?.status || error.message}`);
}
}
downloadBundle();

