@venizia/dev-configs 0.0.5 → 0.0.7-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,37 +1,106 @@
1
- # @venizia/dev-configs
1
+ <div align="center">
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@venizia/dev-configs.svg)](https://www.npmjs.com/package/@venizia/dev-configs)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
3
+ # :fire: IGNIS - @venizia/dev-configs
5
4
 
6
- Shared development configurations for the **Ignis Framework** - includes ESLint, Prettier, and TypeScript settings.
5
+ **Shared ESLint, Prettier, and TypeScript configs for the Ignis ecosystem**
6
+
7
+ [![npm](https://img.shields.io/npm/v/@venizia/dev-configs.svg?style=flat-square&color=cb3837)](https://www.npmjs.com/package/@venizia/dev-configs)
8
+ [![License](https://img.shields.io/badge/License-MIT-3DA639.svg?style=flat-square)](https://opensource.org/licenses/MIT)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6.svg?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
10
+ [![ESLint](https://img.shields.io/badge/ESLint-9.x-4B32C3.svg?style=flat-square&logo=eslint&logoColor=white)](https://eslint.org/)
11
+ [![Prettier](https://img.shields.io/badge/Prettier-3.x-F7B93E.svg?style=flat-square&logo=prettier&logoColor=black)](https://prettier.io/)
12
+
13
+ Single source of truth for development tooling across the entire Ignis monorepo. Flat ESLint config (v9+), Prettier formatting, and TypeScript compiler settings with `experimentalDecorators` + `emitDecoratorMetadata` enabled.
14
+
15
+ [Installation](#installation) &#8226; [Quick Start](#quick-start) &#8226; [API Reference](#eslint-configuration) &#8226; [Documentation](https://venizia-ai.github.io/ignis)
16
+
17
+ </div>
18
+
19
+ ## Highlights
20
+
21
+ | | Feature | |
22
+ | :---: | :--- | :--- |
23
+ | **1** | **3-Line Setup** | Import and re-export -- ESLint, Prettier, and TypeScript ready |
24
+ | **2** | **Flat Config (ESLint v9+)** | Modern flat config format with composable layers |
25
+ | **3** | **Decorator Support** | `experimentalDecorators` + `emitDecoratorMetadata` pre-configured |
26
+ | **4** | **Consistent Formatting** | 100-char width, single quotes, trailing commas everywhere |
27
+
28
+ ---
29
+
30
+ ## Table of Contents
31
+
32
+ - [Installation](#installation)
33
+ - [Quick Start](#quick-start)
34
+ - [ESLint Configuration](#eslint-configuration)
35
+ - [Architecture: Three-Layer Rule Stack](#architecture-three-layer-rule-stack)
36
+ - [Layer 1: eslint-common (Foundation)](#layer-1-eslint-common-foundation)
37
+ - [Layer 2: eslint-node (Node.js Specialization)](#layer-2-eslint-node-nodejs-specialization)
38
+ - [Layer 3: dev-configs (Ignis Overrides)](#layer-3-dev-configs-ignis-overrides)
39
+ - [Complete Rule Reference](#complete-rule-reference)
40
+ - [Rule Examples: Pass vs Fail](#rule-examples-pass-vs-fail)
41
+ - [Flat Config Format (ESLint v9+)](#flat-config-format-eslint-v9)
42
+ - [Adding Project-Specific Overrides](#adding-project-specific-overrides)
43
+ - [Adding File-Specific Overrides](#adding-file-specific-overrides)
44
+ - [Prettier Configuration](#prettier-configuration)
45
+ - [Settings with Before/After Examples](#settings-with-beforeafter-examples)
46
+ - [Overriding Prettier Settings](#overriding-prettier-settings)
47
+ - [.prettierignore Patterns](#prettierignore-patterns)
48
+ - [TypeScript Configuration](#typescript-configuration)
49
+ - [tsconfig.base.json](#tsconfigbasejson)
50
+ - [tsconfig.common.json](#tsconfigcommonjson)
51
+ - [Critical Flags for Decorator-Based DI](#critical-flags-for-decorator-based-di)
52
+ - [Consumption Pattern](#consumption-pattern)
53
+ - [Real-World Consumer Examples](#real-world-consumer-examples)
54
+ - [Setting Up a New Package](#setting-up-a-new-package)
55
+ - [Extending and Overriding](#extending-and-overriding)
56
+ - [IDE Integration](#ide-integration)
57
+ - [CI/CD Integration](#cicd-integration)
58
+ - [Troubleshooting](#troubleshooting)
59
+ - [Version Compatibility](#version-compatibility)
60
+ - [Comparison with Alternatives](#comparison-with-alternatives)
61
+ - [Package Exports](#package-exports)
62
+ - [Important Notes](#important-notes)
63
+ - [License](#license)
64
+
65
+ ---
7
66
 
8
67
  ## Installation
9
68
 
10
69
  ```bash
11
70
  bun add -d @venizia/dev-configs
12
- # or
13
- npm install -D @venizia/dev-configs
14
71
  ```
15
72
 
16
- ## Usage
73
+ Peer dependencies -- install only the tools you intend to use:
74
+
75
+ ```bash
76
+ bun add -d eslint@^9.0.0 prettier@^3.0.0 typescript@^5.0.0
77
+ ```
78
+
79
+ All peer dependencies are marked optional, so you only need to install the ones relevant to your workflow.
80
+
81
+ ---
82
+
83
+ ## Quick Start
17
84
 
18
- ### ESLint (`eslint.config.mjs`)
85
+ Three lines per config file -- that is all any consuming package needs:
86
+
87
+ **ESLint** (`eslint.config.mjs`):
19
88
 
20
89
  ```javascript
21
- import { eslintConfigs } from "@venizia/dev-configs";
90
+ import { eslintConfigs } from '@venizia/dev-configs';
22
91
 
23
92
  export default eslintConfigs;
24
93
  ```
25
94
 
26
- ### Prettier (`.prettierrc.mjs`)
95
+ **Prettier** (`.prettierrc.mjs`):
27
96
 
28
97
  ```javascript
29
- import { prettierConfigs } from "@venizia/dev-configs";
98
+ import { prettierConfigs } from '@venizia/dev-configs';
30
99
 
31
100
  export default prettierConfigs;
32
101
  ```
33
102
 
34
- ### TypeScript (`tsconfig.json`)
103
+ **TypeScript** (`tsconfig.json`):
35
104
 
36
105
  ```json
37
106
  {
@@ -43,16 +112,1661 @@ export default prettierConfigs;
43
112
  }
44
113
  ```
45
114
 
46
- ## About Ignis
115
+ ---
116
+
117
+ ## ESLint Configuration
118
+
119
+ The ESLint configuration uses the **flat config format** (ESLint v9+) and is exported as a `Linter.Config[]` array.
120
+
121
+ ### Architecture: Three-Layer Rule Stack
122
+
123
+ The final ESLint configuration is built by composing three layers, each applied in order. Later layers override earlier ones:
124
+
125
+ ```
126
+ Layer 1: @minimaltech/eslint-common (Foundation: JS recommended + Prettier + TS recommended + naming conventions)
127
+ |
128
+ Layer 2: @minimaltech/eslint-node (Node.js: LB4-derived rules + eslint-plugin-n + TS strictness adjustments)
129
+ |
130
+ Layer 3: @venizia/dev-configs (Ignis: relax no-explicit-any + add unicorn + enforce curly braces)
131
+ ```
132
+
133
+ Each layer is a flat config array. The final export is the concatenation of all three, meaning rules defined later take precedence.
134
+
135
+ ---
136
+
137
+ ### Layer 1: eslint-common (Foundation)
138
+
139
+ Provided by `@minimaltech/eslint-common`. This layer establishes:
140
+
141
+ - **`@eslint/js` recommended** -- core JavaScript best-practice rules
142
+ - **`eslint-plugin-prettier/recommended`** -- runs Prettier as an ESLint rule and disables formatting rules that conflict with Prettier
143
+ - **`typescript-eslint` recommended** -- TypeScript-specific rules from the `@typescript-eslint` project
144
+
145
+ Key rules from this layer:
146
+
147
+ | Rule | Value | Purpose |
148
+ | :--- | :---- | :------ |
149
+ | `curly` | `["error", "all"]` | Braces required on all control flow |
150
+ | `prefer-const` | `"error"` | Must use `const` when variable is never reassigned |
151
+ | `no-restricted-imports` | Blocks `lodash` barrel import | Forces `import get from 'lodash/get'` for tree-shaking |
152
+ | `@typescript-eslint/no-explicit-any` | `"warn"` | Warns on `any` types (overridden later) |
153
+ | `@typescript-eslint/no-unused-vars` | `"warn"` | Warns on unused variables; `_` prefix suppresses |
154
+ | `@typescript-eslint/naming-convention` | Complex | Interfaces must have `I` prefix; type aliases must have `T` prefix or `Type`/`Like` suffix; booleans must have `is`/`has`/`should`/etc. prefix |
155
+
156
+ **File targeting:** Applies to `**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}`.
157
+
158
+ **Default ignores:** `node_modules/`, `*.d.ts`, `build/`, `dist/`, `release/`, `babel.config.*`, `.eslintrc.*`, `.prettierrc.*`, `eslint.config.*`.
159
+
160
+ ---
161
+
162
+ ### Layer 2: eslint-node (Node.js Specialization)
163
+
164
+ Provided by `@minimaltech/eslint-node`. Built on top of the common layer, this adds:
165
+
166
+ **LoopBack 4-derived rules (`lbRules`)** -- a comprehensive rule set originally from the LoopBack Next project, adapted for Ignis:
167
+
168
+ | Rule | Value | Purpose |
169
+ | :--- | :---- | :------ |
170
+ | `prefer-const` | `"error"` | Enforce immutable bindings |
171
+ | `no-unused-labels` | `"error"` | Disallow unused labels |
172
+ | `no-new-wrappers` | `"error"` | Disallow `new String()`, `new Number()`, etc. |
173
+ | `no-throw-literal` | `"error"` | Must throw `Error` objects, not strings |
174
+ | `no-unused-expressions` | `"error"` | Disallow expression statements with no side effects |
175
+ | `no-var` | `"error"` | Disallow `var`; use `let`/`const` |
176
+ | `eqeqeq` | `["error", "smart"]` | Require `===`/`!==` except for `null` checks |
177
+ | `no-void` | `"error"` | Disallow `void` operator |
178
+ | `no-caller` | `"error"` | Disallow `arguments.caller`/`arguments.callee` |
179
+ | `no-shadow` | `"error"` | Disallow variable shadowing |
180
+ | `@typescript-eslint/no-explicit-any` | `"error"` | Error on `any` (overridden by dev-configs) |
181
+ | `@typescript-eslint/no-unused-vars` | `"error"` (all vars, no args) | Error on unused variables |
182
+ | `@typescript-eslint/await-thenable` | `"error"` | Disallow `await` on non-Promise values |
183
+ | `@typescript-eslint/no-floating-promises` | `"error"` | Must handle or return Promises |
184
+ | `@typescript-eslint/no-misused-promises` | `"error"` | Prevent passing Promises where not expected |
185
+ | `@typescript-eslint/prefer-optional-chain` | `"error"` | Prefer `a?.b` over `a && a.b` |
186
+ | `@typescript-eslint/prefer-nullish-coalescing` | `"error"` | Prefer `a ?? b` over `a \|\| b` |
187
+ | `@typescript-eslint/return-await` | `"error"` | Require `return await` in try/catch |
188
+ | `@typescript-eslint/prefer-for-of` | `"error"` | Prefer `for...of` over indexed `for` loops |
189
+ | `@typescript-eslint/unified-signatures` | `"error"` | Merge overloaded signatures when possible |
190
+ | `@typescript-eslint/no-use-before-define` | `"error"` | Disallow use before declaration |
191
+ | `@typescript-eslint/naming-convention` | Complex | Overrides Layer 1 naming; allows `camelCase`, `PascalCase`, `UPPER_CASE` for variables/properties; `PascalCase` for types; allows leading underscore for private/protected members; allows `Mixin` suffix on PascalCase functions |
192
+
193
+ **Node.js-specific rules** via `eslint-plugin-n`:
194
+
195
+ | Rule | Value | Purpose |
196
+ | :--- | :---- | :------ |
197
+ | `n/prefer-node-protocol` | `"error"` | Require `node:` prefix: `import fs from 'node:fs'` |
198
+
199
+ **Relaxations applied at this layer:**
200
+
201
+ | Rule | Value | Purpose |
202
+ | :--- | :---- | :------ |
203
+ | `@typescript-eslint/no-inferrable-types` | `"off"` | Allow explicit types on initialized variables |
204
+ | `@typescript-eslint/no-misused-promises` | `"off"` | Re-disabled (was enabled in lbRules, turned off here) |
205
+ | `@typescript-eslint/ban-ts-comment` | `"off"` | Allow `@ts-ignore`, `@ts-expect-error` |
206
+ | Various legacy TS rules | `"off"` | Old rules no longer relevant in modern TS-ESLint |
207
+
208
+ ---
209
+
210
+ ### Layer 3: dev-configs (Ignis Overrides)
211
+
212
+ This is the final layer defined in `@venizia/dev-configs` itself:
213
+
214
+ ```typescript
215
+ import mtLinterConfs from '@minimaltech/eslint-node';
216
+ import type { Linter } from 'eslint';
217
+ import unicorn from 'eslint-plugin-unicorn';
218
+
219
+ export const eslintConfigs: Linter.Config[] = [
220
+ ...mtLinterConfs,
221
+ {
222
+ rules: {
223
+ '@typescript-eslint/no-explicit-any': 'off',
224
+ },
225
+ },
226
+ {
227
+ plugins: { unicorn },
228
+ rules: {
229
+ curly: ['error', 'all'],
230
+ 'unicorn/switch-case-braces': ['error', 'always'],
231
+ },
232
+ },
233
+ ];
234
+ ```
235
+
236
+ **Config object 1** -- overrides `@typescript-eslint/no-explicit-any` from `"error"` (Layer 2) to `"off"`:
237
+
238
+ | Rule | Value | Purpose |
239
+ | :--- | :---- | :------ |
240
+ | `@typescript-eslint/no-explicit-any` | `"off"` | Pragmatic decision. Framework-level code dealing with metadata, decorators, and dynamic DI resolution frequently requires `any`. |
241
+
242
+ **Config object 2** -- adds `eslint-plugin-unicorn` and enforces brace rules:
243
+
244
+ | Rule | Value | Purpose |
245
+ | :--- | :---- | :------ |
246
+ | `curly` | `["error", "all"]` | Re-enforces braces on all control flow (already set in Layer 1, repeated for clarity) |
247
+ | `unicorn/switch-case-braces` | `["error", "always"]` | Every `case`/`default` in a `switch` must be wrapped in braces |
248
+
249
+ ---
250
+
251
+ ### Complete Rule Reference
252
+
253
+ The following table shows every explicitly configured rule across all three layers, with the **final effective value** after all overrides:
254
+
255
+ | Rule | Final Value | Set By |
256
+ | :--- | :---------- | :----- |
257
+ | **Core JavaScript** | | |
258
+ | `curly` | `["error", "all"]` | Layer 1 + 3 |
259
+ | `prefer-const` | `"error"` | Layer 1 + 2 |
260
+ | `eqeqeq` | `["error", "smart"]` | Layer 2 |
261
+ | `no-var` | `"error"` | Layer 2 |
262
+ | `no-void` | `"error"` | Layer 2 |
263
+ | `no-caller` | `"error"` | Layer 2 |
264
+ | `no-shadow` | `"error"` | Layer 2 |
265
+ | `no-throw-literal` | `"error"` | Layer 2 |
266
+ | `no-new-wrappers` | `"error"` | Layer 2 |
267
+ | `no-unused-labels` | `"error"` | Layer 2 |
268
+ | `no-unused-expressions` | `"error"` | Layer 2 |
269
+ | `no-restricted-imports` | Blocks `lodash` barrel | Layer 1 |
270
+ | `no-console` | `"off"` | Layer 2 |
271
+ | `no-undef` | `"off"` | Layer 1 |
272
+ | `no-constant-condition` | `"off"` | Layer 1 |
273
+ | `no-mixed-operators` | `"off"` | Layer 2 |
274
+ | `no-inner-declarations` | `"off"` | Layer 2 |
275
+ | `no-dupe-class-members` | `"off"` | Layer 2 |
276
+ | `no-redeclare` | `"off"` | Layer 2 |
277
+ | `no-invalid-this` | `"off"` | Layer 2 |
278
+ | `no-return-await` | `"off"` | Layer 2 |
279
+ | `camelcase` | `"off"` | Layer 2 |
280
+ | `prefer-promise-reject-errors` | `"off"` | Layer 1 |
281
+ | **TypeScript** | | |
282
+ | `@typescript-eslint/no-explicit-any` | `"off"` | Layer 3 (override) |
283
+ | `@typescript-eslint/no-unused-vars` | `["error", ...]` | Layer 2 (override) |
284
+ | `@typescript-eslint/naming-convention` | Complex (see above) | Layer 2 (override) |
285
+ | `@typescript-eslint/await-thenable` | `"error"` | Layer 2 |
286
+ | `@typescript-eslint/no-floating-promises` | `"error"` | Layer 2 |
287
+ | `@typescript-eslint/prefer-optional-chain` | `"error"` | Layer 2 |
288
+ | `@typescript-eslint/prefer-nullish-coalescing` | `"error"` | Layer 2 |
289
+ | `@typescript-eslint/return-await` | `"error"` | Layer 2 |
290
+ | `@typescript-eslint/prefer-for-of` | `"error"` | Layer 2 |
291
+ | `@typescript-eslint/unified-signatures` | `"error"` | Layer 2 |
292
+ | `@typescript-eslint/no-use-before-define` | `"error"` | Layer 2 |
293
+ | `@typescript-eslint/no-non-null-asserted-optional-chain` | `"error"` | Layer 2 |
294
+ | `@typescript-eslint/adjacent-overload-signatures` | `"error"` | Layer 2 |
295
+ | `@typescript-eslint/no-misused-new` | `"error"` | Layer 2 |
296
+ | `@typescript-eslint/no-invalid-this` | `"error"` | Layer 2 |
297
+ | `@typescript-eslint/no-shadow` | `"error"` | Layer 2 |
298
+ | `@typescript-eslint/no-misused-promises` | `"off"` | Layer 2 (node override) |
299
+ | `@typescript-eslint/no-inferrable-types` | `"off"` | Layer 2 (node) |
300
+ | `@typescript-eslint/ban-ts-comment` | `"off"` | Layer 2 (node) |
301
+ | `@typescript-eslint/no-non-null-assertion` | `"off"` | Layer 2 |
302
+ | `@typescript-eslint/explicit-function-return-type` | `"off"` | Layer 1 + 2 |
303
+ | `@typescript-eslint/no-namespace` | `"off"` | Layer 2 |
304
+ | `@typescript-eslint/no-require-imports` | `"off"` | Layer 1 + 2 |
305
+ | `@typescript-eslint/no-empty-function` | `"off"` | Layer 2 |
306
+ | `@typescript-eslint/consistent-type-assertions` | `"off"` | Layer 2 |
307
+ | `@typescript-eslint/explicit-member-accessibility` | `"off"` | Layer 2 |
308
+ | `@typescript-eslint/no-empty-object-type` | `"off"` | Layer 1 |
309
+ | `@typescript-eslint/no-unsafe-function-type` | `"off"` | Layer 1 |
310
+ | `@typescript-eslint/no-extraneous-class` | `"off"` | Layer 1 |
311
+ | `@typescript-eslint/strict-boolean-expressions` | `"off"` | Layer 1 |
312
+ | **Node.js** | | |
313
+ | `n/prefer-node-protocol` | `"error"` | Layer 2 |
314
+ | **Unicorn** | | |
315
+ | `unicorn/switch-case-braces` | `["error", "always"]` | Layer 3 |
316
+
317
+ ---
318
+
319
+ ### Rule Examples: Pass vs Fail
320
+
321
+ #### `curly: ["error", "all"]` -- Braces Required on All Control Flow
322
+
323
+ ```typescript
324
+ // PASSES -- braces on every branch
325
+ if (user.isAdmin) {
326
+ grantAccess();
327
+ }
328
+
329
+ if (count > 0) {
330
+ process();
331
+ } else {
332
+ skip();
333
+ }
334
+
335
+ for (const item of items) {
336
+ transform(item);
337
+ }
338
+
339
+ while (hasNext()) {
340
+ advance();
341
+ }
342
+ ```
343
+
344
+ ```typescript
345
+ // FAILS -- missing braces
346
+ if (user.isAdmin) grantAccess(); // Error: Expected { after 'if' condition
347
+
348
+ if (count > 0) process(); // Error: Expected { after 'if' condition
349
+ else skip(); // Error: Expected { after 'else'
350
+
351
+ for (const item of items) transform(item); // Error: Expected { after 'for'
352
+
353
+ while (hasNext()) advance(); // Error: Expected { after 'while' condition
354
+ ```
355
+
356
+ ---
357
+
358
+ #### `@typescript-eslint/no-explicit-any: off` -- Why This Is Pragmatic
359
+
360
+ In framework-level code, `any` is sometimes unavoidable when dealing with decorator metadata, dynamic DI resolution, and generic container patterns:
361
+
362
+ ```typescript
363
+ // These patterns are common in Ignis and would be painful to type perfectly:
364
+
365
+ // Decorator factory that accepts any class
366
+ function controller(opts: { path: string }) {
367
+ return function (target: any) { // 'any' is acceptable here
368
+ MetadataRegistry.define({ target, key: 'controller', value: opts });
369
+ };
370
+ }
371
+
372
+ // IoC container resolving unknown types at runtime
373
+ class Container {
374
+ get<T>({ key }: { key: string }): T {
375
+ const binding = this.bindings.get(key);
376
+ return binding?.getValue(this) as any; // Runtime type, compile-time unknown
377
+ }
378
+ }
379
+
380
+ // Dynamic metadata inspection
381
+ const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', target) ?? [];
382
+ ```
383
+
384
+ While `any` is turned off globally, individual packages can re-enable it:
385
+
386
+ ```javascript
387
+ // eslint.config.mjs -- stricter for application code
388
+ export default [
389
+ ...eslintConfigs,
390
+ { rules: { '@typescript-eslint/no-explicit-any': 'error' } },
391
+ ];
392
+ ```
393
+
394
+ ---
395
+
396
+ #### `unicorn/switch-case-braces: ["error", "always"]` -- Case Clause Braces
397
+
398
+ ```typescript
399
+ // PASSES -- every case wrapped in braces
400
+ switch (action.type) {
401
+ case 'CREATE': {
402
+ const entity = buildEntity(action.payload);
403
+ await repository.create({ data: entity });
404
+ break;
405
+ }
406
+ case 'UPDATE': {
407
+ const changes = diffEntity(action.payload);
408
+ await repository.updateById({ id: action.id, data: changes });
409
+ break;
410
+ }
411
+ default: {
412
+ throw getError({ message: `Unknown action: ${action.type}`, statusCode: 400 });
413
+ }
414
+ }
415
+ ```
416
+
417
+ ```typescript
418
+ // FAILS -- case clauses without braces
419
+ switch (action.type) {
420
+ case 'CREATE': // Error: Missing braces around case clause
421
+ const entity = buildEntity(action.payload);
422
+ await repository.create({ data: entity });
423
+ break;
424
+ case 'UPDATE': // Error: Missing braces around case clause
425
+ const changes = diffEntity(action.payload); // 'changes' leaks into 'CREATE' scope!
426
+ await repository.updateById({ id: action.id, data: changes });
427
+ break;
428
+ default: // Error: Missing braces around case clause
429
+ throw getError({ message: `Unknown action: ${action.type}`, statusCode: 400 });
430
+ }
431
+ ```
432
+
433
+ Without braces, `const entity` and `const changes` share the same lexical scope. This can cause `SyntaxError: Identifier already declared` or subtle bugs where variables from one case are visible in another.
434
+
435
+ ---
436
+
437
+ #### `n/prefer-node-protocol: "error"` -- Require `node:` Prefix
438
+
439
+ ```typescript
440
+ // PASSES
441
+ import fs from 'node:fs';
442
+ import path from 'node:path';
443
+ import { createServer } from 'node:http';
444
+ import { pipeline } from 'node:stream/promises';
445
+ ```
446
+
447
+ ```typescript
448
+ // FAILS
449
+ import fs from 'fs'; // Error: Prefer 'node:fs' over 'fs'
450
+ import path from 'path'; // Error: Prefer 'node:path' over 'path'
451
+ import { createServer } from 'http'; // Error: Prefer 'node:http' over 'http'
452
+ ```
453
+
454
+ ---
455
+
456
+ #### `eqeqeq: ["error", "smart"]` -- Strict Equality with Exceptions
457
+
458
+ ```typescript
459
+ // PASSES
460
+ if (user.role === 'admin') { /* ... */ }
461
+ if (count !== 0) { /* ... */ }
462
+ if (value == null) { /* ... */ } // 'smart' mode: allows == null (checks null AND undefined)
463
+ if (typeof x === 'string') { /* ... */ }
464
+
465
+ // FAILS
466
+ if (user.role == 'admin') { /* ... */ } // Error: Expected '===' but found '=='
467
+ if (count != 0) { /* ... */ } // Error: Expected '!==' but found '!='
468
+ ```
469
+
470
+ ---
471
+
472
+ ### Flat Config Format (ESLint v9+)
473
+
474
+ ESLint v9 introduced the **flat config** format, replacing the legacy `.eslintrc` approach. Understanding the difference is important when working with `@venizia/dev-configs`:
475
+
476
+ **Flat config (ESLint v9+ -- what we use):**
477
+
478
+ ```javascript
479
+ // eslint.config.mjs -- a simple array of config objects
480
+ export default [
481
+ {
482
+ // Config object 1: applies to all files
483
+ rules: { 'no-console': 'warn' },
484
+ },
485
+ {
486
+ // Config object 2: applies only to test files
487
+ files: ['**/*.test.ts'],
488
+ rules: { 'no-console': 'off' },
489
+ },
490
+ {
491
+ // Config object 3: ignore patterns
492
+ ignores: ['dist/**'],
493
+ },
494
+ ];
495
+ ```
496
+
497
+ Key differences from the legacy format:
498
+
499
+ | Aspect | Legacy `.eslintrc` | Flat Config |
500
+ | :----- | :----------------- | :---------- |
501
+ | File name | `.eslintrc.json`, `.eslintrc.js` | `eslint.config.mjs` |
502
+ | Structure | Single object with `extends`, `overrides` | Array of config objects |
503
+ | Plugin loading | `plugins: ['unicorn']` (string name) | `plugins: { unicorn: importedPlugin }` (object reference) |
504
+ | Inheritance | `extends: ['config-name']` | `...spread` array |
505
+ | File targeting | `overrides: [{ files, rules }]` | Each array element can have `files` |
506
+ | Ignore patterns | `.eslintignore` file | `ignores` key in a config object |
507
+
508
+ **Why flat config matters for `@venizia/dev-configs`:** The exported `eslintConfigs` is already a `Linter.Config[]` array. To extend it, you spread it into your own array and append additional config objects. No `extends` keyword, no magic resolution -- just arrays and objects.
509
+
510
+ ---
511
+
512
+ ### Adding Project-Specific Overrides
513
+
514
+ Since the config is a standard flat config array, you can append additional config objects after spreading:
515
+
516
+ ```javascript
517
+ // eslint.config.mjs
518
+ import { eslintConfigs } from '@venizia/dev-configs';
519
+
520
+ export default [
521
+ ...eslintConfigs,
522
+ {
523
+ rules: {
524
+ // Stricter rules for this specific package
525
+ 'no-console': 'error',
526
+ '@typescript-eslint/no-explicit-any': 'error',
527
+ },
528
+ },
529
+ {
530
+ // Package-specific ignore patterns
531
+ ignores: ['generated/**', 'migrations/**'],
532
+ },
533
+ ];
534
+ ```
535
+
536
+ ---
537
+
538
+ ### Adding File-Specific Overrides
539
+
540
+ You can target specific file patterns with different rules:
541
+
542
+ ```javascript
543
+ // eslint.config.mjs
544
+ import { eslintConfigs } from '@venizia/dev-configs';
545
+
546
+ export default [
547
+ ...eslintConfigs,
548
+
549
+ // Relax rules for test files
550
+ {
551
+ files: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**/*.ts'],
552
+ rules: {
553
+ '@typescript-eslint/no-unused-vars': 'off',
554
+ '@typescript-eslint/no-floating-promises': 'off',
555
+ 'no-unused-expressions': 'off',
556
+ },
557
+ },
558
+
559
+ // Stricter rules for controller files
560
+ {
561
+ files: ['**/controllers/**/*.ts'],
562
+ rules: {
563
+ '@typescript-eslint/no-explicit-any': 'warn',
564
+ },
565
+ },
566
+
567
+ // Allow require() in config files
568
+ {
569
+ files: ['*.config.mjs', '*.config.js'],
570
+ rules: {
571
+ '@typescript-eslint/no-require-imports': 'off',
572
+ },
573
+ },
574
+ ];
575
+ ```
576
+
577
+ ---
578
+
579
+ ## Prettier Configuration
580
+
581
+ All formatting settings are exported as a `Config` object from Prettier's type definitions.
582
+
583
+ ### Source
584
+
585
+ ```typescript
586
+ import type { Config } from 'prettier';
587
+
588
+ export const prettierConfigs: Config = {
589
+ bracketSpacing: true,
590
+ singleQuote: true,
591
+ printWidth: 100,
592
+ tabWidth: 2,
593
+ trailingComma: 'all',
594
+ arrowParens: 'avoid',
595
+ semi: true,
596
+ };
597
+ ```
598
+
599
+ ---
600
+
601
+ ### Settings with Before/After Examples
602
+
603
+ #### `printWidth: 100` -- Maximum Line Length
604
+
605
+ Wider than the 80-character default. This reduces unnecessary line breaks in TypeScript generics, decorator signatures, and long import paths.
606
+
607
+ ```typescript
608
+ // With printWidth: 80 (default) -- forces wrapping
609
+ const result = await this.userRepository.find({
610
+ filter: {
611
+ where: { isActive: true },
612
+ },
613
+ });
614
+
615
+ // With printWidth: 100 (our setting) -- fits on fewer lines
616
+ const result = await this.userRepository.find({ filter: { where: { isActive: true } } });
617
+ ```
618
+
619
+ ```typescript
620
+ // With printWidth: 80 -- long generic signatures break awkwardly
621
+ export class DefaultCRUDRepository<
622
+ T extends Record<string, unknown>,
623
+ > extends PersistableRepository<T> {
624
+
625
+ // With printWidth: 100 -- fits naturally
626
+ export class DefaultCRUDRepository<T extends Record<string, unknown>> extends PersistableRepository<T> {
627
+ ```
628
+
629
+ ---
630
+
631
+ #### `tabWidth: 2` -- Indentation
632
+
633
+ Two spaces per indentation level. Consistent with most Node.js/TypeScript projects.
634
+
635
+ ```typescript
636
+ // tabWidth: 2
637
+ class UserController extends BaseController {
638
+ constructor(
639
+ @inject({ key: 'services.UserService' }) private userService: UserService,
640
+ ) {
641
+ super();
642
+ }
643
+ }
644
+ ```
645
+
646
+ ```typescript
647
+ // tabWidth: 4 (NOT our setting)
648
+ class UserController extends BaseController {
649
+ constructor(
650
+ @inject({ key: 'services.UserService' }) private userService: UserService,
651
+ ) {
652
+ super();
653
+ }
654
+ }
655
+ ```
656
+
657
+ ---
658
+
659
+ #### `singleQuote: true` -- Quote Style
660
+
661
+ Single quotes for string literals. Double quotes are still used in JSX attributes and when a string contains a single quote.
662
+
663
+ ```typescript
664
+ // singleQuote: true (our setting)
665
+ import { controller } from '@venizia/ignis';
666
+ const name = 'hello';
667
+ const message = "it's a test"; // Double quotes when string contains single quote
668
+
669
+ // singleQuote: false (NOT our setting)
670
+ import { controller } from "@venizia/ignis";
671
+ const name = "hello";
672
+ ```
673
+
674
+ ---
675
+
676
+ #### `semi: true` -- Semicolons
677
+
678
+ Always include semicolons at the end of statements. Prevents ASI (Automatic Semicolon Insertion) edge cases.
679
+
680
+ ```typescript
681
+ // semi: true (our setting)
682
+ const port = 3000;
683
+ const host = 'localhost';
684
+ app.start({ port, host });
685
+
686
+ // semi: false (NOT our setting)
687
+ const port = 3000
688
+ const host = 'localhost'
689
+ app.start({ port, host })
690
+ ```
691
+
692
+ ---
693
+
694
+ #### `trailingComma: "all"` -- Trailing Commas
695
+
696
+ Trailing commas in objects, arrays, function parameters, and type parameters wherever valid in ES5+. This produces cleaner git diffs because adding a new item only changes one line instead of two.
697
+
698
+ ```typescript
699
+ // trailingComma: "all" (our setting)
700
+ const config = {
701
+ host: 'localhost',
702
+ port: 3000,
703
+ debug: true, // <-- trailing comma
704
+ };
705
+
706
+ function createUser(
707
+ name: string,
708
+ email: string,
709
+ role: string, // <-- trailing comma
710
+ ) {
711
+ // ...
712
+ }
713
+
714
+ const items = [
715
+ 'controllers',
716
+ 'services',
717
+ 'repositories', // <-- trailing comma
718
+ ];
719
+ ```
720
+
721
+ ```typescript
722
+ // trailingComma: "none" (NOT our setting)
723
+ const config = {
724
+ host: 'localhost',
725
+ port: 3000,
726
+ debug: true // no trailing comma -- adding a new property changes this line too
727
+ };
728
+ ```
729
+
730
+ **Git diff advantage:**
731
+
732
+ ```diff
733
+ # With trailing commas -- adding a property is a 1-line diff
734
+ const config = {
735
+ host: 'localhost',
736
+ port: 3000,
737
+ debug: true,
738
+ + verbose: false,
739
+ };
740
+
741
+ # Without trailing commas -- adding a property is a 2-line diff
742
+ const config = {
743
+ host: 'localhost',
744
+ port: 3000,
745
+ - debug: true
746
+ + debug: true,
747
+ + verbose: false
748
+ };
749
+ ```
750
+
751
+ ---
752
+
753
+ #### `arrowParens: "avoid"` -- Arrow Function Parentheses
754
+
755
+ Omit parentheses around a sole arrow function parameter when possible.
756
+
757
+ ```typescript
758
+ // arrowParens: "avoid" (our setting)
759
+ const double = x => x * 2;
760
+ const greet = name => `Hello, ${name}`;
761
+ items.map(item => item.id);
762
+ items.filter(item => item.isActive);
763
+
764
+ // Multiple parameters always need parens (regardless of setting)
765
+ const add = (a, b) => a + b;
766
+
767
+ // Type annotations always need parens (regardless of setting)
768
+ const parse = (input: string) => JSON.parse(input);
769
+ ```
770
+
771
+ ```typescript
772
+ // arrowParens: "always" (NOT our setting)
773
+ const double = (x) => x * 2;
774
+ const greet = (name) => `Hello, ${name}`;
775
+ items.map((item) => item.id);
776
+ items.filter((item) => item.isActive);
777
+ ```
778
+
779
+ ---
780
+
781
+ #### `bracketSpacing: true` -- Object Literal Spacing
782
+
783
+ Spaces inside object literal braces.
784
+
785
+ ```typescript
786
+ // bracketSpacing: true (our setting)
787
+ const point = { x: 10, y: 20 };
788
+ const { name, age } = user;
789
+ import { controller, inject } from '@venizia/ignis';
790
+
791
+ // bracketSpacing: false (NOT our setting)
792
+ const point = {x: 10, y: 20};
793
+ const {name, age} = user;
794
+ import {controller, inject} from '@venizia/ignis';
795
+ ```
796
+
797
+ ---
798
+
799
+ ### Overriding Prettier Settings
800
+
801
+ Import and spread to override specific settings:
802
+
803
+ ```javascript
804
+ // .prettierrc.mjs
805
+ import { prettierConfigs } from '@venizia/dev-configs';
806
+
807
+ export default {
808
+ ...prettierConfigs,
809
+ printWidth: 120, // Wider lines for this project
810
+ arrowParens: 'always', // Always wrap arrow params
811
+ };
812
+ ```
813
+
814
+ ---
815
+
816
+ ### .prettierignore Patterns
817
+
818
+ Prettier does not have a config for ignore patterns in the shared config object. Create a `.prettierignore` file in your package root:
819
+
820
+ ```
821
+ # Build output
822
+ dist/
823
+ build/
824
+ coverage/
825
+
826
+ # Dependencies
827
+ node_modules/
828
+
829
+ # Generated files
830
+ *.tsbuildinfo
831
+ *.generated.ts
832
+
833
+ # Lock files
834
+ bun.lockb
835
+
836
+ # Assets
837
+ *.min.js
838
+ *.min.css
839
+ ```
840
+
841
+ ---
842
+
843
+ ## TypeScript Configuration
844
+
845
+ Two TypeScript config files are provided via package exports. They are consumed through the `"extends"` field in `tsconfig.json`.
846
+
847
+ ### tsconfig.base.json
848
+
849
+ The comprehensive base configuration containing all compiler options. Located at `tsconfig/tsconfig.base.json` and exported as `@venizia/dev-configs/tsconfig.base.json`.
850
+
851
+ ```json
852
+ {
853
+ "compilerOptions": {
854
+ "target": "ES2022",
855
+ "lib": ["ES2022"],
856
+ "experimentalDecorators": true,
857
+ "emitDecoratorMetadata": true,
858
+ "useDefineForClassFields": false,
859
+
860
+ "module": "Node16",
861
+ "moduleResolution": "node16",
862
+ "resolveJsonModule": true,
863
+ "allowSyntheticDefaultImports": true,
864
+ "esModuleInterop": true,
865
+ "isolatedModules": false,
866
+ "forceConsistentCasingInFileNames": true,
867
+
868
+ "declaration": true,
869
+ "declarationMap": true,
870
+ "sourceMap": true,
871
+ "removeComments": false,
872
+ "importHelpers": false,
873
+ "downlevelIteration": true,
874
+ "preserveConstEnums": true,
875
+
876
+ "strict": true,
877
+ "noImplicitAny": false,
878
+ "strictNullChecks": true,
879
+ "strictFunctionTypes": true,
880
+ "strictBindCallApply": true,
881
+ "strictPropertyInitialization": false,
882
+ "noImplicitThis": true,
883
+ "useUnknownInCatchVariables": false,
884
+ "alwaysStrict": true,
885
+
886
+ "noUnusedLocals": true,
887
+ "noUnusedParameters": true,
888
+ "noImplicitReturns": false,
889
+ "noFallthroughCasesInSwitch": true,
890
+ "noUncheckedIndexedAccess": false,
891
+ "noImplicitOverride": true,
892
+ "allowUnusedLabels": false,
893
+ "allowUnreachableCode": false,
894
+
895
+ "incremental": true,
896
+ "skipLibCheck": true,
897
+ "noEmitOnError": true
898
+ },
899
+ "exclude": [
900
+ "node_modules",
901
+ "dist",
902
+ "build",
903
+ "coverage",
904
+ "**/*.spec.ts",
905
+ "**/*.test.ts",
906
+ "**/__tests__/**"
907
+ ]
908
+ }
909
+ ```
910
+
911
+ ---
912
+
913
+ #### Compiler Options Reference
914
+
915
+ **Language and Environment**
916
+
917
+ | Option | Value | What It Does | Why This Value | What Breaks If Changed |
918
+ | :----- | :---- | :----------- | :------------- | :--------------------- |
919
+ | `target` | `ES2022` | Sets the JavaScript version for emit output. | ES2022 provides native `class`, `async/await`, `top-level await`, private class fields, and `Array.at()` without downleveling. Bun and modern Node.js fully support ES2022. | Lowering to ES5/ES6 introduces unnecessary transpilation overhead. Raising to ESNext may use features not yet stable in all runtimes. |
920
+ | `lib` | `["ES2022"]` | Includes type definitions for ES2022 standard library APIs (`structuredClone`, `Array.at()`, `Object.hasOwn()`, `Error.cause`, etc.). | Matches the `target` to ensure type-checked APIs match the emitted code. | Removing this causes TypeScript to not recognize modern APIs like `structuredClone`. Adding `"DOM"` would introduce browser-specific types (e.g., `window`, `document`) that do not exist in Node.js/Bun. |
921
+ | `experimentalDecorators` | `true` | **CRITICAL.** Enables the legacy/experimental decorator syntax (`@decorator`) used by Ignis for DI, controllers, models, and repositories. | See [Critical Flags](#critical-flags-for-decorator-based-di). | All Ignis decorators fail to compile. |
922
+ | `emitDecoratorMetadata` | `true` | **CRITICAL.** Emits design-time type metadata via `Reflect.metadata()`. | See [Critical Flags](#critical-flags-for-decorator-based-di). | DI container cannot resolve constructor parameter types. |
923
+ | `useDefineForClassFields` | `false` | **CRITICAL.** Uses assignment semantics for class fields instead of `Object.defineProperty()`. | See [Critical Flags](#critical-flags-for-decorator-based-di). | Decorator-based property injection is overwritten by defineProperty. |
924
+
925
+ **Modules**
926
+
927
+ | Option | Value | What It Does | Why This Value | What Breaks If Changed |
928
+ | :----- | :---- | :----------- | :------------- | :--------------------- |
929
+ | `module` | `Node16` | Sets the module system for emitted code. | Node16 provides full ESM and CJS interop with `package.json` `"type"` field support. Matches the target Node.js/Bun environment. | Changing to `CommonJS` loses ESM support. Changing to `ESNext` loses CJS interop. |
930
+ | `moduleResolution` | `node16` | Controls how TypeScript resolves `import` paths to files. | Supports `package.json` `"exports"` field, conditional exports, and the `#imports` private imports pattern. | Changing to `node` (legacy) ignores `"exports"` field in `package.json`, causing resolution failures for modern packages. |
931
+ | `resolveJsonModule` | `true` | Allows importing `.json` files with full type inference. | Used for importing `package.json` and configuration files. | `import pkg from './package.json'` stops working. |
932
+ | `allowSyntheticDefaultImports` | `true` | Allows `import foo from 'module'` even when the module has no explicit `default` export. | Many CJS packages (e.g., `express`, `lodash`) do not have default exports but are commonly imported this way. | `import _ from 'lodash/get'` and similar patterns produce type errors. |
933
+ | `esModuleInterop` | `true` | Emits `__importDefault` and `__importStar` helpers for correct CJS/ESM interop at runtime. | Without this, `import` of CJS modules may resolve to `{ default: module }` instead of `module` directly. | Default imports from CJS modules break at runtime (the module object itself becomes `{ default: ... }`). |
934
+ | `isolatedModules` | `false` | When `true`, restricts features that require cross-file analysis (const enums, namespace merging). | Ignis uses `const enum` for binding scopes and namespace merging for type augmentation. These features require cross-file compilation. | Enabling this would disallow `const enum` usage in `@venizia/ignis-inversion` and related packages. |
935
+ | `forceConsistentCasingInFileNames` | `true` | Errors on imports that do not match the file system's actual casing. | Prevents cross-platform bugs where `import './UserService'` works on macOS (case-insensitive) but fails on Linux (case-sensitive). | Disabling this allows case mismatches that cause runtime `MODULE_NOT_FOUND` errors on Linux CI servers. |
936
+
937
+ **Emit**
938
+
939
+ | Option | Value | What It Does | Why This Value | What Breaks If Changed |
940
+ | :----- | :---- | :----------- | :------------- | :--------------------- |
941
+ | `declaration` | `true` | Generates `.d.ts` type declaration files alongside JavaScript output. | Required for packages consumed as dependencies so that consumers get type information. | Downstream packages lose all type information and IntelliSense. |
942
+ | `declarationMap` | `true` | Generates `.d.ts.map` files mapping declarations back to original `.ts` source. | Enables "Go to Definition" in IDEs to navigate to original TypeScript source instead of the `.d.ts` file. | IDE "Go to Definition" lands on `.d.ts` stubs instead of real source code. |
943
+ | `sourceMap` | `true` | Generates `.js.map` files for debugging. | Allows debuggers and stack traces to show original TypeScript source locations instead of compiled JavaScript. | Stack traces and breakpoints reference compiled JS line numbers, making debugging harder. |
944
+ | `removeComments` | `false` | Preserves comments in output files. | Keeps JSDoc comments in `.d.ts` files, which IDEs display as hover documentation. | Hover documentation in IDEs for framework APIs disappears. |
945
+ | `importHelpers` | `false` | When `true`, imports shared helper functions from `tslib` instead of inlining them per file. | Keeps each file self-contained without requiring `tslib` as a dependency. With `target: ES2022`, very few helpers are needed anyway. | Enabling this requires adding `tslib` to dependencies. |
946
+ | `downlevelIteration` | `true` | Full support for iteration protocols on `for...of`, spread (`...`), and destructuring when targeting older runtimes. | Ensures `for (const x of generator())` works correctly with custom iterables. With ES2022 target, this has minimal runtime cost. | Spread and destructuring on non-Array iterables (Map, Set, generators) may produce incorrect code. |
947
+ | `preserveConstEnums` | `true` | Keeps `const enum` declarations in output instead of inlining values. | Required for `.d.ts` file compatibility -- inlined enums cannot be re-exported in declaration files. | Consumers of the package cannot reference `const enum` values from their own code. |
948
+ | `noEmitOnError` | `true` | Prevents emitting output files if any type errors exist. | Prevents shipping builds with type errors. A broken build should fail loudly. | Build output may contain code with type errors, leading to runtime failures that should have been caught at compile time. |
949
+
950
+ **Type Checking -- Strict Mode (Balanced for Production)**
951
+
952
+ | Option | Value | What It Does | Why This Value | What Breaks If Changed |
953
+ | :----- | :---- | :----------- | :------------- | :--------------------- |
954
+ | `strict` | `true` | Enables all strict type-checking options as a baseline. Individual overrides below relax specific checks. | Maximum type safety as the default, with targeted relaxations for framework patterns. | Disabling this removes all strict checks at once, dramatically reducing type safety. |
955
+ | `noImplicitAny` | `false` | Allows variables and parameters to implicitly have the `any` type when TypeScript cannot infer a more specific type. | Pragmatic choice. Framework code dealing with decorator metadata (`Reflect.getMetadata`) and dynamic DI patterns frequently encounters values with no inferrable type. | Enabling this would require explicit type annotations on every decorator metadata access and dynamic container resolution, adding significant boilerplate to framework internals. |
956
+ | `strictNullChecks` | `true` | Makes `null` and `undefined` their own distinct types rather than assignable to everything. | Prevents the most common class of runtime errors (`Cannot read property of null/undefined`). | Disabling this allows `null` to be assigned anywhere, removing compile-time null safety. |
957
+ | `strictFunctionTypes` | `true` | Enforces contravariant function parameter type checking. | Catches type-unsafe function assignments like passing `(animal: Dog) => void` where `(animal: Animal) => void` is expected. | Disabling this allows unsound function type assignments that can cause runtime type errors. |
958
+ | `strictBindCallApply` | `true` | Enables strict type checking on `bind`, `call`, and `apply` methods. | Catches incorrect argument types when using `.call()` and `.apply()`. | Disabling this allows `fn.call(null, wrongArgType)` to pass type checking. |
959
+ | `strictPropertyInitialization` | `false` | When `true`, requires all class properties to be initialized in the constructor or with a default value. | **Disabled for DI.** Properties injected via `@inject` decorators are set by the IoC container, not in the constructor body. Enabling this would require `!` (definite assignment assertion) on every injected property. | Enabling this would require adding `!` to every `@inject`-decorated property: `@inject({ key }) private service!: Service`. |
960
+ | `noImplicitThis` | `true` | Errors when `this` has an implicit `any` type. | Catches bugs where `this` is used in a context where its type is ambiguous (e.g., standalone functions passed as callbacks). | Disabling this allows `this` to silently become `any` in ambiguous contexts. |
961
+ | `useUnknownInCatchVariables` | `false` | When `true`, types `catch` clause variables as `unknown` instead of `any`. | Simplifies error handling. Framework code frequently catches errors and accesses `.message`, `.statusCode`, etc. without narrowing. | Enabling this would require `if (error instanceof Error)` guards in every `catch` block throughout the codebase. |
962
+ | `alwaysStrict` | `true` | Emits `"use strict"` directive in every output file. | Ensures all generated JavaScript runs in strict mode, which prevents common JavaScript pitfalls. | Disabling this allows sloppy mode, which permits silent errors and behaviors like implicit global variable creation. |
963
+
964
+ **Additional Checks**
965
+
966
+ | Option | Value | What It Does | Why This Value | What Breaks If Changed |
967
+ | :----- | :---- | :----------- | :------------- | :--------------------- |
968
+ | `noUnusedLocals` | `true` | Errors on declared but unused local variables. | Keeps code clean and prevents dead code accumulation. | Disabling this allows unused variables to accumulate silently. |
969
+ | `noUnusedParameters` | `true` | Errors on declared but unused function parameters. Prefix with `_` to suppress. | Catches dead parameters that may indicate incomplete refactoring. The `_` prefix convention explicitly marks intentionally unused params. | Disabling this allows function signatures to diverge from implementation. |
970
+ | `noImplicitReturns` | `false` | When `true`, errors when not all code paths return a value. | Disabled because many methods intentionally return `void` from some branches (e.g., early return guards). | Enabling this would require explicit `return undefined` in many guard-clause patterns. |
971
+ | `noFallthroughCasesInSwitch` | `true` | Errors on `switch` cases that fall through without `break` or `return`. | Prevents accidental fallthrough bugs. Intentional fallthrough must use `// falls through` comment or empty case body. | Disabling this allows silent fallthrough, a common source of bugs. |
972
+ | `noUncheckedIndexedAccess` | `false` | When `true`, adds `undefined` to the type of index signature access (`obj[key]` becomes `T | undefined`). | Disabled because framework-level dynamic access patterns (binding maps, metadata registries) would require excessive null checking. | Enabling this would require null guards on every `container.bindings.get(key)`, `metadata[prop]`, etc. |
973
+ | `noImplicitOverride` | `true` | Requires the `override` keyword when overriding base class methods. | Makes the inheritance chain explicit. Prevents accidentally overriding a base class method and catches renames in base classes. | Disabling this allows silent overrides that may break when the base class changes. |
974
+ | `allowUnusedLabels` | `false` | Errors on declared but unused labels. | Unused labels are almost always mistakes. | Disabling this allows dead labels to accumulate. |
975
+ | `allowUnreachableCode` | `false` | Errors on unreachable code (code after `return`, `throw`, etc.). | Dead code indicates logic errors or incomplete refactoring. | Disabling this allows dead code to accumulate silently. |
976
+
977
+ **Performance**
978
+
979
+ | Option | Value | What It Does | Why This Value | What Breaks If Changed |
980
+ | :----- | :---- | :----------- | :------------- | :--------------------- |
981
+ | `incremental` | `true` | Stores build state in `.tsbuildinfo` files and only recompiles changed files. | Significantly speeds up rebuilds in the monorepo. First build is the same speed; subsequent builds only process changed files. | Disabling this forces full recompilation on every build, which is noticeably slower in a 50k+ LOC monorepo. |
982
+ | `skipLibCheck` | `true` | Skips type checking of `.d.ts` files from `node_modules`. | Dramatically reduces build times. Type errors in third-party `.d.ts` files are not actionable anyway. | Disabling this adds seconds to every build for no practical benefit, and may surface false positives from poorly-typed third-party packages. |
983
+
984
+ **Default Excludes**
985
+
986
+ The base config excludes the following from compilation:
987
+
988
+ - `node_modules` -- third-party dependencies
989
+ - `dist` -- build output directory
990
+ - `build` -- alternative build output directory
991
+ - `coverage` -- test coverage reports
992
+ - `**/*.spec.ts`, `**/*.test.ts`, `**/__tests__/**` -- test files (compiled separately by test runners)
993
+
994
+ ---
995
+
996
+ ### tsconfig.common.json
997
+
998
+ The consumer-facing configuration that most packages should extend. Located at `tsconfig/tsconfig.common.json` and exported as `@venizia/dev-configs/tsconfig.common.json`.
999
+
1000
+ ```json
1001
+ {
1002
+ "extends": "./tsconfig.base.json",
1003
+ "compilerOptions": {
1004
+ "target": "ES2022",
1005
+ "module": "nodenext",
1006
+ "moduleResolution": "nodenext"
1007
+ },
1008
+ "ts-node": {
1009
+ "transpileOnly": true
1010
+ }
1011
+ }
1012
+ ```
1013
+
1014
+ This config extends `tsconfig.base.json` and makes two adjustments:
1015
+
1016
+ | Override | Base Value | Common Value | Rationale |
1017
+ | :------- | :--------- | :----------- | :-------- |
1018
+ | `module` | `Node16` | `nodenext` | Uses the latest Node.js module resolution. `nodenext` tracks the most current Node.js behavior and will adopt future changes automatically. While `Node16` is frozen to Node 16 semantics, `nodenext` evolves with the Node.js release cycle. |
1019
+ | `moduleResolution` | `node16` | `nodenext` | Matching module resolution strategy for `nodenext` module mode. Must always match the `module` setting. |
1020
+
1021
+ **The `ts-node` section:**
1022
+
1023
+ ```json
1024
+ {
1025
+ "ts-node": {
1026
+ "transpileOnly": true
1027
+ }
1028
+ }
1029
+ ```
1030
+
1031
+ This configures `ts-node` (when used during development) to run in `transpileOnly` mode:
1032
+
1033
+ - **What it does:** Skips type checking at runtime, only transpiling TypeScript to JavaScript.
1034
+ - **Why:** Type checking at runtime via `ts-node` is slow and redundant -- the IDE and `tsc` build step already catch type errors. `transpileOnly` makes `ts-node` execution near-instant.
1035
+ - **When it matters:** When running scripts directly with `ts-node`, or when tools like Drizzle Kit load TypeScript config files (e.g., `drizzle-kit push --config=src/migration.ts`).
1036
+
1037
+ **Use `tsconfig.common.json` unless you have a specific reason to extend `tsconfig.base.json` directly.**
1038
+
1039
+ ---
1040
+
1041
+ ### Critical Flags for Decorator-Based DI
1042
+
1043
+ Three TypeScript compiler options are **absolutely essential** for the Ignis decorator-based dependency injection system to function. Changing any of these will break the entire framework.
1044
+
1045
+ #### `experimentalDecorators: true`
1046
+
1047
+ Enables the TypeScript experimental decorator syntax used throughout Ignis:
1048
+
1049
+ ```typescript
1050
+ @controller({ path: '/users' })
1051
+ export class UserController extends BaseController {
1052
+ constructor(
1053
+ @inject({ key: 'services.UserService' }) private userService: UserService,
1054
+ ) {
1055
+ super();
1056
+ }
1057
+ }
1058
+ ```
1059
+
1060
+ Without this flag, TypeScript will emit a compilation error on every decorator:
1061
+
1062
+ ```
1063
+ error TS1219: Experimental support for decorators is a feature that is subject
1064
+ to change in a future release. Set the 'experimentalDecorators' option in your
1065
+ 'tsconfig' or 'jsconfig' to remove this error.
1066
+ ```
1067
+
1068
+ The following decorators all require this flag: `@controller`, `@inject`, `@injectable`, `@repository`, `@datasource`, `@service`, `@model`, `@get`, `@post`, `@put`, `@patch`, `@del`, `@api`.
1069
+
1070
+ **Note on TC39 decorators:** TypeScript 5.0+ supports the new TC39 stage 3 decorator proposal (without `experimentalDecorators`). However, the TC39 decorators do NOT support `emitDecoratorMetadata`, which Ignis relies on for automatic DI resolution. Ignis intentionally uses the legacy/experimental decorator syntax for this reason.
1071
+
1072
+ ---
1073
+
1074
+ #### `emitDecoratorMetadata: true`
1075
+
1076
+ Instructs TypeScript to emit design-time type information as metadata using `Reflect.metadata()`. This is what allows the IoC container (`@venizia/ignis-inversion`) to inspect constructor parameter types at runtime and automatically resolve dependencies.
1077
+
1078
+ When this flag is enabled, TypeScript emits additional metadata for decorated classes:
1079
+
1080
+ ```typescript
1081
+ // Source code:
1082
+ @injectable()
1083
+ class UserService {
1084
+ constructor(
1085
+ @inject({ key: 'repositories.UserRepository' }) private repo: UserRepository,
1086
+ ) {}
1087
+ }
1088
+
1089
+ // TypeScript emits (simplified):
1090
+ UserService = __decorate([
1091
+ injectable(),
1092
+ __metadata("design:paramtypes", [UserRepository]) // <-- This is emitDecoratorMetadata
1093
+ ], UserService);
1094
+ ```
1095
+
1096
+ The `design:paramtypes` metadata tells the DI container: "The first constructor parameter should be of type `UserRepository`." The container uses this to:
1097
+
1098
+ 1. Look up the binding for `UserRepository` in its registry
1099
+ 2. Instantiate or retrieve the cached `UserRepository` instance
1100
+ 3. Pass it as the first constructor argument
1101
+
1102
+ Without this flag:
1103
+
1104
+ - `Reflect.getMetadata('design:paramtypes', UserService)` returns `undefined`
1105
+ - The DI container cannot determine what types the constructor expects
1106
+ - All automatic constructor injection silently fails
1107
+ - `@inject` decorators lose their type-resolution capability
1108
+
1109
+ ---
1110
+
1111
+ #### `useDefineForClassFields: false`
1112
+
1113
+ Controls how TypeScript initializes class fields. This flag has a subtle but critical interaction with decorators.
1114
+
1115
+ **The problem with `useDefineForClassFields: true` (default for ES2022+ targets):**
1116
+
1117
+ When set to `true`, TypeScript uses `Object.defineProperty()` semantics for class fields. The `defineProperty` call runs **after** the constructor, which means it runs **after** decorators have already set property values. The result: `defineProperty` overwrites the decorator-injected value with `undefined`.
1118
+
1119
+ ```typescript
1120
+ class UserController extends BaseController {
1121
+ @inject({ key: 'services.UserService' })
1122
+ private userService: UserService; // Declared but not assigned
1123
+
1124
+ handleRequest() {
1125
+ this.userService.findAll(); // TypeError: Cannot read property 'findAll' of undefined
1126
+ }
1127
+ }
1128
+ ```
1129
+
1130
+ **What happens at runtime with `useDefineForClassFields: true`:**
1131
+
1132
+ ```javascript
1133
+ // Step 1: Decorator runs, sets this.userService = <resolved UserService instance>
1134
+ // Step 2: Object.defineProperty(this, 'userService', { value: undefined, ... })
1135
+ // Result: userService is undefined, decorator's work is lost
1136
+ ```
1137
+
1138
+ **What happens at runtime with `useDefineForClassFields: false`:**
1139
+
1140
+ ```javascript
1141
+ // Step 1: Decorator runs, sets this.userService = <resolved UserService instance>
1142
+ // Step 2: this.userService = value (assignment semantics, in constructor body)
1143
+ // Result: userService retains the decorator-injected value
1144
+ ```
1145
+
1146
+ **Related flag:** `strictPropertyInitialization` is also set to `false` because DI-injected properties are initialized by the container, not in the constructor body, and would otherwise trigger errors like:
1147
+
1148
+ ```
1149
+ error TS2564: Property 'userService' has no initializer and is not definitely
1150
+ assigned in the constructor.
1151
+ ```
1152
+
1153
+ ---
1154
+
1155
+ ## Consumption Pattern
1156
+
1157
+ Every package in the Ignis monorepo follows an identical pattern to consume these configs. Here is the complete setup for a new package:
1158
+
1159
+ ### 1. ESLint (`eslint.config.mjs`)
1160
+
1161
+ ```javascript
1162
+ import { eslintConfigs } from '@venizia/dev-configs';
1163
+
1164
+ export default eslintConfigs;
1165
+ ```
1166
+
1167
+ ### 2. Prettier (`.prettierrc.mjs`)
1168
+
1169
+ ```javascript
1170
+ import { prettierConfigs } from '@venizia/dev-configs';
1171
+
1172
+ export default prettierConfigs;
1173
+ ```
1174
+
1175
+ ### 3. TypeScript (`tsconfig.json`)
1176
+
1177
+ ```json
1178
+ {
1179
+ "extends": "@venizia/dev-configs/tsconfig.common.json",
1180
+ "compilerOptions": {
1181
+ "outDir": "dist",
1182
+ "rootDir": "src"
1183
+ },
1184
+ "include": ["src"],
1185
+ "exclude": ["node_modules", "dist"]
1186
+ }
1187
+ ```
1188
+
1189
+ Packages that need path aliases or additional settings simply add them under `compilerOptions`:
1190
+
1191
+ ```json
1192
+ {
1193
+ "extends": "@venizia/dev-configs/tsconfig.common.json",
1194
+ "compilerOptions": {
1195
+ "outDir": "dist",
1196
+ "rootDir": "src",
1197
+ "baseUrl": "src",
1198
+ "paths": {
1199
+ "@/*": ["./*"]
1200
+ }
1201
+ }
1202
+ }
1203
+ ```
1204
+
1205
+ ---
1206
+
1207
+ ## Real-World Consumer Examples
1208
+
1209
+ The following are actual configurations from packages in the Ignis monorepo.
1210
+
1211
+ ### packages/inversion (Standard Package)
1212
+
1213
+ The simplest and most common pattern -- direct consumption with path aliases:
1214
+
1215
+ **`eslint.config.mjs`:**
1216
+
1217
+ ```javascript
1218
+ import { eslintConfigs } from '@venizia/dev-configs';
1219
+
1220
+ export default eslintConfigs;
1221
+ ```
1222
+
1223
+ **`.prettierrc.mjs`:**
1224
+
1225
+ ```javascript
1226
+ import { prettierConfigs } from '@venizia/dev-configs';
1227
+
1228
+ export default prettierConfigs;
1229
+ ```
1230
+
1231
+ **`tsconfig.json`:**
1232
+
1233
+ ```json
1234
+ {
1235
+ "$schema": "http://json.schemastore.org/tsconfig",
1236
+ "extends": "@venizia/dev-configs/tsconfig.common.json",
1237
+ "compilerOptions": {
1238
+ "outDir": "dist/cjs",
1239
+ "rootDir": "src",
1240
+ "baseUrl": "src",
1241
+ "paths": {
1242
+ "@/*": ["./*"]
1243
+ }
1244
+ },
1245
+ "include": ["src", "./*.config.*", ".prettierrc.*"],
1246
+ "exclude": ["node_modules", "dist", "app_data"]
1247
+ }
1248
+ ```
1249
+
1250
+ Note that `include` adds `./*.config.*` and `.prettierrc.*` so ESLint and Prettier config files are also type-checked.
1251
+
1252
+ ---
1253
+
1254
+ ### packages/core (Extended TypeScript Config)
1255
+
1256
+ The core package needs JSX support for Hono's JSX rendering, so it uses a two-level tsconfig chain:
1257
+
1258
+ **`tsconfig.core.json`** (base for core):
1259
+
1260
+ ```json
1261
+ {
1262
+ "$schema": "http://json.schemastore.org/tsconfig",
1263
+ "extends": "@venizia/dev-configs/tsconfig.common.json",
1264
+ "compilerOptions": {
1265
+ "outDir": "dist",
1266
+ "rootDir": "src",
1267
+ "baseUrl": "src",
1268
+ "paths": {
1269
+ "@/*": ["./*"]
1270
+ }
1271
+ },
1272
+ "include": ["src", "./*.config.*", ".prettierrc.*"],
1273
+ "exclude": ["node_modules", "dist", "app_data"]
1274
+ }
1275
+ ```
1276
+
1277
+ **`tsconfig.json`** (adds JSX on top):
1278
+
1279
+ ```json
1280
+ {
1281
+ "$schema": "http://json.schemastore.org/tsconfig",
1282
+ "extends": "./tsconfig.core.json",
1283
+ "compilerOptions": {
1284
+ "jsx": "react-jsx",
1285
+ "jsxImportSource": "hono/jsx"
1286
+ }
1287
+ }
1288
+ ```
1289
+
1290
+ This pattern demonstrates that you can chain `extends` references: `tsconfig.json` extends `tsconfig.core.json` which extends `@venizia/dev-configs/tsconfig.common.json`.
1291
+
1292
+ ---
1293
+
1294
+ ### examples/vert (Application-Level Config)
1295
+
1296
+ Applications use the same pattern as library packages:
1297
+
1298
+ **`tsconfig.json`:**
1299
+
1300
+ ```json
1301
+ {
1302
+ "$schema": "http://json.schemastore.org/tsconfig",
1303
+ "extends": "@venizia/dev-configs/tsconfig.common.json",
1304
+ "compilerOptions": {
1305
+ "outDir": "dist",
1306
+ "rootDir": "src",
1307
+ "baseUrl": "src",
1308
+ "paths": {
1309
+ "@/*": ["./*"]
1310
+ }
1311
+ },
1312
+ "include": ["src", "./*.config.*", ".prettierrc.*"],
1313
+ "exclude": ["node_modules", "dist", "app_data"]
1314
+ }
1315
+ ```
1316
+
1317
+ ---
1318
+
1319
+ ## Setting Up a New Package
1320
+
1321
+ Complete step-by-step guide for adding a new package to the Ignis monorepo (or any project consuming `@venizia/dev-configs`).
1322
+
1323
+ ### Step 1: Create the Package Directory
1324
+
1325
+ ```bash
1326
+ mkdir -p packages/my-package/src
1327
+ ```
1328
+
1329
+ ### Step 2: Create `package.json`
1330
+
1331
+ ```json
1332
+ {
1333
+ "name": "@venizia/ignis-my-package",
1334
+ "version": "0.0.1",
1335
+ "description": "My new Ignis package",
1336
+ "exports": {
1337
+ ".": {
1338
+ "types": "./dist/index.d.ts",
1339
+ "default": "./dist/index.js"
1340
+ }
1341
+ },
1342
+ "scripts": {
1343
+ "build": "tsc",
1344
+ "rebuild": "bun run clean && bun run build",
1345
+ "clean": "rm -rf dist *.tsbuildinfo",
1346
+ "eslint": "eslint --report-unused-disable-directives .",
1347
+ "lint": "bun run eslint && bun run prettier:cli",
1348
+ "lint:fix": "bun run eslint --fix && bun run prettier:fix",
1349
+ "prettier:cli": "prettier \"**/*.{js,ts}\" -l",
1350
+ "prettier:fix": "bun run prettier:cli --write"
1351
+ },
1352
+ "devDependencies": {
1353
+ "@venizia/dev-configs": "workspace:*",
1354
+ "eslint": "^9.36.0",
1355
+ "prettier": "^3.6.2",
1356
+ "typescript": "^5.9.3"
1357
+ }
1358
+ }
1359
+ ```
1360
+
1361
+ ### Step 3: Create `eslint.config.mjs`
1362
+
1363
+ ```javascript
1364
+ import { eslintConfigs } from '@venizia/dev-configs';
1365
+
1366
+ export default eslintConfigs;
1367
+ ```
1368
+
1369
+ ### Step 4: Create `.prettierrc.mjs`
1370
+
1371
+ ```javascript
1372
+ import { prettierConfigs } from '@venizia/dev-configs';
1373
+
1374
+ export default prettierConfigs;
1375
+ ```
1376
+
1377
+ ### Step 5: Create `tsconfig.json`
1378
+
1379
+ ```json
1380
+ {
1381
+ "$schema": "http://json.schemastore.org/tsconfig",
1382
+ "extends": "@venizia/dev-configs/tsconfig.common.json",
1383
+ "compilerOptions": {
1384
+ "outDir": "dist",
1385
+ "rootDir": "src",
1386
+ "baseUrl": "src",
1387
+ "paths": {
1388
+ "@/*": ["./*"]
1389
+ }
1390
+ },
1391
+ "include": ["src", "./*.config.*", ".prettierrc.*"],
1392
+ "exclude": ["node_modules", "dist", "app_data"]
1393
+ }
1394
+ ```
1395
+
1396
+ ### Step 6: Create the Entry Point
1397
+
1398
+ ```typescript
1399
+ // src/index.ts
1400
+ export const hello = 'world';
1401
+ ```
1402
+
1403
+ ### Step 7: Install Dependencies and Verify
1404
+
1405
+ ```bash
1406
+ # From the monorepo root
1407
+ bun install
1408
+
1409
+ # Verify TypeScript compiles
1410
+ cd packages/my-package
1411
+ tsc --noEmit
1412
+
1413
+ # Verify ESLint runs
1414
+ bun run eslint
1415
+
1416
+ # Verify Prettier checks
1417
+ bun run prettier:cli
1418
+ ```
1419
+
1420
+ ---
1421
+
1422
+ ## Extending and Overriding
1423
+
1424
+ ### ESLint
1425
+
1426
+ Spread the base configs and append your overrides:
1427
+
1428
+ ```javascript
1429
+ import { eslintConfigs } from '@venizia/dev-configs';
1430
+
1431
+ export default [
1432
+ ...eslintConfigs,
1433
+ {
1434
+ rules: {
1435
+ 'no-console': 'error',
1436
+ '@typescript-eslint/no-explicit-any': 'error',
1437
+ },
1438
+ },
1439
+ ];
1440
+ ```
1441
+
1442
+ ### Prettier
1443
+
1444
+ Spread the base config object:
1445
+
1446
+ ```javascript
1447
+ import { prettierConfigs } from '@venizia/dev-configs';
1448
+
1449
+ export default {
1450
+ ...prettierConfigs,
1451
+ printWidth: 120,
1452
+ };
1453
+ ```
1454
+
1455
+ ### TypeScript
1456
+
1457
+ Use `"extends"` and override specific `compilerOptions`:
1458
+
1459
+ ```json
1460
+ {
1461
+ "extends": "@venizia/dev-configs/tsconfig.common.json",
1462
+ "compilerOptions": {
1463
+ "jsx": "react-jsx",
1464
+ "jsxImportSource": "hono/jsx"
1465
+ }
1466
+ }
1467
+ ```
1468
+
1469
+ **Warning:** Do not override `experimentalDecorators`, `emitDecoratorMetadata`, or `useDefineForClassFields` unless you fully understand the consequences for the DI system. See [Critical Flags](#critical-flags-for-decorator-based-di).
1470
+
1471
+ ---
1472
+
1473
+ ## IDE Integration
1474
+
1475
+ ### Visual Studio Code
1476
+
1477
+ Install these extensions for full integration:
1478
+
1479
+ 1. **ESLint** (`dbaeumer.vscode-eslint`) -- Lint on save, show inline errors.
1480
+ 2. **Prettier** (`esbenp.prettier-vscode`) -- Format on save.
1481
+
1482
+ Recommended `.vscode/settings.json` for the monorepo:
1483
+
1484
+ ```json
1485
+ {
1486
+ "editor.formatOnSave": true,
1487
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
1488
+ "editor.codeActionsOnSave": {
1489
+ "source.fixAll.eslint": "explicit"
1490
+ },
1491
+ "eslint.useFlatConfig": true,
1492
+ "typescript.tsdk": "node_modules/typescript/lib",
1493
+ "typescript.enablePromptUseWorkspaceTsdk": true
1494
+ }
1495
+ ```
1496
+
1497
+ Key points:
1498
+
1499
+ - **`eslint.useFlatConfig: true`** -- Tells the ESLint extension to use the flat config format. Without this, the extension may look for `.eslintrc` files instead of `eslint.config.mjs`.
1500
+ - **`typescript.tsdk`** -- Points to the workspace's TypeScript version instead of the VS Code bundled one. Ensures the same TypeScript version is used for type checking in the IDE and during builds.
1501
+ - Prettier handles formatting; ESLint handles code quality rules. The `eslint-plugin-prettier` integration in the config stack ensures they do not conflict.
1502
+
1503
+ ---
1504
+
1505
+ ### WebStorm / IntelliJ IDEA
1506
+
1507
+ WebStorm automatically detects `eslint.config.mjs` and `.prettierrc.mjs` files. Ensure these settings are enabled:
1508
+
1509
+ 1. **Settings > Languages & Frameworks > JavaScript > Code Quality Tools > ESLint**: Select "Automatic ESLint configuration."
1510
+ 2. **Settings > Languages & Frameworks > JavaScript > Prettier**: Check "On save" and "On 'Reformat Code' action."
1511
+ 3. **Settings > Languages & Frameworks > TypeScript**: Point the TypeScript service to `node_modules/typescript/lib`.
1512
+
1513
+ ---
1514
+
1515
+ ## CI/CD Integration
1516
+
1517
+ ### Lint Check in CI
1518
+
1519
+ Add a lint step to your CI pipeline. Example using GitHub Actions:
1520
+
1521
+ ```yaml
1522
+ name: Lint
1523
+ on: [push, pull_request]
1524
+
1525
+ jobs:
1526
+ lint:
1527
+ runs-on: ubuntu-latest
1528
+ steps:
1529
+ - uses: actions/checkout@v4
1530
+ - uses: oven-sh/setup-bun@v2
1531
+ with:
1532
+ bun-version: latest
1533
+ - run: bun install --frozen-lockfile
1534
+ - run: bun run lint # Runs ESLint + Prettier check across all packages
1535
+ ```
1536
+
1537
+ ### Per-Package Lint
1538
+
1539
+ If you need to lint a specific package:
1540
+
1541
+ ```bash
1542
+ # From the monorepo root
1543
+ cd packages/core && bun run lint
1544
+
1545
+ # Or using make targets
1546
+ make lint # All packages
1547
+ make lint-all # All packages + examples
1548
+ ```
1549
+
1550
+ ### Type Check in CI
1551
+
1552
+ ```yaml
1553
+ - run: make build # tsc compilation validates types as a side effect
1554
+ ```
1555
+
1556
+ Since `noEmitOnError: true` is set, a failed type check will cause the build to fail.
1557
+
1558
+ ---
1559
+
1560
+ ## Troubleshooting
1561
+
1562
+ ### ESLint flat config not recognized
1563
+
1564
+ **Symptom:** ESLint ignores `eslint.config.mjs` and reports "no configuration found."
1565
+
1566
+ **Cause:** ESLint version is below v9.0.0, which does not support flat config as the default format.
1567
+
1568
+ **Fix:**
1569
+
1570
+ ```bash
1571
+ bun add -d eslint@^9.0.0
1572
+ ```
1573
+
1574
+ Verify your version:
1575
+
1576
+ ```bash
1577
+ bun eslint --version # Should be 9.x.x or later
1578
+ ```
1579
+
1580
+ ---
1581
+
1582
+ ### Prettier conflicts with ESLint
1583
+
1584
+ **Symptom:** ESLint and Prettier disagree on formatting, causing one to undo the other's changes.
1585
+
1586
+ **Cause:** Usually happens when ESLint formatting rules are not disabled by `eslint-plugin-prettier`.
1587
+
1588
+ **Fix:** The `@venizia/dev-configs` ESLint config already includes `eslint-plugin-prettier/recommended` (via the `@minimaltech/eslint-common` base layer), which automatically disables ESLint rules that conflict with Prettier. If you see conflicts, check that you are not adding formatting rules in your project-level overrides that conflict with Prettier settings.
1589
+
1590
+ ---
1591
+
1592
+ ### TypeScript decorator errors
1593
+
1594
+ **Symptom:**
1595
+
1596
+ ```
1597
+ error TS1219: Experimental support for decorators is a feature that is subject
1598
+ to change in a future release.
1599
+ ```
1600
+
1601
+ **Cause:** `experimentalDecorators` is not enabled, usually because `tsconfig.json` does not extend the shared config or the `extends` path is wrong.
1602
+
1603
+ **Fix:** Verify your `tsconfig.json`:
1604
+
1605
+ ```json
1606
+ {
1607
+ "extends": "@venizia/dev-configs/tsconfig.common.json"
1608
+ }
1609
+ ```
1610
+
1611
+ If the path cannot be resolved, ensure `@venizia/dev-configs` is installed:
1612
+
1613
+ ```bash
1614
+ bun add -d @venizia/dev-configs
1615
+ ```
1616
+
1617
+ ---
1618
+
1619
+ ### DI injection returns undefined at runtime
1620
+
1621
+ **Symptom:** `@inject`-decorated properties are `undefined` at runtime even though the binding exists in the container.
1622
+
1623
+ **Cause:** Usually one of:
1624
+
1625
+ 1. `emitDecoratorMetadata: false` -- the container cannot read constructor parameter types.
1626
+ 2. `useDefineForClassFields: true` -- `Object.defineProperty` overwrites decorator-injected values.
1627
+
1628
+ **Fix:** Verify both flags are set correctly in your `tsconfig.json`. If you extend `@venizia/dev-configs/tsconfig.common.json`, these are already set. Do not override them.
1629
+
1630
+ ---
1631
+
1632
+ ### Path alias resolution fails at runtime (tsc-alias)
1633
+
1634
+ **Symptom:** TypeScript compiles successfully, but runtime `import` statements fail with `MODULE_NOT_FOUND` for aliased paths like `@/services/UserService`.
1635
+
1636
+ **Cause:** TypeScript's `paths` configuration only affects type checking, not the emitted JavaScript. The compiled `.js` files still contain `@/...` import paths, which Node.js/Bun cannot resolve.
1637
+
1638
+ **Fix:** Use `tsc-alias` as a post-compilation step to rewrite path aliases in emitted JavaScript:
1639
+
1640
+ ```bash
1641
+ bun add -d tsc-alias
1642
+ ```
1643
+
1644
+ ```json
1645
+ {
1646
+ "scripts": {
1647
+ "build": "tsc && tsc-alias"
1648
+ }
1649
+ }
1650
+ ```
1651
+
1652
+ ---
1653
+
1654
+ ### Module resolution errors (node16 vs nodenext)
1655
+
1656
+ **Symptom:**
1657
+
1658
+ ```
1659
+ error TS2835: Relative import paths need explicit file extensions in ECMAScript
1660
+ imports when '--moduleResolution' is 'node16' or 'nodenext'.
1661
+ ```
1662
+
1663
+ **Cause:** With `moduleResolution: nodenext`, TypeScript enforces Node.js ESM resolution rules, which require explicit file extensions on relative imports.
1664
+
1665
+ **Fix:** Add `.js` extensions to relative imports (even though the source files are `.ts`):
1666
+
1667
+ ```typescript
1668
+ // CORRECT
1669
+ import { UserService } from './services/user.service.js';
1670
+
1671
+ // INCORRECT (will error with nodenext resolution)
1672
+ import { UserService } from './services/user.service';
1673
+ ```
1674
+
1675
+ This is a TypeScript convention: you write `.js` in the import path because the compiled output will be `.js` files.
1676
+
1677
+ ---
1678
+
1679
+ ### Incremental build stale cache
1680
+
1681
+ **Symptom:** TypeScript does not pick up changes, or emits stale output after modifying files.
1682
+
1683
+ **Cause:** The `.tsbuildinfo` incremental cache file is corrupted or stale.
1684
+
1685
+ **Fix:** Delete the `.tsbuildinfo` file and rebuild:
1686
+
1687
+ ```bash
1688
+ rm -f *.tsbuildinfo
1689
+ tsc
1690
+ ```
1691
+
1692
+ Or use the clean script:
1693
+
1694
+ ```bash
1695
+ bun run clean && bun run build
1696
+ ```
1697
+
1698
+ ---
1699
+
1700
+ ## Version Compatibility
1701
+
1702
+ | Tool | Required Version | Notes |
1703
+ | :--- | :--------------- | :---- |
1704
+ | **ESLint** | `^9.0.0` | Flat config format. Versions below 9.0 do not support `eslint.config.mjs` as the default config file. |
1705
+ | **Prettier** | `^3.0.0` | Major version 3 for `trailingComma: "all"` support in type parameters. |
1706
+ | **TypeScript** | `^5.0.0` | Required for `@typescript-eslint` v8 compatibility and modern decorator metadata support. |
1707
+ | **Bun** | `>=1.3` | Primary runtime. Specified in `package.json` `engines` field. |
1708
+ | **Node.js** | `>=18.0` | Secondary runtime. ES2022 target requires Node.js 18+ for full API support. |
1709
+ | **@typescript-eslint** | `^8.44.0` | Provided transitively via `@minimaltech/eslint-node`. |
1710
+ | **eslint-plugin-unicorn** | `^62.0.0` | Direct dependency of `@venizia/dev-configs`. |
1711
+ | **eslint-plugin-n** | `^17.21.3` | Provided transitively via `@minimaltech/eslint-node`. |
1712
+
1713
+ ---
1714
+
1715
+ ## Comparison with Alternatives
1716
+
1717
+ ### Why a shared config package instead of per-package configs?
1718
+
1719
+ | Approach | Pros | Cons |
1720
+ | :------- | :--- | :--- |
1721
+ | **Shared package (what we do)** | Single source of truth; update once, propagate everywhere; guaranteed consistency; 3-line consumption | Requires publishing/building the package before consumers can use it; tighter coupling |
1722
+ | **Copy-paste configs** | Independent per package; no build dependency | Config drift across packages; painful to update 10+ packages; no guarantee of consistency |
1723
+ | **Root-level configs only** | Zero duplication | Does not work for published packages that need their own `tsconfig.json` for declaration emit; monorepo tools may not respect root configs |
1724
+ | **Tool-specific workspace configs** (e.g., ESLint `root: true`) | Built into the tool | Only works for ESLint (legacy format); Prettier and TypeScript have no equivalent workspace inheritance |
1725
+
1726
+ ---
1727
+
1728
+ ### Why flat config (ESLint v9+) instead of `.eslintrc`?
1729
+
1730
+ - **Explicit composition:** Config is a plain array of objects. No magic resolution, no `extends` chains, no cascading from parent directories.
1731
+ - **Type safety:** The `Linter.Config[]` type provides autocomplete and type checking.
1732
+ - **Plugin loading:** Plugins are imported as ES modules, not resolved by string name. No more version mismatch confusion.
1733
+ - **Future-proof:** ESLint has deprecated `.eslintrc` as of v9. It will be removed in a future major version.
1734
+
1735
+ ---
1736
+
1737
+ ### Why Prettier config in `.prettierrc.mjs` instead of `.prettierrc.json`?
1738
+
1739
+ - **Import support:** `.mjs` files can `import` from npm packages. `.json` files cannot reference external configs.
1740
+ - **Spread operator:** JavaScript allows `{ ...baseConfig, override: value }`. JSON does not support any form of inheritance.
1741
+ - **Consistency:** All three configs (ESLint, Prettier, TypeScript) follow the same "import from shared package" pattern.
1742
+
1743
+ ---
1744
+
1745
+ ## Package Exports
1746
+
1747
+ | Export Path | Resolves To | Description |
1748
+ | :---------- | :---------- | :---------- |
1749
+ | `@venizia/dev-configs` | `dist/index.js` | ESLint and Prettier configs (`eslintConfigs`, `prettierConfigs`) |
1750
+ | `@venizia/dev-configs/tsconfig.base.json` | `tsconfig/tsconfig.base.json` | Comprehensive base TypeScript config |
1751
+ | `@venizia/dev-configs/tsconfig.common.json` | `tsconfig/tsconfig.common.json` | Consumer-facing TypeScript config (recommended) |
1752
+ | `@venizia/dev-configs/package.json` | `package.json` | Package manifest |
1753
+
1754
+ ---
1755
+
1756
+ ## Important Notes
1757
+
1758
+ - **Monorepo-wide impact.** This package is the foundation of every other package in the Ignis monorepo. Any change to ESLint rules, Prettier settings, or TypeScript compiler options propagates to **all** packages and examples. Test thoroughly before modifying.
1759
+
1760
+ - **Decorator flags are non-negotiable.** The `experimentalDecorators`, `emitDecoratorMetadata`, and `useDefineForClassFields` settings are structural requirements of the Ignis DI system. Changing them will break `@inject`, `@controller`, `@repository`, and every other decorator in the framework.
1761
+
1762
+ - **ESLint v9+ required.** This package exports flat config arrays, not the legacy `.eslintrc` format. ESLint v9.0.0 or later is required.
47
1763
 
48
- Ignis brings together the structured, enterprise development experience of **LoopBack 4** with the blazing speed and simplicity of **Hono** - giving you the best of both worlds.
1764
+ - **Bun is the primary runtime.** The `engines` field specifies `bun >= 1.3`. While the configs themselves are runtime-agnostic, the monorepo build system uses Bun exclusively.
49
1765
 
50
- ## Documentation
1766
+ - **Build order matters.** `@venizia/dev-configs` is the root of the build dependency chain (`dev-configs -> inversion -> helpers -> boot -> core`). It must be built before any other package.
51
1767
 
52
- - [Ignis Repository](https://github.com/venizia-ai/ignis)
53
- - [Getting Started](https://github.com/venizia-ai/ignis/blob/main/packages/docs/wiki/get-started/index.md)
54
- - [Code Style Standards](https://github.com/venizia-ai/ignis/blob/main/packages/docs/wiki/get-started/best-practices/code-style-standards.md)
1768
+ ---
55
1769
 
56
1770
  ## License
57
1771
 
58
- MIT
1772
+ [MIT](./LICENSE.md)