nebula_api_studio 0.1.1
nebula_api_studio: ^0.1.1 copied to clipboard
A professional Dart/Flutter package combining a Compiler + Runtime SDK. Convert OpenAPI 3.x / Swagger 2.0 specs into fully type-safe Dart SDKs with smart caching, retry policies, a plugin system, offl [...]
π Nebula API Studio #
A complete Dart/Flutter toolkit for consuming REST APIs β from OpenAPI/Swagger spec to production-ready client code, with a powerful runtime layer.
Table of Contents #
- Overview
- Features
- Architecture
- Installation
- Quick Start
- CLI Usage
- Runtime Configuration
- Plugins
- Caching
- Retry & Resilience
- Offline Support
- Code Generation
- Configuration Reference
- Testing
- Contributing
- Changelog
- License
Overview #
Nebula API Studio is a monolithic Dart package that covers the entire API lifecycle:
| Layer | What it does |
|---|---|
| Compiler | Parses OpenAPI 3.x / Swagger 2.0 specs β builds an AST β optimizes β emits null-safe Dart code |
| Runtime | NebulaClient with plugin chain, caching, retry, offline queue and a typed Result<T> monad |
| CLI | nebula generate, nebula watch, nebula init, nebula doctor commands |
Features #
Compiler #
- β OpenAPI 3.0 / 3.1 parser
- β Swagger 2.0 parser
- β AST-based intermediate representation
- β Name normalizer (snake_case β PascalCase / camelCase, reserved-keyword escaping)
- β Null-safety inferrer (required fields β non-nullable, optional β nullable)
- β Schema deduplicator (removes exact duplicates, renames structural conflicts)
- β Code emitter (models, enums, services)
- β Pluggable parser interface for custom spec formats
Runtime #
- β
NebulaClientβ single entry point for all HTTP calls - β
Result<T>monad β no more uncaught exceptions - β
Typed
ApiErrorwith status code, machine-readable code and details map - β
HttpAdapterinterface β swap real HTTP for mocks in tests - β
Plugin chain β
AuthPlugin,LoggingPlugin,AnalyticsPlugin+ custom - β
MemoryCachewith TTL and LRU eviction - β
CachePolicyβ configurable per-method caching - β
InvalidationGraphβ tag-based cascading cache invalidation - β
RetryPolicy+ExponentialBackoff(with optional jitter) - β
RetryEngineβ transparent retry orchestration - β
Offline
RequestQueue+SyncEnginefor mobile resilience
CLI #
- β
nebula generateβ one-shot code generation - β
nebula watchβ incremental regeneration on file change - β
nebula initβ scaffold a new project withnebula.config.json - β
nebula doctorβ environment health check - β
ConfigLoaderβ JSON config with CLI overrides
Architecture #
nebula_api_studio/
βββ lib/
β βββ nebula_api_studio.dart β Public API barrel
β βββ cli/
β β βββ commands/
β β β βββ generate_command.dart
β β β βββ watch_command.dart
β β β βββ init_command.dart
β β β βββ doctor_command.dart
β β βββ config_loader.dart
β β βββ watcher.dart
β βββ src/
β βββ compiler/
β β βββ ast/ β ApiAst, ApiEndpoint, ApiModel, ApiType
β β βββ parser/ β ParserInterface, OpenApiParser, SwaggerParser
β β βββ optimizer/ β NameNormalizer, NullSafetyInferrer, SchemaDeduplicator
β β βββ generator/ β CodeEmitter, ModelGenerator, EnumGenerator, ServiceGenerator
β β βββ nebula_compiler.dart β Compiler faΓ§ade
β βββ runtime/
β β βββ core/ β Result, ApiError, ApiRequest, ApiResponse
β β βββ client/ β NebulaClient, HttpAdapter, DefaultAdapter
β β βββ cache/ β CacheProvider, MemoryCache, CachePolicy, InvalidationGraph
β β βββ retry/ β RetryPolicy, ExponentialBackoff, RetryEngine
β β βββ plugins/ β ApiPlugin, PluginChain, AuthPlugin, LoggingPlugin, AnalyticsPlugin
β β βββ offline/ β RequestQueue, SyncEngine
β βββ shared/
β βββ nebula_config.dart
β βββ exceptions.dart
βββ bin/
β βββ nebula.dart β CLI entry point
βββ example/
β βββ main.dart β Full usage demo
β βββ swagger.yaml β Pet Store OpenAPI spec
β βββ nebula.config.json β Example config
βββ test/
βββ compiler/ β ast_test, parser_test, optimizer_test
βββ runtime/ β result_test, client_test, cache_test, retry_plugin_test
βββ cli/ β config_loader_test
Installation #
Add to your pubspec.yaml:
dependencies:
nebula_api_studio: ^1.0.0
Or for CLI usage, activate globally:
dart pub global activate nebula_api_studio
Quick Start #
1 β Generate code from a spec #
# Create nebula.config.json in your project root
nebula init
# Edit nebula.config.json to point to your spec
# Then generate
nebula generate
2 β Use the runtime client #
import 'package:nebula_api_studio/nebula_api_studio.dart';
final client = NebulaClient(
adapter: DefaultAdapter(),
config: NebulaConfig(
baseUrl: 'https://api.example.com/v1',
timeout: const Duration(seconds: 30),
defaultHeaders: {'Accept': 'application/json'},
),
plugins: [
AuthPlugin(
tokenProvider: () async => await getAccessToken(),
headerName: 'Authorization',
scheme: 'Bearer',
),
LoggingPlugin(logRequests: true, logResponses: true),
],
retryPolicy: RetryPolicy(
maxAttempts: 3,
retryOn: {408, 429, 500, 502, 503, 504},
backoff: ExponentialBackoff(
initialDelay: const Duration(milliseconds: 200),
multiplier: 2.0,
maxDelay: const Duration(seconds: 10),
jitter: true,
),
),
cachePolicy: CachePolicy(
enabled: true,
ttl: const Duration(minutes: 5),
maxEntries: 500,
),
);
// Execute a request
final result = await client.execute(
ApiRequest(method: 'GET', path: '/pets/pet-001'),
);
result.when(
success: (response) {
final pet = Pet.fromJson(response.body as Map<String, dynamic>);
print('Got pet: ${pet.name}');
},
failure: (error) {
print('Error ${error.statusCode}: ${error.message}');
},
);
// Always close when done
client.close();
3 β Result monad chaining #
final names = (await petsService.listPets())
.map((pets) => pets.map((p) => p.name).toList())
.getOrElse([]);
print(names); // ['Buddy', 'Rio', 'Nemo']
CLI Usage #
# Initialize a new project
nebula init [--output-dir lib/generated]
# Generate code (uses nebula.config.json in current directory)
nebula generate [--config path/to/nebula.config.json] [--verbose]
# Watch for spec changes and regenerate automatically
nebula watch [--config path/to/nebula.config.json]
# Check environment health
nebula doctor
Runtime Configuration #
final config = NebulaConfig(
baseUrl: 'https://api.example.com/v1', // Required
timeout: const Duration(seconds: 30), // Default: 30s
defaultHeaders: { // Merged into every request
'Accept': 'application/json',
'X-App-Version': '2.0.0',
},
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
);
Plugins #
Plugins intercept the request/response lifecycle. Implement ApiPlugin:
class TimingPlugin implements ApiPlugin {
final _stopwatches = <String, Stopwatch>{};
@override
Future<ApiRequest> beforeRequest(ApiRequest request) async {
_stopwatches[request.path] = Stopwatch()..start();
return request;
}
@override
Future<ApiResponse> afterResponse(ApiRequest req, ApiResponse res) async {
final elapsed = _stopwatches.remove(req.path)?.elapsedMilliseconds;
print('${req.method} ${req.path} β ${res.statusCode} (${elapsed}ms)');
return res;
}
@override
Future<ApiError?> onError(ApiRequest req, ApiError error) async => error;
}
Built-in Plugins #
| Plugin | Purpose |
|---|---|
AuthPlugin |
Adds Bearer / API-key / custom auth headers |
LoggingPlugin |
Structured request/response logging with log-level control |
AnalyticsPlugin |
Fires events on every request for analytics backends |
Caching #
// Client-level policy
final client = NebulaClient(
...
cachePolicy: CachePolicy(
enabled: true,
ttl: const Duration(minutes: 10),
maxEntries: 1000,
cacheableMethods: {'GET', 'HEAD'},
),
);
// Tag-based invalidation
final graph = InvalidationGraph();
graph.addDependency(tag: 'pets', invalidates: ['pet-list', 'pet-search']);
// Invalidating 'pets' automatically clears 'pet-list' and 'pet-search'
Retry & Resilience #
final policy = RetryPolicy(
maxAttempts: 4,
retryOn: {408, 429, 500, 502, 503, 504},
backoff: ExponentialBackoff(
initialDelay: const Duration(milliseconds: 100),
multiplier: 2.0,
maxDelay: const Duration(seconds: 30),
jitter: true, // Β±25% random jitter to avoid thundering herd
),
);
Offline Support #
// The SyncEngine replays queued requests when connectivity is restored
final syncEngine = SyncEngine(
queue: RequestQueue(),
client: client,
onSync: (result) => print('Synced: $result'),
);
// Register connectivity listener
connectivityStream.listen((isOnline) {
if (isOnline) syncEngine.flush();
});
Code Generation #
nebula.config.json #
{
"input": "swagger.yaml",
"output": "lib/generated",
"packageName": "my_api",
"options": {
"nullSafety": true,
"generateModels": true,
"generateServices": true,
"generateEnums": true,
"useJsonSerializable": true,
"addCopyWith": true,
"addEquality": true,
"addToString": true,
"dateTimeType": "DateTime",
"enumSuffix": "Type",
"serviceSuffix": "Service"
},
"runtime": {
"baseUrl": "https://api.example.com/v1",
"timeout": 30000,
"retries": 3,
"cacheEnabled": true,
"defaultCacheDuration": 300,
"offlineEnabled": true
},
"plugins": ["auth", "logging", "analytics"]
}
Generated file structure #
lib/generated/
βββ models/
β βββ pet.dart
β βββ owner.dart
β βββ order.dart
β βββ address.dart
βββ enums/
β βββ pet_status_type.dart
β βββ pet_category_type.dart
β βββ order_status_type.dart
βββ services/
βββ pets_service.dart
βββ owners_service.dart
βββ orders_service.dart
Configuration Reference #
| Key | Type | Default | Description |
|---|---|---|---|
input |
string |
β | Path to the OpenAPI/Swagger spec file (required) |
output |
string |
lib/generated |
Output directory for generated code |
packageName |
string |
generated_api |
Dart package name prefix for generated files |
options.nullSafety |
bool |
true |
Enable Dart null-safety annotations |
options.generateModels |
bool |
true |
Generate model classes |
options.generateServices |
bool |
true |
Generate service classes |
options.generateEnums |
bool |
true |
Generate enum types |
options.useJsonSerializable |
bool |
true |
Use json_serializable annotations |
options.addCopyWith |
bool |
true |
Add copyWith() to models |
options.addEquality |
bool |
true |
Add == / hashCode to models |
options.enumSuffix |
string |
Type |
Suffix appended to generated enum names |
options.serviceSuffix |
string |
Service |
Suffix appended to generated service names |
runtime.baseUrl |
string |
β | Base URL injected into generated service constructors |
runtime.timeout |
int |
30000 |
Default request timeout in milliseconds |
runtime.retries |
int |
3 |
Default maximum retry attempts |
runtime.cacheEnabled |
bool |
true |
Enable response caching |
runtime.defaultCacheDuration |
int |
300 |
Default cache TTL in seconds |
runtime.offlineEnabled |
bool |
false |
Enable offline request queue |
Testing #
# Run all tests
dart test
# Run with coverage
dart test --coverage=coverage
dart pub global run coverage:format_coverage \
--lcov --in=coverage --out=coverage/lcov.info \
--report-on=lib
# Run specific test file
dart test test/runtime/result_test.dart
# Run tests matching a pattern
dart test --name "RetryEngine"
Contributing #
- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Commit your changes:
git commit -m 'feat: add my feature' - Push to the branch:
git push origin feat/my-feature - Open a Pull Request
Please run dart analyze and dart test before submitting.
Changelog #
See CHANGELOG.md for the full history.
License #
MIT Β© 2026 Nebula API Studio contributors. See LICENSE.