Nuvaka › Geliştirici belgeleri › Örnekler

Örnek uzantılar

Beş örnek, uygulamanın kaynak deposunda generalappclient/examples/apps/ altında durur. Hepsi nuvaka-ext lint'ten hatasız ve uyarısız geçer.

ÖrnekGösterdiğiİzinler
Merhabaiki sayfa, arka plan, bildirim, yerel pano, ağ, MVCC sayaçnotifications, background, clipboard: write, net
Not özetleyicinotları okuma, anahtar başına özet, storage.updatenuvaka.notes: read
Pano geçmişipano havuzu, zamanlama, onChanged, sürümlü silmenuvaka.clipboard: read, background
GitHub takipçisinet, push/notify, bildirim sınırı, MVCC ile tek bildirimnet, nuvaka.push, notifications, background
Yerel dosya düzenleyiciklasör seçme, listeleme, okuma/yazma, satır içi onayfiles: read, files: write
nuvaka-ext lint examples/apps/github-watcher
nuvaka-ext pack examples/apps/github-watcher --out /tmp/

Klasörden yüklenen örnekte yerel yetenekler çalışır; depolama, notlar, pano havuzu ve push gibi sunucu uçları için uzantı hesabında kurulu olmalıdır (hızlı başlangıç).

Merhaba (tarikgkhsn.hello)

En küçük tam örnek: iki sayfa (main, about) ve nuvaka.ui.onPage, yerel bildirim, yerel panoya yazma, api.github.com'a istek, izinsiz adrese istek (reddedilir) ve storage.update ile sayaç. Arka plan every 15m, app.started ve app.focused ile tetiklenir.

document.getElementById('fetch').onclick = run(async () => {
  const r = await nuvaka.net.fetch('https://api.github.com/repos/tauri-apps/tauri', { headers: { Accept: 'application/vnd.github+json' } })
  const j = await r.json()
  return { status: r.status, name: j.full_name, stars: j.stargazers_count }
})
// MVCC: update() okur, yazar; başka cihaz araya girdiyse güncel değerle yeniden dener
document.getElementById('count').onclick = run(async () => {
  const r = await nuvaka.storage.update('count', (n) => (n || 0) + 1)
  return `sayaç: ${r.value} (sürüm ${r.version})`
})
nuvaka.storage.onChanged((key, e) => { if (key === 'count') log(`başka yerde değişti: ${key} = ${e.value} (sürüm ${e.version})`) })
document.getElementById('blocked').onclick = run(() => nuvaka.net.fetch('https://example.com/'))

Kaynak: examples/apps/hello/app.js (satır 21–32)

// Nuvaka Apps örneği (arka plan, QuickJS). Tek dosyalık ES modülü; `nuvaka` hazır gelir.
let ticks = 0

nuvaka.on('app.started', () => console.log('arka plan: uygulama açıldı'))
nuvaka.on('app.focused', () => { ticks++ })

nuvaka.on('schedule', async ({ schedule, at }) => {
  const settings = await nuvaka.settings.get()
  console.log(`zamanlanmış görev: ${schedule} @ ${new Date(at * 1000).toISOString()} (odak ${ticks} kez)`)
  await nuvaka.ui.notify(settings.greeting, `Zamanlanmış görev çalıştı (${schedule})`)
})

setTimeout(() => console.log('arka plan: 2 sn sonra setTimeout çalıştı'), 2000)

Kaynak: examples/apps/hello/background.js

Not özetleyici (tarikgkhsn.notes-summary)

Notları listeler, seçilen not için cihazda kısa bir özet ve anahtar kelimeler çıkarır (dış servis ya da yapay zekâ yok). Özet summary/<not id> anahtarında durur; not değişmediyse update işlevi undefined döner ve hiçbir şey yazılmaz. Başka cihazın yazdığı özet onChanged ile listeye düşer.

/** Özeti depodan okur; yoksa ya da not değiştiyse çıkarıp yazar. update() çakışmada güncel değerle yeniden dener. */
async function ensureSummary(note) {
  const stamp = stampOf(note)
  let wrote = false
  const r = await nuvaka.storage.update(keyOf(note.id), (cur) => {
    if (cur && cur.stamp === stamp && cur.n === perSummary) return undefined // güncel: yazma yok
    const s = Summarize.summarize(note.content, perSummary)
    wrote = true
    return { stamp, n: perSummary, sentences: s.sentences, keywords: s.keywords, at: new Date().toISOString() }
  })
  if (r && r.value) cache.set(note.id, r.value)
  return { value: r && r.value, wrote }
}

Kaynak: examples/apps/notes-summary/app.js (satır 43–55)

notes.get(id) yanıtı { note, images, reminders }'dır; özetleme saf bir işlevdir (summarize.js): cümleleri kelime sıklığına göre puanlar ve en iyi N'i özgün sırasıyla döner. Cümle sayısı manifest settings'teki sentences ayarından gelir.

Pano geçmişi (tarikgkhsn.clipboard-history)

Arka plan 15 dakikada bir ve açılışta pano havuzunun son 50 öğesini okur, yeni metinleri tek bir history anahtarına ekler. İki cihazın arka planı aynı anda çalışsa da update çakışmada güncel değerle yeniden denediği için kayıt kaybolmaz. Yeni öğe yoksa yazma yapılmaz. background.js arayüzde de yüklenir; "Şimdi al" düğmesi aynı işlevi çağırır, olay dinleyicileri yalnız nuvaka.background iken kurulur.

/** Havuzdaki son metinleri geçmişe ekler; eklenen sayısını döner. */
async function snapshot() {
  const settings = await nuvaka.settings.get()
  const keep = Number(settings.keep) || 200
  // → { items: [{ id, deviceId, deviceName, kind, mime, fileName, size, hash, createdAt, text, textTruncated, blocks }] } (yeniden eskiye)
  const r = await nuvaka.pool.list({ limit: 50 })
  const items = (r && r.items) || []
  const fresh = items
    .filter((i) => i && typeof i.text === 'string' && i.text.trim() && (!i.kind || i.kind === 'text'))
    .map((i) => ({ id: i.id, text: cut(i.text), device: i.deviceName || null, at: i.createdAt || null }))
    .sort((a, b) => b.id - a.id)

  let added = 0
  // MVCC: update() okur, fn'i çağırır, okunan sürümle yazar; başka cihaz araya girdiyse güncel değerle yeniden dener
  await nuvaka.storage.update(HISTORY_KEY, (cur) => {
    const list = (cur && Array.isArray(cur.items)) ? cur.items : []
    const lastId = (cur && cur.lastId) || 0
    // Yalnız son görülen kimlikten yeniler; aynı metin bir kez (havuz en yeniden eskiye gelir, en yenisi kalır)
    const add = [], seen = new Set()
    for (const e of fresh) if (e.id > lastId && !seen.has(e.text)) { add.push(e); seen.add(e.text) }
    added = add.length
    if (added === 0) return undefined // değişiklik yok: yazma, sürüm artmaz
    const merged = add.concat(list.filter((e) => !seen.has(e.text))).slice(0, keep)
    return { items: merged, lastId: Math.max(lastId, ...add.map((e) => e.id)) }
  })
  return added
}

if (nuvaka.background) {
  const run = (why) => snapshot()
    .then((n) => console.log(`pano geçmişi (${why}): ${n} yeni kayıt`))
    .catch((e) => console.error(`pano geçmişi (${why}) başarısız: ${e.code} ${e.message}`))
  nuvaka.on('schedule', (ev) => run(ev.schedule))
  nuvaka.on('app.started', () => run('açılış'))
}

Kaynak: examples/apps/clipboard-history/background.js (satır 11–45)

Arayüz arka planın yazmasını onChanged ile alır; "Geçmişi temizle" okunan sürümle storage.delete çağırır ve bu arada arka plan yazdıysa version_conflict gelir:

// Arka plan ya da başka cihaz yazdı: olay yalnız bildiğimizden yeni sürümle gelir; değer olayla birlikte okunmuş olur
nuvaka.storage.onChanged((key, c) => {
  if (key === null) { hist = { value: null, version: 0 }; render(); return }
  if (key !== HISTORY_KEY) return
  hist = c.deleted ? { value: null, version: 0 } : { value: c.value, version: c.version }
  render()
})

Kaynak: examples/apps/clipboard-history/app.js (satır 35–41)

GitHub takipçisi (tarikgkhsn.github-watcher)

Saatte bir (ve ağ geri gelince) listedeki depoların son sürümünü api.github.com'dan okur. Yeni sürümde kullanıcının seçimine göre nuvaka.push (tüm cihazlar) ya da nuvaka.ui.notify (bu cihaz) gönderir. Depo listesi arayüzde düzenlenir ve config anahtarında cihazlar arası tutulur; yoksa manifest settings varsayılanları kullanılır.

Bildirim sınırı dolarsa (notify_limit) hata yakalanır, o turda başka bildirim denenmez ve kayıt "bildirim bekliyor" olarak kalır; bir sonraki denetimde yeniden denenir:

async function announce(channel, title, body) {
  if (channel === 'none') return 'off'
  try {
    if (channel === 'local') await nuvaka.ui.notify(title, body) // yalnız bu cihaz
    else await nuvaka.push(title, body) // tüm cihazlar
    return 'sent'
  } catch (e) {
    // Yerel: notify_limit. Push: kullanıcı "Hayır" dediyse sunucu notify_limit döner. İkisi de sayaç olarak ortaktır.
    if (e.code === 'notify_limit' || e.code === 'notify_limit_reached') return 'limited'
    throw e
  }
}

Kaynak: examples/apps/github-watcher/background.js (satır 44–55)

Her cihaz kendi zamanlamasını çalıştırdığı için iki cihaz aynı yeni sürümü aynı anda görebilir. Bildirimden önce kayıt update ile notified: true yapılır; bunu yalnız bir cihaz başarır, diğeri güncel değeri görüp vazgeçer:

  const key = releaseKey(repo)
  const now = new Date().toISOString()
  // 1) Kaydı güncelle: etiket değiştiyse yeni kayıt, notified=false (ilk görüşte true: eski sürümü duyurma)
  const saved = await nuvaka.storage.update(key, (cur) => {
    const tag = rel ? rel.tag : null
    if (cur && cur.tag === tag) return undefined // değişiklik yok, yazma yok
    return { repo, tag, name: rel && rel.name, url: rel && rel.url, at: rel && rel.at, seenAt: now, notified: !cur || !tag }
  })
  results.push({ repo, tag: rel ? rel.tag : null })
  const v = saved && saved.value
  if (!v || v.notified || limited) continue

  // 2) Bildirimi sahiplen: MVCC sayesinde iki cihaz aynı anda denetlese de yalnız biri notified=true yazabilir
  let mine = false
  await nuvaka.storage.update(key, (cur) => {
    if (!cur || cur.tag !== v.tag || cur.notified) return undefined
    mine = true
    return Object.assign({}, cur, { notified: true })
  })
  if (!mine) continue
  const res = await announce(cfg.channel, `${repo} ${v.tag}`, v.name && v.name !== v.tag ? v.name : 'Yeni sürüm yayınlandı')
  if (res === 'limited') {
    // Sınır doldu: kaydı geri al, bir sonraki denetimde yeniden denenir; bu turda başka bildirim deneme
    limited = true
    await nuvaka.storage.update(key, (cur) => (cur && cur.tag === v.tag ? Object.assign({}, cur, { notified: false }) : undefined))
  }
}

Kaynak: examples/apps/github-watcher/background.js (satır 69–95)

Yerel dosya düzenleyici (tarikgkhsn.local-editor)

Kullanıcının Klasör ekle ile seçtiği klasörlerde dosyaları listeler, metin dosyalarını açar, düzenler, kaydeder; yeni dosya ve klasör oluşturur, siler. Seçim cihaza özeldir (files.folders()). Metin olmayan dosya salt okunur açılır. Çerçevede confirm() olmadığı için sorular sayfanın içinde sorulur. Yerel dosyada sürüm olmadığından kaydetmeden önce boyut ve değişme zamanı yeniden okunur:

async function save() {
  if (!file || file.readOnly || !isDirty()) return
  try {
    // Başka bir program bu arada değiştirdiyse sor (yerel dosyada sürüm yok; boyut + değişme zamanı karşılaştırılır)
    const st = await statOf(file.path)
    if (st && (st.mtime !== file.mtime || st.size !== file.size) && !(await ask('Dosya diskte değişmiş. Üzerine yazılsın mı?', 'Üzerine yaz'))) return
    const text = $('editor').value
    await nuvaka.files.writeText(root, file.path, text)
    original = text
    const after = await statOf(file.path)
    if (after) { file.size = after.size; file.mtime = after.mtime }
    setStatus(`Kaydedildi · ${new Date().toLocaleTimeString()}`)
    syncButtons()
    if (parentOf(file.path) === dir) openDir(dir)
  } catch (e) { setStatus(errText(e), true) }
}

Kaynak: examples/apps/local-editor/app.js (satır 121–136)

$('pick').onclick = async () => {
  try {
    const picked = await nuvaka.files.pick() // kullanıcı vazgeçerse null
    if (picked) await loadRoots(picked)
  } catch (e) { setStatus(errText(e), true) }
}

Kaynak: examples/apps/local-editor/app.js (satır 183–188)

Nuvaka Apps API v1 · son güncelleme 2026-09-27