Dokümanlar menüsü
Storage adaptörleri
Journal + memory + vektör + iş kuyruğu için takılabilir depolama arka uçları: Postgres, SQLite, MySQL/MariaDB, MongoDB, libSQL ve bellek içi; her biri aynı depo sözleşmesi setiyle ölçülü; capability matrisi ve composite ile karışık backend.
Ne işe yarar / ne zaman kullanılır#
GNL'nin dayanıklılık çekirdeği (run journal, exactly-once, replay) hep aynı Storage sözleşmesiyle konuşur; hangi arka ucu takacağın ortama göre değişir. Geliştirirken veya tek dosyalık bir demo kurarken SqliteStorage, prod'da PostgresStorage, testte ise InMemoryStorage kullanılır — hiçbiri agent/tool/workflow kodunu değiştirmez, sadece createGnl'e verdiğin storage alanı değişir.
Tek bir storage her port'u (runs/memory/vectors/work/cache) aynı kalitede sağlamayabilir — bu yüzden her adaptör bir capability matrisi taşır ve gerektiğinde composite() ile port bazında farklı bir storage'e (ör. thread hafızası için yerel bir SQLite dosyası) yönlendirilebilir.
Kurulum / import#
Somut implementasyonlar çekirdek paketi şişirmesin diye alt-export'larda yaşar; ana @gnldev/durable girişi sadece arayüzleri ve composite/toJournal gibi saf yardımcıları taşır.
import { SqliteStorage } from '@gnldev/durable/sqlite';
import { PostgresStorage } from '@gnldev/durable/postgres';
import { MysqlStorage } from '@gnldev/durable/mysql'; // npm i mysql2
import { MongoStorage } from '@gnldev/durable/mongo'; // npm i mongodb
import { LibsqlStorage } from '@gnldev/durable/libsql'; // npm i libsql
import { InMemoryStorage, toJournal, composite } from '@gnldev/durable';Adım adım kullanım#
1) Bir storage örneği oluştur ve createGnl yapılandırmasına ver (swapi-free örneğinden birebir):
import { SqliteStorage } from '@gnldev/durable/sqlite';
import { createGnl, toJournal } from '@gnldev/durable';
import type { CreateGnlConfig } from '@gnldev/durable';
const storage = new SqliteStorage(process.env.DB_PATH ?? 'swapi-free.db');
const config: CreateGnlConfig = {
storage,
agents: {
starwars: { model: buildModel(), system: SYSTEM, tools: makeSwapiTools(), maxSteps: 4 },
},
};2) Studio veya başka bir sayfasız (non-paged) tüketici Journal & JournalReader beklediğinde, toJournal() ile storage.runs'u köprüle:
app.mount('/studio', createStudioApp({
reader: toJournal(storage),
apiBase: '/studio',
gnl: createStudioRunner(gnl, config, { toJsonSchema: aiToolSchema }),
auth,
}));3) Prod'a geçerken tek satır değişir — SqliteStorage yerine PostgresStorage:
import { PostgresStorage } from '@gnldev/durable/postgres';
const storage = new PostgresStorage({ connectionString: process.env.DATABASE_URL });
// or inject an existing pg.Pool / pg-mem:
const storage2 = new PostgresStorage({ pool: myPgPool });Üç sunucu daha her port'u Postgres ile aynı garantilerle taşır; her biri kendi alt-export'unda, sürücüsü isteğe bağlı bağımlılık: MysqlStorage (MySQL 8.4+ ve MariaDB 11+), MongoStorage (MongoDB 8, replica set — tek düğüm yeter; tek başına sunucu daha hiçbir şey yazılmadan reddedilir) ve LibsqlStorage (libSQL/Turso, yalnızca yerel dosya — gömülü replika ya da uzak veritabanı reddedilir, çünkü orada bir söz tutulamaz). Veritabanı var olmalı ve URL'de adı geçmeli; depo tablolarını ilk kullanımda kurar.
import { MysqlStorage } from '@gnldev/durable/mysql';
import { MongoStorage } from '@gnldev/durable/mongo';
import { LibsqlStorage } from '@gnldev/durable/libsql';
// MySQL 8.4+ or MariaDB 11+
const mysql = new MysqlStorage({ connectionString: 'mysql://app:secret@db:3306/agents' });
// MongoDB 8 — a replica set (one node is enough); a standalone server is refused
const mongo = new MongoStorage({ connectionString: 'mongodb://app:secret@db:27017/agents?replicaSet=rs0' });
// libSQL — a local file only
const libsql = new LibsqlStorage('runs.db');Her depo varsayılmaz, ölçülür: tek bir uyum seti (@gnldev/durable/testing) aynı senaryoları — tek kazananlı claim, yarışan iki süreç, silme, organizasyon izolasyonu — her arka uca karşı koşar; garantileri sağlamayan bir depo girdiği yerde reddedilir. Sunucuya özel gereksinimler (MySQL ve MongoDB'de kısa TCP keepalive, Linux 6.19–7.0.13'te MongoDB 8) paketin README'sinde.
4) Testte kalıcılık istemiyorsan InMemoryStorage — hiçbir kurulum/dosya gerektirmez, tüm port'lar tam kapasiteli (full):
import { InMemoryStorage } from '@gnldev/durable';
const storage = new InMemoryStorage();Redis store'u yok. Store'lar InMemoryStorage, SqliteStorage ve LibsqlStorage (tek makine), PostgresStorage, MysqlStorage ve MongoStorage'dır (birden fazla süreç ya da makine); GNL'de başka hiçbir şey Redis üzerinde çalışmaz — kuyruk, olaylar, zamanlamalar, stream'ler ve kilitler store'un port'larını okur. 0.10'da RedisStorage kullanıyorsan: bir store'un kayıtları taşınmaz, bu yüzden açık run'larını 0.10'da bitir ya da iptal et, sonra süreçleri başka bir store'a yönlendir.
5) Yalnızca tek bir port'u (ör. thread hafızası) farklı bir storage'e devretmek istersen composite() kullan — runs ve meta her zaman default'tan gelir (replay tek journal'da kalmalı). Burada her şey Postgres'te kalıyor, thread hafızası yerel bir SQLite dosyasına gidiyor; pg_advisory_xact_lock olmayan, Postgres uyumlu bir sunucu için — orada Postgres memory port'unu kapatır (açılış uyarısına bak):
import { composite } from '@gnldev/durable';
import { PostgresStorage } from '@gnldev/durable/postgres';
import { SqliteStorage } from '@gnldev/durable/sqlite';
const storage = composite({
default: new PostgresStorage({ connectionString: process.env.DATABASE_URL }),
overrides: { memory: new SqliteStorage('memory.db') },
});RAG için kalıcı bir vektör deposu gerekiyorsa @gnldev/rag'da InMemoryVectorStore dışında dört tane var, hepsi birebir aynı VectorStore arayüzüyle: PostgresVectorStore (pgvector, HNSW/ivfflat index), QdrantVectorStore, ChromaVectorStore ve WeaviateVectorStore — her biri composite()'ın vectors port'una drop-in bağlanır ve aynı vektör setinden geçer (RAG pipeline sayfasına bakın). createRagTool içinde durable-wrapped çalıştıkları için sorgu sonucu da journal'lanır — resume'da sorgu tekrar atılmaz:
import { PostgresVectorStore } from '@gnldev/rag';
const vectors = new PostgresVectorStore({ connectionString: process.env.DATABASE_URL, index: 'hnsw' });Süreç canlılığı depoya aittir#
Başka bir sürecin hâlâ yaşayıp yaşamadığı, ölü bir sürecin run'ını, işini ya da tetiklemesini kimin devralacağını belirler — donmuş bir süreç işini korur, ölü bir süreç hemen bırakır. Bu cevap tek bir yerden gelir: deponun süreç canlılığı (Postgres oturum kilidi, MySQL adlandırılmış kilit, süreç adıyla adlandırılmış bir MongoDB bağlantısı, SQLite dosya kilidi). Bir depo bir süreçte tek bir canlılığa sahiptir ve üzerindeki her bileşen — registry, kuyruk worker'ı, zamanlayıcı, doğrudan çağrılan runDurable — onu kullanır.
Bu yüzden canlılık bileşenlere değil depoya verilir. Beş yerleşik depoda liveness'i createGnl'e ya da createWorker'a vermek LivenessConflictError fırlatır. Seçenek, processLiveness()'i olmayan özel bir Storage için durur. Depo, kendisine verilen canlılığı hiç kapatmaz: onu kim yaptıysa o kapatır.
import { createGnl, postgresLiveness, sqliteLiveness } from '@gnldev/durable';
import { PostgresStorage } from '@gnldev/durable/postgres';
import { SqliteStorage } from '@gnldev/durable/sqlite';
// Postgres behind a pooler: the store stays on the pooled URL, its liveness takes the direct one
const liveness = await postgresLiveness({ connectionString: process.env.DIRECT_URL! });
const storage = new PostgresStorage({ connectionString: process.env.POOLED_URL!, liveness });
const gnl = createGnl({ storage /* no liveness here: it would throw LivenessConflictError */ });
// SQLite takes it as a second argument
const local = new SqliteStorage('runs.db', { liveness: sqliteLiveness('runs.db') });Buna ihtiyaç duyan durum, transaction modlu bir pooler'ın (transaction modunda PgBouncer, yönetilen bir "-pooler" uç noktası) arkasındaki Postgres'tir: oturum kilidi başka istemcilere ödünç verilen bir bağlantıda yaşayamaz, bu yüzden depo açılırken onu reddeder. Depoyu pooler'lı URL'de tutun, canlılığa doğrudan URL'yi verin.
API referansı#
SqliteStoragenode:sqlite tabanlı tüm port'ların implementasyonu — dev/tek-dosya default (@gnldev/durable/sqlite).
PostgresStoragepg tabanlı prod storage; connectionString veya enjekte pool (pg-mem dahil) kabul eder (@gnldev/durable/postgres).
PostgresStorageOptions{ connectionString?, pool?, liveness? } — PostgresStorage kurucu seçenekleri; liveness deponun süreç canlılığıdır (yukarıya bakın).
MysqlStorageMySQL 8.4+ / MariaDB 11+ deposu, her port; connectionString ya da enjekte edilen mysql2 havuzu (@gnldev/durable/mysql).
MongoStorageReplica set üzerinde MongoDB 8 deposu, her port; vektörler index'lenmez, taranır (@gnldev/durable/mongo).
LibsqlStoragelibSQL sürücüsü üzerinde SQLite deposu; yalnızca yerel dosya (@gnldev/durable/libsql).
InMemoryStorageKalıcılık olmadan tüm port'ları 'full' capability ile sağlayan test/örnek storage.
StorageTüm store port’larını (runs/memory/vectors/work/cache/meta) + capabilities taşıyan birim arayüz.
toJournalRunJournal'ı eski Journal & JournalReader sözleşmesine sarar — sayfasız tüketiciler (ör. Studio) için köprü.
compositeVarsayılan storage + port bazında override alan bir Storage üretir; capability matrisini yeniden hesaplar.
CompositeConfig{ default, overrides? } — composite() girdisi.
requireCapabilityBir port'un en az 'scan' düzeyinde mevcut olmasını zorunlu kılar; yoksa CapabilityError fırlatır.
CapabilityMatrix{ runs, memory, vectors, work, cache } — her port için 'full' | 'scan' | 'ttl' | 'none'.
RunJournalKORUNAN append-only journal port'u — get/put/putIfAbsent (CAS)/listKeys/readRun/listRuns (sayfalı).
MemoryStoreThread/mesaj/working-memory/observation port'u — konuşma artefaktları (replay state değil).
VectorStoreRAG korpusu port'u — upsert/query (embedding tabanlı benzerlik).
WorkStoreKuyruk/olay/scheduler port'u — kendi namespace'inde, RunJournal'ı kirletmez. Özel bir depo read, appendMany ve ready set'i (appendReady, takeReady, heldReady, deferReady, dropReady) uygulamalıdır — kuyruğun üzerine kurulduğu adil alma budur; listNamespaces ve getMany isteğe bağlıdır.
runs ve meta port'ları composite()'ta her zaman default'tan gelir ve override edilemez — replay/exactly-once tek bir RunJournal'a bağlı kalmalı. Vektör arama @gnldev/durable'ın iki somut adaptöründe (Sqlite/Postgres) hâlâ 'scan' (brute-force cosine) seviyesinde; kalıcı/ölçekli RAG için @gnldev/rag'ın PostgresVectorStore'u (pgvector) kullanılır.