feat(sdk): add Database, Hosting, and Bridge SDKs for Flutter
Phase 3 SDK expansion - Flutter/Dart implementations: - Database SDK: Multi-model database with Key-Value, Document, Vector, and Time Series stores - Hosting SDK: Decentralized web hosting with domain management, DNS, deployments, and SSL provisioning - Bridge SDK: Cross-chain asset transfers with lock-mint and burn-unlock patterns All SDKs follow consistent patterns with: - Async/await API using Futures - Proper error handling with typed exceptions - Configurable retry logic and timeouts - Full type safety with Dart's type system
This commit is contained in:
parent
74b82d2bb2
commit
dd01a06116
7 changed files with 2109 additions and 0 deletions
325
sdk/flutter/lib/src/bridge/client.dart
Normal file
325
sdk/flutter/lib/src/bridge/client.dart
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
/// Synor Bridge SDK Client for Flutter
|
||||
///
|
||||
/// Cross-chain asset transfers with lock-mint and burn-unlock patterns.
|
||||
library synor_bridge;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'types.dart';
|
||||
|
||||
export 'types.dart';
|
||||
|
||||
/// Synor Bridge Client
|
||||
class SynorBridge {
|
||||
final BridgeConfig config;
|
||||
final http.Client _client;
|
||||
bool _closed = false;
|
||||
|
||||
static const _finalStatuses = {
|
||||
TransferStatus.completed,
|
||||
TransferStatus.failed,
|
||||
TransferStatus.refunded,
|
||||
};
|
||||
|
||||
SynorBridge(this.config) : _client = http.Client();
|
||||
|
||||
// ==================== Chain Operations ====================
|
||||
|
||||
Future<List<Chain>> getSupportedChains() async {
|
||||
final resp = await _request('GET', '/chains');
|
||||
return (resp['chains'] as List).map((e) => Chain.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<Chain> getChain(ChainId chainId) async {
|
||||
final resp = await _request('GET', '/chains/${chainId.name}');
|
||||
return Chain.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<bool> isChainSupported(ChainId chainId) async {
|
||||
try {
|
||||
final chain = await getChain(chainId);
|
||||
return chain.supported;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Asset Operations ====================
|
||||
|
||||
Future<List<Asset>> getSupportedAssets(ChainId chainId) async {
|
||||
final resp = await _request('GET', '/chains/${chainId.name}/assets');
|
||||
return (resp['assets'] as List).map((e) => Asset.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<Asset> getAsset(String assetId) async {
|
||||
final resp = await _request('GET', '/assets/${Uri.encodeComponent(assetId)}');
|
||||
return Asset.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<WrappedAsset> getWrappedAsset(String originalAssetId, ChainId targetChain) async {
|
||||
final path = '/assets/${Uri.encodeComponent(originalAssetId)}/wrapped/${targetChain.name}';
|
||||
final resp = await _request('GET', path);
|
||||
return WrappedAsset.fromJson(resp);
|
||||
}
|
||||
|
||||
// ==================== Fee & Rate Operations ====================
|
||||
|
||||
Future<FeeEstimate> estimateFee(String asset, String amount, ChainId sourceChain, ChainId targetChain) async {
|
||||
final resp = await _request('POST', '/fees/estimate', {
|
||||
'asset': asset,
|
||||
'amount': amount,
|
||||
'sourceChain': sourceChain.name,
|
||||
'targetChain': targetChain.name,
|
||||
});
|
||||
return FeeEstimate.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<ExchangeRate> getExchangeRate(String fromAsset, String toAsset) async {
|
||||
final path = '/rates/${Uri.encodeComponent(fromAsset)}/${Uri.encodeComponent(toAsset)}';
|
||||
final resp = await _request('GET', path);
|
||||
return ExchangeRate.fromJson(resp);
|
||||
}
|
||||
|
||||
// ==================== Lock-Mint Flow ====================
|
||||
|
||||
Future<LockReceipt> lock(String asset, String amount, ChainId targetChain, {LockOptions? options}) async {
|
||||
final body = <String, dynamic>{
|
||||
'asset': asset,
|
||||
'amount': amount,
|
||||
'targetChain': targetChain.name,
|
||||
};
|
||||
if (options != null) body.addAll(options.toJson());
|
||||
final resp = await _request('POST', '/transfers/lock', body);
|
||||
return LockReceipt.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<LockProof> getLockProof(String lockReceiptId) async {
|
||||
final resp = await _request('GET', '/transfers/lock/${Uri.encodeComponent(lockReceiptId)}/proof');
|
||||
return LockProof.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<LockProof> waitForLockProof(
|
||||
String lockReceiptId, {
|
||||
Duration pollInterval = const Duration(seconds: 5),
|
||||
Duration maxWait = const Duration(minutes: 10),
|
||||
}) async {
|
||||
final deadline = DateTime.now().add(maxWait);
|
||||
while (DateTime.now().isBefore(deadline)) {
|
||||
try {
|
||||
return await getLockProof(lockReceiptId);
|
||||
} on BridgeException catch (e) {
|
||||
if (e.isConfirmationsPending) {
|
||||
await Future.delayed(pollInterval);
|
||||
continue;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
throw const BridgeException('Timeout waiting for lock proof', code: 'CONFIRMATIONS_PENDING');
|
||||
}
|
||||
|
||||
Future<SignedTransaction> mint(LockProof proof, String targetAddress, {MintOptions? options}) async {
|
||||
final body = <String, dynamic>{
|
||||
'proof': proof.toJson(),
|
||||
'targetAddress': targetAddress,
|
||||
};
|
||||
if (options != null) body.addAll(options.toJson());
|
||||
final resp = await _request('POST', '/transfers/mint', body);
|
||||
return SignedTransaction.fromJson(resp);
|
||||
}
|
||||
|
||||
// ==================== Burn-Unlock Flow ====================
|
||||
|
||||
Future<BurnReceipt> burn(String wrappedAsset, String amount, {BurnOptions? options}) async {
|
||||
final body = <String, dynamic>{
|
||||
'wrappedAsset': wrappedAsset,
|
||||
'amount': amount,
|
||||
};
|
||||
if (options != null) body.addAll(options.toJson());
|
||||
final resp = await _request('POST', '/transfers/burn', body);
|
||||
return BurnReceipt.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<BurnProof> getBurnProof(String burnReceiptId) async {
|
||||
final resp = await _request('GET', '/transfers/burn/${Uri.encodeComponent(burnReceiptId)}/proof');
|
||||
return BurnProof.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<BurnProof> waitForBurnProof(
|
||||
String burnReceiptId, {
|
||||
Duration pollInterval = const Duration(seconds: 5),
|
||||
Duration maxWait = const Duration(minutes: 10),
|
||||
}) async {
|
||||
final deadline = DateTime.now().add(maxWait);
|
||||
while (DateTime.now().isBefore(deadline)) {
|
||||
try {
|
||||
return await getBurnProof(burnReceiptId);
|
||||
} on BridgeException catch (e) {
|
||||
if (e.isConfirmationsPending) {
|
||||
await Future.delayed(pollInterval);
|
||||
continue;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
throw const BridgeException('Timeout waiting for burn proof', code: 'CONFIRMATIONS_PENDING');
|
||||
}
|
||||
|
||||
Future<SignedTransaction> unlock(BurnProof proof, {UnlockOptions? options}) async {
|
||||
final body = <String, dynamic>{'proof': proof.toJson()};
|
||||
if (options != null) body.addAll(options.toJson());
|
||||
final resp = await _request('POST', '/transfers/unlock', body);
|
||||
return SignedTransaction.fromJson(resp);
|
||||
}
|
||||
|
||||
// ==================== Transfer Management ====================
|
||||
|
||||
Future<Transfer> getTransfer(String transferId) async {
|
||||
final resp = await _request('GET', '/transfers/${Uri.encodeComponent(transferId)}');
|
||||
return Transfer.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<TransferStatus> getTransferStatus(String transferId) async {
|
||||
final transfer = await getTransfer(transferId);
|
||||
return transfer.status;
|
||||
}
|
||||
|
||||
Future<List<Transfer>> listTransfers({TransferFilter? filter}) async {
|
||||
var path = '/transfers';
|
||||
final params = <String>[];
|
||||
if (filter?.status != null) params.add('status=${filter!.status!.name}');
|
||||
if (filter?.sourceChain != null) params.add('sourceChain=${filter!.sourceChain!.name}');
|
||||
if (filter?.targetChain != null) params.add('targetChain=${filter!.targetChain!.name}');
|
||||
if (filter?.limit != null) params.add('limit=${filter!.limit}');
|
||||
if (filter?.offset != null) params.add('offset=${filter!.offset}');
|
||||
if (params.isNotEmpty) path = '$path?${params.join('&')}';
|
||||
|
||||
final resp = await _request('GET', path);
|
||||
return (resp['transfers'] as List).map((e) => Transfer.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<Transfer> waitForTransfer(
|
||||
String transferId, {
|
||||
Duration pollInterval = const Duration(seconds: 10),
|
||||
Duration maxWait = const Duration(minutes: 30),
|
||||
}) async {
|
||||
final deadline = DateTime.now().add(maxWait);
|
||||
while (DateTime.now().isBefore(deadline)) {
|
||||
final transfer = await getTransfer(transferId);
|
||||
if (_finalStatuses.contains(transfer.status)) {
|
||||
return transfer;
|
||||
}
|
||||
await Future.delayed(pollInterval);
|
||||
}
|
||||
throw const BridgeException('Timeout waiting for transfer completion');
|
||||
}
|
||||
|
||||
// ==================== Convenience Methods ====================
|
||||
|
||||
Future<Transfer> bridgeTo(
|
||||
String asset,
|
||||
String amount,
|
||||
ChainId targetChain,
|
||||
String targetAddress, {
|
||||
LockOptions? lockOptions,
|
||||
MintOptions? mintOptions,
|
||||
}) async {
|
||||
final lockReceipt = await lock(asset, amount, targetChain, options: lockOptions);
|
||||
if (config.debug) print('Locked: ${lockReceipt.id}, waiting for confirmations...');
|
||||
|
||||
final proof = await waitForLockProof(lockReceipt.id);
|
||||
if (config.debug) print('Proof ready, minting on ${targetChain.name}...');
|
||||
|
||||
await mint(proof, targetAddress, options: mintOptions);
|
||||
return waitForTransfer(lockReceipt.id);
|
||||
}
|
||||
|
||||
Future<Transfer> bridgeBack(
|
||||
String wrappedAsset,
|
||||
String amount, {
|
||||
BurnOptions? burnOptions,
|
||||
UnlockOptions? unlockOptions,
|
||||
}) async {
|
||||
final burnReceipt = await burn(wrappedAsset, amount, options: burnOptions);
|
||||
if (config.debug) print('Burned: ${burnReceipt.id}, waiting for confirmations...');
|
||||
|
||||
final proof = await waitForBurnProof(burnReceipt.id);
|
||||
if (config.debug) print('Proof ready, unlocking on ${burnReceipt.targetChain.name}...');
|
||||
|
||||
await unlock(proof, options: unlockOptions);
|
||||
return waitForTransfer(burnReceipt.id);
|
||||
}
|
||||
|
||||
// ==================== Lifecycle ====================
|
||||
|
||||
void close() {
|
||||
_closed = true;
|
||||
_client.close();
|
||||
}
|
||||
|
||||
bool get isClosed => _closed;
|
||||
|
||||
Future<bool> healthCheck() async {
|
||||
try {
|
||||
final resp = await _request('GET', '/health');
|
||||
return resp['status'] == 'healthy';
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Private Methods ====================
|
||||
|
||||
Future<Map<String, dynamic>> _request(String method, String path, [Map<String, dynamic>? body]) async {
|
||||
if (_closed) throw const BridgeException('Client has been closed');
|
||||
|
||||
Exception? lastError;
|
||||
for (var attempt = 0; attempt < config.retries; attempt++) {
|
||||
try {
|
||||
return await _doRequest(method, path, body);
|
||||
} catch (e) {
|
||||
lastError = e as Exception;
|
||||
if (attempt < config.retries - 1) {
|
||||
await Future.delayed(Duration(seconds: 1 << attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError ?? const BridgeException('Unknown error');
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _doRequest(String method, String path, Map<String, dynamic>? body) async {
|
||||
final uri = Uri.parse('${config.endpoint}$path');
|
||||
final headers = {
|
||||
'Authorization': 'Bearer ${config.apiKey}',
|
||||
'Content-Type': 'application/json',
|
||||
'X-SDK-Version': 'flutter/0.1.0',
|
||||
};
|
||||
|
||||
http.Response response;
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
response = await _client.get(uri, headers: headers);
|
||||
break;
|
||||
case 'POST':
|
||||
response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null);
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await _client.delete(uri, headers: headers);
|
||||
break;
|
||||
default:
|
||||
throw BridgeException('Unknown method: $method');
|
||||
}
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
final error = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
throw BridgeException(
|
||||
error['message'] as String? ?? 'HTTP ${response.statusCode}',
|
||||
code: error['code'] as String?,
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
if (response.body.isEmpty) return {};
|
||||
return jsonDecode(response.body) as Map<String, dynamic>;
|
||||
}
|
||||
}
|
||||
636
sdk/flutter/lib/src/bridge/types.dart
Normal file
636
sdk/flutter/lib/src/bridge/types.dart
Normal file
|
|
@ -0,0 +1,636 @@
|
|||
/// Synor Bridge SDK Types for Flutter
|
||||
///
|
||||
/// Cross-chain asset transfers with lock-mint and burn-unlock patterns.
|
||||
library synor_bridge_types;
|
||||
|
||||
// ==================== Chain Types ====================
|
||||
|
||||
/// Supported blockchain networks
|
||||
enum ChainId { synor, ethereum, polygon, arbitrum, optimism, bsc, avalanche, solana, cosmos }
|
||||
|
||||
/// Native currency info
|
||||
class NativeCurrency {
|
||||
final String name;
|
||||
final String symbol;
|
||||
final int decimals;
|
||||
|
||||
const NativeCurrency({required this.name, required this.symbol, required this.decimals});
|
||||
|
||||
factory NativeCurrency.fromJson(Map<String, dynamic> json) => NativeCurrency(
|
||||
name: json['name'] as String,
|
||||
symbol: json['symbol'] as String,
|
||||
decimals: json['decimals'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
/// Chain information
|
||||
class Chain {
|
||||
final ChainId id;
|
||||
final String name;
|
||||
final int chainId;
|
||||
final String rpcUrl;
|
||||
final String explorerUrl;
|
||||
final NativeCurrency nativeCurrency;
|
||||
final int confirmations;
|
||||
final int estimatedBlockTime;
|
||||
final bool supported;
|
||||
|
||||
const Chain({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.chainId,
|
||||
required this.rpcUrl,
|
||||
required this.explorerUrl,
|
||||
required this.nativeCurrency,
|
||||
required this.confirmations,
|
||||
required this.estimatedBlockTime,
|
||||
required this.supported,
|
||||
});
|
||||
|
||||
factory Chain.fromJson(Map<String, dynamic> json) => Chain(
|
||||
id: ChainId.values.firstWhere((e) => e.name == json['id']),
|
||||
name: json['name'] as String,
|
||||
chainId: json['chainId'] as int,
|
||||
rpcUrl: json['rpcUrl'] as String,
|
||||
explorerUrl: json['explorerUrl'] as String,
|
||||
nativeCurrency: NativeCurrency.fromJson(json['nativeCurrency']),
|
||||
confirmations: json['confirmations'] as int,
|
||||
estimatedBlockTime: json['estimatedBlockTime'] as int,
|
||||
supported: json['supported'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Asset Types ====================
|
||||
|
||||
/// Asset type
|
||||
enum AssetType { native, erc20, erc721, erc1155 }
|
||||
|
||||
/// Asset information
|
||||
class Asset {
|
||||
final String id;
|
||||
final String symbol;
|
||||
final String name;
|
||||
final AssetType type;
|
||||
final ChainId chain;
|
||||
final String? contractAddress;
|
||||
final int decimals;
|
||||
final String? logoUrl;
|
||||
final bool verified;
|
||||
|
||||
const Asset({
|
||||
required this.id,
|
||||
required this.symbol,
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.chain,
|
||||
this.contractAddress,
|
||||
required this.decimals,
|
||||
this.logoUrl,
|
||||
this.verified = false,
|
||||
});
|
||||
|
||||
factory Asset.fromJson(Map<String, dynamic> json) => Asset(
|
||||
id: json['id'] as String,
|
||||
symbol: json['symbol'] as String,
|
||||
name: json['name'] as String,
|
||||
type: AssetType.values.firstWhere((e) => e.name == json['type']),
|
||||
chain: ChainId.values.firstWhere((e) => e.name == json['chain']),
|
||||
contractAddress: json['contractAddress'] as String?,
|
||||
decimals: json['decimals'] as int,
|
||||
logoUrl: json['logoUrl'] as String?,
|
||||
verified: json['verified'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'symbol': symbol,
|
||||
'name': name,
|
||||
'type': type.name,
|
||||
'chain': chain.name,
|
||||
if (contractAddress != null) 'contractAddress': contractAddress,
|
||||
'decimals': decimals,
|
||||
if (logoUrl != null) 'logoUrl': logoUrl,
|
||||
'verified': verified,
|
||||
};
|
||||
}
|
||||
|
||||
/// Wrapped asset mapping
|
||||
class WrappedAsset {
|
||||
final Asset originalAsset;
|
||||
final Asset wrappedAsset;
|
||||
final ChainId chain;
|
||||
final String bridgeContract;
|
||||
|
||||
const WrappedAsset({
|
||||
required this.originalAsset,
|
||||
required this.wrappedAsset,
|
||||
required this.chain,
|
||||
required this.bridgeContract,
|
||||
});
|
||||
|
||||
factory WrappedAsset.fromJson(Map<String, dynamic> json) => WrappedAsset(
|
||||
originalAsset: Asset.fromJson(json['originalAsset']),
|
||||
wrappedAsset: Asset.fromJson(json['wrappedAsset']),
|
||||
chain: ChainId.values.firstWhere((e) => e.name == json['chain']),
|
||||
bridgeContract: json['bridgeContract'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Transfer Types ====================
|
||||
|
||||
/// Transfer status
|
||||
enum TransferStatus { pending, locked, confirming, minting, completed, failed, refunded }
|
||||
|
||||
/// Transfer direction
|
||||
enum TransferDirection { lockMint, burnUnlock }
|
||||
|
||||
/// Validator signature
|
||||
class ValidatorSignature {
|
||||
final String validator;
|
||||
final String signature;
|
||||
final int timestamp;
|
||||
|
||||
const ValidatorSignature({
|
||||
required this.validator,
|
||||
required this.signature,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
factory ValidatorSignature.fromJson(Map<String, dynamic> json) => ValidatorSignature(
|
||||
validator: json['validator'] as String,
|
||||
signature: json['signature'] as String,
|
||||
timestamp: json['timestamp'] as int,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'validator': validator,
|
||||
'signature': signature,
|
||||
'timestamp': timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
/// Lock receipt
|
||||
class LockReceipt {
|
||||
final String id;
|
||||
final String txHash;
|
||||
final ChainId sourceChain;
|
||||
final ChainId targetChain;
|
||||
final Asset asset;
|
||||
final String amount;
|
||||
final String sender;
|
||||
final String recipient;
|
||||
final int lockTimestamp;
|
||||
final int confirmations;
|
||||
final int requiredConfirmations;
|
||||
|
||||
const LockReceipt({
|
||||
required this.id,
|
||||
required this.txHash,
|
||||
required this.sourceChain,
|
||||
required this.targetChain,
|
||||
required this.asset,
|
||||
required this.amount,
|
||||
required this.sender,
|
||||
required this.recipient,
|
||||
required this.lockTimestamp,
|
||||
required this.confirmations,
|
||||
required this.requiredConfirmations,
|
||||
});
|
||||
|
||||
factory LockReceipt.fromJson(Map<String, dynamic> json) => LockReceipt(
|
||||
id: json['id'] as String,
|
||||
txHash: json['txHash'] as String,
|
||||
sourceChain: ChainId.values.firstWhere((e) => e.name == json['sourceChain']),
|
||||
targetChain: ChainId.values.firstWhere((e) => e.name == json['targetChain']),
|
||||
asset: Asset.fromJson(json['asset']),
|
||||
amount: json['amount'] as String,
|
||||
sender: json['sender'] as String,
|
||||
recipient: json['recipient'] as String,
|
||||
lockTimestamp: json['lockTimestamp'] as int,
|
||||
confirmations: json['confirmations'] as int,
|
||||
requiredConfirmations: json['requiredConfirmations'] as int,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'txHash': txHash,
|
||||
'sourceChain': sourceChain.name,
|
||||
'targetChain': targetChain.name,
|
||||
'asset': asset.toJson(),
|
||||
'amount': amount,
|
||||
'sender': sender,
|
||||
'recipient': recipient,
|
||||
'lockTimestamp': lockTimestamp,
|
||||
'confirmations': confirmations,
|
||||
'requiredConfirmations': requiredConfirmations,
|
||||
};
|
||||
}
|
||||
|
||||
/// Lock proof
|
||||
class LockProof {
|
||||
final LockReceipt lockReceipt;
|
||||
final List<String> merkleProof;
|
||||
final String blockHeader;
|
||||
final List<ValidatorSignature> signatures;
|
||||
|
||||
const LockProof({
|
||||
required this.lockReceipt,
|
||||
required this.merkleProof,
|
||||
required this.blockHeader,
|
||||
required this.signatures,
|
||||
});
|
||||
|
||||
factory LockProof.fromJson(Map<String, dynamic> json) => LockProof(
|
||||
lockReceipt: LockReceipt.fromJson(json['lockReceipt']),
|
||||
merkleProof: List<String>.from(json['merkleProof']),
|
||||
blockHeader: json['blockHeader'] as String,
|
||||
signatures: (json['signatures'] as List)
|
||||
.map((e) => ValidatorSignature.fromJson(e))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'lockReceipt': lockReceipt.toJson(),
|
||||
'merkleProof': merkleProof,
|
||||
'blockHeader': blockHeader,
|
||||
'signatures': signatures.map((s) => s.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Burn receipt
|
||||
class BurnReceipt {
|
||||
final String id;
|
||||
final String txHash;
|
||||
final ChainId sourceChain;
|
||||
final ChainId targetChain;
|
||||
final Asset wrappedAsset;
|
||||
final Asset originalAsset;
|
||||
final String amount;
|
||||
final String sender;
|
||||
final String recipient;
|
||||
final int burnTimestamp;
|
||||
final int confirmations;
|
||||
final int requiredConfirmations;
|
||||
|
||||
const BurnReceipt({
|
||||
required this.id,
|
||||
required this.txHash,
|
||||
required this.sourceChain,
|
||||
required this.targetChain,
|
||||
required this.wrappedAsset,
|
||||
required this.originalAsset,
|
||||
required this.amount,
|
||||
required this.sender,
|
||||
required this.recipient,
|
||||
required this.burnTimestamp,
|
||||
required this.confirmations,
|
||||
required this.requiredConfirmations,
|
||||
});
|
||||
|
||||
factory BurnReceipt.fromJson(Map<String, dynamic> json) => BurnReceipt(
|
||||
id: json['id'] as String,
|
||||
txHash: json['txHash'] as String,
|
||||
sourceChain: ChainId.values.firstWhere((e) => e.name == json['sourceChain']),
|
||||
targetChain: ChainId.values.firstWhere((e) => e.name == json['targetChain']),
|
||||
wrappedAsset: Asset.fromJson(json['wrappedAsset']),
|
||||
originalAsset: Asset.fromJson(json['originalAsset']),
|
||||
amount: json['amount'] as String,
|
||||
sender: json['sender'] as String,
|
||||
recipient: json['recipient'] as String,
|
||||
burnTimestamp: json['burnTimestamp'] as int,
|
||||
confirmations: json['confirmations'] as int,
|
||||
requiredConfirmations: json['requiredConfirmations'] as int,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'txHash': txHash,
|
||||
'sourceChain': sourceChain.name,
|
||||
'targetChain': targetChain.name,
|
||||
'wrappedAsset': wrappedAsset.toJson(),
|
||||
'originalAsset': originalAsset.toJson(),
|
||||
'amount': amount,
|
||||
'sender': sender,
|
||||
'recipient': recipient,
|
||||
'burnTimestamp': burnTimestamp,
|
||||
'confirmations': confirmations,
|
||||
'requiredConfirmations': requiredConfirmations,
|
||||
};
|
||||
}
|
||||
|
||||
/// Burn proof
|
||||
class BurnProof {
|
||||
final BurnReceipt burnReceipt;
|
||||
final List<String> merkleProof;
|
||||
final String blockHeader;
|
||||
final List<ValidatorSignature> signatures;
|
||||
|
||||
const BurnProof({
|
||||
required this.burnReceipt,
|
||||
required this.merkleProof,
|
||||
required this.blockHeader,
|
||||
required this.signatures,
|
||||
});
|
||||
|
||||
factory BurnProof.fromJson(Map<String, dynamic> json) => BurnProof(
|
||||
burnReceipt: BurnReceipt.fromJson(json['burnReceipt']),
|
||||
merkleProof: List<String>.from(json['merkleProof']),
|
||||
blockHeader: json['blockHeader'] as String,
|
||||
signatures: (json['signatures'] as List)
|
||||
.map((e) => ValidatorSignature.fromJson(e))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'burnReceipt': burnReceipt.toJson(),
|
||||
'merkleProof': merkleProof,
|
||||
'blockHeader': blockHeader,
|
||||
'signatures': signatures.map((s) => s.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Transfer record
|
||||
class Transfer {
|
||||
final String id;
|
||||
final TransferDirection direction;
|
||||
final TransferStatus status;
|
||||
final ChainId sourceChain;
|
||||
final ChainId targetChain;
|
||||
final Asset asset;
|
||||
final String amount;
|
||||
final String sender;
|
||||
final String recipient;
|
||||
final String? sourceTxHash;
|
||||
final String? targetTxHash;
|
||||
final String fee;
|
||||
final Asset feeAsset;
|
||||
final int createdAt;
|
||||
final int updatedAt;
|
||||
final int? completedAt;
|
||||
final String? errorMessage;
|
||||
|
||||
const Transfer({
|
||||
required this.id,
|
||||
required this.direction,
|
||||
required this.status,
|
||||
required this.sourceChain,
|
||||
required this.targetChain,
|
||||
required this.asset,
|
||||
required this.amount,
|
||||
required this.sender,
|
||||
required this.recipient,
|
||||
this.sourceTxHash,
|
||||
this.targetTxHash,
|
||||
required this.fee,
|
||||
required this.feeAsset,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.completedAt,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
factory Transfer.fromJson(Map<String, dynamic> json) => Transfer(
|
||||
id: json['id'] as String,
|
||||
direction: json['direction'] == 'lock_mint'
|
||||
? TransferDirection.lockMint
|
||||
: TransferDirection.burnUnlock,
|
||||
status: TransferStatus.values.firstWhere((e) => e.name == json['status']),
|
||||
sourceChain: ChainId.values.firstWhere((e) => e.name == json['sourceChain']),
|
||||
targetChain: ChainId.values.firstWhere((e) => e.name == json['targetChain']),
|
||||
asset: Asset.fromJson(json['asset']),
|
||||
amount: json['amount'] as String,
|
||||
sender: json['sender'] as String,
|
||||
recipient: json['recipient'] as String,
|
||||
sourceTxHash: json['sourceTxHash'] as String?,
|
||||
targetTxHash: json['targetTxHash'] as String?,
|
||||
fee: json['fee'] as String,
|
||||
feeAsset: Asset.fromJson(json['feeAsset']),
|
||||
createdAt: json['createdAt'] as int,
|
||||
updatedAt: json['updatedAt'] as int,
|
||||
completedAt: json['completedAt'] as int?,
|
||||
errorMessage: json['errorMessage'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Fee Types ====================
|
||||
|
||||
/// Fee estimate
|
||||
class FeeEstimate {
|
||||
final String bridgeFee;
|
||||
final String gasFeeSource;
|
||||
final String gasFeeTarget;
|
||||
final String totalFee;
|
||||
final Asset feeAsset;
|
||||
final int estimatedTime;
|
||||
final String? exchangeRate;
|
||||
|
||||
const FeeEstimate({
|
||||
required this.bridgeFee,
|
||||
required this.gasFeeSource,
|
||||
required this.gasFeeTarget,
|
||||
required this.totalFee,
|
||||
required this.feeAsset,
|
||||
required this.estimatedTime,
|
||||
this.exchangeRate,
|
||||
});
|
||||
|
||||
factory FeeEstimate.fromJson(Map<String, dynamic> json) => FeeEstimate(
|
||||
bridgeFee: json['bridgeFee'] as String,
|
||||
gasFeeSource: json['gasFeeSource'] as String,
|
||||
gasFeeTarget: json['gasFeeTarget'] as String,
|
||||
totalFee: json['totalFee'] as String,
|
||||
feeAsset: Asset.fromJson(json['feeAsset']),
|
||||
estimatedTime: json['estimatedTime'] as int,
|
||||
exchangeRate: json['exchangeRate'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Exchange rate
|
||||
class ExchangeRate {
|
||||
final Asset fromAsset;
|
||||
final Asset toAsset;
|
||||
final String rate;
|
||||
final String inverseRate;
|
||||
final int lastUpdated;
|
||||
final String source;
|
||||
|
||||
const ExchangeRate({
|
||||
required this.fromAsset,
|
||||
required this.toAsset,
|
||||
required this.rate,
|
||||
required this.inverseRate,
|
||||
required this.lastUpdated,
|
||||
required this.source,
|
||||
});
|
||||
|
||||
factory ExchangeRate.fromJson(Map<String, dynamic> json) => ExchangeRate(
|
||||
fromAsset: Asset.fromJson(json['fromAsset']),
|
||||
toAsset: Asset.fromJson(json['toAsset']),
|
||||
rate: json['rate'] as String,
|
||||
inverseRate: json['inverseRate'] as String,
|
||||
lastUpdated: json['lastUpdated'] as int,
|
||||
source: json['source'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
/// Signed transaction
|
||||
class SignedTransaction {
|
||||
final String txHash;
|
||||
final ChainId chain;
|
||||
final String from;
|
||||
final String to;
|
||||
final String value;
|
||||
final String data;
|
||||
final String gasLimit;
|
||||
final String? gasPrice;
|
||||
final String? maxFeePerGas;
|
||||
final String? maxPriorityFeePerGas;
|
||||
final int nonce;
|
||||
final String signature;
|
||||
|
||||
const SignedTransaction({
|
||||
required this.txHash,
|
||||
required this.chain,
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.value,
|
||||
required this.data,
|
||||
required this.gasLimit,
|
||||
this.gasPrice,
|
||||
this.maxFeePerGas,
|
||||
this.maxPriorityFeePerGas,
|
||||
required this.nonce,
|
||||
required this.signature,
|
||||
});
|
||||
|
||||
factory SignedTransaction.fromJson(Map<String, dynamic> json) => SignedTransaction(
|
||||
txHash: json['txHash'] as String,
|
||||
chain: ChainId.values.firstWhere((e) => e.name == json['chain']),
|
||||
from: json['from'] as String,
|
||||
to: json['to'] as String,
|
||||
value: json['value'] as String,
|
||||
data: json['data'] as String,
|
||||
gasLimit: json['gasLimit'] as String,
|
||||
gasPrice: json['gasPrice'] as String?,
|
||||
maxFeePerGas: json['maxFeePerGas'] as String?,
|
||||
maxPriorityFeePerGas: json['maxPriorityFeePerGas'] as String?,
|
||||
nonce: json['nonce'] as int,
|
||||
signature: json['signature'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Options & Filter ====================
|
||||
|
||||
/// Lock options
|
||||
class LockOptions {
|
||||
final String? recipient;
|
||||
final int? deadline;
|
||||
final double? slippage;
|
||||
|
||||
const LockOptions({this.recipient, this.deadline, this.slippage});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (recipient != null) 'recipient': recipient,
|
||||
if (deadline != null) 'deadline': deadline,
|
||||
if (slippage != null) 'slippage': slippage,
|
||||
};
|
||||
}
|
||||
|
||||
/// Mint options
|
||||
class MintOptions {
|
||||
final String? gasLimit;
|
||||
final String? maxFeePerGas;
|
||||
final String? maxPriorityFeePerGas;
|
||||
|
||||
const MintOptions({this.gasLimit, this.maxFeePerGas, this.maxPriorityFeePerGas});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (gasLimit != null) 'gasLimit': gasLimit,
|
||||
if (maxFeePerGas != null) 'maxFeePerGas': maxFeePerGas,
|
||||
if (maxPriorityFeePerGas != null) 'maxPriorityFeePerGas': maxPriorityFeePerGas,
|
||||
};
|
||||
}
|
||||
|
||||
/// Burn options
|
||||
class BurnOptions {
|
||||
final String? recipient;
|
||||
final int? deadline;
|
||||
|
||||
const BurnOptions({this.recipient, this.deadline});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (recipient != null) 'recipient': recipient,
|
||||
if (deadline != null) 'deadline': deadline,
|
||||
};
|
||||
}
|
||||
|
||||
/// Unlock options
|
||||
class UnlockOptions {
|
||||
final String? gasLimit;
|
||||
final String? gasPrice;
|
||||
|
||||
const UnlockOptions({this.gasLimit, this.gasPrice});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (gasLimit != null) 'gasLimit': gasLimit,
|
||||
if (gasPrice != null) 'gasPrice': gasPrice,
|
||||
};
|
||||
}
|
||||
|
||||
/// Transfer filter
|
||||
class TransferFilter {
|
||||
final TransferStatus? status;
|
||||
final ChainId? sourceChain;
|
||||
final ChainId? targetChain;
|
||||
final String? asset;
|
||||
final String? sender;
|
||||
final String? recipient;
|
||||
final int? limit;
|
||||
final int? offset;
|
||||
|
||||
const TransferFilter({
|
||||
this.status,
|
||||
this.sourceChain,
|
||||
this.targetChain,
|
||||
this.asset,
|
||||
this.sender,
|
||||
this.recipient,
|
||||
this.limit,
|
||||
this.offset,
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== Config ====================
|
||||
|
||||
/// Bridge configuration
|
||||
class BridgeConfig {
|
||||
final String apiKey;
|
||||
final String endpoint;
|
||||
final Duration timeout;
|
||||
final int retries;
|
||||
final bool debug;
|
||||
|
||||
const BridgeConfig({
|
||||
required this.apiKey,
|
||||
this.endpoint = 'https://bridge.synor.io/v1',
|
||||
this.timeout = const Duration(seconds: 60),
|
||||
this.retries = 3,
|
||||
this.debug = false,
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== Error ====================
|
||||
|
||||
/// Bridge error
|
||||
class BridgeException implements Exception {
|
||||
final String message;
|
||||
final String? code;
|
||||
final int? statusCode;
|
||||
|
||||
const BridgeException(this.message, {this.code, this.statusCode});
|
||||
|
||||
bool get isConfirmationsPending => code == 'CONFIRMATIONS_PENDING';
|
||||
|
||||
@override
|
||||
String toString() => 'BridgeException: $message';
|
||||
}
|
||||
246
sdk/flutter/lib/src/database/client.dart
Normal file
246
sdk/flutter/lib/src/database/client.dart
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/// Synor Database SDK Client for Flutter
|
||||
///
|
||||
/// Multi-model database with Key-Value, Document, Vector, and Time Series stores.
|
||||
library synor_database;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'types.dart';
|
||||
|
||||
export 'types.dart';
|
||||
|
||||
/// Synor Database Client
|
||||
class SynorDatabase {
|
||||
final DatabaseConfig config;
|
||||
final http.Client _client;
|
||||
bool _closed = false;
|
||||
|
||||
late final KeyValueStore kv;
|
||||
late final DocumentStore documents;
|
||||
late final VectorStore vectors;
|
||||
late final TimeSeriesStore timeseries;
|
||||
|
||||
SynorDatabase(this.config) : _client = http.Client() {
|
||||
kv = KeyValueStore(this);
|
||||
documents = DocumentStore(this);
|
||||
vectors = VectorStore(this);
|
||||
timeseries = TimeSeriesStore(this);
|
||||
}
|
||||
|
||||
/// Close the client
|
||||
void close() {
|
||||
_closed = true;
|
||||
_client.close();
|
||||
}
|
||||
|
||||
/// Check if closed
|
||||
bool get isClosed => _closed;
|
||||
|
||||
/// Health check
|
||||
Future<bool> healthCheck() async {
|
||||
try {
|
||||
final resp = await _request('GET', '/health');
|
||||
return resp['status'] == 'healthy';
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _request(
|
||||
String method,
|
||||
String path, [
|
||||
Map<String, dynamic>? body,
|
||||
]) async {
|
||||
if (_closed) throw DatabaseException('Client has been closed');
|
||||
|
||||
Exception? lastError;
|
||||
for (var attempt = 0; attempt < config.retries; attempt++) {
|
||||
try {
|
||||
return await _doRequest(method, path, body);
|
||||
} catch (e) {
|
||||
lastError = e as Exception;
|
||||
if (attempt < config.retries - 1) {
|
||||
await Future.delayed(Duration(seconds: 1 << attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError ?? DatabaseException('Unknown error');
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _doRequest(
|
||||
String method,
|
||||
String path,
|
||||
Map<String, dynamic>? body,
|
||||
) async {
|
||||
final uri = Uri.parse('${config.endpoint}$path');
|
||||
final headers = {
|
||||
'Authorization': 'Bearer ${config.apiKey}',
|
||||
'Content-Type': 'application/json',
|
||||
'X-SDK-Version': 'flutter/0.1.0',
|
||||
};
|
||||
|
||||
http.Response response;
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
response = await _client.get(uri, headers: headers);
|
||||
break;
|
||||
case 'POST':
|
||||
response = await _client.post(uri, headers: headers, body: jsonEncode(body));
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await _client.put(uri, headers: headers, body: jsonEncode(body));
|
||||
break;
|
||||
case 'PATCH':
|
||||
response = await _client.patch(uri, headers: headers, body: jsonEncode(body));
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await _client.delete(uri, headers: headers, body: body != null ? jsonEncode(body) : null);
|
||||
break;
|
||||
default:
|
||||
throw DatabaseException('Unknown method: $method');
|
||||
}
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
final error = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
throw DatabaseException(
|
||||
error['message'] as String? ?? 'HTTP ${response.statusCode}',
|
||||
code: error['code'] as String?,
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
return jsonDecode(response.body) as Map<String, dynamic>;
|
||||
}
|
||||
}
|
||||
|
||||
/// Key-Value Store
|
||||
class KeyValueStore {
|
||||
final SynorDatabase _db;
|
||||
KeyValueStore(this._db);
|
||||
|
||||
Future<dynamic> get(String key) async {
|
||||
final resp = await _db._request('GET', '/kv/${Uri.encodeComponent(key)}');
|
||||
return resp['value'];
|
||||
}
|
||||
|
||||
Future<void> set(String key, dynamic value, {int? ttl}) async {
|
||||
await _db._request('PUT', '/kv/${Uri.encodeComponent(key)}', {
|
||||
'key': key,
|
||||
'value': value,
|
||||
if (ttl != null) 'ttl': ttl,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> delete(String key) async {
|
||||
await _db._request('DELETE', '/kv/${Uri.encodeComponent(key)}');
|
||||
}
|
||||
|
||||
Future<List<KeyValue>> list(String prefix) async {
|
||||
final resp = await _db._request('GET', '/kv?prefix=${Uri.encodeComponent(prefix)}');
|
||||
return (resp['items'] as List).map((e) => KeyValue.fromJson(e)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
/// Document Store
|
||||
class DocumentStore {
|
||||
final SynorDatabase _db;
|
||||
DocumentStore(this._db);
|
||||
|
||||
Future<String> create(String collection, Map<String, dynamic> document) async {
|
||||
final resp = await _db._request(
|
||||
'POST',
|
||||
'/collections/${Uri.encodeComponent(collection)}/documents',
|
||||
document,
|
||||
);
|
||||
return resp['id'] as String;
|
||||
}
|
||||
|
||||
Future<Document> get(String collection, String id) async {
|
||||
final resp = await _db._request(
|
||||
'GET',
|
||||
'/collections/${Uri.encodeComponent(collection)}/documents/${Uri.encodeComponent(id)}',
|
||||
);
|
||||
return Document.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<void> update(String collection, String id, Map<String, dynamic> update) async {
|
||||
await _db._request(
|
||||
'PATCH',
|
||||
'/collections/${Uri.encodeComponent(collection)}/documents/${Uri.encodeComponent(id)}',
|
||||
update,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> delete(String collection, String id) async {
|
||||
await _db._request(
|
||||
'DELETE',
|
||||
'/collections/${Uri.encodeComponent(collection)}/documents/${Uri.encodeComponent(id)}',
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Document>> query(String collection, Query query) async {
|
||||
final resp = await _db._request(
|
||||
'POST',
|
||||
'/collections/${Uri.encodeComponent(collection)}/query',
|
||||
query.toJson(),
|
||||
);
|
||||
return (resp['documents'] as List).map((e) => Document.fromJson(e)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
/// Vector Store
|
||||
class VectorStore {
|
||||
final SynorDatabase _db;
|
||||
VectorStore(this._db);
|
||||
|
||||
Future<void> upsert(String collection, List<VectorEntry> vectors) async {
|
||||
await _db._request(
|
||||
'POST',
|
||||
'/vectors/${Uri.encodeComponent(collection)}/upsert',
|
||||
{'vectors': vectors.map((v) => v.toJson()).toList()},
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<SearchResult>> search(String collection, List<double> vector, int k) async {
|
||||
final resp = await _db._request(
|
||||
'POST',
|
||||
'/vectors/${Uri.encodeComponent(collection)}/search',
|
||||
{'vector': vector, 'k': k},
|
||||
);
|
||||
return (resp['results'] as List).map((e) => SearchResult.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<void> delete(String collection, List<String> ids) async {
|
||||
await _db._request(
|
||||
'DELETE',
|
||||
'/vectors/${Uri.encodeComponent(collection)}',
|
||||
{'ids': ids},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Time Series Store
|
||||
class TimeSeriesStore {
|
||||
final SynorDatabase _db;
|
||||
TimeSeriesStore(this._db);
|
||||
|
||||
Future<void> write(String series, List<DataPoint> points) async {
|
||||
await _db._request(
|
||||
'POST',
|
||||
'/timeseries/${Uri.encodeComponent(series)}/write',
|
||||
{'points': points.map((p) => p.toJson()).toList()},
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<DataPoint>> query(String series, TimeRange range, {Aggregation? aggregation}) async {
|
||||
final body = <String, dynamic>{'range': range.toJson()};
|
||||
if (aggregation != null) body['aggregation'] = aggregation.toJson();
|
||||
|
||||
final resp = await _db._request(
|
||||
'POST',
|
||||
'/timeseries/${Uri.encodeComponent(series)}/query',
|
||||
body,
|
||||
);
|
||||
return (resp['points'] as List).map((e) => DataPoint.fromJson(e)).toList();
|
||||
}
|
||||
}
|
||||
224
sdk/flutter/lib/src/database/types.dart
Normal file
224
sdk/flutter/lib/src/database/types.dart
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/// Synor Database SDK Types for Flutter
|
||||
///
|
||||
/// Multi-model database with Key-Value, Document, Vector, and Time Series stores.
|
||||
library synor_database_types;
|
||||
|
||||
// ==================== Config ====================
|
||||
|
||||
/// Database configuration
|
||||
class DatabaseConfig {
|
||||
final String apiKey;
|
||||
final String endpoint;
|
||||
final Duration timeout;
|
||||
final int retries;
|
||||
final bool debug;
|
||||
|
||||
const DatabaseConfig({
|
||||
required this.apiKey,
|
||||
this.endpoint = 'https://db.synor.io/v1',
|
||||
this.timeout = const Duration(seconds: 60),
|
||||
this.retries = 3,
|
||||
this.debug = false,
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== Key-Value Types ====================
|
||||
|
||||
/// Key-Value entry
|
||||
class KeyValue {
|
||||
final String key;
|
||||
final dynamic value;
|
||||
final int? ttl;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
const KeyValue({
|
||||
required this.key,
|
||||
required this.value,
|
||||
this.ttl,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
factory KeyValue.fromJson(Map<String, dynamic> json) => KeyValue(
|
||||
key: json['key'] as String,
|
||||
value: json['value'],
|
||||
ttl: json['ttl'] as int?,
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(json['createdAt'] as int)
|
||||
: null,
|
||||
updatedAt: json['updatedAt'] != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(json['updatedAt'] as int)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Document Types ====================
|
||||
|
||||
/// Document record
|
||||
class Document {
|
||||
final String id;
|
||||
final String collection;
|
||||
final Map<String, dynamic> data;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
const Document({
|
||||
required this.id,
|
||||
required this.collection,
|
||||
required this.data,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory Document.fromJson(Map<String, dynamic> json) => Document(
|
||||
id: json['id'] as String,
|
||||
collection: json['collection'] as String,
|
||||
data: Map<String, dynamic>.from(json['data'] as Map),
|
||||
createdAt: DateTime.fromMillisecondsSinceEpoch(json['createdAt'] as int),
|
||||
updatedAt: DateTime.fromMillisecondsSinceEpoch(json['updatedAt'] as int),
|
||||
);
|
||||
}
|
||||
|
||||
/// Query builder
|
||||
class Query {
|
||||
final Map<String, dynamic>? filter;
|
||||
final Map<String, int>? sort;
|
||||
final int? limit;
|
||||
final int? offset;
|
||||
final List<String>? projection;
|
||||
|
||||
const Query({
|
||||
this.filter,
|
||||
this.sort,
|
||||
this.limit,
|
||||
this.offset,
|
||||
this.projection,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (filter != null) json['filter'] = filter;
|
||||
if (sort != null) json['sort'] = sort;
|
||||
if (limit != null) json['limit'] = limit;
|
||||
if (offset != null) json['offset'] = offset;
|
||||
if (projection != null) json['projection'] = projection;
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Vector Types ====================
|
||||
|
||||
/// Vector entry for upsert
|
||||
class VectorEntry {
|
||||
final String id;
|
||||
final List<double> vector;
|
||||
final Map<String, dynamic>? metadata;
|
||||
|
||||
const VectorEntry({
|
||||
required this.id,
|
||||
required this.vector,
|
||||
this.metadata,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'vector': vector,
|
||||
if (metadata != null) 'metadata': metadata,
|
||||
};
|
||||
}
|
||||
|
||||
/// Vector search result
|
||||
class SearchResult {
|
||||
final String id;
|
||||
final double score;
|
||||
final List<double>? vector;
|
||||
final Map<String, dynamic>? metadata;
|
||||
|
||||
const SearchResult({
|
||||
required this.id,
|
||||
required this.score,
|
||||
this.vector,
|
||||
this.metadata,
|
||||
});
|
||||
|
||||
factory SearchResult.fromJson(Map<String, dynamic> json) => SearchResult(
|
||||
id: json['id'] as String,
|
||||
score: (json['score'] as num).toDouble(),
|
||||
vector: json['vector'] != null
|
||||
? List<double>.from((json['vector'] as List).map((e) => (e as num).toDouble()))
|
||||
: null,
|
||||
metadata: json['metadata'] != null
|
||||
? Map<String, dynamic>.from(json['metadata'] as Map)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Time Series Types ====================
|
||||
|
||||
/// Data point
|
||||
class DataPoint {
|
||||
final DateTime timestamp;
|
||||
final double value;
|
||||
final Map<String, String>? tags;
|
||||
|
||||
const DataPoint({
|
||||
required this.timestamp,
|
||||
required this.value,
|
||||
this.tags,
|
||||
});
|
||||
|
||||
factory DataPoint.fromJson(Map<String, dynamic> json) => DataPoint(
|
||||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||||
value: (json['value'] as num).toDouble(),
|
||||
tags: json['tags'] != null
|
||||
? Map<String, String>.from(json['tags'] as Map)
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'timestamp': timestamp.millisecondsSinceEpoch,
|
||||
'value': value,
|
||||
if (tags != null) 'tags': tags,
|
||||
};
|
||||
}
|
||||
|
||||
/// Time range
|
||||
class TimeRange {
|
||||
final DateTime start;
|
||||
final DateTime end;
|
||||
|
||||
const TimeRange({required this.start, required this.end});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'start': start.millisecondsSinceEpoch,
|
||||
'end': end.millisecondsSinceEpoch,
|
||||
};
|
||||
}
|
||||
|
||||
/// Aggregation options
|
||||
class Aggregation {
|
||||
final String function; // sum, avg, min, max, count
|
||||
final String interval; // 1m, 5m, 1h, 1d
|
||||
|
||||
const Aggregation({required this.function, required this.interval});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'function': function,
|
||||
'interval': interval,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== Error Types ====================
|
||||
|
||||
/// Database error
|
||||
class DatabaseException implements Exception {
|
||||
final String message;
|
||||
final String? code;
|
||||
final int? statusCode;
|
||||
|
||||
const DatabaseException(this.message, {this.code, this.statusCode});
|
||||
|
||||
@override
|
||||
String toString() => 'DatabaseException: $message';
|
||||
}
|
||||
253
sdk/flutter/lib/src/hosting/client.dart
Normal file
253
sdk/flutter/lib/src/hosting/client.dart
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/// Synor Hosting SDK Client for Flutter
|
||||
///
|
||||
/// Decentralized web hosting with domain management, DNS, and deployments.
|
||||
library synor_hosting;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'types.dart';
|
||||
|
||||
export 'types.dart';
|
||||
|
||||
/// Synor Hosting Client
|
||||
class SynorHosting {
|
||||
final HostingConfig config;
|
||||
final http.Client _client;
|
||||
bool _closed = false;
|
||||
|
||||
SynorHosting(this.config) : _client = http.Client();
|
||||
|
||||
// ==================== Domain Operations ====================
|
||||
|
||||
Future<DomainAvailability> checkAvailability(String name) async {
|
||||
final resp = await _request('GET', '/domains/check/${Uri.encodeComponent(name)}');
|
||||
return DomainAvailability.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<Domain> registerDomain(String name, {RegisterDomainOptions? options}) async {
|
||||
final body = <String, dynamic>{'name': name};
|
||||
if (options != null) body.addAll(options.toJson());
|
||||
final resp = await _request('POST', '/domains', body);
|
||||
return Domain.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<Domain> getDomain(String name) async {
|
||||
final resp = await _request('GET', '/domains/${Uri.encodeComponent(name)}');
|
||||
return Domain.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<List<Domain>> listDomains() async {
|
||||
final resp = await _request('GET', '/domains');
|
||||
return (resp['domains'] as List).map((e) => Domain.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<Domain> updateDomainRecord(String name, DomainRecord record) async {
|
||||
final resp = await _request('PUT', '/domains/${Uri.encodeComponent(name)}/record', record.toJson());
|
||||
return Domain.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<DomainRecord> resolveDomain(String name) async {
|
||||
final resp = await _request('GET', '/domains/${Uri.encodeComponent(name)}/resolve');
|
||||
return DomainRecord.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<Domain> renewDomain(String name, int years) async {
|
||||
final resp = await _request('POST', '/domains/${Uri.encodeComponent(name)}/renew', {'years': years});
|
||||
return Domain.fromJson(resp);
|
||||
}
|
||||
|
||||
// ==================== DNS Operations ====================
|
||||
|
||||
Future<DnsZone> getDnsZone(String domain) async {
|
||||
final resp = await _request('GET', '/dns/${Uri.encodeComponent(domain)}');
|
||||
return DnsZone.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<DnsZone> setDnsRecords(String domain, List<DnsRecord> records) async {
|
||||
final resp = await _request('PUT', '/dns/${Uri.encodeComponent(domain)}', {
|
||||
'records': records.map((r) => r.toJson()).toList(),
|
||||
});
|
||||
return DnsZone.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<DnsZone> addDnsRecord(String domain, DnsRecord record) async {
|
||||
final resp = await _request('POST', '/dns/${Uri.encodeComponent(domain)}/records', record.toJson());
|
||||
return DnsZone.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<DnsZone> deleteDnsRecord(String domain, String recordType, String name) async {
|
||||
final path = '/dns/${Uri.encodeComponent(domain)}/records/$recordType/${Uri.encodeComponent(name)}';
|
||||
final resp = await _request('DELETE', path);
|
||||
return DnsZone.fromJson(resp);
|
||||
}
|
||||
|
||||
// ==================== Deployment Operations ====================
|
||||
|
||||
Future<Deployment> deploy(String cid, {DeployOptions? options}) async {
|
||||
final body = <String, dynamic>{'cid': cid};
|
||||
if (options != null) body.addAll(options.toJson());
|
||||
final resp = await _request('POST', '/deployments', body);
|
||||
return Deployment.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<Deployment> getDeployment(String id) async {
|
||||
final resp = await _request('GET', '/deployments/${Uri.encodeComponent(id)}');
|
||||
return Deployment.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<List<Deployment>> listDeployments({String? domain}) async {
|
||||
final path = domain != null ? '/deployments?domain=${Uri.encodeComponent(domain)}' : '/deployments';
|
||||
final resp = await _request('GET', path);
|
||||
return (resp['deployments'] as List).map((e) => Deployment.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<Deployment> rollback(String domain, String deploymentId) async {
|
||||
final resp = await _request('POST', '/deployments/${Uri.encodeComponent(deploymentId)}/rollback', {
|
||||
'domain': domain,
|
||||
});
|
||||
return Deployment.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<void> deleteDeployment(String id) async {
|
||||
await _request('DELETE', '/deployments/${Uri.encodeComponent(id)}');
|
||||
}
|
||||
|
||||
// ==================== SSL Operations ====================
|
||||
|
||||
Future<Certificate> provisionSsl(String domain, {ProvisionSslOptions? options}) async {
|
||||
final resp = await _request('POST', '/ssl/${Uri.encodeComponent(domain)}', options?.toJson());
|
||||
return Certificate.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<Certificate> getCertificate(String domain) async {
|
||||
final resp = await _request('GET', '/ssl/${Uri.encodeComponent(domain)}');
|
||||
return Certificate.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<Certificate> renewCertificate(String domain) async {
|
||||
final resp = await _request('POST', '/ssl/${Uri.encodeComponent(domain)}/renew');
|
||||
return Certificate.fromJson(resp);
|
||||
}
|
||||
|
||||
Future<void> deleteCertificate(String domain) async {
|
||||
await _request('DELETE', '/ssl/${Uri.encodeComponent(domain)}');
|
||||
}
|
||||
|
||||
// ==================== Site Configuration ====================
|
||||
|
||||
Future<Map<String, dynamic>> getSiteConfig(String domain) async {
|
||||
return await _request('GET', '/sites/${Uri.encodeComponent(domain)}/config');
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> updateSiteConfig(String domain, Map<String, dynamic> siteConfig) async {
|
||||
return await _request('PATCH', '/sites/${Uri.encodeComponent(domain)}/config', siteConfig);
|
||||
}
|
||||
|
||||
Future<int> purgeCache(String domain, {List<String>? paths}) async {
|
||||
final resp = await _request('DELETE', '/sites/${Uri.encodeComponent(domain)}/cache', {
|
||||
if (paths != null) 'paths': paths,
|
||||
});
|
||||
return resp['purged'] as int;
|
||||
}
|
||||
|
||||
// ==================== Analytics ====================
|
||||
|
||||
Future<AnalyticsData> getAnalytics(String domain, {AnalyticsOptions? options}) async {
|
||||
var path = '/sites/${Uri.encodeComponent(domain)}/analytics';
|
||||
final params = <String>[];
|
||||
if (options?.period != null) params.add('period=${Uri.encodeComponent(options!.period!)}');
|
||||
if (options?.start != null) params.add('start=${Uri.encodeComponent(options!.start!)}');
|
||||
if (options?.end != null) params.add('end=${Uri.encodeComponent(options!.end!)}');
|
||||
if (params.isNotEmpty) path = '$path?${params.join('&')}';
|
||||
|
||||
final resp = await _request('GET', path);
|
||||
return AnalyticsData.fromJson(resp);
|
||||
}
|
||||
|
||||
// ==================== Lifecycle ====================
|
||||
|
||||
void close() {
|
||||
_closed = true;
|
||||
_client.close();
|
||||
}
|
||||
|
||||
bool get isClosed => _closed;
|
||||
|
||||
Future<bool> healthCheck() async {
|
||||
try {
|
||||
final resp = await _request('GET', '/health');
|
||||
return resp['status'] == 'healthy';
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Private Methods ====================
|
||||
|
||||
Future<Map<String, dynamic>> _request(
|
||||
String method,
|
||||
String path, [
|
||||
Map<String, dynamic>? body,
|
||||
]) async {
|
||||
if (_closed) throw HostingException('Client has been closed');
|
||||
|
||||
Exception? lastError;
|
||||
for (var attempt = 0; attempt < config.retries; attempt++) {
|
||||
try {
|
||||
return await _doRequest(method, path, body);
|
||||
} catch (e) {
|
||||
lastError = e as Exception;
|
||||
if (attempt < config.retries - 1) {
|
||||
await Future.delayed(Duration(seconds: 1 << attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError ?? HostingException('Unknown error');
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _doRequest(
|
||||
String method,
|
||||
String path,
|
||||
Map<String, dynamic>? body,
|
||||
) async {
|
||||
final uri = Uri.parse('${config.endpoint}$path');
|
||||
final headers = {
|
||||
'Authorization': 'Bearer ${config.apiKey}',
|
||||
'Content-Type': 'application/json',
|
||||
'X-SDK-Version': 'flutter/0.1.0',
|
||||
};
|
||||
|
||||
http.Response response;
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
response = await _client.get(uri, headers: headers);
|
||||
break;
|
||||
case 'POST':
|
||||
response = await _client.post(uri, headers: headers, body: body != null ? jsonEncode(body) : null);
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await _client.put(uri, headers: headers, body: jsonEncode(body));
|
||||
break;
|
||||
case 'PATCH':
|
||||
response = await _client.patch(uri, headers: headers, body: jsonEncode(body));
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await _client.delete(uri, headers: headers, body: body != null ? jsonEncode(body) : null);
|
||||
break;
|
||||
default:
|
||||
throw HostingException('Unknown method: $method');
|
||||
}
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
final error = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
throw HostingException(
|
||||
error['message'] as String? ?? 'HTTP ${response.statusCode}',
|
||||
code: error['code'] as String?,
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
if (response.body.isEmpty) return {};
|
||||
return jsonDecode(response.body) as Map<String, dynamic>;
|
||||
}
|
||||
}
|
||||
397
sdk/flutter/lib/src/hosting/types.dart
Normal file
397
sdk/flutter/lib/src/hosting/types.dart
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
/// Synor Hosting SDK Types for Flutter
|
||||
///
|
||||
/// Decentralized web hosting with domain management, DNS, and deployments.
|
||||
library synor_hosting_types;
|
||||
|
||||
// ==================== Config ====================
|
||||
|
||||
/// Hosting configuration
|
||||
class HostingConfig {
|
||||
final String apiKey;
|
||||
final String endpoint;
|
||||
final Duration timeout;
|
||||
final int retries;
|
||||
final bool debug;
|
||||
|
||||
const HostingConfig({
|
||||
required this.apiKey,
|
||||
this.endpoint = 'https://hosting.synor.io/v1',
|
||||
this.timeout = const Duration(seconds: 60),
|
||||
this.retries = 3,
|
||||
this.debug = false,
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== Domain Types ====================
|
||||
|
||||
/// Domain status
|
||||
enum DomainStatus { pending, active, expired, suspended }
|
||||
|
||||
/// Domain information
|
||||
class Domain {
|
||||
final String name;
|
||||
final DomainStatus status;
|
||||
final String owner;
|
||||
final DateTime registeredAt;
|
||||
final DateTime expiresAt;
|
||||
final bool autoRenew;
|
||||
final DomainRecord? records;
|
||||
|
||||
const Domain({
|
||||
required this.name,
|
||||
required this.status,
|
||||
required this.owner,
|
||||
required this.registeredAt,
|
||||
required this.expiresAt,
|
||||
required this.autoRenew,
|
||||
this.records,
|
||||
});
|
||||
|
||||
factory Domain.fromJson(Map<String, dynamic> json) => Domain(
|
||||
name: json['name'] as String,
|
||||
status: DomainStatus.values.firstWhere(
|
||||
(e) => e.name == json['status'],
|
||||
orElse: () => DomainStatus.pending),
|
||||
owner: json['owner'] as String,
|
||||
registeredAt: DateTime.fromMillisecondsSinceEpoch(json['registeredAt'] as int),
|
||||
expiresAt: DateTime.fromMillisecondsSinceEpoch(json['expiresAt'] as int),
|
||||
autoRenew: json['autoRenew'] as bool? ?? false,
|
||||
records: json['records'] != null ? DomainRecord.fromJson(json['records']) : null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Domain record
|
||||
class DomainRecord {
|
||||
final String? cid;
|
||||
final List<String>? ipv4;
|
||||
final List<String>? ipv6;
|
||||
final String? cname;
|
||||
final List<String>? txt;
|
||||
final Map<String, String>? metadata;
|
||||
|
||||
const DomainRecord({
|
||||
this.cid,
|
||||
this.ipv4,
|
||||
this.ipv6,
|
||||
this.cname,
|
||||
this.txt,
|
||||
this.metadata,
|
||||
});
|
||||
|
||||
factory DomainRecord.fromJson(Map<String, dynamic> json) => DomainRecord(
|
||||
cid: json['cid'] as String?,
|
||||
ipv4: (json['ipv4'] as List?)?.cast<String>(),
|
||||
ipv6: (json['ipv6'] as List?)?.cast<String>(),
|
||||
cname: json['cname'] as String?,
|
||||
txt: (json['txt'] as List?)?.cast<String>(),
|
||||
metadata: (json['metadata'] as Map?)?.cast<String, String>(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (cid != null) 'cid': cid,
|
||||
if (ipv4 != null) 'ipv4': ipv4,
|
||||
if (ipv6 != null) 'ipv6': ipv6,
|
||||
if (cname != null) 'cname': cname,
|
||||
if (txt != null) 'txt': txt,
|
||||
if (metadata != null) 'metadata': metadata,
|
||||
};
|
||||
}
|
||||
|
||||
/// Domain availability
|
||||
class DomainAvailability {
|
||||
final String name;
|
||||
final bool available;
|
||||
final double? price;
|
||||
final bool premium;
|
||||
|
||||
const DomainAvailability({
|
||||
required this.name,
|
||||
required this.available,
|
||||
this.price,
|
||||
this.premium = false,
|
||||
});
|
||||
|
||||
factory DomainAvailability.fromJson(Map<String, dynamic> json) => DomainAvailability(
|
||||
name: json['name'] as String,
|
||||
available: json['available'] as bool,
|
||||
price: (json['price'] as num?)?.toDouble(),
|
||||
premium: json['premium'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Options for registering a domain
|
||||
class RegisterDomainOptions {
|
||||
final int? years;
|
||||
final bool? autoRenew;
|
||||
|
||||
const RegisterDomainOptions({this.years, this.autoRenew});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (years != null) 'years': years,
|
||||
if (autoRenew != null) 'autoRenew': autoRenew,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== DNS Types ====================
|
||||
|
||||
/// DNS record type
|
||||
enum DnsRecordType { A, AAAA, CNAME, TXT, MX, NS, SRV, CAA }
|
||||
|
||||
/// DNS record
|
||||
class DnsRecord {
|
||||
final DnsRecordType type;
|
||||
final String name;
|
||||
final String value;
|
||||
final int ttl;
|
||||
final int? priority;
|
||||
|
||||
const DnsRecord({
|
||||
required this.type,
|
||||
required this.name,
|
||||
required this.value,
|
||||
this.ttl = 3600,
|
||||
this.priority,
|
||||
});
|
||||
|
||||
factory DnsRecord.fromJson(Map<String, dynamic> json) => DnsRecord(
|
||||
type: DnsRecordType.values.firstWhere((e) => e.name == json['type']),
|
||||
name: json['name'] as String,
|
||||
value: json['value'] as String,
|
||||
ttl: json['ttl'] as int? ?? 3600,
|
||||
priority: json['priority'] as int?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'type': type.name,
|
||||
'name': name,
|
||||
'value': value,
|
||||
'ttl': ttl,
|
||||
if (priority != null) 'priority': priority,
|
||||
};
|
||||
}
|
||||
|
||||
/// DNS zone
|
||||
class DnsZone {
|
||||
final String domain;
|
||||
final List<DnsRecord> records;
|
||||
final DateTime updatedAt;
|
||||
|
||||
const DnsZone({
|
||||
required this.domain,
|
||||
required this.records,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory DnsZone.fromJson(Map<String, dynamic> json) => DnsZone(
|
||||
domain: json['domain'] as String,
|
||||
records: (json['records'] as List).map((e) => DnsRecord.fromJson(e)).toList(),
|
||||
updatedAt: DateTime.fromMillisecondsSinceEpoch(json['updatedAt'] as int),
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== Deployment Types ====================
|
||||
|
||||
/// Deployment status
|
||||
enum DeploymentStatus { pending, building, deploying, active, failed, inactive }
|
||||
|
||||
/// Deployment information
|
||||
class Deployment {
|
||||
final String id;
|
||||
final String domain;
|
||||
final String cid;
|
||||
final DeploymentStatus status;
|
||||
final String url;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String? buildLogs;
|
||||
final String? errorMessage;
|
||||
|
||||
const Deployment({
|
||||
required this.id,
|
||||
required this.domain,
|
||||
required this.cid,
|
||||
required this.status,
|
||||
required this.url,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.buildLogs,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
factory Deployment.fromJson(Map<String, dynamic> json) => Deployment(
|
||||
id: json['id'] as String,
|
||||
domain: json['domain'] as String,
|
||||
cid: json['cid'] as String,
|
||||
status: DeploymentStatus.values.firstWhere(
|
||||
(e) => e.name == json['status'],
|
||||
orElse: () => DeploymentStatus.pending),
|
||||
url: json['url'] as String,
|
||||
createdAt: DateTime.fromMillisecondsSinceEpoch(json['createdAt'] as int),
|
||||
updatedAt: DateTime.fromMillisecondsSinceEpoch(json['updatedAt'] as int),
|
||||
buildLogs: json['buildLogs'] as String?,
|
||||
errorMessage: json['errorMessage'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deploy options
|
||||
class DeployOptions {
|
||||
final String? domain;
|
||||
final String? subdomain;
|
||||
final bool? spa;
|
||||
final bool? cleanUrls;
|
||||
final bool? trailingSlash;
|
||||
|
||||
const DeployOptions({
|
||||
this.domain,
|
||||
this.subdomain,
|
||||
this.spa,
|
||||
this.cleanUrls,
|
||||
this.trailingSlash,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (domain != null) 'domain': domain,
|
||||
if (subdomain != null) 'subdomain': subdomain,
|
||||
if (spa != null) 'spa': spa,
|
||||
if (cleanUrls != null) 'cleanUrls': cleanUrls,
|
||||
if (trailingSlash != null) 'trailingSlash': trailingSlash,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== SSL Types ====================
|
||||
|
||||
/// Certificate status
|
||||
enum CertificateStatus { pending, issued, expired, revoked }
|
||||
|
||||
/// SSL certificate
|
||||
class Certificate {
|
||||
final String domain;
|
||||
final CertificateStatus status;
|
||||
final bool autoRenew;
|
||||
final String issuer;
|
||||
final DateTime? issuedAt;
|
||||
final DateTime? expiresAt;
|
||||
final String? fingerprint;
|
||||
|
||||
const Certificate({
|
||||
required this.domain,
|
||||
required this.status,
|
||||
required this.autoRenew,
|
||||
required this.issuer,
|
||||
this.issuedAt,
|
||||
this.expiresAt,
|
||||
this.fingerprint,
|
||||
});
|
||||
|
||||
factory Certificate.fromJson(Map<String, dynamic> json) => Certificate(
|
||||
domain: json['domain'] as String,
|
||||
status: CertificateStatus.values.firstWhere(
|
||||
(e) => e.name == json['status'],
|
||||
orElse: () => CertificateStatus.pending),
|
||||
autoRenew: json['autoRenew'] as bool? ?? false,
|
||||
issuer: json['issuer'] as String,
|
||||
issuedAt: json['issuedAt'] != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(json['issuedAt'] as int)
|
||||
: null,
|
||||
expiresAt: json['expiresAt'] != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(json['expiresAt'] as int)
|
||||
: null,
|
||||
fingerprint: json['fingerprint'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Provision SSL options
|
||||
class ProvisionSslOptions {
|
||||
final bool? includeWww;
|
||||
final bool? autoRenew;
|
||||
|
||||
const ProvisionSslOptions({this.includeWww, this.autoRenew});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (includeWww != null) 'includeWww': includeWww,
|
||||
if (autoRenew != null) 'autoRenew': autoRenew,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== Analytics Types ====================
|
||||
|
||||
/// Analytics data
|
||||
class AnalyticsData {
|
||||
final String domain;
|
||||
final String period;
|
||||
final int pageViews;
|
||||
final int uniqueVisitors;
|
||||
final int bandwidth;
|
||||
final List<PageView> topPages;
|
||||
final List<Referrer> topReferrers;
|
||||
final List<Country> topCountries;
|
||||
|
||||
const AnalyticsData({
|
||||
required this.domain,
|
||||
required this.period,
|
||||
required this.pageViews,
|
||||
required this.uniqueVisitors,
|
||||
required this.bandwidth,
|
||||
required this.topPages,
|
||||
required this.topReferrers,
|
||||
required this.topCountries,
|
||||
});
|
||||
|
||||
factory AnalyticsData.fromJson(Map<String, dynamic> json) => AnalyticsData(
|
||||
domain: json['domain'] as String,
|
||||
period: json['period'] as String,
|
||||
pageViews: json['pageViews'] as int,
|
||||
uniqueVisitors: json['uniqueVisitors'] as int,
|
||||
bandwidth: json['bandwidth'] as int,
|
||||
topPages: (json['topPages'] as List).map((e) => PageView.fromJson(e)).toList(),
|
||||
topReferrers: (json['topReferrers'] as List).map((e) => Referrer.fromJson(e)).toList(),
|
||||
topCountries: (json['topCountries'] as List).map((e) => Country.fromJson(e)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
class PageView {
|
||||
final String path;
|
||||
final int views;
|
||||
const PageView({required this.path, required this.views});
|
||||
factory PageView.fromJson(Map<String, dynamic> json) =>
|
||||
PageView(path: json['path'] as String, views: json['views'] as int);
|
||||
}
|
||||
|
||||
class Referrer {
|
||||
final String referrer;
|
||||
final int count;
|
||||
const Referrer({required this.referrer, required this.count});
|
||||
factory Referrer.fromJson(Map<String, dynamic> json) =>
|
||||
Referrer(referrer: json['referrer'] as String, count: json['count'] as int);
|
||||
}
|
||||
|
||||
class Country {
|
||||
final String country;
|
||||
final int count;
|
||||
const Country({required this.country, required this.count});
|
||||
factory Country.fromJson(Map<String, dynamic> json) =>
|
||||
Country(country: json['country'] as String, count: json['count'] as int);
|
||||
}
|
||||
|
||||
/// Analytics options
|
||||
class AnalyticsOptions {
|
||||
final String? period;
|
||||
final String? start;
|
||||
final String? end;
|
||||
|
||||
const AnalyticsOptions({this.period, this.start, this.end});
|
||||
}
|
||||
|
||||
// ==================== Error Types ====================
|
||||
|
||||
/// Hosting error
|
||||
class HostingException implements Exception {
|
||||
final String message;
|
||||
final String? code;
|
||||
final int? statusCode;
|
||||
|
||||
const HostingException(this.message, {this.code, this.statusCode});
|
||||
|
||||
@override
|
||||
String toString() => 'HostingException: $message';
|
||||
}
|
||||
|
|
@ -5,6 +5,9 @@
|
|||
/// - **Wallet**: Key management and transaction signing
|
||||
/// - **RPC**: Blockchain queries and subscriptions
|
||||
/// - **Storage**: IPFS-compatible decentralized storage
|
||||
/// - **Database**: Multi-model database (KV, Document, Vector, TimeSeries)
|
||||
/// - **Hosting**: Decentralized web hosting
|
||||
/// - **Bridge**: Cross-chain asset transfers
|
||||
///
|
||||
/// ## Quick Start
|
||||
///
|
||||
|
|
@ -28,10 +31,25 @@
|
|||
/// final upload = await storage.upload(Uint8List.fromList(data));
|
||||
/// print('CID: ${upload.cid}');
|
||||
///
|
||||
/// // Database operations
|
||||
/// final db = SynorDatabase(DatabaseConfig(apiKey: 'your-api-key'));
|
||||
/// await db.kv.set('key', 'value');
|
||||
///
|
||||
/// // Hosting operations
|
||||
/// final hosting = SynorHosting(HostingConfig(apiKey: 'your-api-key'));
|
||||
/// final deployment = await hosting.deploy('Qm...');
|
||||
///
|
||||
/// // Bridge operations
|
||||
/// final bridge = SynorBridge(BridgeConfig(apiKey: 'your-api-key'));
|
||||
/// final transfer = await bridge.bridgeTo('SYNR', '1000', ChainId.ethereum, '0x...');
|
||||
///
|
||||
/// // Clean up
|
||||
/// wallet.close();
|
||||
/// rpc.close();
|
||||
/// storage.close();
|
||||
/// db.close();
|
||||
/// hosting.close();
|
||||
/// bridge.close();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
|
|
@ -42,6 +60,7 @@
|
|||
/// - RPC: `RpcNetwork`, `RpcPriority`, `RpcTransaction`
|
||||
/// - Storage: `PinStatus`, `HashAlgorithm`, `EntryType`
|
||||
/// - Compute: `Precision`, `ProcessorType`, `Priority` (as `ComputePriority`)
|
||||
/// - Bridge: `ChainId`, `TransferStatus`, `TransferDirection`
|
||||
library synor_sdk;
|
||||
|
||||
// Compute SDK - hide Priority to avoid conflict with Wallet
|
||||
|
|
@ -56,5 +75,14 @@ export 'src/rpc/synor_rpc.dart';
|
|||
// Storage SDK
|
||||
export 'src/storage/synor_storage.dart';
|
||||
|
||||
// Database SDK
|
||||
export 'src/database/client.dart';
|
||||
|
||||
// Hosting SDK
|
||||
export 'src/hosting/client.dart';
|
||||
|
||||
// Bridge SDK - hide types that conflict with other SDKs
|
||||
export 'src/bridge/client.dart' hide FeeEstimate, SignedTransaction;
|
||||
|
||||
// Re-export Compute Priority with alias-friendly access
|
||||
// Users can import compute directly for Priority: import 'package:synor_sdk/synor_compute.dart';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue