@stopnetic/js 0.0.5-pilot → 0.1.0

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 (39) hide show
  1. package/README.md +77 -44
  2. package/dist/client-guide.md +406 -27
  3. package/dist/server.d.ts +17 -17
  4. package/dist/server.js +125 -123
  5. package/dist/setup.js +126 -124
  6. package/examples/frontend/angular/app/app.config.ts +8 -8
  7. package/examples/frontend/angular/app/app.routes.ts +20 -20
  8. package/examples/frontend/angular/app/app.ts +21 -21
  9. package/examples/frontend/angular/app/members.page.ts +21 -21
  10. package/examples/frontend/angular/app/stopnetic-access.service.ts +27 -27
  11. package/examples/frontend/angular/main.ts +4 -4
  12. package/examples/frontend/load-private-data.d.ts +1 -1
  13. package/examples/frontend/load-private-data.js +9 -9
  14. package/examples/frontend/react/App.tsx +48 -48
  15. package/examples/frontend/react/main.tsx +3 -3
  16. package/examples/frontend/solid/App.tsx +54 -54
  17. package/examples/frontend/solid/main.tsx +3 -3
  18. package/examples/frontend/svelte/App.svelte +56 -56
  19. package/examples/frontend/svelte/main.js +4 -4
  20. package/examples/frontend/vanilla/main.js +46 -46
  21. package/examples/frontend/vue/App.vue +17 -17
  22. package/examples/frontend/vue/Members.vue +14 -14
  23. package/examples/frontend/vue/main.js +4 -4
  24. package/examples/frontend/vue/router.js +40 -40
  25. package/examples/ssr/angular/app.routes.server.ts +5 -0
  26. package/examples/ssr/angular/server.ts +12 -0
  27. package/examples/ssr/astro/middleware.js +11 -0
  28. package/examples/ssr/astro/page.astro +4 -0
  29. package/examples/ssr/node/fetch.mjs +9 -0
  30. package/examples/ssr/node/install.mjs +9 -0
  31. package/examples/ssr/nuxt/middleware.mjs +10 -0
  32. package/examples/ssr/react/install.mjs +10 -0
  33. package/examples/ssr/react-router/install.mjs +22 -0
  34. package/examples/ssr/solidstart/middleware-v1.js +19 -0
  35. package/examples/ssr/solidstart/middleware-v2.js +25 -0
  36. package/examples/ssr/sveltekit/hooks.server.js +16 -0
  37. package/examples/ssr/sveltekit/page.server.js +3 -0
  38. package/examples/ssr/vue/install.mjs +10 -0
  39. package/package.json +37 -10
@@ -2,9 +2,11 @@
2
2
 
3
3
  Angular, React, Vue, Svelte, Solid and plain JavaScript use the same server guard
4
4
  and browser access check. React is optional. Install `@stopnetic/js` in the
5
- website project; choose a hosting platform, not a frontend framework.
5
+ website project. The CLI chooses hosting files; choose your actual SSR framework
6
+ in the instructions below to see where its server guard belongs.
6
7
 
7
- Jump to your implementation: [React Router](#react-router),
8
+ Start with [SSR framework instructions](#ssr-framework-instructions) if the app
9
+ renders on the server. Optional browser-only demos: [React Router](#react-router),
8
10
  [Angular Router](#angular-router), [Vue Router](#vue-router),
9
11
  [Svelte](#svelte-navigation), [Solid Router](#solid-router),
10
12
  [plain JavaScript](#plain-javascript-navigation). Each section includes the
@@ -99,8 +101,11 @@ STOPNETIC_COOKIE_SECRET=your_separate_random_cookie_secret
99
101
 
100
102
  The installer creates a private cookie secret in `.env.local` when missing.
101
103
  Reuse it across deployments. Do not commit `.env.local`. Copy complete PEM
102
- header/body/footer with real newlines or literal `\n`; quote multiline values
103
- in local env files. Hosting value fields do not need surrounding quotes.
104
+ header/body/footer with real newlines or literal `\n`. The dashboard's generated
105
+ local configuration uses an unquoted single-line key with literal `\n`; the SDK
106
+ restores its line breaks. Hosting fields take only the value, with no variable
107
+ name, equals sign or surrounding quotes. Manually written actual multiline PEMs
108
+ still need quotes in env files. Previously generated quoted env files remain valid.
104
109
  Never put private settings in frontend environment files or prefix them with
105
110
  `VITE_`, `REACT_APP_` or `NEXT_PUBLIC_`. Angular's `environment.ts` is browser
106
111
  configuration, not a private server environment.
@@ -148,11 +153,57 @@ the shared guard before its `resolve`, `next` or renderer. A Fetch wrapper is
148
153
  for a complete handler returning a Response, not every framework middleware
149
154
  signature. Node/Express can mount the generated helper around any renderer.
150
155
 
156
+ <!-- BEGIN SSR EXAMPLES -->
157
+
158
+ ### SSR framework instructions
159
+
160
+ Choose the actual SSR framework, not just the browser UI library. Angular
161
+ instructions apply ONLY to Angular. Each section below identifies the server
162
+ file, integration steps, prerender constraints and runtime. The same sections
163
+ appear in the dashboard SSR framework selector. Merge examples into existing
164
+ entries; do not replace SSR, hydration, context or application features.
165
+
166
+ Use your selected framework hook OR the shared Node wrapper above. Do not
167
+ stack both or mount the generated Express helper as an additional guard.
168
+
169
+ | Your app | Choose |
170
+ | --- | --- |
171
+ | Angular with SSR | Angular SSR |
172
+ | React Router framework/server mode | React Router SSR |
173
+ | React plus your own Node renderer | React custom SSR |
174
+ | Nuxt | Nuxt (Vue SSR) |
175
+ | Vue plus your own Node renderer | Vue custom SSR |
176
+ | SvelteKit | SvelteKit SSR |
177
+ | Astro on-demand rendering | Astro SSR |
178
+ | SolidStart | SolidStart SSR; match the installed major version |
179
+ | Other Node renderer / plain JavaScript server | Other Node SSR |
180
+ | Next.js | Dedicated @stopnetic/next guide; do not install the JS control files |
181
+ | React/Vue/Svelte/Solid UI without an SSR server | Option B: static/browser app |
182
+ | PHP, Python, Ruby, C#, Java or Edge/Deno | No native adapter in this Node npm SDK; a separate runtime adapter/gateway is needed |
183
+
184
+ All snippets require SDK 0.0.3-pilot or newer and six SERVER settings. They
185
+ are manual integration examples, not automatic installers for each framework.
186
+ Validate the actual deployed adapter, versions, internal data endpoints and
187
+ cache/static aliases. Hook-contract checks do not prove provider deployment.
188
+
151
189
  ### Angular SSR on Node
152
190
 
153
- For a Fetch-style Angular SSR entry in `src/server.ts`:
191
+ **Where:** src/server.ts (Fetch entry), or your existing Express server.ts before AngularNodeAppEngine.handle.
192
+
193
+ 1. Keep server.ts, main.server.ts, server providers, host configuration and hydration. Merge the guard into the existing request entry; preserve extra framework context.
194
+ 2. Use the Fetch example only for an AngularAppEngine Request/Response entry. An Express entry instead mounts the Node helper before express.static and AngularNodeAppEngine.handle.
195
+ 3. Protected paths use RenderMode.Server. The wildcard below is an example, not a replacement for existing public/hybrid route configuration.
196
+
197
+ **Prerendering and caches:** Do not publish protected RenderMode.Prerender output to a public CDN. Retain public rendering modes only where they cannot bypass future protection. Any path the dashboard may protect must reach the server guard before HTML.
198
+
199
+ **Hosting runtime:** Node.js 22+ only. Keep the existing Node hosting adapter; check the deployed runtime, not just the hosting brand. Edge/Deno deployments need a separate supported adapter or a gateway decision. Do not convert SSR to static rendering automatically.
200
+
201
+ **Netlify Angular only:** Netlify’s automatic Angular SSR uses Edge Functions. The current Node SDK has no drop-in integration for that combination. Keep SSR; obtain a supported Node deployment or gateway decision. A static index.html function is not an Angular SSR adapter.
202
+
203
+ #### src/server.ts
154
204
 
155
205
  ```ts
206
+ // Merge into your existing Node SSR entry; retain host settings and context.
156
207
  import { AngularAppEngine, createRequestHandler } from '@angular/ssr';
157
208
  import { createStopneticAppHandler } from '@stopnetic/js/server';
158
209
 
@@ -166,36 +217,364 @@ async function handle(request: Request): Promise<Response> {
166
217
  export const reqHandler = createRequestHandler(handle);
167
218
  ```
168
219
 
169
- Keep existing Angular host configuration and request context if your entry
170
- uses them. For an Express-based Angular entry, mount `installStopnetic(app)`
171
- before `express.static` and `AngularNodeAppEngine.handle` instead.
172
-
173
- Protected routes in `src/app/app.routes.server.ts` must use request-time
174
- rendering rather than `RenderMode.Prerender`:
220
+ #### src/app/app.routes.server.ts
175
221
 
176
222
  ```ts
177
223
  import { RenderMode, ServerRoute } from '@angular/ssr';
178
224
  export const serverRoutes: ServerRoute[] = [
179
- // All routes are rendered on incoming requests in this example.
225
+ // Demo only: merge with existing hybrid modes rather than replacing them.
180
226
  { path: '**', renderMode: RenderMode.Server },
181
227
  ];
182
228
  ```
183
229
 
184
- This is an all-request-time demonstration, not an instruction to replace an
185
- existing hybrid route configuration. Preserve public rendering modes where
186
- appropriate. Any path the dashboard may protect must reach the guard before
187
- HTML is served; publicly cached/prerendered HTML bypassing it cannot be protected
188
- by adding a dashboard rule alone. Plan server coverage for those paths first.
230
+ Framework references: [Angular SSR](https://angular.dev/guide/ssr), [Netlify Angular runtime](https://docs.netlify.com/build/frameworks/framework-setup-guides/angular/).
231
+
232
+ ### React Router framework mode on Node
233
+
234
+ **Where:** Existing Express entry before @react-router/express createRequestHandler.
235
+
236
+ 1. React Router framework mode supplies the SSR server. Keep its server build, loaders/actions, getLoadContext and hydration; BrowserRouter alone is not an SSR backend.
237
+ 2. Copy the helper, then import and call it at the application root before parsers/assets and the existing createRequestHandler. Do not place it after the renderer or mount it at a stripped URL prefix.
238
+ 3. The helper checks React Router 7 .data requests against their page path, including /_root.data. The actual router still receives its original URL and unread body. This example uses the default / basename; custom basenames and other router versions need alias mapping review.
239
+
240
+ **Prerendering and caches:** Keep ssr enabled in react-router.config.ts. Exclude protected paths from prerender output and CDN caches. Direct .data requests must not expose private loaders merely because the document path is guarded. Independently authorize resource routes, actions and server functions.
241
+
242
+ **Hosting runtime:** Node.js 22+ only. Keep the existing Node hosting adapter; check the deployed runtime, not just the hosting brand. Edge/Deno deployments need a separate supported adapter or a gateway decision. Do not convert SSR to static rendering automatically.
243
+
244
+ #### stopnetic-react-router.mjs
245
+
246
+ ```js
247
+ // Call installStopneticReactRouter(app) before parsers, assets and the existing
248
+ // @react-router/express createRequestHandler. Keep that handler/context intact.
249
+ import { createStopneticServer } from '@stopnetic/js/server';
250
+
251
+ export function installStopneticReactRouter(app) {
252
+ let server;
253
+ app.use((req, res, next) => {
254
+ server ??= createStopneticServer();
255
+ return server.access(req, res, () => {
256
+ // React Router 7 single-fetch data URLs must use the page's policy.
257
+ // The renderer still receives the original request and unread body.
258
+ const url = new URL(req.originalUrl || req.url, 'https://example.invalid');
259
+ let checked = req;
260
+ if (url.pathname.endsWith('.data')) {
261
+ url.pathname = url.pathname === '/_root.data' ? '/' : url.pathname.slice(0, -5);
262
+ checked = Object.create(req);
263
+ checked.originalUrl = url.pathname + url.search;
264
+ }
265
+ return server.guard(checked, res, next);
266
+ });
267
+ });
268
+ }
269
+ ```
270
+
271
+ #### Existing Express entry (merge fragment)
272
+
273
+ ```js
274
+ // In your EXISTING Express entry, before parsers, static files and rendering:
275
+ import { installStopneticReactRouter } from './stopnetic-react-router.mjs';
276
+ installStopneticReactRouter(app);
277
+ // Keep the existing renderer, application routes and server startup below.
278
+ ```
279
+
280
+ Framework references: [React Router server adapters](https://reactrouter.com/api/other-api/adapter), [React Router prerendering](https://reactrouter.com/how-to/pre-rendering).
281
+
282
+ ### React with an existing custom SSR server
283
+
284
+ **Where:** Existing Node/Express server before React rendering and any private data handler.
285
+
286
+ 1. React is a UI library; this option assumes you already have a Node SSR server. Keep renderToPipeableStream/renderToReadableStream, request context and hydrateRoot.
287
+ 2. Copy the helper and call it before the existing assets/API/renderer middleware. For a Fetch-style server use the custom Node Fetch wrapper instead.
288
+ 3. A Vite React SPA without a server uses Option B: static/browser app. Next.js and React Router SSR have their own selector entries. Do not add a second renderer or replace the app with the browser demo.
289
+
290
+ **Prerendering and caches:** Do not statically publish protected HTML or embed private data in public bundles. Keep guarded rendering at request time, retaining public prerendering only with correct server/CDN coverage.
291
+
292
+ **Hosting runtime:** Node.js 22+ only. Keep the existing Node hosting adapter; check the deployed runtime, not just the hosting brand. Edge/Deno deployments need a separate supported adapter or a gateway decision. Do not convert SSR to static rendering automatically.
293
+
294
+ #### stopnetic-react-ssr.mjs
295
+
296
+ ```js
297
+ // Existing custom React SSR server: call before assets, APIs and React rendering.
298
+ // Keep renderToPipeableStream/renderToReadableStream and hydration unchanged.
299
+ import { createStopneticServer } from '@stopnetic/js/server';
300
+ export function installStopneticReactSSR(app) {
301
+ let server;
302
+ app.use((req, res, next) => {
303
+ server ??= createStopneticServer();
304
+ return server.access(req, res, () => server.guard(req, res, next));
305
+ });
306
+ }
307
+ ```
308
+
309
+ #### Existing Express entry (merge fragment)
310
+
311
+ ```js
312
+ // In your EXISTING Express entry, before parsers, static files and rendering:
313
+ import { installStopneticReactSSR } from './stopnetic-react-ssr.mjs';
314
+ installStopneticReactSSR(app);
315
+ // Keep the existing renderer, application routes and server startup below.
316
+ ```
317
+
318
+ Framework references: [React Node SSR](https://react.dev/reference/react-dom/server/renderToPipeableStream).
319
+
320
+ ### Nuxt 3/4 with Nitro 2 and H3 1 on Node
321
+
322
+ **Where:** server/middleware/00-stopnetic.mjs; runs before Nitro renders pages and serves API handlers.
323
+
324
+ 1. Keep Nuxt SSR, its app, pages, hydration and existing Node Nitro preset. Check the installed Nitro/H3 versions; this example uses H3 1 fromNodeMiddleware, not the H3 2 API.
325
+ 2. Add this server middleware before other body readers and handlers. app/middleware and defineNuxtRouteMiddleware are navigation UI hooks and cannot replace this server guard.
326
+ 3. Keep the private environment settings in the deployed Node process. Do not use runtimeConfig.public. Test the built server and separate provider functions, not only Nuxt dev.
327
+
328
+ **Prerendering and caches:** Do not use nuxt generate or prerender/SWR/ISR/cached route rules for protected paths. Protected _payload.json files must not be generated into public output. Keep public route rules only where they cannot expose protected content through CDN/cache aliases.
329
+
330
+ **Hosting runtime:** Node.js 22+ only. Keep the existing Node hosting adapter; check the deployed runtime, not just the hosting brand. Edge/Deno deployments need a separate supported adapter or a gateway decision. Do not convert SSR to static rendering automatically.
331
+
332
+ #### server/middleware/00-stopnetic.mjs
333
+
334
+ ```js
335
+ // server/middleware/00-stopnetic.mjs. Nuxt 3/4, Nitro 2, H3 1, NODE only.
336
+ // Use server middleware, not app/middleware or defineNuxtRouteMiddleware.
337
+ import { fromNodeMiddleware } from 'h3';
338
+ import { createStopneticServer } from '@stopnetic/js/server';
339
+
340
+ let server;
341
+ export default fromNodeMiddleware((req, res, next) => {
342
+ server ??= createStopneticServer();
343
+ return server.access(req, res, () => server.guard(req, res, next));
344
+ });
345
+ ```
346
+
347
+ Framework references: [Nuxt server middleware](https://nuxt.com/docs/4.x/directory-structure/server), [H3 1 Node middleware adapter](https://v1.h3.dev/examples/from-expressjs-to-h3).
348
+
349
+ ### Vue with an existing custom SSR server
350
+
351
+ **Where:** Existing Node/Express server before Vue rendering and private APIs.
352
+
353
+ 1. Vue alone is a UI library. Keep the existing createSSRApp/renderToString server, request context and client hydration.
354
+ 2. Copy and mount the helper before the existing parsers/assets/API/renderer middleware. For a Request/Response server use the custom Node Fetch wrapper.
355
+ 3. A Vue SPA uses Option B. A Nuxt app uses the Nuxt entry; do not replace either with a newly invented Express renderer.
356
+
357
+ **Prerendering and caches:** Protected HTML and private serialized state must be produced only after the server check. Avoid publicly cached/prerendered output for protected paths.
358
+
359
+ **Hosting runtime:** Node.js 22+ only. Keep the existing Node hosting adapter; check the deployed runtime, not just the hosting brand. Edge/Deno deployments need a separate supported adapter or a gateway decision. Do not convert SSR to static rendering automatically.
360
+
361
+ #### stopnetic-vue-ssr.mjs
362
+
363
+ ```js
364
+ // Existing custom Vue SSR server: call before assets, APIs and Vue rendering.
365
+ // Keep createSSRApp/renderToString and client hydration unchanged.
366
+ import { createStopneticServer } from '@stopnetic/js/server';
367
+ export function installStopneticVueSSR(app) {
368
+ let server;
369
+ app.use((req, res, next) => {
370
+ server ??= createStopneticServer();
371
+ return server.access(req, res, () => server.guard(req, res, next));
372
+ });
373
+ }
374
+ ```
375
+
376
+ #### Existing Express entry (merge fragment)
377
+
378
+ ```js
379
+ // In your EXISTING Express entry, before parsers, static files and rendering:
380
+ import { installStopneticVueSSR } from './stopnetic-vue-ssr.mjs';
381
+ installStopneticVueSSR(app);
382
+ // Keep the existing renderer, application routes and server startup below.
383
+ ```
384
+
385
+ Framework references: [Vue SSR](https://vuejs.org/guide/scaling-up/ssr.html).
386
+
387
+ ### SvelteKit server hooks on Node
388
+
389
+ **Where:** src/hooks.server.js, before resolve(event) and any existing application hooks.
390
+
391
+ 1. Keep the existing Node adapter, SSR pages, loads/actions and hydration. Merge this handle into existing hooks; put Stopnetic first and retain other hooks in its continuation.
392
+ 2. The guard receives the original request and per-request continuation. Data requests are checked against event.url, the canonical page path, then resolve receives the original event and body.
393
+ 3. Merge prerender=false into each protected +page.server.js or a covering server layout. building skips runtime enforcement only during build; no protected path may be prerendered. Test with the full adapter server.
394
+
395
+ **Prerendering and caches:** Keep SSR enabled. The runtime handle hook does not protect files already served statically. Exclude protected routes from prerender output, including __data.json. Do not rely on a page rule alone for separately exposed endpoints or remote functions.
396
+
397
+ **Hosting runtime:** Node.js 22+ only. Keep the existing Node hosting adapter; check the deployed runtime, not just the hosting brand. Edge/Deno deployments need a separate supported adapter or a gateway decision. Do not convert SSR to static rendering automatically.
398
+
399
+ #### src/hooks.server.js
400
+
401
+ ```js
402
+ import { building } from '$app/environment';
403
+ import { createStopneticAppHandler } from '@stopnetic/js/server';
404
+
405
+ let guarded;
406
+ // When merging existing hooks, run this first and put them in the continuation.
407
+ export async function handle({ event, resolve }) {
408
+ // Build-time public prerendering only. Protected routes must have prerender=false.
409
+ if (building) return resolve(event);
410
+ guarded ??= createStopneticAppHandler((_request, run) => run());
411
+ // SvelteKit exposes the canonical page URL on data requests. Check that page
412
+ // rather than allowing an unmatched /members/__data.json alias through.
413
+ const checked = event.isDataRequest
414
+ ? new Request(event.url, { method: event.request.method, headers: event.request.headers })
415
+ : event.request;
416
+ return guarded(checked, () => resolve(event));
417
+ }
418
+ ```
419
+
420
+ #### src/routes/members/+page.server.js (merge example)
421
+
422
+ ```js
423
+ // Merge into the PROTECTED route's +page.server.js, retaining loads/actions.
424
+ export const prerender = false;
425
+ export const ssr = true;
426
+ ```
427
+
428
+ Framework references: [SvelteKit server hooks](https://svelte.dev/docs/kit/hooks), [SvelteKit page options](https://svelte.dev/docs/kit/page-options).
429
+
430
+ ### Astro request middleware on Node
431
+
432
+ **Where:** src/middleware.js, before next() renders a page or executes an endpoint.
433
+
434
+ 1. Keep the existing compatible Node adapter and Astro renderer. If middleware exists, merge/sequence this guard first, preserving locals and the existing continuation.
435
+ 2. Use context.request and next(), not a browser startup gate. The isPrerendered branch is build-only public rendering; protected pages must never enter it.
436
+ 3. Set prerender=false on protected .astro pages/endpoints. Keep their existing content. For @astrojs/node middleware mode, an outer Express guard before static files is also available; do not install both patterns unnecessarily.
437
+
438
+ **Prerendering and caches:** Protected pages need on-demand rendering with a Node adapter. Public prerendered pages can remain public, but files bypassing request middleware cannot acquire protection through a dashboard rule alone.
439
+
440
+ **Hosting runtime:** Node.js 22+ only. Keep the existing Node hosting adapter; check the deployed runtime, not just the hosting brand. Edge/Deno deployments need a separate supported adapter or a gateway decision. Do not convert SSR to static rendering automatically.
441
+
442
+ #### src/middleware.js
443
+
444
+ ```js
445
+ // src/middleware.js. Keep the existing Node deployment adapter.
446
+ import { defineMiddleware } from 'astro:middleware';
447
+ import { createStopneticAppHandler } from '@stopnetic/js/server';
448
+
449
+ let guarded;
450
+ export const onRequest = defineMiddleware(async (context, next) => {
451
+ // Public prerender builds only; protected pages must export prerender=false.
452
+ if (context.isPrerendered) return next();
453
+ guarded ??= createStopneticAppHandler((_request, run) => run());
454
+ return guarded(context.request, () => next());
455
+ });
456
+ ```
457
+
458
+ #### src/pages/members.astro (frontmatter flag)
459
+
460
+ ```astro
461
+ ---
462
+ // Merge this flag into a protected page, preserving its existing content.
463
+ export const prerender = false;
464
+ ---
465
+ ```
466
+
467
+ Framework references: [Astro middleware](https://docs.astro.build/en/guides/middleware/), [Astro Node adapter](https://docs.astro.build/en/guides/integrations-guide/node/).
468
+
469
+ ### SolidStart request middleware on Node
470
+
471
+ **Where:** src/middleware.js, registered in the existing SolidStart config before rendering and API execution.
472
+
473
+ 1. Choose the snippet matching your installed major version. SolidStart 2 uses createMiddleware([...]) and H3 event.req/event.res; SolidStart 1 uses onRequest and event.request/event.response.
474
+ 2. For v2 register solidStart({ middleware: "./src/middleware.js" }) in existing vite.config.ts. For v1 register middleware in existing defineConfig in app.config.ts. Keep all other settings and the SSR entry-server.tsx.
475
+ 3. A per-request allowed marker distinguishes an authorized continuation from a verification 204. The framework continues only when the marker is set; no renderer runs on challenge/block/outage. Server functions still require their own permissions and policy checks for their actual endpoint paths.
476
+
477
+ **Prerendering and caches:** Keep SSR enabled and protected routes out of prerender/static/ISR output. Use the existing Node server plugin/preset; do not assume a middleware export changes an Edge deployment into Node.
478
+
479
+ **Hosting runtime:** Node.js 22+ only. Keep the existing Node hosting adapter; check the deployed runtime, not just the hosting brand. Edge/Deno deployments need a separate supported adapter or a gateway decision. Do not convert SSR to static rendering automatically.
480
+
481
+ #### src/middleware.js — SolidStart 2
482
+
483
+ ```js
484
+ // SolidStart 2: register this module using solidStart({ middleware: ... }).
485
+ import { createMiddleware } from '@solidjs/start/middleware';
486
+ import { createStopneticAppHandler } from '@stopnetic/js/server';
487
+
488
+ let check;
489
+ export default createMiddleware([
490
+ async (event, next) => {
491
+ check ??= createStopneticAppHandler((_request, state) => {
492
+ state.allowed = true;
493
+ return new Response(null, { status: 204 });
494
+ });
495
+ // Per-request state: a verification 204 must NEVER be mistaken for permission.
496
+ const state = { allowed: false };
497
+ const response = await check(event.req, state);
498
+ if (!state.allowed) return response;
499
+ event.res.headers.set('cache-control', 'private, no-store');
500
+ const result = await next(); // Preserve H3/SolidStart context and raw results.
501
+ if (!(result instanceof Response)) return result;
502
+ const headers = new Headers(result.headers);
503
+ headers.set('cache-control', 'private, no-store');
504
+ return new Response(result.body, {
505
+ status: result.status, statusText: result.statusText, headers,
506
+ });
507
+ },
508
+ ]);
509
+ ```
510
+
511
+ #### src/middleware.js — SolidStart 1 alternative
512
+
513
+ ```js
514
+ // SolidStart 1: register using defineConfig({ middleware: './src/middleware.js' }).
515
+ // Keep the existing entry-server.tsx renderer. This is NOT SolidStart 2 config.
516
+ import { createMiddleware } from '@solidjs/start/middleware';
517
+ import { createStopneticAppHandler } from '@stopnetic/js/server';
518
+
519
+ let check;
520
+ export default createMiddleware({
521
+ onRequest: async event => {
522
+ check ??= createStopneticAppHandler((_request, state) => {
523
+ state.allowed = true;
524
+ return new Response(null, { status: 204 });
525
+ });
526
+ const state = { allowed: false };
527
+ const response = await check(event.request, state);
528
+ if (!state.allowed) return response;
529
+ event.response.headers.set('cache-control', 'private, no-store');
530
+ // undefined continues to SolidStart; preserve its rendering and API handling.
531
+ },
532
+ });
533
+ ```
534
+
535
+ Framework references: [SolidStart 2 middleware](https://docs.solidjs.com/solid-start/v2/reference/server/create-middleware), [SolidStart 1 middleware](https://docs.solidjs.com/solid-start/v1/reference/server/create-middleware).
536
+
537
+ ### Custom Node SSR or plain JavaScript server
538
+
539
+ **Where:** Existing root request entry, before static HTML, API data or rendering.
540
+
541
+ 1. Use the Express helper OR the Fetch wrapper according to the existing server signature. Do not replace the renderer or introduce a second server.
542
+ 2. Call installStopneticSSR(app) first for Express. For Fetch, export the protected version of the existing handler using protectSSR(renderApplication); all context arguments are retained.
543
+ 3. Keep the canonical Host and private environment in the Node service. Only expose the guarded entry. Independently guard separately deployed API functions.
544
+
545
+ **Prerendering and caches:** Static HTML/cache aliases must not bypass the guard. Preserve existing public routes and apply protection only through dashboard policies.
546
+
547
+ **Hosting runtime:** Node.js 22+ only. Keep the existing Node hosting adapter; check the deployed runtime, not just the hosting brand. Edge/Deno deployments need a separate supported adapter or a gateway decision. Do not convert SSR to static rendering automatically.
548
+
549
+ #### stopnetic-ssr.mjs
550
+
551
+ ```js
552
+ // Call before parsers, static files, APIs and your existing Node SSR renderer.
553
+ import { createStopneticServer } from '@stopnetic/js/server';
554
+ export function installStopneticSSR(app) {
555
+ let server;
556
+ app.use((req, res, next) => {
557
+ server ??= createStopneticServer();
558
+ return server.access(req, res, () => server.guard(req, res, next));
559
+ });
560
+ }
561
+ ```
562
+
563
+ #### stopnetic-fetch-ssr.mjs
564
+
565
+ ```js
566
+ // Wrap your existing Node Request -> Response renderer; retain all context.
567
+ import { createStopneticAppHandler } from '@stopnetic/js/server';
568
+ export function protectSSR(renderApplication) {
569
+ let guarded;
570
+ return (request, ...context) => {
571
+ guarded ??= createStopneticAppHandler(renderApplication);
572
+ return guarded(request, ...context);
573
+ };
574
+ }
575
+ ```
189
576
 
190
- Netlify's automatic Angular SSR adapter currently uses Edge Functions. This
191
- combination does not have a drop-in integration with the current Node SDK.
192
- Do not import the SDK into that Edge entry or assume a Node function intercepts
193
- the Edge renderer. If SSR must stay, keep the SSR files and explain that a
194
- supported Node SSR deployment or a Node gateway that cannot be bypassed is
195
- needed. A custom Node SSR adapter requires separate deployment/routing work;
196
- the static page-function example is not that adapter. Stop and obtain a rendering
197
- or deployment decision if the current runtime is incompatible. Do not delete
198
- `server.ts`, `main.server.ts`, SSR providers or hydration as a workaround.
577
+ <!-- END SSR EXAMPLES -->
199
578
 
200
579
  ### Static apps on Netlify / Vercel
201
580
 
@@ -932,7 +1311,7 @@ server endpoints.
932
1311
  ## Updating an existing generated guide
933
1312
 
934
1313
  After installing the new SDK, rerun `stopnetic-js init --platform <same-platform>`
935
- (preview with `--dry-run`). An unchanged 0.0.2/0.0.3/0.0.4 generated guide is upgraded
1314
+ (preview with `--dry-run`). An unchanged 0.0.2/0.0.3/0.0.4/0.0.5/0.0.6 generated guide is upgraded
936
1315
  with its setup manifest. Your key, cookie secret and server files are preserved.
937
1316
  Customized guides or handlers require manual merging; switching platforms also
938
1317
  requires review. This command does not publish, deploy or contact Stopnetic.
package/dist/server.d.ts CHANGED
@@ -1,17 +1,17 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import type { StopneticServerOptions } from './index.js';
3
- export const accessBase: '/api/stopnetic';
4
- export function optionsFromEnv(env?: Record<string, string | undefined>): StopneticServerOptions;
5
- export function createStopneticAccessHandler(options?: StopneticServerOptions): (request: Request) => Promise<Response>;
6
- export function withStopnetic<Context extends unknown[]>(handler: (request: Request, ...context: Context) => Response | Promise<Response>,
7
- options?: StopneticServerOptions): (request: Request, ...context: Context) => Promise<Response>;
8
- /** Node.js application wrapper: control endpoints, pages and APIs before rendering. */
9
- export function createStopneticAppHandler<Context extends unknown[]>(handler: (request: Request, ...context: Context) => Response | Promise<Response>,
10
- options?: StopneticServerOptions): (request: Request, ...context: Context) => Promise<Response>;
11
- export function createStopneticAccessMiddleware(options?: StopneticServerOptions): (
12
- request: IncomingMessage & { originalUrl?: string }, response: ServerResponse, next: () => unknown,
13
- ) => Promise<unknown>;
14
- export function createStopneticServer(options?: StopneticServerOptions): {
15
- access: ReturnType<typeof createStopneticAccessMiddleware>;
16
- guard: ReturnType<typeof import('./index.js').createStopneticMiddleware>;
17
- };
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { StopneticServerOptions } from './index.js';
3
+ export const accessBase: '/api/stopnetic';
4
+ export function optionsFromEnv(env?: Record<string, string | undefined>): StopneticServerOptions;
5
+ export function createStopneticAccessHandler(options?: StopneticServerOptions): (request: Request) => Promise<Response>;
6
+ export function withStopnetic<Context extends unknown[]>(handler: (request: Request, ...context: Context) => Response | Promise<Response>,
7
+ options?: StopneticServerOptions): (request: Request, ...context: Context) => Promise<Response>;
8
+ /** Node.js application wrapper: control endpoints, pages and APIs before rendering. */
9
+ export function createStopneticAppHandler<Context extends unknown[]>(handler: (request: Request, ...context: Context) => Response | Promise<Response>,
10
+ options?: StopneticServerOptions): (request: Request, ...context: Context) => Promise<Response>;
11
+ export function createStopneticAccessMiddleware(options?: StopneticServerOptions): (
12
+ request: IncomingMessage & { originalUrl?: string }, response: ServerResponse, next: () => unknown,
13
+ ) => Promise<unknown>;
14
+ export function createStopneticServer(options?: StopneticServerOptions): {
15
+ access: ReturnType<typeof createStopneticAccessMiddleware>;
16
+ guard: ReturnType<typeof import('./index.js').createStopneticMiddleware>;
17
+ };