Skip to content

Instantly share code, notes, and snippets.

@idibidiart
Last active October 11, 2019 15:42
Show Gist options
  • Save idibidiart/efb0d81cd2e64901cb56c661e2c08b40 to your computer and use it in GitHub Desktop.
Save idibidiart/efb0d81cd2e64901cb56c661e2c08b40 to your computer and use it in GitHub Desktop.
Cyclic Graph App-State Model
/*
Cyclic Graph App State Model:
1. Fully normalized data, no repetition and no need for synchronization of duplicated entities
2. Easy to serialize since objects don't directly reference other objects.
3. Allows type annotations using natural JS syntax (works with user defined types in e.g. type.js, see BigSet.)
4. Uses Symbols assigned to const as top-level keys, so accidental key name collision is not possible (works for
large teams.)
5. Setting state does not invoke a default renderer, so you cam use HTML/SVG renderer, WebGL renderer,
Audio/Conversational renderer etc.
6. Single-renderer, multi-renderer and render-less effects are encapsulated and named and the entire app state is
captured in the graph, which can be visualized to show app-state dependencies of all effects within the app. It allows
the developer to more easily answer the question "which parts of the app's state will be involved when a certain effect
is invoked?" The effect can be render-ful or render-less. It's different than simply being able to see state changes,
or time travel through state changes, as in Redux, which this can be adapted to do, too. It captures, statically,
which part of the state will be involved (read and/or write) in each effect. TODO: build a visualizer for this.
7. The default HTML/SVG renderer allows rendering some or all siblings at each level of the DOM tree, asynchronously,
using a custom filter function.
8. Zero hidden state (not enforced but encouraged as the norm)
*/
// symbols as consts (for collision-free key names)
const entropyLevel1Container = Symbol('entropyLevel1Container')
const entropyLevel2Container = Symbol('entropyLevel2Container')
const entropyLevel3Container = Symbol('entropyLevel3Container')
const entropyDialogContainer = Symbol('entropyDialogContainer')
const entropyDialogElement = Symbol('entropyDialogElement')
const modalContainer = Symbol('modalContainer')
const actionAreaContainer = Symbol('actionAreaContainer')
const titleContainer = Symbol('titleContainer')
const action = Symbol('action')
const emailContainer = Symbol('emailContainer')
const emailStatusContainer = Symbol('emailStatusContainer')
const passPhraseContainer = Symbol('passPhraseContainer')
const ppStatusContainer = Symbol('ppStatusContainer')
//
const emailInputElement = Symbol('emailInputElement')
const ppInputElement = Symbol('ppInputElement')
const ppEyeElement = Symbol('ppEyeElement')
const ppBrainElement = Symbol('ppBrainElement')
const ppRefreshElement = Symbol('ppRefreshElement')
const loginButtonElement = Symbol('loginButtonElement')
//
const numWords = Symbol('numWords')
const randomSet = Symbol('randomSet')
//
const entropyDialogFunction = Symbol('entropyDialogFunction')
const getPassPhraseFunction = Symbol('getPassPhraseFunction')
const errorWhileLoadingFunction = Symbol('errorWhileLoading')
const entropyCheckedFunction = Symbol('entropyCheckedFunction')
const randomWordsFunction = Symbol('randomWordsFunction')
// shared mutable state (app and DOM state) as a graph of types
var _State = {
[numWords]: {
value: void Number,
},
[randomSet]: {
set: void BigSet,
full: void Boolean
},
[action]: {
type: void String
},
[entropyDialogFunction]: {
namedEdges: {
modalContainer,
numWords,
}
},
[getPassPhraseFunction]: {
namedEdges: {
ppInputElement,
ppStatusContainer,
emailStatusContainer,
actionAreaContainer,
numWords,
randomSet
}
},
[errorWhileLoadingFunction]: {
namedEdges: {
ppStatusContainer,
ppStatusContainer,
actionAreaContainer,
passPhraseContainer
}
},
[entropyCheckedFunction]: {
namedEdges: {
numWords
}
},
[randomWordsFunction]: {
namedEdges: {
numWords,
randomSet
}
},
[entropyDialogContainer]: {
id: void String,
message: void String,
renderFn: void Function,
init: void Boolean
},
[entropyDialogElement]: {
id: void String,
text: void String,
namedEdges: {
modalContainer,
entropyDialogContainer
},
},
[entropyLevel1Container]: {
id: void String,
inputID: void String,
renderFn: void Function,
message: void String,
namedEdges: {
entropyLevel2Container,
entropyLevel3Container,
numWords
}
},
[entropyLevel2Container]: {
id: void String,
inputID: void String,
renderFn: void Function,
message: void String,
namedEdges: {
entropyLevel1Container,
entropyLevel3Container,
numWords
}
},
[entropyLevel3Container]: {
id: void String,
inputID: void String,
renderFn: void Function,
message: void String,
namedEdges: {
entropyLevel1Container,
entropyLevel2Container,
numWords
}
},
[modalContainer]: {
id: void String,
open: void Boolean
},
[titleContainer]: {
id: void String,
content: void String,
renderFn: void Function
},
[ppInputElement]: {
id: void String,
value: void String,
readonly: void Boolean,
namedEdges: {
ppStatusContainer,
passPhraseContainer
}
},
[ppEyeElement]: {
id: void String,
namedEdges: {
ppInputElement
}
},
[ppBrainElement]: {
id: void String,
namedEdges: {
ppInputElement
}
},
[ppRefreshElement]: {
id: void String,
namedEdges: {
ppInputElement,
entropyDialogContainer,
passPhraseContainer,
ppStatusContainer,
actionAreaContainer
}
},
[emailInputElement]: {
id: void String,
value: void String
},
[loginButtonElement]: {
id: void String,
disabled: void Boolean,
text: void String,
namedEdges: {
ppInputElement,
emailInputElement,
actionAreaContainer
}
},
[actionAreaContainer]:
{
message: void String,
done: void Boolean,
id: void String,
renderFn: void Function,
namedEdges: {
loginButtonElement,
emailStatusContainer,
ppStatusContainer
}
},
[emailContainer]: {
title: void String,
id: void String,
renderFn: void Function
},
[emailStatusContainer]: {
passed: void Boolean,
id: void String
},
[passPhraseContainer]: {
id: void String,
title: void String,
renderFn: void Function,
namedEdges: {
ppInputElement,
ppEyeElement,
ppBrainElement,
ppRefreshElement,
ppStatusContainer,
entropyDialogContainer,
randomSet
}
},
[ppStatusContainer]: {
passed: void Boolean,
busy: void Boolean,
error: void Boolean
}
}
const renderState = function(type, filterFn) {
if (type instanceof Array) {
if (typeof filterFN == "function") {
type = type.filter(filterFN)
}
type.forEach(function (v, i, o) {
renderState(v)
})
} else {
_State[type].renderFn(type, self[_State[type].id])
}
}
const State = function(type) {
return _State[type]
}
const linkedState = function(type, linkName) {
return _State[_State[type].namedEdges[linkName]]
}
const linkedTypes = function(type, linkName) {
return _State[type].namedEdges[linkName]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment