Skip to content

Commit

Permalink
Merge pull request #45 from felanios/beta
Browse files Browse the repository at this point in the history
Beta
  • Loading branch information
felanios authored May 3, 2024
2 parents d361252 + 3300d31 commit 0bb626e
Show file tree
Hide file tree
Showing 14 changed files with 499 additions and 193 deletions.
72 changes: 57 additions & 15 deletions lib/als/als-manager.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,70 @@
import { AsyncLocalStorage } from 'async_hooks';
import { AsyncStorageManagerException } from '../exceptions';
import { Injectable } from '@nestjs/common';

@Injectable()
export class AsyncStorageManager<T> {
private asyncLocalStorage = new AsyncLocalStorage<Map<string, T>>();
export class AsyncStorageManager<T> implements Map<string, T> {
constructor(private readonly asyncLocalStorage: AsyncLocalStorage<Map<string, T>>) { }

runWithNewContext<R>(fn: () => Promise<R>): Promise<R> {
return this.asyncLocalStorage.run(new Map<string, T>(), fn);
}

set(key: string, value: T) {
private getStore(): Map<string, T> {
const store = this.asyncLocalStorage.getStore();

if (!store) {
throw new AsyncStorageManagerException('No active store found');
}
store[key] = value;

return store;
}

register(): void {
this.asyncLocalStorage.enterWith(new Map());
}

runWithNewContext<R, TArgs extends any[]>(fn: (...args: TArgs) => R, ...args: TArgs): R {
return this.asyncLocalStorage.run<R, TArgs>(new Map<string, T>(), fn, ...args);
}

set(key: string, value: T): this {
this.getStore().set(key, value);
return this;
}

get(key: string): T | undefined {
const store = this.asyncLocalStorage.getStore();
if (!store) {
throw new AsyncStorageManagerException('No active store found');
}
return store?.[key];
return this.getStore().get(key);
}

clear(): void {
return this.getStore().clear();
}

delete(key: string): boolean {
return this.getStore().delete(key);
}

forEach(callbackfn: (value: T, key: string, map: Map<string, T>) => void, thisArg?: any): void {
return this.getStore().forEach(callbackfn, thisArg);
}
has(key: string): boolean {
return this.getStore().has(key);
}

get size(): number {
return this.getStore().size;
}

entries(): IterableIterator<[string, T]> {
return this.getStore().entries();
}

keys(): IterableIterator<string> {
return this.getStore().keys();
}

values(): IterableIterator<T> {
return this.getStore().values();
}

[Symbol.iterator](): IterableIterator<[string, T]> {
return this.getStore()[Symbol.iterator]()
}

[Symbol.toStringTag]: string = '[object AsyncContext]';
}
15 changes: 15 additions & 0 deletions lib/als/als.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { DynamicModule, Global, Module } from '@nestjs/common'
import { AsyncStorageManager } from './als-manager';
import { AsyncStorageService } from './als.service';

@Global()
@Module({})
export class AsyncStorageManagerModule {
static forRoot(): DynamicModule {
return {
module: AsyncStorageManagerModule,
providers: [AsyncStorageService, AsyncStorageManager],
exports: [AsyncStorageService]
}
}
}
28 changes: 28 additions & 0 deletions lib/als/als.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Injectable, OnModuleInit } from "@nestjs/common";
import { AsyncStorageManager } from "./als-manager";
import { AsyncLocalStorage } from "async_hooks";

@Injectable()
export class AsyncStorageService implements OnModuleInit {
constructor(private asyncStorageManager: AsyncStorageManager<string>) { }

onModuleInit() {
this.asyncStorageManager = new AsyncStorageManager(new AsyncLocalStorage<any>());
}

async runWithNewContext<R, TArgs extends any[]>(fn: (...args: TArgs) => R, ...args: TArgs): Promise<R> {
return this.asyncStorageManager.runWithNewContext(fn, ...args);
}

registerContext(): void {
this.asyncStorageManager.register();
}

get(key: string): string {
return this.asyncStorageManager.get(key);
}

setClientID(key: string, value: string): void {
this.asyncStorageManager.set(key, value);
}
}
37 changes: 25 additions & 12 deletions lib/decorators/murlock.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Inject } from '@nestjs/common';
import { MurLockException } from '../exceptions';
import { MurLockService } from '../murlock.service';
import { generateUuid } from '../utils';
import { AsyncStorageService } from '../als/als.service';

/**
* Get all parameter names of a function
Expand All @@ -24,13 +26,15 @@ function getParameterNames(func: Function): string[] {
*/
export function MurLock(releaseTime: number, ...keyParams: string[]) {
const injectMurlockService = Inject(MurLockService);
const injectAsyncStorageService = Inject(AsyncStorageService);

return (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) => {
injectMurlockService(target, 'murlockServiceDecorator');
injectAsyncStorageService(target, 'asyncStorageService');

const originalMethod = descriptor.value;
const methodParameterNames = getParameterNames(originalMethod);
Expand Down Expand Up @@ -59,19 +63,28 @@ export function MurLock(releaseTime: number, ...keyParams: string[]) {
}

descriptor.value = async function (...args: any[]) {

const lockKey = constructLockKey(args);

const murLockService: MurLockService = this.murlockServiceDecorator;
const asyncStorageService: AsyncStorageService = this.asyncStorageService;

if (!murLockService) {
throw new MurLockException('MurLockService is not available.');
}
return asyncStorageService.runWithNewContext(async () => {

await acquireLock(lockKey, murLockService, releaseTime);
try {
return await originalMethod.apply(this, args);
} finally {
await releaseLock(lockKey, murLockService);
}
asyncStorageService.registerContext();

asyncStorageService.setClientID('clientId', generateUuid());

await acquireLock(lockKey, murLockService, releaseTime);
try {
return await originalMethod.apply(this, args);
} finally {
await releaseLock(lockKey, murLockService);
}
});
};

return descriptor;
Expand Down Expand Up @@ -102,7 +115,7 @@ async function releaseLock(lockKey: string, murLockService: MurLockService): Pro
function isNumber(value) {
const parsedValue = parseFloat(value);
if (!isNaN(parsedValue)) {
return true;
return true;
}
return false;
}
Expand All @@ -111,12 +124,12 @@ function isObject(value: any): boolean {
return value !== null && value instanceof Object && !Array.isArray(value);
}

function findParameterValue({ args, source, parameterIndex, path }){
if(isNumber(source) && path){
return args[source][path]
function findParameterValue({ args, source, parameterIndex, path }) {
if (isNumber(source) && path) {
return args[source][path]
}
if(args.length == 1 && isObject(args[0]) && !path){
return args[0][source]
if (args.length == 1 && isObject(args[0]) && !path) {
return args[0][source]
}
return args[parameterIndex];
}
23 changes: 0 additions & 23 deletions lib/interceptors/async-local-storage.interceptor.ts

This file was deleted.

1 change: 0 additions & 1 deletion lib/interceptors/index.ts

This file was deleted.

22 changes: 7 additions & 15 deletions lib/murlock.module.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import { Module, DynamicModule, Global, Provider } from '@nestjs/common';
import { Module, DynamicModule, Provider, Global } from '@nestjs/common';
import { MurLockService } from './murlock.service';
import { MurLockModuleAsyncOptions, MurLockModuleOptions } from './interfaces';
import { AsyncStorageManager } from './als/als-manager';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { AsyncStorageInterceptor } from './interceptors';
import { AsyncStorageManagerModule } from './als/als.module';

@Global()
@Module({})
export class MurLockModule {
static forRoot(options: MurLockModuleOptions): DynamicModule {
return {
module: MurLockModule,
imports: [AsyncStorageManagerModule.forRoot()],
providers: [
{
provide: 'MURLOCK_OPTIONS',
useValue: options,
},
AsyncStorageManager,
MurLockService,
{
provide: APP_INTERCEPTOR,
useClass: AsyncStorageInterceptor,
},
],
exports: [MurLockService],
};
Expand All @@ -30,15 +24,13 @@ export class MurLockModule {
static forRootAsync(options: MurLockModuleAsyncOptions): DynamicModule {
return {
module: MurLockModule,
imports: options.imports || [],
imports: [
AsyncStorageManagerModule.forRoot(),
...options.imports
] || [AsyncStorageManagerModule.forRoot()],
providers: [
this.createAsyncOptionsProvider(options),
MurLockService,
AsyncStorageManager,
{
provide: APP_INTERCEPTOR,
useClass: AsyncStorageInterceptor,
},
],
exports: [MurLockService],
};
Expand Down
25 changes: 3 additions & 22 deletions lib/murlock.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { readFileSync } from 'fs';
import { join } from 'path';
import { MurLockModuleOptions } from './interfaces';
import { MurLockRedisException, MurLockException } from './exceptions';
import { AsyncStorageManager } from './als/als-manager';
import { AsyncStorageService } from './als/als.service';

/**
* A service for MurLock to manage locks
Expand All @@ -18,7 +18,7 @@ export class MurLockService implements OnModuleInit, OnApplicationShutdown {

constructor(
@Inject('MURLOCK_OPTIONS') protected readonly options: MurLockModuleOptions,
private readonly asyncStorageManager: AsyncStorageManager<string>,
private readonly asyncStorageService: AsyncStorageService,
) { }

async onModuleInit() {
Expand All @@ -39,27 +39,8 @@ export class MurLockService implements OnModuleInit, OnApplicationShutdown {
}
}

private generateUuid(): string {
let d = Date.now();
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
(c: string) => {
const r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
}
);
}

private getClientId(): string {
let clientId = this.asyncStorageManager.get('clientId');

if (!clientId) {
clientId = this.generateUuid();
this.asyncStorageManager.set('clientId', clientId);
}

return clientId;
return this.asyncStorageService.get('clientId');
}

private sleep(ms: number): Promise<void> {
Expand Down
1 change: 1 addition & 0 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './uuid-generator';
11 changes: 11 additions & 0 deletions lib/utils/uuid-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function generateUuid(): string {
let d = Date.now();
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
(c: string) => {
const r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
}
);
}
Loading

0 comments on commit 0bb626e

Please sign in to comment.