M
hello_mongodb.mongo
●
mongodb/hello_mongodb.mongo
⟲ Reset
✕ Clear
▶ Run
⌘↵
hello_mongodb.mongo
mongosh
Idle
Copy
Clear
Press ▶ Run (or ⌘/Ctrl+Enter) to execute the editor's code.
Loading…
×
Embedded from
source ↗
{ b.classList.toggle('active', b.dataset.file === name); }); fileNameEl.firstChild.textContent = name; if(!state.loaded[name]){ editor.setValue('// Loading ' + name + '…'); await loadFile(name); } // (Re)compute steps from the current "All" buffer state.steps[name] = parseSteps(state.buffers[name] ?? state.originals[name] ?? ''); // First time this file is opened: default to the LAST step when there are // multiple steps; otherwise fall back to All (0). if(state.activeStep[name] == null){ const ss = state.steps[name]; state.activeStep[name] = (ss && ss.length >= 2) ? ss[ss.length - 1].n : 0; } // If a previously-active step no longer exists (e.g. headings removed), fall back to the last available step or All if(state.activeStep[name] !== 0 && !(state.steps[name] || []).some(s => s.n === state.activeStep[name])){ const ss = state.steps[name]; state.activeStep[name] = (ss && ss.length) ? ss[ss.length - 1].n : 0; } loadEditorForCurrentStep(); renderStepTabs(); renderFileLinks(); refreshDirty(name); editor.focus(); } function refreshDirty(name){ // "Dirty" = the file's canonical (All) buffer differs from the original. // The editor view may be a step extraction — that derivation is not // a real edit, so don't read editor.getValue() here. const buf = state.buffers[name]; const dirty = state.loaded[name] && buf != null && buf !== state.originals[name]; const btn = document.querySelector(`.file-btn[data-file="${name}"]`); if(btn) btn.classList.toggle('dirty', dirty); if(name === state.active){ fileNameEl.classList.toggle('dirty', dirty); } } editor.on('change', ()=>{ if(!state.active) return; const file = state.active; const step = state.activeStep[file] || 0; const v = editor.getValue(); if(step === 0){ state.buffers[file] = v; if(state.stepEdits[file]) delete state.stepEdits[file]; const newSteps = parseSteps(v); state.steps[file] = newSteps; renderStepTabs(); }else{ if(!state.stepEdits[file]) state.stepEdits[file] = {}; state.stepEdits[file][step] = v; } refreshDirty(file); }); /* ========================================================== Reset / Clear / Run ========================================================== */ document.getElementById('resetBtn').addEventListener('click', ()=>{ const file = state.active; if(!file || state.originals[file] == null) return; const step = state.activeStep[file] || 0; if(step === 0){ state.buffers[file] = state.originals[file]; if(state.stepEdits[file]) delete state.stepEdits[file]; state.steps[file] = parseSteps(state.originals[file]); loadEditorForCurrentStep(); renderStepTabs(); toast('Reset ' + file); }else{ // Forget any in-flight edits for this step, then regenerate from current All buffer if(state.stepEdits[file]) delete state.stepEdits[file][step]; loadEditorForCurrentStep(); toast(`Reset step ${step}`); } refreshDirty(file); }); document.getElementById('clearBtn').addEventListener('click', clearConsole); document.getElementById('clearOutBtn').addEventListener('click', clearConsole); document.getElementById('runBtn').addEventListener('click', () => { if(isRunning) stopRun('user'); else runCode(); }); document.getElementById('copyOutBtn').addEventListener('click', ()=>{ const out = document.getElementById('consoleOut'); const text = Array.from(out.querySelectorAll('.line .c')).map(n=>n.textContent).join('\n'); if(!text){ toast('Nothing to copy'); return; } navigator.clipboard?.writeText(text).then(()=> toast('Output copied')); }); window.addEventListener('keydown', (e)=>{ if((e.metaKey || e.ctrlKey) && e.key === 'Enter'){ e.preventDefault(); runCode(); } }); /* ========================================================== Console output ========================================================== */ const consoleOut = document.getElementById('consoleOut'); const consoleStatus = document.getElementById('consoleStatus'); const consoleStats = document.getElementById('consoleStats'); let runStartTime = 0; let logCount = 0; let warnCount = 0; let errorCount = 0; function clearConsole(){ consoleOut.innerHTML = '
Console cleared.
'; logCount = warnCount = errorCount = 0; consoleStats.textContent = ''; consoleStatus.classList.remove('error','running'); consoleStatus.textContent = 'Idle'; } function ensureConsoleEmptyCleared(){ const empty = consoleOut.querySelector('.empty'); if(empty) empty.remove(); } function ts(){ const now = (performance.now() - runStartTime) / 1000; return (now >= 0 && runStartTime > 0) ? '+' + now.toFixed(3) + 's' : ''; } function appendLine(kind, parts){ ensureConsoleEmptyCleared(); const line = document.createElement('div'); line.className = 'line ' + kind; const t = document.createElement('span'); t.className = 't'; t.textContent = ts(); const c = document.createElement('span'); c.className = 'c'; c.textContent = parts.map(formatVal).join(' '); line.append(t, c); consoleOut.appendChild(line); consoleOut.scrollTop = consoleOut.scrollHeight; } function appendTable(parts){ ensureConsoleEmptyCleared(); const data = parts[0]; if(data == null || typeof data !== 'object'){ appendLine('log', parts); return; } const rows = Array.isArray(data) ? data : Object.entries(data).map(([k,v])=>({ '(key)':k, value:v })); const cols = new Set(); rows.forEach(r => { if(r && typeof r === 'object') Object.keys(r).forEach(k=>cols.add(k)); else cols.add('value'); }); const colArr = ['(index)', ...cols]; const tbl = document.createElement('table'); const thead = document.createElement('thead'); const trH = document.createElement('tr'); colArr.forEach(c=>{ const th = document.createElement('th'); th.textContent = c; trH.appendChild(th); }); thead.appendChild(trH); tbl.appendChild(thead); const tbody = document.createElement('tbody'); rows.forEach((r, i)=>{ const tr = document.createElement('tr'); colArr.forEach(col=>{ const td = document.createElement('td'); let val; if(col === '(index)') val = i; else if(r && typeof r === 'object') val = r[col]; else if(col === 'value') val = r; td.textContent = val === undefined ? '' : formatVal(val); tr.appendChild(td); }); tbody.appendChild(tr); }); tbl.appendChild(tbody); const wrap = document.createElement('div'); wrap.className = 'line log'; const t = document.createElement('span'); t.className = 't'; t.textContent = ts(); const c = document.createElement('span'); c.className = 'c'; c.appendChild(tbl); wrap.append(t, c); consoleOut.appendChild(wrap); consoleOut.scrollTop = consoleOut.scrollHeight; } function formatVal(v){ if(v === null) return 'null'; if(v === undefined) return 'undefined'; if(typeof v === 'string') return v; if(typeof v === 'number' || typeof v === 'boolean') return String(v); if(typeof v === 'bigint') return v.toString() + 'n'; if(typeof v === 'function') return v.toString(); if(typeof v === 'symbol') return v.toString(); try { return JSON.stringify(v, replacer, 2); } catch { return String(v); } } function replacer(_, value){ if(typeof value === 'bigint') return value.toString() + 'n'; if(typeof value === 'function') return '[Function ' + (value.name || 'anonymous') + ']'; if(typeof value === 'symbol') return value.toString(); return value; } function bumpStats(kind){ if(kind === 'log' || kind === 'info') logCount++; else if(kind === 'warn') warnCount++; else if(kind === 'error') errorCount++; consoleStats.textContent = `${logCount} log · ${warnCount} warn · ${errorCount} err`; } /* ========================================================== Sandbox runner — uses srcdoc iframe so timers + async work, and so the runner cannot pollute or break this page. ========================================================== */ let sandbox = null; let sandboxId = 0; function killSandbox(){ if(sandbox){ sandbox.remove(); sandbox = null; } } /* Run/Stop button state — keeps Stop visible while the iframe is alive (so async setTimeout/Promise output can still arrive). Auto-reverts to Run after a brief idle window with no incoming messages, or immediately if the user clicks Stop. */ let isRunning = false; let idleTimer = null; const IDLE_WINDOW_MS = 800; function setRunButton(running){ const btn = document.getElementById('runBtn'); if(!btn) return; if(running){ btn.classList.remove('run-btn'); btn.classList.add('stop-btn'); btn.title = 'Stop running code'; btn.innerHTML = '■ Stop
⌘↵
'; }else{ btn.classList.remove('stop-btn'); btn.classList.add('run-btn'); btn.title = 'Run code'; btn.innerHTML = '▶ Run
⌘↵
'; } } function scheduleIdle(){ if(idleTimer) clearTimeout(idleTimer); idleTimer = setTimeout(()=>{ idleTimer = null; if(!isRunning) return; killSandbox(); isRunning = false; setRunButton(false); }, IDLE_WINDOW_MS); } function stopRun(reason){ if(idleTimer){ clearTimeout(idleTimer); idleTimer = null; } const wasRunning = isRunning; killSandbox(); isRunning = false; setRunButton(false); if(wasRunning){ consoleStatus.classList.remove('running'); if(errorCount > 0){ consoleStatus.classList.add('error'); consoleStatus.textContent = 'Failed'; } else { consoleStatus.textContent = reason === 'user' ? 'Stopped' : 'Done'; } if(reason === 'user') appendLine('system', ['■ Stopped by user']); } } /* In-browser mongosh emulator. Concatenated to the user's code before it runs in the sandbox iframe. Exposes `db` and `__mongo` (used by the shell-command preprocessor below) on window. The store resets every run. */ const MONGO_EMULATOR_SOURCE = ` (function(){ const dbs = Object.create(null); let currentDbName = 'test'; function __getDb(){ if(!dbs[currentDbName]) dbs[currentDbName] = Object.create(null); return dbs[currentDbName]; } function __getColl(name, opts){ const db = __getDb(); if(!db[name]) db[name] = { _docs:[], _options:opts||{}, _indexes:[{ v:2, key:{_id:1}, name:'_id_' }] }; return db[name]; } function __genId(){ return Array.from({length:24},()=>Math.floor(Math.random()*16).toString(16)).join(''); } function __isPlain(v){ return v && typeof v==='object' && !Array.isArray(v) && Object.getPrototypeOf(v)===Object.prototype; } function __matches(doc, filter){ if(!filter || Object.keys(filter).length===0) return true; return Object.entries(filter).every(([k,v])=>{ if(k==='$and') return v.every(s=>__matches(doc,s)); if(k==='$or') return v.some(s=>__matches(doc,s)); if(k==='$nor') return v.every(s=>!__matches(doc,s)); if(__isPlain(v)){ return Object.entries(v).every(([op,opv])=>{ const dv = doc[k]; if(op==='$eq') return dv===opv; if(op==='$ne') return dv!==opv; if(op==='$gt') return dv>opv; if(op==='$gte') return dv>=opv; if(op==='$lt') return dv
k!=='_id' && (v===1||v===true)); let out; if(includeMode){ out = {}; if(projection._id!==0 && projection._id!==false) out._id = doc._id; fields.forEach(([k,v])=>{ if(k==='_id') return; if((v===1||v===true) && doc[k]!==undefined) out[k] = doc[k]; }); } else { out = { ...doc }; fields.forEach(([k,v])=>{ if(v===0||v===false) delete out[k]; }); } return out; } function __applyUpdate(doc, update){ Object.entries(update).forEach(([op,fields])=>{ if(op==='$set') Object.assign(doc, fields); else if(op==='$unset') Object.keys(fields).forEach(k=>delete doc[k]); else if(op==='$rename') Object.entries(fields).forEach(([from,to])=>{ if(from in doc){ doc[to] = doc[from]; delete doc[from]; } }); else if(op==='$inc') Object.entries(fields).forEach(([k,v])=>{ doc[k]=(doc[k]||0)+v; }); else if(op==='$push') Object.entries(fields).forEach(([k,v])=>{ if(!Array.isArray(doc[k])) doc[k]=[]; doc[k].push(v); }); else throw new Error('Unsupported update operator: '+op); }); } function __later(fn){ setTimeout(fn, 0); } class Cursor { constructor(coll, filter, projection){ this._coll = coll; this._filter = filter||{}; this._projection = projection||null; this._sort = null; this._limit = null; this._skip = 0; this._pending = null; this._schedule(); } _schedule(){ if(this._pending) clearTimeout(this._pending); this._pending = setTimeout(()=>this._auto(), 0); } _cancel(){ if(this._pending){ clearTimeout(this._pending); this._pending = null; } } sort(s){ this._cancel(); this._sort = s; this._schedule(); return this; } limit(n){ this._cancel(); this._limit = n; this._schedule(); return this; } skip(n){ this._cancel(); this._skip = n; this._schedule(); return this; } toArray(){ this._cancel(); return this._materialize(); } _materialize(){ let arr = this._coll._docs.filter(d=>__matches(d, this._filter)); if(this._sort){ const entries = Object.entries(this._sort); arr = arr.slice().sort((a,b)=>{ for(const [k,dir] of entries){ if(a[k]===b[k]) continue; return (a[k]>b[k]?1:-1) * (dir<0?-1:1); } return 0; }); } if(this._skip) arr = arr.slice(this._skip); if(this._limit!=null) arr = arr.slice(0, this._limit); if(this._projection) arr = arr.map(d=>__project(d, this._projection)); return arr; } _auto(){ const arr = this._materialize(); if(arr.length===0) console.log('(no documents)'); else arr.forEach(d=>console.log(d)); } explain(){ this._cancel(); const arr = this._materialize(); const indexes = this._coll._indexes; const useIdx = indexes.find(i => Object.keys(i.key).some(k => k!=='_id' && this._filter && Object.prototype.hasOwnProperty.call(this._filter,k))); const stage = useIdx ? 'IXSCAN' : 'COLLSCAN'; const explained = { executionStats: { executionSuccess: true, nReturned: arr.length, executionTimeMillis: Math.floor(Math.random()*5), totalDocsExamined: stage==='IXSCAN' ? arr.length : this._coll._docs.length, executionStages: { stage, indexName: useIdx ? useIdx.name : undefined } } }; __later(()=>console.log(explained)); return explained; } } function __collProxy(name){ const coll = __getColl(name); return { insertOne(doc){ if(!doc._id) doc._id = __genId(); coll._docs.push(doc); const res = { acknowledged:true, insertedId: doc._id }; __later(()=>console.log(res)); return res; }, insertMany(docs){ const ids = []; docs.forEach(d=>{ if(!d._id) d._id = __genId(); coll._docs.push(d); ids.push(d._id); }); const res = { acknowledged:true, insertedIds: ids }; __later(()=>console.log(res)); return res; }, find(filter, projection){ return new Cursor(coll, filter||{}, projection||null); }, findOne(filter, projection){ const found = coll._docs.find(d=>__matches(d, filter||{})); const out = found ? (projection ? __project(found, projection) : found) : null; __later(()=>console.log(out)); return out; }, updateOne(filter, update){ const idx = coll._docs.findIndex(d=>__matches(d, filter||{})); if(idx===-1){ const res = { acknowledged:true, matchedCount:0, modifiedCount:0 }; __later(()=>console.log(res)); return res; } const before = JSON.stringify(coll._docs[idx]); __applyUpdate(coll._docs[idx], update); const modified = JSON.stringify(coll._docs[idx])!==before; const res = { acknowledged:true, matchedCount:1, modifiedCount: modified?1:0 }; __later(()=>console.log(res)); return res; }, updateMany(filter, update){ let m=0, mod=0; coll._docs.forEach(d=>{ if(__matches(d, filter||{})){ m++; const before = JSON.stringify(d); __applyUpdate(d, update); if(JSON.stringify(d)!==before) mod++; } }); const res = { acknowledged:true, matchedCount:m, modifiedCount:mod }; __later(()=>console.log(res)); return res; }, deleteOne(filter){ const idx = coll._docs.findIndex(d=>__matches(d, filter||{})); const deleted = idx!==-1 ? 1 : 0; if(deleted) coll._docs.splice(idx,1); const res = { acknowledged:true, deletedCount: deleted }; __later(()=>console.log(res)); return res; }, deleteMany(filter){ let d=0; coll._docs = coll._docs.filter(x=>{ if(__matches(x, filter||{})){ d++; return false; } return true; }); const res = { acknowledged:true, deletedCount: d }; __later(()=>console.log(res)); return res; }, drop(){ const db = __getDb(); const existed = !!db[name]; delete db[name]; __later(()=>console.log(existed)); return existed; }, countDocuments(filter){ const n = coll._docs.filter(d=>__matches(d, filter||{})).length; __later(()=>console.log(n)); return n; }, aggregate(pipeline){ let docs = coll._docs.slice(); for(const stage of pipeline){ if(stage.$match) docs = docs.filter(d=>__matches(d, stage.$match)); else if(stage.$group){ const spec = stage.$group; const idExpr = spec._id; const aggs = Object.entries(spec).filter(([k])=>k!=='_id'); const evalKey = (d) => (typeof idExpr==='string' && idExpr.startsWith('$')) ? d[idExpr.slice(1)] : idExpr; const evalExpr = (expr, d) => (typeof expr==='string' && expr.startsWith('$')) ? d[expr.slice(1)] : expr; const groups = new Map(); docs.forEach(d=>{ const key = JSON.stringify(evalKey(d)); if(!groups.has(key)) groups.set(key, { _id: evalKey(d), _vals: {} }); const g = groups.get(key); aggs.forEach(([k, agg])=>{ const op = Object.keys(agg)[0]; const v = evalExpr(agg[op], d); if(!g._vals[k]) g._vals[k] = []; g._vals[k].push(v); }); }); docs = Array.from(groups.values()).map(g=>{ const out = { _id: g._id }; aggs.forEach(([k, agg])=>{ const op = Object.keys(agg)[0]; const vs = g._vals[k] || []; const nums = vs.filter(v=>typeof v==='number'); if(op==='$sum'){ if(typeof agg[op]==='number') out[k] = agg[op] * vs.length; else out[k] = nums.reduce((a,b)=>a+b, 0); } else if(op==='$avg') out[k] = nums.length ? nums.reduce((a,b)=>a+b,0)/nums.length : null; else if(op==='$min') out[k] = nums.length ? Math.min.apply(null, nums) : null; else if(op==='$max') out[k] = nums.length ? Math.max.apply(null, nums) : null; else if(op==='$count') out[k] = vs.length; else if(op==='$first') out[k] = vs[0]; else if(op==='$last') out[k] = vs[vs.length-1]; else if(op==='$push') out[k] = vs; else out[k] = null; }); return out; }); } else if(stage.$sort){ const entries = Object.entries(stage.$sort); docs = docs.slice().sort((a,b)=>{ for(const [k,dir] of entries){ if(a[k]===b[k]) continue; return (a[k]>b[k]?1:-1) * (dir<0?-1:1); } return 0; }); } else if(stage.$limit) docs = docs.slice(0, stage.$limit); else if(stage.$skip) docs = docs.slice(stage.$skip); else if(stage.$project) docs = docs.map(d=>__project(d, stage.$project)); } __later(()=>{ if(!docs.length) console.log('(no documents)'); else docs.forEach(d=>console.log(d)); }); return docs; }, createIndex(spec, options){ const idxName = (options && options.name) || Object.entries(spec).map(([k,v])=>k+'_'+v).join('_'); if(!coll._indexes.some(i=>i.name===idxName)){ coll._indexes.push({ v:2, key:{...spec}, name: idxName }); } __later(()=>console.log(idxName)); return idxName; }, getIndexes(){ const arr = coll._indexes.map(i=>({ ...i, key:{...i.key} })); __later(()=>arr.forEach(i=>console.log(i))); return arr; }, dropIndex(idxName){ const before = coll._indexes.length; coll._indexes = coll._indexes.filter(i=>i.name!==idxName); const dropped = before - coll._indexes.length; const res = { nIndexesWas: before, ok: dropped>0 ? 1 : 0 }; __later(()=>console.log(res)); return res; }, }; } const db = new Proxy({}, { get(_, name){ if(name==='createCollection'){ return function(collName, opts){ __getColl(collName, opts); const res = { ok: 1 }; __later(()=>console.log(res)); return res; }; } if(name==='getCollectionNames' || name==='getCollectionInfos'){ return function(){ const ndb = __getDb(); const out = Object.keys(ndb); __later(()=>console.log(out)); return out; }; } if(name==='dropDatabase'){ return function(){ delete dbs[currentDbName]; const res = { dropped: currentDbName, ok: 1 }; __later(()=>console.log(res)); return res; }; } if(typeof name==='symbol' || name==='then') return undefined; return __collProxy(name); } }); const __mongo = { show(what){ if(what==='dbs' || what==='databases'){ const list = Object.keys(dbs).map(n=>({ name: n, sizeOnDisk: 0, empty: !dbs[n] || Object.keys(dbs[n]).length===0 })); __later(()=>{ if(!list.length) console.log('(no databases yet — write something with insertOne to create one)'); else list.forEach(d=>console.log(d)); }); return list; } if(what==='collections' || what==='tables'){ const ndb = __getDb(); const list = Object.keys(ndb); __later(()=>{ if(!list.length) console.log('(no collections in '+currentDbName+')'); else list.forEach(c=>console.log(c)); }); return list; } __later(()=>console.log('show '+what+' — not implemented in this lab')); return null; }, use(name){ currentDbName = name; __later(()=>console.log('switched to db '+name)); }, cls(){ __later(()=>console.log('(cls — clears the real terminal; in this lab use the ✕ Clear button)')); }, }; Object.defineProperty(window, 'db', { value: db, writable: false, configurable: false }); Object.defineProperty(window, '__mongo', { value: __mongo, writable: false, configurable: false }); })(); `; /* Convert mongosh shell-only directives at the start of a line into JS calls on __mongo (because `show dbs`, `use myapp`, `cls` aren't valid JS). */ function preprocessMongoshell(code){ return code.split('\n').map(line => { const m = line.match(/^(\s*)(show|use|cls|clear)\b\s*([^\n]*)$/); if(!m) return line; let [, lead, cmd, rest] = m; // Strip a JS line comment from the rest const cIdx = rest.indexOf('//'); if(cIdx !== -1) rest = rest.slice(0, cIdx); rest = rest.replace(/[;\s]+$/, '').trim(); if(cmd === 'cls' || cmd === 'clear') return lead + '__mongo.cls();'; if(cmd === 'show'){ const arg = rest.split(/\s+/)[0] || ''; return lead + '__mongo.show(' + JSON.stringify(arg) + ');'; } if(cmd === 'use'){ const name = rest.split(/\s+/)[0] || ''; return lead + '__mongo.use(' + JSON.stringify(name) + ');'; } return line; }).join('\n'); } function buildSandboxSrc(code, runId){ // serialize the user code as a JSON string so quotes/backticks survive intact const codeJson = JSON.stringify(code); return `