-
v0.13.0 Stable
released this
2026-10-22 14:11:52 +00:00 | 3 commits to main since this releaseVSKI v0.13.0
Light graphs: calendar-grade indexing
Light graphs landed in v0.10 as a relationship index for collections, and
the first heavy users all converged on the same ask: index events by
time. "What did this person do last week", "what changed on this date",
"how many hours accumulated on this edge" - the graph data model already
fit (nodes and edges carry labels, weights and free-form JSON properties),
but the ingest and query primitives were missing. v0.13.0 closes that gap
without growing the engine: no new tables, no migrations, one new index.Idempotent ingest:
upsert=trueIndexing pipelines re-run.
POST /graphs/:name/nodes|edges?upsert=true
(now alsograph.createNodes(data, { upsert: true })in the JS SDK)
replaces an existing id wholesale instead of erroring - the graph
equivalent ofMERGE, and the primitive every timetree ingester needs.
Upserts hitting an existing id additionally check the graph'supdateRule
(the item already passedcreateRulelike any create). The whole batch is
written in a single transaction: on failure nothing is persisted (batch
creates previously could persist a prefix of the batch).Atomic accumulation:
weightDeltaPATCH /graphs/:name/edges/:idacceptsweightDelta(mutually exclusive
withweight): the server reads the current weight under the graph's
write lock and adds to it, so concurrent increments cannot lose updates -
the classic "hours accumulated", "stock consumed" counter. Weights also
feed a new stats aggregate:GET .../statsnow reports
edgeWeightSumsper edge label (computed in SQL, where NULL weights are
distinguishable from 0) alongside a newnodeLabelshistogram.Property filters:
prop=path op valueThe JSON
propertiesmaps are no longer opaque. List endpoints accept
repeatablepropfilters -ref.collection=users,capacity>=5,
status!=draft,title~meeting,archived=null- compiled to SQLite
json_extractcomparisons with bound parameters (numeric-looking values
compare numerically,~is a substring match,nulltests for
absence). This makesproperties.refand capacity/stock metadata
first-class query targets. Nodes also acceptids=a,b,c(max 500) and
edgesource/targetaccept comma lists, so "everything touching last
week's day nodes" is one request.Node types in the index:
nodeLabelsNode labels are now interned in the in-memory topology index (alongside
edge labels and weights; ~4 bytes/node).neighbors,traverseand
subgraphacceptnodeLabels=a,bto restrict visited/returned nodes by
type - "documents linked to this day, not the people". Node label PATCHes
keep the index in sync; a SQL index onnodes(label)is created for new
graphs and backfilled idempotently for existing ones.Correctness fixes
- Edge weight PATCHes left the in-memory index stale on the master:
the index entry was rebuilt only when the label changed, and with the
pre-update weight - weighted shortest paths kept answering with the old
cost until restart. Weight and label changes now sync the index
immediately. - Shortest path ignored edge direction - it always relaxed both
adjacency lists, so directed graphs got undirected routes. The default
stays both-ways (documented behavior), butpathnow accepts
direction=out|in|both.
Calendar timetree, packaged
The Calendar Timetree recipe
documents the pattern end to end:week --includes_day--> daybackbone
nodes,record --on_day--> dayactivity edges with magnitudes on the
weight, deterministic ids so re-ingest is a no-op. The new
calendar-index example plugin automates it - subscribe it to a
collection with a date field and it maintains the tree on every
create/update/delete (record deletes cascade their edges; the calendar
backbone stays), withbackfillandweek_ofhelper functions. It uses
native upsert (hencevski-sdk >= 0.6.0) and needs read rules on the
source collection plus write rules on the target graph for its service
account (the recipe shows both).The server stays calendar-agnostic: any bucketing (sprints, fiscal
quarters) is just more backbone nodes with the same shape.SDKs
- JS/TS:
createNodes/createEdgestake an optional
{ upsert }second argument;updateEdgetakesweightDelta;
pathtakesdirection;getNodestakesidsandprop,
getEdgestakes arraysource/targetandprop;
neighbors/traverse/subgraphtakenodeLabels;GraphStatsadds
nodeLabelsandedgeWeightSums. All additive. - Python (
vski-sdkv0.6.0):create_nodes/create_edgestake
upsert=;update_edgetakesweight_delta=;get_nodestakesids=
andprop=,get_edgestakes list-valuedsource=/target=and
prop=;neighbors/traverse/subgraphtakenode_labels=;path
takesdirection=;GraphStatsaddsnodeLabelsandedgeWeightSums.
The sync wrapper mirrors everything automatically.
Tests
47_graph_test.tsgrows a "Calendar indexing primitives" step (upsert
replacement + duplicate rejection, weightDelta accumulation and
exclusivity, weight-patch freshness on weighted paths, directed path,
property/ids/multi-value filters, nodeLabels on neighbors and subgraph,
stats sums);wiki_graph_verify.tspins every new claim the wiki pages
make;graphdbunit tests coverSetEdgeWeight,SetNodeLabel,
directed paths and node-label filters. On the Python side, the SDK's
test_47_graphs.pymirrors the new surface andtest_53_calendar_index.py
exercises the calendar-index plugin end to end (tree build, re-ingest
idempotency, delete cascade,week_of,backfill). A pre-existing flaky
SchemaFromMaptest (map iteration order) is now order-independent.Compatibility
- No breaking API or storage changes; everything new is opt-in query/body
parameters. Path defaults to the historical both-ways walk; plain
POST stays insert-only. - No migrations: the node-label index is
CREATE INDEX IF NOT EXISTS,
applied on graph creation and backfilled lazily (master only) when an
existing graph's index loads. - Upsert replaces
label/properties(and edge endpoints/weight)
wholesale - it does not merge. Pipelines that relied on the
create-then-update fallback ingraph-synckeep working unchanged.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Edge weight PATCHes left the in-memory index stale on the master: