OapiCodemode.SpecStore.ETS (oapi_codemode v0.5.0)

Copy Markdown View Source

The OapiCodemode.SpecStore the library ships with: one ETS table.

For tests, single-node deployments and small specs. The handle is the table itself — new/0 creates it and hands it back — so the caller owns it and its lifetime is the caller's business: link it to a supervised process to keep it, or let it die with the process that made it.

Rows are tagged tuples in one :set, so every read is a key lookup:

{{:canonical, spec_id}, binary}          # the evidence, shared by versions
{{:operation, ref, op_id}, %Operation{}}
{{:component, ref, kind, name}, json}
{{:index, ref}, [%OperationSummary{}]}
{{:meta, ref}, %Meta{}}                  # written last: the commit marker

spec_id is the content hash itself — nothing else is needed to name a document, and it makes put/2 idempotent without a read-modify-write. put/2 writes in the order listed — canonical bytes, then the operation and component rows, then the index, and {:meta, ref} last — and all five reads gate on {:meta, ref} first, so a projection mid-write is invisible rather than half-visible, and a concurrent putter that finds the marker knows the rest is already there. Every write is the same bytes at the same key, so racing putters cannot interleave into something neither of them wrote.

pointer/4 decodes the canonical document per call. That is the slow path by design (SpecStore's docs), and a store meant for small specs should not hold a decoded copy of every document it has ever seen.

Summary

Functions

Deletes one projection's rows, and the canonical bytes if it was the last version holding them.

Creates a store table and returns it — the handle a store tuple carries.

Functions

delete_projection(table, ref)

@spec delete_projection(:ets.table(), OapiCodemode.SpecStore.ref()) :: :ok

Deletes one projection's rows, and the canonical bytes if it was the last version holding them.

Not an OapiCodemode.SpecStore callback, and deliberately not: a store is append-only in the version dimension precisely so a ref handed out earlier keeps reading what it always read, and a host's own store owns its own retention. This exists for the one store whose lifetime the library owns — OapiCodemode.Registry's private table, where re-registering an api name through OapiCodemode.ingest_and_register/4 supersedes the projection that name held and nothing would otherwise ever free it.

So the append-only promise holds for every store but that one, and there it is narrower than it sounds: the registry checks that no live binding names the ref, not that nobody is mid-read against it. A run that took its ref before a concurrent re-registration keeps reading a projection that can be deleted underneath it — every read then answers {:error, :not_found} ({:meta, ref} goes first, below), which surfaces as a describe error payload or the proxy's :resolve phase. A stale read, never a wrong one: no other document's rows can appear under a ref this one wrote. Deleting a ref a registration still names is the caller's mistake to avoid.

{:meta, ref} goes first, so a concurrent reader sees :not_found rather than half a projection — put/2's write order, reversed.

new()

@spec new() :: :ets.table()

Creates a store table and returns it — the handle a store tuple carries.