Observed since 21 Oct, 08:56 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Type-safe URL builders + codex consumers for the freeside-storage URL contract. EffectTS Schema validates inputs/outputs; URL_CONTRACT_V1 is the source of truth.
@freeside-storage/clientType-safe URL builders + codex consumers for the freeside-storage URL contract. EffectTS Schema validates inputs; typed errors make failure modes first-class; codex-authority cascade for per-token overrides (read-only).
import {
miberaImageURL, // sync, hash → URL
miberaImageURLByToken, // Effect, tokenId → URL (codex)
miberaMetadataURL, // sync, tokenId → manifest URL
mstImageURL, // Effect, tokenId → MST image (handles timestamp-suffix)
mstMetadataURL, // sync, tokenId → honeyroad endpoint
grailImageURL, // Effect, tokenId → grail image (codex authority)
} from "@freeside-storage/client";
Sync builders are pure functions. Async builders return Effect.Effect<string, AssetError> and integrate codex authority + remote fetch.
Three things bare TypeScript doesn't give you:
import { TokenId, Sha40 } from "@freeside-storage/client";
import { Schema } from "effect";
const decoded = Schema.decodeEither(TokenId)(rawNumber);
// → Either<ParseError, ValidatedTokenId>
Bad inputs surface AT THE BOUNDARY with a typed error, not three layers deep in a render tree.
Effect<A, E, R> — A is success, E is a typed error union, R is the dependency context. Errors are first-class values:
type AssetError =
| NotFoundError // collection + tokenId
| MalformedURLError // raw + reason
| VersionDriftError // expected + got
| MissingHashError // tokenId + source
| NotGrailError // tokenId
Consumers Effect.catchTag("NotFoundError", ...) to handle each case.
import { Effect, pipe } from "effect";
import { mstImageURL } from "@freeside-storage/client";
const fetchWithFallback = (tokenId: number) =>
pipe(
mstImageURL(tokenId),
Effect.timeout("3 seconds"),
Effect.retry({ times: 2 }),
Effect.catchTag("NotFoundError", () =>
Effect.succeed("https://example.com/fallback.webp"),
),
);
Each pipe step is a composable layer. Same primitives across honeyroad, dimensions, midi.
URL_CONTRACT_V1 (from @freeside-storage/protocol) is the source of truth. Builders return URLs where bytes live TODAY — both canonical routes and legacyRoutes are consumed.
Mibera image URLs resolve via the reveal_phase{N}/images/{hash}.png legacyRoute shape (where bytes live), since URL_CONTRACT_V1 marks the canonical Mibera/final/{tokenId}.png as gated by the optional mibera-2 polish cycle.
Grail URLs are NOT constructed from slug + extension — extensions are heterogeneous (.png / .PNG), some slugs contain spaces. The codex mibera-image-urls.json publishes the full URL per tokenId; the client reads.
Per [[consuming-codex-overrides]] doctrine. The client reads the published codex artifacts; never writes.
| codex source | consumer | what it provides |
|---|---|---|
mibera-image-urls.json | lookupMiberaURL(tokenId) | tokenId → published image URL |
grails.jsonl | lookupGrail(tokenId) | grail identity (id, name, slug, category) |
In-memory cache per source URL. Call resetMiberaURLCache() / resetGrailCache() to clear (testing).
pnpm add @freeside-storage/client effect
pnpm -F @freeside-storage/client test
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0Registry latest | 2 May 2026 | 5 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 21 Oct, 08:56 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:2a0ebdc16b4525405375da4a9b6a35271c006c482e9f77faf5b77e7b2eab1495