Creates a new key value store with the given namespace
.
You can think of each namespace
as an individual database that you can store data in.
Creates a key value store with the namespace name "simple". All the examples in this module assume
this namespace is defined earlier in the code, along with a command group named commands
.
const simpleKv = new pylon.KVNamespace('simple');
The name used to identify your namespace.
The namespace that this KVNamespace was constructed with.
Compare and set a key's value atomically.
NOTE: This function is a lower-level building block for safer data mutation. You almost always want to use the higher level transact or transactWithResult functions.
If you are reading a value, modifying that value, and then writing a new value, for example, if you are writing a currency or economy system and are looking to ensure that the user's balance is atomically updated.
Compare and set is fundamental to ensure that read after write and races do not occur. For example, if a player was to spend their currency twice concurrently (because let's say they execute the command quickly...). There is a chance that both reads will occur with their original balance, thus causing the deduction in funds to only happen once, essentially letting them cheat the system. Compare and set will ensure that if the key was updated since you've read it, that the operation fails.
Update a player's balance, giving them 100 "credits". This is not safe, as two kv.get()
s can happen concurrently, thus causing
the player's balance to only get incremented by 100, instead of 200 if this code was run twice concurrently.
const prevBalance = await simpleKv.get<number>('balance');
await simpleKv.put('balance', (prevBalance ?? 0) + 100);
Updates a player's balance atomically. Failing if the balance was updated since it was read.
const prevBalance = await simpleKv.get<number>('balance');
await simpleKv.cas('balance', prevBalance, (prevBalance ?? 0) + 100);
The key to compare and set
The value the key must equal in order for the key to be set
.
The value the key will be set to if the current value equals compare
.
Compare and set a key's value atomically, setting the value only if the key does not already exist.
The following are equivalent: simpleKv.put('foo', 'bar', { ifNotExists: true })
and simpleKv.cas('foo', undefined, 'bar')
The key to compare and set
When set to undefined
, the key will only be set
if it does not exist.
The value the key will be set to if the key does not exist.
Compare and delete a key's value atomically. This is functionally equivalent to delete, if the prevValue
option is
provided.
The following are equivalent: simpleKv.delete('foo', {prevValue: 'bar'})
and simpleKv.cas('foo', 'bar', undefined)
.
The key to compare and set.
The value to compare
the key to.
When set to undefined
, causes the key to be deleted if it is equal to compare
Compare and set multiple keys atomically. This is like cas, but operates across of multiple keys.
NOTE: This function is a lower-level building block for safer data mutation. You almost always want to use the higher level transactMulti or transactMultiWithResult functions.
This is useful if you want to atomically update multiple keys at a time. The casMulti
operation will only succeed if all of the
operations
provided succeed. If any of the operations fail, no data will be mutated.
Deprecated casMulti
, prefer the one that takes pylon.KVNamespace.CasOperation
Clears the namespace. Returning the number of keys deleted.
This operation will delete all the data in the namespace. The data is irrecoverably deleted.
Use with caution!
Returns the number of keys present in this namespace.
Deletes a given key from the namespace. Throwing if the key does not exist, or if options.prevValue
is set, the previous value is not equal to the
value provided.
A command to delete a given key:
commands.on('delete', ctx => ({key: ctx.string()}), async (message, {key}) => {
await simpleKv.delete(key);
await message.reply('deleted');
});
Deletes a given key, if its value matches prevValue
.
commands.on('delete.ifEquals', ctx => ({key: ctx.string(), prevValue: ctx.text()}), async (message, {key, prevValue}) => {
await simpleKv.delete(key, { prevValue });
await message.reply('deleted');
});
The key to delete
Options, which can provide a delete if equals.
Gets a key's value - returning the value or undefined. Type T
can be provided, in order to cast the return type of the
function to a given Json type.
A command that gets the value of the key as a string
and replies with it.
commands.on('get', ctx => ({key: ctx.string()}), async (message, {key}) => {
const value = await simpleKv.get<string>(key);
await message.reply(`${key} is ${value}`);
});
The key to get
Exactly like list, except that it returns the key + value pairs instead.
The maximum limit is 100, however.
Lists the keys that are set within the namespace.
Note that the each call to list will return at most 1,000 keys. In order to
paginate, simply call list
again, with the last item in the array set to the from
option. Keys are
returned in their lexicographical sort order.
A command to list the first 1,000 keys:
commands.raw('list', async (message) => {
const keys = await simpleKv.list();
await message.reply(`The ${keys.length} keys in the simpleKV are:\n${keys.join('\n')}`);
});
How to paginate through all keys:
async function getAllKeys(kv: pylon.KVNamespace): Promise<string[]> {
const keys: string[] = [];
let from = "";
while (true) {
const page = await kv.list({from, limit: 1000});
keys.push(...page);
if (page.length < 1000) break;
from = page[page.length - 1];
}
return keys;
}
Sets the value of a given key within the key-value store.
Sets a key to a given value.
commands.on('put', ctx => ({key: ctx.string(), value: ctx.text()}), async (message, {key, value}) => {
await simpleKv.put(key, value);
await message.reply('OK!');
});
We can also get a bit more fancy, and set a key only if it doesn't exist:
commands.on('put.nx', ctx => ({key: ctx.string(), value: ctx.text()}), async (message, {key, value}) => {
try {
await simpleKv.put(key, value, { ifNotExists: true });
await message.reply('key set!');
} catch(_) {
await message.reply('key not set - already exists!');
}
});
Additionally, we can also set an expiry for a key, if we only want pylon to store it for a limited time:
commands.on('put.ttl', ctx => ({key: ctx.string(), ttl: ctx.integer(), value: ctx.text()}), async (message, {key, value}) => {
try {
await simpleKv.put(key, value, { ttl });
await message.reply(`key set, with ${ttl} millisecond time to live`);
} catch(_) {
await message.reply('key not set - already exists!');
}
});
The key to set.
The value to set - must be JSON serializeable.
A higher level alternative to cas. Atomically updates a key, using the specified transaction
function.
Atomically increments a key by a given value, returning the updated value.
const incr = (key: string, inc: number): Promise<number> =>
simpleKv
.transact<number>(key, (prev) => (prev ?? 0) + inc)
.then((val) => val ?? 0);
commands.on('incr', ctx => ({key: ctx.string(), inc: ctx.integer()}), async (message, {key, inc}) => {
const next = await incr(key, inc);
await message.reply(`Incremented value to ${next}`);
})
The key to transact upon.
A function that mutates the value, returning the value that should be set to key
.
Generated using TypeDoc
Pylon provides a built in key-value store that you can use to persist data.
Since the isolate that runs your scripts is torn down every time you publish your script, and is not guaranteed to have any specific lifetime, in order to persist data, you can't use global variables, as those variables may be cleared out at any given time. Instead, you can use the pylon.KVNamespace to store data associated with your scripts.