@definitely-fine/hono 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,137 @@
1
+ # @definitely-fine/hono
2
+
3
+ `@definitely-fine/hono` connects `definitely-fine` scenario context to Hono request handling.
4
+
5
+ It reads a scenario id from a request header and runs Hono handlers or middleware inside that active scenario context.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add definitely-fine @definitely-fine/hono hono
11
+ ```
12
+
13
+ ## Important
14
+
15
+ > [!IMPORTANT]
16
+ > Do not leave scenario activation enabled in production by accident.
17
+ >
18
+ > `@definitely-fine/hono` 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 installing the middleware and wrappers 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 middleware guard:
32
+
33
+ ```ts
34
+ import { Hono } from "hono";
35
+ import { definitelyFineScenarioMiddleware } from "@definitely-fine/hono";
36
+
37
+ const app = new Hono();
38
+
39
+ if (process.env.NODE_ENV !== "production") {
40
+ app.use("*", definitelyFineScenarioMiddleware());
41
+ }
42
+ ```
43
+
44
+ ## What This Package Does
45
+
46
+ This package does not replace the core runtime. You still create your scenarios and wrapped runtime with `definitely-fine`.
47
+
48
+ Its job is narrower:
49
+
50
+ - define the scenario header name constant
51
+ - read that header from `Headers`, `Request`, or Hono `Context`
52
+ - run Hono handlers or middleware inside `runWithRuntimeScenarioContext()`
53
+
54
+ ```mermaid
55
+ flowchart LR
56
+ subgraph C[Client or browser process]
57
+ C1[request]
58
+ C2[x-definitely-fine-scenario-id header]
59
+ end
60
+
61
+ subgraph H[Hono server process]
62
+ H1[@definitely-fine/hono middleware or handler wrapper]
63
+ H2[runWithRuntimeScenarioContext]
64
+ H3[definitely-fine runtime]
65
+ H4[wrapped app service or function]
66
+ end
67
+
68
+ subgraph S[Shared scenario storage]
69
+ S1[(persisted scenario JSON)]
70
+ end
71
+
72
+ C1 --> C2 --> H1
73
+ H1 --> H2 --> H3 --> H4
74
+ H3 -- load active scenario --> S1
75
+ ```
76
+
77
+ ## App Middleware
78
+
79
+ Use `definitelyFineScenarioMiddleware()` when you want one app-level middleware instead of wrapping every route.
80
+
81
+ ```ts
82
+ import { Hono } from "hono";
83
+ import { definitelyFineScenarioMiddleware } from "@definitely-fine/hono";
84
+
85
+ const app = new Hono();
86
+
87
+ app.use("*", definitelyFineScenarioMiddleware());
88
+ ```
89
+
90
+ Downstream handlers then run inside the matching scenario automatically when the request includes the scenario header.
91
+
92
+ ## Route Handlers
93
+
94
+ Use `withDefinitelyFineScenario()` when you want to scope scenario activation to a specific Hono handler.
95
+
96
+ ```ts
97
+ import { Hono } from "hono";
98
+ import { withDefinitelyFineScenario } from "@definitely-fine/hono";
99
+
100
+ import { incrementCounter } from "../lib/demo-runtime";
101
+
102
+ const app = new Hono();
103
+
104
+ app.post(
105
+ "/api/counter",
106
+ withDefinitelyFineScenario((context) => {
107
+ return context.json(incrementCounter());
108
+ }),
109
+ );
110
+ ```
111
+
112
+ If the request does not include the scenario header, the handler runs normally.
113
+
114
+ ## Header Name
115
+
116
+ The exported header constant is `DEFINITELY_FINE_SCENARIO_HEADER`.
117
+
118
+ ```ts
119
+ import { DEFINITELY_FINE_SCENARIO_HEADER } from "@definitely-fine/hono";
120
+
121
+ const headers = {
122
+ [DEFINITELY_FINE_SCENARIO_HEADER]: "scenario-id",
123
+ };
124
+ ```
125
+
126
+ ## Typical Setup
127
+
128
+ 1. Create and save a scenario with `definitely-fine`.
129
+ 2. Add `definitelyFineScenarioMiddleware()` at app level, or wrap specific handlers with `withDefinitelyFineScenario()`.
130
+ 3. Send the scenario id in the request header.
131
+ 4. Let your core runtime wrappers decide which calls are intercepted.
132
+
133
+ For safety, production apps should normally disable the core runtime with `enabled: false` and optionally skip this middleware or these wrappers entirely.
134
+
135
+ ## With Playwright
136
+
137
+ 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,84 @@
1
+ import { Context, Env, Handler, Input, MiddlewareHandler } from "hono";
2
+ import { HandlerResponse } from "hono/types";
3
+
4
+ //#region src/index.d.ts
5
+ /**
6
+ * Header name used to carry the active definitely-fine scenario id.
7
+ * @public
8
+ */
9
+ /**
10
+ * Header name used to carry the active definitely-fine scenario id.
11
+ * @public
12
+ */
13
+ declare const DEFINITELY_FINE_SCENARIO_HEADER = "x-definitely-fine-scenario-id";
14
+ /**
15
+ * Returns the raw definitely-fine scenario header value from a `Headers` object.
16
+ * @public
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const scenarioId = getScenarioIdFromHeaders(
21
+ * new Headers([[DEFINITELY_FINE_SCENARIO_HEADER, "checkout"]]),
22
+ * );
23
+ * ```
24
+ */
25
+ declare function getScenarioIdFromHeaders(headers: Headers): string | undefined;
26
+ /**
27
+ * Returns the raw definitely-fine scenario header value from a `Request`.
28
+ * @public
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const scenarioId = getScenarioIdFromRequest(
33
+ * new Request("https://example.test", {
34
+ * headers: {
35
+ * [DEFINITELY_FINE_SCENARIO_HEADER]: "checkout",
36
+ * },
37
+ * }),
38
+ * );
39
+ * ```
40
+ */
41
+ declare function getScenarioIdFromRequest(request: Request): string | undefined;
42
+ /**
43
+ * Returns the raw definitely-fine scenario header value from a Hono `Context`.
44
+ * @public
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * app.get("/demo", (context) => {
49
+ * return context.text(getScenarioIdFromContext(context) ?? "none");
50
+ * });
51
+ * ```
52
+ */
53
+ declare function getScenarioIdFromContext<TEnv extends Env, TPath extends string, TInput extends Input>(context: Context<TEnv, TPath, TInput>): string | undefined;
54
+ /**
55
+ * Wraps a Hono handler or middleware so it runs inside definitely-fine scenario context.
56
+ * @public
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const handler = withDefinitelyFineScenario((context) => {
61
+ * return context.json({ ok: true });
62
+ * });
63
+ * ```
64
+ */
65
+ declare function withDefinitelyFineScenario<TEnv extends Env, TPath extends string, TInput extends Input, TResult extends HandlerResponse<unknown>>(handler: Handler<TEnv, TPath, TInput, TResult>): Handler<TEnv, TPath, TInput, TResult>;
66
+ declare function withDefinitelyFineScenario<TEnv extends Env, TPath extends string, TInput extends Input, TResult extends HandlerResponse<unknown>>(handler: MiddlewareHandler<TEnv, TPath, TInput, TResult>): MiddlewareHandler<TEnv, TPath, TInput, TResult>;
67
+ /**
68
+ * Creates Hono middleware that activates definitely-fine scenario context for downstream handlers.
69
+ * @public
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const app = new Hono();
74
+ *
75
+ * app.use("*", definitelyFineScenarioMiddleware());
76
+ * ```
77
+ */
78
+ declare function definitelyFineScenarioMiddleware<TEnv extends Env, TPath extends string = string, TInput extends Input = Input>(): MiddlewareHandler<TEnv, TPath, TInput>;
79
+
80
+ //#endregion
81
+ //# sourceMappingURL=index.d.ts.map
82
+
83
+ export { DEFINITELY_FINE_SCENARIO_HEADER, definitelyFineScenarioMiddleware, getScenarioIdFromContext, getScenarioIdFromHeaders, getScenarioIdFromRequest, withDefinitelyFineScenario };
84
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;cAgBa,+BAAA;;AAAb;;;;;;;;;;iBAagB,wBAAA,UAAkC;;AAAlD;;;;;;;;;;;;;;iBAmBgB,wBAAA,UAAkC;;AAAlD;;;;;;;;;;iBAegB,sCACD,0CAEE,gBACN,QAAQ,MAAM,OAAO;;AAJhC;;;;;;;;AAIkB;;iBAeF,wCACD,0CAEE,uBACC,mCAEP,QAAQ,MAAM,OAAO,QAAQ,WACrC,QAAQ,MAAM,OAAO,QAAQ;iBAChB,wCACD,0CAEE,uBACC,mCAEP,kBAAkB,MAAM,OAAO,QAAQ,WAC/C,kBAAkB,MAAM,OAAO,QAAQ;;;;;;;;;AAf1C;;;AAGiB,iBAkDD,gCAlDC,CAAA,aAmDF,GAnDE,EAAA,cAAA,MAAA,GAAA,MAAA,EAAA,eAqDA,KArDA,GAqDQ,KArDR,CAAA,CAAA,CAAA,EAsDZ,iBAtDY,CAsDM,IAtDN,EAsDY,KAtDZ,EAsDmB,MAtDnB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,83 @@
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
+ * Returns the raw definitely-fine scenario header value from a Hono `Context`.
43
+ * @public
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * app.get("/demo", (context) => {
48
+ * return context.text(getScenarioIdFromContext(context) ?? "none");
49
+ * });
50
+ * ```
51
+ */
52
+ function getScenarioIdFromContext(context) {
53
+ return getScenarioIdFromRequest(context.req.raw);
54
+ }
55
+ function withDefinitelyFineScenario(handler) {
56
+ return function definitelyFineHonoHandler(context, next) {
57
+ const scenarioId = getScenarioIdFromContext(context);
58
+ if (scenarioId === void 0) return handler(context, next);
59
+ return runWithRuntimeScenarioContext({ scenarioId }, () => {
60
+ return handler(context, next);
61
+ });
62
+ };
63
+ }
64
+ /**
65
+ * Creates Hono middleware that activates definitely-fine scenario context for downstream handlers.
66
+ * @public
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const app = new Hono();
71
+ *
72
+ * app.use("*", definitelyFineScenarioMiddleware());
73
+ * ```
74
+ */
75
+ function definitelyFineScenarioMiddleware() {
76
+ return withDefinitelyFineScenario(async (_context, next) => {
77
+ await next();
78
+ });
79
+ }
80
+
81
+ //#endregion
82
+ export { DEFINITELY_FINE_SCENARIO_HEADER, definitelyFineScenarioMiddleware, getScenarioIdFromContext, getScenarioIdFromHeaders, getScenarioIdFromRequest, withDefinitelyFineScenario };
83
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["headers: Headers","request: Request","context: Context<TEnv, TPath, TInput>","handler:\n | Handler<TEnv, TPath, TInput, TResult>\n | MiddlewareHandler<TEnv, TPath, TInput, TResult>","next: Next"],"sources":["../src/index.ts"],"sourcesContent":["import { runWithRuntimeScenarioContext } from \"definitely-fine\";\n\nimport type {\n Context,\n Env,\n Handler,\n Input,\n MiddlewareHandler,\n Next,\n} from \"hono\";\nimport type { HandlerResponse } from \"hono/types\";\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 * Returns the raw definitely-fine scenario header value from a Hono `Context`.\n * @public\n *\n * @example\n * ```ts\n * app.get(\"/demo\", (context) => {\n * return context.text(getScenarioIdFromContext(context) ?? \"none\");\n * });\n * ```\n */\nexport function getScenarioIdFromContext<\n TEnv extends Env,\n TPath extends string,\n TInput extends Input,\n>(context: Context<TEnv, TPath, TInput>): string | undefined {\n return getScenarioIdFromRequest(context.req.raw);\n}\n\n/**\n * Wraps a Hono handler or middleware so it runs inside definitely-fine scenario context.\n * @public\n *\n * @example\n * ```ts\n * const handler = withDefinitelyFineScenario((context) => {\n * return context.json({ ok: true });\n * });\n * ```\n */\nexport function withDefinitelyFineScenario<\n TEnv extends Env,\n TPath extends string,\n TInput extends Input,\n TResult extends HandlerResponse<unknown>,\n>(\n handler: Handler<TEnv, TPath, TInput, TResult>,\n): Handler<TEnv, TPath, TInput, TResult>;\nexport function withDefinitelyFineScenario<\n TEnv extends Env,\n TPath extends string,\n TInput extends Input,\n TResult extends HandlerResponse<unknown>,\n>(\n handler: MiddlewareHandler<TEnv, TPath, TInput, TResult>,\n): MiddlewareHandler<TEnv, TPath, TInput, TResult>;\nexport function withDefinitelyFineScenario<\n TEnv extends Env,\n TPath extends string,\n TInput extends Input,\n TResult extends HandlerResponse<unknown>,\n>(\n handler:\n | Handler<TEnv, TPath, TInput, TResult>\n | MiddlewareHandler<TEnv, TPath, TInput, TResult>,\n) {\n return function definitelyFineHonoHandler(\n context: Context<TEnv, TPath, TInput>,\n next: Next,\n ): TResult | Promise<TResult | void> {\n const scenarioId = getScenarioIdFromContext(context);\n\n if (scenarioId === undefined) {\n return handler(context, next);\n }\n\n return runWithRuntimeScenarioContext({ scenarioId }, () => {\n return handler(context, next);\n });\n };\n}\n\n/**\n * Creates Hono middleware that activates definitely-fine scenario context for downstream handlers.\n * @public\n *\n * @example\n * ```ts\n * const app = new Hono();\n *\n * app.use(\"*\", definitelyFineScenarioMiddleware());\n * ```\n */\nexport function definitelyFineScenarioMiddleware<\n TEnv extends Env,\n TPath extends string = string,\n TInput extends Input = Input,\n>(): MiddlewareHandler<TEnv, TPath, TInput> {\n return withDefinitelyFineScenario(async (_context, next) => {\n await next();\n });\n}\n"],"mappings":";;;;;;;AAgBA,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,yBAIdC,SAA2D;AAC3D,QAAO,yBAAyB,QAAQ,IAAI,IAAI;AACjD;AA6BD,SAAgB,2BAMdC,SAGA;AACA,QAAO,SAAS,0BACdD,SACAE,MACmC;EACnC,MAAM,aAAa,yBAAyB,QAAQ;AAEpD,MAAI,sBACF,QAAO,QAAQ,SAAS,KAAK;AAG/B,SAAO,8BAA8B,EAAE,WAAY,GAAE,MAAM;AACzD,UAAO,QAAQ,SAAS,KAAK;EAC9B,EAAC;CACH;AACF;;;;;;;;;;;;AAaD,SAAgB,mCAI4B;AAC1C,QAAO,2BAA2B,OAAO,UAAU,SAAS;AAC1D,QAAM,MAAM;CACb,EAAC;AACH"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@definitely-fine/hono",
3
+ "version": "0.1.0",
4
+ "description": "Hono 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
+ "hono": "^4.10.1"
24
+ },
25
+ "peerDependencies": {
26
+ "hono": ">=4"
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
+ }