@geekity/cms 0.3.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/LICENSE +21 -0
- package/README.md +870 -0
- package/admin/layouts/_flash.njk +5 -0
- package/admin/layouts/base.njk +27 -0
- package/admin/layouts/dashboard.njk +78 -0
- package/admin/layouts/document-conflict.njk +49 -0
- package/admin/layouts/document-editor.njk +167 -0
- package/admin/layouts/document-list.njk +92 -0
- package/admin/layouts/federation.njk +145 -0
- package/admin/layouts/login.njk +23 -0
- package/admin/layouts/placeholder.njk +10 -0
- package/admin/layouts/settings.njk +116 -0
- package/admin/layouts/setup.njk +27 -0
- package/admin/layouts/shell.njk +37 -0
- package/admin/layouts/users.njk +91 -0
- package/admin/static/admin.css +726 -0
- package/admin/static/editor.js +30 -0
- package/dist/admin/assets.d.ts +26 -0
- package/dist/admin/assets.d.ts.map +1 -0
- package/dist/admin/assets.js +38 -0
- package/dist/admin/assets.js.map +1 -0
- package/dist/admin/credentials.d.ts +28 -0
- package/dist/admin/credentials.d.ts.map +1 -0
- package/dist/admin/credentials.js +39 -0
- package/dist/admin/credentials.js.map +1 -0
- package/dist/admin/documents.d.ts +134 -0
- package/dist/admin/documents.d.ts.map +1 -0
- package/dist/admin/documents.js +613 -0
- package/dist/admin/documents.js.map +1 -0
- package/dist/admin/federation.d.ts +194 -0
- package/dist/admin/federation.d.ts.map +1 -0
- package/dist/admin/federation.js +299 -0
- package/dist/admin/federation.js.map +1 -0
- package/dist/admin/flash.d.ts +24 -0
- package/dist/admin/flash.d.ts.map +1 -0
- package/dist/admin/flash.js +31 -0
- package/dist/admin/flash.js.map +1 -0
- package/dist/admin/index.d.ts +23 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +15 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/passwords.d.ts +49 -0
- package/dist/admin/passwords.d.ts.map +1 -0
- package/dist/admin/passwords.js +122 -0
- package/dist/admin/passwords.js.map +1 -0
- package/dist/admin/preview.d.ts +18 -0
- package/dist/admin/preview.d.ts.map +1 -0
- package/dist/admin/preview.js +83 -0
- package/dist/admin/preview.js.map +1 -0
- package/dist/admin/routes.d.ts +54 -0
- package/dist/admin/routes.d.ts.map +1 -0
- package/dist/admin/routes.js +383 -0
- package/dist/admin/routes.js.map +1 -0
- package/dist/admin/session.d.ts +44 -0
- package/dist/admin/session.d.ts.map +1 -0
- package/dist/admin/session.js +69 -0
- package/dist/admin/session.js.map +1 -0
- package/dist/admin/settings.d.ts +194 -0
- package/dist/admin/settings.d.ts.map +1 -0
- package/dist/admin/settings.js +470 -0
- package/dist/admin/settings.js.map +1 -0
- package/dist/admin/store.d.ts +407 -0
- package/dist/admin/store.d.ts.map +1 -0
- package/dist/admin/store.js +704 -0
- package/dist/admin/store.js.map +1 -0
- package/dist/admin/templates.d.ts +36 -0
- package/dist/admin/templates.d.ts.map +1 -0
- package/dist/admin/templates.js +43 -0
- package/dist/admin/templates.js.map +1 -0
- package/dist/admin/uploads.d.ts +96 -0
- package/dist/admin/uploads.d.ts.map +1 -0
- package/dist/admin/uploads.js +179 -0
- package/dist/admin/uploads.js.map +1 -0
- package/dist/admin/users.d.ts +95 -0
- package/dist/admin/users.d.ts.map +1 -0
- package/dist/admin/users.js +233 -0
- package/dist/admin/users.js.map +1 -0
- package/dist/cli.d.ts +85 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +424 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +172 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +184 -0
- package/dist/config.js.map +1 -0
- package/dist/content/document.d.ts +66 -0
- package/dist/content/document.d.ts.map +1 -0
- package/dist/content/document.js +13 -0
- package/dist/content/document.js.map +1 -0
- package/dist/content/index.d.ts +17 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +10 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/markdown.d.ts +3 -0
- package/dist/content/markdown.d.ts.map +1 -0
- package/dist/content/markdown.js +39 -0
- package/dist/content/markdown.js.map +1 -0
- package/dist/content/media.d.ts +65 -0
- package/dist/content/media.d.ts.map +1 -0
- package/dist/content/media.js +143 -0
- package/dist/content/media.js.map +1 -0
- package/dist/content/parser.d.ts +24 -0
- package/dist/content/parser.d.ts.map +1 -0
- package/dist/content/parser.js +165 -0
- package/dist/content/parser.js.map +1 -0
- package/dist/content/save.d.ts +60 -0
- package/dist/content/save.d.ts.map +1 -0
- package/dist/content/save.js +103 -0
- package/dist/content/save.js.map +1 -0
- package/dist/content/slug.d.ts +25 -0
- package/dist/content/slug.d.ts.map +1 -0
- package/dist/content/slug.js +75 -0
- package/dist/content/slug.js.map +1 -0
- package/dist/content/store.d.ts +133 -0
- package/dist/content/store.d.ts.map +1 -0
- package/dist/content/store.js +411 -0
- package/dist/content/store.js.map +1 -0
- package/dist/content/sync.d.ts +146 -0
- package/dist/content/sync.d.ts.map +1 -0
- package/dist/content/sync.js +421 -0
- package/dist/content/sync.js.map +1 -0
- package/dist/content/writer.d.ts +37 -0
- package/dist/content/writer.d.ts.map +1 -0
- package/dist/content/writer.js +97 -0
- package/dist/content/writer.js.map +1 -0
- package/dist/env.d.ts +48 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +2 -0
- package/dist/env.js.map +1 -0
- package/dist/federation/actor.d.ts +55 -0
- package/dist/federation/actor.d.ts.map +1 -0
- package/dist/federation/actor.js +101 -0
- package/dist/federation/actor.js.map +1 -0
- package/dist/federation/article.d.ts +83 -0
- package/dist/federation/article.d.ts.map +1 -0
- package/dist/federation/article.js +169 -0
- package/dist/federation/article.js.map +1 -0
- package/dist/federation/delivery.d.ts +90 -0
- package/dist/federation/delivery.d.ts.map +1 -0
- package/dist/federation/delivery.js +289 -0
- package/dist/federation/delivery.js.map +1 -0
- package/dist/federation/federation.d.ts +75 -0
- package/dist/federation/federation.d.ts.map +1 -0
- package/dist/federation/federation.js +190 -0
- package/dist/federation/federation.js.map +1 -0
- package/dist/federation/followers.d.ts +43 -0
- package/dist/federation/followers.d.ts.map +1 -0
- package/dist/federation/followers.js +69 -0
- package/dist/federation/followers.js.map +1 -0
- package/dist/federation/inbox.d.ts +68 -0
- package/dist/federation/inbox.d.ts.map +1 -0
- package/dist/federation/inbox.js +171 -0
- package/dist/federation/inbox.js.map +1 -0
- package/dist/federation/index.d.ts +14 -0
- package/dist/federation/index.d.ts.map +1 -0
- package/dist/federation/index.js +10 -0
- package/dist/federation/index.js.map +1 -0
- package/dist/federation/keys.d.ts +27 -0
- package/dist/federation/keys.d.ts.map +1 -0
- package/dist/federation/keys.js +60 -0
- package/dist/federation/keys.js.map +1 -0
- package/dist/federation/mount.d.ts +23 -0
- package/dist/federation/mount.d.ts.map +1 -0
- package/dist/federation/mount.js +71 -0
- package/dist/federation/mount.js.map +1 -0
- package/dist/federation/paths.d.ts +85 -0
- package/dist/federation/paths.d.ts.map +1 -0
- package/dist/federation/paths.js +97 -0
- package/dist/federation/paths.js.map +1 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +71 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +125 -0
- package/dist/init.js.map +1 -0
- package/dist/web/assets.d.ts +65 -0
- package/dist/web/assets.d.ts.map +1 -0
- package/dist/web/assets.js +152 -0
- package/dist/web/assets.js.map +1 -0
- package/dist/web/context.d.ts +121 -0
- package/dist/web/context.d.ts.map +1 -0
- package/dist/web/context.js +112 -0
- package/dist/web/context.js.map +1 -0
- package/dist/web/documents.d.ts +28 -0
- package/dist/web/documents.d.ts.map +1 -0
- package/dist/web/documents.js +39 -0
- package/dist/web/documents.js.map +1 -0
- package/dist/web/feeds.d.ts +122 -0
- package/dist/web/feeds.d.ts.map +1 -0
- package/dist/web/feeds.js +229 -0
- package/dist/web/feeds.js.map +1 -0
- package/dist/web/index.d.ts +17 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +10 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/negotiate.d.ts +219 -0
- package/dist/web/negotiate.d.ts.map +1 -0
- package/dist/web/negotiate.js +409 -0
- package/dist/web/negotiate.js.map +1 -0
- package/dist/web/pagination.d.ts +48 -0
- package/dist/web/pagination.d.ts.map +1 -0
- package/dist/web/pagination.js +30 -0
- package/dist/web/pagination.js.map +1 -0
- package/dist/web/render.d.ts +72 -0
- package/dist/web/render.d.ts.map +1 -0
- package/dist/web/render.js +73 -0
- package/dist/web/render.js.map +1 -0
- package/dist/web/routes.d.ts +31 -0
- package/dist/web/routes.d.ts.map +1 -0
- package/dist/web/routes.js +366 -0
- package/dist/web/routes.js.map +1 -0
- package/dist/web/templates.d.ts +34 -0
- package/dist/web/templates.d.ts.map +1 -0
- package/dist/web/templates.js +106 -0
- package/dist/web/templates.js.map +1 -0
- package/package.json +68 -0
- package/templates/site/content/_data/site.json +8 -0
- package/templates/site/content/pages/about.md +11 -0
- package/templates/site/content/pages/pages.json +3 -0
- package/templates/site/content/posts/2026-01-01-hello-world.md +34 -0
- package/templates/site/content/posts/posts.json +4 -0
- package/templates/site/geekity.config.ts +11 -0
- package/templates/site/gitignore +3 -0
- package/templates/site/server.ts +20 -0
- package/templates/site/tsconfig.json +18 -0
- package/themes/default/README.md +148 -0
- package/themes/default/layouts/404.njk +9 -0
- package/themes/default/layouts/base.njk +59 -0
- package/themes/default/layouts/home.njk +9 -0
- package/themes/default/layouts/page.njk +13 -0
- package/themes/default/layouts/post.njk +30 -0
- package/themes/default/layouts/tag.njk +19 -0
- package/themes/default/partials/pagination.njk +14 -0
- package/themes/default/partials/post-list.njk +24 -0
- package/themes/default/partials/tags.njk +12 -0
- package/themes/default/static/style.css +327 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { Document } from '../content/document.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Which of a resource's representations a request asked for.
|
|
4
|
+
*
|
|
5
|
+
* Every public content URL is one resource with several bodies. The routes
|
|
6
|
+
* decide *what* is at a URL; this module decides *which body* to hand back and
|
|
7
|
+
* how to label it, so the two questions never get tangled together.
|
|
8
|
+
*/
|
|
9
|
+
export type Representation = 'html' | 'markdown' | 'json';
|
|
10
|
+
/** The media type each representation is served as. */
|
|
11
|
+
export declare const MEDIA_TYPES: Readonly<Record<Representation, string>>;
|
|
12
|
+
/**
|
|
13
|
+
* What a document offers, in preference order.
|
|
14
|
+
*
|
|
15
|
+
* Order is the tie-break when a request is indifferent — `Accept: *\/*`, or no
|
|
16
|
+
* `Accept` at all — so HTML comes first: a browser that says nothing gets a
|
|
17
|
+
* page.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DOCUMENT_REPRESENTATIONS: readonly Representation[];
|
|
20
|
+
/** What a listing offers. Markdown is not a listing; there is no file to serve. */
|
|
21
|
+
export declare const LISTING_REPRESENTATIONS: readonly Representation[];
|
|
22
|
+
/**
|
|
23
|
+
* The representation a request accepts, or `undefined` when it accepts none of
|
|
24
|
+
* them and is owed a 406.
|
|
25
|
+
*
|
|
26
|
+
* A missing header means "anything", which is HTML. A present header is matched
|
|
27
|
+
* by the usual rules: highest q wins, the more specific match wins a tie, and
|
|
28
|
+
* our own preference order breaks what is left.
|
|
29
|
+
*/
|
|
30
|
+
export declare function selectRepresentation(accept: string | undefined, available: readonly Representation[]): Representation | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* The media types that ask for the ActivityStreams document rather than for
|
|
33
|
+
* one of the representations this layer serves (doc-3).
|
|
34
|
+
*
|
|
35
|
+
* The `ld+json` spelling is listed without its
|
|
36
|
+
* `profile="https://www.w3.org/ns/activitystreams"` parameter, because that is
|
|
37
|
+
* how `Accept` matching works: a range's parameters other than `q` do not
|
|
38
|
+
* narrow what it matches, and nothing else the CMS serves is `ld+json`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const ACTIVITY_STREAMS_MEDIA_TYPES: readonly string[];
|
|
41
|
+
/**
|
|
42
|
+
* Whether a request would rather have the ActivityStreams object than any
|
|
43
|
+
* representation of the document.
|
|
44
|
+
*
|
|
45
|
+
* It is the same comparison {@link selectRepresentation} makes — highest q
|
|
46
|
+
* wins, the more specific range wins a tie — with the ActivityStreams types on
|
|
47
|
+
* one side and the document's own on the other. A tie goes to the document, so
|
|
48
|
+
* `Accept: application/*` still gets JSON and a browser's
|
|
49
|
+
* `text/html,…,*\/*;q=0.8` still gets a page.
|
|
50
|
+
*/
|
|
51
|
+
export declare function prefersActivityStreams(accept: string | undefined): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The extension that names a representation in a URL. HTML has none: it is
|
|
54
|
+
* what the canonical URL itself serves.
|
|
55
|
+
*/
|
|
56
|
+
export declare const REPRESENTATION_EXTENSIONS: Readonly<Partial<Record<Representation, string>>>;
|
|
57
|
+
/** A request that named its representation in the path rather than in a header. */
|
|
58
|
+
export interface RepresentationExtension {
|
|
59
|
+
/** The representation the extension asked for. */
|
|
60
|
+
representation: Representation;
|
|
61
|
+
/** The paths to look the resource up under, likeliest first. */
|
|
62
|
+
paths: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A `.md` or `.json` suffix taken off a request path, or `undefined` when the
|
|
66
|
+
* path carries neither.
|
|
67
|
+
*
|
|
68
|
+
* Both spellings work: `/2026/09/hello/index.md` — the one the `Link` header
|
|
69
|
+
* advertises, and the one an Eleventy-style static build would produce — and
|
|
70
|
+
* the shorter `/2026/09/hello.md` that people actually type. The result is a
|
|
71
|
+
* list of candidate paths rather than one, because the trailing slash a
|
|
72
|
+
* permalink carries is exactly what an extension has to displace.
|
|
73
|
+
*/
|
|
74
|
+
export declare function splitRepresentationExtension(pathname: string): RepresentationExtension | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* The URL one representation of a resource is served at, given the resource's
|
|
77
|
+
* canonical URL.
|
|
78
|
+
*
|
|
79
|
+
* `index` is inserted before the extension when the canonical URL is a
|
|
80
|
+
* directory-style path, which every permalink and every listing URL is.
|
|
81
|
+
*/
|
|
82
|
+
export declare function representationHref(href: string, representation: Representation): string;
|
|
83
|
+
/**
|
|
84
|
+
* Version of the JSON representation.
|
|
85
|
+
*
|
|
86
|
+
* It is in every JSON body so a consumer can tell what it is reading. Keys are
|
|
87
|
+
* only ever added within a version; anything that would break a reader gets a
|
|
88
|
+
* new number.
|
|
89
|
+
*/
|
|
90
|
+
export declare const JSON_SCHEMA_VERSION = 1;
|
|
91
|
+
/** A document as JSON. `markdown` and `html` are absent from a listing summary. */
|
|
92
|
+
export interface DocumentJson {
|
|
93
|
+
/** {@link JSON_SCHEMA_VERSION}. */
|
|
94
|
+
schema: number;
|
|
95
|
+
/** Absolute URL of the document, built on the site's `baseUrl`. */
|
|
96
|
+
url: string;
|
|
97
|
+
/** The front matter, exactly as the Markdown file carries it. */
|
|
98
|
+
frontMatter: Record<string, unknown>;
|
|
99
|
+
/** The Markdown body, without the front matter. Omitted from a summary. */
|
|
100
|
+
markdown?: string;
|
|
101
|
+
/** The rendered body. Omitted from a summary. */
|
|
102
|
+
html?: string;
|
|
103
|
+
}
|
|
104
|
+
/** How to build a {@link DocumentJson}. */
|
|
105
|
+
export interface DocumentJsonOptions {
|
|
106
|
+
/** The site's public origin, for the absolute `url`. */
|
|
107
|
+
baseUrl: string;
|
|
108
|
+
/** `false` leaves out `markdown` and `html`. Defaults to `true`. */
|
|
109
|
+
body?: boolean | undefined;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* One document as JSON.
|
|
113
|
+
*
|
|
114
|
+
* The front matter is the writer's own block rather than a second rendering of
|
|
115
|
+
* it, so what this reports and what the `.md` representation contains are the
|
|
116
|
+
* same data.
|
|
117
|
+
*/
|
|
118
|
+
export declare function documentJson(document: Document, options: DocumentJsonOptions): DocumentJson;
|
|
119
|
+
/**
|
|
120
|
+
* A path on the site as an absolute URL.
|
|
121
|
+
*
|
|
122
|
+
* A base URL carrying a path — a site served from a subdirectory — puts that
|
|
123
|
+
* path in front of a root-relative one, exactly as the theme's `absoluteUrl`
|
|
124
|
+
* filter does, so a document's id is the same string wherever it is built.
|
|
125
|
+
*/
|
|
126
|
+
export declare function absoluteUrl(pathname: string, baseUrl: string): string;
|
|
127
|
+
/** The conditional headers a request may carry. */
|
|
128
|
+
export interface ConditionalHeaders {
|
|
129
|
+
/** `If-None-Match`. */
|
|
130
|
+
ifNoneMatch?: string | undefined;
|
|
131
|
+
/** `If-Modified-Since`. */
|
|
132
|
+
ifModifiedSince?: string | undefined;
|
|
133
|
+
}
|
|
134
|
+
/** Everything one negotiated response needs. */
|
|
135
|
+
export interface RepresentationResponseOptions {
|
|
136
|
+
/** A string for HTML and Markdown; anything serialisable for JSON. */
|
|
137
|
+
body: unknown;
|
|
138
|
+
/** Which representation `body` is. */
|
|
139
|
+
representation: Representation;
|
|
140
|
+
/** The resource's canonical URL path, percent-encoded. */
|
|
141
|
+
href: string;
|
|
142
|
+
/** Which representations this resource offers, for the `Link` alternates. */
|
|
143
|
+
available: readonly Representation[];
|
|
144
|
+
/** Validator for this representation. Omit when one cannot be trusted. */
|
|
145
|
+
etag?: string | undefined;
|
|
146
|
+
/** When the resource last changed. */
|
|
147
|
+
lastModified?: Date | undefined;
|
|
148
|
+
/** The request's conditional headers, for the 304. */
|
|
149
|
+
conditional?: ConditionalHeaders | undefined;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* One representation as an HTTP response, validators and alternates included.
|
|
153
|
+
*
|
|
154
|
+
* Every negotiated response in the CMS comes from here, so `Vary`, `Link` and
|
|
155
|
+
* the conditional handling cannot be right on one route and forgotten on the
|
|
156
|
+
* next. A request whose validators still match gets the 304 from here too: the
|
|
157
|
+
* body is built either way, but building it is cheap next to shipping it, and
|
|
158
|
+
* one exit means one place where the headers are decided.
|
|
159
|
+
*/
|
|
160
|
+
export declare function representationResponse(options: RepresentationResponseOptions): Response;
|
|
161
|
+
/**
|
|
162
|
+
* The 406 a request earns when it accepts none of the representations on
|
|
163
|
+
* offer, with the options in the body so a client can pick one and retry.
|
|
164
|
+
*/
|
|
165
|
+
export declare function notAcceptableResponse(href: string, available: readonly Representation[]): Response;
|
|
166
|
+
/**
|
|
167
|
+
* A `Link` header advertising the representations other than the current one,
|
|
168
|
+
* as doc-3 specifies.
|
|
169
|
+
*/
|
|
170
|
+
export declare function alternateLinks(href: string, current: Representation | undefined, available: readonly Representation[]): string;
|
|
171
|
+
/**
|
|
172
|
+
* The validator for one representation of something whose content is already
|
|
173
|
+
* hashed.
|
|
174
|
+
*
|
|
175
|
+
* Mixing the representation in is what keeps the three bodies at a URL apart:
|
|
176
|
+
* a client holding the JSON must not be told its Markdown is unchanged.
|
|
177
|
+
*/
|
|
178
|
+
export declare function representationEtag(representation: Representation, fingerprint: string): string;
|
|
179
|
+
/**
|
|
180
|
+
* A validator over any kind of body, given something that changes whenever the
|
|
181
|
+
* body does.
|
|
182
|
+
*
|
|
183
|
+
* `kind` is what keeps two bodies built from the same documents apart — the
|
|
184
|
+
* Atom feed and the JSON feed over one archive share every hash, and a client
|
|
185
|
+
* holding one must not be told the other is unchanged.
|
|
186
|
+
*/
|
|
187
|
+
export declare function contentEtag(kind: string, fingerprint: string): string;
|
|
188
|
+
/** When a document last changed: its `updated`, else its publish `date`. */
|
|
189
|
+
export declare function lastModifiedOf(document: Document): Date | undefined;
|
|
190
|
+
/** The most recent modification time among some documents, if any has one. */
|
|
191
|
+
export declare function latestModified(documents: readonly Document[]): Date | undefined;
|
|
192
|
+
/**
|
|
193
|
+
* Whether a conditional request may be answered with 304.
|
|
194
|
+
*
|
|
195
|
+
* `If-None-Match` wins outright when it is present, as RFC 9110 requires, so a
|
|
196
|
+
* client that has both validators is never told a page is fresh on the
|
|
197
|
+
* strength of a timestamp its ETag disagrees with.
|
|
198
|
+
*/
|
|
199
|
+
export declare function isNotModified(conditional: ConditionalHeaders | undefined, etag: string | undefined, lastModified: Date | undefined): boolean;
|
|
200
|
+
/** One media range from an `Accept` header. */
|
|
201
|
+
export interface AcceptRange {
|
|
202
|
+
/** The type, or `*`. */
|
|
203
|
+
type: string;
|
|
204
|
+
/** The subtype, or `*`. */
|
|
205
|
+
subtype: string;
|
|
206
|
+
/** The q-value, 0 to 1. Defaults to 1, as the header's grammar says. */
|
|
207
|
+
quality: number;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* An `Accept` header as media ranges.
|
|
211
|
+
*
|
|
212
|
+
* Parameters other than `q` are parsed and dropped: none of our representations
|
|
213
|
+
* are distinguished by one, and a range that carries some unknown parameter is
|
|
214
|
+
* still a request for its media type. Anything unparseable is skipped rather
|
|
215
|
+
* than failing the request, because a malformed `Accept` should cost a client
|
|
216
|
+
* its preference, not its page.
|
|
217
|
+
*/
|
|
218
|
+
export declare function parseAccept(header: string): AcceptRange[];
|
|
219
|
+
//# sourceMappingURL=negotiate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiate.d.ts","sourceRoot":"","sources":["../../src/web/negotiate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { documentFrontMatter } from '../content/writer.js';
|
|
3
|
+
import { matchesEtag } from './assets.js';
|
|
4
|
+
/** The media type each representation is served as. */
|
|
5
|
+
export const MEDIA_TYPES = {
|
|
6
|
+
html: 'text/html',
|
|
7
|
+
markdown: 'text/markdown',
|
|
8
|
+
json: 'application/json',
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* What a document offers, in preference order.
|
|
12
|
+
*
|
|
13
|
+
* Order is the tie-break when a request is indifferent — `Accept: *\/*`, or no
|
|
14
|
+
* `Accept` at all — so HTML comes first: a browser that says nothing gets a
|
|
15
|
+
* page.
|
|
16
|
+
*/
|
|
17
|
+
export const DOCUMENT_REPRESENTATIONS = ['html', 'markdown', 'json'];
|
|
18
|
+
/** What a listing offers. Markdown is not a listing; there is no file to serve. */
|
|
19
|
+
export const LISTING_REPRESENTATIONS = ['html', 'json'];
|
|
20
|
+
/**
|
|
21
|
+
* The representation a request accepts, or `undefined` when it accepts none of
|
|
22
|
+
* them and is owed a 406.
|
|
23
|
+
*
|
|
24
|
+
* A missing header means "anything", which is HTML. A present header is matched
|
|
25
|
+
* by the usual rules: highest q wins, the more specific match wins a tie, and
|
|
26
|
+
* our own preference order breaks what is left.
|
|
27
|
+
*/
|
|
28
|
+
export function selectRepresentation(accept, available) {
|
|
29
|
+
if (accept === undefined || accept.trim() === '')
|
|
30
|
+
return available[0];
|
|
31
|
+
const ranges = parseAccept(accept);
|
|
32
|
+
if (ranges.length === 0)
|
|
33
|
+
return available[0];
|
|
34
|
+
let best;
|
|
35
|
+
for (const representation of available) {
|
|
36
|
+
const match = bestRange(ranges, MEDIA_TYPES[representation]);
|
|
37
|
+
if (match === undefined || match.quality <= 0)
|
|
38
|
+
continue;
|
|
39
|
+
if (best === undefined ||
|
|
40
|
+
match.quality > best.quality ||
|
|
41
|
+
(match.quality === best.quality && match.specificity > best.specificity)) {
|
|
42
|
+
best = { representation, quality: match.quality, specificity: match.specificity };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return best?.representation;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The media types that ask for the ActivityStreams document rather than for
|
|
49
|
+
* one of the representations this layer serves (doc-3).
|
|
50
|
+
*
|
|
51
|
+
* The `ld+json` spelling is listed without its
|
|
52
|
+
* `profile="https://www.w3.org/ns/activitystreams"` parameter, because that is
|
|
53
|
+
* how `Accept` matching works: a range's parameters other than `q` do not
|
|
54
|
+
* narrow what it matches, and nothing else the CMS serves is `ld+json`.
|
|
55
|
+
*/
|
|
56
|
+
export const ACTIVITY_STREAMS_MEDIA_TYPES = [
|
|
57
|
+
'application/activity+json',
|
|
58
|
+
'application/ld+json',
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Whether a request would rather have the ActivityStreams object than any
|
|
62
|
+
* representation of the document.
|
|
63
|
+
*
|
|
64
|
+
* It is the same comparison {@link selectRepresentation} makes — highest q
|
|
65
|
+
* wins, the more specific range wins a tie — with the ActivityStreams types on
|
|
66
|
+
* one side and the document's own on the other. A tie goes to the document, so
|
|
67
|
+
* `Accept: application/*` still gets JSON and a browser's
|
|
68
|
+
* `text/html,…,*\/*;q=0.8` still gets a page.
|
|
69
|
+
*/
|
|
70
|
+
export function prefersActivityStreams(accept) {
|
|
71
|
+
if (accept === undefined || accept.trim() === '')
|
|
72
|
+
return false;
|
|
73
|
+
const ranges = parseAccept(accept);
|
|
74
|
+
if (ranges.length === 0)
|
|
75
|
+
return false;
|
|
76
|
+
const wanted = bestOf(ranges, ACTIVITY_STREAMS_MEDIA_TYPES);
|
|
77
|
+
if (wanted === undefined || wanted.quality <= 0)
|
|
78
|
+
return false;
|
|
79
|
+
const alternative = bestOf(ranges, DOCUMENT_REPRESENTATIONS.map((representation) => MEDIA_TYPES[representation]));
|
|
80
|
+
if (alternative === undefined || alternative.quality <= 0)
|
|
81
|
+
return true;
|
|
82
|
+
return (wanted.quality > alternative.quality ||
|
|
83
|
+
(wanted.quality === alternative.quality && wanted.specificity > alternative.specificity));
|
|
84
|
+
}
|
|
85
|
+
/** The best match among several media types, or `undefined` when none match. */
|
|
86
|
+
function bestOf(ranges, mediaTypes) {
|
|
87
|
+
let best;
|
|
88
|
+
for (const mediaType of mediaTypes) {
|
|
89
|
+
const match = bestRange(ranges, mediaType);
|
|
90
|
+
if (match === undefined)
|
|
91
|
+
continue;
|
|
92
|
+
if (best === undefined ||
|
|
93
|
+
match.quality > best.quality ||
|
|
94
|
+
(match.quality === best.quality && match.specificity > best.specificity)) {
|
|
95
|
+
best = match;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return best;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The extension that names a representation in a URL. HTML has none: it is
|
|
102
|
+
* what the canonical URL itself serves.
|
|
103
|
+
*/
|
|
104
|
+
export const REPRESENTATION_EXTENSIONS = {
|
|
105
|
+
markdown: '.md',
|
|
106
|
+
json: '.json',
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* A `.md` or `.json` suffix taken off a request path, or `undefined` when the
|
|
110
|
+
* path carries neither.
|
|
111
|
+
*
|
|
112
|
+
* Both spellings work: `/2026/09/hello/index.md` — the one the `Link` header
|
|
113
|
+
* advertises, and the one an Eleventy-style static build would produce — and
|
|
114
|
+
* the shorter `/2026/09/hello.md` that people actually type. The result is a
|
|
115
|
+
* list of candidate paths rather than one, because the trailing slash a
|
|
116
|
+
* permalink carries is exactly what an extension has to displace.
|
|
117
|
+
*/
|
|
118
|
+
export function splitRepresentationExtension(pathname) {
|
|
119
|
+
for (const [representation, extension] of Object.entries(REPRESENTATION_EXTENSIONS)) {
|
|
120
|
+
if (extension === undefined || !pathname.endsWith(extension))
|
|
121
|
+
continue;
|
|
122
|
+
let base = pathname.slice(0, -extension.length);
|
|
123
|
+
const named = base.endsWith('/index');
|
|
124
|
+
if (named)
|
|
125
|
+
base = base.slice(0, -'index'.length);
|
|
126
|
+
// `/.md` and `/x/.md` name no resource: the extension is the whole of the
|
|
127
|
+
// last segment. Only the `index` spelling may leave a path ending in `/`.
|
|
128
|
+
if (base === '' || (!named && base.endsWith('/')))
|
|
129
|
+
continue;
|
|
130
|
+
const withSlash = base.endsWith('/') ? base : `${base}/`;
|
|
131
|
+
const paths = base === withSlash ? [base] : [withSlash, base];
|
|
132
|
+
return { representation: representation, paths };
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The URL one representation of a resource is served at, given the resource's
|
|
138
|
+
* canonical URL.
|
|
139
|
+
*
|
|
140
|
+
* `index` is inserted before the extension when the canonical URL is a
|
|
141
|
+
* directory-style path, which every permalink and every listing URL is.
|
|
142
|
+
*/
|
|
143
|
+
export function representationHref(href, representation) {
|
|
144
|
+
const extension = REPRESENTATION_EXTENSIONS[representation];
|
|
145
|
+
if (extension === undefined)
|
|
146
|
+
return href;
|
|
147
|
+
return href.endsWith('/') ? `${href}index${extension}` : `${href}${extension}`;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Version of the JSON representation.
|
|
151
|
+
*
|
|
152
|
+
* It is in every JSON body so a consumer can tell what it is reading. Keys are
|
|
153
|
+
* only ever added within a version; anything that would break a reader gets a
|
|
154
|
+
* new number.
|
|
155
|
+
*/
|
|
156
|
+
export const JSON_SCHEMA_VERSION = 1;
|
|
157
|
+
/**
|
|
158
|
+
* One document as JSON.
|
|
159
|
+
*
|
|
160
|
+
* The front matter is the writer's own block rather than a second rendering of
|
|
161
|
+
* it, so what this reports and what the `.md` representation contains are the
|
|
162
|
+
* same data.
|
|
163
|
+
*/
|
|
164
|
+
export function documentJson(document, options) {
|
|
165
|
+
const json = {
|
|
166
|
+
schema: JSON_SCHEMA_VERSION,
|
|
167
|
+
url: absoluteUrl(document.permalink, options.baseUrl),
|
|
168
|
+
frontMatter: documentFrontMatter(document),
|
|
169
|
+
};
|
|
170
|
+
if (options.body !== false) {
|
|
171
|
+
json.markdown = document.body;
|
|
172
|
+
json.html = document.html;
|
|
173
|
+
}
|
|
174
|
+
return json;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* A path on the site as an absolute URL.
|
|
178
|
+
*
|
|
179
|
+
* A base URL carrying a path — a site served from a subdirectory — puts that
|
|
180
|
+
* path in front of a root-relative one, exactly as the theme's `absoluteUrl`
|
|
181
|
+
* filter does, so a document's id is the same string wherever it is built.
|
|
182
|
+
*/
|
|
183
|
+
export function absoluteUrl(pathname, baseUrl) {
|
|
184
|
+
const base = new URL(baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);
|
|
185
|
+
const basePath = base.pathname === '/' ? '' : base.pathname.replace(/\/$/, '');
|
|
186
|
+
const resolved = pathname.startsWith('/') ? `${basePath}${pathname}` : pathname;
|
|
187
|
+
return new URL(resolved, base).toString();
|
|
188
|
+
}
|
|
189
|
+
/** What each representation is labelled with on the wire. */
|
|
190
|
+
const CONTENT_TYPES = {
|
|
191
|
+
html: 'text/html; charset=utf-8',
|
|
192
|
+
markdown: 'text/markdown; charset=utf-8',
|
|
193
|
+
json: 'application/json; charset=utf-8',
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* One representation as an HTTP response, validators and alternates included.
|
|
197
|
+
*
|
|
198
|
+
* Every negotiated response in the CMS comes from here, so `Vary`, `Link` and
|
|
199
|
+
* the conditional handling cannot be right on one route and forgotten on the
|
|
200
|
+
* next. A request whose validators still match gets the 304 from here too: the
|
|
201
|
+
* body is built either way, but building it is cheap next to shipping it, and
|
|
202
|
+
* one exit means one place where the headers are decided.
|
|
203
|
+
*/
|
|
204
|
+
export function representationResponse(options) {
|
|
205
|
+
const headers = new Headers({
|
|
206
|
+
vary: 'Accept',
|
|
207
|
+
link: alternateLinks(options.href, options.representation, options.available),
|
|
208
|
+
});
|
|
209
|
+
if (options.etag !== undefined)
|
|
210
|
+
headers.set('etag', options.etag);
|
|
211
|
+
if (options.lastModified !== undefined) {
|
|
212
|
+
headers.set('last-modified', options.lastModified.toUTCString());
|
|
213
|
+
}
|
|
214
|
+
// A validator without a directive leaves the browser to guess how long the
|
|
215
|
+
// response stays fresh. `no-cache` keeps the validator's value — a cheap 304
|
|
216
|
+
// — without ever letting a stale page through.
|
|
217
|
+
if (options.etag !== undefined || options.lastModified !== undefined) {
|
|
218
|
+
headers.set('cache-control', 'no-cache');
|
|
219
|
+
}
|
|
220
|
+
if (isNotModified(options.conditional, options.etag, options.lastModified)) {
|
|
221
|
+
return new Response(null, { status: 304, headers });
|
|
222
|
+
}
|
|
223
|
+
headers.set('content-type', CONTENT_TYPES[options.representation]);
|
|
224
|
+
if (options.representation === 'markdown') {
|
|
225
|
+
// The point of the Markdown representation is to be read, not downloaded.
|
|
226
|
+
headers.set('content-disposition', 'inline');
|
|
227
|
+
}
|
|
228
|
+
const body = options.representation === 'json'
|
|
229
|
+
? `${JSON.stringify(options.body, undefined, 2)}\n`
|
|
230
|
+
: String(options.body);
|
|
231
|
+
return new Response(body, { headers });
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The 406 a request earns when it accepts none of the representations on
|
|
235
|
+
* offer, with the options in the body so a client can pick one and retry.
|
|
236
|
+
*/
|
|
237
|
+
export function notAcceptableResponse(href, available) {
|
|
238
|
+
return new Response(`${JSON.stringify({
|
|
239
|
+
error: 'not_acceptable',
|
|
240
|
+
message: 'This URL is not available in any of the media types you accept.',
|
|
241
|
+
alternates: available.map((representation) => ({
|
|
242
|
+
type: MEDIA_TYPES[representation],
|
|
243
|
+
url: representationHref(href, representation),
|
|
244
|
+
})),
|
|
245
|
+
}, undefined, 2)}\n`, {
|
|
246
|
+
status: 406,
|
|
247
|
+
headers: {
|
|
248
|
+
'content-type': 'application/json; charset=utf-8',
|
|
249
|
+
vary: 'Accept',
|
|
250
|
+
link: alternateLinks(href, undefined, available),
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* A `Link` header advertising the representations other than the current one,
|
|
256
|
+
* as doc-3 specifies.
|
|
257
|
+
*/
|
|
258
|
+
export function alternateLinks(href, current, available) {
|
|
259
|
+
return available
|
|
260
|
+
.filter((representation) => representation !== current)
|
|
261
|
+
.map((representation) => `<${representationHref(href, representation)}>; rel="alternate"; type="${MEDIA_TYPES[representation]}"`)
|
|
262
|
+
.join(', ');
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The validator for one representation of something whose content is already
|
|
266
|
+
* hashed.
|
|
267
|
+
*
|
|
268
|
+
* Mixing the representation in is what keeps the three bodies at a URL apart:
|
|
269
|
+
* a client holding the JSON must not be told its Markdown is unchanged.
|
|
270
|
+
*/
|
|
271
|
+
export function representationEtag(representation, fingerprint) {
|
|
272
|
+
return contentEtag(representation, fingerprint);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* A validator over any kind of body, given something that changes whenever the
|
|
276
|
+
* body does.
|
|
277
|
+
*
|
|
278
|
+
* `kind` is what keeps two bodies built from the same documents apart — the
|
|
279
|
+
* Atom feed and the JSON feed over one archive share every hash, and a client
|
|
280
|
+
* holding one must not be told the other is unchanged.
|
|
281
|
+
*/
|
|
282
|
+
export function contentEtag(kind, fingerprint) {
|
|
283
|
+
const digest = createHash('sha256').update(`${kind}\n${fingerprint}`, 'utf8').digest('hex');
|
|
284
|
+
return `"${digest.slice(0, 32)}"`;
|
|
285
|
+
}
|
|
286
|
+
/** When a document last changed: its `updated`, else its publish `date`. */
|
|
287
|
+
export function lastModifiedOf(document) {
|
|
288
|
+
for (const value of [document.updated, document.date]) {
|
|
289
|
+
if (value === undefined)
|
|
290
|
+
continue;
|
|
291
|
+
const parsed = new Date(value);
|
|
292
|
+
if (!Number.isNaN(parsed.getTime()))
|
|
293
|
+
return parsed;
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
/** The most recent modification time among some documents, if any has one. */
|
|
298
|
+
export function latestModified(documents) {
|
|
299
|
+
let latest;
|
|
300
|
+
for (const document of documents) {
|
|
301
|
+
const modified = lastModifiedOf(document);
|
|
302
|
+
if (modified === undefined)
|
|
303
|
+
continue;
|
|
304
|
+
if (latest === undefined || modified > latest)
|
|
305
|
+
latest = modified;
|
|
306
|
+
}
|
|
307
|
+
return latest;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Whether a conditional request may be answered with 304.
|
|
311
|
+
*
|
|
312
|
+
* `If-None-Match` wins outright when it is present, as RFC 9110 requires, so a
|
|
313
|
+
* client that has both validators is never told a page is fresh on the
|
|
314
|
+
* strength of a timestamp its ETag disagrees with.
|
|
315
|
+
*/
|
|
316
|
+
export function isNotModified(conditional, etag, lastModified) {
|
|
317
|
+
if (conditional === undefined)
|
|
318
|
+
return false;
|
|
319
|
+
if (conditional.ifNoneMatch !== undefined) {
|
|
320
|
+
return etag !== undefined && matchesEtag(conditional.ifNoneMatch, etag);
|
|
321
|
+
}
|
|
322
|
+
if (conditional.ifModifiedSince === undefined || lastModified === undefined)
|
|
323
|
+
return false;
|
|
324
|
+
const since = new Date(conditional.ifModifiedSince);
|
|
325
|
+
if (Number.isNaN(since.getTime()))
|
|
326
|
+
return false;
|
|
327
|
+
// HTTP dates have one-second resolution; a document modified within the same
|
|
328
|
+
// second as the copy the client holds is the copy the client holds.
|
|
329
|
+
return Math.floor(lastModified.getTime() / 1000) <= Math.floor(since.getTime() / 1000);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* An `Accept` header as media ranges.
|
|
333
|
+
*
|
|
334
|
+
* Parameters other than `q` are parsed and dropped: none of our representations
|
|
335
|
+
* are distinguished by one, and a range that carries some unknown parameter is
|
|
336
|
+
* still a request for its media type. Anything unparseable is skipped rather
|
|
337
|
+
* than failing the request, because a malformed `Accept` should cost a client
|
|
338
|
+
* its preference, not its page.
|
|
339
|
+
*/
|
|
340
|
+
export function parseAccept(header) {
|
|
341
|
+
const ranges = [];
|
|
342
|
+
for (const part of splitList(header)) {
|
|
343
|
+
const [mediaRange, ...parameters] = part.split(';');
|
|
344
|
+
const slash = (mediaRange ?? '').indexOf('/');
|
|
345
|
+
if (slash <= 0)
|
|
346
|
+
continue;
|
|
347
|
+
const type = (mediaRange ?? '').slice(0, slash).trim().toLowerCase();
|
|
348
|
+
const subtype = (mediaRange ?? '')
|
|
349
|
+
.slice(slash + 1)
|
|
350
|
+
.trim()
|
|
351
|
+
.toLowerCase();
|
|
352
|
+
if (type === '' || subtype === '')
|
|
353
|
+
continue;
|
|
354
|
+
ranges.push({ type, subtype, quality: qualityOf(parameters) });
|
|
355
|
+
}
|
|
356
|
+
return ranges;
|
|
357
|
+
}
|
|
358
|
+
/** The best range matching one media type, or `undefined` when none does. */
|
|
359
|
+
function bestRange(ranges, mediaType) {
|
|
360
|
+
const slash = mediaType.indexOf('/');
|
|
361
|
+
const type = mediaType.slice(0, slash);
|
|
362
|
+
const subtype = mediaType.slice(slash + 1);
|
|
363
|
+
let best;
|
|
364
|
+
for (const range of ranges) {
|
|
365
|
+
const specificity = specificityOf(range, type, subtype);
|
|
366
|
+
if (specificity === undefined)
|
|
367
|
+
continue;
|
|
368
|
+
if (best === undefined || specificity > best.specificity) {
|
|
369
|
+
best = { quality: range.quality, specificity };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return best;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* How specifically a range names a media type: 2 for an exact match, 1 for
|
|
376
|
+
* `type/*`, 0 for `*\/*`, `undefined` for no match at all.
|
|
377
|
+
*/
|
|
378
|
+
function specificityOf(range, type, subtype) {
|
|
379
|
+
if (range.type === '*' && range.subtype === '*')
|
|
380
|
+
return 0;
|
|
381
|
+
if (range.type !== type)
|
|
382
|
+
return undefined;
|
|
383
|
+
if (range.subtype === '*')
|
|
384
|
+
return 1;
|
|
385
|
+
return range.subtype === subtype ? 2 : undefined;
|
|
386
|
+
}
|
|
387
|
+
/** The `q` parameter of a media range, clamped to the range the grammar allows. */
|
|
388
|
+
function qualityOf(parameters) {
|
|
389
|
+
for (const parameter of parameters) {
|
|
390
|
+
const equals = parameter.indexOf('=');
|
|
391
|
+
if (equals < 0)
|
|
392
|
+
continue;
|
|
393
|
+
if (parameter.slice(0, equals).trim().toLowerCase() !== 'q')
|
|
394
|
+
continue;
|
|
395
|
+
const quality = Number(parameter.slice(equals + 1).trim());
|
|
396
|
+
if (Number.isNaN(quality))
|
|
397
|
+
return 1;
|
|
398
|
+
return Math.min(1, Math.max(0, quality));
|
|
399
|
+
}
|
|
400
|
+
return 1;
|
|
401
|
+
}
|
|
402
|
+
/** Split a comma-separated header, dropping the empty elements a stray comma leaves. */
|
|
403
|
+
function splitList(header) {
|
|
404
|
+
return header
|
|
405
|
+
.split(',')
|
|
406
|
+
.map((part) => part.trim())
|
|
407
|
+
.filter((part) => part !== '');
|
|
408
|
+
}
|
|
409
|
+
//# sourceMappingURL=negotiate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiate.js","sourceRoot":"","sources":["../../src/web/negotiate.ts"],"names":[],"mappings":"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"}
|