next-kit changelog
Every next-kit release, newest first.
Versions
0.15.0
3 added, 1 changedAdded
i18n(new subpath, no next-intl import):LocaleConfig,DEFAULT_LOCALES,hasLocaleandnegotiateLocale(saved user locale, then Accept-Language q-order with a region fallback, then the default). The header is capped at 1 KB and 20 entries before parsing.i18n/next-intl(new subpath):createRequestConfig(injected app and package catalog loaders, merged deeply with the app's last) andcreateI18nMiddleware(next-intl's middleware, "as-needed" prefix,NEXT_LOCALEcookie on an optional parent domain, then the app's own step). PlusmergeMessagesandresolveRequestConfig.next-intl^4.14.0 is an optional peer dependency; only this subpath needs it.seo:hreflangAlternates(config, path, baseUrl).
Changed
seo:pageMetadatatakesalternates: { languages }and writes it next to the canonical.
0.14.0
2 added, 1 changedAdded
account(new subpath): account export and delete across apps.createAccountHandlersgives an app POST handlers for/api/internal/account/{export,delete}behind its own bearer secret (ACCOUNT_FANOUT_SECRET, at least 32 bytes), with a zod-checked{ userId }body and no-store answers.fanOutlets the hub call every registered app with that app's secret: https only (http on localhost), redirects never followed, a 10 s timeout, three tries for delete, and unset secrets reported asnot_configured.exportBundlepacks the results into one JSON download. PlussecretFor,appUrl,usableSecretand theAccountAppregistry type.inbox(new subpath): invites and notifications in the identity database.createInboxStore(invites upserted by id and held by one app, notifications with an unread filter,markRead, a 90-day TTL,deleteForfor account delete),createInboxRoutesfor the hub's/api/internal/inbox(the matching secret picks the app, checked against every app with no early exit; another app's invite id is a 409),createInboxClientfor apps,matchAppandinboxIndexSpecs.
Changed
mongo:buildIdentityIndexesalso builds the inbox indexes.
0.13.0
3 addedAdded
auth: Discord link for the hub.discordLinkConfig(env, hubUrl)(null whenDISCORD_CLIENT_IDorDISCORD_CLIENT_SECRETis unset, which turns the feature off),createDiscordLinkRoutes(POSTstartbehindrefuseCrossSite, GETcallback, POSTunlink; a signed, 10-minute, host-only state cookie; writes onlydiscordIdanddiscordUsername, never a token or anaccountrow;?discord=linked|taken|error),findUserByDiscordId(refuses banned users),DISCORD_SCOPESandDISCORD_STATE_COOKIE.mongo:buildIdentityIndexesadds a partial unique index onuser.discordId(IDENTITY_INDEXES.userDiscordId). A duplicate-key error on link maps totaken.api-keys: scopes.createApiKeyStore({ scopes })declares the app's names,issue(userId, scopes?)defaults to["*"]and refuses undeclared names,ApiKeyInfoandApiKeyMatchcarryscopes(a stored key without the field reads as["*"], so nothing migrates), andwithApiKey(handler, { scope })answers 403insufficient_scope. NewhasScope,normalizeScopes,ALL_SCOPESandAPI_INSUFFICIENT_SCOPE.
0.12.2
1 fixed, 1 changedFixed
check'snext-kit migrate-identitynow skips users without a numericosuId(system accounts) entirely: they're never grouped with other users, never inserted intoidentity, and never get anidMapentry, so references pointing at them are left untouched instead of being rewritten to nowhere.MigrateReportgainsusersSkipped; the CLI's summary line reports it alongsideusersSeen/usersWritten.
Changed
auth's oldSessionReadertype (the better-auth instance shape used bygetOsuUser/getSessionUser/requireSession/requireAdmin) is renamed toOsuAuthInstance, soSessionReadercan mean one thing:createSessionReader's own instance type (session-reader.ts), which the barrel used to export only asSessionReaderInstanceto dodge this exact collision.session.tskeeps aSessionReaderalias ofOsuAuthInstancefor source compatibility, andsession-reader.tskeeps aSessionReaderInstancealias ofSessionReader(the barrel exports both —bb/packs/pools/haruhime.moeall importSessionReaderInstance, none import the oldSessionReader, so this is a non-breaking patch for every real consumer).
0.12.1
1 changedChanged
check'snext-kit migrate-identityis idempotent now that the hub is live andidentityholds real users who signed in there directly: a source group's osuId matching an existing identity user merges into it (that user's_idand fields win outright, only its own missing fields get filled from the source) instead of refusing on a non-empty identity. Accounts dedupe byproviderId+accountIdand API keys byhashagainst identity's own existing rows too, so a second--executerun inserts nothing new.MigrateReportgainsusersMerged.--drop-oldis unchanged: still its own run, still refusing whileidentityis empty.
0.12.0
1 added, 2 changedAdded
- Identity core (breaking for every app — see below):
mongo'screateMongogainsidentityDbName/getIdentityDb()so the hub reads anidentitydatabase on the same client,buildIdentityIndexesbuilds its four indexes,auth'screateOsuAuthgainscookieDomain/trustedOriginsfor the hub (every better-auth cookie, including OAuth state and PKCE, on the parent domain) and a 30-day session with a 1-dayupdateAge,createSessionReaderreads a satellite's session with zero database writes (raw cookie verification, nobetterAuth()instance) and pings the hub to refresh a session pastupdateAge,getSessionUser/requireSession/requireAdminread either source and refuse a banned user,server'ssafeAbsoluteNextis the hub's exact-hostname allowlist for a satellite's absolutenext(it returns the normalized URL),hubSignInUrlbuilds a satellite's link to the hub's sign-in page through the same allowlist, andcheck'snext-kit migrate-identitymergesbb/packs/poolsusers intoidentitybyosuId(dry run by default).
Changed
- Breaking.
mongo'sonConnectnow takes one{ db, identityDb?, client }argument instead of(db, client). - Breaking. The identity user gains
locale,notificationPrefs,bannedAt,banReason,limits,discordIdanddiscordUsername(auth'sIDENTITY_USER_FIELDS, merged into everycreateOsuAuthinstance'sadditionalFields, single-DB apps included).OsuSessionUserandOsuSessiongainbannedAt. packs'systemfield does not move with this release: it stays a plain field on packs' ownusercollection for now. Once packs cuts over to the sharedidentitydatabase (after the migration script runs),systemmust move to an app-side profile collection keyed byuserId, because the identity user row is shared withbbandpoolsand has no room for one app's own flag. Do that move in the same deploy that switches packs tocreateSessionReader, not before.
- Identity core (breaking for every app — see below):
0.11.1
1 fixedFixed
HARUHIME_ORG's email is haruhime@haruhime.moe and its Discord link is https://haruhime.moe/discord.
0.11.0
1 addedAdded
legalMarkdownTransform(site)inlegal: amdxToMarkdowntransformsentry that turns each self-closing legal block tag (<LegalContact />,<DataWeKeep />,<Processors />,<YourRights />,<DmcaNotice />,<NoWarranty />,<Changes />and<Changes date="YYYY-MM-DD" />) into Markdown carrying the same words as its React block. Without it, an app that drops the legal blocks straight into itscontent/legal/*.mdxloses that text from its.mdmirrors and llms-full.txt, sincemdxToMarkdownstrips unknown capitalized JSX. The seven blocks' sentences and list items now live in a newlegal/copy.ts, shared byblocks.tsxandlegalMarkdownTransformso the two outputs can't drift apart;blocks.tsx's rendered output is unchanged.
0.10.0
1 changedChanged
- Breaking.
next-kit check's legal standard requires all five pages of the legal convention:content/legal/{terms,privacy,your-privacy-rights,copyright,disclaimers}.mdx(was terms and privacy only).
- Breaking.
0.9.0
1 added, 1 changedAdded
legalentry point: the five-page legal convention every service ships (terms, privacy, your-privacy-rights, copyright, disclaimers). ALegalSiteconfig (site name, operator, contact email, effective date, data stores, processors, cookies), seven plain server-safe blocks an app drops into its own legal MDX (LegalContact,DataWeKeep,Processors,YourRights,DmcaNotice,NoWarranty,Changes; no hooks, no@haruhimemoe/ui), andlegalEntries(site, pages?)for the five standardContentEntryrecords so apps stop hand-writing the same titles and descriptions. next-kit's first.tsxentry point.LegalSite.hostingadds an optional line toDmcaNotice;Changestakes an optional per-pagedate.
Changed
- The
@haruhimemoe/uipeer range also allows^0.18.0.
0.8.0
3 changedChanged
- Breaking. The
@haruhimemoe/uipeer range is now^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0(0.15 to 0.17 not yet published):DeleteAccountFormneeds ui'sConfirmDialog. DeleteAccountFormis a "Delete my account" button that opens a dialog: what goes is its description, the username is typed there, and a refusal or no answer is said in the dialog, which stays open. Once the account is deleted, focus moves to the "Your account is deleted." line.- The account components use ui's text tones (
Text,textClasses) and Button's own width. No change in how they look.
- Breaking. The
0.7.0
1 addedAdded
vcsentry point:createRevisionStore, document history in MongoDB on top of@haruhimemoe/vcs(a new optional peer). Saves name their base revision and merge onto anything newer; conflicts write nothing and come back for the client to resolve. Also revert, diffs, author renames, history removal and autosave pruning.
0.6.2
1 changedChanged
- Peer range accepts @haruhimemoe/ui 0.12 and 0.13 (0.13 not yet published).
0.6.1
1 changedChanged
- Peer range accepts @haruhimemoe/ui 0.11.
0.6.0
4 added, 2 changedAdded
docsentry point: a content registry (defineContent,CONTENT_SECTIONS,SECTION_LABELS) and the path helpers (contentPath,markdownPath,findEntry,contentParams) for an app's docs, guides and legal pages, plus app-made extra entries like bb's tag pages. No runtime imports.mdxToMarkdownindocs: converts bb-flavored MDX to plain Markdown (callouts to blockquotes, import/export lines dropped, capitalized JSX removed, root-relative links and images absolutized, a title heading added when missing). Content inside fenced code blocks is left untouched. Still no runtime imports.docs/filesentry point:readContentMarkdownreads and converts a registered entry's markdown file, andcontentFileDriftcompares a registry against the files on disk. Loadsnode:fs.contentLlmsTxt,contentLlmsFull,contentSitemapandcontentRewritesindocs: llms.txt (sections in order Docs, Guides, API, Legal, empty ones left out), llms-full.txt (each entry's own leading H1 stripped, sincellmsFullwrites the part title), sitemap records per section (an index path, each entry, each extra) and the one rewrite rule for a content page's ".md" mirror. Built onllmsTxt/llmsFull/SitemapRecordfromseo. Still no runtime imports.
Changed
- Breaking (check only).
next-kit checknow also checks files undercontent/and adds thebrandandlegalstandards:brand/page.tsxandlegal/page.tsxpluslegal/[x]/page.tsx,legal/[x]/md/route.ts,content/legal/terms.mdxandcontent/legal/privacy.mdxare required on every app. Adocsstandard (docs/page.tsx,docs/[x]/page.tsx,docs/[x]/md/route.ts) joins in oncesrc/app/api/v1exists or anycontent/docsfile does; aguidesstandard joins in only once acontent/guidesfile does. Theapistandard drops itsdocs/api/page.tsxrequirement in favor ofcontent/docs/api.mdx.checkStandardstakes a secondcontentFilesargument;StandardResult.missingentries now carry their ownsrc/app/orcontent/prefix. This is a 0.x minor: an app with no/brand,/legalroute or legal content failsnext-kit checkin CI until it adds them. @haruhimemoe/uipeer range also covers 0.10.0 (not yet published).
0.5.0
3 added, 1 changedAdded
buildSecurityTxttakescontactUrl, listed before the email.api-keysentry point: the shared key format (h+ two letters +_, then 32 random bytes),createApiKeyStoreoverapi_keys, andcreateApiKeyGuardwithAPI_LIMITSfor/api/v1routes. Moved from packs.next-kit checkbin: fails when an app is missing a standard route.
Changed
@haruhimemoe/uipeer range now covers 0.5 through 0.9.
0.4.0
1 addedAdded
seo: a short title suffix.Site.shortTitleSuffix(like "pools") andpageMetadata'stitleSuffixoption:"auto"(the default) keeps "keyword · host" and switches to "keyword · pools" only when the full title passes 60 characters (TITLE_MAX) and the site sets a short suffix;"full","short"and"none"force one.pageTitletakes the same mode (default"full") andnotFoundMetadatashortens like"auto". Sites withoutshortTitleSuffixget the same titles as before.
0.3.0
1 addedAdded
@haruhimemoe/next-kit/seo, for www, packs, pools and bb. No runtime imports (Next's types only).- Metadata:
siteMetadata,homeMetadata,pageMetadata,notFoundMetadata,pageTitle("keyword · host") andclampDescription(160 characters, cut at a word).pageMetadataalways sets the canonical and og:url together and always writes a full openGraph with the site's images, so a page never loses its preview. robotswith the AI crawler stance written down ("allow","block-training"or"block-all") and theAI_BOTStable.sitemapEntries: absolute URLs, one per URL, lastmod only when it's a real date, and an error past 50,000 URLs.- JSON-LD builders in
ld(graph, Organization, WebSite with SearchAction, WebApplication, BreadcrumbList, ItemList, FAQPage, HowTo, TechArticle, CreativeWork, Dataset) with stable@ids,HARUHIME_ORG, andserializeLdfor script-safe JSON. llmsTxt,llmsFullandtextResponsefor /llms.txt and /llms-full.txt.
- Metadata:
0.2.1
1 fixedFixed
- The
@haruhimemoe/osupeer range also allows^0.4.0, so apps on osu 0.4.0 install without npm's ERESOLVE.
- The
0.2.0
3 added, 1 changedAdded
@haruhimemoe/next-kit/auth-react: the account components, styled with@haruhimemoe/ui(a new optional peer, ^0.5.0).SignInWithOsu,SignOutButton,AccountMenu(ui's HeaderMenu with the app's links) andDeleteAccountForm(type the username, then one DELETE), withcreateAuthComponents(authClient, kit)to bind the app's client and account kit. Moved from packs, pools and bb, which each had a copy.osuAvatarSrcandOSU_AVATAR_HOSTS: an osu! avatar URL only from a.ppy.sh or osu.ppy.sh. Moved from pools and bb.signInErrorMessage: reads better-auth's flat or nested error message, as packs did.
Changed
auth-reactnow also loads@haruhimemoe/uiat runtime.
0.1.0
6 addedAdded
@haruhimemoe/next-kit/server:jsonError,ERROR_CODES,noStore,parseJsonBodywith a per-route cap,parseIdList,refuseCrossSite,clientIpandrateLimitSubject,createRateLimiterandcreateBudgetover one MongoDB counter shape,refuseWithoutBearerandsameSecretfor machine routes,safeNextPath,signInHrefandbuildSecurityTxt. Moved from packs.haruhime.moe and pools.haruhime.moe, taking pools' behavior where they differed.@haruhimemoe/next-kit/env:createServerEnvwith SKIP_ENV_VALIDATION and the production placeholder guard, the osu! app's five variables, and per-call readers (optionalSecret,readIdSet,readFlag,readOrigin).@haruhimemoe/next-kit/mongo:createMongo(one client per process, Mongoose on the same client, anonConnecthook),ensureIndexesthat skips and logs an index existing duplicates break,ttlIndexanddefineCollections.@haruhimemoe/next-kit/auth:createOsuAuth(better-auth with osu! genericOAuth, no stored osu! tokens, trusted account linking, errors to the sign-in page, the signed-in marker cookie, guard hooks and extra user fields),AUTH_INDEX_SPECSandgetOsuUser.@haruhimemoe/next-kit/auth-react:createSignedInMarker,createAccountStore,useAccount,createAccount,RestoreSignedInandosuSignIn.@haruhimemoe/next-kit/testing:startMemoryMongo,setupTestDb,setupMswand the fake osu! app env.