@macroforge/mcp-server 0.1.33 → 0.1.35
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 +68 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/docs-loader.d.ts +133 -5
- package/dist/tools/docs-loader.d.ts.map +1 -1
- package/dist/tools/docs-loader.js +131 -15
- package/dist/tools/docs-loader.js.map +1 -1
- package/dist/tools/index.d.ts +48 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +163 -14
- package/dist/tools/index.js.map +1 -1
- package/docs/api/api-overview.md +24 -46
- package/docs/api/expand-sync.md +24 -51
- package/docs/api/native-plugin.md +24 -56
- package/docs/api/position-mapper.md +34 -76
- package/docs/api/transform-sync.md +27 -59
- package/docs/builtin-macros/clone.md +45 -104
- package/docs/builtin-macros/debug.md +33 -104
- package/docs/builtin-macros/default.md +78 -114
- package/docs/builtin-macros/deserialize.md +93 -273
- package/docs/builtin-macros/hash.md +58 -100
- package/docs/builtin-macros/macros-overview.md +42 -103
- package/docs/builtin-macros/ord.md +65 -133
- package/docs/builtin-macros/partial-eq.md +53 -179
- package/docs/builtin-macros/partial-ord.md +67 -159
- package/docs/builtin-macros/serialize.md +64 -194
- package/docs/concepts/architecture.md +40 -99
- package/docs/concepts/derive-system.md +129 -125
- package/docs/concepts/how-macros-work.md +52 -84
- package/docs/custom-macros/custom-overview.md +17 -39
- package/docs/custom-macros/rust-setup.md +22 -55
- package/docs/custom-macros/ts-macro-derive.md +43 -107
- package/docs/custom-macros/ts-quote.md +177 -507
- package/docs/getting-started/first-macro.md +108 -33
- package/docs/getting-started/installation.md +32 -73
- package/docs/integration/cli.md +70 -156
- package/docs/integration/configuration.md +32 -75
- package/docs/integration/integration-overview.md +16 -55
- package/docs/integration/mcp-server.md +30 -69
- package/docs/integration/svelte-preprocessor.md +60 -83
- package/docs/integration/typescript-plugin.md +32 -74
- package/docs/integration/vite-plugin.md +30 -79
- package/docs/language-servers/ls-overview.md +22 -46
- package/docs/language-servers/svelte.md +30 -69
- package/docs/language-servers/zed.md +34 -72
- package/docs/roadmap/roadmap.md +54 -130
- package/docs/sections.json +3 -262
- package/package.json +2 -2
|
@@ -1,223 +1,93 @@
|
|
|
1
1
|
# Serialize
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The `Serialize` macro generates JSON serialization methods with **cycle detection**
|
|
4
|
+
and object identity tracking. This enables serialization of complex object graphs
|
|
5
|
+
including circular references.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Generated Methods
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
| Type | Generated Code | Description |
|
|
10
|
+
|------|----------------|-------------|
|
|
11
|
+
| Class | `toStringifiedJSON()`, `toObject()`, `__serialize(ctx)` | Instance methods |
|
|
12
|
+
| Enum | `toStringifiedJSONEnumName(value)`, `__serializeEnumName` | Standalone functions |
|
|
13
|
+
| Interface | `toStringifiedJSONInterfaceName(value)`, etc. | Standalone functions |
|
|
14
|
+
| Type Alias | `toStringifiedJSONTypeName(value)`, etc. | Standalone functions |
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
const user = new User("Alice", 30);
|
|
11
|
-
console.log(JSON.stringify(user));
|
|
12
|
-
// {"name":"Alice","age":30,"createdAt":"2024-01-15T10:30:00.000Z"}
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Automatic Type Handling
|
|
16
|
-
|
|
17
|
-
Serialize automatically handles various TypeScript types:
|
|
18
|
-
|
|
19
|
-
| `string`, `number`, `boolean`
|
|
20
|
-
| Direct copy
|
|
21
|
-
|
|
22
|
-
| `Date`
|
|
23
|
-
| `.toISOString()`
|
|
24
|
-
|
|
25
|
-
| `T[]`
|
|
26
|
-
| Maps items, calling `toJSON()` if available
|
|
27
|
-
|
|
28
|
-
| `Map<K, V>`
|
|
29
|
-
| `Object.fromEntries()`
|
|
16
|
+
## Configuration
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
The `functionNamingStyle` option in `macroforge.json` controls naming:
|
|
19
|
+
- `"suffix"` (default): Suffixes with type name (e.g., `toStringifiedJSONMyType`)
|
|
20
|
+
- `"prefix"`: Prefixes with type name (e.g., `myTypeToStringifiedJSON`)
|
|
21
|
+
- `"generic"`: Uses TypeScript generics (e.g., `toStringifiedJSON<T extends MyType>`)
|
|
22
|
+
- `"namespace"`: Legacy namespace wrapping
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
| Calls `toJSON()` if available
|
|
36
|
-
|
|
37
|
-
## Serde Options
|
|
38
|
-
|
|
39
|
-
Use the `@serde` decorator for fine-grained control over serialization:
|
|
40
|
-
|
|
41
|
-
### Renaming Fields
|
|
42
|
-
|
|
43
|
-
<MacroExample before={data.examples.rename.before} after={data.examples.rename.after} />
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
const user = new User();
|
|
47
|
-
user.id = "123";
|
|
48
|
-
user.name = "Alice";
|
|
49
|
-
console.log(JSON.stringify(user));
|
|
50
|
-
// {"user_id":"123","full_name":"Alice"}
|
|
51
|
-
```
|
|
24
|
+
## Cycle Detection Protocol
|
|
52
25
|
|
|
53
|
-
|
|
26
|
+
The generated code handles circular references using `__id` and `__ref` markers:
|
|
54
27
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
### Rename All Fields
|
|
63
|
-
|
|
64
|
-
Apply a naming convention to all fields at the container level:
|
|
65
|
-
|
|
66
|
-
<InteractiveMacro code={`/** @derive(Serialize) */
|
|
67
|
-
/** @serde({ rename_all: "camelCase" }) */
|
|
68
|
-
class ApiResponse {
|
|
69
|
-
user_name: string;
|
|
70
|
-
created_at: Date;
|
|
71
|
-
is_active: boolean;
|
|
72
|
-
}`} />
|
|
73
|
-
|
|
74
|
-
Supported conventions:
|
|
75
|
-
|
|
76
|
-
- `camelCase`
|
|
77
|
-
|
|
78
|
-
- `snake_case`
|
|
79
|
-
|
|
80
|
-
- `PascalCase`
|
|
81
|
-
|
|
82
|
-
- `SCREAMING_SNAKE_CASE`
|
|
83
|
-
|
|
84
|
-
- `kebab-case`
|
|
85
|
-
|
|
86
|
-
### Flattening Nested Objects
|
|
87
|
-
|
|
88
|
-
<InteractiveMacro code={`/** @derive(Serialize) */
|
|
89
|
-
class Address {
|
|
90
|
-
city: string;
|
|
91
|
-
zip: string;
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"__type": "User",
|
|
31
|
+
"__id": 1,
|
|
32
|
+
"name": "Alice",
|
|
33
|
+
"friend": { "__ref": 2 } // Reference to object with __id: 2
|
|
92
34
|
}
|
|
93
|
-
|
|
94
|
-
/** @derive(Serialize) */
|
|
95
|
-
class User {
|
|
96
|
-
name: string;
|
|
97
|
-
|
|
98
|
-
/** @serde({ flatten: true }) */
|
|
99
|
-
address: Address;
|
|
100
|
-
}`} />
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
const user = new User();
|
|
104
|
-
user.name = "Alice";
|
|
105
|
-
user.address = { city: "NYC", zip: "10001" };
|
|
106
|
-
console.log(JSON.stringify(user));
|
|
107
|
-
// {"name":"Alice","city":"NYC","zip":"10001"}
|
|
108
35
|
```
|
|
109
36
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
| `rename_all`
|
|
115
|
-
| `string`
|
|
116
|
-
| Apply naming convention to all fields
|
|
117
|
-
|
|
118
|
-
### Field Options (on properties)
|
|
119
|
-
|
|
120
|
-
| `rename`
|
|
121
|
-
| `string`
|
|
122
|
-
| Use a different JSON key
|
|
123
|
-
|
|
124
|
-
| `skip`
|
|
125
|
-
| `boolean`
|
|
126
|
-
| Exclude from serialization and deserialization
|
|
37
|
+
When an object is serialized:
|
|
38
|
+
1. Check if it's already been serialized (has an `__id`)
|
|
39
|
+
2. If so, return `{ "__ref": existingId }` instead
|
|
40
|
+
3. Otherwise, register the object and serialize its fields
|
|
127
41
|
|
|
128
|
-
|
|
129
|
-
| `boolean`
|
|
130
|
-
| Exclude from serialization only
|
|
42
|
+
## Type-Specific Serialization
|
|
131
43
|
|
|
132
|
-
|
|
|
133
|
-
|
|
134
|
-
|
|
|
44
|
+
| Type | Serialization Strategy |
|
|
45
|
+
|------|------------------------|
|
|
46
|
+
| Primitives | Direct value |
|
|
47
|
+
| `Date` | `toISOString()` |
|
|
48
|
+
| Arrays | For primitive-like element types, pass through; for `Date`/`Date | null`, map to ISO strings; otherwise map and call `__serialize(ctx)` when available |
|
|
49
|
+
| `Map<K,V>` | For primitive-like values, `Object.fromEntries(map.entries())`; for `Date`/`Date | null`, convert to ISO strings; otherwise call `__serialize(ctx)` per value when available |
|
|
50
|
+
| `Set<T>` | Convert to array; element handling matches `Array<T>` |
|
|
51
|
+
| Nullable | Include `null` explicitly; for primitive-like and `Date` unions the generator avoids runtime `__serialize` checks |
|
|
52
|
+
| Objects | Call `__serialize(ctx)` if available (to support user-defined implementations) |
|
|
135
53
|
|
|
136
|
-
|
|
54
|
+
Note: the generator specializes some code paths based on the declared TypeScript type to
|
|
55
|
+
avoid runtime feature detection on primitives and literal unions.
|
|
137
56
|
|
|
138
|
-
|
|
57
|
+
## Field-Level Options
|
|
139
58
|
|
|
140
|
-
|
|
59
|
+
The `@serde` decorator supports:
|
|
141
60
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
message: "OK",
|
|
146
|
-
timestamp: new Date()
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
console.log(JSON.stringify(ApiResponse.toJSON(response)));
|
|
150
|
-
// {"status":200,"message":"OK","timestamp":"2024-01-15T10:30:00.000Z"}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Enum Support
|
|
154
|
-
|
|
155
|
-
Serialize also works with enums. The `toJSON` function returns the underlying enum value (string or number):
|
|
61
|
+
- `skip` / `skip_serializing` - Exclude field from serialization
|
|
62
|
+
- `rename = "jsonKey"` - Use different JSON property name
|
|
63
|
+
- `flatten` - Merge nested object's fields into parent
|
|
156
64
|
|
|
157
|
-
|
|
65
|
+
## Example
|
|
158
66
|
|
|
159
67
|
```typescript
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
Works with numeric enums too:
|
|
165
|
-
|
|
166
|
-
<InteractiveMacro code={`/** @derive(Serialize) */
|
|
167
|
-
enum Priority {
|
|
168
|
-
Low = 1,
|
|
169
|
-
Medium = 2,
|
|
170
|
-
High = 3,
|
|
171
|
-
}`} />
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
console.log(Priority.toJSON(Priority.High)); // 3
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Type Alias Support
|
|
178
|
-
|
|
179
|
-
Serialize works with type aliases. For object types, fields are serialized with full type handling:
|
|
68
|
+
@derive(Serialize)
|
|
69
|
+
class User {
|
|
70
|
+
id: number;
|
|
180
71
|
|
|
181
|
-
|
|
72
|
+
@serde(rename = "userName")
|
|
73
|
+
name: string;
|
|
182
74
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
id: "123",
|
|
186
|
-
name: "Alice",
|
|
187
|
-
createdAt: new Date("2024-01-15")
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
console.log(JSON.stringify(UserProfile.toJSON(profile)));
|
|
191
|
-
// {"id":"123","name":"Alice","createdAt":"2024-01-15T00:00:00.000Z"}
|
|
192
|
-
```
|
|
75
|
+
@serde(skip_serializing)
|
|
76
|
+
password: string;
|
|
193
77
|
|
|
194
|
-
|
|
78
|
+
@serde(flatten)
|
|
79
|
+
metadata: UserMetadata;
|
|
80
|
+
}
|
|
195
81
|
|
|
196
|
-
|
|
197
|
-
|
|
82
|
+
// Usage:
|
|
83
|
+
const user = new User();
|
|
84
|
+
const json = user.toStringifiedJSON();
|
|
85
|
+
// => '{"__type":"User","__id":1,"id":1,"userName":"Alice",...}'
|
|
198
86
|
|
|
199
|
-
|
|
200
|
-
|
|
87
|
+
const obj = user.toObject();
|
|
88
|
+
// => { __type: "User", __id: 1, id: 1, userName: "Alice", ... }
|
|
201
89
|
```
|
|
202
90
|
|
|
203
|
-
##
|
|
91
|
+
## Required Import
|
|
204
92
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
<InteractiveMacro code={`/** @derive(Serialize, Deserialize) */
|
|
208
|
-
class User {
|
|
209
|
-
name: string;
|
|
210
|
-
createdAt: Date;
|
|
211
|
-
}`} />
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// Serialize
|
|
215
|
-
const user = new User();
|
|
216
|
-
user.name = "Alice";
|
|
217
|
-
user.createdAt = new Date();
|
|
218
|
-
const json = JSON.stringify(user);
|
|
219
|
-
|
|
220
|
-
// Deserialize
|
|
221
|
-
const parsed = User.fromJSON(JSON.parse(json));
|
|
222
|
-
console.log(parsed.createdAt instanceof Date); // true
|
|
223
|
-
```
|
|
93
|
+
The generated code automatically imports `SerializeContext` from `macroforge/serde`.
|
|
@@ -1,96 +1,41 @@
|
|
|
1
1
|
# Architecture
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- `ts_template!` - Generate TypeScript code from templates
|
|
41
|
-
|
|
42
|
-
- `body!` - Generate class body members
|
|
43
|
-
|
|
44
|
-
- Control flow: `{"{#for}"}`, `{"{#if}"}`, `{"{$let}"}`
|
|
45
|
-
|
|
46
|
-
### macroforge_ts_macros
|
|
47
|
-
|
|
48
|
-
The procedural macro attribute for defining derive macros:
|
|
49
|
-
|
|
50
|
-
- `#[ts_macro_derive(Name)]` attribute
|
|
51
|
-
|
|
52
|
-
- Automatic registration with the macro system
|
|
53
|
-
|
|
54
|
-
- Error handling and span tracking
|
|
55
|
-
|
|
56
|
-
### NAPI-RS Bindings
|
|
57
|
-
|
|
58
|
-
Bridges Rust and Node.js:
|
|
59
|
-
|
|
60
|
-
- Exposes `expandSync`, `transformSync`, etc.
|
|
61
|
-
|
|
62
|
-
- Provides the `NativePlugin` class for caching
|
|
63
|
-
|
|
64
|
-
- Handles data marshaling between Rust and JavaScript
|
|
65
|
-
|
|
66
|
-
## Data Flow
|
|
67
|
-
|
|
68
|
-
<Flowchart steps={[
|
|
69
|
-
{ title: "1. Source Code", description: "TypeScript with @derive" },
|
|
70
|
-
{ title: "2. NAPI-RS", description: "receives JavaScript string" },
|
|
71
|
-
{ title: "3. SWC Parser", description: "parses to AST" },
|
|
72
|
-
{ title: "4. Macro Expander", description: "finds @derive decorators" },
|
|
73
|
-
{ title: "5. For Each Macro", description: "extract data, run macro, generate AST nodes" },
|
|
74
|
-
{ title: "6. Merge", description: "generated nodes into AST" },
|
|
75
|
-
{ title: "7. SWC Codegen", description: "generates source code" },
|
|
76
|
-
{ title: "8. Return", description: "to JavaScript with source mapping" }
|
|
77
|
-
]} />
|
|
78
|
-
|
|
79
|
-
## Performance Characteristics
|
|
80
|
-
|
|
81
|
-
- **Thread-safe**: Each expansion runs in an isolated thread with a 32MB stack
|
|
82
|
-
|
|
83
|
-
- **Caching**: `NativePlugin` caches results by file version
|
|
84
|
-
|
|
85
|
-
- **Binary search**: Position mapping uses O(log n) lookups
|
|
86
|
-
|
|
87
|
-
- **Zero-copy**: SWC's arena allocator minimizes allocations
|
|
88
|
-
|
|
89
|
-
## Re-exported Crates
|
|
90
|
-
|
|
91
|
-
For custom macro development, `macroforge_ts` re-exports everything you need:
|
|
92
|
-
|
|
93
|
-
```rust
|
|
2
|
+
*Macroforge is built as a native Node.js module using Rust and NAPI-RS. It leverages SWC for fast TypeScript parsing and code generation.*
|
|
3
|
+
## Overview
|
|
4
|
+
<div class="border border-border bg-card p-4 text-center rounded-t-lg "><div class="font-semibold text-foreground">Node.js / Vite <div class="font-semibold text-foreground">NAPI-RS Bindings <div class="font-semibold text-foreground">Macro Crates <div class="px-3 py-1.5 bg-muted rounded text-sm text-muted-foreground font-mono">macroforge_ts_synmacroforge_ts_quotemacroforge_ts_macros<div class="font-semibold text-foreground">SWC Core <div class="px-3 py-1.5 bg-muted rounded text-sm text-muted-foreground font-mono">TypeScript parsing & codegen ## Core Components
|
|
5
|
+
### SWC Core
|
|
6
|
+
The foundation layer provides:
|
|
7
|
+
- Fast TypeScript/JavaScript parsing
|
|
8
|
+
- AST representation
|
|
9
|
+
- Code generation (AST → source code)
|
|
10
|
+
### macroforge_ts_syn
|
|
11
|
+
A Rust crate that provides:
|
|
12
|
+
- TypeScript-specific AST types
|
|
13
|
+
- Parsing utilities for macro input
|
|
14
|
+
- Derive input structures (class fields, decorators, etc.)
|
|
15
|
+
### macroforge_ts_quote
|
|
16
|
+
Template-based code generation similar to Rust's `quote!`:
|
|
17
|
+
- `ts_template!` - Generate TypeScript code from templates
|
|
18
|
+
- `body!` - Generate class body members
|
|
19
|
+
- Control flow: `{#for}`, `{#if}`, `{$let}`
|
|
20
|
+
### macroforge_ts_macros
|
|
21
|
+
The procedural macro attribute for defining derive macros:
|
|
22
|
+
- `#[ts_macro_derive(Name)]` attribute
|
|
23
|
+
- Automatic registration with the macro system
|
|
24
|
+
- Error handling and span tracking
|
|
25
|
+
### NAPI-RS Bindings
|
|
26
|
+
Bridges Rust and Node.js:
|
|
27
|
+
- Exposes `expandSync`, `transformSync`, etc.
|
|
28
|
+
- Provides the `NativePlugin` class for caching
|
|
29
|
+
- Handles data marshaling between Rust and JavaScript
|
|
30
|
+
## Data Flow
|
|
31
|
+
<div class="w-full max-w-md border border-border rounded-lg bg-card p-4 text-center shadow-sm"><div class="font-semibold text-foreground">1. Source Code TypeScript with @derive <div class="font-semibold text-foreground">2. NAPI-RS receives JavaScript string <div class="font-semibold text-foreground">3. SWC Parser parses to AST <div class="font-semibold text-foreground">4. Macro Expander finds @derive decorators <div class="font-semibold text-foreground">5. For Each Macro extract data, run macro, generate AST nodes <div class="font-semibold text-foreground">6. Merge generated nodes into AST <div class="font-semibold text-foreground">7. SWC Codegen generates source code <div class="font-semibold text-foreground">8. Return to JavaScript with source mapping ## Performance Characteristics
|
|
32
|
+
- **Thread-safe**: Each expansion runs in an isolated thread with a 32MB stack
|
|
33
|
+
- **Caching**: `NativePlugin` caches results by file version
|
|
34
|
+
- **Binary search**: Position mapping uses O(log n) lookups
|
|
35
|
+
- **Zero-copy**: SWC's arena allocator minimizes allocations
|
|
36
|
+
## Re-exported Crates
|
|
37
|
+
For custom macro development, `macroforge_ts` re-exports everything you need:
|
|
38
|
+
```
|
|
94
39
|
// Convenient re-exports for macro development
|
|
95
40
|
use macroforge_ts::macros::{ts_macro_derive, body, ts_template, above, below, signature};
|
|
96
41
|
use macroforge_ts::ts_syn::{Data, DeriveInput, MacroforgeError, TsStream, parse_ts_macro_input};
|
|
@@ -99,10 +44,6 @@ use macroforge_ts::ts_syn::{Data, DeriveInput, MacroforgeError, TsStream, parse_
|
|
|
99
44
|
use macroforge_ts::swc_core;
|
|
100
45
|
use macroforge_ts::swc_common;
|
|
101
46
|
use macroforge_ts::swc_ecma_ast;
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- [Write custom macros]({base}/docs/custom-macros)
|
|
107
|
-
|
|
108
|
-
- [Explore the API reference]({base}/docs/api)
|
|
47
|
+
``` ## Next Steps
|
|
48
|
+
- [Write custom macros](../../docs/custom-macros)
|
|
49
|
+
- [Explore the API reference](../../docs/api)
|