···105105106106## How It Works
107107108108-The fetch command follows the ATProto lexicon discovery protocol:
109109-110110-1. **DNS Lookup** - Queries `_lexicon.<reversed-authority>` TXT record
111111-2. **DID Resolution** - Resolves the DID to a PDS endpoint
112112-3. **Fetch Records** - Queries `com.atproto.repo.listRecords` for lexicon schemas
113113-4. **Save & Convert** - Saves JSON and converts to MLF format
114114-5. **Update Lockfile** - Records NSIDs, DIDs, checksums, and dependencies
108108+The fetch command uses ATProto's lexicon discovery protocol to locate and download lexicons via DNS lookups and DID resolution, then converts them to MLF format and updates the lockfile.
115109116110## Examples
117111···2082022. Adds `"com.example.forum.*"` to the dependencies array in `mlf.toml`
2092033. Creates `mlf.toml` if it doesn't exist
210204211211-## Storage Structure
212205213213-Fetched lexicons are stored in `.mlf/lexicons/`:
214214-215215-```
216216-.mlf/
217217-├── .gitignore # Auto-generated
218218-└── lexicons/
219219- ├── json/ # Original JSON lexicons
220220- │ ├── com/
221221- │ │ └── example/
222222- │ │ ├── forum/
223223- │ │ │ ├── post.json
224224- │ │ │ └── thread.json
225225- │ │ └── social/
226226- │ │ └── profile.json
227227- │ └── app/
228228- │ └── bsky/
229229- │ └── feed/
230230- │ └── post.json
231231- └── mlf/ # Converted MLF format
232232- ├── com/
233233- │ └── example/
234234- │ ├── forum/
235235- │ │ ├── post.mlf
236236- │ │ └── thread.mlf
237237- │ └── social/
238238- │ └── profile.mlf
239239- └── app/
240240- └── bsky/
241241- └── feed/
242242- └── post.mlf
243243-```
244244-245245-**Note:** The lockfile (`mlf-lock.toml`) lives at the project root, sibling to `mlf.toml`.
246246-247247-## DNS Resolution
248248-249249-For an NSID like `com.example.forum.post`:
250250-251251-1. Extract authority: `com.example` (first 2 segments)
252252-2. Reverse for DNS: `example.com`
253253-3. Query TXT record: `_lexicon.example.com`
254254-4. Parse `did=did:web:...` or `did=did:plc:...`
255255-256256-**Example DNS record:**
257257-```
258258-_lexicon.example.com. 300 IN TXT "did=did:web:example.com"
259259-```
260260-261261-## DID Resolution
262262-263263-### did:web
264264-265265-For `did:web:example.com`, the PDS is `https://example.com`
266266-267267-### did:plc
268268-269269-For `did:plc:abc123...`, query `https://plc.directory/did:plc:abc123...` to get the PDS endpoint from the DID document.
270270-271271-## Fetched Lexicons in Your Code
272272-273273-Once fetched, lexicons in `.mlf/lexicons/mlf/` are automatically available for:
274274-275275-### Type References
276276-277277-```mlf
278278-use com.example.forum.post;
279279-280280-record reply {
281281- post!: com.example.forum.post,
282282- text!: string,
283283-}
284284-```
285285-286286-### Code Generation
287287-288288-```bash
289289-mlf generate code -g typescript -i my-lexicon.mlf -o src/types/
290290-```
291291-292292-The generator can resolve references to fetched lexicons.
293293-294294-### Validation
295295-296296-```bash
297297-mlf check my-lexicon.mlf
298298-```
299299-300300-The check command loads fetched lexicons for type resolution.
301301-302302-## Working Without mlf.toml
303303-304304-If you don't have an `mlf.toml`, the fetch command will offer to create one:
305305-306306-```bash
307307-$ mlf fetch com.example.forum.*
308308-No mlf.toml found in current or parent directories.
309309-Would you like to create one in the current directory? (y/n)
310310-y
311311-Created mlf.toml in /path/to/current/dir
312312-...
313313-```
314314-315315-## Error Handling
316316-317317-### DNS Errors
318318-319319-```
320320-✗ DNS lookup failed: No TXT record found for _lexicon.example.com
321321-```
322322-323323-**Causes:**
324324-- Domain doesn't have a lexicon TXT record
325325-- DNS propagation delay
326326-- Network issues
327327-328328-### DID Resolution Errors
329329-330330-```
331331-✗ Failed to resolve DID: No PDS endpoint found in DID document
332332-```
333333-334334-**Causes:**
335335-- Invalid DID format
336336-- PLC directory unreachable
337337-- DID document missing PDS service
338338-339339-### No Records Found
340340-341341-```
342342-✗ No lexicons matched pattern: com.example.forum.*
343343-```
344344-345345-**Causes:**
346346-- No lexicons exist matching the pattern
347347-- Wrong NSID (typo)
348348-- PDS doesn't support lexicon publishing
349349-350350-### Invalid NSID Format
351351-352352-```
353353-✗ NSID must have at least 2 segments or use wildcard: com
354354-```
355355-356356-**Solution:**
357357-- Use a specific NSID: `com.example.forum.post`
358358-- Or use a wildcard: `com.example.forum.*`
359359-360360-### Checksum Mismatch (--locked mode)
361361-362362-```
363363-✗ Checksum mismatch for place.stream.facet: expected sha256:abc123, got sha256:def456
364364-```
365365-366366-**Causes:**
367367-- Lexicon was updated on the server
368368-- Lock file is out of date
369369-370370-**Solution:**
371371-```bash
372372-mlf fetch --update # Update lockfile with new checksums
373373-```
374206375207## Best Practices
3762083772091. **Commit lockfile** - Always commit `mlf-lock.toml` to version control
3782102. **Use --locked in CI** - Ensures reproducible builds in CI/CD pipelines
379379-3. **Fetch before work** - Always fetch dependencies before coding
380380-4. **Use --save** - Keep `mlf.toml` up to date with dependencies
381381-5. **Don't commit `.mlf/`** - Let each developer fetch independently
382382-6. **Check DNS** - Verify TXT records before fetching
383383-7. **Update explicitly** - Use `mlf fetch --update` when you want latest versions
211211+3. **Don't commit `.mlf/`** - Let each developer fetch independently
212212+4. **Update explicitly** - Use `mlf fetch --update` when you want latest versions
384213385214## Comparison with npm/cargo
386215···396225| Lock file | `package-lock.json` | `Cargo.lock` | `mlf-lock.toml` |
397226| Cache | `node_modules/` | `~/.cargo/` | `.mlf/` |
398227399399-## Troubleshooting
400400-401401-### Network Issues
402402-403403-```bash
404404-# Check DNS resolution
405405-dig TXT _lexicon.example.com
406406-407407-# Test DID resolution
408408-curl https://plc.directory/did:plc:abc123
409409-```
410410-411411-### Invalid NSID Format
412412-413413-Make sure you're using the correct format:
414414-- ✓ `com.example.forum.post` (specific lexicon)
415415-- ✓ `com.example.forum.*` (wildcard)
416416-- ✓ `app.bsky.feed.*` (real-world wildcard)
417417-- ✗ `com` (must have at least 2 segments)
418418-419419-### Permission Errors
420420-421421-Ensure you have write permissions for the project directory to create `.mlf/` and `mlf-lock.toml`.
422422-423423-### Conflicting Flags
424424-425425-```
426426-✗ Cannot use --update and --locked together
427427-```
428428-429429-Choose one mode:
430430-- Use `--update` to get latest versions
431431-- Use `--locked` for strict reproducible builds
···37373838```mlf
3939@validate(min: 0, max: 100, strict: true)
4040-@codegen(language: "rust", derive: "Debug, Clone")
4040+@cache(ttl: 3600, strategy: "lru")
4141record example {
4242 field: integer,
4343}
···7272}
7373```
74747575-## MLF Annotations vs Generator Annotations
7575+## Annotation Semantics
76767777-MLF distinguishes between two categories:
7777+Annotations in MLF are interpreted by whatever consumes them - whether that's the MLF compiler itself or external code generators.
78787979-### 1. MLF Annotations
7979+### Bare Annotations
80808181-Built into the MLF language and affect compilation/validation. These are **bare annotations** without any namespace prefix:
8282-8383-**`@main`** - Marks an item as the main definition when there's ambiguity:
8181+**Bare annotations** (without generator selectors) are visible to **all generators** and each can interpret them as needed:
84828583```mlf
8686-// File: com/example/thread.mlf
8787-@main
8888-record thread {
8989- title!: string,
9090-}
9191-9292-// This def shares the same name but is not main
9393-def type thread = {
9494- id!: string,
9595- viewCount!: integer,
9696-}
8484+// Visible to all generators - each interprets as appropriate
8585+@deprecated
8686+query foo();
9787```
98889999-See [Important Info](/docs/language-guide/important-info/#the-main-definition) for more details on the `@main` annotation.
8989+MLF itself recognizes the **`@main` annotation** for resolving naming conflicts. See [Important Info](/docs/language-guide/important-info/#the-main-definition) for details.
10090101101-### 2. Generator Annotations
9191+### Generator Selectors
10292103103-Used by code generators and external tools. These have no effect on MLF compilation and **must** be namespaced with the generator name:
9393+To target **specific generators**, use the generator selector syntax with a colon:
1049410595```mlf
106106-@rust:derive("Debug, Clone, Serialize")
107107-@typescript:export
108108-@go:tag(json: "custom_name")
109109-record example {
110110- field: string,
111111-}
9696+// Only for rust generator
9797+@rust:deprecated
9898+query bar();
9999+100100+// Only for typescript generator
101101+@typescript:deprecated
102102+query baz();
112103```
113104114114-**Generator namespacing rules:**
115115-- All generator annotations must have a namespace prefix (e.g., `@rust:foo`)
116116-- Use `@all:annotation` to apply an annotation to all generators
117117-- Bare annotations (without `:`) are reserved for MLF itself
105105+### Multiple Generator Selectors
118106119119-**Common generator namespaces:**
120120-- `@rust:*` - Rust code generator annotations
121121-- `@typescript:*` - TypeScript code generator annotations
122122-- `@go:*` - Go code generator annotations
123123-- `@python:*` - Python code generator annotations
124124-- `@all:*` - Applies to all generators
107107+You can apply an annotation to multiple specific generators using comma-separated selectors:
125108126126-## Custom Annotations
109109+```mlf
110110+// For both rust AND typescript
111111+@rust,typescript:deprecated
112112+query qux();
113113+```
127114128128-You can define your own annotations for custom tooling:
115115+Alternatively, you can write separate annotations:
129116130117```mlf
131131-@myapp:cache(ttl: 3600)
132132-@myapp:permission("read:public")
133133-query getProfile(actor!: Did): profile;
134134-135135-@myapp:audit_log
136136-@myapp:rate_limit(requests: 100, window: 60)
137137-procedure updateProfile(data!: profile): unit;
118118+// Equivalent to above
119119+@rust:deprecated
120120+@typescript:deprecated
121121+query qux();
138122```
139123140140-The interpretation is entirely up to your tooling.
124124+**Common generator selectors:**
125125+- `@rust:*` - Rust code generator annotations
126126+- `@typescript:*` - TypeScript code generator annotations
127127+- `@go:*` - Go code generator annotations
141128142129## Annotation Processing
143130···153140154141## Best Practices
155142156156-1. **Always namespace generator annotations** - Use `@generator:name` for all generator-specific annotations
157157-2. **Use `@all:` for cross-generator annotations** - When an annotation should apply to all generators
158158-3. **Document custom annotations** - Keep a registry of annotations your project uses
159159-4. **Be consistent** - Use the same annotation patterns across your codebase
160160-5. **Don't overuse** - Annotations should augment, not replace, good design
143143+1. **Use bare annotations for universal concepts** - Use `@deprecated` without selectors when you want all generators to see it
144144+2. **Use generator selectors for specific tooling** - Use `@rust:derive` or `@typescript:export` when targeting one generator
145145+3. **Group with comma selectors when appropriate** - Use `@rust,typescript:deprecated` to apply the same annotation to multiple generators
146146+4. **Document custom annotations** - Keep a registry of annotations your project uses
147147+5. **Be consistent** - Use the same annotation patterns across your codebase
148148+6. **Don't overuse** - Annotations should augment, not replace, good design
161149162150## What's Next?
163151