Skip to content

Writing Packages

Introduction

Freestyle enables a new kind of opensource package: Full Stack Feature Packages. Full Stack Feature Packages come with a whole feature: frontend, backend, and cloudstate, all in one package. When a user installs them, the backend can be deployed in their backend, and the components can be used in their frontend. This removes the need for third-party services, allows for better customization, and empowers opensource developers to share features as packages.

Writing a Package

Prerequisites

Setting up a new package

  1. Create a new package

    Terminal window
    npm init -y
  2. Install Freestyle:

    Terminal window
    npm install freestyle-sh

Writing the Package

To show you how to write a package, we will write a simple counter package. The package will have a counter that can be incremented and decremented.

  1. Create a new file src/index.ts:

    import { cloudstate, invalidate } from "freestyle-sh";
    @cloudstate
    export class SimpleCounter {
    static id = "simple-counter";
    count = 0;
    increment() {
    this.count++;
    invalidate(useCloud("simple-counter").getCount);
    }
    decrement() {
    this.count--;
    invalidate(useCloud("simple-counter").getCount);
    }
    getCount() {
    return this.count;
    }
    }
  2. Create the frontend in src/Counter.tsx:

    import { useCloud } from "freestyle-sh";
    import { useCloudQuery } from "freestyle-sh/react";
    export function Counter() {
    const counter = useCloud("simple-counter");
    let { data: count } = useCloudQuery(counter.getCount);
    return (
    <div>
    <button onClick={counter.increment}>Increment</button>
    <button onClick={counter.decrement}>Decrement</button>
    <div>{count}</div>
    </div>
    );
    }
  3. Configure package.json:

    Fullstack Feature Packages can support multiple frontend frameworks from the same package, we recommend you configure exports in your package.json.

    {
    "exports": {
    ".": {
    "node": "./src/index.ts",
    "default": "./src/index.ts"
    },
    "./react": {
    "node": "./src/Counter.tsx",
    "default": "./src/Counter.tsx"
    }
    }
    }

    This pattern allows your single package to support multiple frontend frameworks and to work with ones you haven’t built yet.

  4. Publish your package:

    Terminal window
    npm publish

Advantages

  1. Extensibility: Users can extend your package by adding new features or modifying existing ones.

    For example, a user could add a reset function to the SimpleCounter class:

    @cloudstate
    class MyCounter extends SimpleCounter {
    static id = "my-counter";
    reset() {
    this.count = 0;
    invalidate(useCloud("simple-counter").getCount);
    }
    }
  2. Composability: Database packages for other databases never took off, because they were too hard to make work with your other code. Your packages automatically fit onto the user’s backend, frontend, and other packages.

    @cloudstate
    class MyCounter extends SimpleCounter {
    id = crypto.randomUUID(); // override so its not a singleton
    }
    @cloudstate
    class CounterManager {
    static readonly id = "counter-manager";
    counters: Record<string, MyCounter> = {};
    createCounter(id: string) {
    this.counters[id] = new SimpleCounter();
    }
    getCounter(id: string) {
    return this.counters[id];
    }
    }
  3. Integration: Many existing opensource providers provide packages that need to be hosted separately from users’ code. Your packages can be installed and run in the user’s codebase.