Fragno
Features

Config, Dependencies, and Services

Learn how to interface with the Fragment user.

Edit on GitHub

The config is the basic contract between the Fragment author and the user. It is used to pass configuration to the Fragment, such as API keys and options. It can also be used to let the user react to events happening in the Fragment by providing callback functions.

Dependencies are objects that are available to route handlers. They are provided using the withDependencies method in the library definition. Dependencies are server-side only and are not included in the client bundle. They are private to the Fragment and cannot be used by the user directly.

Services, like dependencies, are server-side only and are not included in the client bundle. However, unlike dependencies, they are available to the user as well as the route handlers. Users can access them by using the services object that is available as part of the instantiated Fragment. This means that services are part of the public-facing API of your Fragment.

Example Snippet

In the following snippet:

  1. The user is asked to provide an API key, as well as an optional callback function to be called when the text is summarized.
  2. A dependency is provided called aiWrapper. The withDependencies method has access to the config object. Using this to construct objects that require an API key is a common pattern.
  3. A service method called summarizeText is provided. The withServices method has access to both the config object and the dependencies. This can be used to make internal methods available to the user. Here, the callback function is called if it is provided.
src/index.ts
import { defineLibrary } from "@fragno-dev/core";
import { MyOpenAIWrapper } from "./lib/my-openai-wrapper";

export interface MyFragmentConfig {
  openaiApiKey: string;
  model?: "gpt-5-mini" | "4o-mini" | "gpt-5-nano";
  onTextSummarized?: (input: string, output: string, tokensUsed: number) => void;
}

const myFragmentDefinition = defineLibrary<MyFragmentConfig>("my-fragment")
  .withDependencies((config) => {
    return {
      aiWrapper: new MyOpenAIWrapper({
        apiKey: config.openaiApiKey,
        model: config.model,
      }),
    };
  })
  .withServices((config, deps) => {
    return {
      summarizeText: (text: string) => {
        const result = deps.aiWrapper.summarizeText(text);
        config.onTextSummarized?.(text, result.summary, result.tokensUsed);
        return result;
      },
    };
  });

Using defineRoutes to work with Dependencies and Services

The defineRoute function has a companion, defineRoutes (note the plural), which is a factory function that can be used to define multiple routes that have access to the dependencies and services.

The dependencies and services are scoped to the defineRoutes call. This means that we don't need global types for the dependencies and services, and can define them locally instead.

Another benefit of this approach is that we can define routes in a single TypeScript file (like the post-todos.ts file in the previous section). This makes the codebase easier to understand and maintain.

We define two types per defineRoutes call: one for the dependencies and one for the services. These types are passed to the call as generic parameters.

routes/post-todos.ts
import { defineRoute, defineRoutes } from "@fragno-dev/core";
import type { Todo } from "../index";
import type { MyOpenAIWrapper } from "../lib/my-openai-wrapper";

type AddTodoRouteDeps = {
  aiWrapper: MyOpenAIWrapper;
};

type AddTodoRouteServices = {
  summarizeText: (text: string) => { summary: string; tokensUsed: number };
};

export const addTodoRoute = defineRoutes<AddTodoRouteDeps, AddTodoRouteServices>().create(
  ({ config, deps, services }) => {
    const { aiWrapper } = deps;
    const { summarizeText } = services;

    return [
      /* Calls to `defineRoute` that use internal methods on `aiWrapper` or public methods such 
         as `summarizeText` from the services. */
    ];
  },
);

The create callback should return one or more routes. The config, dependencies, and services are available in the callback.

The addTodoRoute object is a route factory that will be instantiated by a call to createLibrary. At that point, the dependencies and services are passed to the route object.

The createLibrary function

The createLibrary function is used to create the library instance. It takes the library definition, the config, and the routes. The createMyFragment function is exported to the Fragment user. This is the final, server-side, object that the user can interact with. It's also the object that contains the services field.

src/index.ts
import { createLibrary } from "@fragno-dev/core";
import type { FragnoPublicConfig } from "@fragno-dev/core";

import { getTodosRoute } from "./routes/get-todos";
import { addTodoRoute } from "./routes/post-todos";

// ... defineLibrary call we've seen above ...

const routes = [getTodosRoute, addTodoRoute] as const;

export function createMyFragment(config: MyFragmentConfig, fragnoConfig: FragnoPublicConfig = {}) {
  return createLibrary(myFragmentDefinition, config, routes, fragnoConfig);
}