Skip to content

Commit

Permalink
feat(utils): add queue
Browse files Browse the repository at this point in the history
  • Loading branch information
ynwd committed Oct 5, 2024
1 parent 497f943 commit 6e6a72f
Show file tree
Hide file tree
Showing 13 changed files with 641 additions and 431 deletions.
5 changes: 0 additions & 5 deletions core/build/esbuildMod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { FunctionComponent, Page } from "../server/types.ts";
import { build } from "./build.ts";
import { esbuild } from "./deps.ts";

export class EsbuildMod {
#elementName: string;
Expand All @@ -13,8 +12,4 @@ export class EsbuildMod {
build = async () => {
return await build(this.#elementName);
};

stop = () => {
esbuild.stop();
};
}
46 changes: 28 additions & 18 deletions core/map/mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ Deno.test("Store: keys", () => {
assertEquals(entries, ["key6"]);
});

export function processMap(map: Store<string, number>): Store<string, number> {
const result = new Store<string, number>();
function processMap(map: Store<string, number>): Map<string, number> {
const result = new Map<string, number>();
map.forEach((value, key) => {
result.set(key, value * 2);
});
Expand All @@ -88,7 +88,7 @@ Deno.test("Store: forEach", () => {
inputMap.set("two", 2);
inputMap.set("three", 3);

const expectedOutput = new Store<string, number>();
const expectedOutput = new Map<string, number>();
expectedOutput.set("one", 2);
expectedOutput.set("two", 4);
expectedOutput.set("three", 6);
Expand Down Expand Up @@ -134,14 +134,10 @@ const store = new Store<string, number>({
branch: "store",
token,
});
const i = store.sync(5000);
Deno.test("Store: save it to github", async () => {
store.set("key1", time);
const r = await store.commit();
assertEquals(r?.data.content?.name, "records.json");
});

Deno.test("Store: get value from github", async () => {
Deno.test("Store: set and get value from github", async () => {
store.set("key1", time);
await store.commit();
const g = await store.get("key1");
assertEquals(g, time);
});
Expand All @@ -157,12 +153,6 @@ Deno.test("Store: sync with github periodically", async () => {
assertEquals(g, 2);
});

Deno.test("Store: destroy map", async () => {
await store.destroy();
const g = await store.get("key1");
assertEquals(g, undefined);
});

Deno.test("Store: destroy map without options", async () => {
try {
const s = new Store();
Expand All @@ -172,6 +162,12 @@ Deno.test("Store: destroy map without options", async () => {
}
});

Deno.test("Store: destroy map", async () => {
await store.destroy();
const g = await store.get("key1");
assertEquals(g, undefined);
});

const s = new Store({
owner: "fastrodev",
repo: "fastro",
Expand All @@ -190,10 +186,24 @@ Deno.test("Store: sync exist file", async () => {
});
await newStore.get("exist");
const intervalId = newStore.sync();
await new Promise((resolve) => setTimeout(resolve, 15000));
await new Promise((resolve) => setTimeout(resolve, 10000));
const r = await newStore.get("exist");
assertEquals(r, true);
clearInterval(intervalId);
});

if (i) clearInterval(i);
Deno.test("Store: sync, same size after multiple commit", async () => {
const newStore = new Store({
owner: "fastrodev",
repo: "fastro",
path: "modules/store/map.json",
branch: "store",
token,
});
await Promise.all([
newStore.set("user", "zaid").commit(),
newStore.set("city", "pare").commit(),
newStore.set("country", "indonesia").commit(),
]);
assertEquals(newStore.size(), 4);
});
103 changes: 56 additions & 47 deletions core/map/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import {
type StoreOptions,
uploadFileToGitHub,
} from "../../utils/octokit.ts";
import { createTaskQueue } from "../../utils/queue.ts";

export class Store<K extends string | number | symbol, V> {
private map: Map<K, { value: V; expiry?: number }>;
private options: StoreOptions;
private intervalId: number | null = null;
private isCommitting: boolean = false;
private taskQueue = createTaskQueue();

constructor(options: StoreOptions = null) {
this.map = new Map<K, { value: V; expiry?: number }>();
Expand Down Expand Up @@ -130,73 +131,81 @@ export class Store<K extends string | number | symbol, V> {
});
}

/**
* Save to github
*/
async commit() {
if (this.isCommitting) {
throw new Error("Commit in progress, please wait.");
}
if (!this.options) throw new Error("Options are needed to commit");
this.isCommitting = true;
this.cleanUpExpiredEntries();
try {
return await this.saveToGitHub(
{
token: this.options.token,
owner: this.options.owner,
repo: this.options.repo,
path: this.options.path,
branch: this.options.branch,
},
);
} finally {
this.isCommitting = false;
}
}

/**
* Delete file from repository
*/
async destroy() {
if (!this.options) throw new Error("Options are needed to destroy.");
if (this.intervalId) clearInterval(this.intervalId);
this.map.clear();
return await deleteGithubFile({
private async joinMaps<K extends string | number | symbol, V>() {
if (!this.options) return this.map;
const remoteMap = await getMap<K, V>({
token: this.options.token,
owner: this.options.owner,
repo: this.options.repo,
path: this.options.path,
branch: this.options.branch,
});
if (!remoteMap) return this.map;

// deno-lint-ignore no-explicit-any
for (const [key, entry] of remoteMap.entries() as any) {
if (!this.map.has(key)) this.map.set(key, entry);
}

this.cleanUpExpiredEntries();
return this.map;
}

/**
* Save to github with queue
*/
commit = async () => {
return await this.taskQueue.process(this.commiting, this.options);
};

private commiting = async (options?: StoreOptions) => {
if (!options) return;
await this.joinMaps<K, V>();
return await this.saveToGitHub(
{
token: options?.token,
owner: options.owner,
repo: options.repo,
path: options.path,
branch: options?.branch,
},
);
};

/**
* Save the map to the repository periodically at intervals
* @param interval
* @returns intervalId
*/
sync(interval: number = 3000) {
sync(interval: number = 5000) {
if (this.intervalId) clearInterval(this.intervalId);
this.intervalId = setInterval(async () => {
if (!this.options || (this.map.size === 0)) {
return;
this.intervalId = setInterval(() => {
if (this.map.size === 0) return;
this.taskQueue.process(this.commiting, this.options);
}, interval);
return this.intervalId;
}

/**
* Delete file from repository
*/
async destroy() {
try {
if (!this.options) {
throw new Error("Options are needed to destroy.");
}
this.isCommitting = true;
const r = await this.saveToGitHub({
if (this.intervalId) clearInterval(this.intervalId);
this.map.clear();
return await deleteGithubFile({
token: this.options.token,
owner: this.options.owner,
repo: this.options.repo,
path: this.options.path,
branch: this.options.branch,
});
console.log(JSON.stringify({
sha: r.data.content?.sha,
path: r.data.content?.path,
}));
this.isCommitting = false;
}, interval);
return this.intervalId;
} catch (error) {
throw error;
}
}

private async syncMap() {
Expand Down
6 changes: 3 additions & 3 deletions core/server/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ export * from "jsr:@std/path@^1.0.1";
export { encodeHex } from "jsr:@std/encoding@^1.0.5/hex";
export { assert, assertEquals, assertExists } from "jsr:@std/assert@^1.0.6";

export { h } from "https://esm.sh/[email protected].1";
export { h } from "https://esm.sh/[email protected].2";
export type {
ComponentChild,
ComponentChildren,
JSX,
VNode,
} from "https://esm.sh/[email protected].1";
} from "https://esm.sh/[email protected].2";
export {
renderToString,
renderToStringAsync,
} from "https://esm.sh/[email protected][email protected].1";
} from "https://esm.sh/[email protected][email protected].2";
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"imports": {
"@app/": "./",
"preact": "npm:preact@^10.24.1"
"preact": "npm:preact@^10.24.2"
},
"compilerOptions": {
"jsx": "react-jsx",
Expand Down
5 changes: 5 additions & 0 deletions modules/blog/blog.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
[
{
"title": "Using Queues to Avoid Race Conditions",
"url": "/blog/queue",
"date": "10/05/2024"
},
{
"title": "Store: Key and Value Map with TTL",
"url": "/blog/store",
Expand Down
Loading

0 comments on commit 6e6a72f

Please sign in to comment.