Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 14 additions & 0 deletions .cursor/rules/bun.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
description:
globs:
alwaysApply: true
---
Default to using Bun instead of Node.js.

- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`

For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
42 changes: 42 additions & 0 deletions .cursor/rules/default-exports.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---

Unless explicitly required by the framework, do not use default exports.

```ts
// BAD
export default function myFunction() {
return <div>Hello</div>;
}
```

```ts
// GOOD
export function myFunction() {
return <div>Hello</div>;
}
```

Default exports create confusion from the importing file.

```ts
// BAD
import myFunction from "./myFunction";
```

```ts
// GOOD
import { myFunction } from "./myFunction";
```

There are certain situations where a framework may require a default export. For instance, Next.js requires a default export for pages.

```tsx
// This is fine, if required by the framework
export default function MyPage() {
return <div>Hello</div>;
}
```
57 changes: 57 additions & 0 deletions .cursor/rules/discriminated-unions.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---
Proactively use discriminated unions to model data that can be in one of a few different shapes.

For example, when sending events between environments:

```ts
type UserCreatedEvent = {
type: "user.created";
data: { id: string; email: string };
};

type UserDeletedEvent = {
type: "user.deleted";
data: { id: string };
};

type Event = UserCreatedEvent | UserDeletedEvent;
```

Use switch statements to handle the results of discriminated unions:

```ts
const handleEvent = (event: Event) => {
switch (event.type) {
case "user.created":
console.log(event.data.email);
break;
case "user.deleted":
console.log(event.data.id);
break;
}
};
```

Use discriminated unions to prevent the 'bag of optionals' problem.

For example, when describing a fetching state:

```ts
// BAD - allows impossible states
type FetchingState<TData> = {
status: "idle" | "loading" | "success" | "error";
data?: TData;
error?: Error;
};

// GOOD - prevents impossible states
type FetchingState<TData> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: TData }
| { status: "error"; error: Error };
```
21 changes: 21 additions & 0 deletions .cursor/rules/enums.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---
Do not introduce new enums into the codebase.

If you require enum-like behaviour, use an `as const` object:

```ts
const backendToFrontendEnum = {
xs: "EXTRA_SMALL",
sm: "SMALL",
md: "MEDIUM",
} as const;

type LowerCaseEnum = keyof typeof backendToFrontendEnum; // "xs" | "sm" | "md"

type UpperCaseEnum =
(typeof backendToFrontendEnum)[LowerCaseEnum]; // "EXTRA_SMALL" | "SMALL" | "MEDIUM"
```
46 changes: 46 additions & 0 deletions .cursor/rules/generic-functions.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---
When building generic functions, you may need to use any inside the function body.

This is because TypeScript often cannot match your runtime logic to the logic done inside your types.

One example:

```ts
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye",
>(
input: TInput,
): TInput extends "hello" ? "goodbye" : "hello" => {
if (input === "goodbye") {
return "hello"; // Error!
} else {
return "goodbye"; // Error!
}
};
```

On the type level (and the runtime), this function returns `goodbye` when the input is `hello`.

There is no way to make this work concisely in TypeScript.

So using `any` is the most concise solution:

```ts
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye",
>(
input: TInput,
): TInput extends "hello" ? "goodbye" : "hello" => {
if (input === "goodbye") {
return "hello" as any;
} else {
return "goodbye" as any;
}
};
```

Outside of generic functions, use `any` extremely sparingly.
36 changes: 36 additions & 0 deletions .cursor/rules/imports.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---
Do not import from files with any of the following names: index, node, bun, browser, common

Use import type whenever you are importing a type.

Prefer top-level `import type` over inline `import { type ... }` when there is only a type being imported

```ts
// BAD
import { type TUser } from "./user";
```

```ts
// GOOD
import type { TUser } from "./user";
```

When mixed, then keep to a single line:
```ts
// BAD
import { type TUser } from "./user";
import { User } from "./user";
```

```ts
// GOOD
import { User, type TUser } from "./user";
```

Merge the two lines when you get a chance.

If a type is being imported and there is not `type` keyword before it, then add it.
38 changes: 38 additions & 0 deletions .cursor/rules/interface-extends.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---
ALWAYS prefer interfaces when modelling inheritance.

The `&` operator has terrible performance in TypeScript. Only use it where `interface extends` is not possible.

```ts
// BAD

type A = {
a: string;
};

type B = {
b: string;
};

type C = A & B;
```

```ts
// GOOD

interface A {
a: string;
}

interface B {
b: string;
}

interface C extends A, B {
// Additional properties can be added here
}
```
27 changes: 27 additions & 0 deletions .cursor/rules/jsdoc-comments.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---

Use JSDoc comments to annotate functions and types.

Be concise in JSDoc comments, and only provide JSDoc comments if the function's behaviour is not self-evident.

Use the JSDoc inline `@link` tag to link to other functions and types within the same file.

Adding examples is good when the function or method is particularly long.

```ts
/**
* Subtracts two numbers
*/
const subtract = (a: number, b: number) => a - b;

/**
* Does the opposite to {@link subtract}
*/
const add = (a: number, b: number) => a + b;
```

Do not add single line comments where the code being added is easy to understand.
31 changes: 31 additions & 0 deletions .cursor/rules/optional-properties.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---

Use optional properties extremely sparingly. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property.

In the example below we always want to pass user ID to `AuthOptions`. This is because if we forget to pass it somewhere in the code base, it will cause our function to be not authenticated.

```ts
// BAD
type AuthOptions = {
userId?: string;
};

const func = (options: AuthOptions) => {
const userId = options.userId;
};
```

```ts
// GOOD
type AuthOptions = {
userId: string | undefined;
};

const func = (options: AuthOptions) => {
const userId = options.userId;
};
```
34 changes: 34 additions & 0 deletions .cursor/rules/read-only-properties.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---
Use `readonly` properties for object types by default. This will prevent accidental mutation at runtime.

Omit `readonly` only when the property is genuinely mutable.

```ts
// BAD
type User = {
id: string;
};

const user: User = {
id: "1",
};

user.id = "2";
```

```ts
// GOOD
type User = {
readonly id: string;
};

const user: User = {
id: "1",
};

user.id = "2"; // Error
```
24 changes: 24 additions & 0 deletions .cursor/rules/return-types.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
description:
globs: *.ts,*.tsx
alwaysApply: false
---
When declaring functions on the top-level of a module,
declare their return types. This will help future AI
assistants understand the function's purpose without inferring.

```ts
const myFunc = (): string => {
return "hello";
};
```

One exception to this is components which return JSX.
No need to declare the return type of a component,
as it is always JSX.

```tsx
const MyComponent = () => {
return <div>Hello</div>;
};
```
1 change: 0 additions & 1 deletion .env.test
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
SEC_RAW_DATA_FOLDER=./sec-data
SEC_DB_FOLDER=./sec-db
SEC_DB_NAME=edgar
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,8 @@
"bun.debugTerminal.enabled": true,
"markdown.extension.toc.levels": "2..6",
"js/ts.implicitProjectConfig.target": "ESNext",
"typescript.tsdk": "./node_modules/typescript/lib"
"typescript.tsdk": "./node_modules/typescript/lib",
"[xml]": {
"editor.defaultFormatter": "DotJoshJohnson.xml"
}
}
Loading