Skip to content

Commit

Permalink
docs: add more detailed README and JSDoc
Browse files Browse the repository at this point in the history
  • Loading branch information
aniravi24 committed Sep 2, 2024
1 parent cc24dd7 commit 13f280c
Show file tree
Hide file tree
Showing 12 changed files with 342 additions and 76 deletions.
175 changes: 171 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,186 @@
# Welcome to typefusion!
# Welcome to Typefusion!

<!-- TODO codecov badge -->

## Table of Contents

- [Welcome to Typefusion!](#welcome-to-typefusion)
- [Table of Contents](#table-of-contents)
- [Introduction](#introduction)
- [Getting Started](#getting-started)
- [Usage](#usage)
- [CLI](#cli)
- [Library](#library)
- [Typefusion Ref](#typefusion-ref)
- [Examples](#examples)
- [Docs](#docs)
- [Have an issue?](#have-an-issue)
- [Contributions](#contributions)

## Introduction

Typefusion is a tool inspired by DBT that lets you run typescript scripts, and the results are materialized into a database (currently PostgreSQL only).
Typefusion lets you run TypeScript scripts, and the results are materialized into a database (currently PostgreSQL only). Scripts can reference each other's results, so you can build complex workflows. It's inspired by [Data Build Tool](https://www.getdbt.com/) (DBT).

## Getting Started

To get started with Typefusion, you need to install the package and set up your database configuration. Follow the steps below:

1. Install Typefusion using your preferred package manager:

```sh
npm install typefusion
```

```sh
pnpm install typefusion
```

```sh
yarn add typefusion
```

```sh
bun add typefusion
```

2. To setup your database configuration, you can may do one of two options:

- Have a full connection string in the `DATABASE_URL` environment variable.
- Have `PGDATABASE`, `PGHOST`, `PGPORT`, `PGPASSWORD`, and `PGUSER` set as environment variables.

## Usage

1. First, create a directory to store your scripts.

2. Then, create a script file in the directory, for example, `main.ts`:

```ts
import { pgType, TypefusionPgResult } from "typefusion";

export const mainSchema = {
id: pgType.integer().notNull(),
name: pgType.text().notNull(),
age: pgType.integer().notNull(),
email: pgType.text().notNull(),
address: pgType.text().notNull(),
};

export default async function main(): Promise<
TypefusionPgResult<typeof mainSchema>
> {
console.log("running main");
return {
types: mainSchema,
data: [
{
id: 1,
name: "John Doe",
age: 30,
email: "[email protected]",
address: "123 Main St",
},
],
};
}
```

Then, you may either use the CLI or library to run your script.

### CLI

You can use the Typefusion CLI to run your scripts. Here is an example of how to run the CLI:

```sh
npm run typefusion --help # The CLI mode has some nifty features, check them out!
```

A common setup in your package.json scripts (presuming you have dotenv setup with a `.env` file in your project):

```json
"scripts": {
"run-typefusion": "dotenv -- typefusion ./scripts"
}
```

Then you can run your scripts with:

```sh
npm run run-typefusion
```

You should now see the output of your script in your database! There should be a table called `main` with the data you returned from the script above.

### Library

You can also use Typefusion as a library in your TypeScript projects. Here is an example of how to use it:

```ts
import typefusion from "typefusion";

await typefusion({
directory: "./scripts",
});
```

## Typefusion Ref

Typefusion Refs allow you to reference the results of a script in another script. It is useful for building complex workflows. Following the usage of the library above, here is an example of how to use Typefusion Ref to reference the results of the `main` script:

```ts
import { pgType, typefusionRef, TypefusionPgResult } from "typefusion";
import main from "./main.js";

const smallSchema = {
small: pgType.text().notNull(),
};

export default async function typefusion_ref(): Promise<
TypefusionPgResult<typeof smallSchema>
> {
const result = await typefusionRef(main);
console.log("typefusion ref main result", result);
return {
types: smallSchema,
data: [
{
small: "smallString" as const,
},
],
};
}
```

You'll notice that the `main` script is run first, and then the `typefusion_ref` script is run. The result of the `main` script is then used in the `typefusion_ref` script. `result` is fully type-safe depending on the types you used in the `main` script.

As long as there are no circular dependencies, you can reference any script from any other script, allowing for complex workflows.

If you want to run a query of some kind and want to just grab the table name without getting the full data, you can use the `typefusionRefTableName` function. This is useful if you want to run a query of some kind and want to just grab the table name from the other script without the full data.

```ts
import { typefusionRefTableName } from "typefusion";
import main from "./main.js";

const tableName = await typefusionRefTableName(main);
console.log("typefusion ref table name", tableName); // should print out "main"
```

## Examples

Here are some examples of how to use Typefusion:

1. [Main Example](packages/typefusion/example/main.ts)
2. [Ref Example](packages/typefusion/example/options/typefusion_pg_result.ts)

You can find the remaining examples [here](packages/typefusion/example).

## Docs

Reference docs can be found at https://aniravi24.github.io/typefusion
Reference docs can be found at [https://aniravi24.github.io/typefusion](https://aniravi24.github.io/typefusion). JSDoc comments are used to document more details not covered in this README.

## Have an issue?

Open a GitHub issue [here](https://github.com/aniravi24/typefusion/issues/new)

## Contributions

Please open an issue to document a bug or suggest a feature request before opening a PR!
Please open an issue to document a bug or suggest a feature request before opening a PR.
13 changes: 10 additions & 3 deletions packages/typefusion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@
"typefusion": "./dist/cli.js"
},
"exports": {
".": "./dist/index.js",
"./effect": "./dist/effect.js"
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./effect": {
"import": "./dist/effect.js",
"require": "./dist/effect.cjs"
}
},
"main": "dist/index.js",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup",
Expand Down
2 changes: 1 addition & 1 deletion packages/typefusion/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const command = Command.make(

const cli = Command.run(command, {
name: "Typefusion CLI",
version: "v0.0.1",
version: "IGNORE_VERSION_OUTPUT_HERE_CHECK_YOUR_PACKAGE_JSON",
});

Effect.suspend(() => cli(process.argv)).pipe(
Expand Down
64 changes: 64 additions & 0 deletions packages/typefusion/src/db/postgres/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { Data, Effect } from "effect";

export type Nullable<T> = T | null;

/**
* This is a simple wrapper class to represent a Postgres type that will be used to define a table.
* It also provides a fluent API to set properties of the column (e.g. nullability).
* You can easily create your own custom types by instantiating this class.
*
* @example
* ```ts
* const myCustomType = new PgType<Nullable<string>>("myCustomType");
* ```
*/
export class PgType<T> {
public _type: T;
private _nullable: boolean;
Expand Down Expand Up @@ -76,3 +88,55 @@ export const pgType = {
xml: () => new PgType<Nullable<string>>("xml"),
bytea: () => new PgType<Nullable<Uint8Array>>("bytea"),
};

export class UnsupportedJSTypePostgresConversionError extends Data.TaggedError(
"UnsupportedJSTypePostgresConversionError",
)<{
cause: unknown;
message: string;
}> {}

// People shouldn't really use this function, it's only useful for the simplest of types
export const valueToPostgresType = (value: unknown) =>
Effect.gen(function* () {
if (value === null || value === undefined) {
return "TEXT";
}
if (value instanceof Date) {
return "TIMESTAMP WITH TIME ZONE";
}
switch (typeof value) {
case "object":
return "JSONB";
case "string":
return "TEXT";
case "bigint":
return "BIGINT";
case "number":
if (Number.isInteger(value)) {
// PostgreSQL INTEGER range: -2,147,483,648 to +2,147,483,647
const MIN_INTEGER = -2147483648;
const MAX_INTEGER = 2147483647;
if (value >= MIN_INTEGER && value <= MAX_INTEGER) {
return "INTEGER";
}
return "BIGINT";
}
return "DOUBLE PRECISION";
case "boolean":
return "BOOLEAN";
default:
return yield* Effect.fail(
new UnsupportedJSTypePostgresConversionError({
cause: null,
message: `Unsupported type for value provided in script result: ${typeof value}`,
}),
);
}
});

export const postgresIdColumn = (type?: PgType<unknown>) => {
const idType =
type?.getPostgresType() || "BIGINT GENERATED ALWAYS AS IDENTITY";
return `id ${idType} PRIMARY KEY` as const;
};
2 changes: 2 additions & 0 deletions packages/typefusion/src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ import { typefusion } from "./typefusion.js";

export { typefusionRefEffect, typefusionRefTableNameEffect } from "./lib.js";

export { typefusion };

export default typefusion;
5 changes: 5 additions & 0 deletions packages/typefusion/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export class ModuleExecutionError extends Data.TaggedError(
message: string;
}> {}

/**
* Runs a module and inserts the result into the database.
* @param leaf - The relative path of the module to run.
* @returns void
*/
export function runModule(leaf: string) {
return Effect.gen(function* () {
const path = `../${leaf}`;
Expand Down
16 changes: 11 additions & 5 deletions packages/typefusion/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Effect } from "effect";
import { typefusion, TypefusionConfig } from "./typefusion.js";
import {
typefusion as typefusionEffect,
TypefusionConfig,
} from "./typefusion.js";

export {
DependencyGraphGenerationError,
Expand All @@ -8,20 +11,23 @@ export {

export { typefusionRef, typefusionRefTableName } from "./lib.js";

export { UnsupportedJSTypePostgresConversionError } from "./db/postgres/types.js";

export {
TypefusionPgResult,
TypefusionResult,
TypefusionResultDataOnly,
TypefusionResultUnknown,
ConvertDataToSQLDDLError,
DatabaseGetError,
DatabaseSelectError,
DatabaseInsertError,
UnsupportedTypeError,
} from "./store.js";

export { ModuleExecutionError, ModuleImportError } from "./helpers.js";

export * from "./db/postgres/types.js";

export default (config: TypefusionConfig) =>
typefusion(config).pipe(Effect.runPromise);
export const typefusion = (config: TypefusionConfig) =>
typefusionEffect(config).pipe(Effect.runPromise);

export default typefusion;
19 changes: 17 additions & 2 deletions packages/typefusion/src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Effect } from "effect";
import { DatabaseGetError, dbSelect, TypefusionScriptResult } from "./store.js";
import {
DatabaseSelectError,
dbSelect,
TypefusionScriptResult,
} from "./store.js";
import { SqlLive } from "./db/postgres/client.js";
import { ConfigError } from "effect/ConfigError";

/**
* Get the data from a module (i.e. the result of one of your Typefusion scripts).
* @param module - The module to get the data from.
* @returns The data from the associated table for that module.
*/
export const typefusionRef = async <
T extends (...args: any[]) => PromiseLike<TypefusionScriptResult>,
>(
Expand All @@ -26,6 +35,9 @@ export const typefusionRef = async <
) as any;
};

/**
* Analogous to {@link typefusionRef} but for use in Effect.
*/
export const typefusionRefEffect = <
T extends (...args: any[]) => PromiseLike<TypefusionScriptResult>,
>(
Expand All @@ -42,7 +54,7 @@ export const typefusionRefEffect = <
: Awaited<ReturnType<T>>["data"] extends Array<infer U>
? U
: never,
DatabaseGetError | ConfigError
DatabaseSelectError | ConfigError
> => {
return dbSelect(module).pipe(Effect.provide(SqlLive)) as any;
};
Expand All @@ -61,6 +73,9 @@ export const typefusionRefTableName = async <
return module.name;
};

/**
* Analogous to {@link typefusionRefTableName} but for use in Effect.
*/
export const typefusionRefTableNameEffect = <
T extends (...args: any[]) => PromiseLike<TypefusionScriptResult>,
>(
Expand Down
Loading

0 comments on commit 13f280c

Please sign in to comment.