@haruhimemoe/next-kit 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @file src/docs/registry.ts
3
+ * @desc The content registry every haruhime.moe site builds its docs, guides and legal pages
4
+ * from: a frozen list of sections, the entry shape each page fills in, the app-made extra
5
+ * entries a section's nav and search also carry (like bb's tag pages), and the validation
6
+ * that catches a bad slug, a duplicate, a made-up date or a blank title at build time
7
+ * instead of at a 404. Pure: no node: imports, so it runs anywhere.
8
+ * @author David @dvhsh (https://dvh.sh)
9
+ * @created Sun Oct 4, 2026
10
+ * @modified Sun Oct 4, 2026
11
+ */
12
+ /** A slug is lowercase words separated by single hyphens, like "make-a-pack". */
13
+ const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
14
+ /** A date is YYYY-MM-DD and a real calendar day. */
15
+ const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
16
+ /** Every content section a haruhime.moe site can have, in the order it's shown. */
17
+ export const CONTENT_SECTIONS = ["docs", "guides", "legal"];
18
+ /** The nav label for each section. */
19
+ export const SECTION_LABELS = {
20
+ docs: "Docs",
21
+ guides: "Guides",
22
+ legal: "Legal",
23
+ };
24
+ const assertEntries = (section, entries) => {
25
+ const seen = new Set();
26
+ for (const entry of entries) {
27
+ if (!SLUG_PATTERN.test(entry.slug))
28
+ throw new Error(`docs: bad slug "${entry.slug}" in ${section}`);
29
+ if (seen.has(entry.slug))
30
+ throw new Error(`docs: duplicate slug "${entry.slug}" in ${section}`);
31
+ seen.add(entry.slug);
32
+ if (!DATE_PATTERN.test(entry.lastUpdated) || Number.isNaN(Date.parse(entry.lastUpdated)))
33
+ throw new Error(`docs: bad lastUpdated "${entry.lastUpdated}" in ${section} "${entry.slug}"`);
34
+ if (!entry.title.trim())
35
+ throw new Error(`docs: blank title in ${section} "${entry.slug}"`);
36
+ }
37
+ };
38
+ const assertExtras = (section, extras) => {
39
+ const seen = new Set();
40
+ for (const extra of extras) {
41
+ if (!extra.href.startsWith("/"))
42
+ throw new Error(`docs: extra href "${extra.href}" must start with "/" in ${section}`);
43
+ if (seen.has(extra.href))
44
+ throw new Error(`docs: duplicate extra href "${extra.href}" in ${section}`);
45
+ seen.add(extra.href);
46
+ if (!extra.title.trim())
47
+ throw new Error(`docs: blank title for extra "${extra.href}" in ${section}`);
48
+ }
49
+ };
50
+ /**
51
+ * @function defineContent
52
+ * @param input {ContentInput} each section's entries, and the app-made extras under `extra`
53
+ * @returns {Content} every section filled in (empty arrays for the ones left out), and
54
+ * `sections` listing only the non-empty ones, in `CONTENT_SECTIONS` order
55
+ * @throws {Error} naming the section and slug or extra href, for a bad slug, a duplicate slug,
56
+ * a duplicate extra href, a `lastUpdated` that isn't a real `YYYY-MM-DD` date, or a blank title
57
+ */
58
+ export const defineContent = (input) => {
59
+ const entries = {};
60
+ const extra = {};
61
+ const sections = [];
62
+ for (const section of CONTENT_SECTIONS) {
63
+ const sectionEntries = input[section] ?? [];
64
+ const sectionExtras = input.extra?.[section] ?? [];
65
+ assertEntries(section, sectionEntries);
66
+ assertExtras(section, sectionExtras);
67
+ entries[section] = sectionEntries;
68
+ extra[section] = sectionExtras;
69
+ if (sectionEntries.length > 0 || sectionExtras.length > 0)
70
+ sections.push(section);
71
+ }
72
+ return { sections, entries, extra };
73
+ };
74
+ /**
75
+ * @function contentPath
76
+ * @param section {ContentSection} the section the page lives under
77
+ * @param slug {string} the entry's slug
78
+ * @returns {string} the page's path, like "/guides/make-a-pack"
79
+ */
80
+ export const contentPath = (section, slug) => `/${section}/${slug}`;
81
+ /**
82
+ * @function markdownPath
83
+ * @param section {ContentSection} the section the page lives under
84
+ * @param slug {string} the entry's slug
85
+ * @returns {string} the page's raw markdown path, like "/guides/make-a-pack.md"
86
+ */
87
+ export const markdownPath = (section, slug) => `${contentPath(section, slug)}.md`;
88
+ /**
89
+ * @function findEntry
90
+ * @param content {Content} a registry from `defineContent`
91
+ * @param section {ContentSection} the section to search
92
+ * @param slug {string} the entry's slug
93
+ * @returns {ContentEntry | undefined} the matching entry, or undefined
94
+ */
95
+ export const findEntry = (content, section, slug) => content.entries[section].find((entry) => entry.slug === slug);
96
+ /**
97
+ * @function contentParams
98
+ * @param content {Content} a registry from `defineContent`
99
+ * @param section {ContentSection} the section to list
100
+ * @returns {{ slug: string }[]} one param per entry, for a dynamic route's `generateStaticParams`
101
+ */
102
+ export const contentParams = (content, section) => content.entries[section].map((entry) => ({ slug: entry.slug }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haruhimemoe/next-kit",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "The Next.js plumbing the haruhime.moe tools share: JSON route helpers, rate limits and budgets in MongoDB, bearer machine auth, zod env parsing, a connect-once MongoDB client with safe index builds, better-auth with osu! sign-in, the signed-in marker and account store for the browser, SEO: metadata, robots.txt, sitemaps, JSON-LD and llms.txt, and per-app API keys and the /api/v1 guard.",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -62,6 +62,14 @@
62
62
  "types": "./dist/api-keys/index.d.ts",
63
63
  "default": "./dist/api-keys/index.js"
64
64
  },
65
+ "./docs": {
66
+ "types": "./dist/docs/index.d.ts",
67
+ "default": "./dist/docs/index.js"
68
+ },
69
+ "./docs/files": {
70
+ "types": "./dist/docs/files/index.d.ts",
71
+ "default": "./dist/docs/files/index.js"
72
+ },
65
73
  "./package.json": "./package.json"
66
74
  },
67
75
  "files": [
@@ -92,7 +100,7 @@
92
100
  },
93
101
  "peerDependencies": {
94
102
  "@haruhimemoe/osu": "^0.2.0 || ^0.3.0 || ^0.4.0",
95
- "@haruhimemoe/ui": "^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0",
103
+ "@haruhimemoe/ui": "^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0",
96
104
  "better-auth": "^1.7.5",
97
105
  "mongodb": "^7.6.0",
98
106
  "mongodb-memory-server": "^11.3.0",