htmx-router 1.0.0-pre1 → 1.0.0-pre2

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.
Files changed (66) hide show
  1. package/cli/config.d.ts +13 -0
  2. package/cli/config.js +11 -0
  3. package/cli/index.d.ts +2 -0
  4. package/cli/index.js +38 -0
  5. package/cookies.d.ts +29 -0
  6. package/cookies.js +80 -0
  7. package/css.d.ts +21 -0
  8. package/css.js +60 -0
  9. package/defer.d.ts +14 -0
  10. package/defer.js +80 -0
  11. package/endpoint.d.ts +20 -0
  12. package/endpoint.js +40 -0
  13. package/event-source.d.ts +26 -0
  14. package/event-source.js +116 -0
  15. package/index.d.ts +19 -0
  16. package/index.js +2 -0
  17. package/internal/client.d.ts +1 -0
  18. package/internal/client.js +14 -0
  19. package/internal/compile/manifest.d.ts +1 -0
  20. package/internal/compile/manifest.js +179 -0
  21. package/internal/component/defer.d.ts +4 -0
  22. package/internal/component/defer.js +19 -0
  23. package/internal/component/head.d.ts +5 -0
  24. package/internal/component/head.js +22 -0
  25. package/internal/component/index.d.ts +4 -0
  26. package/internal/component/index.js +4 -0
  27. package/internal/component/scripts.d.ts +4 -0
  28. package/internal/component/scripts.js +23 -0
  29. package/internal/mount.d.ts +10 -0
  30. package/internal/mount.js +88 -0
  31. package/internal/request/http.d.ts +10 -0
  32. package/internal/request/http.js +61 -0
  33. package/internal/request/index.d.ts +17 -0
  34. package/internal/request/index.js +8 -0
  35. package/internal/request/native.d.ts +9 -0
  36. package/internal/request/native.js +48 -0
  37. package/internal/router.d.ts +15 -0
  38. package/internal/router.js +24 -0
  39. package/internal/util.d.ts +4 -0
  40. package/internal/util.js +49 -0
  41. package/package.json +1 -1
  42. package/response.d.ts +13 -0
  43. package/response.js +46 -0
  44. package/router.d.ts +33 -0
  45. package/router.js +206 -0
  46. package/shell.d.ts +120 -0
  47. package/shell.js +261 -0
  48. package/util/parameters.d.ts +10 -0
  49. package/util/parameters.js +1 -0
  50. package/util/path-builder.d.ts +1 -0
  51. package/util/path-builder.js +45 -0
  52. package/util/route.d.ts +2 -0
  53. package/util/route.js +58 -0
  54. package/vite/bundle-splitter.d.ts +4 -0
  55. package/vite/bundle-splitter.js +26 -0
  56. package/vite/client-island.d.ts +4 -0
  57. package/vite/client-island.js +14 -0
  58. package/vite/index.d.ts +3 -0
  59. package/vite/index.js +3 -0
  60. package/vite/router.d.ts +2 -0
  61. package/vite/router.js +29 -0
  62. package/example/eventdim-react/package.json +0 -67
  63. package/example/eventdim-react/server.js +0 -90
  64. package/example/island-react/global.d.ts +0 -8
  65. package/example/island-react/package.json +0 -38
  66. package/example/island-react/server.js +0 -58
package/response.js ADDED
@@ -0,0 +1,46 @@
1
+ export function text(text, init) {
2
+ init ||= {};
3
+ init.statusText ||= "ok";
4
+ init.status ||= 200;
5
+ const res = new Response(text, init);
6
+ res.headers.set("Content-Type", "text/plain");
7
+ res.headers.set("X-Caught", "true");
8
+ return res;
9
+ }
10
+ export function json(data, init) {
11
+ init ||= {};
12
+ init.statusText ||= "ok";
13
+ init.status ||= 200;
14
+ const res = new Response(JSON.stringify(data), init);
15
+ res.headers.set("Content-Type", "application/json");
16
+ res.headers.set("X-Caught", "true");
17
+ return res;
18
+ }
19
+ export function redirect(url, init) {
20
+ init ||= {};
21
+ init.statusText ||= "Temporary Redirect";
22
+ init.status ||= 307;
23
+ const res = new Response("", init);
24
+ if (!init?.clientOnly)
25
+ res.headers.set("Location", url);
26
+ res.headers.set("HX-Location", url); // use hx-boost if applicable
27
+ return res;
28
+ }
29
+ export function revalidate(init) {
30
+ init ||= {};
31
+ init.statusText ||= "ok";
32
+ init.status ||= 200;
33
+ const res = new Response("", init);
34
+ res.headers.set("HX-Location", "");
35
+ return res;
36
+ }
37
+ export function refresh(init) {
38
+ init ||= {};
39
+ init.statusText ||= "ok";
40
+ init.status ||= 200;
41
+ const res = new Response("", init);
42
+ if (!init?.clientOnly)
43
+ res.headers.set("Refresh", "0"); // fallback
44
+ res.headers.set("HX-Refresh", "true");
45
+ return res;
46
+ }
package/router.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { GenericContext } from "./internal/router.js";
2
+ import { Parameterized, ParameterPrelude, ParameterShaper } from './util/parameters.js';
3
+ import { RouteModule } from "./index.js";
4
+ import { Cookies } from './cookies.js';
5
+ export declare function GenerateRouteTree(props: {
6
+ modules: Record<string, unknown>;
7
+ scope: string;
8
+ }): RouteTree;
9
+ export declare class RouteContext<T extends ParameterShaper = {}> {
10
+ readonly request: Request;
11
+ readonly headers: Headers;
12
+ readonly cookie: Cookies;
13
+ readonly params: Parameterized<T>;
14
+ readonly url: URL;
15
+ render: (res: JSX.Element) => Response;
16
+ constructor(base: GenericContext | RouteContext, params: ParameterPrelude<T>, shape: T);
17
+ }
18
+ export declare class RouteTree {
19
+ private nested;
20
+ private index;
21
+ private slug;
22
+ private wild;
23
+ private wildCard;
24
+ constructor();
25
+ ingest(path: string | string[], module: RouteModule<any>): void;
26
+ resolve(fragments: string[], ctx: GenericContext): Promise<Response | null>;
27
+ private _resolve;
28
+ private resolveIndex;
29
+ private resolveNext;
30
+ private resolveWild;
31
+ private resolveSlug;
32
+ private unwrap;
33
+ }
package/router.js ADDED
@@ -0,0 +1,206 @@
1
+ import { ServerOnlyWarning } from "./internal/util.js";
2
+ ServerOnlyWarning("router");
3
+ // builtin routes
4
+ import * as endpoint from './endpoint.js';
5
+ import * as dynamic from './defer.js';
6
+ import * as mount from './internal/mount.js';
7
+ import * as css from './css.js';
8
+ export function GenerateRouteTree(props) {
9
+ if (!props.scope.endsWith("/"))
10
+ props.scope += "/";
11
+ const tree = new RouteTree();
12
+ for (const path in props.modules) {
13
+ const mod = props.modules[path];
14
+ const tail = path.lastIndexOf(".");
15
+ const url = path.slice(props.scope.length, tail);
16
+ tree.ingest(url, mod);
17
+ if (mod.route)
18
+ mod.route(url);
19
+ }
20
+ // ingest router builtins
21
+ tree.ingest(endpoint.path, endpoint);
22
+ tree.ingest(dynamic.path, dynamic);
23
+ tree.ingest(mount.path, mount);
24
+ tree.ingest(css.path, css);
25
+ return tree;
26
+ }
27
+ export class RouteContext {
28
+ request;
29
+ headers; // response headers
30
+ cookie;
31
+ params;
32
+ url;
33
+ render;
34
+ constructor(base, params, shape) {
35
+ this.cookie = base.cookie;
36
+ this.headers = base.headers;
37
+ this.request = base.request;
38
+ this.render = base.render;
39
+ this.url = base.url;
40
+ this.params = {};
41
+ for (const key in shape) {
42
+ if (!(key in params))
43
+ console.warn(`Parameter ${key} not present in route, but defined in parameters`);
44
+ const func = shape[key];
45
+ const val = func(params[key] || "");
46
+ // NaN moment
47
+ if (func === Number && typeof val === "number" && isNaN(val))
48
+ throw new Error("Invalid Number");
49
+ this.params[key] = val;
50
+ }
51
+ }
52
+ }
53
+ export class RouteTree {
54
+ nested;
55
+ // Leaf nodes
56
+ index; // _index.tsx
57
+ // Wild card routes
58
+ slug; // $
59
+ wild; // e.g. $userID
60
+ wildCard;
61
+ constructor() {
62
+ this.nested = new Map();
63
+ this.index = null;
64
+ this.wildCard = "";
65
+ this.wild = null;
66
+ this.slug = null;
67
+ }
68
+ ingest(path, module) {
69
+ if (!Array.isArray(path))
70
+ path = path.split("/");
71
+ if (path.length === 0 || (path.length == 1 && path[0] === "_index")) {
72
+ this.index = new RouteLeaf(module);
73
+ return;
74
+ }
75
+ if (path[0] === "$") {
76
+ this.slug = new RouteLeaf(module);
77
+ return;
78
+ }
79
+ if (path[0][0] === "$") {
80
+ const wildCard = path[0].slice(1);
81
+ // Check wildcard isn't being changed
82
+ if (!this.wild) {
83
+ this.wildCard = wildCard;
84
+ this.wild = new RouteTree();
85
+ }
86
+ else if (wildCard !== this.wildCard) {
87
+ throw new Error(`Redefinition of wild card ${this.wildCard} to ${wildCard}`);
88
+ }
89
+ path.splice(0, 1);
90
+ this.wild.ingest(path, module);
91
+ return;
92
+ }
93
+ let next = this.nested.get(path[0]);
94
+ if (!next) {
95
+ next = new RouteTree();
96
+ this.nested.set(path[0], next);
97
+ }
98
+ path.splice(0, 1);
99
+ next.ingest(path, module);
100
+ }
101
+ async resolve(fragments, ctx) {
102
+ if (!this.slug)
103
+ return await this._resolve(fragments, ctx);
104
+ try {
105
+ return await this._resolve(fragments, ctx);
106
+ }
107
+ catch (e) {
108
+ return this.unwrap(ctx, e);
109
+ }
110
+ }
111
+ async _resolve(fragments, ctx) {
112
+ let res = await this.resolveIndex(fragments, ctx)
113
+ || await this.resolveNext(fragments, ctx)
114
+ || await this.resolveWild(fragments, ctx)
115
+ || await this.resolveSlug(fragments, ctx);
116
+ if (res instanceof Response) {
117
+ if (100 <= res.status && res.status <= 399)
118
+ return res;
119
+ if (res.headers.has("X-Caught"))
120
+ return res;
121
+ return this.unwrap(ctx, res);
122
+ }
123
+ return res;
124
+ }
125
+ async resolveIndex(fragments, ctx) {
126
+ if (fragments.length > 0)
127
+ return null;
128
+ if (!this.index)
129
+ return null;
130
+ return await this.index.resolve(ctx);
131
+ }
132
+ async resolveNext(fragments, ctx) {
133
+ if (fragments.length < 1)
134
+ return null;
135
+ const next = this.nested.get(fragments[0]);
136
+ if (!next)
137
+ return null;
138
+ return await next.resolve(fragments.slice(1), ctx);
139
+ }
140
+ async resolveWild(fragments, ctx) {
141
+ if (!this.wild)
142
+ return null;
143
+ if (fragments.length < 1)
144
+ return null;
145
+ ctx.params[this.wildCard] = fragments[0];
146
+ return this.wild.resolve(fragments.slice(1), ctx);
147
+ }
148
+ async resolveSlug(fragments, ctx) {
149
+ if (!this.slug)
150
+ return null;
151
+ ctx.params["$"] = fragments.join("/");
152
+ const res = this.slug.resolve
153
+ ? await this.slug.resolve(ctx)
154
+ : null;
155
+ return res;
156
+ }
157
+ async unwrap(ctx, res) {
158
+ if (!this.slug)
159
+ throw res;
160
+ const caught = await this.slug.error(ctx, res);
161
+ caught.headers.set("X-Caught", "true");
162
+ return caught;
163
+ }
164
+ }
165
+ class RouteLeaf {
166
+ module;
167
+ constructor(module) {
168
+ this.module = module;
169
+ }
170
+ async resolve(ctx) {
171
+ const res = await this.renderWrapper(ctx);
172
+ if (res === null)
173
+ return null;
174
+ if (res instanceof Response)
175
+ return res;
176
+ return ctx.render(res);
177
+ }
178
+ async error(ctx, e) {
179
+ if (!this.module.error)
180
+ throw e;
181
+ const res = await this.module.error(ctx, e);
182
+ if (res instanceof Response)
183
+ return res;
184
+ return ctx.render(res);
185
+ }
186
+ async renderWrapper(ctx) {
187
+ try {
188
+ if (!this.module.loader && !this.module.action)
189
+ return null;
190
+ const context = ctx.shape(this.module.parameters || {});
191
+ if (ctx.request.method === "HEAD" || ctx.request.method === "GET") {
192
+ if (this.module.loader)
193
+ return await this.module.loader(context);
194
+ else
195
+ return null;
196
+ }
197
+ if (this.module.action)
198
+ return await this.module.action(context);
199
+ throw new Response("Method not Allowed", { status: 405, statusText: "Method not Allowed", headers: ctx.headers });
200
+ }
201
+ catch (e) {
202
+ return await this.error(ctx, e);
203
+ }
204
+ return null;
205
+ }
206
+ }
package/shell.d.ts ADDED
@@ -0,0 +1,120 @@
1
+ export type ShellOptions<D = {}> = D & MetaDescriptor;
2
+ export declare function ApplyMetaDefaults(options: ShellOptions, defaults: Readonly<Partial<ShellOptions>>): void;
3
+ export type InferShellOptions<F> = F extends (jsx: any, options: infer U) => any ? U : never;
4
+ export type MetaDescriptor = {
5
+ title?: string;
6
+ description?: string;
7
+ meta?: Record<string, string>;
8
+ og?: OpenGraph<string>;
9
+ jsonLD?: LdJsonObject[];
10
+ };
11
+ export declare function RenderMetaDescriptor<T>(options: ShellOptions<T>): string;
12
+ export type LdJsonObject = {
13
+ [Key in string]?: LdJsonValue | undefined;
14
+ };
15
+ type LdJsonArray = LdJsonValue[] | readonly LdJsonValue[];
16
+ type LdJsonPrimitive = string | number | boolean | null;
17
+ type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray;
18
+ export type OpenGraphType = "website" | "article" | "book" | "profile" | "music.song" | "music.album" | "music.playlist" | "music.radio_station" | "video.movie" | "video.episode" | "video.tv_show" | "video.other" | string;
19
+ export type OpenGraph<T extends OpenGraphType = string> = {
20
+ type?: T;
21
+ title?: string;
22
+ description?: string;
23
+ determiner?: string;
24
+ url?: string;
25
+ secure_url?: string;
26
+ locale?: string | {
27
+ base: string;
28
+ alternative: string[];
29
+ };
30
+ image?: OpenGraphImage[];
31
+ video?: OpenGraphVideo[];
32
+ audio?: OpenGraphAudio[];
33
+ } & (T extends "music.song" ? OpenGraphSong : T extends "music.album" ? OpenGraphAlbum : T extends "music.playlist" ? OpenGraphPlaylist : T extends "music.radio_station" ? OpenGraphRadioStation : T extends "video.movie" ? OpenGraphMovie : T extends "video.episode" ? OpenGraphEpisode : T extends "video.tv_show" ? OpenGraphTvShow : T extends "video.other" ? OpenGraphVideoOther : T extends "article" ? OpenGraphArticle : T extends "book" ? OpenGraphBook : T extends "profile" ? OpenGraphProfile : {});
34
+ export type OpenGraphImage = {
35
+ url: string;
36
+ secure_url?: string;
37
+ type?: string;
38
+ width?: number;
39
+ height?: number;
40
+ alt?: string;
41
+ };
42
+ export type OpenGraphVideo = {
43
+ url: string;
44
+ type?: string;
45
+ secure_url?: string;
46
+ width?: number;
47
+ height?: number;
48
+ alt?: string;
49
+ };
50
+ export type OpenGraphAudio = {
51
+ url: string;
52
+ type?: string;
53
+ secure_url?: string;
54
+ };
55
+ type OpenGraphSong = {
56
+ duration?: number;
57
+ album?: Array<string | {
58
+ url: string;
59
+ disc?: number;
60
+ track?: number;
61
+ }>;
62
+ musician?: string[];
63
+ };
64
+ type OpenGraphAlbum = {
65
+ songs?: Array<string | {
66
+ url: string;
67
+ disc?: number;
68
+ track?: number;
69
+ }>;
70
+ musician?: string[];
71
+ release_date?: Date;
72
+ };
73
+ type OpenGraphPlaylist = {
74
+ songs?: Array<string | {
75
+ url: string;
76
+ disc?: number;
77
+ track?: number;
78
+ }>;
79
+ creator?: string[];
80
+ };
81
+ type OpenGraphRadioStation = {
82
+ creator?: string[];
83
+ };
84
+ type OpenGraphMovie = {
85
+ actors?: Array<string | {
86
+ url: string;
87
+ role: string;
88
+ }>;
89
+ directors?: string[];
90
+ writers?: string[];
91
+ duration?: number;
92
+ release_date?: Date;
93
+ tag: string[];
94
+ };
95
+ type OpenGraphEpisode = OpenGraphMovie & {
96
+ series?: string;
97
+ };
98
+ type OpenGraphTvShow = OpenGraphMovie;
99
+ type OpenGraphVideoOther = OpenGraphMovie;
100
+ type OpenGraphArticle = {
101
+ published_time?: Date;
102
+ modified_time?: Date;
103
+ expiration_time?: Date;
104
+ authors?: string[];
105
+ section?: string;
106
+ tag?: string;
107
+ };
108
+ type OpenGraphBook = {
109
+ authors?: string[];
110
+ isbn?: string;
111
+ release_date?: Date;
112
+ tag?: string;
113
+ };
114
+ type OpenGraphProfile = {
115
+ first_name?: string;
116
+ last_name?: string;
117
+ username?: string;
118
+ gender?: "male" | "female";
119
+ };
120
+ export {};
package/shell.js ADDED
@@ -0,0 +1,261 @@
1
+ import { ServerOnlyWarning } from "./internal/util.js";
2
+ ServerOnlyWarning("shell");
3
+ export function ApplyMetaDefaults(options, defaults) {
4
+ if (defaults.title && !options.title)
5
+ options.title = defaults.title;
6
+ if (defaults.description && !options.description)
7
+ options.description = defaults.description;
8
+ if (defaults.meta && !options.meta)
9
+ options.meta = defaults.meta;
10
+ if (defaults.og && !options.og)
11
+ options.og = defaults.og;
12
+ if (defaults.jsonLD && !options.jsonLD)
13
+ options.jsonLD = defaults.jsonLD;
14
+ }
15
+ export function RenderMetaDescriptor(options) {
16
+ let out = "";
17
+ if (options.title)
18
+ out += `<title>${EscapeHTML(options.title)}</title>`;
19
+ if (options.description)
20
+ out += `<meta name="description" content="${EscapeHTML(options.description)}">\n`;
21
+ if (options.meta)
22
+ for (const key in options.meta) {
23
+ out += `<meta name="${EscapeHTML(key)}" content="${EscapeHTML(options.meta[key])}">\n`;
24
+ }
25
+ if (options.jsonLD)
26
+ for (const json of options.jsonLD) {
27
+ out += `<script type="application/ld+json">${JSON.stringify(json)}</script>\n`;
28
+ }
29
+ // Apply open graphs
30
+ if (options.og) {
31
+ // Infer from meta if not present
32
+ if (!options.og.title)
33
+ options.og.title = options.title;
34
+ if (!options.og.description)
35
+ options.og.title = options.description;
36
+ out += RenderOpenGraph(options.og);
37
+ }
38
+ else {
39
+ // Auto apply og:title + og:description if og not present
40
+ if (options.title)
41
+ out += `<meta property="og:title" content="${EscapeHTML(options.title)}">\n`;
42
+ if (options.description)
43
+ out += `<meta property="og:description" content="${EscapeHTML(options.description)}">\n`;
44
+ }
45
+ return out;
46
+ }
47
+ function RenderOpenGraph(og) {
48
+ // Manually encoding everything rather than using a loop to ensure they are in the correct order
49
+ // And to ensure extra values can't leak in creating unsafe og tags
50
+ const type = og.type || "website";
51
+ let out = RenderProperty("og:type", type);
52
+ if (og.title)
53
+ out += RenderProperty("og:title", og.title);
54
+ if (og.description)
55
+ out += RenderProperty("og:description", og.description);
56
+ if (og.determiner)
57
+ out += RenderProperty("og:determiner", og.determiner);
58
+ if (og.url)
59
+ out += RenderProperty("og:url", og.url);
60
+ if (og.secure_url)
61
+ out += RenderProperty("og:secure_url", og.secure_url);
62
+ if (og.locale) {
63
+ if (typeof og.locale === "string")
64
+ out += RenderProperty("og:locale", og.locale);
65
+ else {
66
+ out += RenderProperty("og:locale", og.locale.base);
67
+ for (const l of og.locale.alternative)
68
+ out += RenderProperty("og:locale:alternative", l);
69
+ }
70
+ }
71
+ if (og.image)
72
+ for (const img of og.image) {
73
+ out += RenderProperty("og:image", img.url);
74
+ if (img.secure_url)
75
+ out += RenderProperty("og:image:secure_url", img.secure_url);
76
+ if (img.type)
77
+ out += RenderProperty("og:image:type", img.type);
78
+ if (img.width)
79
+ out += RenderProperty("og:image:width", img.width.toString());
80
+ if (img.height)
81
+ out += RenderProperty("og:image:height", img.height.toString());
82
+ if (img.alt)
83
+ out += RenderProperty("og:image:alt", img.alt);
84
+ }
85
+ if (og.video)
86
+ for (const vid of og.video) {
87
+ out += RenderProperty("og:video", vid.url);
88
+ if (vid.secure_url)
89
+ out += RenderProperty("og:video:secure_url", vid.secure_url);
90
+ if (vid.type)
91
+ out += RenderProperty("og:video:type", vid.type);
92
+ if (vid.width)
93
+ out += RenderProperty("og:video:width", vid.width.toString());
94
+ if (vid.height)
95
+ out += RenderProperty("og:video:height", vid.height.toString());
96
+ if (vid.alt)
97
+ out += RenderProperty("og:video:alt", vid.alt);
98
+ }
99
+ if (og.audio)
100
+ for (const audio of og.audio) {
101
+ out += RenderProperty("og:audio", audio.url);
102
+ if (audio.secure_url)
103
+ out += RenderProperty("og:audio:secure_url", audio.secure_url);
104
+ if (audio.type)
105
+ out += RenderProperty("og:audio:type", audio.type);
106
+ }
107
+ return out + RenderOpenGraphExtras(og);
108
+ }
109
+ function RenderProperty(name, value) {
110
+ return `<meta property="${name}" content="${EscapeHTML(value)}">\n`;
111
+ }
112
+ function RenderOpenGraphExtras(og) {
113
+ let out = "";
114
+ if (og.type === "music.song") {
115
+ const g = og;
116
+ if (g.duration)
117
+ out += RenderProperty("og:music:duration", g.duration.toString());
118
+ if (g.album)
119
+ for (const album of g.album) {
120
+ if (typeof album === "string")
121
+ out += RenderProperty("og:music:album", album);
122
+ else {
123
+ out += RenderProperty("og:music:album", album.url);
124
+ if (album.disc)
125
+ out += RenderProperty("og:music:album:disc", album.disc.toString());
126
+ if (album.track)
127
+ out += RenderProperty("og:music:album:track", album.track.toString());
128
+ }
129
+ }
130
+ if (g.musician)
131
+ for (const profile of g.musician)
132
+ out += RenderProperty("og:music:musician", profile);
133
+ return out;
134
+ }
135
+ if (og.type === "music.album") {
136
+ const g = og;
137
+ if (g.songs)
138
+ for (const song of g.songs) {
139
+ if (typeof song === "string")
140
+ out += RenderProperty("og:music:song", song);
141
+ else {
142
+ out += RenderProperty("og:music:song", song.url);
143
+ if (song.disc)
144
+ out += RenderProperty("og:music:song:disc", song.disc.toString());
145
+ if (song.track)
146
+ out += RenderProperty("og:music:song:track", song.track.toString());
147
+ }
148
+ }
149
+ if (g.musician)
150
+ for (const profile of g.musician)
151
+ out += RenderProperty("og:music:musician", profile);
152
+ if (g.release_date)
153
+ out += RenderProperty("og:music:release_date", g.release_date.toISOString());
154
+ return out;
155
+ }
156
+ if (og.type === "music.playlist") {
157
+ const g = og;
158
+ if (g.songs)
159
+ for (const song of g.songs) {
160
+ if (typeof song === "string")
161
+ out += RenderProperty("og:music:song", song);
162
+ else {
163
+ out += RenderProperty("og:music:song", song.url);
164
+ if (song.disc)
165
+ out += RenderProperty("og:music:song:disc", song.disc.toString());
166
+ if (song.track)
167
+ out += RenderProperty("og:music:song:track", song.track.toString());
168
+ }
169
+ }
170
+ if (g.creator)
171
+ for (const profile of g.creator)
172
+ out += RenderProperty("og:music:creator", profile);
173
+ return out;
174
+ }
175
+ if (og.type === "music.radio_station") {
176
+ const g = og;
177
+ if (g.creator)
178
+ for (const profile of g.creator)
179
+ out += RenderProperty("og:music:creator", profile);
180
+ return out;
181
+ }
182
+ if (og.type === "video.movie" || og.type === "video.episode" || og.type === "video.tv_show" || og.type === "video.other") {
183
+ const g = og;
184
+ if (g.actors)
185
+ for (const actor of g.actors) {
186
+ if (typeof actor === "string")
187
+ out += RenderProperty("og:video:actor", actor);
188
+ else {
189
+ out += RenderProperty("og:video:actor", actor.url);
190
+ out += RenderProperty("og:video:actor:role", actor.role);
191
+ }
192
+ }
193
+ if (g.directors)
194
+ for (const profile of g.directors)
195
+ out += RenderProperty("og:video:director", profile);
196
+ if (g.writers)
197
+ for (const profile of g.writers)
198
+ out += RenderProperty("og:video:writer", profile);
199
+ if (g.duration)
200
+ out += RenderProperty("og:video:duration", g.duration.toString());
201
+ if (g.release_date)
202
+ out += RenderProperty("og:video:release_date", g.release_date.toISOString());
203
+ if (g.tag)
204
+ for (const tag of g.tag)
205
+ out += RenderProperty("og:video:tag", tag);
206
+ if (g.series)
207
+ out += RenderProperty("og:video:series", g.series);
208
+ }
209
+ if (og.type === "article") {
210
+ const g = og;
211
+ if (g.published_time)
212
+ out += RenderProperty("og:article:published_time", g.published_time.toISOString());
213
+ if (g.modified_time)
214
+ out += RenderProperty("og:article:modified_time", g.modified_time.toISOString());
215
+ if (g.expiration_time)
216
+ out += RenderProperty("og:article:expiration_time", g.expiration_time.toISOString());
217
+ if (g.authors)
218
+ for (const profile of g.authors)
219
+ out += RenderProperty("og:article:author", profile);
220
+ if (g.section)
221
+ out += RenderProperty("og:article:section", g.section);
222
+ if (g.tag)
223
+ for (const tag of g.tag)
224
+ out += RenderProperty("og:video:tag", tag);
225
+ }
226
+ if (og.type === "book") {
227
+ const g = og;
228
+ if (g.authors)
229
+ for (const profile of g.authors)
230
+ out += RenderProperty("og:article:author", profile);
231
+ if (g.isbn)
232
+ out += RenderProperty("og:book:isbn", g.isbn);
233
+ if (g.release_date)
234
+ out += RenderProperty("og:book:release_date", g.release_date.toISOString());
235
+ if (g.tag)
236
+ for (const tag of g.tag)
237
+ out += RenderProperty("og:video:tag", tag);
238
+ }
239
+ if (og.type === "profile") {
240
+ const g = og;
241
+ if (g.first_name)
242
+ out += RenderProperty("og:profile:first_name", g.first_name);
243
+ if (g.last_name)
244
+ out += RenderProperty("og:profile:last_name", g.last_name);
245
+ if (g.username)
246
+ out += RenderProperty("og:profile:username", g.username);
247
+ if (g.gender)
248
+ out += RenderProperty("og:profile:gender", g.gender);
249
+ }
250
+ return "";
251
+ }
252
+ const escapeTo = {
253
+ "&": "&amp;",
254
+ "<": "&lt;",
255
+ ">": "&gt;",
256
+ "\"": "&quot;",
257
+ "'": "&#39;",
258
+ };
259
+ function EscapeHTML(str) {
260
+ return str.replace(/[&<>"']/g, (match) => escapeTo[match] || match);
261
+ }
@@ -0,0 +1,10 @@
1
+ export type ParameterShaper = Record<string, (val: string) => any>;
2
+ export type ParameterPrelude<T extends ParameterShaper> = {
3
+ [K in keyof T]: string;
4
+ };
5
+ export type Parameterized<T extends ParameterShaper> = {
6
+ [K in keyof T]: ReturnType<T[K]>;
7
+ };
8
+ export type Parameterizer<T extends ParameterShaper> = {
9
+ [K in keyof T]: (val: ReturnType<T[K]>) => string;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};