getreadystack's picture
Upload index.html with huggingface_hub
da0105e verified
Raw History Blame Contribute Delete
41.6 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PowerSync Migration 2026 — check your code for free</title>
<meta name="description" content="This free tool finds Realm Sync, Atlas Data API and HTTPS Endpoint calls and shows the PowerSync replacement.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/powersync-migration-check"><style data-yj-mfit="1">#out{max-width:100%}#out td,#out th{overflow-wrap:anywhere}#out pre,#out code{white-space:pre-wrap;overflow-wrap:anywhere}@media (max-width:640px){table{display:block;max-width:100%;overflow-x:auto}pre{max-width:100%;overflow-x:auto}code,a{overflow-wrap:anywhere}img,svg,video,canvas,iframe{max-width:100%}textarea,input,select{max-width:100%;box-sizing:border-box}.yj-lgbar{position:static!important;justify-content:flex-end;margin:0 0 10px}#out table:not(:has(tr>:nth-child(4))){display:table;table-layout:fixed;width:100%}#out table:has(tr>:nth-child(4)),#out table:has(tr>:nth-child(4)) tbody,#out table:has(tr>:nth-child(4)) tr,#out table:has(tr>:nth-child(4)) td{display:block;width:auto;overflow:visible}#out table:has(tr>:nth-child(4)) thead{display:none}#out table:has(tr>:nth-child(4)) tr{padding:10px 0;border-bottom:1px solid rgba(128,128,128,.28)}#out table:has(tr>:nth-child(4)) td{border:0;padding:1px 0}#out table:has(tr>:nth-child(4)) td:nth-child(-n+3){display:inline-block;margin-right:10px;font-size:12px;opacity:.78}}</style>
</head>
<body>
<div class="wrap">
<h1>PowerSync Migration 2026 — check your code for free</h1>
<p class="lede">Finds MongoDB Atlas Device Sync (Realm Sync), Atlas Data API and HTTPS Endpoint calls in JS/TS, Swift, Kotlin and Dart that stopped at the 2025-09-30 end-of-life, with the PowerSync replacement. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">// orders.ts — offline order list, written for Atlas Device Sync in 2023
import * as Realm from 'realm-web';
const app = new Realm.App({ id: 'orders-abcde' });
export async function loadOrders(customerId: string) {
const user = await app.logIn(Realm.Credentials.anonymous());
const mongo = user.mongoClient('mongodb-atlas');
const recent = await mongo.db('shop').collection('orders').find({ customerId });
return recent;
}
const DATA_API = 'https://data.mongodb-api.com/app/orders-abcde/endpoint/data/v1';
export async function openOrders(token: string) {
const res = await fetch(`${DATA_API}/action/find`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ dataSource: 'Cluster0', database: 'shop', collection: 'orders', filter: { status: 'open' } }),
});
return (await res.json()).documents;
}</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Scan the open file: every Realm Sync, Atlas Data API and HTTPS Endpoint call, its line, and the PowerSync replacement.. <strong>In the editor:</strong> Scan the whole workspace and save one Markdown migration report (file, line, dead call, PowerSync replacement) to hand to your team. — that part asks for a licence key.</div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var PSM_RULES = [
{"id": "RS01", "name": "realm-web SDK import", "re": "\\bfrom\\s+['\"]realm-web['\"]|require\\(\\s*['\"]realm-web['\"]\\s*\\)", "what": "realm-web is the Atlas Device SDK for the browser; its App Services login and sync path reached end-of-life", "fix": "import { PowerSyncDatabase } from '@powersync/web' (React Native: '@powersync/react-native')"},
{"id": "RS02", "name": "Realm.App client init", "re": "\\bnew\\s+Realm\\.App\\s*\\(|\\bRealm\\.App\\.getApp\\s*\\(|\\bnew\\s+App\\s*\\(\\s*\\{\\s*id\\s*:", "what": "Realm.App({ id }) points the client at an Atlas App Services app ID that no longer serves Device Sync", "fix": "new PowerSyncDatabase({ schema, database: { dbFilename } }) then db.connect(connector)"},
{"id": "RS03", "name": "Flexible Sync configuration", "re": "\\bflexible\\s*:\\s*true\\b|\\.flexibleSyncConfiguration\\s*\\(|Configuration\\.flexibleSync\\s*\\(|SyncConfiguration\\.Builder\\s*\\(", "what": "Flexible Sync config opens a Device Sync session", "fix": "move the query into PowerSync Sync Rules (bucket_definitions: parameters + data queries)"},
{"id": "RS04", "name": "Flexible Sync subscriptions", "re": "\\.subscriptions\\.update\\s*\\(|\\.subscriptions\\.add\\s*\\(|\\bmutableSubs\\.add\\s*\\(", "what": "client-side sync subscriptions are a Device Sync feature", "fix": "declare what each user receives server-side in PowerSync Sync Rules; read locally with db.getAll('SELECT ...')"},
{"id": "RS05", "name": "Partition-based sync", "re": "\\bpartitionValue\\b", "what": "partitionValue is legacy partition-based Device Sync", "fix": "turn the partition key into a PowerSync bucket parameter (e.g. request.user_id())"},
{"id": "RS06", "name": "Atlas Data API base URL", "re": "data\\.mongodb-api\\.com/app/[^'\"`\\s/]+/endpoint/data/v1", "flags": "i", "what": "the Atlas Data API endpoint reached end-of-life", "fix": "call your own backend (MongoDB driver) instead, or read the synced local copy through PowerSync"},
{"id": "RS07", "name": "Custom HTTPS Endpoint URL", "re": "data\\.mongodb-api\\.com/app/[^'\"`\\s/]+/endpoint/(?!data/v1)", "flags": "i", "what": "App Services custom HTTPS Endpoints reached end-of-life", "fix": "host the route on your own API; PowerSync writes go through connector.uploadData()"},
{"id": "RS08", "name": "Data API key header", "re": "['\"]api-key['\"]\\s*:", "when": "mongodb-api\\.com", "what": "an api-key header for the Data API (a server secret in client code)", "fix": "drop it; PowerSync clients authenticate with a short-lived JWT from connector.fetchCredentials()"},
{"id": "RS09", "name": "Data API action call", "re": "/action/(?:findOne|find|insertOne|insertMany|updateOne|updateMany|deleteOne|deleteMany|replaceOne|aggregate)\\b", "when": "mongodb-api\\.com|dataSource", "what": "a Data API action request (find/insert/update/delete/aggregate)", "fix": "reads: db.getAll / db.watch on the local SQLite copy; writes: db.execute, uploaded by connector.uploadData()"},
{"id": "RS10", "name": "App Services login credentials", "re": "\\bRealm\\.Credentials\\.|\\bCredentials\\.(?:anonymous|emailPassword|jwt|apiKey|google|apple|facebook|function)\\s*\\(", "what": "App Services authentication through the Device SDK", "fix": "keep your own auth (Auth0, Supabase, Firebase, custom JWT) and return its token from fetchCredentials()"},
{"id": "RS11", "name": "Remote mongoClient", "re": "\\.mongoClient\\s*\\(", "what": "user.mongoClient() queries Atlas through App Services from the device", "fix": "query the local database with db.getAll('SELECT ...'); server-side changes arrive through Sync Rules"},
{"id": "RS12", "name": "Function call through the SDK", "re": "\\b\\w*[uU]ser\\??\\.functions\\.|\\.callFunction\\s*\\(", "what": "calling an App Services function through the Device SDK client", "fix": "expose the logic as an endpoint on your own backend and call it from the app or from uploadData()"},
{"id": "RS13", "name": "@realm/react sync providers", "re": "\u003c\\s*(?:AppProvider|UserProvider)\\b|\\bfrom\\s+['\"]@realm/react['\"]", "what": "@realm/react AppProvider/UserProvider wire the app to App Services", "fix": "\u003cPowerSyncContext.Provider value={db}\u003e and useQuery() from '@powersync/react'"},
{"id": "RS14", "name": "Sync session control", "re": "\\.syncSession\\b|\\buploadAllLocalChanges\\s*\\(|\\bdownloadAllServerChanges\\s*\\(", "what": "Device Sync session control (pause/resume/upload/download)", "fix": "db.connect(connector) / db.disconnect(); wait with db.waitForFirstSync(); status in db.currentStatus"},
{"id": "RS15", "name": "Native Realm sync SDK", "re": "^\\s*import\\s+RealmSwift\\b|io\\.realm\\.kotlin\\.mongodb|package:realm/realm\\.dart", "when": "flexibleSync|SyncConfiguration|syncSession|logIn|Credentials|App\\s*\\(\\s*id\\s*:", "what": "a Swift, Kotlin or Dart Realm SDK used with Device Sync", "fix": "PowerSync Swift SDK, Kotlin Multiplatform SDK (com.powersync) or package:powersync/powersync.dart"},
{"id": "RS16", "name": "App Services client API URL", "re": "(?:realm|services\\.cloud)\\.mongodb\\.com/api/client/v2\\.0/app/", "flags": "i", "what": "the App Services client API that Device SDK login and GraphQL traffic used", "fix": "point the app at your own auth and API; the PowerSync endpoint comes from fetchCredentials()"}
]
;</script>
<script>/* PowerSync Migration Check engine: finds MongoDB Atlas Device Sync (Realm Sync),
Atlas Data API and HTTPS Endpoint calls and names the PowerSync replacement. */
(function () {
var RULES = (typeof module !== 'undefined' && typeof require !== 'undefined') ? require('./rules.json') : window.PSM_RULES;
var EOL = '2025-09-30';
function dayNum(s) {
var m = String(s || '').match(/(\d{4})-(\d{2})-(\d{2})/);
if (!m) return null;
var t = Date.UTC(+m[1], +m[2] - 1, +m[3]);
return isNaN(t) ? null : Math.round(t / 86400000);
}
function clock(today) {
var t = dayNum(today), e = dayNum(EOL);
if (t === null) return { sev: 'error', tail: ' End-of-life ' + EOL + '.' };
var d = t - e;
if (d >= 0) return { sev: 'error', tail: ' Stopped ' + EOL + ' (' + d + ' days before ' + today.slice(0, 10) + ').' };
return { sev: 'warn', tail: ' Stops ' + EOL + ' (' + (-d) + ' days left).' };
}
function check(text, opts) {
opts = opts || {};
var src = String(text || '');
var flat = src.replace(/\s+/g, ' ');
var c = clock(opts.today);
var lines = src.split(/\r?\n/);
var findings = [];
RULES.forEach(function (r) {
if (r.when && !new RegExp(r.when).test(flat)) return;
var re = new RegExp(r.re, r.flags || '');
for (var i = 0; i < lines.length; i++) {
var ln = lines[i];
if (/^\s*(\/\/|\/\*|\*)/.test(ln)) continue;
if (re.test(ln)) {
findings.push({ check: r.id, sev: c.sev, line: i + 1,
msg: r.id + ' ' + r.name + ': ' + r.what + '. PowerSync: ' + r.fix + '.' + c.tail });
}
}
});
findings.sort(function (a, b) { return a.line - b.line; });
return { findings: findings };
}
var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
if (typeof module !== 'undefined' && module.exports) module.exports = API;
if (typeof window !== 'undefined') window.PSMENGINE = API;
})();
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
const f = (res && res.findings) || [];
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
return lines.join('\n');
}
function toHtml(res) {
const f = (res && res.findings) || [];
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var PSMREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = PSMREPORT;
else window.PSMREPORT = PSMREPORT;
</script>
<script>
(function(){
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
today.value=new Date().toISOString().slice(0,10);
function run(){ try { var res=window.PSMENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.PSMREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
go.onclick=run; if (src.value.trim()) run();
})();
</script>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://open-vsx.org/extension/readystack/powersync-migration-check" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'powersync-migration-check',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind PowerSync Migration Check: Realm Sync &amp; Data API every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"powersync-migration-check",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);document.addEventListener("click",function(ev){var b=ev.target&&ev.target.closest?ev.target.closest("button,input[type=submit]"):null;if(b&&!b.closest("[data-yj],[data-yj-cta],[data-yj-install],[data-yj-alert],.yj-find,.yj-related,#yj-ask,.yjp"))mv(ev);},true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the full version <span class="yj-price">$29</span></h2><p class="yj-cta-paid" style="margin:.25rem 0 .6rem;color:#1f2a37">Scan the whole workspace and save one Markdown migration report (file, line, dead call, PowerSync replacement) to hand to your team.</p><p class="yj-cta-pay" style="margin:.25rem 0 1rem;color:#5b6b7c;font-size:.94rem">One payment, one licence key for this tool. The key is shown right after payment.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_eNviNWzYdaK2eneTfI70dcBnrTGWMNcqlCP2R2VSrHs" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div><section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-30">Updated 2026-09-30</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-30","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku394490_result_card.jpg" alt="Migrate from MongoDB Atlas Device Sync to PowerSync: find the dead calls first" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>PowerSync migration: 6 findings in one 22-line orders.ts, which is what a React Native, web, Swift, Kotlin or Flutter developer still on MongoDB Realm Sync gets from the free check before writing a line of PowerSync code.</p>
<h3>What MongoDB changed, and when</h3>
<p>In September 2024 MongoDB deprecated Atlas Device Sync, the sync side of the Atlas Device SDKs (Realm), the Atlas Data API and custom HTTPS Endpoints. All of them reached end-of-life on 2025-09-30. Since that day a client that logs in through Realm.App, opens a Flexible Sync session or posts to data.mongodb-api.com gets no sync. PowerSync&#x27;s MongoDB module reached general availability in March 2025 and is the path MongoDB pointed Device Sync users to.</p>
<p>The local database is a different story. The Realm SDKs stayed open source, and a local-only Realm still opens and queries. That is why the first job of a migration is a list: which lines are dead, and which ones are fine.</p>
<h3>The sample file</h3>
<p>The sample is a small offline order list written for Device Sync:</p>
<pre><code>import * as Realm from &#x27;realm-web&#x27;;
const app = new Realm.App({ id: &#x27;orders-abcde&#x27; });
const user = await app.logIn(Realm.Credentials.anonymous());
const mongo = user.mongoClient(&#x27;mongodb-atlas&#x27;);
const DATA_API = &#x27;https://data.mongodb-api.com/app/orders-abcde/endpoint/data/v1&#x27;;
const res = await fetch(`${DATA_API}/action/find`, { ... });</code></pre>
<p>The check returns 6 findings, one per line above:</p>
<ol><li>RS01 realm-web import: replace with @powersync/web (or @powersync/react-native).</li><li>RS02 new Realm.App: replace with new PowerSyncDatabase({ schema, database: { dbFilename } }) and db.connect(connector).</li><li>RS10 Realm.Credentials.anonymous(): keep your own auth provider and return its JWT from fetchCredentials().</li><li>RS11 user.mongoClient(&#x27;mongodb-atlas&#x27;): query the local SQLite copy with db.getAll(&#x27;SELECT ...&#x27;).</li><li>RS06 the Data API base URL: call your own backend with the MongoDB driver.</li><li>RS09 /action/find: reads become db.getAll or db.watch, writes become db.execute, uploaded by connector.uploadData().</li></ol>
<p>Each message ends with the clock. With the date set to 2026-09-29 it reads &quot;Stopped 2025-09-30 (364 days before 2026-09-29)&quot;. With a date before the end-of-life the same finding is a warning with the days left.</p>
<h3>Why not grep for realm</h3>
<p>A grep for realm flags every schema class and every local query you can keep. The 16 rules fire only on the paths that stopped: realm-web and the native sync SDKs used with sync, Realm.App, Realm.Credentials, mongoClient, Flexible Sync configuration, subscriptions, partitionValue, syncSession, user.functions, @realm/react AppProvider and UserProvider, the Data API base URL, Data API actions, the api-key header, custom HTTPS Endpoints and the App Services client API URL. Comment lines are skipped, and a clean PowerSync version of the same file returns 0 findings.</p>
<h3>What the PowerSync side looks like</h3>
<p>The replacement for the sample is one PowerSyncDatabase with a Schema of Tables, a connector with fetchCredentials() returning your endpoint and JWT, and uploadData() sending each CRUD transaction to your own API. Which rows each user receives moves from client subscriptions into Sync Rules on the server.</p>
<h3>Try it</h3>
<p>The check has 2 inputs: the file text and a date. Paste a file into the web version or open it in VS Code; all 16 rules run on it with no key. The full version scans a whole workspace and writes one Markdown migration report for the team.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku394490_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku394490_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does PowerSync Migration Check do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It scans JavaScript, TypeScript, Swift, Kotlin and Dart files for MongoDB Atlas Device Sync (Realm Sync), Atlas Data API and HTTPS Endpoint calls, which reached end-of-life on 2025-09-30. It has 16 rules. Each finding gives the line, the dead call and the PowerSync replacement, such as PowerSyncDatabase, fetchCredentials, uploadData or Sync Rules. On the sample orders.ts it reports 6 findings.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It is for mobile and web developers whose offline-first app was built on MongoDB Realm Sync or the Atlas Data API and now has to move to PowerSync. That includes React Native, web (realm-web), Swift, Kotlin and Flutter/Dart teams. It also suits a tech lead who needs a list of every call to replace before estimating the migration.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just grep for realm or ask a chatbot?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Local-only Realm databases still work, because the SDKs stayed open source. A grep for realm therefore flags every schema and query you can keep. These 16 rules only fire on the sync, Data API, HTTPS Endpoint and App Services login paths that stopped on 2025-09-30. Each finding names the PowerSync call that replaces it, taken from the PowerSync client API.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what does the full version add?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The free check takes 2 inputs: the source text and a date. It runs all 16 rules on the open file in VS Code or in the web version, with no key and no limit. The full version scans the whole workspace and saves one Markdown migration report listing each file, line, dead call and PowerSync replacement for your team. It is one payment of $29 per licence key.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does the alternative cost?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The alternative is a developer reading every file by hand for Realm.App, Credentials, mongoClient, flexible sync, subscriptions, syncSession, partitionValue and data.mongodb-api.com URLs, then looking up each PowerSync equivalent. The free check does the file you have open in seconds. The workspace report is one $29 payment instead of a subscription, and the free result on your own file comes first.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does PowerSync Migration Check do?", "acceptedAnswer": {"@type": "Answer", "text": "It scans JavaScript, TypeScript, Swift, Kotlin and Dart files for MongoDB Atlas Device Sync (Realm Sync), Atlas Data API and HTTPS Endpoint calls, which reached end-of-life on 2025-09-30. It has 16 rules. Each finding gives the line, the dead call and the PowerSync replacement, such as PowerSyncDatabase, fetchCredentials, uploadData or Sync Rules. On the sample orders.ts it reports 6 findings."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "It is for mobile and web developers whose offline-first app was built on MongoDB Realm Sync or the Atlas Data API and now has to move to PowerSync. That includes React Native, web (realm-web), Swift, Kotlin and Flutter/Dart teams. It also suits a tech lead who needs a list of every call to replace before estimating the migration."}}, {"@type": "Question", "name": "Why not just grep for realm or ask a chatbot?", "acceptedAnswer": {"@type": "Answer", "text": "Local-only Realm databases still work, because the SDKs stayed open source. A grep for realm therefore flags every schema and query you can keep. These 16 rules only fire on the sync, Data API, HTTPS Endpoint and App Services login paths that stopped on 2025-09-30. Each finding names the PowerSync call that replaces it, taken from the PowerSync client API."}}, {"@type": "Question", "name": "What is free and what does the full version add?", "acceptedAnswer": {"@type": "Answer", "text": "The free check takes 2 inputs: the source text and a date. It runs all 16 rules on the open file in VS Code or in the web version, with no key and no limit. The full version scans the whole workspace and saves one Markdown migration report listing each file, line, dead call and PowerSync replacement for your team. It is one payment of $29 per licence key."}}, {"@type": "Question", "name": "What does the alternative cost?", "acceptedAnswer": {"@type": "Answer", "text": "The alternative is a developer reading every file by hand for Realm.App, Credentials, mongoClient, flexible sync, subscriptions, syncSession, partitionValue and data.mongodb-api.com URLs, then looking up each PowerSync equivalent. The free check does the file you have open in seconds. The workspace report is one $29 payment instead of a subscription, and the free result on your own file comes first."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:394490,slug:"powersync-migration-check",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/personal-data-map-dsar-audit"><span class="yj-rel-t">Personal Data Map: find every column a GDPR subject access request has to reac</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/brave-search-api-migration-check"><span class="yj-rel-t">Brave Search API Migration Check — Google Custom Search &amp; Bing</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/crashlytics-migration-lint"><span class="yj-rel-t">Crashlytics Migration Lint for App Center</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ferretdb-migration-lint"><span class="yj-rel-t">FerretDB Migration Lint — MongoDB SSPL gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/swift6-concurrency-lint"><span class="yj-rel-t">Swift 6 Concurrency Migration Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/mobile-app-ui-kit-accessible-wcag"><span class="yj-rel-t">Mobile App UI Kit — Accessible Components for Figma (WCAG 2.2 AA, EAA)</span><span class="yj-rel-p">$168</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body>
</html>