Search This Blog

2025/12/29

SELECT ENGINE

 

/*
====================================================
UNIFIED SQL SELECT ENGINE (v1 + v2 + v3)
====================================================
Author intent: Sangram Desai

DESIGN GOAL
-----------
✔ Never remove existing functionality when adding new
✔ Layer features instead of replacing logic
✔ Single-file, readable, hackable engine

SUPPORTED FEATURES
------------------
• SELECT columns, aliases
• Scalar functions: upper, lower, coalesce
• WHERE (AND conditions)
• INNER JOIN, LEFT JOIN
• GROUP BY + aggregates (sum, count, avg, min, max)
• DISTINCT
• ORDER BY
• LIMIT
• Window functions: row_number, rank, dense_rank
• WITH(...) execution hints:
- PAGINATE
- HEADERCOLUMNUPPERCASE
- OUTPUTJSON
• MySQL-style ASCII table output

====================================================
*/

const readline = require('readline');

// ==================================================
// 1. FUNCTION REGISTRY
// ==================================================
const sqlFunctions = {
upper: ([v]) => String(v ?? '').toUpperCase(),
lower: ([v]) => String(v ?? '').toLowerCase(),
coalesce: (args) => args.find(v => v !== null && v !== undefined && v !== 'NULL' && v !== '') ?? 'NULL',

sum: (vals) => vals.filter(v => v != null && v !== 'NULL').reduce((a, b) => a + Number(b || 0), 0),
count: (vals) => vals.filter(v => v != null && v !== 'NULL').length,
avg: (vals) => {
const v = vals.filter(x => x != null && x !== 'NULL');
return v.length ? v.reduce((a, b) => a + Number(b || 0), 0) / v.length : 0;
},
min: (vals) => {
const v = vals.filter(x => x != null && x !== 'NULL').map(Number);
return v.length ? Math.min(...v) : 'NULL';
},
max: (vals) => {
const v = vals.filter(x => x != null && x !== 'NULL').map(Number);
return v.length ? Math.max(...v) : 'NULL';
}
};

// ==================================================
// 2. HELPERS
// ==================================================
function splitArgs(str) {
let out = [], buf = '', depth = 0;
for (const ch of str) {
if (ch === '(') depth++;
if (ch === ')') depth--;
if (ch === ',' && depth === 0) { out.push(buf.trim()); buf = ''; }
else buf += ch;
}
if (buf) out.push(buf.trim());
return out;
}

function resolvePath(path, scope) {
if (!path) return null;
const parts = path.split('.');
let cur = scope;

for (let p of parts) {
if (cur === scope && (p === 'data' || p === 'root')) continue;
cur = cur?.[p];
}
return cur;
}

function evaluateCondition(row, clause) {
if (!clause) return true;
return clause.split(/\s+and\s+/i).every(part => {
const m = part.match(/(.+?)\s*(=|!=|>|<|>=|<=)\s*(.+)/);
if (!m) return true;
let [, col, op, val] = m;
val = val.replace(/['"]/g, '');
let rv = row[col.trim()];
if (!isNaN(rv) && !isNaN(val)) { rv = Number(rv); val = Number(val); }
if (op === '=') return rv == val;
if (op === '!=') return rv != val;
if (op === '>') return rv > val;
if (op === '<') return rv < val;
if (op === '>=') return rv >= val;
if (op === '<=') return rv <= val;
return true;
});
}

// ==================================================
// 3. PARSER
// ==================================================
function findTop(sql, kw) {
let d = 0; const l = sql.toLowerCase(); kw = kw.toLowerCase();
for (let i = 0; i < sql.length; i++) {
if (sql[i] === '(') d++;
if (sql[i] === ')') d--;
if (d === 0 && l.startsWith(kw, i)) return i;
}
return -1;
}

function parseToken(raw) {
const m = raw.match(/(.+) as (.+)/i);
const expr = m ? m[1].trim() : raw.trim();
const alias = m ? m[2].trim() : expr.split('.').pop();
const isAgg = /^(sum|avg|count|min|max)\(/i.test(expr);
const isWin = /over\s*\(/i.test(expr);
let aggFunc = null, aggCol = null;
if (isAgg) {
const mm = expr.match(/^(\w+)\((.*)\)/);
aggFunc = mm[1].toLowerCase();
aggCol = mm[2] === '*' ? null : mm[2];
}
return { expr, alias, isAgg, aggFunc, aggCol, isWin };
}

function parseSQL(sql) {
const hints = sql.match(/with\s*\(([^)]+)\)/i);
const activeHints = hints ? hints[1].split(',').map(x => x.trim().toLowerCase()) : [];
sql = sql.replace(/with\s*\([^)]+\)/i, '').trim();

const s = findTop(sql, 'select');
const f = findTop(sql, 'from');
const w = findTop(sql, 'where');
const g = findTop(sql, 'group by');
const o = findTop(sql, 'order by');
const l = findTop(sql, 'limit');

const end = (x) => [w, g, o, l].filter(i => i > x).sort((a, b) => a - b)[0] ?? sql.length;

let selectRaw = sql.slice(s + 6, f).trim();
let distinct = false;

if (/^distinct\s+/i.test(selectRaw)) {
distinct = true;
selectRaw = selectRaw.replace(/^distinct\s+/i, '');
}

let depth = 0, buf = '', tokens = [];
for (const ch of selectRaw) {
if (ch === '(') depth++;
if (ch === ')') depth--;
if (ch === ',' && depth === 0) { tokens.push(parseToken(buf)); buf = ''; }
else buf += ch;
}
tokens.push(parseToken(buf));

const fromPart = sql.slice(f + 4, end(f)).trim();
const joinRegex = /(inner|left|right|full)?\s*(outer)?\s*join\s+/ig;
let match, lastIndex = 0;

const base = fromPart.split(joinRegex)[0].trim();
const joinDefs = [];

while ((match = joinRegex.exec(fromPart)) !== null) {
const joinType = (match[1] || 'inner').toLowerCase();
const start = match.index + match[0].length;
const next = joinRegex.exec(fromPart);
const chunk = fromPart.slice(start, next ? next.index : undefined).trim();
if (next) joinRegex.lastIndex = next.index;

const [table, cond] = chunk.split(/\son\s/i);
joinDefs.push({
type: joinType,
table: table.trim(),
cond: cond.trim()
});

if (!next) break;
}


return {
tokens,
base,
joins: joinDefs,
where: w !== -1 ? sql.slice(w + 5, end(w)).trim() : null,
groupBy: g !== -1 ? sql.slice(g + 8, end(g)).trim() : null,
orderBy: o !== -1 ? sql.slice(o + 8, end(o)).trim() : null,
limit: l !== -1 ? Number(sql.slice(l + 5).trim()) : null,
hints: activeHints,
distinct
};
}

// ==================================================
// 4. EXECUTION ENGINE
// ==================================================
async function executeQuery(sql, db) {
const p = parseSQL(sql);
const ar = resolvePath(p.base, db)
let rows = []
if (Array.isArray(ar)) {
rows = resolvePath(p.base, db).map(r => ({ ...r }));
}


// JOIN
for (const j of p.joins) {
const rightData = resolvePath(j.table, db) || [];
const [lKey, rKey] = j.cond.split('=').map(x => x.trim());
const out = [];
const matchedRight = new Set();

rows.forEach(leftRow => {
let matched = false;

rightData.forEach((rightRow, ri) => {
if (String(leftRow[lKey]) === String(rightRow[rKey])) {
matched = true;
matchedRight.add(ri);
out.push({ ...leftRow, ...rightRow });
}
});

if (!matched && (j.type === 'left' || j.type === 'full')) {
out.push({ ...leftRow });
}
});

if (j.type === 'right' || j.type === 'full') {
rightData.forEach((rightRow, ri) => {
if (!matchedRight.has(ri)) {
out.push({ ...rightRow });
}
});
}

rows = out;
}


// WHERE
rows = rows.filter(r => evaluateCondition(r, p.where));

// GROUP BY + AGG
if (p.groupBy) {
const keys = p.groupBy.split(',').map(x => x.trim());
const groups = {};
rows.forEach(r => {
const k = keys.map(c => r[c]).join('|');
groups[k] ??= { row: r, aggs: {} };
p.tokens.filter(t => t.isAgg).forEach(t => {
groups[k].aggs[t.alias] ??= [];
groups[k].aggs[t.alias].push(t.aggCol ? r[t.aggCol] : 1);
});
});
rows = Object.values(groups).map(g => {
const o = { ...g.row };
p.tokens.filter(t => t.isAgg).forEach(t => o[t.alias] = sqlFunctions[t.aggFunc](g.aggs[t.alias]));
return o;
});
}

// ORDER BY
// ORDER BY (numeric + string safe)
if (p.orderBy) {
const [c, d] = p.orderBy.split(/\s+/);
const desc = d?.toUpperCase() === 'DESC';

rows.sort((a, b) => {
const av = a[c];
const bv = b[c];

// numeric sort
if (!isNaN(av) && !isNaN(bv)) {
return desc ? bv - av : av - bv;
}

// string sort (locale-aware)
return desc
? String(bv).localeCompare(String(av))
: String(av).localeCompare(String(bv));
});
}


// LIMIT
if (p.limit) rows = rows.slice(0, p.limit);

// FINAL SELECT
let result = rows.map(r => {
const o = {};
p.tokens.forEach(t => {
const m = t.expr.match(/(\w+)\((.*)\)/);
if (m && sqlFunctions[m[1]]) {
const args = splitArgs(m[2]).map(a => r[a] ?? a.replace(/['"]/g, ''));
o[t.alias] = sqlFunctions[m[1]](args);
} else {
if (t.expr.includes('.')) {
const col = t.expr.split('.').pop();
o[t.alias] = r[col] ?? 'NULL';
} else {
o[t.alias] = r[t.expr] ?? 'NULL';
}
}

});
return o;
});


// DISTINCT (applied after SELECT, before ORDER BY/LIMIT behavior)
if (p.distinct && !p.groupBy) {
const seen = new Set();
result = result.filter(row => {
const key = JSON.stringify(row);
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}


// HINTS
if (p.hints.includes('headercolumnuppercase'))
result = result.map(r => Object.fromEntries(Object.entries(r).map(([k, v]) => [k.toUpperCase(), v])));

if (p.hints.includes('paginate')) await displayWithPager(result);

if (p.hints.includes('outputjson')) return JSON.stringify(result, null, 2);

return result;
}



// ==================================================
// 5. FORMATTER
// ==================================================
async function displayWithPager(allRows, pageSize = 5) {
if (!allRows || allRows.length === 0) {
console.log("Empty set.");
return;
}
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
let currentIndex = 0;
while (currentIndex < allRows.length) {
console.clear();
const page = allRows.slice(currentIndex, currentIndex + pageSize);
const startRow = currentIndex + 1;
const endRow = Math.min(currentIndex + pageSize, allRows.length);

console.log(`\n--- Showing rows ${startRow} to ${endRow} of ${allRows.length} ---`);
console.log(formatAsMySQLTable(page));

if (currentIndex + pageSize >= allRows.length) {
console.log("\n[END OF DATA] Press [Enter] to exit.");
await new Promise(res => rl.question('', res));
break;
}
const answer = await new Promise(res => rl.question('\nPress [Enter] for next page, or type ":q" to quit: ', res));
if (answer.toLowerCase().trim() === ':q') break;
currentIndex += pageSize;
}
rl.close();
console.clear();
}

function formatAsMySQLTable(rows) {
if (!rows.length) return 'Empty set';
const cols = Object.keys(rows[0]);
const w = {}; cols.forEach(c => w[c] = Math.max(c.length, ...rows.map(r => String(r[c]).length)));
const line = () => '+' + cols.map(c => '-'.repeat(w[c] + 2)).join('+') + '+';
const row = v => '| ' + v.map((x, i) => String(x).padEnd(w[cols[i]])).join(' | ') + ' |';
let out = line() + '\n' + row(cols) + '\n' + line() + '\n';
rows.forEach(r => out += row(cols.map(c => r[c])) + '\n');
return out + line();
}

// ==================================================
// 6. TEST CASES
// ==================================================
const db = {
friends: [
{ name: "Chris", city: "New York", countryCode: "USA", "age": 50 },
{ name: "Yuki", city: "Tokyo", countryCode: "JPN", "age": 50 },
{ name: "Emily", city: "Atlanta", countryCode: "USA", "age": 50 },
{ name: "Sato", city: "Tokyo", countryCode: "JPN", "age": 50 },
{ name: "James", city: "New York", countryCode: "USA", "age": 50 },
{ name: "Aiko", city: "Tokyo", countryCode: "JPN", "age": 50 },
{ name: "Sarah", city: "Atlanta", countryCode: "USA", "age": 50 },
{ name: "Kenji", city: "Tokyo", countryCode: "JPN", "age": 50 },
{ name: "John", city: "New York", countryCode: "USA", "age": 50 },
{ name: "Miku", city: "Tokyo", countryCode: "JPN", "age": 50 },
{ name: "Robert", city: "Atlanta", countryCode: "USA", "age": 50 },
{ name: "Hiro", city: "Tokyo", countryCode: "JPN", "age": 50 },
{ name: "Alice", city: "New York", countryCode: "USA", "age": 50 },
{ name: "Takumi", city: "Tokyo", countryCode: "JPN", "age": 50 },
{ name: "Laura", city: "Atlanta", countryCode: "USA", "age": 50 },
{ name: "Mia", city: "LONDON", countryCode: "UK", "age": 50 },
],
cities: [{ cityName: "New York" }, { cityName: "Atlanta" }, { cityName: "Tokyo" }, { cityName: "Bejing" }],
countries: [{ code: "USA", countryName: "United States" }, { code: "JPN", countryName: "Japan" }]
};

// (async()=>{
// const q = `SELECT upper(name) as NAME, cityName, countryName
// FROM friends
// JOIN cities ON city = cityName
// JOIN countries ON countryCode = code
// WHERE cityName='Tokyo'
// WITH(HeaderColumnUpperCase,Paginate);`;

// const res = await executeQuery(q, db);
// if (Array.isArray(res)) console.log(formatAsMySQLTable(res));
// })();

// ===============================
// 6. RUN TESTS (EXTENDED COVERAGE)
// ===============================


(async () => {
console.log("=== TEST 1: Simple SELECT ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT name, city FROM friends",
db
)));

console.log("=== TEST 2: WHERE filter ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT name, city FROM friends WHERE city='Tokyo'",
db
)));

console.log("=== TEST 3: JOIN ===");
console.log(formatAsMySQLTable(await executeQuery(
`SELECT friends.name, cities.cityName
FROM friends
INNER JOIN cities ON city = cityName`,
db
)));

console.log("=== TEST 4: JOIN + WHERE ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT friends.name, countries.countryName FROM friends JOIN countries ON countryCode = code WHERE countryName='Japan'",
db
)));

console.log("=== TEST 5: Scalar function ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT upper(name) AS NAME_UPPER FROM friends",
db
)));

console.log("=== TEST 6: Aggregate + GROUP BY ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT city, count(name) AS total FROM friends GROUP BY city",
db
)));

console.log("=== TEST 7: ORDER BY + LIMIT ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT name, age FROM friends ORDER BY age DESC LIMIT 3",
db
)));

console.log("=== TEST 8: DISTINCT ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT DISTINCT city FROM friends",
db
)));
console.log("=== TEST 8.1: DISTINCT ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT DISTINCT upper(city) AS CITY FROM friends",
db
)));
console.log("=== TEST 8.2: DISTINCT ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT DISTINCT city FROM friends LIMIT 2",
db
)));
console.log("=== TEST 8.3: DISTINCT ===");
console.log(formatAsMySQLTable(await executeQuery(
"SELECT DISTINCT upper(city) FROM friends ORDER BY upper(city)",
db
)));


console.log("=== TEST 9: HINT HeaderColumnUpperCase ===");
console.log(await executeQuery(
"SELECT name, city FROM friends WITH(HeaderColumnUpperCase)",
db
));

console.log("=== TEST 10: HINT OutputJSON ===");
console.log(await executeQuery(
"SELECT name, city FROM friends ",
db
));

console.log("=== TEST 11: ===");
console.log(await executeQuery(
"SELECT code,countryName FROM countries ",
db
));
console.log("=== TEST 12: ===");
console.log(await executeQuery(
"SELECT name, city,age,age*2-2 FROM data.friends WHERE city='Tokyo' LIMIT 1 OFFSET 0",
db
));
console.log("=== TEST 13: ===");
console.log(await executeQuery(
`SELECT name, cityName
FROM friends
LEFT JOIN cities ON city = cityName`,
db
));
console.log("=== TEST 14: ===");
console.log(await executeQuery(
`SELECT name, cityName
FROM friends
RIGHT JOIN cities ON city = cityName`,
db
));
console.log("=== TEST 15:===");
console.log(await executeQuery(
`SELECT name, cityName
FROM friends
FULL OUTER JOIN cities ON city = cityName`,
db
));
})();


2025/12/24

What is HEIC image format ?

What does HEIC mean?

HEIC = High Efficiency Image Container

It’s based on the HEIF (High Efficiency Image File Format) standard and commonly uses HEVC (H.265) compression.

Why HEIC is used

📉 Smaller file size (about 40–50% smaller than JPG)

🖼️ Better image quality at the same size

🎨 Supports 16-bit color (JPG is 8-bit)

🌈 Supports transparency

📸 Can store multiple images (bursts, Live Photos)

🧭 Can store metadata (EXIF, GPS, depth info)

How to Open ?

Linux (Ubuntu)
Install support:

sudo apt install heif-gdk-pixbuf libheif-examples


Then open with Image Viewer, GIMP, etc.

Windows 10/11
HEIF Image Extensions
HEVC Video Extensions (from Microsoft Store)

Convert HEIC to JPG/PNG
On Linux:
heif-convert photo.heic photo.jpg


Or batch convert:
for f in *.heic; do heif-convert "$f" "${f%.heic}.jpg"; done

2025/12/23

Query JSON Object using TSQL (Non Nexted JSON Object Attempt)

Building a Lightweight SQL Engine for In-Memory JSON Data

Have you ever wished you could query your JavaScript objects using standard SQL syntax? While libraries like AlaSQL exist, there is a certain magic (and performance benefit) in building a custom, lightweight execution pipeline tailored to your specific data needs.

In this post, I’ll walk through the architecture of JSON-SQLParser, a project I developed to bring the power of SQL—including nested functions, math libraries, and complex filtering—directly to JSON data structures.
The Challenge

Most in-memory filtering involves messy, nested .filter() and .map() chains that are hard to read and even harder to change dynamically. I wanted a way to pass a string like:
SQL

SELECT name, lpad(name, 10, '.') AS padded, age
FROM data.$friends
WHERE age > 25 AND contains(city, 'New')

And have it return a beautifully formatted result set.
The Architecture: A 3-Step Pipeline

The engine doesn't just "run" the string; it follows a professional compiler-style pipeline:
1. The Parser (Tokenization)

First, the engine breaks the SQL string into "Tokens." It identifies what is a column, what is a function call, and what is an alias (using the AS keyword).

Nested Support: The parser uses a stack-based approach to handle nested functions like upper(substr(name, 1, 3)).

2. The Execution Plan (The Blueprint)

Instead of executing on the fly, the engine builds a Plan. This is a series of steps that calculate intermediate "temporary variables" (like $t0, $t1). This prevents redundant calculations and allows us to handle complex dependencies in the SELECT clause.
3. The Executor (Virtual Machine)

The executor takes the Plan and the Data. It uses a custom evaluateExpression function that safely maps SQL logic into JavaScript operations. It handles:

Math Library: Automatic integration of the JS Math object (sqrt, sin, log, etc.).
String Utilities: Custom implementations of lpad, trim, instr, and contains.

Key Features
🚀 Case-Insensitive String Searching

In standard JS, .includes() is case-sensitive. My engine's contains() and instr() functions use .toLocaleLowerCase() internally, making queries more robust and user-friendly.
🧮 Dynamic Math Integration

By dynamically reducing the JavaScript Math object, the engine supports dozens of mathematical functions out-of-the-box without manual boilerplate code.
🔍 Advanced WHERE Logic

The engine translates SQL’s AND, OR, and NOT into JavaScript’s &&, ||, and !. This allows for complex conditional filtering that feels exactly like writing a Postgres or MySQL query.
Putting it to the Test

Using a sample dataset of "friends," we ran a Master Query to stress-test the pipeline:
JavaScript

let sqlQuery = `select
name,
lpad(name, 12, '-') AS formatted_name,
age,
instr(city, 'New') AS city_index,
upper(city) AS location
from data.$friends
where contains(city, 'man') OR (age > 20 AND length(name) <= 5)`;

The Result:

The output is rendered in a classic MySQL-style CLI table, proving that the aliasing and formatting logic work in harmony:

+----------+----------------+-----+------------+-----------+
name | formatted_name | age | city_index | location |
+----------+----------------+-----+------------+-----------+
| Joe | ---------Joe | 32 | 1 | NEW YORK |
| Robert | ------Robert | 45 | 0 | MANHATTAN |
+----------+----------------+-----+------------+-----------+

What's Next?

The current version of JSON-SQLParser is a strong proof-of-concept for high-performance in-memory querying. Future updates will focus on:

ORDER BY: To allow sorting of the result sets.

LIMIT: For pagination support.

JOINs: To link multiple JSON arrays together.

Building this engine was a deep dive into string parsing and execution logic. It proves that with just a few hundred lines of vanilla JavaScript, you can create powerful data tools that bridge the gap between SQL and JSON.

Check out the full source code here: GitHub - JSON-SQLParser
https://github.com/gitsangramdesai/JSON-SQLParser
This code achieves:

Flexible Parsing: Dynamically extracts columns and functions even when nested deep.

Aliasing: Supports clean, user-defined headers using AS.
 
Powerful Filtering: The WHERE clause handles complex logic and string searching.

Extensible Functions: Adding a new function is as simple as adding a key-value pair to the sqlFunctions registry.

Professional Output: The built-in formatter produces a standard CLI-style database table.

If you found this project interesting, feel free to star the repo or reach out with suggestions!

2025/12/16

Adaptation of Sudoku Solver for Node.js

Instead of cell wise backtracking it does box wise back tracking,code work
better in scenarios where solution is unique & board is harder as human.

Below is code that is generated by using chat gpt with my own logic thanks to
chatgpt for fixing my code else it might have taken month to perfect for
such complicated task.

// =======================
// PARALLEL SUDOKU SOLVER USING ROW PERMUTATION GENERATOR + WORKERS
// =======================

const { Worker, isMainThread, parentPort, workerData } = require("worker_threads");
const os = require("os");

const GRID_SIZE = 9;
const oneToNine = [1,2,3,4,5,6,7,8,9];

let board1 = [
[7, 0, 2, 0, 5, 0, 6, 0, 0],
[0, 0, 0, 0, 0, 3, 0, 0, 0],
[1, 0, 0, 0, 0, 9, 5, 0, 0],
[8, 0, 0, 0, 0, 0, 0, 9, 0],
[0, 4, 3, 0, 0, 0, 7, 5, 0],
[0, 9, 0, 0, 0, 0, 0, 0, 8],
[0, 0, 9, 7, 0, 0, 0, 0, 5],
[0, 0, 0, 2, 0, 0, 0, 0, 0],
[0, 0, 7, 0, 4, 0, 2, 0, 3]
];

let board2 = [
[0, 6, 0, 1, 0, 4, 0, 5, 0],
[0, 0, 8, 3, 0, 5, 6, 0, 0],
[2, 0, 0, 0, 0, 0, 0, 0, 1],
[8, 0, 0, 4, 0, 7, 0, 0, 6],
[0, 0, 6, 0, 0, 0, 3, 0, 0],
[7, 0, 0, 9, 0, 1, 0, 0, 4],
[5, 0, 0, 0, 0, 0, 0, 0, 2],
[0, 0, 7, 2, 0, 6, 9, 0, 0],
[0, 4, 0, 5, 0, 8, 0, 7, 0]
];

let board3 = [
[0, 0, 0, 6, 0, 0, 4, 0, 0],
[7, 0, 0, 0, 0, 3, 6, 0, 0],
[0, 0, 0, 0, 9, 1, 0, 8, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 5, 0, 1, 8, 0, 0, 0, 3],
[0, 0, 0, 3, 0, 6, 0, 4, 5],
[0, 4, 0, 2, 0, 0, 0, 6, 0],
[9, 0, 3, 0, 0, 0, 0, 0, 0],
[0, 2, 0, 0, 0, 0, 1, 0, 0]
];

let board4 = [
[0, 0, 0, 0, 0, 6, 0, 8, 0],
[0, 0, 0, 0, 7, 0, 0, 0, 9],
[9, 0, 0, 3, 0, 0, 0, 0, 0],
[0, 0, 7, 0, 0, 0, 2, 0, 0],
[0, 8, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 3, 0, 0, 0, 7, 0, 0],
[0, 0, 0, 0, 0, 5, 0, 0, 8],
[1, 0, 0, 0, 9, 0, 0, 0, 0],
[0, 4, 0, 8, 0, 0, 0, 0, 0]
];


let board5 = [
[2, 0, 0, 0, 8, 0, 3, 0, 0],
[0, 6, 0, 0, 7, 0, 0, 8, 4],
[0, 3, 0, 5, 0, 0, 2, 0, 9],
[0, 0, 0, 1, 0, 5, 0, 0, 0],
[8, 0, 0, 0, 0, 0, 0, 0, 7],
[0, 0, 0, 6, 0, 2, 0, 0, 0],
[4, 0, 2, 0, 0, 7, 0, 6, 0],
[6, 8, 0, 0, 2, 0, 0, 7, 0],
[0, 0, 5, 0, 1, 0, 0, 0, 3]
];

let board6 = [
[0, 0, 4, 0, 0, 0, 8, 0, 5],
[0, 3, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 7, 0, 0, 0, 3, 1],
[0, 0, 0, 0, 0, 0, 0, 1, 0],
[9, 0, 0, 0, 6, 0, 0, 0, 4],
[0, 8, 0, 0, 0, 0, 0, 0, 0],
[1, 7, 0, 0, 0, 5, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 7, 0],
[5, 0, 0, 0, 0, 0, 4, 0, 0]
];

let board7 = [
[0, 2, 0, 6, 0, 8, 0, 0, 0],
[5, 8, 0, 0, 0, 9, 7, 0, 0],
[0, 0, 0, 0, 4, 0, 0, 0, 0],
[3, 7, 0, 0, 0, 0, 5, 0, 0],
[6, 0, 0, 0, 0, 0, 0, 0, 4],
[0, 0, 8, 0, 0, 0, 0, 1, 3],
[0, 0, 0, 0, 2, 0, 0, 0, 0],
[0, 0, 9, 8, 0, 0, 0, 3, 6],
[0, 0, 0, 3, 0, 6, 0, 9, 0]
];

let board8 = [
[0, 0, 0, 0, 0, 0, 0, 1, 2],
[0, 0, 6, 0, 0, 0, 0, 0, 0],
[0, 9, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 5, 0, 0, 4, 0, 0],
[0, 7, 0, 0, 0, 0, 0, 6, 0],
[0, 0, 3, 0, 0, 8, 0, 0, 0],
[0, 0, 0, 8, 0, 0, 0, 4, 0],
[0, 0, 0, 0, 0, 0, 7, 0, 0],
[6, 4, 0, 0, 0, 0, 0, 0, 0]
];

let board9 = [
[0, 0, 5, 0, 0, 0, 0, 7, 0],
[0, 7, 0, 0, 1, 0, 0, 0, 9],
[0, 0, 0, 6, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 4, 0, 0, 0],
[4, 0, 0, 0, 0, 0, 0, 0, 6],
[0, 0, 0, 9, 0, 0, 0, 0, 0],
[0, 0, 9, 0, 0, 1, 0, 0, 0],
[8, 0, 0, 0, 6, 0, 0, 4, 0],
[0, 1, 0, 0, 0, 0, 7, 0, 0]
];

//hard
let board10 = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 3, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 9, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 2, 0, 0],
[0, 0, 0, 8, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 6, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 7, 0],
[0, 0, 0, 2, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 5]
];

let board11 = [
[0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 2],
[0, 0, 0, 0, 3, 5, 0, 0, 0],
[0, 0, 0, 7, 0, 0, 0, 0, 0],
[0, 0, 4, 0, 0, 0, 1, 0, 0],
[0, 9, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 5, 0, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 6, 0, 0]
];

let board12 = [
[0, 0, 0, 0, 0, 0, 0, 0, 4],
[0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 6, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 2, 0, 0, 0, 0],
[0, 5, 0, 0, 0, 0, 0, 6, 0],
[0, 0, 0, 0, 0, 7, 0, 0, 0],
[0, 0, 0, 0, 8, 0, 0, 0, 0],
[3, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 9, 0, 0]
];

let board13 = [
[0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 2, 0, 0, 0],
[0, 0, 0, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 4, 0, 0, 0, 0],
[0, 0, 5, 0, 0, 0, 6, 0, 0],
[0, 0, 0, 0, 0, 7, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 3, 0],
[0, 0, 8, 0, 0, 0, 0, 0, 0],
[2, 0, 0, 0, 0, 0, 0, 0, 0]
];

let board = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 2, 0, 0, 0, 0],
[0, 0, 0, 3, 0, 0, 0, 0, 0],
[0, 4, 0, 0, 0, 0, 0, 5, 0],
[0, 0, 0, 0, 0, 6, 0, 0, 0],
[0, 0, 0, 0, 7, 0, 0, 0, 0],
[8, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 9]
];

// -----------------------
// HELPERS
// -----------------------
function getColumn(board, col) {
return board.map(row => row[col]);
}

function getBox(board, row, col) {
const br = row - (row % 3);
const bc = col - (col % 3);
const box = [];
for (let r = br; r < br + 3; r++) {
for (let c = bc; c < bc + 3; c++) {
box.push(board[r][c]);
}
}
return box;
}

function isValidPartial(arr) {
const seen = new Set();
for (const n of arr) {
if (n === 0) continue;
if (seen.has(n)) return false;
seen.add(n);
}
return true;
}

function analyzeRow(row) {
const zeroIndexes = [];
const existing = new Set();
row.forEach((v,i)=>{
if(v===0) zeroIndexes.push(i);
else existing.add(v);
});
const missing = oneToNine.filter(n=>!existing.has(n));
return { zeroIndexes, missing };
}

function* permute(arr, path = [], used = []) {
if(path.length === arr.length) { yield [...path]; return; }
for(let i=0;i<arr.length;i++){
if(used[i]) continue;
used[i] = true;
path.push(arr[i]);
yield* permute(arr, path, used);
path.pop();
used[i] = false;
}
}

function* rowCandidateGenerator(row) {
const { zeroIndexes, missing } = analyzeRow(row);
if(missing.length===0) { yield row.slice(); return; }
for(const perm of permute(missing)){
const newRow = row.slice();
perm.forEach((val,i)=>newRow[zeroIndexes[i]]=val);
yield newRow;
}
}

// -----------------------
// BACKTRACK USING ROW PERM GENERATOR
// -----------------------
function solveUsingRowPermGen(board) {
function backtrack(rowIndex){
if(rowIndex===9) return true;

for(const candidateRow of rowCandidateGenerator(board[rowIndex])){
board[rowIndex] = candidateRow;

let valid = true;
for(let c=0;c<9&&valid;c++){
if(!isValidPartial(getColumn(board,c))) valid=false;
}
for(let r=0;r<=rowIndex&&valid;r++){
for(let c=0;c<9&&valid;c++){
if(!isValidPartial(getBox(board,r,c))) valid=false;
}
}

if(!valid){ board[rowIndex]=Array(9).fill(0); continue; }

if(backtrack(rowIndex+1)) return true;
board[rowIndex]=Array(9).fill(0);
}
return false;
}

return backtrack(0) ? board : null;
}

// -----------------------
// MULTI-THREADING SETUP
// -----------------------
if(isMainThread){
console.time("SudokuSolver");

const numCPUs = os.cpus().length;
const firstRowCandidates = [...rowCandidateGenerator(board[0])];
const chunkSize = Math.ceil(firstRowCandidates.length/numCPUs);

let solved = null;
let completedWorkers = 0;

for(let i=0;i<numCPUs;i++){
const chunk = firstRowCandidates.slice(i*chunkSize, (i+1)*chunkSize);
const worker = new Worker(__filename,{workerData:{board, firstRowChunk:chunk}});

worker.on("message", msg=>{
if(msg.solved && !solved){
solved = msg.board;
console.timeEnd("SudokuSolver");
console.log("Solved Board:");
console.table(solved);
process.exit(0);
}
});

worker.on("exit", ()=>{completedWorkers++; if(completedWorkers===numCPUs && !solved){
console.log("No solution found");
console.timeEnd("SudokuSolver");
}});
}

}else{
const boardCopy = JSON.parse(JSON.stringify(workerData.board));
const chunk = workerData.firstRowChunk;

for(const firstRow of chunk){
boardCopy[0] = firstRow;
const solved = solveUsingRowPermGen(boardCopy.map(r=>r.slice()));
if(solved){
parentPort.postMessage({solved:true, board:solved});
break;
}
}
}

If you are interested in ubuntu app check my github link
https://github.com/gitsangramdesai/deltaCharm