Space MVC
A TypeScript toolset to simplify building peer-to-peer smart contract applications using Ethereum, IPFS, and OrbitDB. All the tools you'll need to get started quickly. Smart contracts, storage, MVC, and unit tests with a proven MVC pattern inspired by proven enterprise Java/.NET libraries like Spring.
SpaceMVC makes it easy to build professional mobile, desktop, web, and Node applications using next generation internet technologies.
Our primary goals
- Provide a radically faster getting started experience for P2P app development.
- Make opinionated decisions to reduce boilerplate code.
- Implements simple, familiar MVC pattern.
- Mobile, desktop, web, and Node applications from a single code base.
- Test Ethereum smart contracts with Truffle.
- Mocha unit tests for business logic and full integration tests.
- Lightweight. Less than 5MB total.
- Use dependency injection to modularize your code.
- Apps work offline by default. Data is primarily stored in the user's browser.
SpaceMVC provides a pre-configured development environment with reasonable defaults. Start building real apps immediately.
Maintainable and Testable
- Object-oriented design and Inversify dependency injection helps you decouple your business logic and your UI to write maintainable and testable code.
- SpaceMVC relies heavily on Framework7 and the answers to many questions can be found in their excellent documentation. You can use it to build full featured iOS, Android & Desktop apps that look and feel native. It uses nothing but HTML, CSS, and Javascript.
Important Technology
- Typescript - TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
- Framework7 - Build full featured iOS, Android & Desktop apps with HTML, CSS, and Javascript
- IPFS - A peer-to-peer hypermedia protocol
designed to make the web faster, safer, and more open.
- OrbitDB - OrbitDB is a serverless, distributed, peer-to-peer database
- The Ethers Project - A complete Ethereum wallet implementation and utilities in JavaScript (and TypeScript).
- Inversify - IoC container Inversify.
Getting Started
The fastest way to create a new project is to clone the SpaceMVC Starter project. It's an example of a basic CRUD application that saves and loads lists of students for a class roster.
For a more complete example see American Sports Oligarch.
Example
import "core-js/stable"
import "regenerator-runtime/runtime"
import SpaceMVC from "space-mvc";
import { FranchiseService } from "./service/franchise-service"
import { LeagueController } from "./controller/league-controller"
const container = new Container()
container.bind(FranchiseService).toSelf().inSingletonScope()
container.bind(HomeController).toSelf().inSingletonScope()
let f7Config = {
root: '#app',
id: 'example',
name: 'SpaceMVC Example',
theme: 'auto',
}
let storeDefinitions:StoreDefinition[] = [{
name: "settings",
type: "kvstore",
load: 100,
options: {}
}]
let truffleJson = require('../truffle/build/contracts/YourContract.json')
let contract:Contract = await ContractService.createContractFromTruffle(truffleJson)
let spaceMvcInit:SpaceMvcInit = {
name: "demo-project",
displayName: "Demo Project",
container: container,
f7Config: f7Config,
storeDefinitions: storeDefinitions,
contracts: [contract],
defaultProvider: new ethers.providers.JsonRpcProvider("http://localhost:7545")
}
await SpaceMVC.init(spaceMvcInit)
Dependency Injection
Use Inversify to configure and inject dependencies into our services, DAOs, controllers, and more. You can also extend this behavior and bind your own objects to the container. You can also inject the services that SpaceMVC exports into your own classes.
The inversify container can be accessed at
SpaceMVC.getContainer()
MVC
SpaceMVC implements a Model-View-Controller pattern.
Controllers
URL routes are mapped to controller objects with the @routeMap() annotation.
- Bind your controller in singleton scope with Inversify
- Routes are mapped by the @routeMap() annotation. Each annotation must have a globally unique path. When that URL is accessed the method it's mapped to will be called.
- The controller method returns a ModelView object. It returns the model to render and the view is a Framework7 component with a template.
- SpaceMVC creates the component and passes the model data to it.
- See Framework7 documentation for on-page event options.
Services
Services are where your business logic goes. A service is a POJO annotated with the @service() annotation.
- Bind service in singleton scope with inversify.
- Create methods and associated unit tests.
- Inject ContractService to make calls to smart contracts.
- Inject your data access objects (DAOs) to store data in OrbitDB or IPFS.
DAOs
Data Access Objects (DAOs) store and retreive data from OrbitDB, MFdb and IPFS. A DAO is a POJO with a @dao() annotation.
- Bind DAO in singleton scope with inversify.
- Create methods and associated unit tests.
- Inject DAO into service to store and retrieve data.
Importing Truffle Contract
SpaceMVC uses ethers.js under the hood for contract communication but we also use Truffle to develop Solidity unit test suites.
We have a basic interface that's agnostic to implementation but an easy way to import Truffle JSON files.
interface Contract {
name:string,
networks:Network[],
abi:any
}
interface Network {
networkId:string,
address:string
}
Example: This will grab all the networking information out of the Truffle JSON and into the right format.
import { ContractService } from "space-mvc"
let truffleJson = require('../truffle/build/contracts/YourContract.json')
let contract:Contract = await ContractService.createContractFromTruffle(truffleJson)
Metastore Service
A Metastore is an object that loads and connects to a group of OrbitDB stores. This allows you to define all of the stores that your application needs and open them all at once.
A Metastore named "WALLET_METASTORE" is created by default. It's only writeable by the user's wallet address. The app will open and load all of the stores and OrbitDB will sync all of the stores to all of the user's devices.
Interface
interface Metastore {
name:string
writeAccess?: string[]
storeDefinitions?:StoreDefinition[]
addresses?: any
stores?: any
}
Store Definition
A store definition contains info to open an OrbitDB.
- Name - the name of the store
- Type - the type of Orbit store to open. Options are log, feed, keyvalue, docs, counter, and mfsstore
- Load - the number of records to load into memory when the store is loaded
- Options - options that will be passed to store initialization
Creating a custom Metastore
let metastore = {
name: "NBA_STATS",
writeAccess: ["0x3Cf6079639D48D37b741D4bcF7c04f2C02062C46"],
storeDefinitions: [
{
name: "player",
type: "mfsstore",
load: 100,
options: {
schema: {
playerKey: { unique: true },
firstName: { unique: false },
lastName: { unique: false }
}
}
}
]
}
await metastoreService.connect(metastore)
Loading Stores
await metastoreService.getStore("YOUR METASTORE NAME", "THE ORBITDB STORE NAME IN STORE DEFINITION")
let store = metastoreService.getWalletStore("student")
Events
- spacemvc:initFinish - Fired when SpaceMVC has finished initializing.
SpaceMVC.getEventEmitter().on('spacemvc:initFinish', async () => {
})
Build
Production:
npm run build
Development:
npm run build:dev
Tests:
npm run test
- dist/index-browser.js - Browser build
- dist/index-node.js - Node build