@ftisindia/sdk
v0.3.0
Published
Per-project client SDK generator for FTIS backends — emits a self-contained Dart/Flutter package from a backend's OpenAPI contract.
Readme
@ftisindia/sdk
ftis-sdk — per-project client SDK generator for FTIS backends. Reads a
backend's OpenAPI contract (including custom modules and the x-ftis vendor
extensions) and emits a self-contained Dart/Flutter package: the
ftis_core runtime (vendored) plus typed models, per-tag API classes, an
org-scoped facade, permission-key constants, and — on by default — the full
clean-architecture stack (domain entities, pure request inputs,
repository contracts + implementations, one use case per operation, and a
sealed Result/Failure core), so a BLoC app only hand-writes its
presentation layer.
# inside a scaffolded FTIS backend
npm run sdk:generate # → sdk/dart/<name>_sdk/
# against any backend
ftis-sdk generate --backend ./my-backend # exports the contract fresh
ftis-sdk generate --backend https://api.example.com --out ./my_sdk # fetches /docs-json (URL mode requires --out)Flutter apps consume the output via a path or git dependency — no pub.dev
needed. See the generated package's README and the backend template's
docs/SDK.md for usage; run ftis-sdk --help for all flags
(--use-existing, --strict, --force, --name, --out, --no-domain).
Clean-architecture layers
The generated tree mirrors the standard Flutter clean-architecture layout:
lib/src/generated/
├─ core/ Result, Failure, PagedResult, guardApiCall
├─ data/
│ ├─ models/ wire models (JSON) — *_model.dart
│ ├─ datasources/ typed API classes (remote data sources)
│ ├─ mappers/ model ↔ domain conversions
│ └─ repositories/ repository implementations
└─ domain/ entities/ inputs/ repositories/ usecases/<feature>/Backend schema names ending in Dto are emitted as *Model Dart classes
(SettingResponseDto → SettingResponseModel in
setting_response_model.dart); a rare name collision with an existing
*Model schema keeps the original name with a warning.
Everything is derived deterministically from the contract: response-reachable
models become pure *Entity classes (plus toEntity() mappers), request-only
models become pure *Input classes (plus toModel() mappers), each API class
becomes a repository contract + implementation, and every operation becomes a
<Feature><Action>UseCase returning Future<Result<T>> (ApiException and
transport errors normalize into a Failure value in one generated guard).
Domain files import nothing outside the domain layer and the pure core trio
(result/failure/paged_result) — enforced by tests.
Session-managed auth operations (login, signup, oauth/exchange,
refresh, logout) delegate to the vendored AuthEngine instead of the raw
API so token storage and silent refresh keep working; the engine surface the
templates rely on is pinned by test/engine-surface.test.mjs. Skipped
operations get no domain surface. Pass --no-domain to emit only
data/models/ + data/datasources/ (same nested layout, no core/domain/
mapper/repository layers).
How generation is driven
The backend declares SDK-relevant facts in its OpenAPI document
(x-ftis block, x-ftis-public, x-ftis-skip — see the template's
src/common/swagger/ftis-sdk.decorators.ts). The generator consumes those
first; documents without any x-ftis metadata fall back to heuristics (path
allowlists + a hard-coded skip ledger) with a warning. --strict turns
warnings into failures.
Operations a typed client cannot call (file streams, multipart uploads,
browser redirects, dual-format exports) are listed in the generated README
and stay reachable through the SDK's raw request escape hatch.
Two behaviours worth knowing:
- The heuristic fallback can rot. The ledger in
src/ir.mjs(FALLBACK_SKIP_LEDGER,FALLBACK_PUBLIC_PATHS) hard-codes template routes as they existed before thex-ftisdecorators; it is only consulted for contracts with nox-ftisblock (typically URL mode against an old deployment). If those routes have since changed, fallback output can be wrong — prefer regenerating the backend so the contract carries metadata, and treat the fallback warning (fatal under--strict) as a prompt to do so. - Pagination detection is shape-exact. A response schema maps to the
runtime's
Page<T>only when it has exactly two properties,items(array) +nextCursor. Adding any field to a page envelope silently demotes it to a plain generated class — intentional, so enriched envelopes keep their fields, but worth knowing when you extend list responses.
Development (monorepo)
The Dart runtime lives at mobile-starter/packages/ftis_core and is baked
into assets/ by scripts/sync-sdk-runtime.mjs at prepack/pretest (the
same lifecycle packages/cli uses for its template). Tests:
npm test (goldens under test/golden/; refresh deliberately with
node test/update-golden.mjs after contract or emitter changes).
