cachel 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,21 @@ Offline-first asset caching for the browser, powered by IndexedDB.
4
4
 
5
5
  Fetch remote assets once, serve them forever from local cache. Works with any framework or none at all.
6
6
 
7
+ ![npm](https://img.shields.io/npm/v/cachel)
8
+ ![license](https://img.shields.io/npm/l/cachel)
9
+
10
+ ---
11
+
12
+ ## Features
13
+
14
+ - Fetch and cache remote assets in IndexedDB with one call
15
+ - Batch cache multiple assets with controlled concurrency via `loadMany`
16
+ - Serve cached assets as object URLs, works fully offline
17
+ - Skips network requests for already cached assets
18
+ - Supports images, videos, audio and fonts
19
+ - No service worker required
20
+ - Zero dependencies
21
+
7
22
  ---
8
23
 
9
24
  ## Install
@@ -59,6 +74,34 @@ Throws if the fetch fails or the content type is not supported.
59
74
 
60
75
  ---
61
76
 
77
+ ### `cache.loadMany(urls, chunkSize?)`
78
+
79
+ Fetches and caches multiple assets in controlled parallel chunks. Returns a status object with results, success count, failed count, and time elapsed in milliseconds.
80
+
81
+ ```javascript
82
+ const status = await cache.loadMany([
83
+ 'https://example.com/logo.png',
84
+ 'https://example.com/hero.jpg',
85
+ 'https://example.com/font.woff2'
86
+ ]);
87
+
88
+ console.log(status);
89
+ // {
90
+ // results: [...], // raw Promise.allSettled results
91
+ // success: 2,
92
+ // failed: 1,
93
+ // timeElapsed: 1240 // in milliseconds
94
+ // }
95
+ ```
96
+
97
+ `chunkSize` controls how many assets are fetched in parallel per round. Defaults to `8`, max is `8`. Assets already cached are skipped automatically.
98
+
99
+ ```javascript
100
+ await cache.loadMany(urls, 4); // 4 parallel fetches per round
101
+ ```
102
+
103
+ ---
104
+
62
105
  ### `cache.get(url)`
63
106
 
64
107
  Retrieves a cached asset and returns it as an object URL. Returns `null` if not found.
package/cachel.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import Idb from "./lib/idb.js";
2
- import convertToBlob from './utils/blob.js';
2
+ import { convertToBlob, chunkify } from './utils/index.js';
3
3
 
4
4
  class Cachel {
5
5
 
6
6
  #idb;
7
+ #defaultChunkSize = 8;
7
8
 
8
9
  constructor(name = 'idb'){
9
10
  this.#idb = new Idb(name);
@@ -56,6 +57,30 @@ class Cachel {
56
57
  return this.#idb.clear();
57
58
  }
58
59
 
60
+ async loadMany(urls, chunkSize = this.#defaultChunkSize){
61
+ if(!(Array.isArray(urls) && urls.length)) return;
62
+ if(chunkSize > this.#defaultChunkSize){
63
+ console.warn(`cachel: supplied chunkSize is greater than max permissible chunk size; clamping the size to ${this.#defaultChunkSize}`);
64
+ }
65
+ chunkSize = Math.min(Math.max(1, chunkSize), this.#defaultChunkSize);
66
+ const chunks = chunkify(urls, chunkSize);
67
+ const results = [];
68
+ const startTime = performance.now();
69
+ for(const chunk of chunks){
70
+ const chunkResults = await Promise.allSettled(chunk.map(url => this.load(url)));
71
+ results.push(...chunkResults);
72
+ }
73
+ const timeElapsed = (performance.now() - startTime);
74
+ const success = results.filter(result => result.status === 'fulfilled').length;
75
+ const status = {
76
+ results,
77
+ success,
78
+ failed: results.length - success,
79
+ timeElapsed
80
+ }
81
+ return status;
82
+ }
83
+
59
84
  }
60
85
 
61
86
  export default Cachel;
package/lib/idb.js CHANGED
@@ -1,4 +1,4 @@
1
- import promisify from "../utils/promisify.js";
1
+ import { promisify } from "../utils/index.js";
2
2
 
3
3
  class Idb {
4
4
  #name = null;
package/package.json CHANGED
@@ -1,12 +1,25 @@
1
1
  {
2
2
  "name": "cachel",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "main": "cachel.js",
5
- "files": [ "cachel.js", "lib/", "utils/", "README.md", "LICENSE" ],
5
+ "files": [
6
+ "cachel.js",
7
+ "lib/",
8
+ "utils/",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
6
12
  "scripts": {
7
13
  "dev": "live-server"
8
14
  },
9
- "keywords": ["cache", "indexeddb", "offline", "pwa", "blob", "assets"],
15
+ "keywords": [
16
+ "cache",
17
+ "indexeddb",
18
+ "offline",
19
+ "pwa",
20
+ "blob",
21
+ "assets"
22
+ ],
10
23
  "author": {
11
24
  "name": "Geet Trivedi",
12
25
  "url": "https://www.geettrivedi.com"
@@ -17,4 +30,4 @@
17
30
  "devDependencies": {
18
31
  "live-server": "^1.2.2"
19
32
  }
20
- }
33
+ }
@@ -0,0 +1,10 @@
1
+ const chunkify = (arr, chunkSize) => arr.reduce((chunksArr, item, index) => {
2
+ const chunkIndex = Math.floor(index/chunkSize);
3
+ if(!chunksArr[chunkIndex]){
4
+ chunksArr[chunkIndex] = [];
5
+ }
6
+ chunksArr[chunkIndex].push(item);
7
+ return chunksArr;
8
+ }, []);
9
+
10
+ export default chunkify;
@@ -1,3 +1,4 @@
1
+ const allowedContentTypes = ['image/', 'video/', 'audio/', 'font/'];
1
2
  const convertToBlob = async url => {
2
3
  try{
3
4
  const response = await fetch(url);
@@ -7,7 +8,6 @@ const convertToBlob = async url => {
7
8
  }
8
9
 
9
10
  const contentType = response.headers.get('content-type');
10
- const allowedContentTypes = ['image/', 'video/', 'audio/', 'font/'];
11
11
  const isContentTypeAllowed = allowedContentTypes.some(type => contentType.startsWith(type));
12
12
 
13
13
  if(!isContentTypeAllowed){
package/utils/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as promisify } from "./promisify.js";
2
+ export { default as convertToBlob } from "./convertToBlob.js";
3
+ export { default as chunkify } from "./chunkify.js";