Latest commit

History

228 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

pub-comp/caching (PubComp.Caching)

This project enables simple plug and play caching with various implementations and enables you to add your own implemenations.

Topics

NuGets

(available at nuget.org)

The core - ICache, CacheManager, LayeredCache: PubComp.Caching.Core

InMemoryCache based on System.Runtime.Caching.ObjectCache: PubComp.Caching.SystemRuntime

RedisCache based on StackExchange.Redis: PubComp.Caching.RedisCaching

AOP (Aspect Oriented Programming) caching based on PostSharp: PubComp.Caching.AopCaching

Basic Usage

You can access the cache directly via its interface:

TryGet

ICachecache=newRedisCache("myRemoteCache",newRedisCachePolicy());MyDatadata;if(!cache.TryGet("myKey",outdata)){data=FallbackMethodToRun();cache.Set("myKey",data);}

Get

or

MyDatadata=cache.Get("myKey",()=>FallbackMethodToRun());

this single line of code above is equivalent to the multiple lines of code in the above TryGet example in the above usage, the method will be run only if the requested key is not in the cache (on cache miss).

AOP Wrapper

or use a PostSharp based aspect to wrap a method with cache:

[Cache]MyDataAMethodThatNeedsCaching(stringparameter1,intparameter2){// Some long running operation}

calling the above method (e.g. with parameter values "one", 2) will be equivalent to calling a non aspect declared method with:

varkey="{"ClassName":"MyNameSpace.MyClassName","MethodName":"MyMethod","ParameterTypeNames":["System.String","System.Int32"],"ParmaterValues":["one",2]}";
MyDataresult=myCache.Get(key,()=>MyMethod("one",2));

Clear Item

You can clear a specific item from the cache:

myCache.Clear("myKey");

Clear All

or clear the entire cache (all items):

myCache.ClearAll();

If you have multiple caches, clearing one will NOT affect the other e.g.

ICachecache1=newInMemoryCache("myLocalCache",newInMemoryPolicy());ICachecache2=newInMemoryCache("myLocalCache",newInMemoryPolicy());cache1.ClearAll();// Only cache1 is cleared

Cache Types And Policies

You can create instances of cache from different types cache e.g. InMemoryCache, RedisCache and instances of the same type with different policies e.g. no automatic expiration, expiration from add, sliding expiration (LRU - Least Recently Used).

When you create a cache, you pass a name and the policy.

You can use the CacheManager to get a cache with a specific name e.g.

varcache=CacheManager.GetCache("myLocalCache");

Cache Management

A cache can be created programmatically:

varcache=newInMemoryCache("myOtherLocalCache",newInMemoryPolicy());

or received from the CacheManager which can be configured via code:

CacheManager.SetCache("noCache*",newNoCache("noCache"));varcache=CacheManager.GetCache("noCache1");

or via config file:

 <configSections>
<sectionGroupname="PubComp">
<sectionname="CacheConfig"type="PubComp.Caching.Core.CacheConfigurationHandler, PubComp.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"allowLocation="true"allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
<PubComp>
<CacheConfig>
<!-- You can either use ' or &quot; in the policy JSON --><!-- Each cache can be of a different type e.g. local (PubComp.Caching.SystemRuntime) or Redis (PubComp.Caching.RedisCaching.RedisCache) and with a different policy --><!-- Each cache type can have various policies e.g. ExpirationFromAdd, SlidingExpiration --><!-- You can create your own cache type, just direct the config to your assembly and class -->
<addname="localCache"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'ExpirationFromAdd':'01:00:00'}" />
<addname="localCacheLRU"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'SlidingExpiration':'00:15:00'}" />
<!-- policy="{&quot;ExpirationFromAdd&quot;:&quot;'01:00:00&quot;}" would work too -->
</CacheConfig>
</PubComp>

Using A Defined Cache

You can request a cache, by name, from the CacheManager:

varcache=CacheManager.GetCache("localCacheLRU");

or via AOP (Aspect Oriented Programming)...

Wrap a method with cache, so that the underlaying method is only called on cache misses:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

(*) When using AOP, the aspect only requests the cache from CacheManager once, for optimization, therefore changing the registeration of the cache (using SetCache won't have an effect later on).

You can also cache an IList of items, each time running the underlaying method only for the missing keys:

AOP Caching

Using AOP, you can easily wrap a method with a lazy-loading cache:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

The return value will be cached under a unique key, built from the class' full name (namespace and name), the method name, the parameter types and the parameter value.

so that the following methods will not have the same key:

[Cache("localCache")]privateList<Item>MethodToCache(stringx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(intx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(bytex){// data retrieval goes here}

Getting The Key

You can get the key used by the Cache aspect (CacheAttribute), in order to enable manual clearing of the data like so:

varkey=CacheKey.GetKey(()=>service.MethodToCache("parameterValue"));

and then clear as usual:

CacheManager.GetCache("localCache").Clear(key);

Caching Multiple Items By Key

You can use the CacheList aspect (CacheListAttribute) to cache data item by item. When using this way, the underlaying method (e.g. DB access) is called only for keys missing in the cache (and only if any are missing).

Example:

// Both IDs parameter and return type have to be of type IList<>[CacheList(typeof(MockDataKeyConverter))]publicIList<MockData>GetItems(IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}// Example data typepublicclassMockData{publicstringId{get;set;}publicstringValue{get;set;}}// This class enables the aspect, given a result what its key (ID) ispublicclassMockDataKeyConverter:IDataKeyConverter<string,MockData>{publicstringGetKey(MockDatadata){returndata.Id;}}

If the keys are not the first parameter of the method, use keyParameterNumber to instruct the aspect which parameter to use (0-based):

[CacheList(typeof(MockDataKeyConverter),keyParameterNumber=1)]publicIList<MockData>GetItems(stringparameter0,IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}

Ignoring Specific Parameters

You can instruct the aspects (Cache and CacheList) to ignore a specific parameter when generating the cache-key using the DoNotIncludeInCacheKey attribute (DoNotIncludeInCacheKeyAttribute):

[Cache]publicstringMethodToCache1(intid,[DoNotIncludeInCacheKey]objectobj){}

Extending With Your Own Classes

You can extend this project with you own classes by creating an assembly (a class library project) with the following classes:

Adding Your Own Cache Type

  • YourCache - the actual cache's code. Must implement ICache
  • YourCacheConfig - a factory for creating an instance of your cache from a given policy. Must inherit from CacheConfig.
  • YourCachePolicy - the policy for your cache - any parameters you need for constructing your cache e.g. eviction policy, connection details. Must be serializable to from JSON.

You will then be able to configure usage of your cache like so:

<PubComp>
<CacheConfig>
<addname="cacheName"assembly="YourAssembly"type="YourType"policy="JSON goes here" />
</CacheConfig>
</PubComp>

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

228 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

pub-comp/caching (PubComp.Caching)

This project enables simple plug and play caching with various implementations and enables you to add your own implemenations.

Topics

NuGets

(available at nuget.org)

The core - ICache, CacheManager, LayeredCache: PubComp.Caching.Core

InMemoryCache based on System.Runtime.Caching.ObjectCache: PubComp.Caching.SystemRuntime

RedisCache based on StackExchange.Redis: PubComp.Caching.RedisCaching

AOP (Aspect Oriented Programming) caching based on PostSharp: PubComp.Caching.AopCaching

Basic Usage

You can access the cache directly via its interface:

TryGet

ICachecache=newRedisCache("myRemoteCache",newRedisCachePolicy());MyDatadata;if(!cache.TryGet("myKey",outdata)){data=FallbackMethodToRun();cache.Set("myKey",data);}

Get

or

MyDatadata=cache.Get("myKey",()=>FallbackMethodToRun());

this single line of code above is equivalent to the multiple lines of code in the above TryGet example in the above usage, the method will be run only if the requested key is not in the cache (on cache miss).

AOP Wrapper

or use a PostSharp based aspect to wrap a method with cache:

[Cache]MyDataAMethodThatNeedsCaching(stringparameter1,intparameter2){// Some long running operation}

calling the above method (e.g. with parameter values "one", 2) will be equivalent to calling a non aspect declared method with:

varkey="{"ClassName":"MyNameSpace.MyClassName","MethodName":"MyMethod","ParameterTypeNames":["System.String","System.Int32"],"ParmaterValues":["one",2]}";
MyDataresult=myCache.Get(key,()=>MyMethod("one",2));

Clear Item

You can clear a specific item from the cache:

myCache.Clear("myKey");

Clear All

or clear the entire cache (all items):

myCache.ClearAll();

If you have multiple caches, clearing one will NOT affect the other e.g.

ICachecache1=newInMemoryCache("myLocalCache",newInMemoryPolicy());ICachecache2=newInMemoryCache("myLocalCache",newInMemoryPolicy());cache1.ClearAll();// Only cache1 is cleared

Cache Types And Policies

You can create instances of cache from different types cache e.g. InMemoryCache, RedisCache and instances of the same type with different policies e.g. no automatic expiration, expiration from add, sliding expiration (LRU - Least Recently Used).

When you create a cache, you pass a name and the policy.

You can use the CacheManager to get a cache with a specific name e.g.

varcache=CacheManager.GetCache("myLocalCache");

Cache Management

A cache can be created programmatically:

varcache=newInMemoryCache("myOtherLocalCache",newInMemoryPolicy());

or received from the CacheManager which can be configured via code:

CacheManager.SetCache("noCache*",newNoCache("noCache"));varcache=CacheManager.GetCache("noCache1");

or via config file:

 <configSections>
<sectionGroupname="PubComp">
<sectionname="CacheConfig"type="PubComp.Caching.Core.CacheConfigurationHandler, PubComp.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"allowLocation="true"allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
<PubComp>
<CacheConfig>
<!-- You can either use ' or &quot; in the policy JSON --><!-- Each cache can be of a different type e.g. local (PubComp.Caching.SystemRuntime) or Redis (PubComp.Caching.RedisCaching.RedisCache) and with a different policy --><!-- Each cache type can have various policies e.g. ExpirationFromAdd, SlidingExpiration --><!-- You can create your own cache type, just direct the config to your assembly and class -->
<addname="localCache"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'ExpirationFromAdd':'01:00:00'}" />
<addname="localCacheLRU"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'SlidingExpiration':'00:15:00'}" />
<!-- policy="{&quot;ExpirationFromAdd&quot;:&quot;'01:00:00&quot;}" would work too -->
</CacheConfig>
</PubComp>

Using A Defined Cache

You can request a cache, by name, from the CacheManager:

varcache=CacheManager.GetCache("localCacheLRU");

or via AOP (Aspect Oriented Programming)...

Wrap a method with cache, so that the underlaying method is only called on cache misses:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

(*) When using AOP, the aspect only requests the cache from CacheManager once, for optimization, therefore changing the registeration of the cache (using SetCache won't have an effect later on).

You can also cache an IList of items, each time running the underlaying method only for the missing keys:

AOP Caching

Using AOP, you can easily wrap a method with a lazy-loading cache:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

The return value will be cached under a unique key, built from the class' full name (namespace and name), the method name, the parameter types and the parameter value.

so that the following methods will not have the same key:

[Cache("localCache")]privateList<Item>MethodToCache(stringx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(intx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(bytex){// data retrieval goes here}

Getting The Key

You can get the key used by the Cache aspect (CacheAttribute), in order to enable manual clearing of the data like so:

varkey=CacheKey.GetKey(()=>service.MethodToCache("parameterValue"));

and then clear as usual:

CacheManager.GetCache("localCache").Clear(key);

Caching Multiple Items By Key

You can use the CacheList aspect (CacheListAttribute) to cache data item by item. When using this way, the underlaying method (e.g. DB access) is called only for keys missing in the cache (and only if any are missing).

Example:

// Both IDs parameter and return type have to be of type IList<>[CacheList(typeof(MockDataKeyConverter))]publicIList<MockData>GetItems(IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}// Example data typepublicclassMockData{publicstringId{get;set;}publicstringValue{get;set;}}// This class enables the aspect, given a result what its key (ID) ispublicclassMockDataKeyConverter:IDataKeyConverter<string,MockData>{publicstringGetKey(MockDatadata){returndata.Id;}}

If the keys are not the first parameter of the method, use keyParameterNumber to instruct the aspect which parameter to use (0-based):

[CacheList(typeof(MockDataKeyConverter),keyParameterNumber=1)]publicIList<MockData>GetItems(stringparameter0,IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}

Ignoring Specific Parameters

You can instruct the aspects (Cache and CacheList) to ignore a specific parameter when generating the cache-key using the DoNotIncludeInCacheKey attribute (DoNotIncludeInCacheKeyAttribute):

[Cache]publicstringMethodToCache1(intid,[DoNotIncludeInCacheKey]objectobj){}

Extending With Your Own Classes

You can extend this project with you own classes by creating an assembly (a class library project) with the following classes:

Adding Your Own Cache Type

  • YourCache - the actual cache's code. Must implement ICache
  • YourCacheConfig - a factory for creating an instance of your cache from a given policy. Must inherit from CacheConfig.
  • YourCachePolicy - the policy for your cache - any parameters you need for constructing your cache e.g. eviction policy, connection details. Must be serializable to from JSON.

You will then be able to configure usage of your cache like so:

<PubComp>
<CacheConfig>
<addname="cacheName"assembly="YourAssembly"type="YourType"policy="JSON goes here" />
</CacheConfig>
</PubComp>

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

228 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

pub-comp/caching (PubComp.Caching)

This project enables simple plug and play caching with various implementations and enables you to add your own implemenations.

Topics

NuGets

(available at nuget.org)

The core - ICache, CacheManager, LayeredCache: PubComp.Caching.Core

InMemoryCache based on System.Runtime.Caching.ObjectCache: PubComp.Caching.SystemRuntime

RedisCache based on StackExchange.Redis: PubComp.Caching.RedisCaching

AOP (Aspect Oriented Programming) caching based on PostSharp: PubComp.Caching.AopCaching

Basic Usage

You can access the cache directly via its interface:

TryGet

ICachecache=newRedisCache("myRemoteCache",newRedisCachePolicy());MyDatadata;if(!cache.TryGet("myKey",outdata)){data=FallbackMethodToRun();cache.Set("myKey",data);}

Get

or

MyDatadata=cache.Get("myKey",()=>FallbackMethodToRun());

this single line of code above is equivalent to the multiple lines of code in the above TryGet example in the above usage, the method will be run only if the requested key is not in the cache (on cache miss).

AOP Wrapper

or use a PostSharp based aspect to wrap a method with cache:

[Cache]MyDataAMethodThatNeedsCaching(stringparameter1,intparameter2){// Some long running operation}

calling the above method (e.g. with parameter values "one", 2) will be equivalent to calling a non aspect declared method with:

varkey="{"ClassName":"MyNameSpace.MyClassName","MethodName":"MyMethod","ParameterTypeNames":["System.String","System.Int32"],"ParmaterValues":["one",2]}";
MyDataresult=myCache.Get(key,()=>MyMethod("one",2));

Clear Item

You can clear a specific item from the cache:

myCache.Clear("myKey");

Clear All

or clear the entire cache (all items):

myCache.ClearAll();

If you have multiple caches, clearing one will NOT affect the other e.g.

ICachecache1=newInMemoryCache("myLocalCache",newInMemoryPolicy());ICachecache2=newInMemoryCache("myLocalCache",newInMemoryPolicy());cache1.ClearAll();// Only cache1 is cleared

Cache Types And Policies

You can create instances of cache from different types cache e.g. InMemoryCache, RedisCache and instances of the same type with different policies e.g. no automatic expiration, expiration from add, sliding expiration (LRU - Least Recently Used).

When you create a cache, you pass a name and the policy.

You can use the CacheManager to get a cache with a specific name e.g.

varcache=CacheManager.GetCache("myLocalCache");

Cache Management

A cache can be created programmatically:

varcache=newInMemoryCache("myOtherLocalCache",newInMemoryPolicy());

or received from the CacheManager which can be configured via code:

CacheManager.SetCache("noCache*",newNoCache("noCache"));varcache=CacheManager.GetCache("noCache1");

or via config file:

 <configSections>
<sectionGroupname="PubComp">
<sectionname="CacheConfig"type="PubComp.Caching.Core.CacheConfigurationHandler, PubComp.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"allowLocation="true"allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
<PubComp>
<CacheConfig>
<!-- You can either use ' or &quot; in the policy JSON --><!-- Each cache can be of a different type e.g. local (PubComp.Caching.SystemRuntime) or Redis (PubComp.Caching.RedisCaching.RedisCache) and with a different policy --><!-- Each cache type can have various policies e.g. ExpirationFromAdd, SlidingExpiration --><!-- You can create your own cache type, just direct the config to your assembly and class -->
<addname="localCache"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'ExpirationFromAdd':'01:00:00'}" />
<addname="localCacheLRU"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'SlidingExpiration':'00:15:00'}" />
<!-- policy="{&quot;ExpirationFromAdd&quot;:&quot;'01:00:00&quot;}" would work too -->
</CacheConfig>
</PubComp>

Using A Defined Cache

You can request a cache, by name, from the CacheManager:

varcache=CacheManager.GetCache("localCacheLRU");

or via AOP (Aspect Oriented Programming)...

Wrap a method with cache, so that the underlaying method is only called on cache misses:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

(*) When using AOP, the aspect only requests the cache from CacheManager once, for optimization, therefore changing the registeration of the cache (using SetCache won't have an effect later on).

You can also cache an IList of items, each time running the underlaying method only for the missing keys:

AOP Caching

Using AOP, you can easily wrap a method with a lazy-loading cache:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

The return value will be cached under a unique key, built from the class' full name (namespace and name), the method name, the parameter types and the parameter value.

so that the following methods will not have the same key:

[Cache("localCache")]privateList<Item>MethodToCache(stringx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(intx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(bytex){// data retrieval goes here}

Getting The Key

You can get the key used by the Cache aspect (CacheAttribute), in order to enable manual clearing of the data like so:

varkey=CacheKey.GetKey(()=>service.MethodToCache("parameterValue"));

and then clear as usual:

CacheManager.GetCache("localCache").Clear(key);

Caching Multiple Items By Key

You can use the CacheList aspect (CacheListAttribute) to cache data item by item. When using this way, the underlaying method (e.g. DB access) is called only for keys missing in the cache (and only if any are missing).

Example:

// Both IDs parameter and return type have to be of type IList<>[CacheList(typeof(MockDataKeyConverter))]publicIList<MockData>GetItems(IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}// Example data typepublicclassMockData{publicstringId{get;set;}publicstringValue{get;set;}}// This class enables the aspect, given a result what its key (ID) ispublicclassMockDataKeyConverter:IDataKeyConverter<string,MockData>{publicstringGetKey(MockDatadata){returndata.Id;}}

If the keys are not the first parameter of the method, use keyParameterNumber to instruct the aspect which parameter to use (0-based):

[CacheList(typeof(MockDataKeyConverter),keyParameterNumber=1)]publicIList<MockData>GetItems(stringparameter0,IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}

Ignoring Specific Parameters

You can instruct the aspects (Cache and CacheList) to ignore a specific parameter when generating the cache-key using the DoNotIncludeInCacheKey attribute (DoNotIncludeInCacheKeyAttribute):

[Cache]publicstringMethodToCache1(intid,[DoNotIncludeInCacheKey]objectobj){}

Extending With Your Own Classes

You can extend this project with you own classes by creating an assembly (a class library project) with the following classes:

Adding Your Own Cache Type

  • YourCache - the actual cache's code. Must implement ICache
  • YourCacheConfig - a factory for creating an instance of your cache from a given policy. Must inherit from CacheConfig.
  • YourCachePolicy - the policy for your cache - any parameters you need for constructing your cache e.g. eviction policy, connection details. Must be serializable to from JSON.

You will then be able to configure usage of your cache like so:

<PubComp>
<CacheConfig>
<addname="cacheName"assembly="YourAssembly"type="YourType"policy="JSON goes here" />
</CacheConfig>
</PubComp>

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

228 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

pub-comp/caching (PubComp.Caching)

This project enables simple plug and play caching with various implementations and enables you to add your own implemenations.

Topics

NuGets

(available at nuget.org)

The core - ICache, CacheManager, LayeredCache: PubComp.Caching.Core

InMemoryCache based on System.Runtime.Caching.ObjectCache: PubComp.Caching.SystemRuntime

RedisCache based on StackExchange.Redis: PubComp.Caching.RedisCaching

AOP (Aspect Oriented Programming) caching based on PostSharp: PubComp.Caching.AopCaching

Basic Usage

You can access the cache directly via its interface:

TryGet

ICachecache=newRedisCache("myRemoteCache",newRedisCachePolicy());MyDatadata;if(!cache.TryGet("myKey",outdata)){data=FallbackMethodToRun();cache.Set("myKey",data);}

Get

or

MyDatadata=cache.Get("myKey",()=>FallbackMethodToRun());

this single line of code above is equivalent to the multiple lines of code in the above TryGet example in the above usage, the method will be run only if the requested key is not in the cache (on cache miss).

AOP Wrapper

or use a PostSharp based aspect to wrap a method with cache:

[Cache]MyDataAMethodThatNeedsCaching(stringparameter1,intparameter2){// Some long running operation}

calling the above method (e.g. with parameter values "one", 2) will be equivalent to calling a non aspect declared method with:

varkey="{"ClassName":"MyNameSpace.MyClassName","MethodName":"MyMethod","ParameterTypeNames":["System.String","System.Int32"],"ParmaterValues":["one",2]}";
MyDataresult=myCache.Get(key,()=>MyMethod("one",2));

Clear Item

You can clear a specific item from the cache:

myCache.Clear("myKey");

Clear All

or clear the entire cache (all items):

myCache.ClearAll();

If you have multiple caches, clearing one will NOT affect the other e.g.

ICachecache1=newInMemoryCache("myLocalCache",newInMemoryPolicy());ICachecache2=newInMemoryCache("myLocalCache",newInMemoryPolicy());cache1.ClearAll();// Only cache1 is cleared

Cache Types And Policies

You can create instances of cache from different types cache e.g. InMemoryCache, RedisCache and instances of the same type with different policies e.g. no automatic expiration, expiration from add, sliding expiration (LRU - Least Recently Used).

When you create a cache, you pass a name and the policy.

You can use the CacheManager to get a cache with a specific name e.g.

varcache=CacheManager.GetCache("myLocalCache");

Cache Management

A cache can be created programmatically:

varcache=newInMemoryCache("myOtherLocalCache",newInMemoryPolicy());

or received from the CacheManager which can be configured via code:

CacheManager.SetCache("noCache*",newNoCache("noCache"));varcache=CacheManager.GetCache("noCache1");

or via config file:

 <configSections>
<sectionGroupname="PubComp">
<sectionname="CacheConfig"type="PubComp.Caching.Core.CacheConfigurationHandler, PubComp.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"allowLocation="true"allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
<PubComp>
<CacheConfig>
<!-- You can either use ' or &quot; in the policy JSON --><!-- Each cache can be of a different type e.g. local (PubComp.Caching.SystemRuntime) or Redis (PubComp.Caching.RedisCaching.RedisCache) and with a different policy --><!-- Each cache type can have various policies e.g. ExpirationFromAdd, SlidingExpiration --><!-- You can create your own cache type, just direct the config to your assembly and class -->
<addname="localCache"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'ExpirationFromAdd':'01:00:00'}" />
<addname="localCacheLRU"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'SlidingExpiration':'00:15:00'}" />
<!-- policy="{&quot;ExpirationFromAdd&quot;:&quot;'01:00:00&quot;}" would work too -->
</CacheConfig>
</PubComp>

Using A Defined Cache

You can request a cache, by name, from the CacheManager:

varcache=CacheManager.GetCache("localCacheLRU");

or via AOP (Aspect Oriented Programming)...

Wrap a method with cache, so that the underlaying method is only called on cache misses:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

(*) When using AOP, the aspect only requests the cache from CacheManager once, for optimization, therefore changing the registeration of the cache (using SetCache won't have an effect later on).

You can also cache an IList of items, each time running the underlaying method only for the missing keys:

AOP Caching

Using AOP, you can easily wrap a method with a lazy-loading cache:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

The return value will be cached under a unique key, built from the class' full name (namespace and name), the method name, the parameter types and the parameter value.

so that the following methods will not have the same key:

[Cache("localCache")]privateList<Item>MethodToCache(stringx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(intx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(bytex){// data retrieval goes here}

Getting The Key

You can get the key used by the Cache aspect (CacheAttribute), in order to enable manual clearing of the data like so:

varkey=CacheKey.GetKey(()=>service.MethodToCache("parameterValue"));

and then clear as usual:

CacheManager.GetCache("localCache").Clear(key);

Caching Multiple Items By Key

You can use the CacheList aspect (CacheListAttribute) to cache data item by item. When using this way, the underlaying method (e.g. DB access) is called only for keys missing in the cache (and only if any are missing).

Example:

// Both IDs parameter and return type have to be of type IList<>[CacheList(typeof(MockDataKeyConverter))]publicIList<MockData>GetItems(IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}// Example data typepublicclassMockData{publicstringId{get;set;}publicstringValue{get;set;}}// This class enables the aspect, given a result what its key (ID) ispublicclassMockDataKeyConverter:IDataKeyConverter<string,MockData>{publicstringGetKey(MockDatadata){returndata.Id;}}

If the keys are not the first parameter of the method, use keyParameterNumber to instruct the aspect which parameter to use (0-based):

[CacheList(typeof(MockDataKeyConverter),keyParameterNumber=1)]publicIList<MockData>GetItems(stringparameter0,IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}

Ignoring Specific Parameters

You can instruct the aspects (Cache and CacheList) to ignore a specific parameter when generating the cache-key using the DoNotIncludeInCacheKey attribute (DoNotIncludeInCacheKeyAttribute):

[Cache]publicstringMethodToCache1(intid,[DoNotIncludeInCacheKey]objectobj){}

Extending With Your Own Classes

You can extend this project with you own classes by creating an assembly (a class library project) with the following classes:

Adding Your Own Cache Type

  • YourCache - the actual cache's code. Must implement ICache
  • YourCacheConfig - a factory for creating an instance of your cache from a given policy. Must inherit from CacheConfig.
  • YourCachePolicy - the policy for your cache - any parameters you need for constructing your cache e.g. eviction policy, connection details. Must be serializable to from JSON.

You will then be able to configure usage of your cache like so:

<PubComp>
<CacheConfig>
<addname="cacheName"assembly="YourAssembly"type="YourType"policy="JSON goes here" />
</CacheConfig>
</PubComp>

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

228 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

pub-comp/caching (PubComp.Caching)

This project enables simple plug and play caching with various implementations and enables you to add your own implemenations.

Topics

NuGets

(available at nuget.org)

The core - ICache, CacheManager, LayeredCache: PubComp.Caching.Core

InMemoryCache based on System.Runtime.Caching.ObjectCache: PubComp.Caching.SystemRuntime

RedisCache based on StackExchange.Redis: PubComp.Caching.RedisCaching

AOP (Aspect Oriented Programming) caching based on PostSharp: PubComp.Caching.AopCaching

Basic Usage

You can access the cache directly via its interface:

TryGet

ICachecache=newRedisCache("myRemoteCache",newRedisCachePolicy());MyDatadata;if(!cache.TryGet("myKey",outdata)){data=FallbackMethodToRun();cache.Set("myKey",data);}

Get

or

MyDatadata=cache.Get("myKey",()=>FallbackMethodToRun());

this single line of code above is equivalent to the multiple lines of code in the above TryGet example in the above usage, the method will be run only if the requested key is not in the cache (on cache miss).

AOP Wrapper

or use a PostSharp based aspect to wrap a method with cache:

[Cache]MyDataAMethodThatNeedsCaching(stringparameter1,intparameter2){// Some long running operation}

calling the above method (e.g. with parameter values "one", 2) will be equivalent to calling a non aspect declared method with:

varkey="{"ClassName":"MyNameSpace.MyClassName","MethodName":"MyMethod","ParameterTypeNames":["System.String","System.Int32"],"ParmaterValues":["one",2]}";
MyDataresult=myCache.Get(key,()=>MyMethod("one",2));

Clear Item

You can clear a specific item from the cache:

myCache.Clear("myKey");

Clear All

or clear the entire cache (all items):

myCache.ClearAll();

If you have multiple caches, clearing one will NOT affect the other e.g.

ICachecache1=newInMemoryCache("myLocalCache",newInMemoryPolicy());ICachecache2=newInMemoryCache("myLocalCache",newInMemoryPolicy());cache1.ClearAll();// Only cache1 is cleared

Cache Types And Policies

You can create instances of cache from different types cache e.g. InMemoryCache, RedisCache and instances of the same type with different policies e.g. no automatic expiration, expiration from add, sliding expiration (LRU - Least Recently Used).

When you create a cache, you pass a name and the policy.

You can use the CacheManager to get a cache with a specific name e.g.

varcache=CacheManager.GetCache("myLocalCache");

Cache Management

A cache can be created programmatically:

varcache=newInMemoryCache("myOtherLocalCache",newInMemoryPolicy());

or received from the CacheManager which can be configured via code:

CacheManager.SetCache("noCache*",newNoCache("noCache"));varcache=CacheManager.GetCache("noCache1");

or via config file:

 <configSections>
<sectionGroupname="PubComp">
<sectionname="CacheConfig"type="PubComp.Caching.Core.CacheConfigurationHandler, PubComp.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"allowLocation="true"allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
<PubComp>
<CacheConfig>
<!-- You can either use ' or &quot; in the policy JSON --><!-- Each cache can be of a different type e.g. local (PubComp.Caching.SystemRuntime) or Redis (PubComp.Caching.RedisCaching.RedisCache) and with a different policy --><!-- Each cache type can have various policies e.g. ExpirationFromAdd, SlidingExpiration --><!-- You can create your own cache type, just direct the config to your assembly and class -->
<addname="localCache"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'ExpirationFromAdd':'01:00:00'}" />
<addname="localCacheLRU"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'SlidingExpiration':'00:15:00'}" />
<!-- policy="{&quot;ExpirationFromAdd&quot;:&quot;'01:00:00&quot;}" would work too -->
</CacheConfig>
</PubComp>

Using A Defined Cache

You can request a cache, by name, from the CacheManager:

varcache=CacheManager.GetCache("localCacheLRU");

or via AOP (Aspect Oriented Programming)...

Wrap a method with cache, so that the underlaying method is only called on cache misses:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

(*) When using AOP, the aspect only requests the cache from CacheManager once, for optimization, therefore changing the registeration of the cache (using SetCache won't have an effect later on).

You can also cache an IList of items, each time running the underlaying method only for the missing keys:

AOP Caching

Using AOP, you can easily wrap a method with a lazy-loading cache:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

The return value will be cached under a unique key, built from the class' full name (namespace and name), the method name, the parameter types and the parameter value.

so that the following methods will not have the same key:

[Cache("localCache")]privateList<Item>MethodToCache(stringx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(intx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(bytex){// data retrieval goes here}

Getting The Key

You can get the key used by the Cache aspect (CacheAttribute), in order to enable manual clearing of the data like so:

varkey=CacheKey.GetKey(()=>service.MethodToCache("parameterValue"));

and then clear as usual:

CacheManager.GetCache("localCache").Clear(key);

Caching Multiple Items By Key

You can use the CacheList aspect (CacheListAttribute) to cache data item by item. When using this way, the underlaying method (e.g. DB access) is called only for keys missing in the cache (and only if any are missing).

Example:

// Both IDs parameter and return type have to be of type IList<>[CacheList(typeof(MockDataKeyConverter))]publicIList<MockData>GetItems(IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}// Example data typepublicclassMockData{publicstringId{get;set;}publicstringValue{get;set;}}// This class enables the aspect, given a result what its key (ID) ispublicclassMockDataKeyConverter:IDataKeyConverter<string,MockData>{publicstringGetKey(MockDatadata){returndata.Id;}}

If the keys are not the first parameter of the method, use keyParameterNumber to instruct the aspect which parameter to use (0-based):

[CacheList(typeof(MockDataKeyConverter),keyParameterNumber=1)]publicIList<MockData>GetItems(stringparameter0,IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}

Ignoring Specific Parameters

You can instruct the aspects (Cache and CacheList) to ignore a specific parameter when generating the cache-key using the DoNotIncludeInCacheKey attribute (DoNotIncludeInCacheKeyAttribute):

[Cache]publicstringMethodToCache1(intid,[DoNotIncludeInCacheKey]objectobj){}

Extending With Your Own Classes

You can extend this project with you own classes by creating an assembly (a class library project) with the following classes:

Adding Your Own Cache Type

  • YourCache - the actual cache's code. Must implement ICache
  • YourCacheConfig - a factory for creating an instance of your cache from a given policy. Must inherit from CacheConfig.
  • YourCachePolicy - the policy for your cache - any parameters you need for constructing your cache e.g. eviction policy, connection details. Must be serializable to from JSON.

You will then be able to configure usage of your cache like so:

<PubComp>
<CacheConfig>
<addname="cacheName"assembly="YourAssembly"type="YourType"policy="JSON goes here" />
</CacheConfig>
</PubComp>

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

228 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

pub-comp/caching (PubComp.Caching)

This project enables simple plug and play caching with various implementations and enables you to add your own implemenations.

Topics

NuGets

(available at nuget.org)

The core - ICache, CacheManager, LayeredCache: PubComp.Caching.Core

InMemoryCache based on System.Runtime.Caching.ObjectCache: PubComp.Caching.SystemRuntime

RedisCache based on StackExchange.Redis: PubComp.Caching.RedisCaching

AOP (Aspect Oriented Programming) caching based on PostSharp: PubComp.Caching.AopCaching

Basic Usage

You can access the cache directly via its interface:

TryGet

ICachecache=newRedisCache("myRemoteCache",newRedisCachePolicy());MyDatadata;if(!cache.TryGet("myKey",outdata)){data=FallbackMethodToRun();cache.Set("myKey",data);}

Get

or

MyDatadata=cache.Get("myKey",()=>FallbackMethodToRun());

this single line of code above is equivalent to the multiple lines of code in the above TryGet example in the above usage, the method will be run only if the requested key is not in the cache (on cache miss).

AOP Wrapper

or use a PostSharp based aspect to wrap a method with cache:

[Cache]MyDataAMethodThatNeedsCaching(stringparameter1,intparameter2){// Some long running operation}

calling the above method (e.g. with parameter values "one", 2) will be equivalent to calling a non aspect declared method with:

varkey="{"ClassName":"MyNameSpace.MyClassName","MethodName":"MyMethod","ParameterTypeNames":["System.String","System.Int32"],"ParmaterValues":["one",2]}";
MyDataresult=myCache.Get(key,()=>MyMethod("one",2));

Clear Item

You can clear a specific item from the cache:

myCache.Clear("myKey");

Clear All

or clear the entire cache (all items):

myCache.ClearAll();

If you have multiple caches, clearing one will NOT affect the other e.g.

ICachecache1=newInMemoryCache("myLocalCache",newInMemoryPolicy());ICachecache2=newInMemoryCache("myLocalCache",newInMemoryPolicy());cache1.ClearAll();// Only cache1 is cleared

Cache Types And Policies

You can create instances of cache from different types cache e.g. InMemoryCache, RedisCache and instances of the same type with different policies e.g. no automatic expiration, expiration from add, sliding expiration (LRU - Least Recently Used).

When you create a cache, you pass a name and the policy.

You can use the CacheManager to get a cache with a specific name e.g.

varcache=CacheManager.GetCache("myLocalCache");

Cache Management

A cache can be created programmatically:

varcache=newInMemoryCache("myOtherLocalCache",newInMemoryPolicy());

or received from the CacheManager which can be configured via code:

CacheManager.SetCache("noCache*",newNoCache("noCache"));varcache=CacheManager.GetCache("noCache1");

or via config file:

 <configSections>
<sectionGroupname="PubComp">
<sectionname="CacheConfig"type="PubComp.Caching.Core.CacheConfigurationHandler, PubComp.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"allowLocation="true"allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
<PubComp>
<CacheConfig>
<!-- You can either use ' or &quot; in the policy JSON --><!-- Each cache can be of a different type e.g. local (PubComp.Caching.SystemRuntime) or Redis (PubComp.Caching.RedisCaching.RedisCache) and with a different policy --><!-- Each cache type can have various policies e.g. ExpirationFromAdd, SlidingExpiration --><!-- You can create your own cache type, just direct the config to your assembly and class -->
<addname="localCache"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'ExpirationFromAdd':'01:00:00'}" />
<addname="localCacheLRU"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'SlidingExpiration':'00:15:00'}" />
<!-- policy="{&quot;ExpirationFromAdd&quot;:&quot;'01:00:00&quot;}" would work too -->
</CacheConfig>
</PubComp>

Using A Defined Cache

You can request a cache, by name, from the CacheManager:

varcache=CacheManager.GetCache("localCacheLRU");

or via AOP (Aspect Oriented Programming)...

Wrap a method with cache, so that the underlaying method is only called on cache misses:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

(*) When using AOP, the aspect only requests the cache from CacheManager once, for optimization, therefore changing the registeration of the cache (using SetCache won't have an effect later on).

You can also cache an IList of items, each time running the underlaying method only for the missing keys:

AOP Caching

Using AOP, you can easily wrap a method with a lazy-loading cache:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

The return value will be cached under a unique key, built from the class' full name (namespace and name), the method name, the parameter types and the parameter value.

so that the following methods will not have the same key:

[Cache("localCache")]privateList<Item>MethodToCache(stringx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(intx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(bytex){// data retrieval goes here}

Getting The Key

You can get the key used by the Cache aspect (CacheAttribute), in order to enable manual clearing of the data like so:

varkey=CacheKey.GetKey(()=>service.MethodToCache("parameterValue"));

and then clear as usual:

CacheManager.GetCache("localCache").Clear(key);

Caching Multiple Items By Key

You can use the CacheList aspect (CacheListAttribute) to cache data item by item. When using this way, the underlaying method (e.g. DB access) is called only for keys missing in the cache (and only if any are missing).

Example:

// Both IDs parameter and return type have to be of type IList<>[CacheList(typeof(MockDataKeyConverter))]publicIList<MockData>GetItems(IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}// Example data typepublicclassMockData{publicstringId{get;set;}publicstringValue{get;set;}}// This class enables the aspect, given a result what its key (ID) ispublicclassMockDataKeyConverter:IDataKeyConverter<string,MockData>{publicstringGetKey(MockDatadata){returndata.Id;}}

If the keys are not the first parameter of the method, use keyParameterNumber to instruct the aspect which parameter to use (0-based):

[CacheList(typeof(MockDataKeyConverter),keyParameterNumber=1)]publicIList<MockData>GetItems(stringparameter0,IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}

Ignoring Specific Parameters

You can instruct the aspects (Cache and CacheList) to ignore a specific parameter when generating the cache-key using the DoNotIncludeInCacheKey attribute (DoNotIncludeInCacheKeyAttribute):

[Cache]publicstringMethodToCache1(intid,[DoNotIncludeInCacheKey]objectobj){}

Extending With Your Own Classes

You can extend this project with you own classes by creating an assembly (a class library project) with the following classes:

Adding Your Own Cache Type

  • YourCache - the actual cache's code. Must implement ICache
  • YourCacheConfig - a factory for creating an instance of your cache from a given policy. Must inherit from CacheConfig.
  • YourCachePolicy - the policy for your cache - any parameters you need for constructing your cache e.g. eviction policy, connection details. Must be serializable to from JSON.

You will then be able to configure usage of your cache like so:

<PubComp>
<CacheConfig>
<addname="cacheName"assembly="YourAssembly"type="YourType"policy="JSON goes here" />
</CacheConfig>
</PubComp>

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

228 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

pub-comp/caching (PubComp.Caching)

This project enables simple plug and play caching with various implementations and enables you to add your own implemenations.

Topics

NuGets

(available at nuget.org)

The core - ICache, CacheManager, LayeredCache: PubComp.Caching.Core

InMemoryCache based on System.Runtime.Caching.ObjectCache: PubComp.Caching.SystemRuntime

RedisCache based on StackExchange.Redis: PubComp.Caching.RedisCaching

AOP (Aspect Oriented Programming) caching based on PostSharp: PubComp.Caching.AopCaching

Basic Usage

You can access the cache directly via its interface:

TryGet

ICachecache=newRedisCache("myRemoteCache",newRedisCachePolicy());MyDatadata;if(!cache.TryGet("myKey",outdata)){data=FallbackMethodToRun();cache.Set("myKey",data);}

Get

or

MyDatadata=cache.Get("myKey",()=>FallbackMethodToRun());

this single line of code above is equivalent to the multiple lines of code in the above TryGet example in the above usage, the method will be run only if the requested key is not in the cache (on cache miss).

AOP Wrapper

or use a PostSharp based aspect to wrap a method with cache:

[Cache]MyDataAMethodThatNeedsCaching(stringparameter1,intparameter2){// Some long running operation}

calling the above method (e.g. with parameter values "one", 2) will be equivalent to calling a non aspect declared method with:

varkey="{"ClassName":"MyNameSpace.MyClassName","MethodName":"MyMethod","ParameterTypeNames":["System.String","System.Int32"],"ParmaterValues":["one",2]}";
MyDataresult=myCache.Get(key,()=>MyMethod("one",2));

Clear Item

You can clear a specific item from the cache:

myCache.Clear("myKey");

Clear All

or clear the entire cache (all items):

myCache.ClearAll();

If you have multiple caches, clearing one will NOT affect the other e.g.

ICachecache1=newInMemoryCache("myLocalCache",newInMemoryPolicy());ICachecache2=newInMemoryCache("myLocalCache",newInMemoryPolicy());cache1.ClearAll();// Only cache1 is cleared

Cache Types And Policies

You can create instances of cache from different types cache e.g. InMemoryCache, RedisCache and instances of the same type with different policies e.g. no automatic expiration, expiration from add, sliding expiration (LRU - Least Recently Used).

When you create a cache, you pass a name and the policy.

You can use the CacheManager to get a cache with a specific name e.g.

varcache=CacheManager.GetCache("myLocalCache");

Cache Management

A cache can be created programmatically:

varcache=newInMemoryCache("myOtherLocalCache",newInMemoryPolicy());

or received from the CacheManager which can be configured via code:

CacheManager.SetCache("noCache*",newNoCache("noCache"));varcache=CacheManager.GetCache("noCache1");

or via config file:

 <configSections>
<sectionGroupname="PubComp">
<sectionname="CacheConfig"type="PubComp.Caching.Core.CacheConfigurationHandler, PubComp.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"allowLocation="true"allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
<PubComp>
<CacheConfig>
<!-- You can either use ' or &quot; in the policy JSON --><!-- Each cache can be of a different type e.g. local (PubComp.Caching.SystemRuntime) or Redis (PubComp.Caching.RedisCaching.RedisCache) and with a different policy --><!-- Each cache type can have various policies e.g. ExpirationFromAdd, SlidingExpiration --><!-- You can create your own cache type, just direct the config to your assembly and class -->
<addname="localCache"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'ExpirationFromAdd':'01:00:00'}" />
<addname="localCacheLRU"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'SlidingExpiration':'00:15:00'}" />
<!-- policy="{&quot;ExpirationFromAdd&quot;:&quot;'01:00:00&quot;}" would work too -->
</CacheConfig>
</PubComp>

Using A Defined Cache

You can request a cache, by name, from the CacheManager:

varcache=CacheManager.GetCache("localCacheLRU");

or via AOP (Aspect Oriented Programming)...

Wrap a method with cache, so that the underlaying method is only called on cache misses:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

(*) When using AOP, the aspect only requests the cache from CacheManager once, for optimization, therefore changing the registeration of the cache (using SetCache won't have an effect later on).

You can also cache an IList of items, each time running the underlaying method only for the missing keys:

AOP Caching

Using AOP, you can easily wrap a method with a lazy-loading cache:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

The return value will be cached under a unique key, built from the class' full name (namespace and name), the method name, the parameter types and the parameter value.

so that the following methods will not have the same key:

[Cache("localCache")]privateList<Item>MethodToCache(stringx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(intx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(bytex){// data retrieval goes here}

Getting The Key

You can get the key used by the Cache aspect (CacheAttribute), in order to enable manual clearing of the data like so:

varkey=CacheKey.GetKey(()=>service.MethodToCache("parameterValue"));

and then clear as usual:

CacheManager.GetCache("localCache").Clear(key);

Caching Multiple Items By Key

You can use the CacheList aspect (CacheListAttribute) to cache data item by item. When using this way, the underlaying method (e.g. DB access) is called only for keys missing in the cache (and only if any are missing).

Example:

// Both IDs parameter and return type have to be of type IList<>[CacheList(typeof(MockDataKeyConverter))]publicIList<MockData>GetItems(IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}// Example data typepublicclassMockData{publicstringId{get;set;}publicstringValue{get;set;}}// This class enables the aspect, given a result what its key (ID) ispublicclassMockDataKeyConverter:IDataKeyConverter<string,MockData>{publicstringGetKey(MockDatadata){returndata.Id;}}

If the keys are not the first parameter of the method, use keyParameterNumber to instruct the aspect which parameter to use (0-based):

[CacheList(typeof(MockDataKeyConverter),keyParameterNumber=1)]publicIList<MockData>GetItems(stringparameter0,IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}

Ignoring Specific Parameters

You can instruct the aspects (Cache and CacheList) to ignore a specific parameter when generating the cache-key using the DoNotIncludeInCacheKey attribute (DoNotIncludeInCacheKeyAttribute):

[Cache]publicstringMethodToCache1(intid,[DoNotIncludeInCacheKey]objectobj){}

Extending With Your Own Classes

You can extend this project with you own classes by creating an assembly (a class library project) with the following classes:

Adding Your Own Cache Type

  • YourCache - the actual cache's code. Must implement ICache
  • YourCacheConfig - a factory for creating an instance of your cache from a given policy. Must inherit from CacheConfig.
  • YourCachePolicy - the policy for your cache - any parameters you need for constructing your cache e.g. eviction policy, connection details. Must be serializable to from JSON.

You will then be able to configure usage of your cache like so:

<PubComp>
<CacheConfig>
<addname="cacheName"assembly="YourAssembly"type="YourType"policy="JSON goes here" />
</CacheConfig>
</PubComp>

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

228 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

pub-comp/caching (PubComp.Caching)

This project enables simple plug and play caching with various implementations and enables you to add your own implemenations.

Topics

NuGets

(available at nuget.org)

The core - ICache, CacheManager, LayeredCache: PubComp.Caching.Core

InMemoryCache based on System.Runtime.Caching.ObjectCache: PubComp.Caching.SystemRuntime

RedisCache based on StackExchange.Redis: PubComp.Caching.RedisCaching

AOP (Aspect Oriented Programming) caching based on PostSharp: PubComp.Caching.AopCaching

Basic Usage

You can access the cache directly via its interface:

TryGet

ICachecache=newRedisCache("myRemoteCache",newRedisCachePolicy());MyDatadata;if(!cache.TryGet("myKey",outdata)){data=FallbackMethodToRun();cache.Set("myKey",data);}

Get

or

MyDatadata=cache.Get("myKey",()=>FallbackMethodToRun());

this single line of code above is equivalent to the multiple lines of code in the above TryGet example in the above usage, the method will be run only if the requested key is not in the cache (on cache miss).

AOP Wrapper

or use a PostSharp based aspect to wrap a method with cache:

[Cache]MyDataAMethodThatNeedsCaching(stringparameter1,intparameter2){// Some long running operation}

calling the above method (e.g. with parameter values "one", 2) will be equivalent to calling a non aspect declared method with:

varkey="{"ClassName":"MyNameSpace.MyClassName","MethodName":"MyMethod","ParameterTypeNames":["System.String","System.Int32"],"ParmaterValues":["one",2]}";
MyDataresult=myCache.Get(key,()=>MyMethod("one",2));

Clear Item

You can clear a specific item from the cache:

myCache.Clear("myKey");

Clear All

or clear the entire cache (all items):

myCache.ClearAll();

If you have multiple caches, clearing one will NOT affect the other e.g.

ICachecache1=newInMemoryCache("myLocalCache",newInMemoryPolicy());ICachecache2=newInMemoryCache("myLocalCache",newInMemoryPolicy());cache1.ClearAll();// Only cache1 is cleared

Cache Types And Policies

You can create instances of cache from different types cache e.g. InMemoryCache, RedisCache and instances of the same type with different policies e.g. no automatic expiration, expiration from add, sliding expiration (LRU - Least Recently Used).

When you create a cache, you pass a name and the policy.

You can use the CacheManager to get a cache with a specific name e.g.

varcache=CacheManager.GetCache("myLocalCache");

Cache Management

A cache can be created programmatically:

varcache=newInMemoryCache("myOtherLocalCache",newInMemoryPolicy());

or received from the CacheManager which can be configured via code:

CacheManager.SetCache("noCache*",newNoCache("noCache"));varcache=CacheManager.GetCache("noCache1");

or via config file:

 <configSections>
<sectionGroupname="PubComp">
<sectionname="CacheConfig"type="PubComp.Caching.Core.CacheConfigurationHandler, PubComp.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"allowLocation="true"allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
<PubComp>
<CacheConfig>
<!-- You can either use ' or &quot; in the policy JSON --><!-- Each cache can be of a different type e.g. local (PubComp.Caching.SystemRuntime) or Redis (PubComp.Caching.RedisCaching.RedisCache) and with a different policy --><!-- Each cache type can have various policies e.g. ExpirationFromAdd, SlidingExpiration --><!-- You can create your own cache type, just direct the config to your assembly and class -->
<addname="localCache"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'ExpirationFromAdd':'01:00:00'}" />
<addname="localCacheLRU"assembly="PubComp.Caching.SystemRuntime"type="PubComp.Caching.SystemRuntime.InMemoryCache"policy="{'SlidingExpiration':'00:15:00'}" />
<!-- policy="{&quot;ExpirationFromAdd&quot;:&quot;'01:00:00&quot;}" would work too -->
</CacheConfig>
</PubComp>

Using A Defined Cache

You can request a cache, by name, from the CacheManager:

varcache=CacheManager.GetCache("localCacheLRU");

or via AOP (Aspect Oriented Programming)...

Wrap a method with cache, so that the underlaying method is only called on cache misses:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

(*) When using AOP, the aspect only requests the cache from CacheManager once, for optimization, therefore changing the registeration of the cache (using SetCache won't have an effect later on).

You can also cache an IList of items, each time running the underlaying method only for the missing keys:

AOP Caching

Using AOP, you can easily wrap a method with a lazy-loading cache:

[Cache("localCache")]privateList<Item>MethodToCache(){// data retrieval goes here}

The return value will be cached under a unique key, built from the class' full name (namespace and name), the method name, the parameter types and the parameter value.

so that the following methods will not have the same key:

[Cache("localCache")]privateList<Item>MethodToCache(stringx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(intx){// data retrieval goes here}[Cache("localCache")]privateList<Item>MethodToCache(bytex){// data retrieval goes here}

Getting The Key

You can get the key used by the Cache aspect (CacheAttribute), in order to enable manual clearing of the data like so:

varkey=CacheKey.GetKey(()=>service.MethodToCache("parameterValue"));

and then clear as usual:

CacheManager.GetCache("localCache").Clear(key);

Caching Multiple Items By Key

You can use the CacheList aspect (CacheListAttribute) to cache data item by item. When using this way, the underlaying method (e.g. DB access) is called only for keys missing in the cache (and only if any are missing).

Example:

// Both IDs parameter and return type have to be of type IList<>[CacheList(typeof(MockDataKeyConverter))]publicIList<MockData>GetItems(IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}// Example data typepublicclassMockData{publicstringId{get;set;}publicstringValue{get;set;}}// This class enables the aspect, given a result what its key (ID) ispublicclassMockDataKeyConverter:IDataKeyConverter<string,MockData>{publicstringGetKey(MockDatadata){returndata.Id;}}

If the keys are not the first parameter of the method, use keyParameterNumber to instruct the aspect which parameter to use (0-based):

[CacheList(typeof(MockDataKeyConverter),keyParameterNumber=1)]publicIList<MockData>GetItems(stringparameter0,IList<string>keys){using(varcontext=newMyDbContext()){returncontent.MyData.Where(d =>keys.Contains(d.Id)).ToList();}}

Ignoring Specific Parameters

You can instruct the aspects (Cache and CacheList) to ignore a specific parameter when generating the cache-key using the DoNotIncludeInCacheKey attribute (DoNotIncludeInCacheKeyAttribute):

[Cache]publicstringMethodToCache1(intid,[DoNotIncludeInCacheKey]objectobj){}

Extending With Your Own Classes

You can extend this project with you own classes by creating an assembly (a class library project) with the following classes:

Adding Your Own Cache Type

  • YourCache - the actual cache's code. Must implement ICache
  • YourCacheConfig - a factory for creating an instance of your cache from a given policy. Must inherit from CacheConfig.
  • YourCachePolicy - the policy for your cache - any parameters you need for constructing your cache e.g. eviction policy, connection details. Must be serializable to from JSON.

You will then be able to configure usage of your cache like so:

<PubComp>
<CacheConfig>
<addname="cacheName"assembly="YourAssembly"type="YourType"policy="JSON goes here" />
</CacheConfig>
</PubComp>

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages