@hulla/api-fastify 0.0.0-stage → 2.0.1
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/dist/index.d.ts +25 -0
- package/dist/index.js +134 -0
- package/package.json +44 -4
- package/README.md +0 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AdapterErrorInput, AdapterResponse } from "@hulla/api/adapters";
|
|
2
|
+
import { Awaitable, ServerAdapter, ServerContextInput, ServerExecutableFor } from "@hulla/api/server";
|
|
3
|
+
import { Contract } from "@hulla/api";
|
|
4
|
+
import { FastifyInstance as FastifyInstance$1, FastifyReply as FastifyReply$1, FastifyRequest as FastifyRequest$1, RouteHandlerMethod } from "fastify";
|
|
5
|
+
//#region src/index.d.ts
|
|
6
|
+
type FastifyInstance = FastifyInstance$1;
|
|
7
|
+
type FastifyRequest = FastifyRequest$1;
|
|
8
|
+
type FastifyReply = FastifyReply$1;
|
|
9
|
+
type FastifyHandler = RouteHandlerMethod;
|
|
10
|
+
type FastifyAdapterContext = {
|
|
11
|
+
readonly request: FastifyRequest;
|
|
12
|
+
readonly reply: FastifyReply;
|
|
13
|
+
};
|
|
14
|
+
type FastifyContextInput<ContractType extends Contract = Contract> = ServerContextInput<ContractType> & FastifyAdapterContext;
|
|
15
|
+
type FastifyServerErrorInput = Omit<AdapterErrorInput, 'hostContext' | 'request'> & FastifyAdapterContext;
|
|
16
|
+
type FastifyServerOptions = {
|
|
17
|
+
readonly onError?: ((input: FastifyServerErrorInput) => Awaitable<AdapterResponse | undefined | void>) | undefined;
|
|
18
|
+
};
|
|
19
|
+
type FastifyAdapter<App extends FastifyInstance = FastifyInstance> = ServerAdapter<'fastify', FastifyAdapterContext> & {
|
|
20
|
+
readonly mount: <const ContractType extends Contract, const Context extends object>(implementation: ServerExecutableFor<ContractType, Context, 'fastify', FastifyAdapterContext>, options?: FastifyServerOptions) => App;
|
|
21
|
+
};
|
|
22
|
+
/** Creates a Fastify adapter bound to one caller-owned application. */
|
|
23
|
+
declare function fastifyAdapter<const App extends FastifyInstance>(app: App, defaults?: FastifyServerOptions): FastifyAdapter<App>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { FastifyAdapter, FastifyContextInput, FastifyHandler, FastifyInstance, FastifyReply, FastifyRequest, FastifyServerErrorInput, FastifyServerOptions, fastifyAdapter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { nodeRequestHeader, nodeRequestHeaders, nodeRequestLifetime } from "@hulla/api-node";
|
|
2
|
+
import { createAdapterRuntime, toFetchHeaders } from "@hulla/api/adapters";
|
|
3
|
+
import { assertAdapterContext, createServerAdapter, serverContextAdapterId } from "@hulla/api/server";
|
|
4
|
+
|
|
5
|
+
//#region src/index.ts
|
|
6
|
+
function readHeaders() {
|
|
7
|
+
return this.cachedHeaders ??= nodeRequestHeaders(this.request.headers);
|
|
8
|
+
}
|
|
9
|
+
function readHeader(name) {
|
|
10
|
+
return this.cachedHeaders === void 0 ? nodeRequestHeader(this.request.headers, name) : this.cachedHeaders[name];
|
|
11
|
+
}
|
|
12
|
+
function missingBodyParser(representation) {
|
|
13
|
+
return Promise.reject(/* @__PURE__ */ new TypeError(`Fastify request body is unavailable; install a matching content-type parser for ${representation} bodies`));
|
|
14
|
+
}
|
|
15
|
+
function responseSetCookies(headers) {
|
|
16
|
+
return headers.getSetCookie?.() ?? [];
|
|
17
|
+
}
|
|
18
|
+
function writeResponseHeaders(source, target) {
|
|
19
|
+
const setCookies = responseSetCookies(source);
|
|
20
|
+
source.forEach((value, name) => {
|
|
21
|
+
if (name !== "set-cookie" || setCookies.length === 0) target.header(name, value);
|
|
22
|
+
});
|
|
23
|
+
for (const cookie of setCookies) target.header("set-cookie", cookie);
|
|
24
|
+
}
|
|
25
|
+
function writeAdapterHeaders(source, target) {
|
|
26
|
+
for (const [name, value] of Object.entries(source)) target.header(name, value);
|
|
27
|
+
}
|
|
28
|
+
function readableStream(source) {
|
|
29
|
+
const stream = source;
|
|
30
|
+
const iterator = typeof stream?.[Symbol.asyncIterator] === "function" ? stream[Symbol.asyncIterator]() : typeof stream?.[Symbol.iterator] === "function" ? stream[Symbol.iterator]() : void 0;
|
|
31
|
+
if (iterator === void 0) throw new TypeError("Stream response body must be iterable");
|
|
32
|
+
return new ReadableStream({
|
|
33
|
+
async pull(controller) {
|
|
34
|
+
try {
|
|
35
|
+
const result = await iterator.next();
|
|
36
|
+
if (result.done) controller.close();
|
|
37
|
+
else if (result.value instanceof Uint8Array) controller.enqueue(result.value);
|
|
38
|
+
else throw new TypeError("Stream chunk must be Uint8Array");
|
|
39
|
+
} catch (error) {
|
|
40
|
+
try {
|
|
41
|
+
await iterator.return?.();
|
|
42
|
+
} catch {}
|
|
43
|
+
controller.error(error);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
async cancel(reason) {
|
|
47
|
+
await iterator.return?.(reason);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function writeFetchResponse(source, target) {
|
|
52
|
+
target.code(source.status);
|
|
53
|
+
writeResponseHeaders(source.headers, target);
|
|
54
|
+
return target.send(source.body ?? void 0);
|
|
55
|
+
}
|
|
56
|
+
function writeAdapterResponse(source, target) {
|
|
57
|
+
const body = source.body;
|
|
58
|
+
if (body.kind === "raw") {
|
|
59
|
+
if (!(body.value instanceof Response) || body.value.status !== source.status) throw new TypeError("Raw Fetch response status must match its declared contract status");
|
|
60
|
+
return writeFetchResponse(body.value, target);
|
|
61
|
+
}
|
|
62
|
+
if (body.kind === "form-data") {
|
|
63
|
+
if (!(body.value instanceof FormData)) throw new TypeError("Form data response body must be FormData");
|
|
64
|
+
const response = new Response(body.value, {
|
|
65
|
+
status: source.status,
|
|
66
|
+
headers: toFetchHeaders(source.headers)
|
|
67
|
+
});
|
|
68
|
+
response.headers.get("content-type");
|
|
69
|
+
return writeFetchResponse(response, target);
|
|
70
|
+
}
|
|
71
|
+
target.code(source.status);
|
|
72
|
+
writeAdapterHeaders(source.headers, target);
|
|
73
|
+
switch (body.kind) {
|
|
74
|
+
case "empty": return target.send();
|
|
75
|
+
case "json":
|
|
76
|
+
if (body.value === void 0) throw new TypeError("JSON response body cannot encode to undefined");
|
|
77
|
+
return target.send(body.value);
|
|
78
|
+
case "text":
|
|
79
|
+
if (typeof body.value !== "string") throw new TypeError("Text response body must be a string");
|
|
80
|
+
return target.send(body.value);
|
|
81
|
+
case "bytes":
|
|
82
|
+
if (!(body.value instanceof Uint8Array)) throw new TypeError("Byte response body must be Uint8Array");
|
|
83
|
+
return target.send(Buffer.from(body.value.buffer, body.value.byteOffset, body.value.byteLength));
|
|
84
|
+
case "stream": return target.send(readableStream(body.value));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function createRouteHandler(route, includeNativeContext, includeHostContext) {
|
|
88
|
+
return async (request, reply) => {
|
|
89
|
+
const lifetime = nodeRequestLifetime(request.raw, reply.raw);
|
|
90
|
+
const body = request.body === void 0 ? void 0 : { value: request.body };
|
|
91
|
+
const nativeContext = {
|
|
92
|
+
request,
|
|
93
|
+
reply
|
|
94
|
+
};
|
|
95
|
+
const input = {
|
|
96
|
+
request,
|
|
97
|
+
signal: lifetime.signal,
|
|
98
|
+
...includeNativeContext ? { contextInput: nativeContext } : {},
|
|
99
|
+
...includeHostContext ? { hostContext: nativeContext } : {},
|
|
100
|
+
params: request.params,
|
|
101
|
+
query: request.query,
|
|
102
|
+
readHeaders,
|
|
103
|
+
readHeader,
|
|
104
|
+
readBody: (representation) => missingBodyParser(representation),
|
|
105
|
+
...body === void 0 ? {} : { body }
|
|
106
|
+
};
|
|
107
|
+
return writeAdapterResponse(await route.execute(input), reply);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function mountFastify(app, implementation, options) {
|
|
111
|
+
assertAdapterContext(implementation.context, "fastify");
|
|
112
|
+
const includeNativeContext = serverContextAdapterId(implementation.context) !== void 0;
|
|
113
|
+
const runtime = createAdapterRuntime(implementation, options.onError === void 0 ? {} : { onError: ({ hostContext, ...input }) => options.onError?.({
|
|
114
|
+
...input,
|
|
115
|
+
...hostContext
|
|
116
|
+
}) });
|
|
117
|
+
for (const route of runtime.routes) app.route({
|
|
118
|
+
method: route.method,
|
|
119
|
+
url: route.path,
|
|
120
|
+
handler: createRouteHandler(route, includeNativeContext, options.onError !== void 0)
|
|
121
|
+
});
|
|
122
|
+
return app;
|
|
123
|
+
}
|
|
124
|
+
/** Creates a Fastify adapter bound to one caller-owned application. */
|
|
125
|
+
function fastifyAdapter(app, defaults = {}) {
|
|
126
|
+
const configuredDefaults = { ...defaults };
|
|
127
|
+
return createServerAdapter("fastify", { mount: (implementation, options) => mountFastify(app, implementation, options === void 0 ? configuredDefaults : {
|
|
128
|
+
...configuredDefaults,
|
|
129
|
+
...options
|
|
130
|
+
}) });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
export { fastifyAdapter };
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hulla/api-fastify",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Fastify server adapter for @hulla/api implementations",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/hulladev/api.git",
|
|
9
|
+
"directory": "packages/fastify"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public",
|
|
24
|
+
"registry": "https://registry.npmjs.org/"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown",
|
|
28
|
+
"clean": "rm -rf dist coverage",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:coverage": "vitest run --coverage",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"typecheck": "tsc --noEmit"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@hulla/api-node": "^2.0.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@hulla/api": "2.0.1",
|
|
39
|
+
"fastify": "^5.12.1",
|
|
40
|
+
"zod": "^4.4.3"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@hulla/api": "^2.0.1",
|
|
44
|
+
"fastify": "^5.12.1"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/README.md
DELETED