Skip to content

Commit cce726e

Browse files
feat(docs): Add comprehensive documentation for AGENTS architecture
- Introduced detailed documentation for the AGENTS SDK, covering the overall structure, conventions, and anti-patterns across various layers: core library utilities, models, and services. - Included guidelines for error handling, async operations, and testing practices to ensure consistency and best practices in development. - Enhanced the documentation with clear examples and a structured overview to facilitate understanding and usage of the SDK. These additions improve the clarity and usability of the AGENTS SDK for developers.
1 parent 9df35df commit cce726e

4 files changed

Lines changed: 307 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# SOLAPI SDK for Node.js
2+
3+
**Generated:** 2026-01-21
4+
**Commit:** 9df35df
5+
**Branch:** master
6+
7+
## OVERVIEW
8+
9+
Server-side SDK for SMS/LMS/MMS and Kakao messaging in Korea. Uses Effect library for type-safe functional programming with Data.TaggedError-based error handling.
10+
11+
## STRUCTURE
12+
13+
```
14+
solapi-nodejs/
15+
├── src/
16+
│ ├── index.ts # SolapiMessageService facade (entry point)
17+
│ ├── errors/ # Data.TaggedError types
18+
│ ├── lib/ # Core utilities (fetcher, auth, error handler)
19+
│ ├── models/ # Schemas, requests, responses (see models/AGENTS.md)
20+
│ ├── services/ # Domain services (see services/AGENTS.md)
21+
│ └── types/ # Shared type definitions
22+
├── test/ # Mirrors src/ structure
23+
├── examples/ # Usage examples (excluded from build)
24+
└── debug/ # Debug scripts
25+
```
26+
27+
## WHERE TO LOOK
28+
29+
| Task | Location | Notes |
30+
|------|----------|-------|
31+
| Add new message type | `src/models/base/messages/` | Extend MessageType union |
32+
| Add new service | `src/services/` | Extend DefaultService |
33+
| Add new error type | `src/errors/defaultError.ts` | Extend Data.TaggedError |
34+
| Add utility function | `src/lib/` | Follow Effect patterns |
35+
| Add Kakao BMS type | `src/models/base/kakao/bms/` | Add to BMS_REQUIRED_FIELDS |
36+
| Fix API request issue | `src/lib/defaultFetcher.ts` | HTTP client with retry |
37+
| Understand error flow | `src/lib/effectErrorHandler.ts` | Effect → Promise conversion |
38+
39+
## CONVENTIONS
40+
41+
**Effect Library (MANDATORY)**:
42+
- All errors: `Data.TaggedError` with environment-aware `toString()`
43+
- Async operations: `Effect.gen` + `Effect.tryPromise`, never wrap with try-catch
44+
- Validation: `Effect Schema` with `Schema.filter`, `Schema.transform`
45+
- Error execution: `runSafePromise()` / `runSafeSync()` from effectErrorHandler
46+
47+
**TypeScript**:
48+
- **NEVER use `any`** — use `unknown` + type guards or Effect Schema
49+
- Strict mode enforced (`noUnusedLocals`, `noUnusedParameters`)
50+
- Path aliases: `@models`, `@lib`, `@services`, `@errors`, `@internal-types`
51+
52+
**Testing**:
53+
- Unit: `vitest` with `Schema.decodeUnknownEither()` for validation tests
54+
- E2E: `@effect/vitest` with `it.effect()` and `Effect.gen`
55+
- Run: `pnpm test` / `pnpm test:watch`
56+
57+
## ANTI-PATTERNS
58+
59+
| Pattern | Why Bad | Do Instead |
60+
|---------|---------|------------|
61+
| `any` type | Loses type safety | `unknown` + type guards |
62+
| `as any`, `@ts-ignore` | Suppresses errors | Fix the type issue |
63+
| try-catch around Effect | Loses Effect benefits | Use `Effect.catchTag` |
64+
| Direct `throw new Error()` | Inconsistent error handling | Use `Data.TaggedError` |
65+
| Empty catch blocks | Swallows errors | Handle or propagate |
66+
67+
## COMMANDS
68+
69+
```bash
70+
pnpm dev # Watch mode (tsup)
71+
pnpm build # Lint + build
72+
pnpm lint # Biome check with auto-fix
73+
pnpm test # Run tests once
74+
pnpm test:watch # Watch mode
75+
pnpm docs # Generate TypeDoc
76+
```
77+
78+
## ARCHITECTURE NOTES
79+
80+
**Service Facade Pattern**: `SolapiMessageService` aggregates 7 domain services via `bindServices()` dynamic method binding. All services extend `DefaultService`.
81+
82+
**Error Flow**:
83+
```
84+
API Response
85+
→ defaultFetcher (creates Effect errors)
86+
→ runSafePromise (converts to Promise)
87+
→ toCompatibleError (preserves properties on Error)
88+
→ Consumer
89+
```
90+
91+
**Production vs Development**: Error messages stripped of stack traces and detailed context in production (`process.env.NODE_ENV === 'production'`).
92+
93+
**Retry Logic**: `defaultFetcher.ts` implements 3x retry with exponential backoff for retryable errors (connection refused, reset, 503).

src/lib/AGENTS.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Core Library Utilities
2+
3+
## OVERVIEW
4+
5+
Cross-cutting utilities used by all services. Effect-based async handling and error management.
6+
7+
## STRUCTURE
8+
9+
```
10+
lib/
11+
├── defaultFetcher.ts # HTTP client with Effect.gen, retry, Match
12+
├── effectErrorHandler.ts # runSafePromise, toCompatibleError, formatError
13+
├── authenticator.ts # HMAC-SHA256 auth header generation
14+
├── stringifyQuery.ts # URL query string builder
15+
├── fileToBase64.ts # File/URL → Base64 converter
16+
└── stringDateTrasnfer.ts # Date parsing with InvalidDateError
17+
```
18+
19+
## WHERE TO LOOK
20+
21+
| Task | File | Notes |
22+
|------|------|-------|
23+
| HTTP request issues | `defaultFetcher.ts` | Retry logic, error handling |
24+
| Error formatting | `effectErrorHandler.ts` | Production vs dev messages |
25+
| Auth issues | `authenticator.ts` | HMAC signature generation |
26+
| Query params | `stringifyQuery.ts` | Array handling, encoding |
27+
| File handling | `fileToBase64.ts` | URL detection, Base64 encoding |
28+
| Date parsing | `stringDateTrasnfer.ts` | ISO format conversion |
29+
30+
## CONVENTIONS
31+
32+
**Effect.tryPromise for Async**:
33+
```typescript
34+
Effect.tryPromise({
35+
try: () => fetch(url, options),
36+
catch: e => new NetworkError({ url, cause: e }),
37+
});
38+
```
39+
40+
**Effect.gen for Complex Flow**:
41+
```typescript
42+
Effect.gen(function* (_) {
43+
const auth = yield* _(buildAuth(params));
44+
const response = yield* _(fetchWithRetry(url, auth));
45+
return yield* _(parseResponse(response));
46+
});
47+
```
48+
49+
**Error to Promise Conversion**:
50+
```typescript
51+
// Always use runSafePromise for Effect → Promise
52+
return runSafePromise(effect);
53+
54+
// Never wrap Effect with try-catch
55+
// BAD: try { await Effect.runPromise(...) } catch { }
56+
```
57+
58+
## ANTI-PATTERNS
59+
60+
- Don't bypass `runSafePromise` — loses error formatting
61+
- Don't use try-catch around Effect — use Effect.catchTag
62+
- Don't create new HTTP client — use defaultFetcher
63+
- Don't hardcode API URL — use DefaultService.baseUrl

src/models/AGENTS.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Models Layer
2+
3+
## OVERVIEW
4+
5+
Three-layer model architecture using Effect Schema for runtime validation.
6+
7+
## STRUCTURE
8+
9+
```
10+
models/
11+
├── base/ # Core domain entities
12+
│ ├── messages/message.ts # MessageType, messageSchema
13+
│ ├── kakao/
14+
│ │ ├── kakaoOption.ts # BMS validation, VariableValidationError
15+
│ │ ├── kakaoButton.ts # Discriminated union (8 types)
16+
│ │ └── bms/ # 7 BMS chat bubble schemas
17+
│ ├── rcs/ # RCS options and buttons
18+
│ └── naver/ # Naver Talk Talk
19+
├── requests/ # Input → API payload transformation
20+
│ ├── messages/ # Send, group, query requests
21+
│ ├── kakao/ # Channel/template operations
22+
│ ├── iam/ # Block list management
23+
│ └── common/datePayload.ts # Shared date range type
24+
└── responses/ # API response types (mostly type-only)
25+
```
26+
27+
## WHERE TO LOOK
28+
29+
| Task | Location | Notes |
30+
|------|----------|-------|
31+
| Add message type | `base/messages/message.ts` | Add to MessageType union |
32+
| Add BMS type | `base/kakao/bms/` + `kakaoOption.ts` | Update BMS_REQUIRED_FIELDS |
33+
| Add button variant | `base/kakao/kakaoButton.ts` | Discriminated union pattern |
34+
| Add request validation | `requests/` domain folder | Use Schema.transform |
35+
| Add response type | `responses/` domain folder | Type-only usually sufficient |
36+
37+
## CONVENTIONS
38+
39+
**Type + Schema + Class Pattern**:
40+
```typescript
41+
// 1. Type
42+
export type MyType = Schema.Schema.Type<typeof mySchema>;
43+
44+
// 2. Schema
45+
export const mySchema = Schema.Struct({
46+
field: Schema.String,
47+
optional: Schema.optional(Schema.Number),
48+
});
49+
50+
// 3. Class (optional, for runtime behavior)
51+
export class MyClass {
52+
constructor(parameter: MyType) { /* ... */ }
53+
}
54+
```
55+
56+
**Discriminated Union**:
57+
```typescript
58+
export const buttonSchema = Schema.Union(
59+
webButtonSchema, // { linkType: 'WL', ... }
60+
appButtonSchema, // { linkType: 'AL', ... }
61+
);
62+
```
63+
64+
**Custom Validation**:
65+
```typescript
66+
Schema.String.pipe(
67+
Schema.filter(isValid, { message: () => 'Error message' }),
68+
);
69+
```
70+
71+
**Transform with Validation**:
72+
```typescript
73+
Schema.transform(Schema.String, Schema.String, {
74+
decode: input => normalize(input),
75+
encode: output => output,
76+
});
77+
```
78+
79+
## ANTI-PATTERNS
80+
81+
- Don't skip schema validation for user input
82+
- Don't use interfaces when schema needed — use Schema.Struct
83+
- Don't duplicate validation logic — compose schemas
84+
- Don't create class without schema — validate first

src/services/AGENTS.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Services Layer
2+
3+
## OVERVIEW
4+
5+
Domain services extending `DefaultService` base class. Each service handles one API domain.
6+
7+
## STRUCTURE
8+
9+
```
10+
services/
11+
├── defaultService.ts # Base class: auth, HTTP abstraction
12+
├── messages/
13+
│ ├── messageService.ts # send(), sendOne(), getMessages()
14+
│ └── groupService.ts # Group operations (create, add, send)
15+
├── kakao/
16+
│ ├── channels/ # Channel CRUD
17+
│ └── templates/ # Template CRUD with Effect.all
18+
├── cash/cashService.ts # getBalance()
19+
├── iam/iamService.ts # Block lists, 080 rejection
20+
└── storage/storageService.ts # File uploads
21+
```
22+
23+
## WHERE TO LOOK
24+
25+
| Task | File | Notes |
26+
|------|------|-------|
27+
| Add new service | Create in domain folder | Extend DefaultService |
28+
| Modify HTTP behavior | `defaultService.ts` | Base URL, auth handling |
29+
| Complex Effect logic | `messageService.ts` | Reference for Effect.gen pattern |
30+
| Parallel processing | `kakaoTemplateService.ts` | Effect.all example |
31+
32+
## CONVENTIONS
33+
34+
**Service Pattern**:
35+
```typescript
36+
export default class MyService extends DefaultService {
37+
constructor(apiKey: string, apiSecret: string) {
38+
super(apiKey, apiSecret);
39+
}
40+
41+
async myMethod(data: Request): Promise<Response> {
42+
return this.request<Request, Response>({
43+
httpMethod: 'POST',
44+
url: 'my/endpoint',
45+
body: data,
46+
});
47+
}
48+
}
49+
```
50+
51+
**Effect.gen Pattern** (for complex logic):
52+
```typescript
53+
async send(messages: Request): Promise<Response> {
54+
const effect = Effect.gen(function* (_) {
55+
const validated = yield* _(validateSchema(messages));
56+
const response = yield* _(Effect.promise(() => this.request(...)));
57+
return response;
58+
});
59+
return runSafePromise(effect);
60+
}
61+
```
62+
63+
## ANTI-PATTERNS
64+
65+
- Don't call `defaultFetcher` directly — use `this.request()`
66+
- Don't bypass schema validation — always validate input
67+
- Don't mix Effect and Promise styles — pick one per method

0 commit comments

Comments
 (0)