|
| 1 | +--- |
| 2 | +description: Coding rules for line-weaver |
| 3 | +alwaysApply: true |
| 4 | +--- |
| 5 | + |
| 6 | +# Line Weaver Coding Rules |
| 7 | + |
| 8 | +## General Principles |
| 9 | + |
| 10 | +- Use functional, declarative programming (no classes) |
| 11 | +- If there are more than two parameters for a method then follow RORO pattern (Receive Object, Return Object) |
| 12 | +- Use arrow functions |
| 13 | +- Omit semicolons |
| 14 | +- Never use logging |
| 15 | +- Never use Enums in TypeScript (use const objects or union types instead) |
| 16 | +- Never use comments |
| 17 | +- For method names use verbs to describe their action |
| 18 | +- Use descriptive variable names with auxiliary verbs (e.g., `isConnected`, `hasError`) |
| 19 | +- Never use Barrel Files |
| 20 | +- Avoid Abbreviations |
| 21 | +- Avoid using "From" in method names |
| 22 | +- Before finishing task, always check if you can smaller methods |
| 23 | + |
| 24 | +## Top Down File Structure |
| 25 | + |
| 26 | +Place the main entry point of a file at the top, followed by functions in the order they are called. This lets you read the file top-down, following the program flow naturally without jumping around. A reader should be able to open a file and understand the flow of logic simply by scrolling downward. The top of the file should describe high-level behavior, and each subsequent function should reveal more detail—just like reading a well-structured narrative. |
| 27 | + |
| 28 | +In best case scenario the entry-point method is same named as the file. E.g. the method `startServer()` comes from the file `start-server.ts` |
| 29 | + |
| 30 | +### Example Structure |
| 31 | + |
| 32 | +1. Public entry point function |
| 33 | +2. High-level orchestration functions |
| 34 | +3. Mid-level helpers |
| 35 | +4. Low-level utility functions |
| 36 | + |
| 37 | +## Use Zod for Schema Validation |
| 38 | + |
| 39 | +Always use Zod schema validation and type safety. Anytime we have to parse an unkown object e.g. coming from API or reading environment variables we want to use zod library for validation |
| 40 | + |
| 41 | +### Pattern |
| 42 | + |
| 43 | +```typescript |
| 44 | +import { z } from "zod" |
| 45 | + |
| 46 | +const configSchema = z.object({ |
| 47 | + LINE_WEAVER_ENV: z.string(), |
| 48 | +}) |
| 49 | + |
| 50 | +export type Config = z.infer<typeof configSchema> |
| 51 | + |
| 52 | +export const loadConfig = (): Config => { |
| 53 | + return configSchema.parse(process.env) |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +### Rules |
| 58 | + |
| 59 | +- Use Zod schemas for all environment variable validation |
| 60 | +- Export typed config with `z.infer<typeof schema>` |
| 61 | +- Fail fast on missing required configuration - let Zod throw |
| 62 | + |
| 63 | +## Testing with node:test |
| 64 | + |
| 65 | +Use Node.js native test framework exclusively. |
| 66 | + |
| 67 | +### Pattern |
| 68 | + |
| 69 | +```typescript |
| 70 | +import assert from "node:assert" |
| 71 | +import { afterEach, beforeEach, describe, it } from "node:test" |
| 72 | + |
| 73 | +describe("Config", () => { |
| 74 | + const originalEnv = process.env |
| 75 | + |
| 76 | + beforeEach(() => { |
| 77 | + process.env = { ...originalEnv } |
| 78 | + }) |
| 79 | + |
| 80 | + afterEach(() => { |
| 81 | + process.env = originalEnv |
| 82 | + }) |
| 83 | + |
| 84 | + it("should load configuration with all required variables", () => { |
| 85 | + process.env.LINE_WEAVER_ENV = "env" |
| 86 | + |
| 87 | + const config = loadConfig() |
| 88 | + |
| 89 | + assert.equal(config.LINE_WEAVER_ENV, "env") |
| 90 | + }) |
| 91 | + |
| 92 | + it("should fail if required variable is missing", () => { |
| 93 | + delete process.env.LINE_WEAVER_ENV |
| 94 | + |
| 95 | + assert.throws(() => loadConfig()) |
| 96 | + }) |
| 97 | +}) |
| 98 | +``` |
| 99 | + |
| 100 | +### Rules |
| 101 | + |
| 102 | +- Use native `node:test` module (no Jest or other frameworks) |
| 103 | +- Import from `node:assert` for assertions |
| 104 | +- Use `describe`, `it`, `beforeEach`, `afterEach` structure |
| 105 | +- Test environment variables: save original in `const originalEnv`, restore in `afterEach` |
| 106 | +- Test both happy paths and error cases |
| 107 | +- Test validation logic thoroughly |
| 108 | +- Test one behavior per test case |
| 109 | +- Use descriptive test names starting with "should" |
| 110 | +- Test edge cases (missing values, invalid types, boundary conditions) |
| 111 | +- Keep tests focused and fast |
| 112 | +- Aim for 10-15 tests maximum per test file |
| 113 | + |
| 114 | +## Keep Methods Small and at a Single Abstraction Level |
| 115 | + |
| 116 | +Methods must be small, focused, and operate at one consistent level of abstraction. A method should either describe what happens (high-level intention) or how it happens (low-level details). Small, well-focused methods are easier to understand, test, and maintain. |
| 117 | + |
| 118 | +## Fail Early & Return Happy Path at the End |
| 119 | + |
| 120 | +Methods should fail fast on invalid conditions and always return the successful result at the end. Avoid nesting the main logic inside multiple if or else blocks — the “happy path” should be clear, linear, and unindented. Keeping the happy path at the bottom or after all validations ensures the main logic is easy to read top-down, with early exits handling exceptional cases. |
| 121 | + |
| 122 | +### Guidelines: |
| 123 | + |
| 124 | +- Check invalid conditions first; throw errors or return early. |
| 125 | +- Keep the successful outcome at the end of the method, clearly showing the happy path. |
| 126 | +- Avoid deep nesting. each method should read like a straight narrative from top to bottom. |
| 127 | +- Use intention-revealing variable names for the returned object. |
| 128 | + |
| 129 | +### Example Pattern |
| 130 | + |
| 131 | +```typescript |
| 132 | +function loadConfig() { |
| 133 | + const config = parse(process.env) |
| 134 | + if (!config) { |
| 135 | + throw new Error("Environment variables are not set") // fail early |
| 136 | + } |
| 137 | + return config // happy path |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +## Lokalitätsprinzip – Invoke Methods Where They Are Needed |
| 142 | + |
| 143 | +Methods should be called close to where their results are used. Avoid scattering method calls far from their context or orchestrating logic from distant parts of the code. |
| 144 | + |
| 145 | +### Guidelines: |
| 146 | + |
| 147 | +- Call helper methods right before or where their result is needed. |
| 148 | +- Avoid precomputing values at the top of a function if they are only used much later. |
| 149 | +- Keep dependent operations grouped together; the reader should not need to scroll or search to understand the logic flow. |
0 commit comments