@definitely-fine/nextjs 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 definitely-fine contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # @definitely-fine/nextjs
2
+
3
+ `@definitely-fine/nextjs` connects `definitely-fine` scenario context to Next.js request handling.
4
+
5
+ It reads a scenario id from a request header and runs route handlers or server actions inside that active scenario context.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add definitely-fine @definitely-fine/nextjs
11
+ ```
12
+
13
+ ## Important
14
+
15
+ > [!IMPORTANT]
16
+ > Do not leave scenario activation enabled in production by accident.
17
+ >
18
+ > `@definitely-fine/nextjs` will activate scenario context whenever the request header is present.
19
+ > In production, pair this package with `createRuntime({ enabled: false })` in your core runtime, or avoid wrapping handlers and server actions there entirely.
20
+
21
+ Core runtime protection:
22
+
23
+ ```ts
24
+ import { createRuntime } from "definitely-fine";
25
+
26
+ const runtime = createRuntime<DemoContract>({
27
+ enabled: process.env.NODE_ENV !== "production",
28
+ });
29
+ ```
30
+
31
+ Optional route-level guard:
32
+
33
+ ```ts
34
+ import { withDefinitelyFineScenario } from "@definitely-fine/nextjs";
35
+
36
+ const postCounterResponse = async (_request: Request): Promise<Response> => {
37
+ return Response.json({ ok: true });
38
+ };
39
+
40
+ export const POST =
41
+ process.env.NODE_ENV === "production"
42
+ ? postCounterResponse
43
+ : withDefinitelyFineScenario(postCounterResponse);
44
+ ```
45
+
46
+ ## What This Package Does
47
+
48
+ This package does not replace the core runtime. You still create your scenarios and wrapped runtime with `definitely-fine`.
49
+
50
+ Its job is narrower:
51
+
52
+ - define the scenario header name constant
53
+ - read that header from `Request` or `Headers`
54
+ - run a route handler or server action inside `runWithRuntimeScenarioContext()`
55
+
56
+ ```mermaid
57
+ flowchart LR
58
+ subgraph C[Client or browser process]
59
+ C1[request]
60
+ C2[x-definitely-fine-scenario-id header]
61
+ end
62
+
63
+ subgraph N[Next.js server process]
64
+ N1[@definitely-fine/nextjs wrapper]
65
+ N2[runWithRuntimeScenarioContext]
66
+ N3[definitely-fine runtime]
67
+ N4[wrapped app service or function]
68
+ end
69
+
70
+ subgraph S[Shared scenario storage]
71
+ S1[(persisted scenario JSON)]
72
+ end
73
+
74
+ C1 --> C2 --> N1
75
+ N1 --> N2 --> N3 --> N4
76
+ N3 -- load active scenario --> S1
77
+ ```
78
+
79
+ ## Route Handlers
80
+
81
+ Use `withDefinitelyFineScenario()` to wrap a route handler.
82
+
83
+ ```ts
84
+ import { withDefinitelyFineScenario } from "@definitely-fine/nextjs";
85
+ import { incrementApiCounter } from "../lib/demo-runtime";
86
+
87
+ async function postCounterResponse(_request: Request): Promise<Response> {
88
+ return Response.json(incrementApiCounter());
89
+ }
90
+
91
+ export const POST = withDefinitelyFineScenario(postCounterResponse);
92
+ ```
93
+
94
+ If the request does not include the scenario header, the handler runs normally.
95
+
96
+ ## Server Actions
97
+
98
+ Use `withDefinitelyFineServerAction()` to wrap a server action.
99
+
100
+ ```ts
101
+ "use server";
102
+
103
+ import { withDefinitelyFineServerAction } from "@definitely-fine/nextjs";
104
+
105
+ import {
106
+ incrementActionCounter,
107
+ type CounterResult,
108
+ } from "../lib/demo-runtime";
109
+
110
+ export const incrementCounterAction = withDefinitelyFineServerAction(
111
+ async function incrementCounterActionImplementation(
112
+ _previousState: CounterResult,
113
+ ): Promise<CounterResult> {
114
+ return incrementActionCounter();
115
+ },
116
+ );
117
+ ```
118
+
119
+ ## Header Name
120
+
121
+ The default exported header constant is `DEFINITELY_FINE_SCENARIO_HEADER`.
122
+
123
+ ```ts
124
+ import { DEFINITELY_FINE_SCENARIO_HEADER } from "@definitely-fine/nextjs";
125
+
126
+ const headers = {
127
+ [DEFINITELY_FINE_SCENARIO_HEADER]: "scenario-id",
128
+ };
129
+ ```
130
+
131
+ ## Typical Setup
132
+
133
+ 1. Create and save a scenario with `definitely-fine`.
134
+ 2. Wrap the relevant Next.js route handlers or server actions with this package.
135
+ 3. Send the scenario id in the request header.
136
+ 4. Let your core runtime wrappers decide which calls are intercepted.
137
+
138
+ For safety, production apps should normally disable the core runtime with `enabled: false` and optionally skip these wrappers entirely.
139
+
140
+ ## With Playwright
141
+
142
+ This package pairs naturally with [`@definitely-fine/playwright`](../playwright/README.md), which can create browser contexts that already include the scenario header.
@@ -0,0 +1,64 @@
1
+ //#region src/index.d.ts
2
+ /**
3
+ * Header name used to carry the active definitely-fine scenario id.
4
+ * @public
5
+ */
6
+ declare const DEFINITELY_FINE_SCENARIO_HEADER = "x-definitely-fine-scenario-id";
7
+ /**
8
+ * Returns the raw definitely-fine scenario header value from a `Headers` object.
9
+ * @public
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const scenarioId = getScenarioIdFromHeaders(
14
+ * new Headers([[DEFINITELY_FINE_SCENARIO_HEADER, "checkout"]]),
15
+ * );
16
+ * ```
17
+ */
18
+ declare function getScenarioIdFromHeaders(headers: Headers): string | undefined;
19
+ /**
20
+ * Returns the raw definitely-fine scenario header value from a `Request`.
21
+ * @public
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const scenarioId = getScenarioIdFromRequest(
26
+ * new Request("https://example.test", {
27
+ * headers: {
28
+ * [DEFINITELY_FINE_SCENARIO_HEADER]: "checkout",
29
+ * },
30
+ * }),
31
+ * );
32
+ * ```
33
+ */
34
+ declare function getScenarioIdFromRequest(request: Request): string | undefined;
35
+ /**
36
+ * Wraps a Next.js route handler so it runs inside definitely-fine scenario context.
37
+ * @public
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const handler = withDefinitelyFineScenario(async (request, context) => {
42
+ * return Response.json({ ok: true, params: context.params });
43
+ * });
44
+ * ```
45
+ */
46
+ declare function withDefinitelyFineScenario<TContext, TResult>(handler: (request: Request, context: TContext) => TResult): (request: Request, context: TContext) => TResult;
47
+ /**
48
+ * Wraps a Next.js server action so it runs inside definitely-fine scenario context.
49
+ * @public
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const submitOrder = withDefinitelyFineServerAction(async (formData: FormData) => {
54
+ * return { ok: formData.has("orderId") };
55
+ * });
56
+ * ```
57
+ */
58
+ declare function withDefinitelyFineServerAction<TArgs extends unknown[], TResult>(action: (...args: TArgs) => TResult): (...args: TArgs) => Promise<Awaited<TResult>>;
59
+
60
+ //#endregion
61
+ //# sourceMappingURL=index.d.ts.map
62
+
63
+ export { DEFINITELY_FINE_SCENARIO_HEADER, getScenarioIdFromHeaders, getScenarioIdFromRequest, withDefinitelyFineScenario, withDefinitelyFineServerAction };
64
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":null,"mappings":";;;;;cAMa,+BAAA;AAAb;;;;;;;;;;;iBAagB,wBAAA,UAAkC;AAAlD;;;;;;;;;;;;;;;iBAmBgB,wBAAA,UAAkC;AAAlD;;;;;;;;;;;iBAegB,iEACK,kBAAkB,aAAa,oBACvC,kBAAkB,aAAa;AAF5C;;;;;;;;AAEmD;;;iBA4BnC,mFAII,UAAU,oBACjB,UAAU,QAAQ,QAAQ"}
package/dist/index.js ADDED
@@ -0,0 +1,86 @@
1
+ import { runWithRuntimeScenarioContext } from "definitely-fine";
2
+
3
+ //#region src/index.ts
4
+ /**
5
+ * Header name used to carry the active definitely-fine scenario id.
6
+ * @public
7
+ */
8
+ const DEFINITELY_FINE_SCENARIO_HEADER = "x-definitely-fine-scenario-id";
9
+ /**
10
+ * Returns the raw definitely-fine scenario header value from a `Headers` object.
11
+ * @public
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const scenarioId = getScenarioIdFromHeaders(
16
+ * new Headers([[DEFINITELY_FINE_SCENARIO_HEADER, "checkout"]]),
17
+ * );
18
+ * ```
19
+ */
20
+ function getScenarioIdFromHeaders(headers) {
21
+ return headers.get(DEFINITELY_FINE_SCENARIO_HEADER) ?? void 0;
22
+ }
23
+ /**
24
+ * Returns the raw definitely-fine scenario header value from a `Request`.
25
+ * @public
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const scenarioId = getScenarioIdFromRequest(
30
+ * new Request("https://example.test", {
31
+ * headers: {
32
+ * [DEFINITELY_FINE_SCENARIO_HEADER]: "checkout",
33
+ * },
34
+ * }),
35
+ * );
36
+ * ```
37
+ */
38
+ function getScenarioIdFromRequest(request) {
39
+ return getScenarioIdFromHeaders(request.headers);
40
+ }
41
+ /**
42
+ * Wraps a Next.js route handler so it runs inside definitely-fine scenario context.
43
+ * @public
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const handler = withDefinitelyFineScenario(async (request, context) => {
48
+ * return Response.json({ ok: true, params: context.params });
49
+ * });
50
+ * ```
51
+ */
52
+ function withDefinitelyFineScenario(handler) {
53
+ return function definitelyFineRouteHandler(request, context) {
54
+ const scenarioId = getScenarioIdFromRequest(request);
55
+ if (scenarioId === void 0) return handler(request, context);
56
+ return runWithRuntimeScenarioContext({ scenarioId }, () => {
57
+ return handler(request, context);
58
+ });
59
+ };
60
+ }
61
+ /**
62
+ * Wraps a Next.js server action so it runs inside definitely-fine scenario context.
63
+ * @public
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const submitOrder = withDefinitelyFineServerAction(async (formData: FormData) => {
68
+ * return { ok: formData.has("orderId") };
69
+ * });
70
+ * ```
71
+ */
72
+ function withDefinitelyFineServerAction(action) {
73
+ return async function definitelyFineServerAction(...args) {
74
+ const { headers } = await import("next/headers");
75
+ const requestHeaders = await headers();
76
+ const scenarioId = getScenarioIdFromHeaders(requestHeaders);
77
+ if (scenarioId === void 0) return await action(...args);
78
+ return await runWithRuntimeScenarioContext({ scenarioId }, async () => {
79
+ return await action(...args);
80
+ });
81
+ };
82
+ }
83
+
84
+ //#endregion
85
+ export { DEFINITELY_FINE_SCENARIO_HEADER, getScenarioIdFromHeaders, getScenarioIdFromRequest, withDefinitelyFineScenario, withDefinitelyFineServerAction };
86
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["headers: Headers","request: Request","handler: (request: Request, context: TContext) => TResult","context: TContext","action: (...args: TArgs) => TResult"],"sources":["../src/index.ts"],"sourcesContent":["import { runWithRuntimeScenarioContext } from \"definitely-fine\";\n\n/**\n * Header name used to carry the active definitely-fine scenario id.\n * @public\n */\nexport const DEFINITELY_FINE_SCENARIO_HEADER = \"x-definitely-fine-scenario-id\";\n\n/**\n * Returns the raw definitely-fine scenario header value from a `Headers` object.\n * @public\n *\n * @example\n * ```ts\n * const scenarioId = getScenarioIdFromHeaders(\n * new Headers([[DEFINITELY_FINE_SCENARIO_HEADER, \"checkout\"]]),\n * );\n * ```\n */\nexport function getScenarioIdFromHeaders(headers: Headers): string | undefined {\n return headers.get(DEFINITELY_FINE_SCENARIO_HEADER) ?? undefined;\n}\n\n/**\n * Returns the raw definitely-fine scenario header value from a `Request`.\n * @public\n *\n * @example\n * ```ts\n * const scenarioId = getScenarioIdFromRequest(\n * new Request(\"https://example.test\", {\n * headers: {\n * [DEFINITELY_FINE_SCENARIO_HEADER]: \"checkout\",\n * },\n * }),\n * );\n * ```\n */\nexport function getScenarioIdFromRequest(request: Request): string | undefined {\n return getScenarioIdFromHeaders(request.headers);\n}\n\n/**\n * Wraps a Next.js route handler so it runs inside definitely-fine scenario context.\n * @public\n *\n * @example\n * ```ts\n * const handler = withDefinitelyFineScenario(async (request, context) => {\n * return Response.json({ ok: true, params: context.params });\n * });\n * ```\n */\nexport function withDefinitelyFineScenario<TContext, TResult>(\n handler: (request: Request, context: TContext) => TResult,\n): (request: Request, context: TContext) => TResult {\n return function definitelyFineRouteHandler(\n request: Request,\n context: TContext,\n ): TResult {\n const scenarioId = getScenarioIdFromRequest(request);\n\n if (scenarioId === undefined) {\n return handler(request, context);\n }\n\n return runWithRuntimeScenarioContext({ scenarioId }, () => {\n return handler(request, context);\n });\n };\n}\n\n/**\n * Wraps a Next.js server action so it runs inside definitely-fine scenario context.\n * @public\n *\n * @example\n * ```ts\n * const submitOrder = withDefinitelyFineServerAction(async (formData: FormData) => {\n * return { ok: formData.has(\"orderId\") };\n * });\n * ```\n */\nexport function withDefinitelyFineServerAction<\n TArgs extends unknown[],\n TResult,\n>(\n action: (...args: TArgs) => TResult,\n): (...args: TArgs) => Promise<Awaited<TResult>> {\n return async function definitelyFineServerAction(\n ...args: TArgs\n ): Promise<Awaited<TResult>> {\n const { headers } = await import(\"next/headers\");\n const requestHeaders = await headers();\n const scenarioId = getScenarioIdFromHeaders(requestHeaders);\n\n if (scenarioId === undefined) {\n return await action(...args);\n }\n\n return await runWithRuntimeScenarioContext({ scenarioId }, async () => {\n return await action(...args);\n });\n };\n}\n"],"mappings":";;;;;;;AAMA,MAAa,kCAAkC;;;;;;;;;;;;AAa/C,SAAgB,yBAAyBA,SAAsC;AAC7E,QAAO,QAAQ,IAAI,gCAAgC;AACpD;;;;;;;;;;;;;;;;AAiBD,SAAgB,yBAAyBC,SAAsC;AAC7E,QAAO,yBAAyB,QAAQ,QAAQ;AACjD;;;;;;;;;;;;AAaD,SAAgB,2BACdC,SACkD;AAClD,QAAO,SAAS,2BACdD,SACAE,SACS;EACT,MAAM,aAAa,yBAAyB,QAAQ;AAEpD,MAAI,sBACF,QAAO,QAAQ,SAAS,QAAQ;AAGlC,SAAO,8BAA8B,EAAE,WAAY,GAAE,MAAM;AACzD,UAAO,QAAQ,SAAS,QAAQ;EACjC,EAAC;CACH;AACF;;;;;;;;;;;;AAaD,SAAgB,+BAIdC,QAC+C;AAC/C,QAAO,eAAe,2BACpB,GAAG,MACwB;EAC3B,MAAM,EAAE,SAAS,GAAG,MAAM,OAAO;EACjC,MAAM,iBAAiB,MAAM,SAAS;EACtC,MAAM,aAAa,yBAAyB,eAAe;AAE3D,MAAI,sBACF,QAAO,MAAM,OAAO,GAAG,KAAK;AAG9B,SAAO,MAAM,8BAA8B,EAAE,WAAY,GAAE,YAAY;AACrE,UAAO,MAAM,OAAO,GAAG,KAAK;EAC7B,EAAC;CACH;AACF"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@definitely-fine/nextjs",
3
+ "version": "0.1.0",
4
+ "description": "Next.js adapter for definitely-fine.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "dependencies": {
20
+ "definitely-fine": "0.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "next": "^16.0.0"
24
+ },
25
+ "peerDependencies": {
26
+ "next": ">=16"
27
+ },
28
+ "engines": {
29
+ "node": ">=22"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "tsdown src/index.ts --format esm --dts --publint",
36
+ "typecheck": "tsc --project tsconfig.json"
37
+ }
38
+ }