sourcedb-client (0.5.2)
Installation
registry=https://gits.hibna.com.tr/api/packages/hibna/npm/npm install sourcedb-client@0.5.2"sourcedb-client": "0.5.2"About this package
sourcedb-client
SourceDB'nin HTTP/JSON API'si için tipli, bağımlılıksız JavaScript/TypeScript
istemcisi. Node 18+ (global fetch) ve modern tarayıcılarda çalışır.
ESM ve CommonJS ikisi de desteklenir (0.4.0+).
Sunucuyu çalıştırın:
sourcedb --serve-http 127.0.0.1:7878 --db ./db
Kurulum
npm install sourcedb-client
Not: paket adı kayıt defterinde doluysa kendi kapsamınızla yayınlayın (
@kullanici/sourcedb) ya da Gitea npm registry'sini kullanın.
// ESM
import { SourceDB } from "sourcedb-client";
// CommonJS (backend)
const { SourceDB } = require("sourcedb-client");
İstemci seçenekleri (0.4.0+)
const db = new SourceDB("http://127.0.0.1:7878", {
token: "gizli", // Authorization: Bearer <token> (sunucuda ayarlıysa)
timeoutMs: 30000, // istek başına zaman aşımı; 0 = kapalı (vars. 30sn)
retries: 2, // YALNIZ okuma uçlarında otomatik yeniden deneme (vars. 2)
retryDelayMs: 200, // denemeler arası taban bekleme; üstel artar (vars. 200ms)
headers: {}, // her isteğe eklenecek ek başlıklar
fetch: undefined, // özel fetch (Node <18 / test ortamları)
});
Yeniden deneme yalnızca idempotent okuma uçlarında yapılır (get, mget,
keys, find, search, records, recent, match, stats, health,
metrics, tables, dicts…). Yazmalar (put, delete, append, add,
createTable, dictIntern) asla otomatik tekrarlanmaz. Tekrarlanabilir
hatalar: ağ hatası / zaman aşımı (status 0) ve HTTP 502/503/504.
Kullanım
import { SourceDB } from "sourcedb-client";
const db = new SourceDB("http://127.0.0.1:7878", { token: "gizli" }); // token isteğe bağlı
// Sunucu / gözlem
await db.health(); // { status: "ok" }
await db.metrics(); // sorgu/gecikme sayaçları
await db.resources(); // { cpu_percent, rss_bytes, disk_bytes, ... }
await db.tables(); // [{ name, engine }]
await db.createTable("users", "Collection");
await db.tableStats("users"); // "engine=Collection records=…"
// KeyValue (değerler string olarak saklanır)
const kv = db.kv("oturumlar");
await kv.put("sess:1", "token-abc");
await kv.get("sess:1"); // "token-abc" | null
await kv.mget(["sess:1", "yok"]); // { "sess:1": "token-abc", "yok": null }
// Değerler JSON ise sunucu tarafı alan projeksiyonu (yük küçülür):
await kv.mget(["8rypRidm"], { root: "_source", fields: ["home", "away"] });
await kv.keys({ limit: 100 }); // ["sess:1", ...]
await kv.delete("sess:1");
// Collection (tipli alanlar + ikincil/tam-metin indeks)
const c = db.collection("users");
await c.createIndex("email", { unique: true });
await c.createTextIndex("bio");
await c.put("user:1", { ad: "Ali Veli", email: "ali@x.com", yas: 30, aktif: true, bio: "rust sever" });
await c.get("user:1"); // { ad, email, yas, aktif, bio } | null
await c.find({ email: "ali@x.com" }); // ["user:1"]
await c.search("bio", "rust", "and"); // ["user:1"]
await c.records({ limit: 50 }); // [{ pk, fields }]
await c.delete("user:1");
// Log (kullanıcı başına akış)
const log = db.log("bildirimler");
await log.append(7, "merhaba");
await log.recent(7, 20); // ["merhaba", ...] en yeni → eski
// Odds (oran eşleştirme)
const odds = db.odds("maclar");
await odds.add(1, [{ source: 0, market: "1X2", outcome: 0, value: 1.85 }]);
await odds.match([{ source: 0, market: "1X2", outcome: 0, value: 1.85 }], { tolerance: 0 });
await odds.stats();
// Docs (iç-içe JSON doküman + ES-uyumlu sorgu DSL'i + aggregations) — 0.5.0
await db.createTable("posts", "Docs");
const docs = db.docs("posts"); // TS: db.docs<Post>("posts")
await docs.put("p:1", { baslik: "merhaba", yazar: { ad: "ali" }, begeni: 0,
etiketler: ["rust", "db"], createdAt: "2026-06-10T10:00:00Z" });
await docs.get("p:1"); // doküman | null
await docs.patch("p:1", { baslik: "yeni" }, {
unset: ["yazar.ad"], // alan silme (dotted path)
inc: { begeni: 1 }, // ATOMİK sayaç (tablo kilidi altında)
upsert: false,
}); // "created" | "updated" | null
await docs.mget(["p:1", "p:2"], { fields: ["baslik"] });
await docs.search({ // ES-uyumlu DSL
query: { bool: {
must: [{ term: { "etiketler.keyword": "rust" } }], // .keyword soyulur
filter: [{ range: { createdAt: { gte: "now-7d" } } }],
}},
sort: [{ createdAt: { order: "desc" } }, "_id"],
from: 0, size: 20,
_source: ["baslik", "begeni"],
aggs: { gunluk: { date_histogram: { field: "createdAt", calendar_interval: "day" },
aggs: { ort: { avg: { field: "begeni" } } } } },
}); // { total, hits: [{pk, doc, sort}], aggregations }
await docs.count({ term: { "yazar.ad": "ali" } });
await docs.updateWhere({ term: { arsiv: true } }, { inc: { puan: 1 }, unset: ["gecici"] });
await docs.deleteWhere({ range: { createdAt: { lt: "now-90d" } } });
await docs.bulk([{ op: "put", pk: "p:2", doc: { baslik: "iki" } },
{ op: "patch", pk: "p:1", inc: { begeni: 1 } },
{ op: "delete", pk: "p:9" }]);
await docs.fields(); // alan keşfi: [{ path, docs, str, num, bool, indexed }]
await docs.setPolicy(["match_id", "meta.*"]); // dev tablolarda RAM koruması ("all"|"none"|[yollar])
// Büyük okumalar: search_after ile akış — 50K+ doküman RAM'de birikmez.
for await (const h of docs.scan({ query: { match_all: {} } }, { pageSize: 2000 })) {
// h.pk, h.doc
}
// Sözlükler: lokal .dict kopyası ve import sırasına bağlı id'ler gerekmez.
await db.dicts(); // [{ name, bytes }]
await db.dictResolve("football_bookmakers", ["bookmaker_bet365"]); // { bookmaker_bet365: 4 }
await db.dictNames("football_matchids", [12, 34]); // { "12": "8rypRidm", "34": ... }
await db.dictIntern("football_bookmakers", ["yeni_buro"]); // YAZMA: yoksa ekler (tek-yazar kuralı!)
await db.dictEntries("football_bookmakers"); // { total, entries:[{id,name}], next } — tüm bahisçiler
await db.dictEntriesAll("football_bookmakers"); // [{id,name}] — sayfaları otomatik birleştirir
// En pratiği: match'te çözümü doğrudan istemek — source bahisçi ADI olur,
// sonuç ES matchId'leriyle döner (matchKeys):
const { matches, matchKeys } = await db.odds("football_odds").match(
[{ source: "bookmaker_bet365", market: "MATCH_AH_-0.25", value: 3.45 }],
{ sourceDict: "football_bookmakers", matchDict: "football_matchids" },
);
Elasticsearch drop-in adaptörü (es-adapter)
ES'e bulk/update/search ile yazan mevcut feed scriptlerini (ör. canlı
oran çekme döngüleri) kod değişikliği olmadan SourceDB'ye taşımak için:
import { SourceDBEsAdapter } from "sourcedb-client/es-adapter"; // ESM
const { SourceDBEsAdapter } = require("sourcedb-client/es-adapter"); // CJS
const esClient = new SourceDBEsAdapter({
node: "http://127.0.0.1:7878",
token: null,
rawEngine: "Docs", // YENİ tabloların motoru; vars. "KeyValue" (eski uyum)
});
v2 (0.5.0): adaptör motor-farkındadır. ES index X → tablo es_X; tablo
hangi motordaysa o yol kullanılır:
- Docs tablosu → tam yüzey:
searchgövdesi sunucuya AYNEN gider (bool/term/terms/range+now/exists/prefix/wildcard/match + sort +_source+ aggregations),update/bulksunucu tarafı atomik merge-upsert (tek istek), gerçekscroll(search_after ile devam),get·index·delete·count·deleteByQuery·updateByQuery(painlessscriptyerinedoc/unset/incuzantısı),indices.getMapping(alan keşfi — market discovery) ·indices.exists/create/refresh·cat.indices(desen). - KeyValue tablosu (CLI
--pull-esdüzeni, 0.3/0.4 mirası) → eski davranış birebir: oku-birleştir-yaz upsert, yalnıztermsaraması, scroll tek sayfa.
bulk artık index/create (tam yazma), update (+doc_as_upsert) ve
delete aksiyonlarını destekler; kalem hatası partiyi durdurmaz
(errors: true + kalemde error).
Odds eşlemesi her iki motorda da aynı:
bookmaker_*/basket_bookmaker_*dokümanı yazılınca maçın TÜM bürolarının oranları ham tablolardan yeniden kurulupfootball_odds/basket_oddstablosuna tam-değiştirme ile yazılır → tek büronün kapanışı güncellenirken diğer bürolar korunur, eski değerler sorgudan düşer; yazılan ANINDA sorgulanabilir.- Kimlikler string gider (
match_key+ bahisçi adı); sunucu sözlüğe intern eder — istemci sayısal id bilmez, id'ler import sırasından bağımsız.
Tek-yazar kuralı: adaptör çalışırken aynı sözlüklere yazan CLI
--pull-esaynı anda çalıştırılmamalıdır (iki yazar id çakıştırır). Aggs notu: aggregations yalnız TEK Docs tablosunu hedefleyen aramalarda desteklenir (desen birden çok tabloya açılırsa hata).
Hata yönetimi
Başarısız istekler SourceDBError fırlatır (adaptörde alt sınıfı
SourceDBEsAdapterError). 0.4.0'dan itibaren ağ hatası ve zaman aşımı da
aynı sınıfla sarılır (status === 0):
import { SourceDB, SourceDBError } from "sourcedb-client";
try {
await db.collection("users").put("user:9", { email: "ali@x.com" }); // benzersiz ihlali
} catch (e) {
if (e instanceof SourceDBError) {
e.status; // HTTP kodu; 0 = ağ hatası / zaman aşımı
e.body; // sunucunun hata gövdesi (JSON çözülmüşse nesne)
e.method; // "PUT"
e.path; // "/coll/users/records/user%3A9"
e.cause; // ağ hatasında alttaki fetch hatası
}
}
TypeScript
Tipler üretilen bildirimlerde gömülü (dist/*.d.ts + CJS için *.d.cts);
ek @types gerekmez.
import { SourceDB, type CollectionRecord } from "sourcedb-client";
const db = new SourceDB();
const recs: CollectionRecord[] = await db.collection("users").records();
Geliştirme
Kaynak src/ altında TypeScript'tir; dist/ tsup ile üretilir (ESM + CJS + d.ts):
npm install
npm run build # dist/ üretir
npm run typecheck # tsc --noEmit
npm run smoke # canlı duman testi (çalışan sunucu ister; SDB_URL/SDB_TOKEN)
npm run smoke:docs # docs<T>() yüzeyi canlı testi
npm run smoke:es # es-adapter (KV mirası) canlı testi (taze db ile)
npm run smoke:es-docs # es-adapter v2 (rawEngine: Docs) canlı testi
npm run smoke:cjs # CJS require doğrulaması (sunucu istemez)
npm run smoke:qol # timeout/retry davranış testi (sunucu istemez)
Kökteki index.js / es-adapter.js yalnızca depo-içi göreli import'lar için
duran shim'lerdir (dist'e yönlendirir); paket tüketicileri exports
haritasıyla doğrudan dist'i alır.
Sürüm notları
- 0.5.2 —
dictEntries(name, {limit, after})+dictEntriesAll(name): bir sözlüğün tüm girişlerini (id-sıralı, cursor sayfalı) listeler — "tüm bahisçileri getir" (/odds/bookmakers) için. SunucuyaGET /dicts/<ad>/entrieseklendi (büyük sözlükler tek seferde dökülmez). - 0.5.1 — es-adapter:
rawEngine: "Docs"ile YENİ oluşturulanbookmaker_*/basket_bookmaker_*ham tablolarına otomatik meta-indeks politikası (CLI--pull-esile birebir) — yüzlerce market alanı değer-indekslenmez, alan keşfi çalışmaya devam eder. - 0.5.0 — Docs API:
db.docs<T>()(put/get/patch[unset+atomik inc]/ delete/mget/search/count/deleteWhere/updateWhere/bulk/fields/list/setPolicy)scan()async iterator (search_after akışı);tables(pattern); es-adapter v2: motor-farkında — Docs tablolarında tam DSL passthrough, aggregations, gerçek scroll, count/deleteByQuery/updateByQuery, get/index/delete, bulk index|create|update|delete,indices.getMapping/exists/create/refresh,cat.indices;rawEngineseçeneği (vars. "KeyValue" — mevcut dağıtım davranışı değişmez).
- 0.4.0 — kaynak TypeScript'e taşındı; dual ESM + CommonJS çıktı;
istek zaman aşımı (
timeoutMs, vars. 30sn); idempotent okumalarda otomatik yeniden deneme (retries, vars. 2; 502/503/504 + ağ hatası); zengin hata (status/body/method/path/cause; ağ hataları daSourceDBError);dictIntern();headersseçeneği. Telde giden API değişmedi. - 0.3.0 —
es-adapter: @elastic/elasticsearch v8 drop-in adaptörü. - 0.2.0 —
kv.mget(projeksiyonlu), sözlük uçları,odds.matchsourceDict/matchDict.
Dependencies
Development Dependencies
| ID | Version |
|---|---|
| @types/node | ^22.10.0 |
| tsup | ^8.3.5 |
| typescript | ^5.7.0 |