@hyrefly/template-sdk
v0.4.2
Published
Runtime contract every Hyrefly Tier-2 portfolio template plugs into — profile loader, hooks, types, helpers.
Readme
@hyrefly/template-sdk
Runtime contract every Hyrefly Tier-2 portfolio template plugs into.
A Hyrefly Tier-2 template is a standalone static bundle (Vite or Next.js with
output: "export") that gets served under the student's URL
(hyreflyai.com/<username>). At render time, the template's JS
fetches the student's profile from Hyrefly's public API and replaces the
bundled demo data with the student's real bio, projects, experience, etc.
This package codifies the fetch + overlay pattern so each template doesn't reinvent it.
Install
npm install @hyrefly/template-sdkWrap your app root
import { ProfileProvider } from "@hyrefly/template-sdk";
export default function App() {
return (
<ProfileProvider>
<Hero />
<Projects />
<Contact />
</ProfileProvider>
);
}Read the profile from any component
import { useProfile, stripHtml, normalizeUrl } from "@hyrefly/template-sdk";
const DEMO = { full_name: "Jane Doe", bio: "<p>Demo bio.</p>" };
function Hero() {
const profile = useProfile(); // null in preview, PublicProfile when live
const name = profile?.full_name || DEMO.full_name;
const bio = stripHtml(profile?.bio || DEMO.bio);
return <h1>{name}<p>{bio}</p></h1>;
}Every section ships its own bundled demo data and falls back to it when
profile is null (preview / unpublished / network blip). That keeps the
template's catalog preview pristine.
Hooks
| hook | returns |
| ------------------ | --------------------------------------------------- |
| useProfile() | PublicProfile \| null |
| useRawProfile() | alias of useProfile() |
| useProfileState()| { profile, loaded, isLive } |
| useProfileContext() | the full ProfileContextValue |
Helpers
stripHtml(html)— convert bio / project description HTML to plain text.fmtPeriod(start, end, current)—"2024-01 – Present"-style range.normalizeUrl(u)— addhttps://if missing.initialsOf(fullName)— two-letter avatar fallback.skillNames(skills)— flattenstring[] | { name: string }[]tostring[].projectCoverUrl(project)— pick cover → first gallery item → "".
Actions (resume + contact)
import { resumeUrl, submitContact } from "@hyrefly/template-sdk";resumeUrl(username) — résumé download
Builds the public URL to download the student's PDF. Drop into an
<a href> and let the browser do the rest:
const profile = useProfile();
if (profile) (
<a href={resumeUrl(profile.username)} download>
Download résumé
</a>
);submitContact(username, payload) — inbox message
POSTs the form to /api/public/profile/<u>/contact. Handles honeypot,
returns a normalised result with field-level errors on validation
failures and a rate-limit message on 429.
const result = await submitContact(profile.username, {
name: form.name,
email: form.email,
message: form.message,
source_template: "vcard-style",
});
if (result.ok) {
showThanks();
} else if (result.fieldErrors) {
highlightFields(result.fieldErrors);
} else {
showToast(result.error);
}The server enforces a per-IP rate limit (10/hr) and a per-recipient
limit (3/hr from the same fingerprint), so templates don't need to
track quotas client-side. Add a hidden honeypot input named
url_extra to your form and the SDK will forward it; bots that
auto-fill every field get accepted silently and discarded.
Types
PublicProfile, PublicProject, PublicExperience, PublicEducation,
PublicCertification, PublicAchievement, PublicMedia, PublicSkill.
What goes in <ProfileProvider> props?
| prop | what it does |
| ---------------- | ----------------------------------------------------- |
| apiBase | override the API host (defaults to prod or localhost) |
| previewOnly | skip the URL detection + fetch |
| initialProfile | inject a pre-fetched profile (SSR / testing) |
Detecting preview vs live mode
useProfileState() exposes { loaded, isLive }. isLive is true only
when the API returned a profile. loaded flips to true once the fetch
resolves (success or miss) so you can avoid a loading flash.
