@ovineko/spa-guard-fastify 0.0.1-alpha-24 → 0.0.1-alpha-26

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/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # @ovineko/spa-guard-fastify
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@ovineko/spa-guard-fastify)](https://www.npmjs.com/package/@ovineko/spa-guard-fastify)
4
+ [![license](https://img.shields.io/npm/l/@ovineko/spa-guard-fastify)](./LICENSE)
5
+
6
+ Fastify plugin for spa-guard beacon endpoint and HTML cache handler with ETag/304 support.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ npm install @ovineko/spa-guard-fastify @ovineko/spa-guard-node fastify fastify-plugin
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ### Beacon endpoint
17
+
18
+ Register the plugin to receive beacon data posted by the spa-guard client runtime:
19
+
20
+ ```ts
21
+ import Fastify from "fastify";
22
+ import { fastifySPAGuard } from "@ovineko/spa-guard-fastify";
23
+
24
+ const app = Fastify();
25
+
26
+ app.addContentTypeParser("text/plain", { parseAs: "string" }, (_req, body, done) => {
27
+ done(null, body);
28
+ });
29
+
30
+ app.register(fastifySPAGuard, {
31
+ path: "/api/beacon",
32
+ onBeacon: async (beacon, request, reply) => {
33
+ request.log.info({ beacon }, "SPA Guard beacon received");
34
+ // optionally suppress default log
35
+ return { skipDefaultLog: true };
36
+ },
37
+ onUnknownBeacon: async (body, request) => {
38
+ request.log.warn({ body }, "Unknown beacon format");
39
+ },
40
+ });
41
+
42
+ await app.listen({ port: 3000 });
43
+ ```
44
+
45
+ ### HTML cache handler
46
+
47
+ Serve your SPA's `index.html` with ETag/304 and compression negotiation:
48
+
49
+ ```ts
50
+ import { createReadStream } from "node:fs";
51
+ import { spaGuardFastifyHandler } from "@ovineko/spa-guard-fastify";
52
+ import { createHtmlCache } from "@ovineko/spa-guard-node";
53
+
54
+ const handlerOptions = {};
55
+
56
+ app.get("/*", async (request, reply) => {
57
+ return spaGuardFastifyHandler(request, reply, {
58
+ ...handlerOptions,
59
+ getHtml: () => ({ html: "<html>...</html>" }),
60
+ });
61
+ });
62
+ ```
63
+
64
+ ## API
65
+
66
+ ### `fastifySPAGuard` (Fastify plugin)
67
+
68
+ Registers a `POST` route at `options.path` to receive beacon payloads.
69
+
70
+ Options:
71
+
72
+ - `path` (required) - Route path for the beacon endpoint, e.g. `"/api/beacon"`
73
+ - `onBeacon(beacon, request, reply)` - Called with parsed beacon data. Return `{ skipDefaultLog: true }` to suppress default logging.
74
+ - `onUnknownBeacon(body, request, reply)` - Called when beacon fails schema validation. Return `{ skipDefaultLog: true }` to suppress default warning.
75
+
76
+ ### `spaGuardFastifyHandler(request, reply, options)`
77
+
78
+ Fastify request handler that serves HTML with ETag/304 and content-encoding negotiation.
79
+
80
+ Options:
81
+
82
+ - `cache` - Pre-built `HtmlCache` instance
83
+ - `getHtml()` - Async factory returning `CreateHtmlCacheOptions`; cache is created lazily on first request
84
+
85
+ ### Exported types
86
+
87
+ - `FastifySPAGuardOptions`
88
+ - `SpaGuardHandlerOptions`
89
+ - `BeaconHandlerResult`
90
+ - `BeaconError`
91
+
92
+ ## Related packages
93
+
94
+ - [@ovineko/spa-guard](../spa-guard/README.md) - Core package
95
+ - [@ovineko/spa-guard-node](../node/README.md) - Node.js HTML cache
96
+ - [@ovineko/spa-guard-vite](../vite/README.md) - Vite build plugin
97
+
98
+ ## License
99
+
100
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
2
+ import type { CreateHtmlCacheOptions, HtmlCache } from "@ovineko/spa-guard-node";
2
3
  import type { BeaconSchema } from "@ovineko/spa-guard/schema";
3
4
  export { BeaconError } from "@ovineko/spa-guard";
4
5
  export interface BeaconHandlerResult {
@@ -31,3 +32,10 @@ export interface FastifySPAGuardOptions {
31
32
  path: string;
32
33
  }
33
34
  export declare const fastifySPAGuard: FastifyPluginAsync<FastifySPAGuardOptions>;
35
+ export interface SpaGuardHandlerOptions {
36
+ /** @internal Promise used to deduplicate concurrent lazy cache creation */
37
+ _cachePromise?: Promise<HtmlCache>;
38
+ cache?: HtmlCache;
39
+ getHtml?: (() => CreateHtmlCacheOptions) | (() => Promise<CreateHtmlCacheOptions>);
40
+ }
41
+ export declare function spaGuardFastifyHandler(request: FastifyRequest, reply: FastifyReply, options: SpaGuardHandlerOptions): Promise<FastifyReply>;
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/index.ts
2
+ import { createHtmlCache } from "@ovineko/spa-guard-node";
2
3
  import fp from "fastify-plugin";
3
4
  import { BeaconError } from "@ovineko/spa-guard";
4
5
  import { logMessage } from "@ovineko/spa-guard/_internal";
@@ -82,7 +83,32 @@ var fastifySPAGuard = fp(fastifySPAGuardPlugin, {
82
83
  fastify: "5.x || 4.x",
83
84
  name: `${name}/fastify`
84
85
  });
86
+ async function spaGuardFastifyHandler(request, reply, options) {
87
+ const { getHtml } = options;
88
+ if (!options.cache && !getHtml) {
89
+ throw new Error("spaGuardFastifyHandler requires either 'cache' or 'getHtml' option");
90
+ }
91
+ if (!options.cache && getHtml) {
92
+ options._cachePromise ??= (async () => createHtmlCache(await getHtml()))().catch(
93
+ (error) => {
94
+ options._cachePromise = void 0;
95
+ throw error;
96
+ }
97
+ );
98
+ options.cache = await options._cachePromise;
99
+ }
100
+ const cache = options.cache;
101
+ const acceptEncoding = request.headers["accept-encoding"];
102
+ const acceptLanguage = request.headers["accept-language"];
103
+ const ifNoneMatch = request.headers["if-none-match"];
104
+ const response = cache.get({ acceptEncoding, acceptLanguage, ifNoneMatch });
105
+ for (const [key, value] of Object.entries(response.headers)) {
106
+ reply.header(key, value);
107
+ }
108
+ return reply.status(response.statusCode).send(response.body);
109
+ }
85
110
  export {
86
111
  BeaconError,
87
- fastifySPAGuard
112
+ fastifySPAGuard,
113
+ spaGuardFastifyHandler
88
114
  };
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@ovineko/spa-guard-fastify",
3
- "version": "0.0.1-alpha-24",
4
- "description": "Fastify plugin for spa-guard beacon endpoint",
3
+ "version": "0.0.1-alpha-26",
4
+ "description": "Fastify plugin for spa-guard beacon endpoint and HTML cache handler with ETag/304",
5
5
  "keywords": [
6
6
  "spa",
7
7
  "fastify",
8
8
  "beacon",
9
- "monitoring"
9
+ "monitoring",
10
+ "etag",
11
+ "cache",
12
+ "html"
10
13
  ],
11
14
  "homepage": "https://github.com/ovineko/ovineko/tree/main/spa-guard/fastify",
12
15
  "bugs": {
@@ -31,11 +34,12 @@
31
34
  "dist"
32
35
  ],
33
36
  "dependencies": {
34
- "@ovineko/spa-guard": "0.0.1-alpha-24"
37
+ "@ovineko/spa-guard": "0.0.1-alpha-26"
35
38
  },
36
39
  "peerDependencies": {
37
40
  "fastify": "^5 || ^4",
38
- "fastify-plugin": "^5 || ^4"
41
+ "fastify-plugin": "^5 || ^4",
42
+ "@ovineko/spa-guard-node": "0.0.1-alpha-26"
39
43
  },
40
44
  "engines": {
41
45
  "node": ">=22.15.0"