Compare commits
No commits in common. "521802a0c17f98f63b661e348bf665c5f35229ed" and "2c697b0de95091906943a4c688dea9d797801943" have entirely different histories.
521802a0c1
...
2c697b0de9
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"deno.enable": true
|
|
||||||
}
|
|
47
README.md
47
README.md
@ -1,47 +1,2 @@
|
|||||||
# BearMetalStore
|
# Store
|
||||||
|
|
||||||
A no-dep, lightweight, synchronous store for storing data in a JSON file.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { BearMetalStore } from "@bearmetal/store";
|
|
||||||
|
|
||||||
const store = new BearMetalStore();
|
|
||||||
|
|
||||||
store.set("key", "value");
|
|
||||||
|
|
||||||
console.log(store.get("key"));
|
|
||||||
|
|
||||||
store.close();
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
### `new BearMetalStore(storePath?: string)`
|
|
||||||
|
|
||||||
Creates a new store.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
- `storePath?: string`
|
|
||||||
|
|
||||||
The path to the store file.
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
- `BEAR_METAL_STORE_PATH`
|
|
||||||
|
|
||||||
The path to the store file. Can be used instead of the `storePath` parameter.
|
|
||||||
If neither is provided, the store will be stored in `./BearMetal/store.json`.
|
|
||||||
|
|
||||||
### Events
|
|
||||||
|
|
||||||
The store will dispatch a custom event with the name
|
|
||||||
`"bm:refresh-store" + storePath` when the store is updated. This can be used to
|
|
||||||
trigger a refresh of the store in other parts of the application.
|
|
||||||
|
|
||||||
## Permisions
|
|
||||||
|
|
||||||
Read and write access to the store file is required. Environment access to the
|
|
||||||
"BEAR_METAL_STORE_PATH" variable is required.
|
|
||||||
|
15
deno.json
15
deno.json
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@bearmetal/store",
|
|
||||||
"version": "0.0.5",
|
|
||||||
"description": "A simple store for storing data in a JSON file.",
|
|
||||||
"files": [
|
|
||||||
"mod.ts",
|
|
||||||
"store.ts"
|
|
||||||
],
|
|
||||||
"exports": "./mod.ts",
|
|
||||||
"imports": {
|
|
||||||
"@std/assert": "jsr:@std/assert@^1.0.6",
|
|
||||||
"@std/fs": "jsr:@std/fs@^1.0.4",
|
|
||||||
"@std/testing": "jsr:@std/testing@^1.0.3"
|
|
||||||
}
|
|
||||||
}
|
|
51
deno.lock
generated
51
deno.lock
generated
@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"specifiers": {
|
|
||||||
"jsr:@std/assert@^1.0.6": "1.0.6",
|
|
||||||
"jsr:@std/data-structures@^1.0.4": "1.0.4",
|
|
||||||
"jsr:@std/fs@^1.0.4": "1.0.4",
|
|
||||||
"jsr:@std/internal@^1.0.4": "1.0.4",
|
|
||||||
"jsr:@std/path@^1.0.6": "1.0.6",
|
|
||||||
"jsr:@std/testing@^1.0.3": "1.0.3"
|
|
||||||
},
|
|
||||||
"jsr": {
|
|
||||||
"@std/assert@1.0.6": {
|
|
||||||
"integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/internal"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/data-structures@1.0.4": {
|
|
||||||
"integrity": "fa0e20c11eb9ba673417450915c750a0001405a784e2a4e0c3725031681684a0"
|
|
||||||
},
|
|
||||||
"@std/fs@1.0.4": {
|
|
||||||
"integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/path"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/internal@1.0.4": {
|
|
||||||
"integrity": "62e8e4911527e5e4f307741a795c0b0a9e6958d0b3790716ae71ce085f755422"
|
|
||||||
},
|
|
||||||
"@std/path@1.0.6": {
|
|
||||||
"integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
|
|
||||||
},
|
|
||||||
"@std/testing@1.0.3": {
|
|
||||||
"integrity": "f98c2bee53860a5916727d7e7d3abe920dd6f9edace022e2d059f00d05c2cf42",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert",
|
|
||||||
"jsr:@std/data-structures",
|
|
||||||
"jsr:@std/fs",
|
|
||||||
"jsr:@std/internal",
|
|
||||||
"jsr:@std/path"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"workspace": {
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@^1.0.6",
|
|
||||||
"jsr:@std/fs@^1.0.4",
|
|
||||||
"jsr:@std/testing@^1.0.3"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
// deno-lint-ignore-file no-explicit-any
|
|
||||||
import { BearMetalStore } from "./store.ts";
|
|
||||||
import { assertEquals } from "@std/assert";
|
|
||||||
import {
|
|
||||||
assertSpyCall,
|
|
||||||
assertSpyCallArgs,
|
|
||||||
spy,
|
|
||||||
type SpyLike,
|
|
||||||
} from "@std/testing/mock";
|
|
||||||
import { beforeAll } from "@std/testing/bdd";
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
let store = "{}";
|
|
||||||
Deno.readTextFileSync = spy(() => store);
|
|
||||||
Deno.writeTextFileSync = spy((_, string) => store = string);
|
|
||||||
|
|
||||||
globalThis.addEventListener = spy(globalThis.addEventListener);
|
|
||||||
globalThis.removeEventListener = spy(globalThis.removeEventListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test("BearMetalStore Traditional", async (t) => {
|
|
||||||
const store = new BearMetalStore();
|
|
||||||
|
|
||||||
const listener = spy();
|
|
||||||
globalThis.addEventListener(store.EVENT_NAME, listener);
|
|
||||||
|
|
||||||
await t.step("set and get", () => {
|
|
||||||
store.set("key", "value");
|
|
||||||
assertEquals(store.get("key"), "value");
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step("write file called", () => {
|
|
||||||
assertSpyCall(Deno.writeTextFileSync as SpyLike, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step("event listener called", () => {
|
|
||||||
assertSpyCall(listener, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step("delete and get", () => {
|
|
||||||
store.delete("key");
|
|
||||||
assertEquals(store.get("key"), undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step("event listeners removed", () => {
|
|
||||||
store[Symbol.dispose]();
|
|
||||||
assertSpyCallArgs(globalThis.removeEventListener as SpyLike, 0, [
|
|
||||||
store.EVENT_NAME,
|
|
||||||
(store as any).readIn,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Deno.test("BearMetalStore `using` syntax", async (t) => {
|
|
||||||
let eventName = "";
|
|
||||||
let readIn = () => {};
|
|
||||||
{
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
eventName = store.EVENT_NAME;
|
|
||||||
readIn = (store as any).readIn;
|
|
||||||
|
|
||||||
const listener = spy();
|
|
||||||
globalThis.addEventListener(store.EVENT_NAME, listener);
|
|
||||||
|
|
||||||
await t.step("set and get", () => {
|
|
||||||
store.set("key", "value");
|
|
||||||
assertEquals(store.get("key"), "value");
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step("write file called", () => {
|
|
||||||
assertSpyCall(Deno.writeTextFileSync as SpyLike, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step("event listener called", () => {
|
|
||||||
assertSpyCall(listener, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
await t.step("delete and get", () => {
|
|
||||||
store.delete("key");
|
|
||||||
assertEquals(store.get("key"), undefined);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await t.step("event listeners removed", () => {
|
|
||||||
assertSpyCallArgs(globalThis.removeEventListener as SpyLike, 0, [
|
|
||||||
eventName,
|
|
||||||
readIn,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
99
store.ts
99
store.ts
@ -1,99 +0,0 @@
|
|||||||
import { ensureFileSync } from "@std/fs";
|
|
||||||
|
|
||||||
const REFRESH_EVENT = "bm:refresh-store";
|
|
||||||
|
|
||||||
type StoreValue = string | number | boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A no-dep, lightweight, simple store for storing data in a JSON file.
|
|
||||||
* usage:
|
|
||||||
* ```ts
|
|
||||||
* import { BearMetalStore } from "@bearmetal/store";
|
|
||||||
*
|
|
||||||
* const store = new BearMetalStore();
|
|
||||||
*
|
|
||||||
* store.set("key", "value");
|
|
||||||
*
|
|
||||||
* console.log(store.get("key"));
|
|
||||||
*
|
|
||||||
* store.close();
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* It's recommended to use the `using` syntax to ensure the store is disposed
|
|
||||||
* of when it's no longer needed.
|
|
||||||
* ```ts
|
|
||||||
* using store = new BearMetalStore();
|
|
||||||
*
|
|
||||||
* store.set("key", "value");
|
|
||||||
*
|
|
||||||
* console.log(store.get("key"));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export class BearMetalStore {
|
|
||||||
private store: Record<string, string | number | boolean> = {};
|
|
||||||
private storePath: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description The name of the event that is dispatched when the store is updated.
|
|
||||||
*/
|
|
||||||
public readonly EVENT_NAME: string;
|
|
||||||
|
|
||||||
constructor(storePath?: string) {
|
|
||||||
this.storePath = storePath || Deno.env.get("BEAR_METAL_STORE_PATH") ||
|
|
||||||
"./BearMetal/store.json";
|
|
||||||
|
|
||||||
ensureFileSync(this.storePath);
|
|
||||||
this.EVENT_NAME = REFRESH_EVENT + this.storePath;
|
|
||||||
this.readIn();
|
|
||||||
|
|
||||||
globalThis.addEventListener(this.EVENT_NAME, this.readIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param key
|
|
||||||
* @returns The value stored at the key, or undefined if the key doesn't exist.
|
|
||||||
*/
|
|
||||||
public get<T = StoreValue>(key: string): T {
|
|
||||||
return this.store[key] as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Sets the value of the key to the value.
|
|
||||||
* @param key
|
|
||||||
* @param value
|
|
||||||
*/
|
|
||||||
public set(key: string, value: StoreValue): void {
|
|
||||||
this.store[key] = value;
|
|
||||||
this.writeOut();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Deletes the key and its value.
|
|
||||||
* @param key
|
|
||||||
*/
|
|
||||||
public delete(key: string): void {
|
|
||||||
delete this.store[key];
|
|
||||||
this.writeOut();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readIn = () => {
|
|
||||||
this.store = JSON.parse(Deno.readTextFileSync(this.storePath) || "{}");
|
|
||||||
};
|
|
||||||
|
|
||||||
private writeOut() {
|
|
||||||
Deno.writeTextFileSync(this.storePath, JSON.stringify(this.store));
|
|
||||||
globalThis.dispatchEvent(new CustomEvent(this.EVENT_NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.dispose]() {
|
|
||||||
this.writeOut();
|
|
||||||
globalThis.removeEventListener(this.EVENT_NAME, this.readIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Closes the store and disposes of the event listener.
|
|
||||||
*/
|
|
||||||
public close() {
|
|
||||||
this[Symbol.dispose]();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user