@alveolus/arch 0.1.0 → 0.3.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.
Files changed (60) hide show
  1. package/README.md +8 -1
  2. package/dist/bin.mjs +4 -2
  3. package/dist/bin.mjs.map +1 -1
  4. package/dist/{cli-CwPCGjDg.mjs → docs-DsQHpTtV.mjs} +287 -38
  5. package/dist/docs-DsQHpTtV.mjs.map +1 -0
  6. package/dist/index.d.mts +90 -36
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +2 -2
  9. package/docs/core/application/command-handlers.md +617 -0
  10. package/docs/core/application/event-publishers.md +234 -0
  11. package/docs/core/application/event-translators.md +329 -0
  12. package/docs/core/application/index.md +99 -0
  13. package/docs/core/application/integration-events.md +277 -0
  14. package/docs/core/application/outbox.md +416 -0
  15. package/docs/core/application/query-handlers.md +292 -0
  16. package/docs/core/application/unit-of-work.md +352 -0
  17. package/docs/core/domain/aggregates.md +822 -0
  18. package/docs/core/domain/domain-errors.md +251 -0
  19. package/docs/core/domain/domain-events.md +292 -0
  20. package/docs/core/domain/domain-services.md +249 -0
  21. package/docs/core/domain/entities.md +431 -0
  22. package/docs/core/domain/index.md +93 -0
  23. package/docs/core/domain/ports.md +284 -0
  24. package/docs/core/domain/repositories.md +335 -0
  25. package/docs/core/domain/value-objects.md +425 -0
  26. package/docs/core/domain/views.md +265 -0
  27. package/docs/core/index.md +108 -0
  28. package/docs/core/strategic/anti-corruption-layers.md +349 -0
  29. package/docs/core/strategic/index.md +83 -0
  30. package/docs/core/strategic/open-host-services.md +287 -0
  31. package/docs/core/strategic/published-language.md +265 -0
  32. package/docs/core/utilities/result.md +413 -0
  33. package/docs/guide/agents.md +68 -0
  34. package/docs/guide/existing-project.md +105 -0
  35. package/docs/guide/getting-started.md +275 -0
  36. package/docs/guide/learning-path.md +123 -0
  37. package/docs/guide/project-layout.md +324 -0
  38. package/docs/guide/versioning.md +42 -0
  39. package/docs/integrations/index.md +112 -0
  40. package/docs/integrations/nestjs.md +169 -0
  41. package/docs/rules/index.md +183 -0
  42. package/docs/rules/layers/no-driving-shortcut.md +119 -0
  43. package/docs/rules/layers/no-impure-domain.md +189 -0
  44. package/docs/rules/layers/no-outward-import.md +184 -0
  45. package/docs/rules/layers/no-portless-adapter.md +123 -0
  46. package/docs/rules/strategic/no-cross-context-import.md +140 -0
  47. package/docs/rules/strategic/no-fat-shared-kernel.md +81 -0
  48. package/docs/rules/strategic/no-leaky-host-service.md +107 -0
  49. package/docs/rules/strategic/no-unmapped-context.md +111 -0
  50. package/docs/rules/tactical/no-aggregate-reference.md +139 -0
  51. package/docs/rules/tactical/no-foreign-command-dependency.md +119 -0
  52. package/docs/rules/tactical/no-foreign-query-dependency.md +106 -0
  53. package/docs/rules/tactical/no-loose-code.md +171 -0
  54. package/docs/rules/tactical/no-misplaced-class.md +146 -0
  55. package/docs/rules/tactical/no-public-field.md +113 -0
  56. package/docs/rules/tactical/no-stateful-service.md +102 -0
  57. package/docs/rules/tactical/no-thrown-failure.md +162 -0
  58. package/docs/rules/tooling/no-loose-disable.md +98 -0
  59. package/package.json +4 -3
  60. package/dist/cli-CwPCGjDg.mjs.map +0 -1
@@ -0,0 +1,113 @@
1
+ ---
2
+ description: "Architecture rule: an aggregate, an entity, a value object or an identifier keeps its state private and exposes it through getters."
3
+ ---
4
+
5
+ # no-public-field
6
+
7
+ An aggregate, an entity, a value object or an identifier keeps its state private: nothing outside
8
+ changes it, and what callers need is read through a getter.
9
+
10
+ <dl class="al-glance">
11
+ <dt>Rule</dt><dd><code>tactical/no-public-field</code></dd>
12
+ <dt>Category</dt><dd><a href="/rules/#tactical">Tactical</a>: how building blocks are written</dd>
13
+ <dt>Reports</dt><dd>A public instance field, declared or as a constructor parameter, <code>readonly</code> or not</dd>
14
+ <dt>Applies to</dt><dd>Every class that extends <code>AggregateRoot</code>, <code>Entity</code>, <code>ValueObject</code> or <code>Identifier</code>, in core bounded contexts and the shared kernel</dd>
15
+ <dt>Turn off</dt><dd><a href="#turn-it-off"><code>"tactical/no-public-field": "off"</code></a></dd>
16
+ </dl>
17
+
18
+ ## Why
19
+
20
+ `Account` has `public balance = 0`. The withdraw handler checks the balance and subtracts the
21
+ amount itself; the next handler does the same with a slightly different check. The rule "an
22
+ account never goes below its overdraft" now lives in four handlers, and the aggregate is a bag of
23
+ fields: the model is anemic, and the day the rule changes, nobody finds every copy.
24
+
25
+ ::: tip The fix
26
+ The state is private, and changes through a method that returns a `Result`: `account.withdraw(amount)`.
27
+ A value the outside needs to read is a getter. The rule lives once, where the state is.
28
+ :::
29
+
30
+ ## What it checks
31
+
32
+ Every instance field of a class that extends one of the four building blocks:
33
+
34
+ | Field | Allowed |
35
+ | --- | --- |
36
+ | `private balance`, `protected readonly opened` | ✅ |
37
+ | `constructor(private readonly currency: string)` | ✅ |
38
+ | `get balance(): Money` | ✅ |
39
+ | `public static readonly limit = 100` | ✅ |
40
+ | `public balance = 0`, `public readonly currency` | ❌ |
41
+ | `constructor(public readonly owner: string)` | ❌ |
42
+
43
+ A `readonly` public field is reported too: a value object of it can still be mutated, and the
44
+ getter keeps the shape of the class free to change.
45
+
46
+ ## What it reports
47
+
48
+ ```
49
+ src/ledger/domain/aggregates/account.aggregate.ts
50
+ 4 error tactical/no-public-field: Account.balance is a public field:
51
+ keep the state private, and expose what callers need through a
52
+ getter.
53
+ ```
54
+
55
+ ## Fix it
56
+
57
+ ### Change the state through a method
58
+
59
+ <div class="al-compare">
60
+
61
+ ```ts [❌ Avoid: src/ledger/domain/aggregates/account.aggregate.ts]
62
+ export class Account extends AggregateRoot<AccountId> {
63
+ public balance = 0;
64
+ }
65
+
66
+ // in a handler
67
+ if (account.balance >= amount) {
68
+ account.balance -= amount;
69
+ }
70
+ ```
71
+
72
+ ```ts [✅ Prefer: src/ledger/domain/aggregates/account.aggregate.ts]
73
+ export class Account extends AggregateRoot<AccountId> {
74
+ private balance = 0;
75
+
76
+ get currentBalance(): number {
77
+ return this.balance;
78
+ }
79
+
80
+ withdraw(amount: number): Result<void, InsufficientFunds> {
81
+ if (this.balance < amount) {
82
+ return err(new InsufficientFunds({ amount }));
83
+ }
84
+ this.balance -= amount;
85
+ return ok();
86
+ }
87
+ }
88
+ ```
89
+
90
+ </div>
91
+
92
+ ## Limits
93
+
94
+ ::: warning What the rule cannot see
95
+ - A getter that returns a mutable object, such as the array of lines: a caller can push into it.
96
+ Return a copy, or a readonly type.
97
+ - A `protected` field: a subclass may change it. The rule stops the outside, not the hierarchy.
98
+ :::
99
+
100
+ ## Turn it off
101
+
102
+ ```ts [alveolus.config.ts]
103
+ rules: { "tactical/no-public-field": "off" },
104
+ ```
105
+
106
+ On an existing project, prefer a [baseline](../../guide/getting-started.md#adopt-it-on-an-existing-project):
107
+ new fields stay private while you move the rules back into the aggregates.
108
+
109
+ ## See also
110
+
111
+ - [Aggregates](../../core/domain/aggregates.md) and [Entities](../../core/domain/entities.md), what is checked
112
+ - [`tactical/no-thrown-failure`](./no-thrown-failure.md), which makes every change return a `Result`
113
+ - [Rules](../index.md), every rule by category
@@ -0,0 +1,102 @@
1
+ ---
2
+ description: "Architecture rule for domain services: a domain service is stateless and holds configuration only, never a port, a repository or another service."
3
+ ---
4
+
5
+ # no-stateful-service
6
+
7
+ A domain service holds configuration only: the command handler passes it what it needs.
8
+
9
+ <dl class="al-glance">
10
+ <dt>Rule</dt><dd><code>tactical/no-stateful-service</code></dd>
11
+ <dt>Category</dt><dd><a href="/rules/#tactical">Tactical</a>: how building blocks are written</dd>
12
+ <dt>Reports</dt><dd>A domain service that holds a port, a repository, another service or any class other than a value object</dd>
13
+ <dt>Applies to</dt><dd>The constructor parameters and fields of every <code>DomainService</code> of a core bounded context or the shared kernel</dd>
14
+ <dt>Turn off</dt><dd><a href="#turn-it-off"><code>"tactical/no-stateful-service": "off"</code></a></dd>
15
+ </dl>
16
+
17
+ ## Why
18
+
19
+ `OrderLimit` receives `Orders` to count the orders of a customer. The rule now loads data on its
20
+ own, a query handler that uses it can write, and testing it needs a repository. A domain service
21
+ is a stateless operation: it is given the aggregates and values it works on.
22
+
23
+ ::: tip The fix
24
+ The command handler loads what the rule needs and passes it to the service method. The service
25
+ keeps only its configuration, such as a limit or a rate.
26
+ :::
27
+
28
+ ## What it checks
29
+
30
+ Every constructor parameter and every field of each `DomainService`:
31
+
32
+ | Holds | Allowed |
33
+ | --- | --- |
34
+ | A plain value such as a `number` or a `string` | ✅ |
35
+ | A value object, an identifier | ✅ |
36
+ | A class of a package listed in `domainDependencies` | ✅ |
37
+ | A `Port`, a `CommandRepository`, a `QueryRepository` | ❌ |
38
+ | Another `DomainService`, an aggregate, an entity, a handler | ❌ |
39
+ | Any other class of the project | ❌ |
40
+
41
+ ## What it reports
42
+
43
+ ```
44
+ src/ordering/domain/services/order-limit.service.ts
45
+ 4 error tactical/no-stateful-service: The DomainService OrderLimit holds
46
+ Orders, a CommandRepository: a domain service holds configuration
47
+ only; the command handler passes it what it needs.
48
+ ```
49
+
50
+ ## Fix it
51
+
52
+ ### Pass what the rule needs to the method
53
+
54
+ So that the service stays a pure operation, the command handler loads the data and passes it in.
55
+
56
+ <div class="al-compare">
57
+
58
+ ```ts [❌ Avoid: src/ordering/domain/services/order-limit.service.ts]
59
+ export class OrderLimit extends DomainService {
60
+ constructor(private readonly orders: Orders) {
61
+ super();
62
+ }
63
+ }
64
+ ```
65
+
66
+ ```ts [✅ Prefer: src/ordering/domain/services/order-limit.service.ts]
67
+ export class OrderLimit extends DomainService {
68
+ constructor(private readonly maxLinesForNewCustomers: number) {
69
+ super();
70
+ }
71
+
72
+ check(order: Order, customer: Customer): Result<void, OrderTooLarge> {
73
+ // …
74
+ }
75
+ }
76
+ ```
77
+
78
+ </div>
79
+
80
+ ## Limits
81
+
82
+ ::: warning What the rule cannot see
83
+ - A port passed to a method of the service, call after call: the rule checks what the service
84
+ holds, not what it receives. In review, a domain service method takes aggregates and values.
85
+ - State reached through a module: the service has no field, but reads a constant that holds a
86
+ connection. `tactical/no-loose-code` reports the constant; the rule does not see the read.
87
+ :::
88
+
89
+ ## Turn it off
90
+
91
+ ```ts [alveolus.config.ts]
92
+ rules: { "tactical/no-stateful-service": "off" },
93
+ ```
94
+
95
+ On an existing project, prefer a [baseline](../../guide/getting-started.md#adopt-it-on-an-existing-project):
96
+ new services stay stateless while you rework the old ones.
97
+
98
+ ## See also
99
+
100
+ - [Domain services](../../core/domain/domain-services.md), what is checked
101
+ - [Command handlers](../../core/application/command-handlers.md), which load what a service needs
102
+ - [Rules](../index.md), every rule by category
@@ -0,0 +1,162 @@
1
+ ---
2
+ description: "Architecture rule: a business failure is returned in a Result, the domain and the application never throw, and exceptions stay in adapters."
3
+ ---
4
+
5
+ # no-thrown-failure
6
+
7
+ A business failure is a value: the aggregate returns it in a `Result`. The domain and the
8
+ application never throw; exceptions stay in adapters, for technical failures.
9
+
10
+ <dl class="al-glance">
11
+ <dt>Rule</dt><dd><code>tactical/no-thrown-failure</code></dd>
12
+ <dt>Category</dt><dd><a href="/rules/#tactical">Tactical</a>: how building blocks are written</dd>
13
+ <dt>Reports</dt><dd>A public method or function property of an aggregate or entity that returns no <code>Result</code>, a public setter, any <code>throw</code> or <code>Promise.reject</code> in the domain or the application</dd>
14
+ <dt>Applies to</dt><dd>Classes that extend <code>AggregateRoot</code> or <code>Entity</code>; every file in <code>domain/</code> and <code>application/</code>; in core bounded contexts and the shared kernel</dd>
15
+ <dt>Turn off</dt><dd><a href="#turn-it-off"><code>"tactical/no-thrown-failure": "off"</code></a></dd>
16
+ </dl>
17
+
18
+ ## Why
19
+
20
+ `order.place()` throws `InvalidTotal` when the total is zero. Nothing in its signature says so: the
21
+ controller that calls it does not catch it, and a customer gets an error 500 for a mistake they
22
+ could have fixed.
23
+
24
+ ::: tip The fix
25
+ The method returns `Result<void, InvalidTotal>`. The failure is in the type, and TypeScript makes
26
+ every caller deal with it before it reaches the value. A method that changes state says whether it
27
+ worked; reads are getters.
28
+ :::
29
+
30
+ ## What it checks
31
+
32
+ <div class="al-cards">
33
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span>Public methods return a Result</span>Every public method of a class that extends <code>AggregateRoot</code> or <code>Entity</code>, and every public property holding a function, such as <code>place = () =&gt; …</code>, even when its return type is inferred. A <code>Promise</code> of a <code>Result</code> counts. Getters, static methods, <code>toSnapshot</code>, <code>equals</code> and the protocol methods <code>toString</code>, <code>toJSON</code>, <code>valueOf</code> and <code>[Symbol.…]</code> are left out.</div>
34
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span>No setter</span>A public setter changes the state without saying whether it worked: a business method does it instead.</div>
35
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">3</span>Nothing is thrown</span>No <code>throw</code> and no <code>Promise.reject</code> in <code>domain/</code> or <code>application/</code>, whatever is thrown: an <code>Error</code>, a domain error or an <code>unknown</code>. Adapters may throw on a technical failure, such as a lost connection.</div>
36
+ </div>
37
+
38
+ ## What it reports
39
+
40
+ ```
41
+ src/ordering/domain/aggregates/order.aggregate.ts
42
+ 2 error tactical/no-thrown-failure: Order.place must return a Result:
43
+ expose reads as getters and return business failures as
44
+ values.
45
+ 4 error tactical/no-thrown-failure: A failure is thrown: return it in a
46
+ Result instead.
47
+ 9 error tactical/no-thrown-failure: Order.status is a setter: change the
48
+ state through a business method that returns a Result.
49
+ ```
50
+
51
+ ## Fix it
52
+
53
+ ### Return the failure in a Result
54
+
55
+ So that the caller sees what can go wrong, return the domain error with `err(…)` and success with
56
+ `ok()`, and declare both in the return type.
57
+
58
+ <div class="al-compare">
59
+
60
+ ```ts [❌ Avoid: src/ordering/domain/aggregates/order.aggregate.ts]
61
+ export class Order extends AggregateRoot<OrderId> {
62
+ place(total: number): void {
63
+ if (total <= 0) {
64
+ throw new InvalidTotal({ total });
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ ```ts [✅ Prefer: src/ordering/domain/aggregates/order.aggregate.ts]
71
+ export class Order extends AggregateRoot<OrderId> {
72
+ place(total: number): Result<void, InvalidTotal> {
73
+ if (total <= 0) {
74
+ return err(new InvalidTotal({ total }));
75
+ }
76
+ return ok();
77
+ }
78
+ }
79
+ ```
80
+
81
+ </div>
82
+
83
+ ### Expose reads as getters
84
+
85
+ So that the public methods are the ones that change state, a read without parameters is a getter,
86
+ which the rule leaves out.
87
+
88
+ <div class="al-compare">
89
+
90
+ ```ts [❌ Avoid: src/ordering/domain/aggregates/order.aggregate.ts]
91
+ isPlaced(): boolean {
92
+ return this.status === "placed";
93
+ }
94
+ ```
95
+
96
+ ```ts [✅ Prefer: src/ordering/domain/aggregates/order.aggregate.ts]
97
+ get isPlaced(): boolean {
98
+ return this.status === "placed";
99
+ }
100
+ ```
101
+
102
+ </div>
103
+
104
+ A read that needs parameters, such as `canShip(date)`, returns `ok(…)`, or moves to a
105
+ [`DomainService`](../../core/domain/domain-services.md) when it involves more than the aggregate.
106
+
107
+ ### Make the impossible state unrepresentable
108
+
109
+ So that a guard against an impossible state needs no `throw`, keep the constructor private and
110
+ build through a static factory that returns a `Result`: an invalid value never exists.
111
+
112
+ <div class="al-compare">
113
+
114
+ ```ts [❌ Avoid: src/ordering/domain/value-objects/quantity.value-object.ts]
115
+ export class Quantity extends ValueObject<{ value: number }> {
116
+ constructor(value: number) {
117
+ if (value <= 0) {
118
+ throw new Error("A quantity is positive.");
119
+ }
120
+ super({ value });
121
+ }
122
+ }
123
+ ```
124
+
125
+ ```ts [✅ Prefer: src/ordering/domain/value-objects/quantity.value-object.ts]
126
+ export class Quantity extends ValueObject<{ value: number }> {
127
+ private constructor(value: number) {
128
+ super({ value });
129
+ }
130
+
131
+ static of(value: number): Result<Quantity, InvalidQuantity> {
132
+ return value > 0 ? ok(new Quantity(value)) : err(new InvalidQuantity({ value }));
133
+ }
134
+ }
135
+ ```
136
+
137
+ </div>
138
+
139
+ ## Limits
140
+
141
+ ::: warning What the rule cannot see
142
+ - A promise rejected from its executor, `new Promise((_, reject) => reject(…))`, is not seen: only
143
+ `throw` and `Promise.reject` are.
144
+ - A function of a package that throws is not seen either: the rule reads your code, not what it
145
+ calls. Wrap such a call in a `Result` where it happens.
146
+ :::
147
+
148
+ ## Turn it off
149
+
150
+ ```ts [alveolus.config.ts]
151
+ rules: { "tactical/no-thrown-failure": "off" },
152
+ ```
153
+
154
+ On an existing project, prefer a [baseline](../../guide/getting-started.md#adopt-it-on-an-existing-project):
155
+ new methods return a `Result` while you convert the old ones.
156
+
157
+ ## See also
158
+
159
+ - [Result](../../core/utilities/result.md) and [Domain errors](../../core/domain/domain-errors.md), what the methods return
160
+ - [Aggregates](../../core/domain/aggregates.md), whose business methods this rule checks
161
+ - [`tactical/no-loose-code`](./no-loose-code.md), which keeps `extends Error` out of the domain
162
+ - [Rules](../index.md), every rule by category
@@ -0,0 +1,98 @@
1
+ ---
2
+ description: "Architecture rule: a disable comment names a rule and gives a reason, and is removed once the line below breaks the rule no more."
3
+ ---
4
+
5
+ # no-loose-disable
6
+
7
+ A disable comment turns one violation off, says which rule and why, and goes away with the
8
+ violation.
9
+
10
+ <dl class="al-glance">
11
+ <dt>Rule</dt><dd><code>tooling/no-loose-disable</code></dd>
12
+ <dt>Category</dt><dd><a href="/rules/#tooling">Tooling</a>: how the checks themselves are used</dd>
13
+ <dt>Reports</dt><dd>A <code>// alveolus-disable-next-line</code> comment that names no known rule, gives no reason, or disables nothing</dd>
14
+ <dt>Applies to</dt><dd>Every analysed file</dd>
15
+ <dt>Turn off</dt><dd><a href="#turn-it-off"><code>"tooling/no-loose-disable": "off"</code></a></dd>
16
+ </dl>
17
+
18
+ ## Why
19
+
20
+ A violation is sometimes right to keep for a while: a legacy import that a ticket will remove, a
21
+ rule that does not fit one file yet. A comment above the line turns it off, where a reviewer sees
22
+ it. Without a rule and a reason, the comment says nothing, and once the line is fixed it stays
23
+ behind and hides the next violation.
24
+
25
+ ::: tip The fix
26
+ Write the rule and the reason: `// alveolus-disable-next-line layers/no-impure-domain: legacy pool, ORD-412`.
27
+ When the line breaks the rule no more, the comment is reported until it is removed.
28
+ :::
29
+
30
+ ## What it checks
31
+
32
+ Every `// alveolus-disable-next-line` comment:
33
+
34
+ <div class="al-cards al-cards-2">
35
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span>Names a rule</span>The id of a rule, as the rules page lists it. One rule per comment.</div>
36
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span>Gives a reason</span>After a colon: why this line keeps its violation. Free text, read in review.</div>
37
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">3</span>Turns something off</span>The line below breaks that rule. A comment that disables nothing is reported.</div>
38
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">4</span>Is counted</span>The summary says how many violations are disabled, and <code>--format json</code> lists them with their reason.</div>
39
+ </div>
40
+
41
+ A comment that names no rule, an unknown rule, or no reason disables nothing: the violation below
42
+ is reported as well.
43
+
44
+ ## What it reports
45
+
46
+ ```
47
+ src/ordering/domain/services/pricing.service.ts
48
+ 1 error tooling/no-loose-disable: The disable comment gives no reason:
49
+ write `// alveolus-disable-next-line layers/no-impure-domain:
50
+ <why this line keeps its violation>`.
51
+ 4 error tooling/no-loose-disable: The disable comment disables nothing:
52
+ the line below breaks layers/no-impure-domain no more; remove
53
+ the comment.
54
+ ```
55
+
56
+ ## Fix it
57
+
58
+ ### Say which rule, and why
59
+
60
+ <div class="al-compare">
61
+
62
+ ```ts [❌ Avoid: src/ordering/domain/services/pricing.service.ts]
63
+ // alveolus-disable-next-line
64
+ import { Pool } from "pg";
65
+ ```
66
+
67
+ ```ts [✅ Prefer: src/ordering/domain/services/pricing.service.ts]
68
+ // alveolus-disable-next-line layers/no-impure-domain: legacy pool, removed with ORD-412
69
+ import { Pool } from "pg";
70
+ ```
71
+
72
+ </div>
73
+
74
+ ### Remove the comment with the violation
75
+
76
+ Once the line is fixed, the comment is reported: delete it. A disable comment never outlives what
77
+ it disables.
78
+
79
+ ## Limits
80
+
81
+ ::: warning What the rule cannot see
82
+ - Whether the reason is a good one: `: because` passes. The reason is for the reviewer.
83
+ - A violation turned off is still a violation: for a whole file or a whole rule, prefer `ignore`
84
+ or `rules` in `alveolus.config.ts`, and for the past, the baseline.
85
+ :::
86
+
87
+ ## Turn it off
88
+
89
+ ```ts [alveolus.config.ts]
90
+ rules: { "tooling/no-loose-disable": "off" },
91
+ ```
92
+
93
+ Disable comments then still work, but nothing checks them.
94
+
95
+ ## See also
96
+
97
+ - [Getting started: turn a violation off](../../guide/getting-started.md#turn-a-violation-off)
98
+ - [Rules](../index.md), every rule by category
package/package.json CHANGED
@@ -22,7 +22,8 @@
22
22
  "./package.json": "./package.json"
23
23
  },
24
24
  "files": [
25
- "dist"
25
+ "dist",
26
+ "docs"
26
27
  ],
27
28
  "homepage": "https://alveolus.dev/",
28
29
  "keywords": [
@@ -47,9 +48,9 @@
47
48
  },
48
49
  "sideEffects": false,
49
50
  "type": "module",
50
- "version": "0.1.0",
51
+ "version": "0.3.0",
51
52
  "scripts": {
52
- "build": "tsdown",
53
+ "build": "tsdown && biome check --write package.json",
53
54
  "typecheck": "tsc -p tsconfig.json"
54
55
  }
55
56
  }