|
| 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). |
0 commit comments