I am developing apps using Titanium and trying to implement the CommonJS approach. I like the modular setup, but I am wondering how best to deal with things like a shopping cart: temporary, user-created data that needs to last through the lifetime of the app.
I can see three approaches:
1. Create a special module for such a Cart. It will be created the first time it's require()d, and you can access the cart in its current state from any other module by require()ing it from those modules.
Pass a quasi global Cart object to every module that needs it. This is in breach of the letter and the spirit of CommonJS.
Store the Cart in local memory using Ti.App.Properties. This way, the cart is retained even when the user quits the app.
Any thoughts on what would be best?
The solution I'd prefer is to create a CommonJS module the following way:
function ShoppingCart(options) {
// do some setup for the shopping cart
}
ShoppingCart.prototype.add(product, qty)
// add product to cart
}
ShoppingCart.prototype.remove(product, qty)
// remove product from cart
}
ShoppingCart.prototype.clear()
// empty cart (and create new, empty one)
}
// etc.
ShoppingCart = new ShoppingCart();
module.exports = ShoppingCart;
How to access?
var Cart = require('path/to/ShoppingCart');
Cart.add();
Cart.remove();
Cart.clear();
This creates a kind of singleton which is created the first time you're calling it and it is kept until the app is finished (removed from memory) or you implement the clear method and clean it by yourself. You can also persist the data using this singleton, it's up to you which parts you gonna implement. It's similar to your first idea.
Your second idea has several disadvantages because data access isn't encapsulated into a module and data is persisted always so you need to detect if it is old and can be removed or not.
Finally it depends on your task. Do you need persisten storage you should combine the module with a database. Do you need this information only during runtime, the module is enough.
Related
I am trying to iteratively replace .cshtml razor views with what I wanted to call Vue "mini-apps". Which should be somewhere in between a micro-frontend and a classic SPA. The aim is to share some of the code base, mainly dependencies. Compile a common chunk-vendors.js and have the "mini-apps" as separate javascript entry files to place on appropriate views. As performance demand would grow, I would progress into splitting chunk-vendors.js and optimize via lazy-loading.
The problem I am hitting here is trying to make two root Vue instances talk to each other through a shared state. Right now only the app that is imported/mounted first stays reactive. I thought that my problem was in the Vue 2 reactivity system/how Vuex binds itself to a concrete Vue instance here. When I implemented a primitive store, the situation ended up being exactly the same.
What confuses me about this is that if I were to instantiate two applications in a single main.js entry file, the store sharing would just work. Which suggest that Vue is either creating some kind of hidden root instance or that my Vuex code analysis deduction of it binding to a concrete instance was incorrect.
I would highly appreciate it if someone could tell me why this can't work, optionally suggest a workaround?
I have created a reproduction both in Vue 2 with Vuex and in Vue 3 with composition API/primitiveStoreImplementation here.
Vue-cli is building the app in an MPA mode with pages specified in vue.config.json, then imported in the root index.html file. The store is initialised once and saved for later check/loading on the window object. In the context of asp/razor I would have webpack set up to remove the redundant files, only leaving javascript bundles. Also, the dev proxy would proxy everything except the path to the script bundles. All of this is removed for the sake of the demonstration.
(once I find a solution I hope to replace the source link with specific code snippets)
Options considered:
I am trying to avoid it, but I might have to always run a "coordinator" root instance that will check the presence of certain elements on a page and load/unload the "mini-apps" as components using something like portal-vue when needed. That coordinator would also contain a state with modules, some of which would be marked as "shared" thus operations from multiple "mini-apps" would be allowed (ownership flag check).
I have considered sessionStorage/localStorage, the problem is that the 'storage' events are only triggered across tabs and not within one document first |Note. I would have to trigger a custom event or play around with iframes. Feels too hacky, but that might be an axiom here. It would also duplicate the same state across many store instances.
These are some relevant articles I have found on this topic:
Probably closest to what I am trying to achieve:
Using Vuex with multiple Vue instances
Same but different:
Build Vue microfrontend app (with routing and vuex store)
The use case for multiple entries are sub-apps that don't coexist on the same page, although they can. They could be web components or regular app bundles. They can even interact with each other but they need something else for this - global event bus or a mediator app that passes data between them.
The problem is that there are more than one Vue library copies and/or more than one Vuex store instance. In order to avoid this, they would need to be precisely loaded only once on the page and reused between apps, i.e. vue and vuex are loaded as CDN libs, possibly used as Webpack externals to use window.Vue and window.Vuex transparently for respective import. Not only Vuex but store needs to be a singleton on the page (basically a said mediator). This is acceptable solution but primarily suitable for existing applications that have design restrictions and need a workaround.
I am trying to avoid it, but I might have to always run a "coordinator" root instance that will check the presence of certain elements on a page and load/unload the "mini-apps" as components using something like portal-vue when needed.
This is the common way to do this. Vue 3 has teleports that has give less control than portal-vue. It has no downsides for application design if done properly. The same thing is achieved similarly in other frameworks (Angular, React) as well, where portals appeared earlier.
I have considered sessionStorage/localStorage, the problem is that the 'storage' events are only triggered across tabs and not within one document
This is solved by using window postMessage and message event in the same tab. In case this shouldn't be limited to a single window, there are third party libs that use both for cross-tab synchronzation, a native alternative is BroadcastChannel that has less browser support than LS but doesn't have its limitations regarding tabs.
Mobx and Redux will normally not persist any data. They will maintain a temporary global state while the app is running.
I know there are redux-persist and mobx-persist packages within both communities. But unfortunately these persisting solutions do not seem good at all. They only stringify or serialize a global state tree and persist it using some sort of key-value storage. Right?
The problem:
When such an app is open again, the stringified store will be parsed and structured back to its original data structure (JSON, for instance) and then fully loaded into the RAM memory. Am I right?
If yes, this is a problem. It is not good to always have a full "database" aka "global state" loaded in-memory. It will probably never be faster to filter data within a long array in my global state... compared to querying a table on SQLite, right?
I have been looking for some repository-like solution for persisting global state for either redux or mobx. I am yarning for some solution for persisting and querying data on some well-known mobile database like SQLite or others.
Any answers will be very much appreciated.
Indeed you can use repository pattern.
On your repository, you may have a save method.
save(group: GroupLocalStorageModel): Promise<boolean> {
let created;
this._localStorage.write(() => {
created = this._localStorage.create<GroupLocalStorageModel>("Group", group);
});
return Promise.resolve(true);
}
This method will literally save your entity to some local storage you set. In the example above, we are saving a group object to a Group collection which are like tables. We are using realm which is no-sql.
Once you have your repository, if you are using either redux or mobx, you will probably call your save method on your action. Both redux and mobx work with actions, right?
export const GroupStoreModel = types
.model("GroupStore")
.props({
groups: types.optional(types.array(GroupModel), []),
})
.extend(withEnvironment)
.actions((self) => {
return ({
_addGroupToStore(group: GroupLocalStorageModel) {
self.groups.push(group)
},
_deleteAllFromStore() {
self.groups.clear()
},
_addGroupsToStoreBatch: (groups: GroupLocalStorageModel[]) => {
self.groups.concat(groups);
},
})
})
/* Async actions */
.actions((self) => {
let groupRepository = self.environment.groupRepository;
return ({
addGroup(group: GroupLocalStorageModel) {
groupRepository.save(group).then(result => self._addGroupToStore(group))
},
getAllGroupsPaginated(page: number) {
groupRepository.getAllPaginated(page).then(groups => self._addGroupsToStoreBatch(groups));
},
deleteAll() {
groupRepository.deleteAll();
self._deleteAllFromStore();
}
})
})
In this example, we are using mobx-state-tree. And this addGroup action will update firstly our database, and then update also the global state.
We still want to use our global state so our views will be re-rendered automatically according to either connect for redux or observable for mobx.
See more informations here on the repository:
https://github.com/Hadajung/poc-react-native-database-example
AFAIK, there are two options for using sqlite with redux persist.
redux-persist-sqlite-storage: By maintainer's own word
By default redux-persist uses AsyncStorage as storage engine in react-native. This is a drop-in replacemet of AsyncStorage.
The library is inspired by react-native-sqlite-storage.
Please, remember, to use this, you need to install an additional package installed react-native-sqlite-storage
redux-persist-sqlite: By maintainer's own word
A redux-persist storage adapter that writes to sqlite.
This is adapted from https://github.com/prsn/redux-persist-sqlite-storage, but uses Node.js sqlite3 rather than react-native.
Great for Electron apps that are backed by Redux.
UPDATE: react-native-mmkv : This is developed by WeChat. As it says in its about section
An extremely fast key/value storage library for React Native. ~30x faster than AsyncStorage!
I'm not really sure what you need but if I understood you correctly you need to persist large amounts of data, and also to load that same data, but only in batches.
I believe that this kind of problem can be solved with a repository pattern and SOLID design principles.
You will need:
store class (mobx store) that holds your business logic.
repository class which is responsible for retrieving and persisting data.
The store gets the repository injected into it via the constructor.
Then when you call the initialize method on your store, it talks to the repository and retrieves the initial data. Now, initial data can be only a subset of all the data that is persisted. And you can implement some kind of paging on the store and repository, to retrieve data in batches as needed. Later you can call other methods to load and save additional data as needed.
pseudo code:
class Repository(){
initialize()// load the first batch
load(next 10 models)
save(data)
}
class Store{
constructor(repository)
initialize(){
repository.initialize()
}
load(){
repository.load()
}
save(){
repository.save()
}
}
Now your application data shouldn't be one giant object, rather it should consist of multiple stores, where each store is responsible for a part of the data. For example, you would have one store and repository for handling todos and another pair that handles address book contacts etc.
Addendum:
The reason the repository is injected into the store is so you could easily swap it for some other implementation (the store doesn't care how the data is persisted and retrieved) and also, unit testing is very easy.
You could also have a root store that would hold all other stores, so in essence you have your complete state in one place. So if you call serialize on the root store, it serializes all stores and returns one big object.
I think the best solution would be bloc hydrated or cubit hydrated package from flutter_bloc.
https://pub.dev/packages/hydrated_bloc
In background it uses Hive DB, very performant DB, and only keys are stored in memmory so it should not add huge bloat to the app like SQLite.
If you could make all you APP logic in blocks/cubits, then extra DB calls would be irelevant.
I'm trying to initialize a Shopify AppBridge instance (https://shopify.dev/tools/app-bridge) one time per store (user), and then use that same instance throughout my app to use the various AppBridge features.
My original idea was to add the AppBridge instance into a Vuex store; but I am unable to call any functions within the AppBridge object when retrieving it from the store (something to do with it being a serialized object?). There are various functions I need to use within the instance/object; so this won't work.
So, what is the best way for me to do the following:
App loads
Middleware or function runs, initializing AppBridge with the user's / store's details
Other pages are loaded, use the same AppBridge instance for various features
My current setup is that I re-create a new AppBridge instance on every page / component, but that causes complications.
Any ideas? Much appreciated!
Background:
I'm building an SPA (Single Page Application) PWA (Progressive Web App) using Vue.js. I've a remote PostgreSQL database, serving the tables over HTTP with PostgREST. I've a working Workbox Service Worker and IndexedDB, which hold a local copy of the database tables. I've also registered some routes in my service-worker.js; everything is fine this far....
I'm letting Workbox cache GET calls that return tables from the REST service. For example:
https://www.example.com/api/customers will return a json object of the customers.
workbox.routing.registerRoute('https://www.example.com/api/customers', workbox.strategies.staleWhileRevalidate())
At this point, I need Workbox to do the stale-while-revalidate pattern, but to:
Not use a cache, but instead return the local version of this table, which I have stored in IndexedDB. (the cache part)
Make the REST call, and update the local version, if it has changed. (the network part)
I'm almost certain that there is no configurable option for this in this workbox strategy. So I would write the code for this, which should be fairly simple. The retrieval of the cache is simply to return the contents of the requested table from IndexedDB. For the update part, I'm thinking to add a data revision number to compare against. And thus decide if I need to update the local database.
Anyway, we're now zooming in on the actual question:
Question:
Is this actually a good way to use Workbox Routes/Caching, or am I now misusing the technology because I use IndexedDB as the cache?
and
How can I make my own version of the StaleWhileRevalidate strategy? I would be happy to understand how to simply make a copy of the existing Workbox version and be able to import it and use it in my Vue.js Service Worker. From there I can make my own necessary code changes.
To make this question a bit easier to answer, these are the underlying subquestions:
First of all, the StaleWhileRevalidate.ts (see link below) is a .ts (TypeScript?) file. Can (should) I simply import this as a module? I propably can. but then I get errors:
When I to import my custom CustomStaleWhileRevalidate.ts in my main.js, I get errors on all of the current import statements because (of course) the workbox-core/_private/ directory doesn't exist.
How to approach this?
This is the current implementation on Github:
https://github.com/GoogleChrome/workbox/blob/master/packages/workbox-strategies/src/StaleWhileRevalidate.ts
I don't think using the built-in StaleWhileRevalidate strategy is the right approach here. It might be possible to do what you're describing using StaleWhileRevalidate along with a number of custom plugin callbacks to override the default behavior... but honestly, you'd end up changing so much via plugins that starting from scratch would make more sense.
What I'd recommend that you do instead is to write a custom handlerCallback function that implements exactly the logic you want, and returns a Response.
// Your full logic goes here.
async function myCustomHandler({event, request}) {
event.waitUntil((() => {
const idbStuff = ...;
const networkResponse = await fetch(...);
// Some IDB operation go here.
return finalResponse;
})());
}
workbox.routing.registerRoute(
'https://www.example.com/api/customers',
myCustomHandler
);
You could do this without Workbox as well, but if you're using Workbox to handle some of your unrelated caching needs, it's probably easiest to also register this logic via a Workbox route.
currently the JSONStore API provides a load() method that says in the documentation:
This function always stores whatever it gets back from the adapter. If
the data exists, it is duplicated in the collection". This means that
if you want to avoid duplicates by calling load() on an already
populated collection, you need to empty or drop the collection before.
But if you want to be able to keep the elements you already have in
the collection in case there is no more connectivity and your
application goes for offline mode, you also need to keep track of
these existing elements.
Since the API doesn't provide a "overwrite" option that would replace the existing elements in case the call to the adapter succeeds, I'm wondering what kind of logic should be put in place in order to manage both offline availability of data and capability to refresh at any time? It is not that obvious to manage all the failure cases by nesting the JS code due to the promises...
Thanks for your advices!
One approach to achieve this:
Use enhance to create your own load method (i.e. loadAndOverwrite). You should have access to the all the variables kept inside an JSONStore instance (collection name, adapter name, adapter load procedure name, etc. -- you will probably use those variables in the invokeProcedure step below).
Call push to make sure there are no local changes.
Call invokeProcedure to get data, all the variables you need should be provided in the context of enhance.
Find if the document already exists and then remove it. Use {push: false} so JSONStore won't track that change.
Use add to add the new/updated document. Use {push: false} so JSONStore won't track that change.
Alternatively, if the document exists you can use replace to update it.
Alternatively, you can use removeCollection and call load again to refresh the data.
There's an example that shows how to use all those API calls here.
Regarding promises, read this from InfoCenter and this from HTML5Rocks. Google can provide more information.