UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

191 lines (167 loc) 4.73 kB
import { randomUUID } from "node:crypto"; import { $inject, Alepha, createPrimitive, type Service } from "alepha"; import { DateTimeProvider, type DurationLike } from "alepha/datetime"; import { AlephaLock, LockPrimitive, LockProvider, LockTopicProvider, lockOptions, MemoryLockProvider, } from "alepha/lock"; import { MemoryTopicProvider, type SubscribeCallback, type TopicProvider, } from "alepha/topic"; import { expect } from "vitest"; export const store: Record<string, string> = {}; export class SharedLockProvider extends MemoryLockProvider { store: Record<string, string> = store; } export const subscriptions: Record<string, SubscribeCallback[]> = {}; export class SharedTopicProvider extends MemoryTopicProvider { subscriptions: Record<string, SubscribeCallback[]> = subscriptions; } export const testLockBasic = async ( provider: Service<LockProvider>, topicProvider: Service<TopicProvider>, ): Promise<void> => { const state: Record<string, string> = {}; class TestLock { lock = $inject(LockProvider); dt = $inject(DateTimeProvider); stack = () => state.stack; migrate = createPrimitive(LockPrimitive, { handler: async () => { await this.dt.wait([50, "milliseconds"]); state.stack = state.stack ? `${state.stack}A` : "A"; }, }); } const prefix = randomUUID(); const createApp = async () => { const app = Alepha.create(); app.store.mut(lockOptions, () => ({ prefixKey: prefix })); return app .with({ provide: LockTopicProvider, use: topicProvider, }) .with({ provide: LockProvider, use: provider, }) .with(AlephaLock) .with(TestLock) .start() .then((a) => a.inject(TestLock)); }; const app1 = await createApp(); const app2 = await createApp(); const app3 = await createApp(); await app1.lock.set("stack", ""); await Promise.all([ app1.migrate.run(), app2.migrate.run(), app3.migrate.run(), ]); expect(app1.stack()).toBe("A"); expect(app2.stack()).toBe("A"); expect(app3.stack()).toBe("A"); }; export const testLockWait = async ( provider: Service<LockProvider>, topicProvider: Service<TopicProvider>, ): Promise<void> => { let count = 0; class TestLockWait { dt = $inject(DateTimeProvider); migrateLock = createPrimitive(LockPrimitive, { wait: true, handler: async () => { count++; await this.dt.wait([500, "milliseconds"]); }, }); async migrate() { const now = Date.now(); await this.migrateLock.run(); return Date.now() - now; } } const createApp = async () => { const app = Alepha.create() .with({ provide: LockTopicProvider, use: topicProvider, }) .with({ provide: LockProvider, use: provider, }); app.with(TestLockWait); return app.start().then(() => app.inject(TestLockWait)); }; const app1 = await createApp(); const app2 = await createApp(); const app3 = await createApp(); const result = await Promise.all([ app1.migrate(), app2.migrate(), app3.migrate(), ]); expect(result[0]).toBeGreaterThanOrEqual(500); expect(result[1]).toBeGreaterThanOrEqual(500); expect(result[2]).toBeGreaterThanOrEqual(500); // All 3 apps should eventually execute (wait: true means queue, not skip) expect(count).toBe(3); }; export const testLockGracePeriod = async ( provider: Service<LockProvider>, topicProvider: Service<TopicProvider>, ): Promise<void> => { const config: { gracePeriod?: DurationLike; } = {}; let count = 0; const createApp = () => { const alepha = Alepha.create() .with({ provide: LockTopicProvider, use: topicProvider, }) .with({ provide: LockProvider, use: provider, }); class TestApp { fn = createPrimitive(LockPrimitive, { gracePeriod: config.gracePeriod, handler: async () => { count += 1; }, }); } const fn = alepha.inject(TestApp).fn; alepha.events.on("ready", () => fn.run()); return alepha; }; // no grace period // as you run apps one by one, lock is released after each start! count = 0; expect(count).toBe(0); for (const app of [createApp(), createApp(), createApp()]) { await app.start(); } expect(count).toBe(3); // but with grace period // as we wait 1 second before releasing lock, all started apps will see the lock! count = 0; expect(count).toBe(0); config.gracePeriod = [1, "seconds"]; for (const app of [createApp(), createApp(), createApp()]) { await app.start(); } expect(count).toBe(1); };