@nubbin/next 0.1.0 → 0.2.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.
- package/README.md +2 -3
- package/dist/index.d.ts +137 -29
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,6 +25,5 @@ await publishRoute(store, "/promotions/summer", hash); // pointer, then revali
|
|
|
25
25
|
page in the gap, and the publish appears to have silently not happened. A store rejection
|
|
26
26
|
propagates without invalidating, so a failed publish never purges a working page.
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<https://effekt.github.io/nubbin/>. MIT.
|
|
28
|
+
Requires Next.js 16 or newer. Read the [Nubbin documentation](https://nubbin.io) for the
|
|
29
|
+
complete Next.js integration reference. MIT.
|
package/dist/index.d.ts
CHANGED
|
@@ -2,31 +2,48 @@ import { Artifact, FieldHintData, ArtifactStore } from '@nubbin/core';
|
|
|
2
2
|
import { Metadata } from 'next';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Turns a resolved artifact into the `Metadata` Next emits into the document head, so the title
|
|
6
|
+
* and meta tags a page carries come from the document that was published rather than from the
|
|
7
|
+
* code serving it.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
9
|
+
* @param artifact - What `resolveArtifact` answered with. `null` is the unpublished case and
|
|
10
|
+
* belongs here, so a caller resolves once and hands the same value to the page and to
|
|
11
|
+
* `generateMetadata`.
|
|
12
|
+
* @returns `title` always, `description` and `robots` when the document carried them, and
|
|
13
|
+
* `canonical` moved under `alternates`, which is where Next reads one. A field the document
|
|
14
|
+
* omitted is omitted here rather than set to `undefined`. A `null` artifact answers `{}`,
|
|
15
|
+
* leaving the layout's own metadata standing for the 404 the page is about to render.
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* export async function generateMetadata({
|
|
19
|
+
* params,
|
|
20
|
+
* }: { params: Promise<{ slug?: string[] }> }): Promise<Metadata> {
|
|
21
|
+
* const { slug } = await params;
|
|
22
|
+
* return artifactMetadata(await resolveArtifact(store, slug));
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
20
25
|
*/
|
|
21
26
|
declare function artifactMetadata(artifact: Artifact | null): Metadata;
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
29
|
+
* Turns a hole's declared lifecycle into the `fetch` options Next reads, for a `resolveHole`
|
|
30
|
+
* fetching the value that fills it.
|
|
26
31
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
32
|
+
* @param spec - The hole's `FieldHintData`, as `compile` copied it from the block's `ui.fields`
|
|
33
|
+
* hint into the artifact node's `holes`. Pass it through rather than choosing an interval at
|
|
34
|
+
* the call site: the published artifact is what says how live the value is.
|
|
35
|
+
* @returns `{ next: { revalidate: spec.revalidate } }`, ready to spread into a `fetch` call.
|
|
36
|
+
* `0` reaches Next as `0` rather than being read as absent, which is Next's instruction not to
|
|
37
|
+
* cache the response at all.
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import type { HoleResolver } from "@nubbin/react";
|
|
41
|
+
*
|
|
42
|
+
* const resolveHole: HoleResolver = async ({ spec }) => {
|
|
43
|
+
* const response = await fetch(`${origin}/api/price`, holeFetchOptions(spec));
|
|
44
|
+
* return response.json();
|
|
45
|
+
* };
|
|
46
|
+
* ```
|
|
30
47
|
*/
|
|
31
48
|
declare function holeFetchOptions(spec: FieldHintData): RequestInit & {
|
|
32
49
|
next: {
|
|
@@ -35,31 +52,122 @@ declare function holeFetchOptions(spec: FieldHintData): RequestInit & {
|
|
|
35
52
|
};
|
|
36
53
|
|
|
37
54
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
55
|
+
* Points a route at an artifact and invalidates that one page, in that order — the body of the
|
|
56
|
+
* `POST /api/nubbin/publish` handler an application exposes, which is where `nubbin publish
|
|
57
|
+
* --origin <url>` sends `{ route, hash }`.
|
|
58
|
+
*
|
|
59
|
+
* `revalidatePath` reaches only the cache of the process that runs it, so this has to run inside
|
|
60
|
+
* the server that serves the page. Moving the pointer from a terminal instead leaves that server
|
|
61
|
+
* answering from its cache until it restarts.
|
|
62
|
+
*
|
|
63
|
+
* @param store - The store serving the application. `publish` is the only method called.
|
|
64
|
+
* @param route - The route to serve, exactly as it was compiled — `resolveArtifact` matches a
|
|
65
|
+
* pointer by its route string, so a route published under a different spelling never resolves.
|
|
66
|
+
* @param hash - The artifact to serve there. Write it first: this moves a pointer and never
|
|
67
|
+
* stores anything, and a pointer at a hash nothing has written is a live 404.
|
|
68
|
+
* @returns Nothing. Publishing the same route and hash twice is a no-op in the store and
|
|
69
|
+
* invalidates the page again, so a retried publish is safe.
|
|
70
|
+
* @throws Whatever `store.publish` raises, before anything is invalidated — a store rejecting an
|
|
71
|
+
* unwritten hash or an unaddressable route leaves the page that is live untouched.
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* export async function POST(request: Request) {
|
|
75
|
+
* const { route, hash } = (await request.json()) as { route: string; hash: string };
|
|
76
|
+
* await publishRoute(store, route, hash);
|
|
77
|
+
* return Response.json({ ok: true, route, hash });
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
41
80
|
*/
|
|
42
81
|
declare function publishRoute(store: ArtifactStore, route: string, hash: string): Promise<void>;
|
|
43
82
|
|
|
44
83
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
84
|
+
* Resolves whatever is published at a catch-all route: one pointer read, then one artifact read.
|
|
85
|
+
* The page component and `generateMetadata` both want the same answer, so wrap the call in
|
|
86
|
+
* React's `cache` rather than reading the store twice per request.
|
|
87
|
+
*
|
|
88
|
+
* @param store - The store the route was published through. Only `pointer` and `read` are
|
|
89
|
+
* called, so a read-only implementation serves the whole render path.
|
|
90
|
+
* @param slug - The catch-all param for the request, passed through `routeFromSlug`. `undefined`
|
|
91
|
+
* and the empty array are the root route.
|
|
92
|
+
* @returns The artifact serving that route, or `null` — for a route with no pointer, and for a
|
|
93
|
+
* pointer naming a hash the store no longer holds. Absence is a value rather than a failure:
|
|
94
|
+
* it is what the caller turns into `notFound()`, and it is why unpublishing produces a server
|
|
95
|
+
* 404 instead of an empty page.
|
|
96
|
+
* @throws Whatever the store raises. Both calls answer absence with `null`, so anything thrown
|
|
97
|
+
* from here is storage failing rather than a route that is not published.
|
|
98
|
+
* @example
|
|
99
|
+
* ```tsx
|
|
100
|
+
* export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
|
|
101
|
+
* const artifact = await resolveArtifact(store, (await params).slug);
|
|
102
|
+
* if (!artifact) notFound();
|
|
103
|
+
* return <Renderer artifact={artifact} registry={blockRegistry} resolveHole={resolveHole} />;
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
48
106
|
*/
|
|
49
107
|
declare function resolveArtifact(store: ArtifactStore, slug: readonly string[] | undefined): Promise<Artifact | null>;
|
|
50
108
|
|
|
51
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* Turns a Next catch-all param into the route string a store keys pointers and artifacts by.
|
|
111
|
+
*
|
|
112
|
+
* @param slug - The `slug` param of a `[...slug]` or `[[...slug]]` segment, as Next resolves it.
|
|
113
|
+
* `undefined` and the empty array both mean the root, which only the optional form matches.
|
|
114
|
+
* @returns `"/"` for an absent or empty slug, and otherwise the segments joined with `/` under a
|
|
115
|
+
* leading slash. Segments pass through as Next handed them over — nothing is encoded, trimmed
|
|
116
|
+
* or lower-cased, so the string matches the route `compile` stamped on the artifact only when
|
|
117
|
+
* the document was published under the same spelling.
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* routeFromSlug(undefined); // "/"
|
|
121
|
+
* routeFromSlug([]); // "/"
|
|
122
|
+
* routeFromSlug(["promotions", "summer"]); // "/promotions/summer"
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
52
125
|
declare function routeFromSlug(slug: readonly string[] | undefined): string;
|
|
53
126
|
|
|
54
127
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
128
|
+
* Every published route that can be prebuilt, shaped as the params a catch-all segment's
|
|
129
|
+
* `generateStaticParams` returns.
|
|
130
|
+
*
|
|
131
|
+
* @param store - The store the routes were published through. Only `manifest` is called, and it
|
|
132
|
+
* is read once per build rather than per request.
|
|
133
|
+
* @returns One `{ slug }` per exact pointer, in whatever order the manifest came back in; the
|
|
134
|
+
* root route is `{ slug: [] }`. A `param` or `prefix` pointer — `/guides/[city]`, `/docs/*` —
|
|
135
|
+
* is left out, because its route string is a pattern rather than a path and prebuilding it
|
|
136
|
+
* would name a page that does not exist.
|
|
137
|
+
* @throws Whatever the store raises reading its manifest. An empty manifest is an empty array,
|
|
138
|
+
* which is a build that prebuilds nothing rather than a failure.
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* export const generateStaticParams = () => staticRouteParams(store);
|
|
142
|
+
* ```
|
|
57
143
|
*/
|
|
58
144
|
declare function staticRouteParams(store: ArtifactStore): Promise<{
|
|
59
145
|
slug: string[];
|
|
60
146
|
}[]>;
|
|
61
147
|
|
|
62
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* Takes a route offline and invalidates that one page — the body of the
|
|
150
|
+
* `POST /api/nubbin/unpublish` handler an application exposes, which is where `nubbin unpublish
|
|
151
|
+
* --origin <url>` sends `{ route }`.
|
|
152
|
+
*
|
|
153
|
+
* Like `publishRoute`, it has to run inside the serving process: `revalidatePath` reaches only
|
|
154
|
+
* that process's cache, so a pointer dropped from a terminal leaves the page still being served.
|
|
155
|
+
*
|
|
156
|
+
* @param store - The store serving the application. `unpublish` is the only method called.
|
|
157
|
+
* @param route - The route to stop serving, exactly as it was published.
|
|
158
|
+
* @returns Nothing. The artifact stays readable at its hash — only the pointer goes — so
|
|
159
|
+
* restoring the route is a `publishRoute` at the same hash.
|
|
160
|
+
* @throws Whatever `store.unpublish` raises, before anything is invalidated. Unpublishing a
|
|
161
|
+
* route with no pointer is a no-op rather than a refusal.
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* export async function POST(request: Request) {
|
|
165
|
+
* const { route } = (await request.json()) as { route: string };
|
|
166
|
+
* await unpublishRoute(store, route);
|
|
167
|
+
* return Response.json({ ok: true, route });
|
|
168
|
+
* }
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
63
171
|
declare function unpublishRoute(store: ArtifactStore, route: string): Promise<void>;
|
|
64
172
|
|
|
65
173
|
export { artifactMetadata, holeFetchOptions, publishRoute, resolveArtifact, routeFromSlug, staticRouteParams, unpublishRoute };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nubbin/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The Next.js binding for Nubbin: resolve a published artifact for a route, prebuild the ones that exist, and publish or unpublish a single route.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nubbin",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@nubbin/core": "0.
|
|
35
|
+
"@nubbin/core": "0.2.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"next": ">=16.0.0"
|