Quickstart - Data Versioning¶
Instead of overwriting data in place, SpecStar keeps every change as a new revision.
In this quickstart, we will show how to use SpecStar resource revision control to keep change history in your application.
With SpecStar, each create, update, and delete operation can produce a new revision. This allows you to:
- keep a full history of changes
- inspect older versions of a resource
- implement soft delete without losing data
- build audit-friendly applications with minimal extra work
⏱️ Estimated time: 5 minutes
1. Define and register a resource¶
We start with a simple Issue resource.
from datetime import datetime
from typing import Literal
import msgspec
from fastapi import FastAPI
from specstar import spec, Schema
class Issue(msgspec.Struct):
title: str
description: str | None = None
status: Literal["open", "in_progress", "resolved"] = "open"
severity: Literal["low", "medium", "high"] = "medium"
assignee: str | None = None
due_date: datetime | None = None
resolved_at: datetime | None = None
app = FastAPI()
spec.add_model(Schema(Issue, "v1"))
spec.apply(app)
Then get the resource manager:
2. Create the first revision¶
Each create() call creates the first revision of the resource.
info = mgr.create(
Issue(
title="Search returns duplicate results",
severity="high",
assignee="alice",
)
)
The returned info contains revision metadata:
Example output:
You can fetch the current version of the resource with:
3. Update the resource and create a new revision¶
Instead of overwriting the existing data, this creates a new revision.
info_updated = mgr.update(
info.resource_id,
Issue(
title="Search returns duplicate results",
severity="high",
assignee="bob",
status="in_progress",
),
)
This produces a new revision:
Example output:
You can fetch both the latest version and an older revision:
new_issue = mgr.get(info.resource_id)
old_issue = mgr.get(info.resource_id, revision_id=info.revision_id)
print(new_issue.data)
print(old_issue.data)
This makes it easy to compare current and historical states of the same resource.
4. List revision history¶
You can inspect the full revision history of a resource:
revision_list = mgr.list_revisions(info.resource_id)
for rev in revision_list:
print(rev.revision_id, rev.created_time, rev.created_by)
This is useful for:
- audit trails
- debugging changes
- showing a change timeline in your UI
5. Soft delete without losing history¶
Deleting a resource does not have to mean losing it forever.
The resource is not physically removed — a new revision marks it as deleted.
You can still inspect older revisions if needed:
And if your application allows it, you can restore the resource later.
6. Why this matters¶
With revision control built into the resource layer, you do not need to design a separate history system for every model.
You now get, by default:
- current state access
- historical revision lookup
- soft delete support
- a foundation for audit logs and rollback workflows
You never lose data — you only move forward through revisions.
What’s next¶
If you plan to rely on revision history in a real deployment, backend setup is a natural follow-up so that history persists beyond a throwaway demo.
Next Steps:
Appendix: Full example¶
from datetime import datetime
from typing import Literal
import msgspec
from fastapi import FastAPI
from specstar import spec, Schema
class Issue(msgspec.Struct):
title: str
description: str | None = None
status: Literal["open", "in_progress", "resolved"] = "open"
severity: Literal["low", "medium", "high"] = "medium"
assignee: str | None = None
due_date: datetime | None = None
resolved_at: datetime | None = None
app = FastAPI()
spec.configure()
spec.add_model(Schema(Issue, "v1"))
spec.apply(app)
mgr = spec.get_resource_manager(Issue)
info = mgr.create(
Issue(
title="Search returns duplicate results",
severity="high",
assignee="alice",
)
)
print("resource_id:", info.resource_id)
print("revision_id:", info.revision_id)
info_updated = mgr.update(
info.resource_id,
Issue(
title="Search returns duplicate results",
severity="high",
assignee="bob",
status="in_progress",
),
)
print("updated revision_id:", info_updated.revision_id)
latest_issue = mgr.get(info.resource_id)
old_issue = mgr.get(info.resource_id, revision_id=info.revision_id)
print("latest issue:", latest_issue.data)
print("old issue:", old_issue.data)
revision_list = mgr.list_revisions(info.resource_id)
for rev in revision_list:
print("history:", rev.revision_id, rev.created_time, rev.created_by)
Run: