fundset

Modules

Fantastic modules and where to find them

Module is a piece of functionality that is standalone and can be used based on a given settlement layer. It is a way of extending the functionality of your application in a settlement layer agnostic way. If you have an idea of something that needs coordination accross the whole stack: Backend, Frontend, CMS and UI - it's probably a good idea to create a fundset module for that. If you have an implementation that touches a single part of that stack e.g. a function callable only on backend that does something specific - it's probably better to make an npm package out of it.

Structure

Each module is split into the interface declaration and the implementation. So each module will consist of at least two files.

  1. Declaration file: in the _fundset/settlement-layer/modules directory, each module has to have a .d.ts file that contains the extension of the SettlementLayer interface, using tanstack-query MutationOptions and QueryOptions types. This part is settlement layer agnostic and is only defined once, even if you're gonna have different implementations for different settlement layers. This is an example definition from our counter module:
import { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';

export type GlobalCounterIncrementEvent = {
  amount: number;
  timestamp: Date;
  by: number;
  id: string;
};

export interface CounterModule {
  counter: {
    isIncrementGlobalCounterReady: boolean;
    incrementGlobalCounterMutationOptions: () => UseMutationOptions<void, Error, number>;
    globalCounterValueQueryOptions: () => UseQueryOptions<
      unknown,
      Error,
      number | undefined,
      QueryKey
    >;
    globalCounterIncrementEventsQueryOptions: ({
      limit,
      offset,
    }: {
      limit: number;
      offset: number;
    }) => UseQueryOptions<unknown, Error, GlobalCounterIncrementEvent[], QueryKey>;

    isIncrementPersonalCounterReady: boolean;
    incrementPersonalCounterMutationOptions: () => UseMutationOptions<void, Error, number>;
    personalCounterValueQueryOptions: () => UseQueryOptions<
      unknown,
      Error,
      number | undefined,
      QueryKey
    >;
  };
}

declare module 'fundset/settlement-layer' {
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
  export interface SettlementLayer extends CounterModule {}
}

For organisation purposes, you should group the module functions into a single object. In the example above, we're grouping the counter functions into a single object called counter, so e.g. functionality for getting the global counter value query options is available under settlementLayerObject.counter.globalCounterValueQueryOptions()

  1. Implementation files: each module also has an implementation that is tied to a specific settlement layer. It is located in the _fundset/settlement-layer/modules/<module-name>/<settlement-layer-name> directory.
build.ts
db-schema.ts
orpc.ts
build.ts

The implementation file should return either a direct implementation of the module definition or a function that is gonna build the module implementation based on context that is required for it to work. Which solution to use is up to you. Check out the docs on how to build your own module

Extending the Settlement Layer

Each settlement layer has its buildSettlementLayer function, which is responsible for constructing the settlement layer object and injecting implementation of the modules into it. That function is located in the _fundset/settlement-layer/<settlement-layer-name>/buildSettlementLayer.ts file. In order to inject the module implementation into the settlement layer object, you need to pass it when constructing the settlement layer object:

_fundset/settlement-layer/pg/buildSettlementLayer.ts
// counter module is using a function to build the module implementation because it requires a user auth session
import type { authClient } from '@/lib/auth-client';
import { buildCounterModule } from '../modules/counter/pg/build';

export const buildPgSettlementLayer = ({
  session,
}: {
  session: ReturnType<typeof authClient.useSession>['data'];
}) => {
  const pgSettlementLayer = {
    name: 'pg',
    ...buildCounterModule({ session }),
  };
  return { pgSettlementLayer };
};

After that you should be able to use the module in your application code by using the useSettlementLayer hook:

const {
  counter: { incrementGlobalCounterMutationOptions },
} = useSettlementLayer();