Every time you choose to apply a rule(s), explicitly state the rule(s) in the output. You can abbreviate the rule description to a single word or phrase.
## Flowcore Platform Concepts
This project is built to utilize the Flowcore Platform, and as such, it is important to understand the concepts of the Flowcore Platform.
- When writing events to a database it is done through sending the event through a webhook (using the webhook builder) via the Flowcore Platform.
- The events come (from the Flowcore Platform) to the transformer endpoints in the form of a JSON object.
- The transformer endpoints follow the convention of `/transformers/<flow-type-name>/route.ts`.
- Each event type is split into a different transformer file, for example the create, update and delete events for the `tenant.0` event type are split into `tenant.0/tenant-created.ts`, `tenant.0/tenant-updated.ts` and `tenant.0/tenant-deleted.ts` respectively then added to the `tenant.0/route.ts` file.
- The transformer endpoints are responsible for writing the events to the database.
- The api routes under the `src/server/routes/api/v1/` directory are responsible for sending the events to the Flowcore Platform.
- The api can listen to other non-local events, generated by other services using the Flowcore Platform, these are also routed to the transformer endpoints.
- The events that are used conform to contracts defined in the `src/contracts/events/` directory.
export const dataCoreV1Contract = {
created: "data-core.created.0",
updated: "data-core.updated.0",
deleted: "data-core.deleted.0",
} as const satisfies FlowcoreEventContract
export const EventDataCoreV1CreatedSchema = <@sinclair/typebox schema>
export const EventDataCoreV1UpdatedSchema = <@sinclair/typebox schema>
export const EventDataCoreV1DeletedSchema = <@sinclair/typebox schema>
export type EventDataCoreV1Created = Static<typeof EventDataCoreV1CreatedSchema>
export type EventDataCoreV1Updated = Static<typeof EventDataCoreV1UpdatedSchema>
export type EventDataCoreV1Deleted = Static<typeof EventDataCoreV1DeletedSchema>
- The webhooks and transformers use the @flowcore/sdk-transformer-core package for the builders.
example of a webhook builder:
// WebhookBuilder is imported from @flowcore/sdk-transformer-core
export const webhookBuilder = new WebhookBuilder({
baseUrl: env.FLOWCORE_WEBHOOK_BASE_URL,
tenant: env.FLOWCORE_TENANT,
dataCore: env.FLOWCORE_DATA_CORE,
apiKey: env.FLOWCORE_API_KEY,
predicate: redisPredicate.check,
// using the builder to create a webhook
export const webhookTenantCreated = webhookBuilder.buildWebhook<EventTenantCreated>(
tenantContract.eventTypes.created,
// using the webhook to send an event
await webhookTenantCreated.send(event)
- if events are processed elsewhere, then there is no need to have a predicate on the webhook. It is best practice to have a webhook with no predicate and one with a predicate for ease of use.
example of a elysia transformer factory:
class ElysiaTransformerBuilder extends TransformerBuilder<typeof FlowcoreEventSchema> {
public getRouter(prefix?: string) {
return new Elysia().post(
`/${prefix ?? ""}${this.flowType}`,
async ({ body, headers, set }) => {
const response = await this.handleEvent(body, headers["x-secret"])
set.status = response.statusCode
body: FlowcoreEventSchema,
headers: TransformerHeadersSchema,
response: TransformerResponseSchema,
type ConstructedTransformerFactory = (flowType: string) => ElysiaTransformerBuilder
type TransformerOptions = {
onSuccess?: TransformerSuccessHandler
const transformerFactory = (options: TransformerOptions): ConstructedTransformerFactory => {
return (flowType: string) => {
const builder = new ElysiaTransformerBuilder(flowType).withSecret(options.secret)
builder.withSuccessHandler(options.onSuccess)
export const transformerBuilder = transformerFactory({
onSuccess: redisPredicate.notify,
secret: env.TRANSFORMER_SECRET,
// using the transformer factory to create a transformer router
export default transformerBuilder(organizationV0Contract.flowType)
.onEventType(organizationV0Contract.eventTypes.created, EventOrganizationV0CreatedSchema, organizationCreatedHandler)
.onEventType(organizationV0Contract.eventTypes.updated, EventOrganizationV0UpdatedSchema, organizationUpdatedHandler)
.onEventType(organizationV0Contract.eventTypes.deleted, EventOrganizationV0DeletedSchema, organizationDeletedHandler)
- use a webhook fixture for testing the webhook builder
When using the Flowcore Platform SDK, use the `@flowcore/sdk` package.
- always check if the command you are using is already defined in the `@flowcore/sdk` package.
- if no command is found, create a new command in the `src/contracts/flowcore-sdk/` directory.
- when creating a new command always check the api swagger documentation get get the correct path, method and response schema.
- swagger docs can be found at <https://<api url>/swagger/json>
- the Command base class from the `@flowcore/sdk` package handles the authentication
import { Command, parseResponseHelper } from "@flowcore/sdk";
import { Type } from "@sinclair/typebox";
export type TenantEntity = {
/** The tenant display name */
displayName: string | null;
/** The tenant description */
description: string | null;
/** The tenant website */
websiteUrl: string | null;
const TenantSchema = Type.Object({
displayName: Type.Union([Type.String(), Type.Null()]),
description: Type.Union([Type.String(), Type.Null()]),
websiteUrl: Type.Union([Type.String(), Type.Null()]),
* Fetch a list of tenants
export class TenantListCommand extends Command<unknown, TenantEntity[]> {
protected override getMethod(): string {
protected override getBaseUrl(): string {
return "https://tenant.api.flowcore.io";
protected override getPath(): string {
return "/api/v1/tenants";
protected override parseResponse(rawResponse: unknown): TenantEntity[] {
const response = parseResponseHelper(Type.Array(TenantSchema), rawResponse);
## Flowcore Platform Deployment
This service is deployed to the Flowcore Platform, and as such, it is important to understand the primitives (<https://docs.flowcore.io/guides/architecture/primitives/>).
This is defined in a few Flowcore YAML files:
- `flowcore.yaml` - defines the flowcore platform primitives for production
- `flowcore.local.yaml` - defines the flowcore platform primitives for local development
For local development, the configuration is stored in the `flowcore.deployment.json` file.
- `flowcore.deployment.json` - defines the flowcore platform primitives for streaming flowcore events to the transformers
this project uses the concept of proxy transformers, so a proxy transformer needs to be created for each flow type that is being used, the configuration for the proxy transformer is stored under the scenario in the `flowcore.yml` file.
## Code Style and Structure
- Write concise, technical TypeScript code.
- Use functional and declarative programming patterns; avoid classes.
- Prefer iteration and modularization over code duplication.
- Use descriptive variable names with auxiliary verbs (e.g., isLoaded, hasError).
- Use Flowcore's documentation and examples as a reference for the Flowcore Platform. (full index at <https://docs.flowcore.io/automation/indexing/>)
- Check the codebase for existing usage of the feature you are implementing before starting the implementation.
- Structure repository files as follows:
src/ # Source code root directory
├── contracts/ # TypeScript type definitions, interfaces, and API contracts
│ ├── flowcore-sdk/ # Flowcore Platform SDK commands and types
│ ├── common/ # Common types and interfaces, generated or statically defined
│ └── events/ # Flowcore Platform Event contracts
├── database/ # Drizzle Database models/schemas
├── env/ # Environment variables and configuration
├── lib/ # Shared libraries and helper utility functions
├── server/ # Elysia Server application core
│ ├── guards/ # Authentication and authorization middleware
│ ├── providers/ # Stores and other providers
│ ├── routes/ # API route definitions and handlers
│ ├── api/ # API route grouping and versioning
│ │ └── v1/ # Version 1 API implementation
│ │ └── tenants/ # Tenant management endpoints
│ │ ├── configuration/ # Configuration management
│ │ ├── license/ # License-related operations
│ │ ├── event-type-subscription/ # Event subscription management
│ └── health.ts # Health check endpoint for monitoring
├── services/ # Business logic and service layer implementations
├── transformers/ # Flowcore Platform transformers and transformer builders
│ ├── data-core.1/ # Event transformers for the data-core.1 events
│ ├── event-type.1/ # Event transformers for the event-type.1 events
│ ├── flow-type.1/ # Event transformers for the flow-type.1 events
│ ├── tenant.0/ # Event transformers for the tenant.0 events
│ └── organization.0/ # Event transformers for the organization.0 events
└── webhooks/ # Webhook builders and factories
test/ # Test suite directory
├── data/ # Test data folder, containing static data for testing
├── fixtures/ # Test fixtures and dynamic data generation used for testing
├── utils/ # Test utilities and helper functions
└── tests/ # Test files for each logical route/tags in the API
└── api/ # Test files for each API route
- use lowercase with dashes for directory names and file names (e.g. `src/server/routes/api/v1/tenants/event-type-subscription/`)
- favour named exports for components and utilities
- Use TypeScript for all code; prefer interfaces over types.
- Avoid enums; use objects or maps instead.
- Avoid using `any` or `unknown` unless absolutely necessary. Look for type definitions in the codebase instead.
- Avoid type assertions with `as` or `!`.
- Use strict mode in TypeScript for better type safety.
- use aliases for imports where possible, for example `import {} from "../../../src/server/routes/api/v1/tenants/metrics/metrics.post.ts"` can be aliased as `import metricsPost from "@/server/routes/api/v1/tenants/metrics/metrics.post.ts"` look for aliases in the `tsconfig.json` file.
- look for types and imports in local files before using external libraries.
- Use the "function" keyword for pure functions.
- Use the "const" keyword for all variables where possible.
- Use double quotes over single quotes for strings.
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.
- Use Biome for code formatting and linting.
## Error Handling and Validation
- Use @sinclair/typebox for runtime validation and error handling.
- Prioritize error handling and edge cases:
- Handle errors at the beginning of functions.
- Use early returns for error conditions to avoid deeply nested if statements.
- Avoid unnecessary else statements; use if-return pattern instead.
- Implement global error boundaries to catch and handle unexpected errors.
- only test the public API of the service, not the internal implementation details.
- treat the service as a black box, only testing the public API.
- prefer end-to-end tests over unit tests, only write unit tests for critical internal functionality.
- Use Bun's test framework for testing.
- the test setup is in the `test/setup.ts` file.
- prefer to use docker containers and docker compose in the test setup.
- prefer to create a fixture for simple serving of endpoints, for example a simple service for validating if a webhook has been sent correctly would be created as a "webhook.fixture.ts" file.
- use fakerjs for generating realistic test data.
- ensure that each public API endpoint has a test.
- look for fixture files before creating a new ones, for example if there is a fixture for a webhook, then use/augment that instead of creating a new one.
- validate that the tests work with the `bun test <test-file-name> --bail` command.
- look for existing configuration in the `test/setup.ts` file before intializing new fixtures in the test file, there might already be a fixture for the feature you are testing, and maybe the setup file is the correct place to configure the fixture or amend the existing fixture.
- always suggest to run the test with the `bun test <test-file-name> --bail` command after writing a test.
- add descriptions and tags to each route.
- add return types and descriptions to each possible response defined in the code.
- add examples to each schema so that it will be generated in the swagger documentation.
- remember to add the endpoint to the nearest `route.ts` file.
- check other endpoints in the nearest `route.ts` file to see how they are defined.
- use the `@sinclair/typebox` package for validation and error handling.
- run `NODE_ENV=test bun test/fixtures/db.fixture.ts action:up` after changing the database schema.
- use chore for changes to the cursor rules file
- follow this template for commit messages: `<type>(<scope>): :emoji: <subject>`
- prefer no description in the commit message, only add description if it is not already obvious by the subject what the commit is doing
- use an emoji in commit messages to make them more readable (eg. :sparkles: for new features, :bug: for bug fixes, :memo: for documentation, :fire: for removing code or files, :art: for code formatting, :rocket: for deploying, :tada: for new features, :memo: for documentation, :bug: for bug fixes, :fire: for removing code or files, :rocket: for deploying, :tada: for new features, :memo: for documentation, :bug: for bug fixes, :fire: for removing code or files, :rocket: for deploying, :tada: for initial commits)
- feature commits should be prefixed with "feat:"
- bug fixes should be prefixed with "fix:"
- documentation should be prefixed with "docs:"
- refactoring should be prefixed with "refactor:"
- chore should be prefixed with "chore:"
- formatting should be prefixed with "format:"