@fedify/h3 0.1.0-dev.5

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 ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright 2024 Hong Minhee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ @fedify/h3: Integrate Fedify with h3
2
+ ====================================
3
+
4
+ This package provides a simple way to integrate [Fedify] with [h3].
5
+ The integration code looks like this:
6
+
7
+ ~~~~ typescript
8
+ import { createApp, createRouter } from "h3";
9
+ import { integrateFederation, onError } from "@fedify/h3";
10
+ import { federation } from "./federation"; // Your `Federation` instance
11
+
12
+ export const app = createApp({ onError });
13
+ app.use(
14
+ integrateFederation(
15
+ federation,
16
+ (event, request) => "context data goes here"
17
+ )
18
+ );
19
+
20
+ const router = createRouter();
21
+ app.use(router);
22
+ ~~~~
23
+
24
+ > [!NOTE]
25
+ > Your app has to configure `onError` to let Fedify negotiate content types.
26
+ > If you don't do this, Fedify will not be able to respond with a proper error
27
+ > status code when a content negotiation fails.
28
+
29
+ [Fedify]: https://fedify.dev/
30
+ [h3]: https://h3.unjs.io/
31
+
32
+
33
+ Changelog
34
+ ---------
35
+
36
+ ### Version 0.1.0
37
+
38
+ To be released.
@@ -0,0 +1,31 @@
1
+ import { Federation } from '@fedify/fedify';
2
+ import { H3Event, EventHandlerRequest, EventHandler, EventHandlerResponse, H3Error } from 'h3';
3
+
4
+ /**
5
+ * A factory function that creates the context data that will be passed to the
6
+ * `Federation` instance.
7
+ * @typeParam TContextData The type of the context data that will be passed to
8
+ * the `Federation` instance.
9
+ * @param event The event that triggered the handler.
10
+ * @param request The request that triggered the handler.
11
+ * @returns The context data that will be passed to the `Federation` instance.
12
+ * This can be a promise that resolves to the context data.
13
+ */
14
+ type ContextDataFactory<TContextData> = (event: H3Event<EventHandlerRequest>, request: Request) => Promise<TContextData> | TContextData;
15
+ /**
16
+ * Integrates a `Federation` instance with an H3 handler.
17
+ * @param federation
18
+ * @param contextDataFactory
19
+ * @returns
20
+ */
21
+ declare function integrateFederation<TContextData>(federation: Federation<TContextData>, contextDataFactory: ContextDataFactory<TContextData>): EventHandler<EventHandlerRequest, EventHandlerResponse>;
22
+ /**
23
+ * An error handler that responds with a 406 Not Acceptable if Fedify
24
+ * responded with a 406 Not Acceptable and the actual handler responded with
25
+ * a 404 Not Found.
26
+ * @param error The error that occurred.
27
+ * @param event The event that triggered the handler.
28
+ */
29
+ declare function onError(error: H3Error<unknown>, event: H3Event<EventHandlerResponse>): Promise<void>;
30
+
31
+ export { type ContextDataFactory, integrateFederation, onError };
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ // src/index.ts
2
+ import {
3
+ defineEventHandler,
4
+ toWebRequest
5
+ } from "h3";
6
+ function integrateFederation(federation, contextDataFactory) {
7
+ return defineEventHandler({
8
+ async handler(event) {
9
+ const request = toWebRequest(event);
10
+ const response = await federation.fetch(request, {
11
+ contextData: await contextDataFactory(event, request)
12
+ });
13
+ if (response.status === 404) return;
14
+ if (response.status === 406) {
15
+ event.context["__fedify_response__"] = response;
16
+ return;
17
+ }
18
+ await event.respondWith(response);
19
+ }
20
+ });
21
+ }
22
+ async function onError(error, event) {
23
+ if ("__fedify_response__" in event.context && event.context["__fedify_response__"].status === 406 && error.statusCode === 404) {
24
+ await event.respondWith(event.context["__fedify_response__"]);
25
+ }
26
+ }
27
+ export {
28
+ integrateFederation,
29
+ onError
30
+ };
31
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL2luZGV4LnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJpbXBvcnQgdHlwZSB7IEZlZGVyYXRpb24gfSBmcm9tIFwiQGZlZGlmeS9mZWRpZnlcIjtcbmltcG9ydCB7XG4gIHR5cGUgRXZlbnRIYW5kbGVyLFxuICB0eXBlIEV2ZW50SGFuZGxlclJlcXVlc3QsXG4gIHR5cGUgRXZlbnRIYW5kbGVyUmVzcG9uc2UsXG4gIHR5cGUgSDNFcnJvcixcbiAgdHlwZSBIM0V2ZW50LFxuICBkZWZpbmVFdmVudEhhbmRsZXIsXG4gIHRvV2ViUmVxdWVzdCxcbn0gZnJvbSBcImgzXCI7XG5cbi8qKlxuICogQSBmYWN0b3J5IGZ1bmN0aW9uIHRoYXQgY3JlYXRlcyB0aGUgY29udGV4dCBkYXRhIHRoYXQgd2lsbCBiZSBwYXNzZWQgdG8gdGhlXG4gKiBgRmVkZXJhdGlvbmAgaW5zdGFuY2UuXG4gKiBAdHlwZVBhcmFtIFRDb250ZXh0RGF0YSBUaGUgdHlwZSBvZiB0aGUgY29udGV4dCBkYXRhIHRoYXQgd2lsbCBiZSBwYXNzZWQgdG9cbiAqICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBgRmVkZXJhdGlvbmAgaW5zdGFuY2UuXG4gKiBAcGFyYW0gZXZlbnQgVGhlIGV2ZW50IHRoYXQgdHJpZ2dlcmVkIHRoZSBoYW5kbGVyLlxuICogQHBhcmFtIHJlcXVlc3QgVGhlIHJlcXVlc3QgdGhhdCB0cmlnZ2VyZWQgdGhlIGhhbmRsZXIuXG4gKiBAcmV0dXJucyBUaGUgY29udGV4dCBkYXRhIHRoYXQgd2lsbCBiZSBwYXNzZWQgdG8gdGhlIGBGZWRlcmF0aW9uYCBpbnN0YW5jZS5cbiAqICAgICAgICAgIFRoaXMgY2FuIGJlIGEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIHRoZSBjb250ZXh0IGRhdGEuXG4gKi9cbmV4cG9ydCB0eXBlIENvbnRleHREYXRhRmFjdG9yeTxUQ29udGV4dERhdGE+ID0gKFxuICBldmVudDogSDNFdmVudDxFdmVudEhhbmRsZXJSZXF1ZXN0PixcbiAgcmVxdWVzdDogUmVxdWVzdCxcbikgPT4gUHJvbWlzZTxUQ29udGV4dERhdGE+IHwgVENvbnRleHREYXRhO1xuXG4vKipcbiAqIEludGVncmF0ZXMgYSBgRmVkZXJhdGlvbmAgaW5zdGFuY2Ugd2l0aCBhbiBIMyBoYW5kbGVyLlxuICogQHBhcmFtIGZlZGVyYXRpb25cbiAqIEBwYXJhbSBjb250ZXh0RGF0YUZhY3RvcnlcbiAqIEByZXR1cm5zXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbnRlZ3JhdGVGZWRlcmF0aW9uPFRDb250ZXh0RGF0YT4oXG4gIGZlZGVyYXRpb246IEZlZGVyYXRpb248VENvbnRleHREYXRhPixcbiAgY29udGV4dERhdGFGYWN0b3J5OiBDb250ZXh0RGF0YUZhY3Rvcnk8VENvbnRleHREYXRhPixcbik6IEV2ZW50SGFuZGxlcjxFdmVudEhhbmRsZXJSZXF1ZXN0LCBFdmVudEhhbmRsZXJSZXNwb25zZT4ge1xuICByZXR1cm4gZGVmaW5lRXZlbnRIYW5kbGVyKHtcbiAgICBhc3luYyBoYW5kbGVyKGV2ZW50KSB7XG4gICAgICBjb25zdCByZXF1ZXN0ID0gdG9XZWJSZXF1ZXN0KGV2ZW50KTtcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmVkZXJhdGlvbi5mZXRjaChyZXF1ZXN0LCB7XG4gICAgICAgIGNvbnRleHREYXRhOiBhd2FpdCBjb250ZXh0RGF0YUZhY3RvcnkoZXZlbnQsIHJlcXVlc3QpLFxuICAgICAgfSk7XG4gICAgICAvLyBJZiB0aGUgcmVzcG9uc2UgaXMgNDA0IE5vdCBGb3VuZCwgdGhlbiB3ZSBkZWxlZ2F0ZSB0aGUgaGFuZGxpbmcgdG9cbiAgICAgIC8vIHRoZSBuZXh0IGhhbmRsZXIgaW4gdGhlIGNoYWluLiAgVGhpcyBpcyBiZWNhdXNlIHRoZSBoYW5kbGVyIG1pZ2h0XG4gICAgICAvLyBoYXZlIGFuIGVuZHBvaW50IHRoYXQgRmVkaWZ5IGRvZXMgbm90IGhhdmUsIGFuZCB3ZSB3YW50IHRvIGdpdmUgdGhlXG4gICAgICAvLyBoYW5kbGVyIGEgY2hhbmNlIHRvIHJlc3BvbmQgdG8gdGhlIHJlcXVlc3Q6XG4gICAgICBpZiAocmVzcG9uc2Uuc3RhdHVzID09PSA0MDQpIHJldHVybjtcbiAgICAgIC8vIFNlZSBhbHNvIG9uQmVmb3JlUmVzcG9uc2UoKSBhYm92ZTpcbiAgICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDQwNikge1xuICAgICAgICBldmVudC5jb250ZXh0W1wiX19mZWRpZnlfcmVzcG9uc2VfX1wiXSA9IHJlc3BvbnNlO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBhd2FpdCBldmVudC5yZXNwb25kV2l0aChyZXNwb25zZSk7XG4gICAgfSxcbiAgfSk7XG59XG5cbi8qKlxuICogQW4gZXJyb3IgaGFuZGxlciB0aGF0IHJlc3BvbmRzIHdpdGggYSA0MDYgTm90IEFjY2VwdGFibGUgaWYgRmVkaWZ5XG4gKiByZXNwb25kZWQgd2l0aCBhIDQwNiBOb3QgQWNjZXB0YWJsZSBhbmQgdGhlIGFjdHVhbCBoYW5kbGVyIHJlc3BvbmRlZCB3aXRoXG4gKiBhIDQwNCBOb3QgRm91bmQuXG4gKiBAcGFyYW0gZXJyb3IgVGhlIGVycm9yIHRoYXQgb2NjdXJyZWQuXG4gKiBAcGFyYW0gZXZlbnQgVGhlIGV2ZW50IHRoYXQgdHJpZ2dlcmVkIHRoZSBoYW5kbGVyLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gb25FcnJvcihcbiAgZXJyb3I6IEgzRXJyb3I8dW5rbm93bj4sXG4gIGV2ZW50OiBIM0V2ZW50PEV2ZW50SGFuZGxlclJlc3BvbnNlPixcbik6IFByb21pc2U8dm9pZD4ge1xuICAvLyBJZiBGZWRpZnkgcmVzcG9uZGVkIHdpdGggYSA0MDYgTm90IEFjY2VwdGFibGUgYW5kIGxhdGVyIG9uIHRoZSBhY3R1YWxcbiAgLy8gaGFuZGxlciByZXNwb25kZWQgd2l0aCBhIDQwNCBOb3QgRm91bmQsIHRoZW4gd2UgY29uc2lkZXIgaXQgZmFpbGVkXG4gIC8vIHRvIG5lZ290aWF0ZSBhIHJlc3BvbnNlLiAgRm9yIGV4YW1wbGUsIGlmIEZlZGlmeSBoYXMgYW4gZW5kcG9pbnQgL2Zvb1xuICAvLyB0aGF0IHN1cHBvcnRzIG9ubHkgYXBwbGljYXRpb24vYWN0aXZpdHkranNvbiBhbmQgdGhlIGhhbmRsZXIgaGFzIG5vXG4gIC8vIGVuZHBvaW50IC9mb28gYXQgYWxsLCB0aGVuIHdoZW4gYSBjbGllbnQgcmVxdWVzdHMgL2ZvbyB3aXRoXG4gIC8vIEFjY2VwdDogdGV4dC9odG1sLCBpdCBzaG91bGQgcmVzcG9uZCB3aXRoIDQwNiBOb3QgQWNjZXB0YWJsZTpcbiAgaWYgKFxuICAgIFwiX19mZWRpZnlfcmVzcG9uc2VfX1wiIGluIGV2ZW50LmNvbnRleHQgJiZcbiAgICBldmVudC5jb250ZXh0W1wiX19mZWRpZnlfcmVzcG9uc2VfX1wiXS5zdGF0dXMgPT09IDQwNiAmJlxuICAgIGVycm9yLnN0YXR1c0NvZGUgPT09IDQwNFxuICApIHtcbiAgICBhd2FpdCBldmVudC5yZXNwb25kV2l0aChldmVudC5jb250ZXh0W1wiX19mZWRpZnlfcmVzcG9uc2VfX1wiXSk7XG4gIH1cbn1cbiJdLAogICJtYXBwaW5ncyI6ICI7QUFDQTtBQUFBLEVBTUU7QUFBQSxFQUNBO0FBQUEsT0FDSztBQXVCQSxTQUFTLG9CQUNkLFlBQ0Esb0JBQ3lEO0FBQ3pELFNBQU8sbUJBQW1CO0FBQUEsSUFDeEIsTUFBTSxRQUFRLE9BQU87QUFDbkIsWUFBTSxVQUFVLGFBQWEsS0FBSztBQUNsQyxZQUFNLFdBQVcsTUFBTSxXQUFXLE1BQU0sU0FBUztBQUFBLFFBQy9DLGFBQWEsTUFBTSxtQkFBbUIsT0FBTyxPQUFPO0FBQUEsTUFDdEQsQ0FBQztBQUtELFVBQUksU0FBUyxXQUFXLElBQUs7QUFFN0IsVUFBSSxTQUFTLFdBQVcsS0FBSztBQUMzQixjQUFNLFFBQVEscUJBQXFCLElBQUk7QUFDdkM7QUFBQSxNQUNGO0FBQ0EsWUFBTSxNQUFNLFlBQVksUUFBUTtBQUFBLElBQ2xDO0FBQUEsRUFDRixDQUFDO0FBQ0g7QUFTQSxlQUFzQixRQUNwQixPQUNBLE9BQ2U7QUFPZixNQUNFLHlCQUF5QixNQUFNLFdBQy9CLE1BQU0sUUFBUSxxQkFBcUIsRUFBRSxXQUFXLE9BQ2hELE1BQU0sZUFBZSxLQUNyQjtBQUNBLFVBQU0sTUFBTSxZQUFZLE1BQU0sUUFBUSxxQkFBcUIsQ0FBQztBQUFBLEVBQzlEO0FBQ0Y7IiwKICAibmFtZXMiOiBbXQp9Cg==
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@fedify/h3",
3
+ "version": "0.1.0-dev.5+74336af4",
4
+ "description": "Integrate Fedify with h3",
5
+ "keywords": [
6
+ "Fedify",
7
+ "h3"
8
+ ],
9
+ "author": {
10
+ "name": "Hong Minhee",
11
+ "email": "hong@minhee.org",
12
+ "url": "https://hongminhee.org/"
13
+ },
14
+ "homepage": "https://github.com/dahlia/fedify-h3",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/dahlia/fedify-h3.git"
18
+ },
19
+ "license": "MIT",
20
+ "bugs": {
21
+ "url": "https://github.com/dahlia/fedify-h3/issues"
22
+ },
23
+ "type": "module",
24
+ "module": "dist/index.js",
25
+ "types": "dist/index.d.ts",
26
+ "files": [
27
+ "src/",
28
+ "dist/"
29
+ ],
30
+ "devDependencies": {
31
+ "@biomejs/biome": "1.8.3",
32
+ "@types/node": "^20.14.10",
33
+ "tsup": "^8.1.0",
34
+ "typescript": "^5.0.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@fedify/fedify": ">=0.10.0, <1",
38
+ "h3": "^1.8.0"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "prepack": "tsup"
43
+ }
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,82 @@
1
+ import type { Federation } from "@fedify/fedify";
2
+ import {
3
+ type EventHandler,
4
+ type EventHandlerRequest,
5
+ type EventHandlerResponse,
6
+ type H3Error,
7
+ type H3Event,
8
+ defineEventHandler,
9
+ toWebRequest,
10
+ } from "h3";
11
+
12
+ /**
13
+ * A factory function that creates the context data that will be passed to the
14
+ * `Federation` instance.
15
+ * @typeParam TContextData The type of the context data that will be passed to
16
+ * the `Federation` instance.
17
+ * @param event The event that triggered the handler.
18
+ * @param request The request that triggered the handler.
19
+ * @returns The context data that will be passed to the `Federation` instance.
20
+ * This can be a promise that resolves to the context data.
21
+ */
22
+ export type ContextDataFactory<TContextData> = (
23
+ event: H3Event<EventHandlerRequest>,
24
+ request: Request,
25
+ ) => Promise<TContextData> | TContextData;
26
+
27
+ /**
28
+ * Integrates a `Federation` instance with an H3 handler.
29
+ * @param federation
30
+ * @param contextDataFactory
31
+ * @returns
32
+ */
33
+ export function integrateFederation<TContextData>(
34
+ federation: Federation<TContextData>,
35
+ contextDataFactory: ContextDataFactory<TContextData>,
36
+ ): EventHandler<EventHandlerRequest, EventHandlerResponse> {
37
+ return defineEventHandler({
38
+ async handler(event) {
39
+ const request = toWebRequest(event);
40
+ const response = await federation.fetch(request, {
41
+ contextData: await contextDataFactory(event, request),
42
+ });
43
+ // If the response is 404 Not Found, then we delegate the handling to
44
+ // the next handler in the chain. This is because the handler might
45
+ // have an endpoint that Fedify does not have, and we want to give the
46
+ // handler a chance to respond to the request:
47
+ if (response.status === 404) return;
48
+ // See also onBeforeResponse() above:
49
+ if (response.status === 406) {
50
+ event.context["__fedify_response__"] = response;
51
+ return;
52
+ }
53
+ await event.respondWith(response);
54
+ },
55
+ });
56
+ }
57
+
58
+ /**
59
+ * An error handler that responds with a 406 Not Acceptable if Fedify
60
+ * responded with a 406 Not Acceptable and the actual handler responded with
61
+ * a 404 Not Found.
62
+ * @param error The error that occurred.
63
+ * @param event The event that triggered the handler.
64
+ */
65
+ export async function onError(
66
+ error: H3Error<unknown>,
67
+ event: H3Event<EventHandlerResponse>,
68
+ ): Promise<void> {
69
+ // If Fedify responded with a 406 Not Acceptable and later on the actual
70
+ // handler responded with a 404 Not Found, then we consider it failed
71
+ // to negotiate a response. For example, if Fedify has an endpoint /foo
72
+ // that supports only application/activity+json and the handler has no
73
+ // endpoint /foo at all, then when a client requests /foo with
74
+ // Accept: text/html, it should respond with 406 Not Acceptable:
75
+ if (
76
+ "__fedify_response__" in event.context &&
77
+ event.context["__fedify_response__"].status === 406 &&
78
+ error.statusCode === 404
79
+ ) {
80
+ await event.respondWith(event.context["__fedify_response__"]);
81
+ }
82
+ }