mirror of
https://github.com/hanzoai/namespace.git
synced 2026-09-30 22:57:59 +00:00
Naming the database and rendering that name as a place were one primitive living in two repositories: hanzoai/cloud held the slugger, the layout and the resolver, and this package held only the value they produced. A fact with two homes is a fact that can disagree with itself, and these particular strings are directory names on live volumes and keys in live buckets — a disagreement does not fail, it opens an empty database beside a real one. So the whole primitive moves here and cloud imports it: Sanitize one injective slug for a name a person chose OrgProject the door those names walk through to become a namespace MustOrgProject Key a namespace rendered as the place one subsystem's file lives Path Key resolved against a data directory Sanitize refuses a name carrying an invisible rune (nothing downstream can be trusted not to trim it) and disambiguates every other fold with a hash of the raw bytes, so two spellings are two files. OrgProject is named for what it takes; Of already means something else here — the namespace a billing subject owns — and clobbering it would have been the same collapse in miniature. Every golden value in the tests was captured from the implementation that wrote the files already on disk, before the code moved, so the rendering is byte-identical and no live data is orphaned. Co-authored-by: Hanzo Dev <[email protected]>
49 lines
2.3 KiB
Markdown
49 lines
2.3 KiB
Markdown
# namespace
|
|
|
|
Names the one database an entity's data lives in.
|
|
|
|
```go
|
|
ns := namespace.MustRepo("42").WithGroup(Issues) // repo/42/issues
|
|
ns, err := namespace.Org(id) // org/<id>
|
|
namespace.System().WithGroup(Notices) // system/notices
|
|
```
|
|
|
|
A namespace is `kind[/id][/group]`, and its string form IS a path: `<dir>/<ns>.db`
|
|
on disk, a key prefix in object storage. So it is a value with constructors, not
|
|
a string with a validator — `Namespace` wraps an unexported field, and the only
|
|
routes to one are the constructors and `Parse`. Illegal names do not exist to be
|
|
caught.
|
|
|
|
Two rules do the work. Segments are `[a-z0-9][a-z0-9_-]*`, which leaves no
|
|
spelling of a separator, a dot-segment or a hidden name to reject later. And ids
|
|
fold to lowercase at construction, because on a case-insensitive filesystem
|
|
`org/Acme` and `org/acme` are two namespaces over one file — two handles, two
|
|
replication streams, one history, and it works on the laptop.
|
|
|
|
An id is the entity's stable identity, never a human-facing name: a rename must
|
|
be a row somewhere, not a file move and a severed history.
|
|
|
|
## From a chosen name, to a place
|
|
|
|
```go
|
|
slug := namespace.Sanitize(org) // one injective slug
|
|
ns, err := namespace.OrgProject(org, project) // org/<slug>[/<project>]
|
|
key, err := namespace.Key(ns, "tracker") // orgs/<slug>/tracker.db
|
|
path, err := namespace.Path(dataDir, ns, "tracker") // <dataDir>/orgs/<slug>/tracker.db
|
|
```
|
|
|
|
`Sanitize` reduces a name a person chose — an IAM org, a project — to a
|
|
`[a-z0-9-]` slug and is **injective**: names carrying an invisible rune are
|
|
refused outright (nothing downstream can be trusted not to trim them), and every
|
|
other fold is disambiguated by a hash of the raw bytes, so `Acme` and `acme` stay
|
|
two orgs and therefore two files. `OrgProject` is the door those names walk
|
|
through to become a namespace.
|
|
|
|
`Key` renders a namespace as the place one subsystem's file for it lives, and
|
|
`Path` resolves that against a data directory. One rendering, two consumers: the
|
|
local file and the object key durability ships it to are the same string by
|
|
construction, so they cannot drift apart.
|
|
|
|
`hanzoai/orm/db.Namespaces` resolves a namespace to an open handle — materialise,
|
|
open, cache, evict. That is the only other thing with an opinion about namespaces.
|