summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
authorThomas F. K. Jorna <[email protected]>2021-07-14 15:10:31 +0200
committerThomas F. K. Jorna <[email protected]>2021-07-14 15:10:31 +0200
commite5021187e96b78b53203bd95d08d6818aea47d17 (patch)
tree37ec45d00eb963db53cd4bb4f04a770414b351cc /app/models
New Ignite 7.0.6 app
Diffstat (limited to 'app/models')
-rw-r--r--app/models/character-store/character-store.test.ts7
-rw-r--r--app/models/character-store/character-store.ts37
-rw-r--r--app/models/character/character.test.ts10
-rw-r--r--app/models/character/character.ts17
-rw-r--r--app/models/environment.ts40
-rw-r--r--app/models/extensions/with-environment.ts17
-rw-r--r--app/models/extensions/with-root-store.ts17
-rw-r--r--app/models/index.ts5
-rw-r--r--app/models/root-store/root-store-context.ts22
-rw-r--r--app/models/root-store/root-store.ts20
-rw-r--r--app/models/root-store/setup-root-store.ts55
11 files changed, 247 insertions, 0 deletions
diff --git a/app/models/character-store/character-store.test.ts b/app/models/character-store/character-store.test.ts
new file mode 100644
index 0000000..6d82079
--- /dev/null
+++ b/app/models/character-store/character-store.test.ts
@@ -0,0 +1,7 @@
+import { CharacterStoreModel } from "./character-store"
+
+test("can be created", () => {
+ const instance = CharacterStoreModel.create({})
+
+ expect(instance).toBeTruthy()
+})
diff --git a/app/models/character-store/character-store.ts b/app/models/character-store/character-store.ts
new file mode 100644
index 0000000..bd92615
--- /dev/null
+++ b/app/models/character-store/character-store.ts
@@ -0,0 +1,37 @@
+import { Instance, SnapshotOut, types } from "mobx-state-tree"
+import { CharacterModel, CharacterSnapshot } from "../character/character"
+import { CharacterApi } from "../../services/api/character-api"
+import { withEnvironment } from "../extensions/with-environment"
+
+/**
+ * Example store containing Rick and Morty characters
+ */
+export const CharacterStoreModel = types
+ .model("CharacterStore")
+ .props({
+ characters: types.optional(types.array(CharacterModel), []),
+ })
+ .extend(withEnvironment)
+ .actions((self) => ({
+ saveCharacters: (characterSnapshots: CharacterSnapshot[]) => {
+ self.characters.replace(characterSnapshots)
+ },
+ }))
+ .actions((self) => ({
+ getCharacters: async () => {
+ const characterApi = new CharacterApi(self.environment.api)
+ const result = await characterApi.getCharacters()
+
+ if (result.kind === "ok") {
+ self.saveCharacters(result.characters)
+ } else {
+ __DEV__ && console.tron.log(result.kind)
+ }
+ },
+ }))
+
+type CharacterStoreType = Instance<typeof CharacterStoreModel>
+export interface CharacterStore extends CharacterStoreType {}
+type CharacterStoreSnapshotType = SnapshotOut<typeof CharacterStoreModel>
+export interface CharacterStoreSnapshot extends CharacterStoreSnapshotType {}
+export const createCharacterStoreDefaultModel = () => types.optional(CharacterStoreModel, {})
diff --git a/app/models/character/character.test.ts b/app/models/character/character.test.ts
new file mode 100644
index 0000000..2f330b4
--- /dev/null
+++ b/app/models/character/character.test.ts
@@ -0,0 +1,10 @@
+import { CharacterModel } from "./character"
+
+test("can be created", () => {
+ const instance = CharacterModel.create({
+ id: 1,
+ name: "Rick Sanchez",
+ })
+
+ expect(instance).toBeTruthy()
+})
diff --git a/app/models/character/character.ts b/app/models/character/character.ts
new file mode 100644
index 0000000..ca3cbc7
--- /dev/null
+++ b/app/models/character/character.ts
@@ -0,0 +1,17 @@
+import { Instance, SnapshotOut, types } from "mobx-state-tree"
+
+/**
+ * Rick and Morty character model.
+ */
+export const CharacterModel = types.model("Character").props({
+ id: types.identifierNumber,
+ name: types.maybe(types.string),
+ status: types.maybe(types.string),
+ image: types.maybe(types.string),
+})
+
+type CharacterType = Instance<typeof CharacterModel>
+export interface Character extends CharacterType {}
+type CharacterSnapshotType = SnapshotOut<typeof CharacterModel>
+export interface CharacterSnapshot extends CharacterSnapshotType {}
+export const createCharacterDefaultModel = () => types.optional(CharacterModel, {})
diff --git a/app/models/environment.ts b/app/models/environment.ts
new file mode 100644
index 0000000..e8569b1
--- /dev/null
+++ b/app/models/environment.ts
@@ -0,0 +1,40 @@
+import { Api } from "../services/api"
+
+let ReactotronDev
+if (__DEV__) {
+ const { Reactotron } = require("../services/reactotron")
+ ReactotronDev = Reactotron
+}
+
+/**
+ * The environment is a place where services and shared dependencies between
+ * models live. They are made available to every model via dependency injection.
+ */
+export class Environment {
+ constructor() {
+ // create each service
+ if (__DEV__) {
+ // dev-only services
+ this.reactotron = new ReactotronDev()
+ }
+ this.api = new Api()
+ }
+
+ async setup() {
+ // allow each service to setup
+ if (__DEV__) {
+ await this.reactotron.setup()
+ }
+ await this.api.setup()
+ }
+
+ /**
+ * Reactotron is only available in dev.
+ */
+ reactotron: typeof ReactotronDev
+
+ /**
+ * Our api.
+ */
+ api: Api
+}
diff --git a/app/models/extensions/with-environment.ts b/app/models/extensions/with-environment.ts
new file mode 100644
index 0000000..9fe5fd5
--- /dev/null
+++ b/app/models/extensions/with-environment.ts
@@ -0,0 +1,17 @@
+import { getEnv, IStateTreeNode } from "mobx-state-tree"
+import { Environment } from "../environment"
+
+/**
+ * Adds a environment property to the node for accessing our
+ * Environment in strongly typed.
+ */
+export const withEnvironment = (self: IStateTreeNode) => ({
+ views: {
+ /**
+ * The environment.
+ */
+ get environment() {
+ return getEnv<Environment>(self)
+ },
+ },
+})
diff --git a/app/models/extensions/with-root-store.ts b/app/models/extensions/with-root-store.ts
new file mode 100644
index 0000000..eff769c
--- /dev/null
+++ b/app/models/extensions/with-root-store.ts
@@ -0,0 +1,17 @@
+import { getRoot, IStateTreeNode } from "mobx-state-tree"
+import { RootStoreModel } from "../root-store/root-store"
+
+/**
+ * Adds a rootStore property to the node for a convenient
+ * and strongly typed way for stores to access other stores.
+ */
+export const withRootStore = (self: IStateTreeNode) => ({
+ views: {
+ /**
+ * The root store.
+ */
+ get rootStore() {
+ return getRoot<typeof RootStoreModel>(self)
+ },
+ },
+})
diff --git a/app/models/index.ts b/app/models/index.ts
new file mode 100644
index 0000000..3538dbb
--- /dev/null
+++ b/app/models/index.ts
@@ -0,0 +1,5 @@
+export * from "./extensions/with-environment"
+export * from "./extensions/with-root-store"
+export * from "./root-store/root-store"
+export * from "./root-store/root-store-context"
+export * from "./root-store/setup-root-store"
diff --git a/app/models/root-store/root-store-context.ts b/app/models/root-store/root-store-context.ts
new file mode 100644
index 0000000..537e51c
--- /dev/null
+++ b/app/models/root-store/root-store-context.ts
@@ -0,0 +1,22 @@
+import { createContext, useContext } from "react"
+import { RootStore } from "./root-store"
+
+/**
+ * Create a context we can use to
+ * - Provide access to our stores from our root component
+ * - Consume stores in our screens (or other components, though it's
+ * preferable to just connect screens)
+ */
+const RootStoreContext = createContext<RootStore>({} as RootStore)
+
+/**
+ * The provider our root component will use to expose the root store
+ */
+export const RootStoreProvider = RootStoreContext.Provider
+
+/**
+ * A hook that screens can use to gain access to our stores, with
+ * `const { someStore, someOtherStore } = useStores()`,
+ * or less likely: `const rootStore = useStores()`
+ */
+export const useStores = () => useContext(RootStoreContext)
diff --git a/app/models/root-store/root-store.ts b/app/models/root-store/root-store.ts
new file mode 100644
index 0000000..1131b48
--- /dev/null
+++ b/app/models/root-store/root-store.ts
@@ -0,0 +1,20 @@
+import { Instance, SnapshotOut, types } from "mobx-state-tree"
+import { CharacterStoreModel } from "../character-store/character-store"
+
+/**
+ * A RootStore model.
+ */
+// prettier-ignore
+export const RootStoreModel = types.model("RootStore").props({
+ characterStore: types.optional(CharacterStoreModel, {} as any),
+})
+
+/**
+ * The RootStore instance.
+ */
+export interface RootStore extends Instance<typeof RootStoreModel> {}
+
+/**
+ * The data of a RootStore.
+ */
+export interface RootStoreSnapshot extends SnapshotOut<typeof RootStoreModel> {}
diff --git a/app/models/root-store/setup-root-store.ts b/app/models/root-store/setup-root-store.ts
new file mode 100644
index 0000000..4a6d0c5
--- /dev/null
+++ b/app/models/root-store/setup-root-store.ts
@@ -0,0 +1,55 @@
+import { onSnapshot } from "mobx-state-tree"
+import { RootStoreModel, RootStore } from "./root-store"
+import { Environment } from "../environment"
+import * as storage from "../../utils/storage"
+
+/**
+ * The key we'll be saving our state as within async storage.
+ */
+const ROOT_STATE_STORAGE_KEY = "root"
+
+/**
+ * Setup the environment that all the models will be sharing.
+ *
+ * The environment includes other functions that will be picked from some
+ * of the models that get created later. This is how we loosly couple things
+ * like events between models.
+ */
+export async function createEnvironment() {
+ const env = new Environment()
+ await env.setup()
+ return env
+}
+
+/**
+ * Setup the root state.
+ */
+export async function setupRootStore() {
+ let rootStore: RootStore
+ let data: any
+
+ // prepare the environment that will be associated with the RootStore.
+ const env = await createEnvironment()
+ try {
+ // load data from storage
+ data = (await storage.load(ROOT_STATE_STORAGE_KEY)) || {}
+ rootStore = RootStoreModel.create(data, env)
+ } catch (e) {
+ // if there's any problems loading, then let's at least fallback to an empty state
+ // instead of crashing.
+ rootStore = RootStoreModel.create({}, env)
+
+ // but please inform us what happened
+ __DEV__ && console.tron.error(e.message, null)
+ }
+
+ // reactotron logging
+ if (__DEV__) {
+ env.reactotron.setRootStore(rootStore, data)
+ }
+
+ // track changes & save to storage
+ onSnapshot(rootStore, (snapshot) => storage.save(ROOT_STATE_STORAGE_KEY, snapshot))
+
+ return rootStore
+}