Features
We’ll show the different applications you can use Memorix for, and how to do so
Cache
Cache is the most basic use of an in-memory service, to use it simply add a line to your Cache scope in schema.memorix
:
Cache {
adminUser {
payload: {
email: string
password: string
}
}
}
And to use it in your project
await memorix.cache.adminUser.set({
email: "me@mail.com",
password: "Aa123456",
});
const adminUser = await memorix.cache.adminUser.get();
from src.generated_schema import Memorix, CacheAdminUser
...
memorix.cache.adminUser.set(CacheAdminUser(email="me@mail.com", password="Aa123456"))
admin_user = memorix.cache.adminUser.get()
memorix.cache.adminUser.set(
memorix_generated::CacheAdminUser {
email: "me@mail.com".to_string(),
password: "Aa123456".to_string(),
}
).await?;
let admin_user = memorix.cache.adminUser.get().await?;
Cache key
If we want to store and get multiple objects of the same kind that’s also possible!
Cache {
user {
key: number
payload: {
email: string
password: string
}
}
}
And to use it in your project
await memorix.cache.user.set(1, {
email: "me@mail.com",
password: "Aa123456",
});
await memorix.cache.user.set(2, {
email: "you@mail.com",
password: "Aa123456",
});
const me = await memorix.cache.user.get(1);
from src.generated_schema import Memorix, CacheUser
...
memorix.cache.user.set(
1,
CacheAdminUser(email="me@mail.com", password="Aa123456"),
)
memorix.cache.user.set(
2,
CacheAdminUser(email="you@mail.com", password="Aa123456"),
)
me = memorix.cache.adminUser.get(1)
memorix.cache.user.set(
&1,
memorix_generated::CacheAdminUser {
email: "me@mail.com".to_string(),
password: "Aa123456".to_string(),
},
).await?;
memorix.cache.user.set(
&2,
memorix_generated::CacheAdminUser {
email: "you@mail.com".to_string(),
password: "Aa123456",
},
).await?;
let me = memorix.cache.adminUser.get(&1).await?;
Key can be any type you like, even a nested object
Cache options
You can define cache options globally or per cache item in your schema to change it’s behaviour
DefaultOptions {
cache: {
expire: {
value: 5
}
}
}
Cache {
hello {
payload: string
options: {
expire: {
value: 10
}
}
}
helloForever {
payload: string
options: {
expire: null
}
}
}
Here to defined that each cache item will expire in 5 seconds, but specifically hello
will expire in 10 seconds and helloForever
won’t expire.
name | Type | Default | Description |
---|---|---|---|
expire | object | null - No expiration | Expiration options for the cache item |
expire.value | int | Required | The numerical value of how many seconds (or milliseconds) until the data is expired and can be deleted from the cache |
expire.isInMs | boolean | false | Whatever the expire.value is in seconds or milliseconds, not setting this will make it in seconds |
expire.extendOnGet | boolean | false | If is set to true, the item’s expiration will be reset |
PubSub
PubSub (short for publish and subscribe) is a feature used to broadcast messages from one publisher to many subscribers.
Unlike Cache
, the message isn’t saved anywhere and is just passed along.
To use it simply add a line to your PubSub scope in schema.memorix
:
PubSub {
message {
payload: string
}
}
And to use it in your project
await { stop } = memorix.pubsub.message.subscribe(({ payload }) => {
// Will be called twice with "hello" then "world"
console.log("Got payload: " + payload);
});
await memorix.pubsub.message.publish("hello");
await memorix.pubsub.message.publish("world");
await stop();
await memorix.pubsub.message.publish("Will be published but no one is listening");
You can also subscribe to an Async iterable
if you don’t pass a callback
const subscription = await memorix.pubsub.message.subscribe();
for await (const { payload } of subscription.asyncIterator) {
console.log("Got payload: " + payload);
}
To use python pubsub, you need to subscribe on a Thread
or a Process
since it’s a blocking code, for example
import multiprocessing
def listen_to_message() -> None:
for res in memorix.pubsub.message.subscribe():
# Will be called twice with "hello" then "world"
print("Got payload: ", res.payload)
process = multiprocessing.Process(target=listen_to_message)
memorix.pubsub.message.publish(payload="hello")
memorix.pubsub.message.publish(payload="world")
process1.kill()
memorix.pubsub.message.publish(payload="Will be published but no one is listening")
To use rust pubsub, you need to subscribe on a different async function, for example
extern crate tokio;
extern crate futures_util;
use futures_util::StreamExt;
async fn listen_to_message(
mut memorix: example_schema_generated::Memorix,
) -> Result<(), Box<dyn std::error::Error>> {
let subscription = memorix.pubsub.message.subscribe().await?
while let Some(res) = stream.next().await {
let payload = res?.payload;
println!("Got payload: {}", payload);
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut memorix = example_schema_generated::Memorix::new("redis://localhost:6379/0").await?;
let futures_v: Vec<
std::pin::Pin<
Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error>>>>,
>,
> = vec![
Box::pin(listen_to_message(memorix.clone())),
Box::pin(async move {
memorix.pubsub.message.publish(&"hello".to_string()).await?;
memorix.pubsub.message.publish(&"world".to_string()).await?;
Ok(())
}),
];
futures::future::select_all(futures_v).await.0?; // Run until one is complete
memorix.pubsub.message.publish(&"Will be published but no one is listening".to_string()).await?;
Ok(())
}
PubSub key
PubSub also supports key the same as Cache
supports it, you can use it to publish a message to a specific subscriber or a group.
Task
Task is the last feature Memorix supports, it sends a message to a specific queue, and one of the listeners picks it up.
Unlike
PubSub
, even if no one is listening to a queue, it’s saved and will be passed to a single listener once it starts listening.
To use it simply add a line to your Task scope in schema.memorix
:
Task {
addMessage {
payload: string
}
}
And to use it in your project
await memorix.task.addMessage.queue("hello");
await memorix.task.addMessage.queue("world");
await { stop } = memorix.task.addMessage.dequeue(async ({ payload }) => {
// Will be called twice with "hello" then "world"
console.log("Got payload: " + payload);
// To stop listening, just for example
if (payload === "world") {
await stop();
}
});
// Clears queue
await memorix.task.addMessage.clear();
You might want to consider also using a
Thread
or aProcess
just like we did withPubSub
, this example won’t do that
memorix.task.addMessage.queue(payload="hello")
memorix.task.addMessage.queue(payload="world")
for res in memorix.task.addMessage.dequeque():
# Will be called twice with "hello" then "world"
print("Got payload: ", res.payload)
# To stop listening, just for example
if res.payload == "world"
break
# Clears queue
memorix.task.addMessage.clear()
Task key
Task also supports key the same as Cache
and PubSub
support it, you can use put messages in different queues.
Task retuns
Since Task sends a message from a single machine to another, we also support sending a response back to the original sender!
To use it simply add returns
to your specific Task
Task {
addMessage {
payload: string
returns: boolean
}
}
And to use it in your project
memorix.task.addMessage.dequeue(async ({ payload }) => {
console.log("Got payload: " + payload);
return true;
});
const { getReturns } = await memorix.task.addMessage.queue("hello");
const isAddSuccessful = await getReturns();
console.log(isAddSuccessful); // Should print "true"
def listen_to_message() -> None:
for res in memorix.task.addMessage.dequeque():
print("Got payload: ", res.payload)
res.send_returns(returns=True)
process = multiprocessing.Process(target=listen_to_message)
queue = memorix.task.addMessage.queue(payload="hello")
res = queue.get_returns()
print(res.value) # Should print "true"
Task options
You can define task options globally or per task item in your schema to change it’s behaviour
DefaultOptions {
task: {
takeNewest: false
}
}
Task {
addMessage {
payload: string
returns: boolean
options: {
takeNewest: true
}
}
}
name | Type | Default | Description |
---|---|---|---|
takeNewest | boolean | False | By default dequeque is FIFO (first in first out), this option changes it |
Namespace
Namespaces are useful when using multiple schemas in order to avoid name collisions, also great for microservices to define their own schema.
To use it simply surround your desired scopes with a namespace scope
messages-schema.memorix
Namespace messages {
PubSub {
addItem {
payload: int
}
}
}
then you can import it in your main schema
schema.memorix
Config {
output: {
language: "typescript"
file: "memorix.generated.ts"
}
extends: [
"<path-to>/messages-schema.memorix"
"<path-to>/another-schema.memorix"
]
}
PubSub {
addItem {
payload: string
}
}
Even though we defined PubSub.addItem
twice, since one is in a namespace, they won’t collide with each other.
To use it in your project
await memorix.pubsub.addItem.publish(12);
await memorix.messages.pubsub.addItem.publish("in 'messages' namespace");
You might want to consider also using a
Thread
or aProcess
just like we did withPubSub
, this example won’t do that
memorix.pubusb.addItem.publish(payload=12)
memorix.messages.pubusb.addItem.publish(payload="woin 'messages' namespacerld")