Skip to content

Latest commit

 

History

History
188 lines (136 loc) · 7.35 KB

File metadata and controls

188 lines (136 loc) · 7.35 KB

API reference

Two surfaces: the REST contract (what build_crud_router serves and what the clean-room frontend store consumes) and the Python API (the importable symbols).


REST contract

This is the contract the frontend createCrudStore was authored against. For a spec with resource = "<resource>" and primary key <pk>, the router mounts at /<resource>.

GET /<resource> — list (filter + cursor pagination)

Query params:

Param Type Default Meaning
limit int (1–200) 50 page size
cursor string opaque keyset token from a prior response's next_cursor; omit for the first page
filter string URL-encoded JSON array of filter specs (see below)

A filter spec is {"field": <col>, "op": <op>, "value": <v>}. op is one of: eq, neq, lt, lte, gt, gte, like, ilike, contains, in, nin, is_null, not_null. in/nin take a non-empty list value; is_null/not_null take no value. A field outside the entity's filterable allow-list → 400.

Response 200:

{ "<resource>": [ { /* row */ }, ... ], "next_cursor": "OPAQUE_OR_NULL" }

The list key is the resource name. next_cursor is null when there are no more pages. Ordering is (created_at DESC, <pk> DESC).

GET /<resource>/{id} — fetch one

200 → the row object. 404 when the row does not exist or is hidden by the caller's tenant scope (deliberately indistinguishable).

POST /<resource> — create

Body: a JSON object of column values. Unknown columns → 400; a missing required column → 400. 201 → the created row (with server-assigned id and timestamps). Emits one create audit event.

PATCH /<resource>/{id} — partial update

Body: a JSON object of the fields to change. Only provided keys are updated; a non-patchable key → 400; an empty body → 400. 200 → the updated row. 404 when absent / tenant-hidden. Emits one update audit event with a (before, after) diff.

DELETE /<resource>/{id} — hard delete

200{ "deleted": true, "<pk>": "<id>" }. 404 when absent / tenant-hidden. Emits one delete audit event.

POST /<resource>/bulk — bulk update / set_field / delete

Body:

{ "operation": "update" | "set_field" | "delete",
  "ids": ["...", "..."],
  "patch": { "col": "val" } }

patch is required for update/set_field, ignored for delete. Up to 500 ids; a larger list or a patch key outside the allow-list → 400. 200:

{ "operation": "update", "affected": 5, "ids": ["...", "..."] }

The whole batch runs in one transaction and emits exactly one audit row (bulk_update / bulk_set_field / bulk_delete).

Authentication

By default the router reads the tenant id from an X-Tenant-Id header (demo-grade). In production you pass an auth_dependency that yields the request's session and a tenant_resolver that maps it to a tenant id; an unresolvable auth → 401.


Python API

All public symbols import from the top-level package:

from vue_pinia_crud_store import (
    EntitySpec, CrudHandlers, build_crud_router, NotFound,
    CrudStore, InMemoryStore, ListPage,
    FilterSpec, compile_filters, SUPPORTED_OPS,
    encode_cursor, decode_cursor,
    BulkRequest, BulkResponse, BulkStatement, BulkError,
    build_bulk_statement, affected_from_status, make_bulk_response, MAX_BULK_IDS,
    WIDGET_SPEC, EVENT_SPEC,
)

EntitySpec

EntitySpec(
    resource, table_name, pk_column="id",
    columns=(), filterable_columns=None, patchable_columns=None,
    required_create_columns=(), jsonb_columns=(), has_updated_at=True,
)

filterable_columns defaults to all columns; patchable_columns defaults to columns minus {pk_column, "created_at", "updated_at"}.

CrudHandlers

Framework-free async CRUD logic. CrudHandlers(spec, store, *, audit_emitter=None, id_validator=None).

Method Returns Notes
list(tenant_id, *, limit=50, cursor=None, filters=None) {<resource>: [...], "next_cursor": ...} filters is a list of spec dicts. Unknown column → ValueError.
get(tenant_id, row_id) row dict NotFound when absent / hidden.
create(tenant_id, data) created row dict Unknown / missing-required column → ValueError. Emits a create audit event.
patch(tenant_id, row_id, patch) updated row dict NotFound / ValueError. Emits an update audit event.
delete(tenant_id, row_id) {"deleted": True, <pk>: ...} NotFound. Emits a delete audit event.
bulk(tenant_id, body) {"operation", "affected", "ids"} body is a bulk request dict. BulkError on bad input. One audit event.

build_crud_router

build_crud_router(
    spec, store, *,
    tenant_resolver, audit_emitter=None, id_validator=None,
    auth_dependency=None, tags=None,
) -> fastapi.APIRouter

Raises RuntimeError if FastAPI is not installed (the rest of the package works without it).

CrudStore / InMemoryStore

CrudStore is the seam interface (async list_rows / get_row / create_row / patch_row / delete_row / bulk). InMemoryStore(pk_column="id") is the dependency-free default. ListPage(rows, next_cursor) is the list_rows return.

Filters

Symbol Description
FilterSpec(field, op, value=None) A dataclass; validates op against SUPPORTED_OPS at construction. FilterSpec.from_dict(d) builds one from a dict.
compile_filters(filters, allowed_columns, *, table_alias, start_param=1) (sql_fragment, params). Empty → ("TRUE", []). Raises ValueError on a non-allow-listed column, a bad in/nin value, or a bad alias.
SUPPORTED_OPS The tuple of valid operator strings.

Pagination

Symbol Description
encode_cursor(ts, row_id) → opaque URL-safe base64 token (padding stripped). Naive datetimes are normalized to UTC.
decode_cursor(token) (ts, row_id) (aware UTC datetime + string id) or None on every failure path (None/empty/bad-base64/bad-json/missing-keys/bad-timestamp). Never raises.

Bulk

Symbol Description
BulkRequest(operation, ids, patch=None) Validates the operation and the id-count cap (MAX_BULK_IDS = 500) at construction. from_dict(d) builds one.
build_bulk_statement(body, *, table_name, pk_column, patchable_columns, has_updated_at=True, id_validator=None) BulkStatement(sql, params, audit_op, diff). Raises BulkError on a bad id / patch.
affected_from_status(status_str, prefix) Parse a driver command tag ("UPDATE 5") → row count (0 on anything unexpected).
make_bulk_response(body, affected) BulkResponse(operation, affected, ids); .to_dict() for the JSON body.

Exceptions

Symbol Maps to Raised when
NotFound HTTP 404 a row is absent or hidden by tenant scope
BulkError (a ValueError) HTTP 400 a malformed bulk request (bad id, empty/invalid patch, key outside allow-list)

Example specs

WIDGET_SPEC — a generic mutable entity (has updated_at). EVENT_SPEC — a generic append-only entity (has_updated_at=False). Both are domain-neutral illustrations; define your own (see FORKING.md).