Captures and instances
Markdown for agentsMany architectures have a set of folders with the same shape: features, modules, packages. Each folder must stay apart from the others. A capture lets one part describe all of these folders, and still tell them apart.
A capture takes a name from the path
Section titled “A capture takes a name from the path”A capture is a name in braces in a path pattern. It takes one folder name from the path of each file.
| Pattern | File | Captures |
|---|---|---|
src/features/{feature}/** |
src/features/cart/views/page.tsx |
feature = cart |
src/{area}/{module}/** |
src/billing/invoices/invoice.ts |
area = billing, module = invoices |
Braces with a comma are not a capture. src/{ui,lib}/** matches src/ui or src/lib.
An instance is the files with the same captures
Section titled “An instance is the files with the same captures”An instance is the set of files that have the same value in each capture of their part. With
the pattern src/{area}/{module}/**, this repository has three instances of the part module:
Directorysrc/
Directorybilling/
Directoryinvoices/ instance
billing/invoices- invoice.ts
- number.ts
Directorypayments/ instance
billing/payments- refund.ts
Directoryshipping/
Directoryinvoices/ instance
shipping/invoices- label.ts
billing/invoices and shipping/invoices are two instances. They have the same module, but a
different area.
Rules that use instances
Section titled “Rules that use instances”This config keeps the modules apart with one shorthand:
import { defineConfig } from '@enhance-eng/archangel';
export default defineConfig({ parts: { module: 'src/{area}/{module}/**', }, rules: ({ isolate }) => [ isolate('module', { because: 'A module changes alone. Modules talk through events, not imports.', examples: { rejected: ['src/sales/orders/a.ts imports src/sales/quotes/b.ts'], }, }), ],});archangel explain shows the instance of the file, and the instance of each import:
$ npx archangel explain src/billing/invoices/invoice.tssrc/billing/invoices/invoice.ts part: "module", instance "billing/invoices" groups: none rules that apply: isolate(module) isolate(module): files in module may not import module in a different instance (judges its imports and imports of it) imports: line 1 './number' -> src/billing/invoices/number.ts ("module", instance "billing/invoices") line 2 '../payments/refund' -> src/billing/payments/refund.ts ("module", instance "billing/payments") breaks isolate(module) imported by: noneThe import on line 1 stays in the instance, so isolate permits it. The import on line 2 goes to
another instance, so isolate refuses it:
$ npx archangel checkerror[isolate]: "billing/invoices" of "module" imports "billing/payments" of "module" through "../payments/refund", and instances of "module" may not import each other --> src/billing/invoices/invoice.ts:2:24 |2 | import { refund } from '../payments/refund'; | ^^^^^^^^^^^^^^^^^^^^ move what both need into an element that instances of "module" may share | = note: rule: isolate(module) = note: because: A module changes alone. Modules talk through events, not imports.
checked 5 files and 3 imports: 1 problemFor what a code means, run `archangel docs isolate`.The rule has no name, so archangel names it after the rule itself: isolate(module). Use this
name in an exception, in the baseline and in off.
These rules use instances:
| Rule | What it does with instances |
|---|---|
| A table row | A row may import files of its own part or kind in the same instance. An import of another instance of its own name needs the name in the row’s list. |
isolate('feature') |
An instance may not import another instance of the same part. |
entry('feature', 'index.ts') |
Code outside an instance may import it only through its entry files. |
mayOnlyImport |
When files is one part with a capture, the rule permits imports in the same instance only. To permit each instance, add the part to the list. |
instance: 'same' or 'different' |
An import condition then applies only to imports in the same instance, or only to imports between two instances. files must be a part with a capture. |
mustContain |
Each instance must have a file that matches the pattern. |
So the table row feature: ['shared'] reads: “a feature may import shared, and files of its own
feature”. Write feature: ['shared', 'feature'] to also permit imports between two features.
Select one instance
Section titled “Select one instance”To write a rule about one instance only, select it by its captures:
rule('login-no-react', { files: { element: 'feature', capture: { feature: 'login' } }, mayNotImport: { packages: ['react'] }, examples: { rejected: ['src/features/login/a.ts imports react'] },})Rules that need a capture
Section titled “Rules that need a capture”isolate needs a part with a capture, because without one the part has only one instance. If you
name a part without a capture, archangel shows the error
rule-needs-capture. With defineConfig,
the TypeScript compiler finds this error before archangel runs.