jsondb

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 11 Imported by: 0

README

jsondb-go

The Go driver for jsondb — a MongoDB-lite JSON document store on SQLite.

jsondb is distributed as a binary; this driver talks to a running server over HTTP. It speaks API version 1 and verifies that on connect.

Shaped after the MongoDB Go driver, so code written against one reads the same against the other. Standard library only: importing this package pulls in nothing else.

go get github.com/pmuston/jsondb-go

Usage

package main

import (
	"context"
	"fmt"

	jsondb "github.com/pmuston/jsondb-go"
)

func main() {
	ctx := context.Background()

	// Connect verifies the server speaks an API version this driver understands.
	c, err := jsondb.Connect(ctx, "jsondb://localhost:8080")
	if err != nil {
		panic(err)
	}
	users := c.Collection("users")

	res, _ := users.InsertOne(ctx, jsondb.Document{
		"name": "Ada", "email": "ada@example.com", "tier": "gold",
	})
	fmt.Println("inserted", res.InsertedID)

	gold, _ := users.Find(ctx, jsondb.M{"tier": "gold"},
		jsondb.WithSort("name", true), jsondb.WithLimit(10))
	fmt.Println("gold members:", len(gold))

	upd, _ := users.UpdateMany(ctx, jsondb.M{"tier": "gold"},
		jsondb.Document{"$inc": jsondb.M{"logins": 1}})
	fmt.Println("modified:", upd.ModifiedCount)
}

jsondb.New(baseURL, opts...) is the constructor without the version check, for when you have already verified the server or are pointing at a test double.

Connection URIs

jsondb://host:port     plain HTTP
jsondbs://host:port    HTTPS
http:// https://       accepted directly

A URI carrying credentials, a database path, or query parameters is rejected with an explanation rather than partly honoured. jsondb has a single namespace — one file, collections as a column — so there is no authSource or database to select, and there is no authentication yet. Silently dropping the part the driver cannot honour would look like it had worked.

API

Collection methods mirror the storage library exactly, and that parity is enforced by a test in the server repository rather than by review.

Read Find FindOne CountDocuments Explain
Write by filter UpdateOne UpdateMany ReplaceOne DeleteOne DeleteMany
Write by id InsertOne InsertMany GetByID ReplaceByID PatchByID DeleteByID
Indexes CreateIndex ListIndexes DropIndex
Collections Drop Rename Stats

On the client: Collection(name), ListCollections, Ping.

Pass WithUpsert() to the update methods; WithLimit, WithSkip and WithSort to the reads.

Errors

Check with errors.Is:

ErrNotFound, ErrInvalidFilter, ErrInvalidJSON, ErrInvalidName, ErrDuplicateKey, ErrInvalidUpdate. Anything else arrives as *APIError carrying the status and message.

These are recovered from a stable code field the server sends, not by matching on message text.

Things that will surprise you

  • Filters traverse arrays. {"tags": "x"} matches {"tags": "x"} and {"tags": ["x","y"]}. $ne and $nin mean no value matches. Objects are not arrays and are never traversed.
  • MatchedCount and ModifiedCount are different numbers. Writing a document's existing values reports one matched, none modified.
  • Type conflicts are errors, not coercions. $inc on a string returns ErrInvalidUpdate naming the path, and writes nothing.
  • Unique indexes are sparse by default — documents missing the path never collide.
  • UpdateOne picks the lowest _id among matches, so repeated calls are deterministic. MongoDB picks arbitrarily.
  • Find returns a slice, not a cursor, and WithSkip is O(skip).

The full contract — including everything the endpoint shapes cannot express — ships with the server as docs/wire-protocol.md, alongside an OpenAPI description of the request and response shapes.

Compatibility

This driver speaks API version 1 and checks it on Connect. The server serves its endpoints under /api/v1/. Additive server changes — new endpoints, new response fields, new error codes — keep the version; the driver ignores fields it does not recognise.

Licence

MIT — see LICENSE.

Documentation

Overview

Package client is a standard-library-only HTTP client for a running jsondbd server. It mirrors the jsondb library's Collection API so a program can switch between embedding jsondb directly and talking to a remote server with only the construction line changing.

The Document and Filter types here are deliberate mirrors of the library's types: they serialize identically but are defined separately so importing this package never drags in the storage library or modernc.org/sqlite. They must stay in sync with the jsondb package.

Package jsondb is the Go driver for jsondb, a MongoDB-lite JSON document store built on SQLite.

It talks HTTP to a running jsondb server and is shaped after the MongoDB Go driver, so code written against one reads the same against the other:

c, err := jsondb.Connect(ctx, "jsondb://localhost:8080")
if err != nil {
	return err
}
users := c.Collection("users")

res, err := users.InsertOne(ctx, jsondb.Document{"name": "Ada", "tier": "plat"})
gold, err := users.Find(ctx, jsondb.M{"tier": "gold"}, jsondb.WithLimit(10))
_, err = users.UpdateMany(ctx, jsondb.M{"tier": "gold"},
	jsondb.Document{"$inc": jsondb.M{"logins": 1}})

Dependencies

The standard library only. Adding a third-party import breaks the package's contract: a program embedding the storage engine and a program talking to a server should be able to swap between them by changing the construction line, and nothing else.

Semantics worth knowing

The full contract is in the server repository's docs/wire-protocol.md. The parts that most often surprise people:

  • Filters traverse arrays. {"tags": "x"} matches both {"tags": "x"} and {"tags": ["x","y"]}. $ne and $nin mean *no* value matches. Objects are not arrays and are never traversed.
  • UpdateResult.MatchedCount and ModifiedCount differ. Writing a document's existing values reports one matched and none modified.
  • Update operators read the document as it was, so order never matters, and a type conflict ($inc on a string) is an error that writes nothing.
  • Unique indexes are sparse by default: documents missing the path never collide.
  • UpdateOne, ReplaceOne and DeleteOne act on the match with the lowest _id, so repeated calls are deterministic.
  • Find returns a complete slice, not a cursor. Skip-based paging is O(skip).

Versioning

The driver speaks API version 1 and verifies it on Connect, failing with both numbers named rather than letting a mismatch surface later as a 404. Unknown fields in server responses are ignored, so a newer server that has added fields still works.

Index

Constants

View Source
const APIPrefix = "/api/v1"

APIPrefix is the versioned path every data endpoint sits under. It must match the server's; Connect and Ping verify that at runtime rather than leaving a mismatch to surface as a puzzling 404.

View Source
const APIVersion = 1

APIVersion is the contract version this client is written against.

Variables

View Source
var (
	ErrNotFound      = errors.New("jsondb: document not found")
	ErrInvalidFilter = errors.New("jsondb: invalid filter")
	ErrInvalidJSON   = errors.New("jsondb: invalid JSON")
	ErrInvalidName   = errors.New("jsondb: invalid collection name")
	ErrDuplicateKey  = errors.New("jsondb: duplicate key")
	ErrInvalidUpdate = errors.New("jsondb: invalid update")
)

Sentinel errors mirror the library's so errors.Is works for consumers.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	Status  int
	Message string
}

APIError carries an unmapped non-2xx server response.

func (*APIError) Error

func (e *APIError) Error() string

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client talks to a jsondbd base URL.

func Connect

func Connect(ctx context.Context, uri string, opts ...Option) (*Client, error)

Connect builds a Client from a connection URI and verifies that the server speaks a contract this client understands.

Accepted schemes are jsondb:// (plain HTTP), jsondbs:// (HTTPS), and http:// or https:// directly. A mismatched API version fails here, with both numbers named, rather than surfacing later as a 404 on a path that moved.

func New

func New(baseURL string, opts ...Option) *Client

New returns a client bound to a jsondbd base URL, e.g. "http://localhost:8080".

func (*Client) Collection

func (c *Client) Collection(name string) *Collection

Collection returns a handle for the named collection.

func (*Client) ListCollections

func (c *Client) ListCollections(ctx context.Context) ([]CollectionInfo, error)

Collections lists collection names and counts.

func (*Client) Ping

func (c *Client) Ping(ctx context.Context) (*VersionInfo, error)

Ping fetches the server's version information. It is the cheapest endpoint and doubles as a connectivity check.

type Collection

type Collection struct {
	// contains filtered or unexported fields
}

Collection is a handle to a named collection on the remote server.

func (*Collection) CountDocuments

func (c *Collection) CountDocuments(ctx context.Context, filter Filter) (int64, error)

CountDocuments returns the number of documents matching the filter.

func (*Collection) CreateIndex

func (c *Collection) CreateIndex(ctx context.Context, spec IndexSpec) error

CreateIndex creates an expression index over one or more JSON paths.

func (*Collection) DeleteByID

func (c *Collection) DeleteByID(ctx context.Context, id string) error

DeleteByID removes the document with the given id.

func (*Collection) DeleteMany

func (c *Collection) DeleteMany(ctx context.Context, filter Filter) (*DeleteResult, error)

DeleteMany removes every matching document.

func (*Collection) DeleteOne

func (c *Collection) DeleteOne(ctx context.Context, filter Filter) (*DeleteResult, error)

DeleteOne removes at most one matching document.

func (*Collection) Drop

func (c *Collection) Drop(ctx context.Context) error

Drop deletes every document in the collection, along with its indexes.

func (*Collection) DropIndex

func (c *Collection) DropIndex(ctx context.Context, name string) error

DropIndex removes an index by name; ErrNotFound if it is not there.

func (*Collection) Explain

func (c *Collection) Explain(ctx context.Context, filter Filter, opts ...FindOption) (*Plan, error)

Explain returns the plan the server would use for the equivalent Find, without running it.

func (*Collection) Find

func (c *Collection) Find(ctx context.Context, filter Filter, opts ...FindOption) ([]Document, error)

Find returns documents matching the filter.

func (*Collection) FindOne

func (c *Collection) FindOne(ctx context.Context, filter Filter, opts ...FindOption) (Document, error)

FindOne returns the first matching document, or ErrNotFound.

func (*Collection) GetByID

func (c *Collection) GetByID(ctx context.Context, id string) (Document, error)

GetByID returns the document with the given id, or ErrNotFound.

func (*Collection) InsertMany

func (c *Collection) InsertMany(ctx context.Context, docs []Document) (*InsertManyResult, error)

InsertMany stores multiple documents in one request and returns their ids.

func (*Collection) InsertOne

func (c *Collection) InsertOne(ctx context.Context, doc Document) (*InsertOneResult, error)

InsertOne stores a document and returns its new id.

func (*Collection) ListIndexes

func (c *Collection) ListIndexes(ctx context.Context) ([]IndexInfo, error)

ListIndexes lists the indexes on this collection.

func (*Collection) PatchByID

func (c *Collection) PatchByID(ctx context.Context, id string, fields Document) error

PatchByID shallow-merges fields into the document (RFC 7396).

func (*Collection) Rename

func (c *Collection) Rename(ctx context.Context, newName string) error

Rename moves the collection's documents and indexes under a new name.

func (*Collection) ReplaceByID

func (c *Collection) ReplaceByID(ctx context.Context, id string, doc Document) error

ReplaceByID fully replaces the document with the given id.

func (*Collection) ReplaceOne

func (c *Collection) ReplaceOne(ctx context.Context, filter Filter, replacement Document, opts ...UpdateOption) (*UpdateResult, error)

ReplaceOne swaps the whole body of at most one matching document.

func (*Collection) Stats

func (c *Collection) Stats(ctx context.Context) (*CollectionStats, error)

Stats reports document count and stored size for the collection.

func (*Collection) UpdateMany

func (c *Collection) UpdateMany(ctx context.Context, filter Filter, update Document, opts ...UpdateOption) (*UpdateResult, error)

UpdateMany applies an update document to every matching document.

func (*Collection) UpdateOne

func (c *Collection) UpdateOne(ctx context.Context, filter Filter, update Document, opts ...UpdateOption) (*UpdateResult, error)

UpdateOne applies an update document to at most one matching document.

type CollectionInfo

type CollectionInfo struct {
	Name  string `json:"name"`
	Count int64  `json:"count"`
}

CollectionInfo is a collection name and its document count.

type CollectionStats

type CollectionStats struct {
	Name       string `json:"name"`
	Count      int64  `json:"count"`
	Size       int64  `json:"size"`
	AvgObjSize int64  `json:"avgObjSize"`
	NumIndexes int    `json:"numIndexes"`
}

CollectionStats mirrors jsondb.CollectionStats.

type DeleteResult

type DeleteResult struct {
	DeletedCount int64 `json:"deletedCount"`
}

DeleteResult mirrors jsondb.DeleteResult.

type Document

type Document map[string]any

Document is an arbitrary JSON object (mirror of jsondb.Document).

type Filter

type Filter map[string]any

Filter is a Mongo-ish query map (mirror of jsondb.Filter).

type FindOption

type FindOption func(*findOptions)

FindOption configures Find/FindOne/Count (mirrors jsondb's options).

func WithLimit

func WithLimit(n int) FindOption

WithLimit caps the number of returned documents.

func WithSkip

func WithSkip(n int) FindOption

WithSkip skips the first n matching documents.

func WithSort

func WithSort(path string, asc bool) FindOption

WithSort orders results by a JSON path, ascending or descending.

type IndexInfo

type IndexInfo struct {
	Name   string   `json:"name"`
	Coll   string   `json:"collection"`
	Paths  []string `json:"paths"`
	Unique bool     `json:"unique"`
}

IndexInfo mirrors jsondb.IndexInfo.

type IndexSpec

type IndexSpec struct {
	Paths  []string `json:"paths"`
	Unique bool     `json:"unique"`
	Name   string   `json:"name,omitempty"`
}

IndexSpec mirrors jsondb.IndexSpec.

type InsertManyResult

type InsertManyResult struct {
	InsertedIDs []string `json:"insertedIds"`
}

InsertManyResult mirrors jsondb.InsertManyResult.

type InsertOneResult

type InsertOneResult struct {
	InsertedID string `json:"insertedId"`
}

InsertOneResult mirrors jsondb.InsertOneResult.

type M

type M = map[string]any

M is a shorthand for an ad-hoc document or filter (mirror of jsondb.M).

type Option

type Option func(*Client)

Option configures a Client.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient sets a custom *http.Client (timeouts, transport).

func WithHeader

func WithHeader(key, value string) Option

WithHeader adds a header sent on every request (e.g. an auth header).

type Plan

type Plan struct {
	Steps []PlanStep `json:"steps"`

	// SeeksIndex reports that the plan narrows the search rather than
	// examining every document in the collection.
	SeeksIndex bool `json:"seeksIndex"`

	// SortsInMemory reports that results are collected and sorted rather than
	// read in index order. An index covering the sort path removes it.
	SortsInMemory bool `json:"sortsInMemory"`
}

Plan is a server-reported query plan: the steps SQLite would take, plus the reading of them.

SeeksIndex and SortsInMemory arrive as fields rather than being derived here, because the markers that produce them depend on how the storage engine answers a query — which is the server's business, and has already changed once. A client deriving them itself would silently go wrong.

func (Plan) String

func (p Plan) String() string

type PlanStep

type PlanStep struct {
	ID     int    `json:"id"`
	Parent int    `json:"parent"`
	Detail string `json:"detail"`
}

PlanStep is one row of the server's query plan.

type UpdateOption

type UpdateOption func(*updateOptions)

UpdateOption mirrors jsondb.UpdateOption.

func WithUpsert

func WithUpsert() UpdateOption

WithUpsert inserts a document when the filter matches nothing.

type UpdateResult

type UpdateResult struct {
	MatchedCount  int64  `json:"matchedCount"`
	ModifiedCount int64  `json:"modifiedCount"`
	UpsertedID    string `json:"upsertedId,omitempty"`
}

UpdateResult mirrors jsondb.UpdateResult.

type VersionInfo

type VersionInfo struct {
	APIVersion int    `json:"apiVersion"`
	Version    string `json:"version"`
	Revision   string `json:"revision,omitempty"`
	Modified   bool   `json:"modified,omitempty"`
	Go         string `json:"go,omitempty"`
}

VersionInfo is what the server reports about itself.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL