From 4ccd8b9e870d2c57c72a44f6ab25de381a230282 Mon Sep 17 00:00:00 2001 From: Nperma <129764133+nperma@users.noreply.github.com> Date: Sun, 15 Mar 2026 07:01:10 +0700 Subject: [PATCH 1/3] Update README.md --- scripts/quick-db/README.md | 186 +++++++++++-------------------------- 1 file changed, 54 insertions(+), 132 deletions(-) diff --git a/scripts/quick-db/README.md b/scripts/quick-db/README.md index c7c2e1a0..6337ec1f 100644 --- a/scripts/quick-db/README.md +++ b/scripts/quick-db/README.md @@ -1,171 +1,93 @@ -# QuickDB - A Lightweight Database for Minecraft Bedrock ScriptAPI +# QuickDB -**QuickDB** is a simple and efficient database system designed for Minecraft Bedrock Edition ScriptAPI. It utilizes dynamic properties from the `@minecraft/server` module, allowing developers to store and manage key-value pairs in a way similar to JavaScript's `Map` object. +QuickDB is a lightweight key-value database designed for Minecraft Bedrock Script API. +It supports two storage backends: ---- - -## Features - -- **CRUD Operations**: - - `set(key, value)` - Save a value to the database. - - `get(key)` - Retrieve a value by its key. - - `delete(key)` - Remove a key-value pair from the database. - - `has(key)` - Check if a key exists in the database. - -- **Iteration**: - - `keys()` - Get all keys in the database. - - `values()` - Retrieve all values stored in the database. - - `entries()` - Retrieve all key-value pairs as an array of entries. - -- **Database Size**: - - `size` - Get the total byte count used by the dynamic properties. - ---- - -## Installation - -Import `QuickDB` into your ScriptAPI project. Ensure `QuickDB` is included in your `index.js` file for easy integration. - -```javascript -import QuickDB from "./index.js"; -``` - ---- - -## Documentation - -### **Constructor** - -```javascript -const db = new QuickDB("user"); -``` - -- **Parameters**: - - `id` *(string)*: A unique identifier for your database instance. - ---- +- **Dynamic Properties** +- **Scoreboard** -### **Methods** - -#### `set(key, value)` -- **Description**: Stores a value associated with a key. -- **Parameters**: - - `key` *(string)*: The key to store the value. - - `value` *(any)*: The value to be stored. -- **Returns**: `boolean` - `true` if successful. - -```javascript -db.set("FomoKiwor", { money: 20000 }); -``` +Both storages share the same API, so you can switch storage types without changing your code. --- -#### `get(key)` -- **Description**: Retrieves the value associated with a key. -- **Parameters**: - - `key` *(string)*: The key to retrieve the value. -- **Returns**: `any` - The value associated with the key. - -```javascript -const userData = db.get("FomoKiwor"); // { money: 20000 } -``` - ---- +# How Caching Works -#### `has(key)` -- **Description**: Checks if a key exists in the database. -- **Parameters**: - - `key` *(string)*: The key to check. -- **Returns**: `boolean` - `true` if the key exists. +When a database is created, it **loads all existing data into memory** (RAM). -```javascript -const exists = db.has("FomoKiwor"); // true -``` +Example process: ---- +1. The database scans the storage (Scoreboard or Dynamic Property). +2. All matching keys are loaded into an internal cache. +3. Future operations read from this cache instead of scanning storage again. -#### `delete(key)` -- **Description**: Removes a key-value pair from the database. -- **Parameters**: - - `key` *(string)*: The key to remove. -- **Returns**: `boolean` - `true` if successful. +Because of this: -```javascript -db.delete("FomoKiwor"); -``` +- `get()` is **very fast** +- `has()` is **very fast** +- `keys()`, `values()`, `entries()` do not need to scan storage again ---- +When data changes: -#### `keys()` -- **Description**: Retrieves all keys in the database. -- **Returns**: `string[]` - An array of all keys. +- `set()` updates **storage + cache** +- `delete()` removes **storage + cache** -```javascript -const allKeys = db.keys(); // ["key1", "key2"] -``` +This keeps both storage and cache synchronized. --- -#### `values()` -- **Description**: Retrieves all values in the database. -- **Returns**: `any[]` - An array of all values. +# Storage Types -```javascript -const allValues = db.values(); // [{ money: 20000 }, { items: [] }] -``` - ---- +| Storage Type | Backend Used | Description | +| ------------ | ------------ | ------------------------------------ | +| `local` | DynamicDB | Stores data using dynamic properties | +| `dynamic` | DynamicDB | Same as local | +| `global` | ScoreboardDB | Stores data using scoreboard | +| `scoreboard` | ScoreboardDB | Same as global | -#### `entries()` -- **Description**: Retrieves all key-value pairs in the database. -- **Returns**: `Array<[string, any]>` - An array of key-value entries. +Example: -```javascript -const allEntries = db.entries(); // [["key1", { money: 20000 }], ["key2", { items: [] }]] +```ts +const db = new QuickDB("playerData"); // default using local or dynamic +const globalDB = new QuickDB("playerData", "global"); // global database, different cache with local or dynamic database, use this to sync database with other addons you wanna make like plugin structures ``` --- -### **Property** +# Methods -#### `size` -- **Description**: Gets the total byte count used by dynamic properties. -- **Returns**: `number` - The total byte count. - -```javascript -console.log(db.size); // e.g., 512 -``` +| Method | Description | Example | +| ----------------- | ------------------------ | ---------------------- | +| `set(key, value)` | Save or update a value | `db.set("coins", 100)` | +| `get(key)` | Get value from cache | `db.get("coins")` | +| `has(key)` | Check if key exists | `db.has("coins")` | +| `delete(key)` | Remove key from database | `db.delete("coins")` | +| `keys()` | Get all keys | `db.keys()` | +| `values()` | Get all values | `db.values()` | +| `entries()` | Get key-value pairs | `db.entries()` | --- -## Example Usage - -```javascript -import QuickDB from "./index.js"; +# Example Usage -const db = new QuickDB("user"); +```ts +const db = new QuickDB("coins"); -db.set("FomoKiwor", { money: 20000 }); -console.log(db.get("FomoKiwor")); // { money: 20000 } +db.set("player1", 100); -console.log(db.has("FomoKiwor")); // true -db.delete("FomoKiwor"); -console.log(db.has("FomoKiwor")); // false +const coins = db.get("player1"); -db.set("User1", { score: 100 }); -db.set("User2", { score: 150 }); - -console.log(db.keys()); // ["User1", "User2"] -console.log(db.values()); // [{ score: 100 }, { score: 150 }] -console.log(db.entries()); // [["User1", { score: 100 }], ["User2", { score: 150 }]] +console.log(coins); ``` --- -## License +# Summary -MIT License +QuickDB works by: ---- +1. Loading data from storage +2. Storing it in an internal cache +3. Using the cache for fast access +4. Keeping storage and cache synchronized -Developed by [Nperma](https://github.com/nperma) +This makes database operations **fast and efficient** for Minecraft Script API projects. From 1af9accfb69f8b97b5f94f52047df8ab532b32c1 Mon Sep 17 00:00:00 2001 From: Nperma <129764133+nperma@users.noreply.github.com> Date: Sun, 15 Mar 2026 07:03:05 +0700 Subject: [PATCH 2/3] Update index.js --- scripts/quick-db/index.js | 331 +++++++++++++++++++++++--------------- 1 file changed, 198 insertions(+), 133 deletions(-) diff --git a/scripts/quick-db/index.js b/scripts/quick-db/index.js index a123782b..055b0e2f 100644 --- a/scripts/quick-db/index.js +++ b/scripts/quick-db/index.js @@ -1,135 +1,200 @@ -// Script example for ScriptAPI -// Author: Nperma -// Project: https://github.com/JaylyDev/ScriptAPI - -import { world,system, World } from '@minecraft/server'; - -const DATABASE_PREFIX = '\u0235\u0235'; - -const { - getDynamicProperty: GET, - setDynamicProperty: SET, - getDynamicPropertyIds: IDS -} = World.prototype; - -class QuickDB { - #identifier; - __cache = {}; - - /** - * @param {string} id - Unique database identifier. - */ - constructor(id) { - if (typeof id !== 'string' || !id.trim()) { - throw new Error('Invalid database ID'); - } - this.#identifier = `${DATABASE_PREFIX}${id}${DATABASE_PREFIX}`; - - for (const keyFull of this.getIds()) { - const key = keyFull.replace(this.#identifier, ''); - let value;system.run(()=>{value=GET.call(world, keyFull);}) - this.__cache[key] = JSON.parse(value); - } - } - - /** @returns {number} */ - get size() { - return this.keys().length; - } - - /** @returns {string[]} */ - keys() { - return Object.keys(this.__cache); - } - - /** @returns {any[]} */ - values() { - return Object.values(this.__cache); - } - - /** @returns {[string, any][]} */ - entries() { - return Object.entries(this.__cache); - } - - /** - * Stores a key-value pair. - * @param {string} key - * @param {any} value - * @returns {void} - */ - set(key, value) { - if (typeof key !== 'string' || !key.trim()) throw new Error('Key must be a non-empty string'); - system.run(()=>SET.call(world, this.#identifier + key, JSON.stringify(value))); - this.__cache[key] = value; - } - - /** - * Deletes a key. - * @param {string} key - * @returns {boolean} - */ - delete(key) { - if (!this.has(key)) return false; - system.run(()=>SET.call(world, this.#identifier + key)); - delete this.__cache[key]; - return true; - } - - /** - * Retrieves a value. - * @param {string} key - * @returns {any} - */ - get(key) { - if (typeof key !== 'string' || !key.trim()) throw new Error('Key must be a non-empty string'); - return this.__cache[key]; - } - - /** - * Checks if a key exists. - * @param {string} key - * @returns {boolean} - */ - has(key) { - return key in this.__cache; - } - - /** @returns {string[]} */ - static get ids() { - let keys; - system.run(() =>{ - keys=IDS.call(world) - .filter((id) => id.startsWith(DATABASE_PREFIX)) - .map((k) => k.slice(DATABASE_PREFIX.length).split(DATABASE_PREFIX)[0]); - }); - return [...new Set( - keys - )]; - } - - /** @returns {string[]} */ - getIds() { - let result;system.run(()=>{result=IDS.call(world).filter((id) => id.startsWith(this.#identifier));}); - return result; - } - - /** Clears the database. */ - clear() { - for (const key of this.keys()) { - this.delete(key); - } - this.__cache = {}; - } - - /** Clears all databases globally. */ - static clearAll() { - let keys;system.run(()=>{keys=IDS.call(world).filter((id) => id.startsWith(DATABASE_PREFIX))}); - for (const real_id of keys) { - system.run(()=>SET.call(world, real_id)); - } - } +import { world, Scoreboard, ScoreboardObjective, ScoreboardIdentity } from "@minecraft/server"; + +const KEY_4NO = "⧉⧉"; + + + +interface DBAdapter { + + set(key: string, value: T): void + + get(key: string): T | undefined + + has(key: string): boolean + + delete(key: string): boolean + + keys(): string[] + + values(): T[] + + entries(): [string, T][] +} + +export class ScoreboardDB implements DBAdapter { + #identifier: string; + readonly #keyacc: string = '⟡⟡'; + readonly #obj; + #participantNames: Record; + #participants: ScoreboardIdentity[]; + #cache: Record = {} + + constructor(name: string) { + this.#identifier = `${KEY_4NO + }${name}${KEY_4NO}` + this.#obj = world.scoreboard.getObjective(this.#identifier) ?? + world.scoreboard.addObjective(this.#identifier) + this.#participantNames = {} + this.#participants = (this.#obj as ScoreboardObjective).getParticipants() + let iteration = this.#participants.length; + while (iteration--) { + const participantName = this.#participants[iteration]?.displayName + const [key, rawValue] = participantName.split(this.#keyacc) + const value = rawValue.startsWith('num:') ? Number(rawValue.slice('num:'.length)) : rawValue.startsWith('bool:') ? Boolean(rawValue.slice('bool:'.length)) : JSON.parse(rawValue) + this.#participantNames[key] = participantName + this.#cache[key] = value + } + } + + set(key: string, value?: T) { + if (key in this.#cache && value == null) this.delete(key) + const valueFormatting = typeof value == 'number' ? `num:${value}` : typeof value == 'boolean' ? `bool:${value}` : value; + const participantName = `${key}${this.#keyacc}${JSON.stringify(valueFormatting)}`; + (this.#obj as ScoreboardObjective).setScore(participantName, 1) + this.#participantNames[key] = participantName; + this.#cache[key] = value + } + + get(key: string) { + return this.#cache[key] + } + + has(key: string) { + return !!this.#cache + } + + delete(key: string) { + if (!(key in this.#cache)) return false; + (this.#obj as ScoreboardObjective).removeParticipant(this.#participantNames[key]) + delete this.#participantNames[key] + delete this.#cache[key] + return true + } + + keys() { + return Object.keys(this.#cache) + } + + values() { + return Object.values(this.#cache) + } + + entries() { + return Object.entries(this.#cache) + } + +} + +export class DynamicDB implements DBAdapter { + #identifier: string; + #cache: Record = {}; + static size: number = world.getDynamicPropertyTotalByteCount() + + + constructor(name: string) { + this.#identifier = `${KEY_4NO + }${name}${KEY_4NO}` + + const ids = world.getDynamicPropertyIds() + let i = ids.length + + while (i--) { + const id = ids[i] + if (!id.startsWith(this.#identifier)) continue + + const raw = world.getDynamicProperty(id) + const key = id.slice(this.#identifier.length) + + const value = + typeof raw === "string" + ? JSON.parse(raw) + : (raw as T) + + this.#cache[key] = value + } + } + + set(key: string, value?: T): void { + const id = this.#identifier + key + if (key in this.#cache && value == null) { this.delete(key); return; } + + const data = + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ? value + : JSON.stringify(value) + + world.setDynamicProperty(id, data) + this.#cache[key] = value + } + + get(key: string): T | undefined { + return this.#cache[key] + } + + has(key: string): boolean { + return key in this.#cache + } + + delete(key: string): boolean { + if (!(key in this.#cache)) return false; + + world.setDynamicProperty(this.#identifier + key, undefined) + delete this.#cache[key] + return true + } + + keys(): string[] { + return Object.keys(this.#cache) + } + + values(): T[] { + return Object.values(this.#cache) + } + + entries(): [string, T][] { + return Object.entries(this.#cache) as [string, T][] + } } -export default QuickDB; -export { QuickDB }; +export default class QuickDB implements DBAdapter { + #db; + constructor( + name: string, + storage: "local" | "dynamic" | "global" | "scoreboard" = "local" + ) { + this.#db = + storage === "scoreboard" || storage === "global" + ? new ScoreboardDB(name) + : new DynamicDB(name) + } + + set(key: string, value?: T): void { + this.#db.set(key, value) + } + + get(key: string): T | undefined { + return this.#db.get(key) + } + + has(key: string): boolean { + return this.#db.has(key) + } + + delete(key: string): boolean { + return this.#db.delete(key) + } + + keys(): string[] { + return this.#db.keys() + } + + values(): T[] { + return this.#db.values() + } + + entries(): [string, T][] { + return this.#db.entries() + } +} From f4aae6fd4dbe11653e0e3bbf4881679b2e57b1c4 Mon Sep 17 00:00:00 2001 From: Nperma <129764133+nperma@users.noreply.github.com> Date: Sun, 15 Mar 2026 07:10:35 +0700 Subject: [PATCH 3/3] Update index.js --- scripts/quick-db/index.js | 330 ++++++++++++++++---------------------- 1 file changed, 134 insertions(+), 196 deletions(-) diff --git a/scripts/quick-db/index.js b/scripts/quick-db/index.js index 055b0e2f..35652f02 100644 --- a/scripts/quick-db/index.js +++ b/scripts/quick-db/index.js @@ -1,200 +1,138 @@ -import { world, Scoreboard, ScoreboardObjective, ScoreboardIdentity } from "@minecraft/server"; - +import { world } from "@minecraft/server"; const KEY_4NO = "⧉⧉"; - - - -interface DBAdapter { - - set(key: string, value: T): void - - get(key: string): T | undefined - - has(key: string): boolean - - delete(key: string): boolean - - keys(): string[] - - values(): T[] - - entries(): [string, T][] -} - -export class ScoreboardDB implements DBAdapter { - #identifier: string; - readonly #keyacc: string = '⟡⟡'; - readonly #obj; - #participantNames: Record; - #participants: ScoreboardIdentity[]; - #cache: Record = {} - - constructor(name: string) { - this.#identifier = `${KEY_4NO - }${name}${KEY_4NO}` - this.#obj = world.scoreboard.getObjective(this.#identifier) ?? - world.scoreboard.addObjective(this.#identifier) - this.#participantNames = {} - this.#participants = (this.#obj as ScoreboardObjective).getParticipants() - let iteration = this.#participants.length; - while (iteration--) { - const participantName = this.#participants[iteration]?.displayName - const [key, rawValue] = participantName.split(this.#keyacc) - const value = rawValue.startsWith('num:') ? Number(rawValue.slice('num:'.length)) : rawValue.startsWith('bool:') ? Boolean(rawValue.slice('bool:'.length)) : JSON.parse(rawValue) - this.#participantNames[key] = participantName - this.#cache[key] = value - } - } - - set(key: string, value?: T) { - if (key in this.#cache && value == null) this.delete(key) - const valueFormatting = typeof value == 'number' ? `num:${value}` : typeof value == 'boolean' ? `bool:${value}` : value; - const participantName = `${key}${this.#keyacc}${JSON.stringify(valueFormatting)}`; - (this.#obj as ScoreboardObjective).setScore(participantName, 1) - this.#participantNames[key] = participantName; - this.#cache[key] = value - } - - get(key: string) { - return this.#cache[key] - } - - has(key: string) { - return !!this.#cache - } - - delete(key: string) { - if (!(key in this.#cache)) return false; - (this.#obj as ScoreboardObjective).removeParticipant(this.#participantNames[key]) - delete this.#participantNames[key] - delete this.#cache[key] - return true - } - - keys() { - return Object.keys(this.#cache) - } - - values() { - return Object.values(this.#cache) - } - - entries() { - return Object.entries(this.#cache) - } - +export class ScoreboardDB { + constructor(name) { + this.keyacc = '⟡⟡'; + this.cache = {}; + this.identifier = `${KEY_4NO}${name}${KEY_4NO}`; + this.obj = world.scoreboard.getObjective(this.identifier) ?? + world.scoreboard.addObjective(this.identifier); + this.participantNames = {}; + this.participants = this.obj.getParticipants(); + let iteration = this.participants.length; + while (iteration--) { + const participantName = this.participants[iteration]?.displayName; + const [key, rawValue] = participantName.split(this.keyacc); + const value = rawValue.startsWith('num:') ? Number(rawValue.slice('num:'.length)) : rawValue.startsWith('bool:') ? Boolean(rawValue.slice('bool:'.length)) : JSON.parse(rawValue); + this.participantNames[key] = participantName; + this.cache[key] = value; + } + } + set(key, value) { + if (key in this.cache && value == null) + this.delete(key); + const valueFormatting = typeof value == 'number' ? `num:${value}` : typeof value == 'boolean' ? `bool:${value}` : value; + const participantName = `${key}${this.keyacc}${JSON.stringify(valueFormatting)}`; + this.obj.setScore(participantName, 1); + this.participantNames[key] = participantName; + this.cache[key] = value; + } + get(key) { + return this.cache[key]; + } + has(key) { + return !!this.cache; + } + delete(key) { + if (!(key in this.cache)) + return false; + this.obj.removeParticipant(this.participantNames[key]); + delete this.participantNames[key]; + delete this.cache[key]; + return true; + } + keys() { + return Object.keys(this.cache); + } + values() { + return Object.values(this.cache); + } + entries() { + return Object.entries(this.cache); + } } - -export class DynamicDB implements DBAdapter { - #identifier: string; - #cache: Record = {}; - static size: number = world.getDynamicPropertyTotalByteCount() - - - constructor(name: string) { - this.#identifier = `${KEY_4NO - }${name}${KEY_4NO}` - - const ids = world.getDynamicPropertyIds() - let i = ids.length - - while (i--) { - const id = ids[i] - if (!id.startsWith(this.#identifier)) continue - - const raw = world.getDynamicProperty(id) - const key = id.slice(this.#identifier.length) - - const value = - typeof raw === "string" - ? JSON.parse(raw) - : (raw as T) - - this.#cache[key] = value - } - } - - set(key: string, value?: T): void { - const id = this.#identifier + key - if (key in this.#cache && value == null) { this.delete(key); return; } - - const data = - typeof value === "string" || - typeof value === "number" || - typeof value === "boolean" - ? value - : JSON.stringify(value) - - world.setDynamicProperty(id, data) - this.#cache[key] = value - } - - get(key: string): T | undefined { - return this.#cache[key] - } - - has(key: string): boolean { - return key in this.#cache - } - - delete(key: string): boolean { - if (!(key in this.#cache)) return false; - - world.setDynamicProperty(this.#identifier + key, undefined) - delete this.#cache[key] - return true - } - - keys(): string[] { - return Object.keys(this.#cache) - } - - values(): T[] { - return Object.values(this.#cache) - } - - entries(): [string, T][] { - return Object.entries(this.#cache) as [string, T][] - } +export class DynamicDB { + constructor(name) { + this.cache = {}; + this.identifier = `${KEY_4NO}${name}${KEY_4NO}`; + const ids = world.getDynamicPropertyIds(); + let i = ids.length; + while (i--) { + const id = ids[i]; + if (!id.startsWith(this.identifier)) + continue; + const raw = world.getDynamicProperty(id); + const key = id.slice(this.identifier.length); + const value = typeof raw === "string" + ? JSON.parse(raw) + : raw; + this.cache[key] = value; + } + } + set(key, value) { + const id = this.identifier + key; + if (key in this.cache && value == null) { + this.delete(key); + return; + } + const data = typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ? value + : JSON.stringify(value); + world.setDynamicProperty(id, data); + this.cache[key] = value; + } + get(key) { + return this.cache[key]; + } + has(key) { + return key in this.cache; + } + delete(key) { + if (!(key in this.cache)) + return false; + world.setDynamicProperty(this.identifier + key, undefined); + delete this.cache[key]; + return true; + } + keys() { + return Object.keys(this.cache); + } + values() { + return Object.values(this.cache); + } + entries() { + return Object.entries(this.cache); + } } - -export default class QuickDB implements DBAdapter { - #db; - constructor( - name: string, - storage: "local" | "dynamic" | "global" | "scoreboard" = "local" - ) { - this.#db = - storage === "scoreboard" || storage === "global" - ? new ScoreboardDB(name) - : new DynamicDB(name) - } - - set(key: string, value?: T): void { - this.#db.set(key, value) - } - - get(key: string): T | undefined { - return this.#db.get(key) - } - - has(key: string): boolean { - return this.#db.has(key) - } - - delete(key: string): boolean { - return this.#db.delete(key) - } - - keys(): string[] { - return this.#db.keys() - } - - values(): T[] { - return this.#db.values() - } - - entries(): [string, T][] { - return this.#db.entries() - } +DynamicDB.size = world.getDynamicPropertyTotalByteCount(); +export default class QuickDB { + constructor(name, storage = "local") { + this.db = + storage === "scoreboard" || storage === "global" + ? new ScoreboardDB(name) + : new DynamicDB(name); + } + set(key, value) { + this.db.set(key, value); + } + get(key) { + return this.db.get(key); + } + has(key) { + return this.db.has(key); + } + delete(key) { + return this.db.delete(key); + } + keys() { + return this.db.keys(); + } + values() { + return this.db.values(); + } + entries() { + return this.db.entries(); + } }