@effect-native/fetch-hooks 0.0.1-placeholder → 0.0.2
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/LICENSE +22 -0
- package/README.md +35 -0
- package/dist/binary-extractor.d.ts +9 -0
- package/dist/binary-extractor.d.ts.map +1 -0
- package/dist/binary-extractor.js +85 -0
- package/dist/binary-extractor.js.map +1 -0
- package/dist/cache-manager.d.ts +8 -0
- package/dist/cache-manager.d.ts.map +1 -0
- package/dist/cache-manager.js +408 -0
- package/dist/cache-manager.js.map +1 -0
- package/dist/environment.d.ts +5 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +25 -0
- package/dist/environment.js.map +1 -0
- package/dist/filesystem-storage.d.ts +10 -0
- package/dist/filesystem-storage.d.ts.map +1 -0
- package/dist/filesystem-storage.js +112 -0
- package/dist/filesystem-storage.js.map +1 -0
- package/dist/flat-file-storage.d.ts +33 -0
- package/dist/flat-file-storage.d.ts.map +1 -0
- package/dist/flat-file-storage.js +153 -0
- package/dist/flat-file-storage.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +183 -0
- package/dist/index.js.map +1 -0
- package/dist/request-hasher.d.ts +5 -0
- package/dist/request-hasher.d.ts.map +1 -0
- package/dist/request-hasher.js +74 -0
- package/dist/request-hasher.js.map +1 -0
- package/dist/sse-handler.d.ts +9 -0
- package/dist/sse-handler.d.ts.map +1 -0
- package/dist/sse-handler.js +225 -0
- package/dist/sse-handler.js.map +1 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -3
- package/src/binary-extractor.ts +104 -0
- package/src/cache-manager.ts +499 -0
- package/src/environment.ts +27 -0
- package/src/filesystem-storage.ts +125 -0
- package/src/flat-file-storage.ts +170 -0
- package/src/index.ts +249 -0
- package/src/request-hasher.ts +86 -0
- package/src/sse-handler.ts +281 -0
- package/src/types.ts +140 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CacheKey, CacheStorage, KV, KVStream, TimedChunk } from "./types.js";
|
|
2
|
+
/** Create a KV store backed by JSON files */
|
|
3
|
+
export declare function createJsonFileKV<T>(baseDir: string, filename: string): KV<CacheKey, T>;
|
|
4
|
+
/** Create a KV store backed by binary files */
|
|
5
|
+
export declare function createBinaryFileKV(baseDir: string, filename: string): KV<CacheKey, Uint8Array>;
|
|
6
|
+
/** Create a streaming KV store for SSE chunks backed by JSONL files */
|
|
7
|
+
export declare function createJsonlFileKVStream(baseDir: string, filename: string): KVStream<CacheKey, TimedChunk>;
|
|
8
|
+
/** Create a complete CacheStorage backed by the filesystem */
|
|
9
|
+
export declare function createFilesystemStorage(baseDir: string): CacheStorage;
|
|
10
|
+
//# sourceMappingURL=filesystem-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem-storage.d.ts","sourceRoot":"","sources":["../src/filesystem-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqC,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAqBrH,6CAA6C;AAC7C,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,CAsBtF;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,CAsB9F;AAED,uEAAuE;AACvE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAyCzG;AAED,8DAA8D;AAC9D,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAQrE"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { jsonlToTimedChunks, timedChunksToJsonl } from "./sse-handler.js";
|
|
4
|
+
function ensureCacheDir(baseDir, cacheKey) {
|
|
5
|
+
const cacheDir = join(baseDir, cacheKey);
|
|
6
|
+
if (!existsSync(cacheDir)) {
|
|
7
|
+
try {
|
|
8
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
// Handle race condition where directory was created between check and mkdir
|
|
12
|
+
if (!(error instanceof Error && error.code === "EEXIST")) {
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return cacheDir;
|
|
18
|
+
}
|
|
19
|
+
/** Create a KV store backed by JSON files */
|
|
20
|
+
export function createJsonFileKV(baseDir, filename) {
|
|
21
|
+
return {
|
|
22
|
+
async get([key]) {
|
|
23
|
+
const filePath = join(baseDir, key, filename);
|
|
24
|
+
if (!existsSync(filePath)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const content = readFileSync(filePath, "utf-8");
|
|
28
|
+
return JSON.parse(content);
|
|
29
|
+
},
|
|
30
|
+
async set([key], value) {
|
|
31
|
+
const cacheDir = ensureCacheDir(baseDir, key);
|
|
32
|
+
const filePath = join(cacheDir, filename);
|
|
33
|
+
writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
34
|
+
},
|
|
35
|
+
async has([key]) {
|
|
36
|
+
const filePath = join(baseDir, key, filename);
|
|
37
|
+
return existsSync(filePath);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Create a KV store backed by binary files */
|
|
42
|
+
export function createBinaryFileKV(baseDir, filename) {
|
|
43
|
+
return {
|
|
44
|
+
async get([key]) {
|
|
45
|
+
const filePath = join(baseDir, key, filename);
|
|
46
|
+
if (!existsSync(filePath)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const buffer = readFileSync(filePath);
|
|
50
|
+
return new Uint8Array(buffer);
|
|
51
|
+
},
|
|
52
|
+
async set([key], value) {
|
|
53
|
+
const cacheDir = ensureCacheDir(baseDir, key);
|
|
54
|
+
const filePath = join(cacheDir, filename);
|
|
55
|
+
writeFileSync(filePath, value);
|
|
56
|
+
},
|
|
57
|
+
async has([key]) {
|
|
58
|
+
const filePath = join(baseDir, key, filename);
|
|
59
|
+
return existsSync(filePath);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Create a streaming KV store for SSE chunks backed by JSONL files */
|
|
64
|
+
export function createJsonlFileKVStream(baseDir, filename) {
|
|
65
|
+
return {
|
|
66
|
+
async *get([key]) {
|
|
67
|
+
const filePath = join(baseDir, key, filename);
|
|
68
|
+
if (!existsSync(filePath)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Read entire file and parse - streaming line-by-line is complex due to JSONL format
|
|
72
|
+
// Future optimization: could stream lines and parse incrementally
|
|
73
|
+
const content = readFileSync(filePath, "utf-8");
|
|
74
|
+
const chunks = jsonlToTimedChunks(content);
|
|
75
|
+
for (const chunk of chunks) {
|
|
76
|
+
yield chunk;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
async set([key], values) {
|
|
80
|
+
const cacheDir = ensureCacheDir(baseDir, key);
|
|
81
|
+
const filePath = join(cacheDir, filename);
|
|
82
|
+
// Collect all values if async iterable
|
|
83
|
+
let chunks;
|
|
84
|
+
if (Array.isArray(values)) {
|
|
85
|
+
chunks = values;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
chunks = [];
|
|
89
|
+
for await (const chunk of values) {
|
|
90
|
+
chunks.push(chunk);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const jsonl = timedChunksToJsonl(chunks);
|
|
94
|
+
writeFileSync(filePath, jsonl, "utf-8");
|
|
95
|
+
},
|
|
96
|
+
async has([key]) {
|
|
97
|
+
const filePath = join(baseDir, key, filename);
|
|
98
|
+
return existsSync(filePath);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Create a complete CacheStorage backed by the filesystem */
|
|
103
|
+
export function createFilesystemStorage(baseDir) {
|
|
104
|
+
return {
|
|
105
|
+
requests: createJsonFileKV(baseDir, "request.json"),
|
|
106
|
+
responseMeta: createJsonFileKV(baseDir, "response.meta.json"),
|
|
107
|
+
responseBody: createJsonFileKV(baseDir, "response.json"),
|
|
108
|
+
binaryBody: createBinaryFileKV(baseDir, "response.bin"),
|
|
109
|
+
sseChunks: createJsonlFileKVStream(baseDir, "response.jsonl")
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=filesystem-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem-storage.js","sourceRoot":"","sources":["../src/filesystem-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flat file storage - stores cache files in subdirectories named by the key.
|
|
3
|
+
* Each key gets its own folder with standard cache filenames inside:
|
|
4
|
+
* baseDir/001/request.json
|
|
5
|
+
* baseDir/001/response.meta.json
|
|
6
|
+
* baseDir/001/response.jsonl
|
|
7
|
+
* baseDir/002/request.json
|
|
8
|
+
* ...
|
|
9
|
+
*
|
|
10
|
+
* This is similar to filesystem-storage but the key IS the subdirectory name
|
|
11
|
+
* (no hashing), making it suitable for conversation turn folders.
|
|
12
|
+
*/
|
|
13
|
+
import type { CacheKey, CacheStorage, KV, KVStream, TimedChunk } from "./types.js";
|
|
14
|
+
/** Create a KV store backed by JSON files in subdirectories: baseDir/{key}/{filename} */
|
|
15
|
+
export declare function createFlatJsonFileKV<T>(baseDir: string, filename: string): KV<CacheKey, T>;
|
|
16
|
+
/** Create a KV store backed by plain text files in subdirectories (no JSON serialization) */
|
|
17
|
+
export declare function createFlatTextFileKV(baseDir: string, filename: string): KV<CacheKey, string>;
|
|
18
|
+
/** Create a KV store backed by binary files in subdirectories */
|
|
19
|
+
export declare function createFlatBinaryFileKV(baseDir: string, filename: string): KV<CacheKey, Uint8Array>;
|
|
20
|
+
/** Create a streaming KV store for SSE chunks in subdirectories */
|
|
21
|
+
export declare function createFlatJsonlFileKVStream(baseDir: string, filename: string): KVStream<CacheKey, TimedChunk>;
|
|
22
|
+
/**
|
|
23
|
+
* Create a CacheStorage that stores files in subdirectories named by the key.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const storage = createFlatFileStorage("/path/to/conversation")
|
|
27
|
+
* // With transformCacheKey returning "001", files will be:
|
|
28
|
+
* // /path/to/conversation/001/request.json
|
|
29
|
+
* // /path/to/conversation/001/response.meta.json
|
|
30
|
+
* // /path/to/conversation/001/response.jsonl
|
|
31
|
+
*/
|
|
32
|
+
export declare function createFlatFileStorage(baseDir: string): CacheStorage;
|
|
33
|
+
//# sourceMappingURL=flat-file-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flat-file-storage.d.ts","sourceRoot":"","sources":["../src/flat-file-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAqC,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAkBrH,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,CAuB1F;AAED,6FAA6F;AAC7F,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAsB5F;AAED,iEAAiE;AACjE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,CAuBlG;AAED,mEAAmE;AACnE,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAuC7G;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CASnE"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flat file storage - stores cache files in subdirectories named by the key.
|
|
3
|
+
* Each key gets its own folder with standard cache filenames inside:
|
|
4
|
+
* baseDir/001/request.json
|
|
5
|
+
* baseDir/001/response.meta.json
|
|
6
|
+
* baseDir/001/response.jsonl
|
|
7
|
+
* baseDir/002/request.json
|
|
8
|
+
* ...
|
|
9
|
+
*
|
|
10
|
+
* This is similar to filesystem-storage but the key IS the subdirectory name
|
|
11
|
+
* (no hashing), making it suitable for conversation turn folders.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { jsonlToTimedChunks, timedChunksToJsonl } from "./sse-handler.js";
|
|
16
|
+
function ensureDir(dir) {
|
|
17
|
+
if (!existsSync(dir)) {
|
|
18
|
+
try {
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!(error instanceof Error && error.code === "EEXIST")) {
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Create a KV store backed by JSON files in subdirectories: baseDir/{key}/{filename} */
|
|
29
|
+
export function createFlatJsonFileKV(baseDir, filename) {
|
|
30
|
+
return {
|
|
31
|
+
async get([key]) {
|
|
32
|
+
const filePath = join(baseDir, key, filename);
|
|
33
|
+
if (!existsSync(filePath)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const content = readFileSync(filePath, "utf-8");
|
|
37
|
+
return JSON.parse(content);
|
|
38
|
+
},
|
|
39
|
+
async set([key], value) {
|
|
40
|
+
const dir = join(baseDir, key);
|
|
41
|
+
ensureDir(dir);
|
|
42
|
+
const filePath = join(dir, filename);
|
|
43
|
+
writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
44
|
+
},
|
|
45
|
+
async has([key]) {
|
|
46
|
+
const filePath = join(baseDir, key, filename);
|
|
47
|
+
return existsSync(filePath);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Create a KV store backed by plain text files in subdirectories (no JSON serialization) */
|
|
52
|
+
export function createFlatTextFileKV(baseDir, filename) {
|
|
53
|
+
return {
|
|
54
|
+
async get([key]) {
|
|
55
|
+
const filePath = join(baseDir, key, filename);
|
|
56
|
+
if (!existsSync(filePath)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return readFileSync(filePath, "utf-8");
|
|
60
|
+
},
|
|
61
|
+
async set([key], value) {
|
|
62
|
+
const dir = join(baseDir, key);
|
|
63
|
+
ensureDir(dir);
|
|
64
|
+
const filePath = join(dir, filename);
|
|
65
|
+
writeFileSync(filePath, value, "utf-8");
|
|
66
|
+
},
|
|
67
|
+
async has([key]) {
|
|
68
|
+
const filePath = join(baseDir, key, filename);
|
|
69
|
+
return existsSync(filePath);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Create a KV store backed by binary files in subdirectories */
|
|
74
|
+
export function createFlatBinaryFileKV(baseDir, filename) {
|
|
75
|
+
return {
|
|
76
|
+
async get([key]) {
|
|
77
|
+
const filePath = join(baseDir, key, filename);
|
|
78
|
+
if (!existsSync(filePath)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const buffer = readFileSync(filePath);
|
|
82
|
+
return new Uint8Array(buffer);
|
|
83
|
+
},
|
|
84
|
+
async set([key], value) {
|
|
85
|
+
const dir = join(baseDir, key);
|
|
86
|
+
ensureDir(dir);
|
|
87
|
+
const filePath = join(dir, filename);
|
|
88
|
+
writeFileSync(filePath, value);
|
|
89
|
+
},
|
|
90
|
+
async has([key]) {
|
|
91
|
+
const filePath = join(baseDir, key, filename);
|
|
92
|
+
return existsSync(filePath);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Create a streaming KV store for SSE chunks in subdirectories */
|
|
97
|
+
export function createFlatJsonlFileKVStream(baseDir, filename) {
|
|
98
|
+
return {
|
|
99
|
+
async *get([key]) {
|
|
100
|
+
const filePath = join(baseDir, key, filename);
|
|
101
|
+
if (!existsSync(filePath)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const content = readFileSync(filePath, "utf-8");
|
|
105
|
+
const chunks = jsonlToTimedChunks(content);
|
|
106
|
+
for (const chunk of chunks) {
|
|
107
|
+
yield chunk;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async set([key], values) {
|
|
111
|
+
const dir = join(baseDir, key);
|
|
112
|
+
ensureDir(dir);
|
|
113
|
+
const filePath = join(dir, filename);
|
|
114
|
+
let chunks;
|
|
115
|
+
if (Array.isArray(values)) {
|
|
116
|
+
chunks = values;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
chunks = [];
|
|
120
|
+
for await (const chunk of values) {
|
|
121
|
+
chunks.push(chunk);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const jsonl = timedChunksToJsonl(chunks);
|
|
125
|
+
writeFileSync(filePath, jsonl, "utf-8");
|
|
126
|
+
},
|
|
127
|
+
async has([key]) {
|
|
128
|
+
const filePath = join(baseDir, key, filename);
|
|
129
|
+
return existsSync(filePath);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Create a CacheStorage that stores files in subdirectories named by the key.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* const storage = createFlatFileStorage("/path/to/conversation")
|
|
138
|
+
* // With transformCacheKey returning "001", files will be:
|
|
139
|
+
* // /path/to/conversation/001/request.json
|
|
140
|
+
* // /path/to/conversation/001/response.meta.json
|
|
141
|
+
* // /path/to/conversation/001/response.jsonl
|
|
142
|
+
*/
|
|
143
|
+
export function createFlatFileStorage(baseDir) {
|
|
144
|
+
return {
|
|
145
|
+
requests: createFlatJsonFileKV(baseDir, "request.json"),
|
|
146
|
+
responseMeta: createFlatJsonFileKV(baseDir, "response.meta.json"),
|
|
147
|
+
// Use text storage for responseBody to preserve raw JSON without double-escaping
|
|
148
|
+
responseBody: createFlatTextFileKV(baseDir, "response.json"),
|
|
149
|
+
binaryBody: createFlatBinaryFileKV(baseDir, "response.bin"),
|
|
150
|
+
sseChunks: createFlatJsonlFileKVStream(baseDir, "response.jsonl")
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=flat-file-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flat-file-storage.js","sourceRoot":"","sources":["../src/flat-file-storage.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CacheOptions } from "./types.js";
|
|
2
|
+
export type { CacheKey, CacheOptions, CacheStorage, GeneratorTransformHook, HashableRequest, KV, KVStream, StorableResponse, TimedChunk, TransformHook } from "./types.js";
|
|
3
|
+
export { createFilesystemStorage } from "./filesystem-storage.js";
|
|
4
|
+
export { createFlatFileStorage } from "./flat-file-storage.js";
|
|
5
|
+
export declare function createCachedFetch(baseFetch: typeof globalThis.fetch, options?: CacheOptions): (input: Request | string | URL, init?: RequestInit) => Promise<Response>;
|
|
6
|
+
export declare function enableDevFetchCache(options?: CacheOptions): void;
|
|
7
|
+
export declare function disableDevFetchCache(): void;
|
|
8
|
+
export declare function isDevFetchCacheEnabled(): boolean;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,YAAY,CAAA;AAc5D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,eAAe,EACf,EAAE,EACF,QAAQ,EACR,gBAAgB,EAChB,UAAU,EACV,aAAa,EACd,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAsF9D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,OAAO,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,YAAY,IAYxD,OAAO,OAAO,GAAG,MAAM,GAAG,GAAG,EAAE,OAAO,WAAW,KAAG,OAAO,CAAC,QAAQ,CAAC,CAkFxG;AAED,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAwBhE;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAO3C;AAED,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { getCachedResponse, getCacheKeyFromUrl, setInternalFetchForCache, storeRequest, storeResponse } from "./cache-manager.js";
|
|
2
|
+
import { getCacheDir, isCacheEnabled, isProduction, isReplayOnly } from "./environment.js";
|
|
3
|
+
import { createFilesystemStorage } from "./filesystem-storage.js";
|
|
4
|
+
import { hashRequest, headersToRecord } from "./request-hasher.js";
|
|
5
|
+
// Export storage factories for custom implementations
|
|
6
|
+
export { createFilesystemStorage } from "./filesystem-storage.js";
|
|
7
|
+
export { createFlatFileStorage } from "./flat-file-storage.js";
|
|
8
|
+
const CACHE_ENABLED_BANNER = `
|
|
9
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
10
|
+
║ DEV-FETCH-CACHE ENABLED ║
|
|
11
|
+
║ Responses are being cached/replayed from .cache/fetch/ ║
|
|
12
|
+
║ To disable: DEV_FETCH_CACHE=0 or --no-fetch-cache ║
|
|
13
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
14
|
+
`;
|
|
15
|
+
let originalFetch = null;
|
|
16
|
+
function getUrlFromInput(input) {
|
|
17
|
+
if (typeof input === "string") {
|
|
18
|
+
return input;
|
|
19
|
+
}
|
|
20
|
+
if (input instanceof URL) {
|
|
21
|
+
return input.href;
|
|
22
|
+
}
|
|
23
|
+
return input.url;
|
|
24
|
+
}
|
|
25
|
+
function normalizeHeadersInit(headersInit) {
|
|
26
|
+
if (!headersInit) {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
if (headersInit instanceof Headers) {
|
|
30
|
+
return headersToRecord(headersInit);
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(headersInit)) {
|
|
33
|
+
return Object.fromEntries(headersInit);
|
|
34
|
+
}
|
|
35
|
+
return headersInit;
|
|
36
|
+
}
|
|
37
|
+
async function extractBodyInit(bodyInit) {
|
|
38
|
+
if (!bodyInit) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
if (typeof bodyInit === "string") {
|
|
42
|
+
return bodyInit;
|
|
43
|
+
}
|
|
44
|
+
if (bodyInit instanceof URLSearchParams) {
|
|
45
|
+
return bodyInit.toString();
|
|
46
|
+
}
|
|
47
|
+
if (bodyInit instanceof Blob) {
|
|
48
|
+
return bodyInit.text();
|
|
49
|
+
}
|
|
50
|
+
if (bodyInit instanceof FormData) {
|
|
51
|
+
const pairs = [];
|
|
52
|
+
for (const [key, value] of bodyInit.entries()) {
|
|
53
|
+
// FormDataEntryValue can be File or string; File needs to be converted to string
|
|
54
|
+
const stringValue = typeof value === "string" ? value : value.name;
|
|
55
|
+
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(stringValue)}`);
|
|
56
|
+
}
|
|
57
|
+
return pairs.join("&");
|
|
58
|
+
}
|
|
59
|
+
if (bodyInit instanceof ArrayBuffer) {
|
|
60
|
+
return new TextDecoder().decode(bodyInit);
|
|
61
|
+
}
|
|
62
|
+
if (ArrayBuffer.isView(bodyInit)) {
|
|
63
|
+
return new TextDecoder().decode(new Uint8Array(bodyInit.buffer, bodyInit.byteOffset, bodyInit.byteLength));
|
|
64
|
+
}
|
|
65
|
+
if (bodyInit instanceof ReadableStream) {
|
|
66
|
+
const reader = bodyInit.getReader();
|
|
67
|
+
const chunks = [];
|
|
68
|
+
while (true) {
|
|
69
|
+
const { done, value } = await reader.read();
|
|
70
|
+
if (done)
|
|
71
|
+
break;
|
|
72
|
+
if (value)
|
|
73
|
+
chunks.push(value);
|
|
74
|
+
}
|
|
75
|
+
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
76
|
+
const combined = new Uint8Array(totalLength);
|
|
77
|
+
let offset = 0;
|
|
78
|
+
for (const chunk of chunks) {
|
|
79
|
+
combined.set(chunk, offset);
|
|
80
|
+
offset += chunk.length;
|
|
81
|
+
}
|
|
82
|
+
return new TextDecoder().decode(combined);
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/** Default identity transform hook */
|
|
87
|
+
const identity = (value) => value;
|
|
88
|
+
export function createCachedFetch(baseFetch, options) {
|
|
89
|
+
const replayOnly = options?.replayOnly ?? isReplayOnly();
|
|
90
|
+
const beforeHash = options?.beforeHash ?? identity;
|
|
91
|
+
const beforeStoreRequest = options?.beforeStoreRequest ?? identity;
|
|
92
|
+
const transformCacheKey = options?.transformCacheKey ?? ((key) => key);
|
|
93
|
+
// Create or use provided storage
|
|
94
|
+
const storage = options?.storage ?? createFilesystemStorage(getCacheDir());
|
|
95
|
+
// Set internal fetch for dev-fs-logs communication (defaults to baseFetch)
|
|
96
|
+
setInternalFetchForCache(options?.internalFetch ?? baseFetch);
|
|
97
|
+
return async function cachedFetch(input, init) {
|
|
98
|
+
if (isProduction()) {
|
|
99
|
+
return baseFetch(input, init);
|
|
100
|
+
}
|
|
101
|
+
const url = getUrlFromInput(input);
|
|
102
|
+
// Skip caching for localhost requests (including dev-fs-logs on port 1090)
|
|
103
|
+
if (url.includes("localhost") || url.includes("127.0.0.1")) {
|
|
104
|
+
return baseFetch(input, init);
|
|
105
|
+
}
|
|
106
|
+
const method = init?.method ?? (typeof input === "object" && "method" in input ? input.method : "GET");
|
|
107
|
+
const cacheKeyFromUrl = getCacheKeyFromUrl(url);
|
|
108
|
+
if (cacheKeyFromUrl) {
|
|
109
|
+
const cached = await getCachedResponse(cacheKeyFromUrl, options?.afterLoadResponse, options?.transformSSEChunk, storage);
|
|
110
|
+
if (cached) {
|
|
111
|
+
return cached;
|
|
112
|
+
}
|
|
113
|
+
return new Response("Cache entry not found", {
|
|
114
|
+
status: 404
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const headersInit = init?.headers ?? (typeof input === "object" && "headers" in input ? input.headers : undefined);
|
|
118
|
+
const headers = normalizeHeadersInit(headersInit);
|
|
119
|
+
let body;
|
|
120
|
+
// Extract body from Request object or init
|
|
121
|
+
if (input instanceof Request && input.body) {
|
|
122
|
+
// Clone the request so we can read the body without consuming the original
|
|
123
|
+
const clonedRequest = input.clone();
|
|
124
|
+
body = await extractBodyInit(clonedRequest.body);
|
|
125
|
+
}
|
|
126
|
+
else if (init?.body !== null && init?.body !== undefined) {
|
|
127
|
+
body = await extractBodyInit(init.body);
|
|
128
|
+
}
|
|
129
|
+
// Apply beforeHash transform to affect cache key generation, then transformCacheKey for custom prefixes/suffixes
|
|
130
|
+
const hashableRequest = await beforeHash({ url, method, headers, body });
|
|
131
|
+
const cacheKey = transformCacheKey(hashRequest(hashableRequest), hashableRequest);
|
|
132
|
+
const cached = await getCachedResponse(cacheKey, options?.afterLoadResponse, options?.transformSSEChunk, storage);
|
|
133
|
+
if (cached) {
|
|
134
|
+
return cached;
|
|
135
|
+
}
|
|
136
|
+
if (replayOnly) {
|
|
137
|
+
return new Response("Cache miss in replay-only mode", {
|
|
138
|
+
status: 503
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const requestToStore = await beforeStoreRequest({ url, method, headers, body });
|
|
142
|
+
await storeRequest({
|
|
143
|
+
url: requestToStore.url,
|
|
144
|
+
method: requestToStore.method,
|
|
145
|
+
headers: requestToStore.headers,
|
|
146
|
+
body: requestToStore.body
|
|
147
|
+
}, cacheKey, options?.beforeStoreRequest !== undefined, // skip header filtering if hook provided
|
|
148
|
+
storage, hashableRequest);
|
|
149
|
+
const response = await baseFetch(input, init);
|
|
150
|
+
return storeResponse(cacheKey, response, options?.beforeStoreResponse, storage, hashableRequest);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export function enableDevFetchCache(options) {
|
|
154
|
+
if (isProduction()) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!isCacheEnabled()) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (originalFetch !== null) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
originalFetch = globalThis.fetch;
|
|
164
|
+
const cachedFetch = createCachedFetch(originalFetch, {
|
|
165
|
+
...options,
|
|
166
|
+
internalFetch: options?.internalFetch ?? originalFetch
|
|
167
|
+
});
|
|
168
|
+
// Cast to typeof fetch to include preconnect method
|
|
169
|
+
globalThis.fetch = cachedFetch;
|
|
170
|
+
// biome-ignore lint/suspicious/noConsole: Banner display is intentional for dev tooling
|
|
171
|
+
console.info(CACHE_ENABLED_BANNER);
|
|
172
|
+
}
|
|
173
|
+
export function disableDevFetchCache() {
|
|
174
|
+
if (originalFetch === null) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
globalThis.fetch = originalFetch;
|
|
178
|
+
originalFetch = null;
|
|
179
|
+
}
|
|
180
|
+
export function isDevFetchCacheEnabled() {
|
|
181
|
+
return originalFetch !== null;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RawCachedRequest } from "./types.js";
|
|
2
|
+
export declare function hashRequest({ body, headers, method, url }: RawCachedRequest): string;
|
|
3
|
+
export declare function getStorableHeaders(headers: Record<string, string>): Record<string, string>;
|
|
4
|
+
export declare function headersToRecord(headers: Headers): Record<string, string>;
|
|
5
|
+
//# sourceMappingURL=request-hasher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-hasher.d.ts","sourceRoot":"","sources":["../src/request-hasher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAyDlD,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,gBAAgB,GAAG,MAAM,CAepF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE1F;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
3
|
+
function toBase62(n) {
|
|
4
|
+
if (n === 0n)
|
|
5
|
+
return "0";
|
|
6
|
+
let result = "";
|
|
7
|
+
while (n > 0n) {
|
|
8
|
+
result = BASE62[Number(n % 62n)] + result;
|
|
9
|
+
n = n / 62n;
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
const SENSITIVE_HEADERS = new Set([
|
|
14
|
+
"authorization",
|
|
15
|
+
"x-api-key",
|
|
16
|
+
"api-key",
|
|
17
|
+
"x-openrouter-api-key",
|
|
18
|
+
"cookie",
|
|
19
|
+
"set-cookie",
|
|
20
|
+
"x-auth-token",
|
|
21
|
+
"x-access-token",
|
|
22
|
+
"bearer"
|
|
23
|
+
]);
|
|
24
|
+
function normalizeUrl(url) {
|
|
25
|
+
const parsed = new URL(url);
|
|
26
|
+
parsed.searchParams.sort();
|
|
27
|
+
const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
|
|
28
|
+
return `${parsed.protocol}//${parsed.host}${pathname}${parsed.search}`;
|
|
29
|
+
}
|
|
30
|
+
function filterHeaders(headers, getOutputKey) {
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
33
|
+
const lowerKey = key.toLowerCase();
|
|
34
|
+
if (SENSITIVE_HEADERS.has(lowerKey)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const outputKey = getOutputKey(key, lowerKey);
|
|
38
|
+
result[outputKey] = value;
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function normalizeHeaders(headers) {
|
|
43
|
+
return filterHeaders(headers, (_originalKey, lowerKey) => lowerKey);
|
|
44
|
+
}
|
|
45
|
+
function filterSensitiveHeaders(headers) {
|
|
46
|
+
return filterHeaders(headers, (originalKey) => originalKey);
|
|
47
|
+
}
|
|
48
|
+
export function hashRequest({ body, headers, method, url }) {
|
|
49
|
+
// Build content string for hashing
|
|
50
|
+
let content = normalizeUrl(url) + "\n" + method.toUpperCase();
|
|
51
|
+
const normalizedHeaders = normalizeHeaders(headers);
|
|
52
|
+
const sortedHeaderKeys = Object.keys(normalizedHeaders).sort();
|
|
53
|
+
for (const key of sortedHeaderKeys) {
|
|
54
|
+
content += `\n${key}:${normalizedHeaders[key]}`;
|
|
55
|
+
}
|
|
56
|
+
if (body) {
|
|
57
|
+
content += `\n${body}`;
|
|
58
|
+
}
|
|
59
|
+
// Create a SHA256 hash and convert to base62 for a short string
|
|
60
|
+
const hash = createHash("sha256").update(content).digest();
|
|
61
|
+
const hashBigInt = BigInt("0x" + hash.toString("hex"));
|
|
62
|
+
return toBase62(hashBigInt % 62n ** 11n); // Limit to base62 with ~11 chars max
|
|
63
|
+
}
|
|
64
|
+
export function getStorableHeaders(headers) {
|
|
65
|
+
return filterSensitiveHeaders(headers);
|
|
66
|
+
}
|
|
67
|
+
export function headersToRecord(headers) {
|
|
68
|
+
const record = {};
|
|
69
|
+
headers.forEach((value, key) => {
|
|
70
|
+
record[key] = value;
|
|
71
|
+
});
|
|
72
|
+
return record;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=request-hasher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-hasher.js","sourceRoot":"","sources":["../src/request-hasher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TimedChunk } from "./types.js";
|
|
2
|
+
export declare function isSSEResponse(headers: Headers): boolean;
|
|
3
|
+
export declare function recordStreamWithTiming(stream: ReadableStream<Uint8Array>): Promise<Array<TimedChunk>>;
|
|
4
|
+
export declare function replayStreamWithTiming(timedChunks: Array<TimedChunk>): ReadableStream<Uint8Array>;
|
|
5
|
+
/** Replay stream from an async iterable of timed chunks (for generator transforms) */
|
|
6
|
+
export declare function replayStreamFromAsyncIterable(chunks: AsyncIterable<TimedChunk>): ReadableStream<Uint8Array>;
|
|
7
|
+
export declare function timedChunksToJsonl(chunks: Array<TimedChunk>): string;
|
|
8
|
+
export declare function jsonlToTimedChunks(jsonl: string): Array<TimedChunk>;
|
|
9
|
+
//# sourceMappingURL=sse-handler.d.ts.map
|