diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 541474d..81ca95d 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -29,6 +29,9 @@ jobs:
- name: npm test
run: npm test
+ - name: npm types
+ run: npm run types
+
- name: npx semantic-release
run: npx semantic-release
env:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2c1b562..1882a67 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -32,3 +32,6 @@ jobs:
- name: npm test
run: npm test
+
+ - name: npm types
+ run: npm run types
diff --git a/.gitignore b/.gitignore
index 671a52f..04e892c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,6 @@ coverage/
node_modules/
*.log
.vscode
-.tap
\ No newline at end of file
+.tap
+*.d.ts
+!podium.d.ts
diff --git a/README.md b/README.md
index c0bb092..23028c4 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ const app = fastify();
const podlet = new Podlet({
pathname: '/',
version: '2.0.0',
- name: 'podletContent',
+ name: 'podlet-content',
});
// Register the plugin, with the podlet as the option
@@ -44,21 +44,24 @@ app.get(podlet.content(), async (request, reply) => {
return;
}
reply.podiumSend('
Hello world
');
+ await reply;
});
app.get(podlet.manifest(), async (request, reply) => {
reply.send(podlet);
+ await reply;
});
const start = async () => {
try {
- await app.listen(7100);
+ await app.listen({ port: 7100 });
app.log.info(`server listening on ${app.server.address().port}`);
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
+
start();
```
@@ -86,6 +89,7 @@ app.get(podlet.content(), async (request, reply) => {
return;
}
reply.podiumSend('Hello world
');
+ await reply;
});
```
diff --git a/example/server.js b/example/server.js
deleted file mode 100644
index 6a9abc5..0000000
--- a/example/server.js
+++ /dev/null
@@ -1,59 +0,0 @@
-'use strict';
-
-const fastifyPodletPlugin = require('../');
-const fastify = require('fastify');
-const Podlet = require('@podium/podlet');
-
-const app = fastify({ logger: true });
-
-const podlet = new Podlet({
- pathname: '/',
- fallback: '/fallback',
- version: `2.0.0-${Date.now().toString()}`,
- logger: console,
- name: 'podletContent',
- development: true,
-});
-
-podlet.defaults({
- locale: 'nb-NO',
-});
-
-app.register(fastifyPodletPlugin, podlet);
-
-app.get(podlet.content(), async (request, reply) => {
- if (reply.app.podium.context.locale === 'nb-NO') {
- reply.podiumSend('Hei verden
');
- return;
- }
- reply.podiumSend('Hello world
');
-});
-
-app.get(podlet.fallback(), async (request, reply) => {
- reply.podiumSend('We are sorry but we can not display this!
');
-});
-
-app.get(podlet.manifest(), async (request, reply) => {
- reply.send(podlet);
-});
-
-app.get('/public', async (request, reply) => {
- reply.send({ say: 'Hello world' });
-});
-
-// Test URL: http://localhost:7100/podium-resource/podletContent/localApi
-podlet.proxy({ target: '/public', name: 'localApi' });
-// Test URL: http://localhost:7100/podium-resource/podletContent/remoteApi
-podlet.proxy({ target: 'https://api.ipify.org', name: 'remoteApi' });
-
-// Run the server!
-const start = async () => {
- try {
- await app.listen(7100);
- app.log.info(`server listening on ${app.server.address().port}`);
- } catch (err) {
- app.log.error(err);
- process.exit(1);
- }
-};
-start();
diff --git a/fixup.js b/fixup.js
new file mode 100755
index 0000000..7fd5625
--- /dev/null
+++ b/fixup.js
@@ -0,0 +1,12 @@
+import fs from 'node:fs';
+import path from 'node:path';
+
+let podium = path.join(process.cwd(), 'types', 'podium.d.ts');
+let module = path.join(process.cwd(), 'types', 'podlet-plugin.d.ts');
+
+fs.writeFileSync(
+ module,
+ `${fs.readFileSync(podium, 'utf-8')}
+${fs.readFileSync(module, 'utf-8')}`,
+ 'utf-8',
+);
diff --git a/lib/podlet-plugin.js b/lib/podlet-plugin.js
index 43c461f..dbd5a3f 100644
--- a/lib/podlet-plugin.js
+++ b/lib/podlet-plugin.js
@@ -1,68 +1,85 @@
import { HttpIncoming, pathnameBuilder } from '@podium/utils';
import fp from 'fastify-plugin';
-const podiumPodletFastifyPlugin = (fastify, podlet, done) => {
- // Decorate reply with .app.podium we can write to throught the request
- fastify.decorateReply('app', null);
- fastify.addHook('onRequest', async (request, reply) => {
- reply.app = {
- podium: {},
- };
- });
-
- // Run parsers on pre handler and store state object on reply.app.podium
- fastify.addHook('preHandler', async (request, reply) => {
- const incoming = new HttpIncoming(
- request.raw,
- reply.raw,
- reply.app.params,
- );
- reply.app.podium = await podlet.process(incoming, { proxy: false });
- });
-
- // Set http headers on response
- fastify.addHook('preHandler', async (request, reply) => {
- reply.header('podlet-version', podlet.version);
- });
-
- // Decorate response with .podiumSend() method
- fastify.decorateReply('podiumSend', function podiumSend(payload) {
- this.type('text/html; charset=utf-8'); // "this" here is the fastify 'Reply' object
- this.send(podlet.render(this.app.podium, payload));
- });
-
- // Mount proxy route as an instance so its executed only on
- // the registered path. Iow: the proxy check is not run on
- // any other routes
- fastify.register((instance, opts, next) => {
- const pathname = pathnameBuilder(
- podlet.httpProxy.pathname,
- podlet.httpProxy.prefix,
- '/*',
- );
+export default fp(
+ /**
+ * @type {import('fastify').FastifyPluginCallback}
+ */
+ (fastify, podlet, done) => {
+ // Decorate reply with .app.podium we can write to throught the request
+ fastify.decorateReply('app', null);
+ fastify.addHook('onRequest', async (request, reply) => {
+ // @ts-ignore We decorate this above
+ reply.app = {
+ podium: {},
+ };
+ });
- // Allow all content types for proxy requests
- // https://github.com/fastify/fastify/blob/main/docs/ContentTypeParser.md#catch-all
- instance.addContentTypeParser('*', (req, payload, cb) => {
- cb();
+ // Run parsers on pre handler and store state object on reply.app.podium
+ fastify.addHook('preHandler', async (request, reply) => {
+ const incoming = new HttpIncoming(
+ request.raw,
+ reply.raw,
+ // @ts-ignore
+ reply.app.params,
+ );
+ // @ts-ignore We decorate this above
+ reply.app.podium = await podlet.process(incoming, { proxy: false });
});
- instance.addHook('preHandler', async (req, reply) => {
- const incoming = await podlet.httpProxy.process(reply.app.podium);
- if (incoming.proxy) return;
- return incoming;
+ // Set http headers on response
+ fastify.addHook('preHandler', async (request, reply) => {
+ reply.header('podlet-version', podlet.version);
});
- instance.all(pathname, (req, reply) => {
- reply.code(404).send('Not found');
+ // Decorate response with .podiumSend() method
+ fastify.decorateReply('podiumSend', function podiumSend(payload) {
+ this.type('text/html; charset=utf-8'); // "this" here is the fastify 'Reply' object
+ this.send(
+ podlet.render(
+ // @ts-ignore We decorate this above
+ this.app.podium,
+ payload,
+ ),
+ );
});
- next();
- });
+ // Mount proxy route as an instance so its executed only on
+ // the registered path. Iow: the proxy check is not run on
+ // any other routes
+ fastify.register((instance, opts, next) => {
+ const pathname = pathnameBuilder(
+ podlet.httpProxy.pathname,
+ podlet.httpProxy.prefix,
+ '/*',
+ );
- done();
-};
+ // Allow all content types for proxy requests
+ // https://github.com/fastify/fastify/blob/main/docs/ContentTypeParser.md#catch-all
+ instance.addContentTypeParser('*', (req, payload, cb) => {
+ // @ts-ignore
+ cb();
+ });
+
+ instance.addHook('preHandler', async (req, reply) => {
+ const incoming = await podlet.httpProxy.process(
+ // @ts-ignore We decorate this above
+ reply.app.podium,
+ );
+ if (incoming.proxy) return;
+ return incoming;
+ });
+
+ instance.all(pathname, (req, reply) => {
+ reply.code(404).send('Not found');
+ });
+
+ next();
+ });
-export default fp(podiumPodletFastifyPlugin, {
- name: 'podium-podlet',
-});
+ done();
+ },
+ {
+ name: 'podium-podlet',
+ },
+);
diff --git a/package.json b/package.json
index 82b9de7..3808dd3 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"type": "module",
"description": "Fastify plugin for Podium Podlet",
"main": "lib/podlet-plugin.js",
+ "types": "types/podlet-plugin.d.ts",
"repository": {
"type": "git",
"url": "git@github.com:podium-lib/fastify-podlet.git"
@@ -17,13 +18,18 @@
"CHANGELOG.md",
"README.md",
"LICENSE",
- "lib"
+ "lib",
+ "types"
],
"scripts": {
"test": "tap --disable-coverage --allow-empty-coverage",
"test:coverage": "tap",
"lint": "eslint .",
- "lint:fix": "eslint . --fix"
+ "lint:fix": "eslint . --fix",
+ "types": "run-s types:tsc types:fixup",
+ "types:tsc": "tsc",
+ "types:test": "tsc --project tsconfig.test.json",
+ "types:fixup": "node ./fixup.js"
},
"author": "Trygve Lie",
"license": "MIT",
@@ -40,7 +46,7 @@
},
"devDependencies": {
"@fastify/formbody": "7.4.0",
- "@podium/podlet": "5.1.12",
+ "@podium/podlet": "5.1.17",
"@podium/test-utils": "2.5.2",
"@semantic-release/changelog": "6.0.3",
"@semantic-release/commit-analyzer": "11.1.0",
@@ -53,8 +59,10 @@
"eslint-plugin-prettier": "5.2.1",
"fastify": "4.28.1",
"globals": "15.9.0",
+ "npm-run-all2": "6.2.3",
"prettier": "3.3.3",
"semantic-release": "23.1.1",
- "tap": "18.8.0"
+ "tap": "18.8.0",
+ "typescript": "5.6.2"
}
}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..076be6a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "lib": ["es2020"],
+ "module": "nodenext",
+ "target": "es2020",
+ "resolveJsonModule": true,
+ "checkJs": true,
+ "allowJs": true,
+ "moduleResolution": "nodenext",
+ "emitDeclarationOnly": true,
+ "declaration": true,
+ "allowSyntheticDefaultImports": true,
+ "outDir": "types"
+ },
+ "include": ["./lib/**/*.js"]
+}
diff --git a/tsconfig.test.json b/tsconfig.test.json
new file mode 100644
index 0000000..530ad59
--- /dev/null
+++ b/tsconfig.test.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["./tests/**/*.js"],
+ "compilerOptions": {
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "noEmit": true
+ }
+}
diff --git a/types/podium.d.ts b/types/podium.d.ts
new file mode 100644
index 0000000..2c56b86
--- /dev/null
+++ b/types/podium.d.ts
@@ -0,0 +1,42 @@
+import { HttpIncoming } from '@podium/utils';
+
+declare module 'fastify' {
+
+
+ interface PodiumHttpIncomingParameters {
+ [key: string]: unknown;
+ }
+
+ // @podium/podlet declares what's on the context. We use the same interface names here to inherit them.
+
+ interface PodiumHttpIncomingContext {
+ [key: string]: unknown;
+ }
+
+ interface PodiumHttpIncomingViewParameters {
+ [key: string]: unknown;
+ }
+
+ interface PodiumLocals {
+ podium: HttpIncoming;
+ }
+
+ interface FastifyReply {
+ app: PodiumLocals;
+
+ /**
+ * Calls the send / write method on the `http.ServerResponse` object.
+ *
+ * When in development mode this method will wrap the provided fragment in a
+ * default HTML document before dispatching. When not in development mode, this
+ * method will just dispatch the fragment.
+ *
+ * @example
+ * app.get(podlet.content(), async (req, reply) => {
+ * reply.podiumSend('Hello World
');
+ * await reply;
+ * });
+ */
+ podiumSend(fragment: string, ...args: unknown[]): Response;
+ }
+}