alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
191 lines (167 loc) • 4.73 kB
text/typescript
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);
};