PlugOS refactor and other tweaks (#631)
* Prep for in-process plug loading (e.g. for CF workers, Deno Deploy) * Prototype of fixed in-process loading plugs * Fix: buttons not to scroll with content * Better positioning of modal especially on mobile * Move query caching outside query * Fix annoying mouse behavior when filter box appears * Page navigator search tweaks
This commit is contained in:
@@ -37,24 +37,24 @@ export class PromiseQueue {
|
||||
resolve: (value: any) => void;
|
||||
reject: (error: any) => void;
|
||||
}[] = [];
|
||||
private running = false;
|
||||
private processing = false;
|
||||
|
||||
runInQueue(fn: () => Promise<any>): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.queue.push({ fn, resolve, reject });
|
||||
if (!this.running) {
|
||||
this.run();
|
||||
if (!this.processing) {
|
||||
this.process();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async run(): Promise<void> {
|
||||
private async process(): Promise<void> {
|
||||
if (this.queue.length === 0) {
|
||||
this.running = false;
|
||||
this.processing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.running = true;
|
||||
this.processing = true;
|
||||
const { fn, resolve, reject } = this.queue.shift()!;
|
||||
|
||||
try {
|
||||
@@ -64,7 +64,7 @@ export class PromiseQueue {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
this.run(); // Continue processing the next promise in the queue
|
||||
this.process(); // Continue processing the next promise in the queue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
replaceNodesMatchingAsync,
|
||||
traverseTreeAsync,
|
||||
} from "$sb/lib/tree.ts";
|
||||
import { expandPropertyNames } from "$sb/lib/json.ts";
|
||||
|
||||
export type FrontMatter = { tags?: string[] } & Record<string, any>;
|
||||
|
||||
@@ -116,6 +117,8 @@ export async function extractFrontmatter(
|
||||
data.tags = [...new Set([...tags.map((t) => t.replace(/^#/, ""))])];
|
||||
|
||||
// console.log("Extracted tags", data.tags);
|
||||
// Expand property names (e.g. "foo.bar" => { foo: { bar: true } })
|
||||
data = expandPropertyNames(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { sleep } from "$sb/lib/async.ts";
|
||||
import { assertEquals } from "../../test_deps.ts";
|
||||
import { LimitedMap } from "./limited_map.ts";
|
||||
|
||||
Deno.test("limited map", async () => {
|
||||
const mp = new LimitedMap<string>(3);
|
||||
mp.set("a", "a");
|
||||
mp.set("b", "b", 5);
|
||||
mp.set("c", "c");
|
||||
assertEquals(mp.get("a"), "a");
|
||||
assertEquals(mp.get("b"), "b");
|
||||
assertEquals(mp.get("c"), "c");
|
||||
// Drops the first key
|
||||
mp.set("d", "d");
|
||||
// console.log(mp.toJSON());
|
||||
assertEquals(mp.get("a"), undefined);
|
||||
await sleep(10);
|
||||
// "b" should have been dropped
|
||||
assertEquals(mp.get("b"), undefined);
|
||||
assertEquals(mp.get("c"), "c");
|
||||
|
||||
console.log(mp.toJSON());
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
type LimitedMapRecord<V> = {
|
||||
value: V;
|
||||
la: number;
|
||||
expTimer?: number;
|
||||
};
|
||||
|
||||
export class LimitedMap<V> {
|
||||
private map: Map<string, LimitedMapRecord<V>>;
|
||||
|
||||
constructor(
|
||||
private maxSize: number,
|
||||
initialJson: Record<string, LimitedMapRecord<V>> = {},
|
||||
) {
|
||||
this.map = new Map(Object.entries(initialJson));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param value
|
||||
* @param ttl time to live (in ms)
|
||||
*/
|
||||
set(key: string, value: V, ttl?: number) {
|
||||
const entry: LimitedMapRecord<V> = { value, la: Date.now() };
|
||||
if (ttl) {
|
||||
const existingEntry = this.map.get(key);
|
||||
if (existingEntry?.expTimer) {
|
||||
clearTimeout(existingEntry.expTimer);
|
||||
}
|
||||
entry.expTimer = setTimeout(() => {
|
||||
this.map.delete(key);
|
||||
}, ttl);
|
||||
}
|
||||
if (this.map.size >= this.maxSize) {
|
||||
// Remove the oldest key before adding a new one
|
||||
const oldestKey = this.getOldestKey();
|
||||
this.map.delete(oldestKey!);
|
||||
}
|
||||
this.map.set(key, entry);
|
||||
}
|
||||
|
||||
get(key: string): V | undefined {
|
||||
const entry = this.map.get(key);
|
||||
if (entry) {
|
||||
// Update the last accessed timestamp
|
||||
entry.la = Date.now();
|
||||
return entry.value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
remove(key: string) {
|
||||
this.map.delete(key);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Object.fromEntries(this.map.entries());
|
||||
}
|
||||
|
||||
private getOldestKey(): string | undefined {
|
||||
let oldestKey: string | undefined;
|
||||
let oldestTimestamp: number | undefined;
|
||||
|
||||
for (const [key, entry] of this.map.entries()) {
|
||||
if (!oldestTimestamp || entry.la < oldestTimestamp) {
|
||||
oldestKey = key;
|
||||
oldestTimestamp = entry.la;
|
||||
}
|
||||
}
|
||||
|
||||
return oldestKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { sleep } from "$sb/lib/async.ts";
|
||||
import { ttlCache } from "$sb/lib/memory_cache.ts";
|
||||
import { assertEquals } from "../../test_deps.ts";
|
||||
|
||||
Deno.test("Memory cache", async () => {
|
||||
let calls = 0;
|
||||
async function expensiveFunction(key: string) {
|
||||
calls++;
|
||||
await sleep(1);
|
||||
return key;
|
||||
}
|
||||
assertEquals("key", await ttlCache("key", expensiveFunction, 0.01));
|
||||
assertEquals(1, calls);
|
||||
assertEquals("key", await ttlCache("key", expensiveFunction, 0.01));
|
||||
assertEquals(1, calls);
|
||||
await sleep(10);
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { LimitedMap } from "$sb/lib/limited_map.ts";
|
||||
|
||||
const cache = new LimitedMap<any>(50);
|
||||
|
||||
export async function ttlCache<K, V>(
|
||||
key: K,
|
||||
fn: (key: K) => Promise<V>,
|
||||
ttlSecs?: number,
|
||||
): Promise<V> {
|
||||
if (!ttlSecs) {
|
||||
return fn(key);
|
||||
}
|
||||
const serializedKey = JSON.stringify(key);
|
||||
const cached = cache.get(serializedKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const result = await fn(key);
|
||||
cache.set(serializedKey, result, ttlSecs * 1000);
|
||||
return result;
|
||||
}
|
||||
@@ -2,10 +2,11 @@ import { base64DecodeDataUrl } from "../../plugos/asset_bundle/base64.ts";
|
||||
import { syscall } from "./syscall.ts";
|
||||
|
||||
export async function readAsset(
|
||||
plugName: string,
|
||||
name: string,
|
||||
encoding: "utf8" | "dataurl" = "utf8",
|
||||
): Promise<string> {
|
||||
const dataUrl = await syscall("asset.readAsset", name) as string;
|
||||
const dataUrl = await syscall("asset.readAsset", plugName, name) as string;
|
||||
switch (encoding) {
|
||||
case "utf8":
|
||||
return new TextDecoder().decode(base64DecodeDataUrl(dataUrl));
|
||||
|
||||
@@ -73,11 +73,6 @@ export type Query = {
|
||||
render?: string;
|
||||
renderAll?: boolean;
|
||||
distinct?: boolean;
|
||||
|
||||
/**
|
||||
* When set, the DS implementation _may_ cache the result for the given number of seconds.
|
||||
*/
|
||||
cacheSecs?: number;
|
||||
};
|
||||
|
||||
export type KvQuery = Omit<Query, "querySource"> & {
|
||||
|
||||
Reference in New Issue
Block a user