Description
A package that can store large chunks of data in a short UTF-16 string, useful for storing data in length-limited
locations such as Discord's custom_id
field in message components.
Features
- Written in TypeScript
- Bundled with esbuild so it can be used in NodeJS and browsers
- Offers CommonJS, ESM, and UMD bundles
- Fully tested
Installation
You can use the following command to install this package, or replace npm install
with your package manager of choice.
npm install @sapphire/string-store
Usage
Note: While this section uses require
, the imports match 1:1 with ESM imports. For example, const { SchemaStore } = require('@sapphire/string-store')
is equivalent to import { SchemaStore } from '@sapphire/string-store'
.
// Require the store classes
const { SchemaStore, Schema, t } = require('@sapphire/string-store');
// Define the enum with the IDs and export it
export const Id = Object.freeze({
AgeUpdate: 0,
StrengthUpdate: 1,
Planet: 2,
User: 3
});
// Create the store in a file and export it
export const store = new SchemaStore()
// Add a schema with an age field stored as a int32:
// Schema<Id.AgeUpdate, { age: number }>
.add(new Schema(Id.AgeUpdate).int32('age'))
// Add a schema with a strength field stored as a float32:
// Schema<Id.StrengthUpdate, { strength: number }>
.add(new Schema(Id.StrengthUpdate).float32('strength'));
// Serialize an `Id.AgeUpdate` object into a string containing:
// - The schema ID (0)
// - The age field (20)
const buffer = store.serialize(Id.AgeUpdate, { age: 20 }).toString();
[!Important] The IDs passed in the
Schema
constant need to be known at compile time, this can be done in the following ways:
- (TS) An
enum
object, which has all of its members strictly typed.- (TS) An object with
as const
, which makes the values known at compile time.- An object with
Object.freeze
as shown in the example, which is equivalent as the previous.- An object with a JSDoc defining the types as the specific values.
- Multiple
const
ants for each value, or
- (TS) Class fields marked as
readonly
.
[!Tip] The serialized string is encoded in UTF-16, meaning it can store 16 bits per character. Each type stores a different number of bits, for example, a single character can store:
- 16 booleans
- 8 2-bit unsigned integers (0-3)
- 4 4-bit unsigned integers (0-15)
- 2 8-bit unsigned integers (0-255)
- 1 16-bit integer (0-65535)
As a use-case, Discord's
custom_id
field in message components can store up to 100 UTF-16 characters, which means it has a storage of 1600 bits, below you can see the supported types and their storage in bits. Keep in mind that the schema ID is stored as a 16-bit integer, and that the property names are not stored.
The schema can be defined using the following methods:
array
Adds an array with a dynamic length to the schema.
// A schema with a single field `names` that is an array of strings:
const schema = new Schema(Id.Planets).array('names', t.string);
// → Schema<Id.Planets, { names: string[] }>
To track the length of the array, it will serialize a 16-bit unsigned integer before the array.
fixedLengthArray
An alternative to array
that has a fixed length. This variant requires the exact number of elements to be
serialized, but it will save space by not storing the length of the array.
// A schema with a single field `names` that is an array of exactly 3 strings:
const schema = new Schema(Id.Planets).fixedLengthArray('names', t.string, 3);
// → Schema<Id.Planets, { names: [string, string, string] }>
nullable
Adds a nullable property of the specified type to the schema.
// A schema with a single field `capitalId` that is a nullable string:
const schema = new Schema(Id.Planets).nullable('capitalId', t.uint32);
// → Schema<Id.Planets, { capitalId: number | null }>
To track whether or not a property is optional, it will serialize a bit, which signals whether or not the value
is defined, due to this, its bit size is null
.
string
Adds a string to the schema.
// A schema with a single field `name` that is a string:
const schema = new Schema(Id.Planet).string('name');
// → Schema<Id.Planets, { name: string }>
The string is serialized as UTF-8, and the length is serialized as a 16-bit unsigned integer before the string.
boolean
Adds a boolean (single bit) to the schema.
// A schema with a single field `isHabitable` that is a boolean:
const schema = new Schema(Id.Planet).boolean('isHabitable');
// → Schema<Id.Planets, { isHabitable: boolean }>
bit
Adds a bit (0 or 1) to the schema. This is a numeric version of boolean
.
// A schema with a single field `isHabitable` that is a bit:
const schema = new Schema(Id.Planet).bit('isHabitable');
// → Schema<Id.Planets, { isHabitable: 0 | 1 }>
int2
Adds a 2-bit signed integer to the schema. It can store values from -2 to 1, inclusive.
// A schema with a single field `type` that is a 2-bit signed integer:
const schema = new Schema(Id.Planet).int2('type');
// → Schema<Id.Planets, { type: -2 | -1 | 0 | 1 }>
uint2
Adds a 2-bit unsigned integer to the schema. It can store values from 0 to 3, inclusive.
// A schema with a single field `type` that is a 2-bit unsigned integer:
const schema = new Schema(Id.Planet).uint2('type');
// → Schema<Id.Planets, { type: 0 | 1 | 2 | 3 }>
int4
Adds a 4-bit signed integer to the schema. It can store values from -8 to 7, inclusive.
// A schema with a single field `type` that is a 4-bit signed integer:
const schema = new Schema(Id.Planet).int4('type');
// → Schema<Id.Planets, { type: -8..=7 }>
uint4
Adds a 4-bit unsigned integer to the schema. It can store values from 0 to 15, inclusive.
// A schema with a single field `type` that is a 4-bit unsigned integer:
const schema = new Schema(Id.Planet).uint4('type');
// → Schema<Id.Planets, { type: 0..=15 }>
int8
Adds an 8-bit signed integer to the schema. It can store values from -128 to 127, inclusive.
// A schema with a single field `type` that is an 8-bit signed integer:
const schema = new Schema(Id.Planet).int8('type');
// → Schema<Id.Planets, { type: -128..=127 }>
uint8
Adds an 8-bit unsigned integer to the schema. It can store values from 0 to 255, inclusive.
// A schema with a single field `type` that is an 8-bit unsigned integer:
const schema = new Schema(Id.Planet).uint8('type');
// → Schema<Id.Planets, { type: 0..=255 }>
int16
Adds a 16-bit signed integer to the schema. It can store values from -32768 to 32767, inclusive.
// A schema with a single field `type` that is a 16-bit signed integer:
const schema = new Schema(Id.Planet).int16('type');
// → Schema<Id.Planets, { type: -32768..=32767 }>
uint16
Adds a 16-bit unsigned integer to the schema. It can store values from 0 to 65535, inclusive.
// A schema with a single field `type` that is a 16-bit unsigned integer:
const schema = new Schema(Id.Planet).uint16('type');
// → Schema<Id.Planets, { type: 0..=65535 }>
int32
Adds a 32-bit signed integer to the schema. It can store values from 2,147,483,648 to 2,147,483,647, inclusive.
// A schema with a single field `type` that is a 32-bit signed integer:
const schema = new Schema(Id.Planet).int32('type');
// → Schema<Id.Planets, { type: 2_147_483_648..=2_147_483_647 }>
uint32
Adds a 32-bit unsigned integer to the schema. It can store values from 0 to 4,294,967,295, inclusive.
// A schema with a single field `type` that is a 32-bit unsigned integer:
const schema = new Schema(Id.Planet).uint32('type');
// → Schema<Id.Planets, { type: 0..=4294967295 }>
int64
Adds a 64-bit signed integer to the schema. It can store values from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807, inclusive.
Note: values smaller than Number.MIN_SAFE_INTEGER
or larger than Number.MAX_SAFE_INTEGER
will lose precision, if you need to store larger numbers, consider using bigInt64
.
// A schema with a single field `type` that is a 64-bit signed integer:
const schema = new Schema(Id.Planet).int64('type');
// → Schema<Id.Planets, { type: -9_223_372_036_854_775_808..=9_223_372_036_854_775_807 }>
uint64
Adds a 64-bit unsigned integer to the schema. It can store values from 0 to 18,446,744,073,709,551,615, inclusive.
Note: values larger than 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER
) will lose precision, if you need to store larger numbers, use bigUint64
.
// A schema with a single field `type` that is a 64-bit unsigned integer:
const schema = new Schema(Id.Planet).uint64('type');
// → Schema<Id.Planets, { type: 0..=18_446_744_073_709_551_615 }>
Note: values larger than Number.MAX_SAFE_INTEGER
will be truncated.
bigInt32
Alternative to int32
that uses BigInt
. It can store values from 2,147,483,648 to 2,147,483,647, inclusive.
// A schema with a single field `type` that is a 32-bit integer:
const schema = new Schema(Id.Planet).bigInt32('type');
// → Schema<Id.Planets, { type: 2_147_483_648n..=2_147_483_647n }>
bigUint32
Alternative to uint32
that uses BigInt
. It can store values from 0 to 4,294,967,295, inclusive.
// A schema with a single field `type` that is a 32-bit integer:
const schema = new Schema(Id.Planet).bigUint32('type');
// → Schema<Id.Planets, { type: 0n..=4294967295n }>
bigInt64
Alternative to int64
that uses BigInt
. It can store values from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807, inclusive.
// A schema with a single field `type` that is a 64-bit integer:
const schema = new Schema(Id.Planet).bigInt64('type');
// → Schema<Id.Planets, { type: -9_223_372_036_854_775_808n..=9_223_372_036_854_775_807n }>
bigUint64
Alternative to uint64
that uses BigInt
. It can store values from 0 to 18,446,744,073,709,551,615, inclusive.
// A schema with a single field `type` that is a 64-bit integer:
const schema = new Schema(Id.Planet).bigUint64('type');
// → Schema<Id.Planets, { type: 0n..=18_446_744_073_709_551_615n }>
float32
Adds a 32-bit floating-point number to the schema.
// A schema with a single field `radius` that is a 32-bit floating-point number:
const schema = new Schema(Id.Planet).float32('radius');
// → Schema<Id.Planets, { radius: number }>
float64
Adds a 64-bit floating-point number to the schema.
// A schema with a single field `radius` that is a 64-bit floating-point number:
const schema = new Schema(Id.Planet).float64('radius');
// → Schema<Id.Planets, { radius: number }>
snowflake
Adds a 64-bit snowflake to the schema.
const schema = new Schema(Id.User).snowflake('id');
// → Schema<Id.User, { id: bigint }>
constant
A constant value that will not get serialized into the buffer, useful for adding extra information to the resulting payloads, such as the name of a piece to run.
// A schema with a single field `handlerName` that is a specific constant:
const schema = new Schema(Id.Planets).constant('handlerFileName', 'planets.ts');
// → Schema<Id.Planets, { handlerFileName: 'planets.ts' }>
Buy us some doughnuts
Sapphire Community is and always will be open source, even if we don't get donations. That being said, we know there are amazing people who may still want to donate just to show their appreciation. Thank you very much in advance!
We accept donations through Open Collective, Ko-fi, PayPal, Patreon, and GitHub Sponsorships. You can use the buttons below to donate through your method of choice.
Donate With | Address |
---|---|
Open Collective | Click Here |
Ko-fi | Click Here |
Patreon | Click Here |
PayPal | Click Here |
Contributors
Please make sure to read the Contributing Guide before making a pull request.
Thank you to all the people who already contributed to Sapphire!