๐Ÿ 

databases

dict-DB, array-DB, and the ones backed by files.

the flavors

flavorshapebest for
DictDatabasekey โ†’ valuelook up by key, update by key
ArrayDatabaseappend-only sequenceevent logs, metrics, history
FileBackedDBtyped entity store (Zen files)persistent, git-friendly
TTGraphDatabasefull time-travel graphfull audit, temporal queries

We'll focus on DictDatabase + ArrayDatabase here (the simplest). FileBacked and TTGraph are covered in their own docs โ€” similar API, more features.

the mental model

a DB is a handle + a blob
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ metadata entity โ”‚ data section โ”‚ โ”‚ - uid โ”‚ (Dict3 or Array5, all โ”‚ โ”‚ - state_counter โ”‚ zef bytes) โ”‚ โ”‚ - creation_time โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ What you hold in python: DictDatabase('๐Ÿƒ-abc...') โ””โ”€ the "handle" โ€” 12 bytes, type tag + UID

The handle is a Zef value. You pass it around. The runtime resolves it to the actual store when you ask.

creating

db = FX.CreateDB(
    type=DictDatabase,
    persistence='in_memory',      # or 'vault', or 'local_disk'
) | run
# โ†’ DictDatabase('๐Ÿƒ-603478f5c40bc4f2a657')

persistence modes

modesurvives exit?notes
'in_memory'โŒfastest, no I/O
'vault'โœ…zef-managed, discoverable by UID across processes
'local_disk'โœ…you pick the path
db = FX.CreateDB(
    type=DictDatabase,
    persistence='local_disk',
    path='~/my-app/state.zef'
) | run

inserting

FX.UpdateDB(db=db, insert={
    'count':  42,
    'name':   'alice',
    'scores': [95, 87, 92],
    'config': {'debug': True, 'verbose': False},
}) | run

Pass any Zef value. Python types auto-convert:

querying

FX.QueryDB(db=db, get='name') | run         # 'alice'
FX.QueryDB(db=db, get='scores') | run       # [95, 87, 92]

# with a ZefOp query โ€” process on the way out
FX.QueryDB(db=db, query=keys | length) | run     # 4

FX.QueryDB(db=db, query=values | filter(Int) | reduce(add)) | run
# sum of all int values

python shorthand

DictDatabase handles behave like python dicts:

db['name']              # 'alice'
db['name'] = 'bob'       # UpdateDB under the hood
'name' in db             # True
len(db)                  # number of keys
list(db.keys())          # keys

snapshots โ€” frozen moments in time

snap = FX.DBSnapshot(db=db) | run

snap.data            # the Dict
snap.state_counter   # mutation count at snapshot time
snap.creation_time   # DB creation time

Mutations after the snapshot don't affect it. Useful for "take a picture and analyze without worrying about concurrent writes."

ArrayDatabase

log_db = FX.CreateDB(type=ArrayDatabase, persistence='in_memory') | run

FX.UpdateDB(db=log_db, append=ET.Event(name='click', at=now())) | run
FX.UpdateDB(db=log_db, append=ET.Event(name='hover', at=now())) | run

# indexed access
log_db[0]                    # first event
log_db[-1]                   # latest event
len(log_db)                # total events

concurrency

optimistic + retry

Single mutations use optimistic concurrency: generate patches under a read lock, then try to commit under a write lock. If another write landed in between, retry. Invisible to you โ€” it just always succeeds eventually.

Batch operations hold a write lock for the whole batch โ€” use batches when you need transactional atomicity.

lifecycle

# activate an existing one
FX.ActivateDB(db=DictDatabase('๐Ÿƒ-a86117bdafe4a1415115')) | run

# from a file โ€” type auto-detected
db = FX.ActivateDB(path='path/to/db.zef') | run

# sync to disk + unload
FX.DeactivateDB(db=db) | run

# permanently remove
FX.DeleteDB(db=db) | run

# list running DBs
FX.ListActiveDBs() | run
FX.ListDBsInVault() | run

no serialization boundary

the zef-only win

A String stored in a Zef DB is the same String_ as the one in your Python variable. Bytes in, bytes out. No JSON encoding. No schema-to-ORM translation. Round-trip is byte-exact.

This is why binary contiguous layout matters โ€” the layer that stores your data is the same layer that represents it in memory.

a worked example: a tiny cache

cache = FX.CreateDB(
    type=DictDatabase,
    persistence='local_disk',
    path='~/.myapp/cache.zef',
) | run

def get_or_fetch(url):
    if url in cache:
        return cache[url]
    resp = FX.HTTPRequest(url=url) | run
    cache[url] = resp['body']
    return resp['body']

performance

operationlatency
first access (cold)~1โ€“10 ms
cached access (thread-local)~10 ns
FX dispatchmicroseconds

Once cached, reads are effectively free. Writes hit the optimistic lock โ€” still fast.

FileBackedDB โ€” a peek

FileBackedDB is for when you want entities (not just keys) stored on disk as versioned files:

db = FX.CreateDB(type=FileBackedDB, path='~/kb') | run

FX.UpdateDB(db=db, insert=[
    ET.Page(0, title='Hello Zef', content='...'),
    ET.Page(1, title='Getting Started', content='...'),
]) | run

# query by path, like a mini graph-DB
FX.QueryFileBackedDB(db=db, query=single(ET.Page(0)) | F.title | value) | run
# 'Hello Zef'

FileBackedDB stores entities as Zen files โ€” literal Python dict-like text. You can cat them, git diff them, grep them. Great for knowledge bases, CMS, config stores.

mini-task

Build a tiny counter DB. Every time you run the program, increment the counter and print it.

solution
db = FX.CreateDB(
    type=DictDatabase,
    persistence='local_disk',
    path='~/tmp/counter.zef',
) | run

count = db.get('visits', 0)
db['visits'] = count + 1
print(f'visit #{count + 1}')

Next up: signals โ€” thread-safe variables with history. โ†’