utilium 1.2.2 → 1.2.3
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/dist/debugging.d.ts +17 -0
- package/dist/requests.d.ts +78 -8
- package/dist/requests.js +60 -19
- package/package.json +1 -1
- package/src/debugging.ts +17 -0
- package/src/requests.ts +115 -30
package/dist/debugging.d.ts
CHANGED
@@ -1,8 +1,25 @@
|
|
1
1
|
export interface CreateLoggerOptions {
|
2
|
+
/**
|
3
|
+
* The function used to output
|
4
|
+
* @default console.log
|
5
|
+
*/
|
2
6
|
output?: (...args: any[]) => void;
|
3
7
|
stringify?: (value: unknown) => string;
|
8
|
+
/**
|
9
|
+
* Whether to output the class name when logging methods
|
10
|
+
* @default true
|
11
|
+
*/
|
4
12
|
className?: boolean;
|
13
|
+
/**
|
14
|
+
* The separator used to separate the class name and method.
|
15
|
+
* Ignored if `className` is `false`
|
16
|
+
* @default '#'
|
17
|
+
*/
|
5
18
|
separator?: string;
|
19
|
+
/**
|
20
|
+
* Whether to log the return value
|
21
|
+
* @default false
|
22
|
+
*/
|
6
23
|
returnValue?: boolean;
|
7
24
|
}
|
8
25
|
type LoggableDecoratorContext = Exclude<DecoratorContext, ClassFieldDecoratorContext>;
|
package/dist/requests.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
export interface
|
1
|
+
export interface CacheOptions {
|
2
2
|
/**
|
3
3
|
* If true, use multiple buffers to cache a file.
|
4
4
|
* This is useful when working with small parts of large files,
|
@@ -12,7 +12,16 @@ export interface ResourceCacheOptions {
|
|
12
12
|
* @default 0xfff // 4 KiB
|
13
13
|
*/
|
14
14
|
regionGapThreshold?: number;
|
15
|
+
/**
|
16
|
+
* Whether to only update the cache when changing or deleting resources
|
17
|
+
* @default false
|
18
|
+
*/
|
19
|
+
cacheOnly?: boolean;
|
15
20
|
}
|
21
|
+
/**
|
22
|
+
* @deprecated Use `CacheOptions`
|
23
|
+
*/
|
24
|
+
export type ResourceCacheOptions = CacheOptions;
|
16
25
|
export type CacheRange = {
|
17
26
|
start: number;
|
18
27
|
end: number;
|
@@ -25,7 +34,37 @@ export interface CacheRegion {
|
|
25
34
|
/** Data for this region */
|
26
35
|
data: Uint8Array;
|
27
36
|
}
|
28
|
-
|
37
|
+
/**
|
38
|
+
* The cache for a specific resource
|
39
|
+
* @internal
|
40
|
+
*/
|
41
|
+
export declare class ResourceCache {
|
42
|
+
/** The resource URL */
|
43
|
+
readonly url: string;
|
44
|
+
/** The full size of the resource */
|
45
|
+
readonly size: number;
|
46
|
+
protected readonly options: CacheOptions;
|
47
|
+
/** Regions used to reduce unneeded allocations. Think of sparse arrays. */
|
48
|
+
readonly regions: CacheRegion[];
|
49
|
+
constructor(
|
50
|
+
/** The resource URL */
|
51
|
+
url: string,
|
52
|
+
/** The full size of the resource */
|
53
|
+
size: number, options: CacheOptions);
|
54
|
+
/** Combines adjacent regions and combines adjacent ranges within a region */
|
55
|
+
collect(): void;
|
56
|
+
/** Takes an initial range and finds the sub-ranges that are not in the cache */
|
57
|
+
missing(start: number, end: number): CacheRange[];
|
58
|
+
/** Get the region who's ranges include an offset */
|
59
|
+
regionAt(offset: number): CacheRegion | undefined;
|
60
|
+
/** Add new data to the cache at given specified offset */
|
61
|
+
add(data: Uint8Array, offset: number): this;
|
62
|
+
}
|
63
|
+
/**
|
64
|
+
* @internal
|
65
|
+
*/
|
66
|
+
export declare const resourcesCache: Map<string, ResourceCache | null>;
|
67
|
+
export type Issue = {
|
29
68
|
tag: 'status';
|
30
69
|
response: Response;
|
31
70
|
} | {
|
@@ -36,7 +75,19 @@ export type RequestError = {
|
|
36
75
|
tag: 'fetch' | 'size';
|
37
76
|
message: string;
|
38
77
|
} | Error;
|
39
|
-
|
78
|
+
/**
|
79
|
+
* @deprecated Use `Issue`
|
80
|
+
*/
|
81
|
+
export type RequestError = Issue;
|
82
|
+
export interface Options extends CacheOptions {
|
83
|
+
/** Optionally provide a function for logging warnings */
|
84
|
+
warn?(message: string): unknown;
|
85
|
+
}
|
86
|
+
/**
|
87
|
+
* @deprecated Use `Options`
|
88
|
+
*/
|
89
|
+
export type RequestOptions = Options;
|
90
|
+
export interface GetOptions extends Options {
|
40
91
|
/**
|
41
92
|
* When using range requests,
|
42
93
|
* a HEAD request must normally be used to determine the full size of the resource.
|
@@ -47,19 +98,38 @@ export interface RequestOptions extends ResourceCacheOptions {
|
|
47
98
|
start?: number;
|
48
99
|
/** The end of the range */
|
49
100
|
end?: number;
|
50
|
-
/** Optionally provide a function for logging warnings */
|
51
|
-
warn?(message: string): unknown;
|
52
101
|
}
|
53
102
|
/**
|
54
103
|
* Make a GET request without worrying about ranges
|
55
104
|
* @throws RequestError
|
56
105
|
*/
|
57
|
-
export declare function
|
106
|
+
export declare function get(url: string, options: GetOptions, init?: RequestInit): Promise<Uint8Array>;
|
107
|
+
/**
|
108
|
+
* @deprecated Use `get`
|
109
|
+
*/
|
110
|
+
export declare const GET: typeof get;
|
58
111
|
/**
|
59
112
|
* Synchronously gets a cached resource
|
60
113
|
* Assumes you pass valid start and end when using ranges
|
61
114
|
*/
|
62
|
-
export declare function getCached(url: string, options:
|
63
|
-
data
|
115
|
+
export declare function getCached(url: string, options: GetOptions): {
|
116
|
+
data?: Uint8Array;
|
64
117
|
missing: CacheRange[];
|
65
118
|
};
|
119
|
+
interface SetOptions extends Options {
|
120
|
+
/** The offset we are updating at */
|
121
|
+
offset?: number;
|
122
|
+
/** If a cache for the resource doesn't exist, this will be used as the full size */
|
123
|
+
size?: number;
|
124
|
+
}
|
125
|
+
/**
|
126
|
+
* Make a POST request to set (or create) data on the server and update the cache.
|
127
|
+
* @throws RequestError
|
128
|
+
*/
|
129
|
+
export declare function set(url: string, data: Uint8Array, options: SetOptions, init?: RequestInit): Promise<void>;
|
130
|
+
/**
|
131
|
+
* Make a DELETE request to remove the resource from the server and clear it from the cache.
|
132
|
+
* @throws RequestError
|
133
|
+
*/
|
134
|
+
export declare function remove(url: string, options?: Options, init?: RequestInit): Promise<void>;
|
135
|
+
export {};
|
package/dist/requests.js
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
/* Utilities for `fetch` when using range requests. It also allows you to handle errors easier */
|
2
2
|
import { extendBuffer } from './buffer.js';
|
3
|
-
/**
|
4
|
-
|
3
|
+
/**
|
4
|
+
* The cache for a specific resource
|
5
|
+
* @internal
|
6
|
+
*/
|
7
|
+
export class ResourceCache {
|
5
8
|
url;
|
6
9
|
size;
|
7
10
|
options;
|
@@ -18,7 +21,7 @@ class ResourceCache {
|
|
18
21
|
options.sparse ??= true;
|
19
22
|
if (!options.sparse)
|
20
23
|
this.regions.push({ offset: 0, data: new Uint8Array(size), ranges: [] });
|
21
|
-
|
24
|
+
resourcesCache.set(url, this);
|
22
25
|
}
|
23
26
|
/** Combines adjacent regions and combines adjacent ranges within a region */
|
24
27
|
collect() {
|
@@ -93,17 +96,19 @@ class ResourceCache {
|
|
93
96
|
}
|
94
97
|
}
|
95
98
|
/** Add new data to the cache at given specified offset */
|
96
|
-
add(data,
|
97
|
-
const
|
99
|
+
add(data, offset) {
|
100
|
+
const end = offset + data.byteLength;
|
101
|
+
const region = this.regionAt(offset);
|
98
102
|
if (region) {
|
99
103
|
region.data = extendBuffer(region.data, end);
|
100
|
-
region.
|
104
|
+
region.data.set(data, offset);
|
105
|
+
region.ranges.push({ start: offset, end });
|
101
106
|
region.ranges.sort((a, b) => a.start - b.start);
|
102
107
|
return this;
|
103
108
|
}
|
104
109
|
// Find the correct index to insert the new region
|
105
|
-
const newRegion = { data, offset:
|
106
|
-
const insertIndex = this.regions.findIndex(region => region.offset >
|
110
|
+
const newRegion = { data, offset: offset, ranges: [{ start: offset, end }] };
|
111
|
+
const insertIndex = this.regions.findIndex(region => region.offset > offset);
|
107
112
|
// Insert at the right index to keep regions sorted
|
108
113
|
if (insertIndex == -1) {
|
109
114
|
this.regions.push(newRegion); // Append if no later region exists
|
@@ -114,31 +119,36 @@ class ResourceCache {
|
|
114
119
|
return this;
|
115
120
|
}
|
116
121
|
}
|
117
|
-
|
122
|
+
/**
|
123
|
+
* @internal
|
124
|
+
*/
|
125
|
+
export const resourcesCache = new Map();
|
118
126
|
/**
|
119
127
|
* Wraps `fetch`
|
120
128
|
* @throws RequestError
|
121
129
|
*/
|
122
|
-
async function
|
130
|
+
async function _fetch(input, init = {}, bodyOptional = false) {
|
123
131
|
const response = await fetch(input, init).catch((error) => {
|
124
132
|
throw { tag: 'fetch', message: error.message };
|
125
133
|
});
|
126
134
|
if (!response.ok)
|
127
135
|
throw { tag: 'status', response };
|
128
|
-
const
|
136
|
+
const raw = await response.arrayBuffer().catch((error) => {
|
137
|
+
if (bodyOptional)
|
138
|
+
return;
|
129
139
|
throw { tag: 'buffer', response, message: error.message };
|
130
140
|
});
|
131
|
-
return { response, data: new Uint8Array(
|
141
|
+
return { response, data: raw ? new Uint8Array(raw) : undefined };
|
132
142
|
}
|
133
143
|
/**
|
134
144
|
* Make a GET request without worrying about ranges
|
135
145
|
* @throws RequestError
|
136
146
|
*/
|
137
|
-
export async function
|
147
|
+
export async function get(url, options, init = {}) {
|
138
148
|
const req = new Request(url, init);
|
139
149
|
// Request no using ranges
|
140
150
|
if (typeof options.start != 'number' || typeof options.end != 'number') {
|
141
|
-
const { data } = await
|
151
|
+
const { data } = await _fetch(url, init);
|
142
152
|
new ResourceCache(url, data.byteLength, options).add(data, 0);
|
143
153
|
return data;
|
144
154
|
}
|
@@ -152,10 +162,10 @@ export async function GET(url, options, init = {}) {
|
|
152
162
|
options.size = size;
|
153
163
|
}
|
154
164
|
const { size, start, end } = options;
|
155
|
-
const cache =
|
165
|
+
const cache = resourcesCache.get(url) ?? new ResourceCache(url, size, options);
|
156
166
|
req.headers.set('If-Range', new Date().toUTCString());
|
157
167
|
for (const { start: from, end: to } of cache.missing(start, end)) {
|
158
|
-
const { data, response } = await
|
168
|
+
const { data, response } = await _fetch(req, { headers: { Range: `bytes=${from}-${to}` } });
|
159
169
|
if (response.status == 206) {
|
160
170
|
cache.add(data, from);
|
161
171
|
continue;
|
@@ -170,17 +180,25 @@ export async function GET(url, options, init = {}) {
|
|
170
180
|
const region = cache.regionAt(start);
|
171
181
|
return region.data.subarray(start - region.offset, end - region.offset);
|
172
182
|
}
|
183
|
+
/**
|
184
|
+
* @deprecated Use `get`
|
185
|
+
*/
|
186
|
+
export const GET = get;
|
173
187
|
/**
|
174
188
|
* Synchronously gets a cached resource
|
175
189
|
* Assumes you pass valid start and end when using ranges
|
176
190
|
*/
|
177
191
|
export function getCached(url, options) {
|
178
|
-
const cache =
|
192
|
+
const cache = resourcesCache.get(url);
|
179
193
|
/**
|
180
194
|
* @todo Make sure we have a size?
|
181
195
|
*/
|
182
|
-
if (!cache)
|
183
|
-
|
196
|
+
if (!cache) {
|
197
|
+
if (options.size)
|
198
|
+
return { data: new Uint8Array(0), missing: [{ start: 0, end: options.size ?? 0 }] };
|
199
|
+
options.warn?.(url + ': Size not provided and cache is empty, can not determine missing range');
|
200
|
+
return { data: undefined, missing: [] };
|
201
|
+
}
|
184
202
|
const { start = 0, end = cache.size } = options;
|
185
203
|
const data = new Uint8Array(end - start);
|
186
204
|
for (const region of cache.regions) {
|
@@ -202,3 +220,26 @@ export function getCached(url, options) {
|
|
202
220
|
}
|
203
221
|
return { data, missing: cache.missing(start, end) };
|
204
222
|
}
|
223
|
+
/**
|
224
|
+
* Make a POST request to set (or create) data on the server and update the cache.
|
225
|
+
* @throws RequestError
|
226
|
+
*/
|
227
|
+
export async function set(url, data, options, init = {}) {
|
228
|
+
if (!resourcesCache.has(url)) {
|
229
|
+
new ResourceCache(url, options.size ?? data.byteLength, options);
|
230
|
+
}
|
231
|
+
const cache = resourcesCache.get(url);
|
232
|
+
const { offset = 0 } = options;
|
233
|
+
if (!options.cacheOnly)
|
234
|
+
await _fetch(new Request(url, init), { method: 'POST' }, true);
|
235
|
+
cache.add(data, offset).collect();
|
236
|
+
}
|
237
|
+
/**
|
238
|
+
* Make a DELETE request to remove the resource from the server and clear it from the cache.
|
239
|
+
* @throws RequestError
|
240
|
+
*/
|
241
|
+
export async function remove(url, options = {}, init = {}) {
|
242
|
+
if (!options.cacheOnly)
|
243
|
+
await _fetch(new Request(url, init), { method: 'DELETE' }, true);
|
244
|
+
resourcesCache.delete(url);
|
245
|
+
}
|
package/package.json
CHANGED
package/src/debugging.ts
CHANGED
@@ -1,9 +1,26 @@
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
2
|
export interface CreateLoggerOptions {
|
3
|
+
/**
|
4
|
+
* The function used to output
|
5
|
+
* @default console.log
|
6
|
+
*/
|
3
7
|
output?: (...args: any[]) => void;
|
4
8
|
stringify?: (value: unknown) => string;
|
9
|
+
/**
|
10
|
+
* Whether to output the class name when logging methods
|
11
|
+
* @default true
|
12
|
+
*/
|
5
13
|
className?: boolean;
|
14
|
+
/**
|
15
|
+
* The separator used to separate the class name and method.
|
16
|
+
* Ignored if `className` is `false`
|
17
|
+
* @default '#'
|
18
|
+
*/
|
6
19
|
separator?: string;
|
20
|
+
/**
|
21
|
+
* Whether to log the return value
|
22
|
+
* @default false
|
23
|
+
*/
|
7
24
|
returnValue?: boolean;
|
8
25
|
}
|
9
26
|
|
package/src/requests.ts
CHANGED
@@ -4,7 +4,7 @@ import { extendBuffer } from './buffer.js';
|
|
4
4
|
|
5
5
|
/* eslint-disable @typescript-eslint/only-throw-error */
|
6
6
|
|
7
|
-
export interface
|
7
|
+
export interface CacheOptions {
|
8
8
|
/**
|
9
9
|
* If true, use multiple buffers to cache a file.
|
10
10
|
* This is useful when working with small parts of large files,
|
@@ -19,8 +19,19 @@ export interface ResourceCacheOptions {
|
|
19
19
|
* @default 0xfff // 4 KiB
|
20
20
|
*/
|
21
21
|
regionGapThreshold?: number;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Whether to only update the cache when changing or deleting resources
|
25
|
+
* @default false
|
26
|
+
*/
|
27
|
+
cacheOnly?: boolean;
|
22
28
|
}
|
23
29
|
|
30
|
+
/**
|
31
|
+
* @deprecated Use `CacheOptions`
|
32
|
+
*/
|
33
|
+
export type ResourceCacheOptions = CacheOptions;
|
34
|
+
|
24
35
|
export type CacheRange = { start: number; end: number };
|
25
36
|
|
26
37
|
export interface CacheRegion {
|
@@ -34,8 +45,11 @@ export interface CacheRegion {
|
|
34
45
|
data: Uint8Array;
|
35
46
|
}
|
36
47
|
|
37
|
-
/**
|
38
|
-
|
48
|
+
/**
|
49
|
+
* The cache for a specific resource
|
50
|
+
* @internal
|
51
|
+
*/
|
52
|
+
export class ResourceCache {
|
39
53
|
/** Regions used to reduce unneeded allocations. Think of sparse arrays. */
|
40
54
|
public readonly regions: CacheRegion[] = [];
|
41
55
|
|
@@ -44,12 +58,12 @@ class ResourceCache {
|
|
44
58
|
public readonly url: string,
|
45
59
|
/** The full size of the resource */
|
46
60
|
public readonly size: number,
|
47
|
-
protected readonly options:
|
61
|
+
protected readonly options: CacheOptions
|
48
62
|
) {
|
49
63
|
options.sparse ??= true;
|
50
64
|
if (!options.sparse) this.regions.push({ offset: 0, data: new Uint8Array(size), ranges: [] });
|
51
65
|
|
52
|
-
|
66
|
+
resourcesCache.set(url, this);
|
53
67
|
}
|
54
68
|
|
55
69
|
/** Combines adjacent regions and combines adjacent ranges within a region */
|
@@ -133,20 +147,22 @@ class ResourceCache {
|
|
133
147
|
}
|
134
148
|
|
135
149
|
/** Add new data to the cache at given specified offset */
|
136
|
-
public add(data: Uint8Array,
|
137
|
-
const
|
150
|
+
public add(data: Uint8Array, offset: number): this {
|
151
|
+
const end = offset + data.byteLength;
|
152
|
+
const region = this.regionAt(offset);
|
138
153
|
|
139
154
|
if (region) {
|
140
155
|
region.data = extendBuffer(region.data, end);
|
141
|
-
region.
|
156
|
+
region.data.set(data, offset);
|
157
|
+
region.ranges.push({ start: offset, end });
|
142
158
|
region.ranges.sort((a, b) => a.start - b.start);
|
143
159
|
|
144
160
|
return this;
|
145
161
|
}
|
146
162
|
|
147
163
|
// Find the correct index to insert the new region
|
148
|
-
const newRegion: CacheRegion = { data, offset:
|
149
|
-
const insertIndex = this.regions.findIndex(region => region.offset >
|
164
|
+
const newRegion: CacheRegion = { data, offset: offset, ranges: [{ start: offset, end }] };
|
165
|
+
const insertIndex = this.regions.findIndex(region => region.offset > offset);
|
150
166
|
|
151
167
|
// Insert at the right index to keep regions sorted
|
152
168
|
if (insertIndex == -1) {
|
@@ -159,29 +175,57 @@ class ResourceCache {
|
|
159
175
|
}
|
160
176
|
}
|
161
177
|
|
162
|
-
|
178
|
+
/**
|
179
|
+
* @internal
|
180
|
+
*/
|
181
|
+
export const resourcesCache = new Map<string, ResourceCache | null>();
|
182
|
+
|
183
|
+
export type Issue = { tag: 'status'; response: Response } | { tag: 'buffer'; response: Response; message: string } | { tag: 'fetch' | 'size'; message: string } | Error;
|
184
|
+
|
185
|
+
/**
|
186
|
+
* @deprecated Use `Issue`
|
187
|
+
*/
|
188
|
+
export type RequestError = Issue;
|
163
189
|
|
164
|
-
|
190
|
+
interface Fetched<TBodyOptional extends boolean> {
|
191
|
+
response: Response;
|
192
|
+
data: false extends TBodyOptional ? Uint8Array : Uint8Array | undefined;
|
193
|
+
}
|
165
194
|
|
166
195
|
/**
|
167
196
|
* Wraps `fetch`
|
168
197
|
* @throws RequestError
|
169
198
|
*/
|
170
|
-
async function
|
199
|
+
async function _fetch<const TBodyOptional extends boolean>(
|
200
|
+
input: RequestInfo,
|
201
|
+
init: RequestInit = {},
|
202
|
+
bodyOptional: TBodyOptional = false as TBodyOptional
|
203
|
+
): Promise<Fetched<TBodyOptional>> {
|
171
204
|
const response = await fetch(input, init).catch((error: Error) => {
|
172
|
-
throw { tag: 'fetch', message: error.message } satisfies
|
205
|
+
throw { tag: 'fetch', message: error.message } satisfies Issue;
|
173
206
|
});
|
174
207
|
|
175
|
-
if (!response.ok) throw { tag: 'status', response } satisfies
|
208
|
+
if (!response.ok) throw { tag: 'status', response } satisfies Issue;
|
176
209
|
|
177
|
-
const
|
178
|
-
|
210
|
+
const raw = await response.arrayBuffer().catch((error: Error) => {
|
211
|
+
if (bodyOptional) return;
|
212
|
+
throw { tag: 'buffer', response, message: error.message } satisfies Issue;
|
179
213
|
});
|
180
214
|
|
181
|
-
return { response, data: new Uint8Array(
|
215
|
+
return { response, data: raw ? new Uint8Array(raw) : undefined } as Fetched<TBodyOptional>;
|
216
|
+
}
|
217
|
+
|
218
|
+
export interface Options extends CacheOptions {
|
219
|
+
/** Optionally provide a function for logging warnings */
|
220
|
+
warn?(message: string): unknown;
|
182
221
|
}
|
183
222
|
|
184
|
-
|
223
|
+
/**
|
224
|
+
* @deprecated Use `Options`
|
225
|
+
*/
|
226
|
+
export type RequestOptions = Options;
|
227
|
+
|
228
|
+
export interface GetOptions extends Options {
|
185
229
|
/**
|
186
230
|
* When using range requests,
|
187
231
|
* a HEAD request must normally be used to determine the full size of the resource.
|
@@ -194,21 +238,18 @@ export interface RequestOptions extends ResourceCacheOptions {
|
|
194
238
|
|
195
239
|
/** The end of the range */
|
196
240
|
end?: number;
|
197
|
-
|
198
|
-
/** Optionally provide a function for logging warnings */
|
199
|
-
warn?(message: string): unknown;
|
200
241
|
}
|
201
242
|
|
202
243
|
/**
|
203
244
|
* Make a GET request without worrying about ranges
|
204
245
|
* @throws RequestError
|
205
246
|
*/
|
206
|
-
export async function
|
247
|
+
export async function get(url: string, options: GetOptions, init: RequestInit = {}): Promise<Uint8Array> {
|
207
248
|
const req = new Request(url, init);
|
208
249
|
|
209
250
|
// Request no using ranges
|
210
251
|
if (typeof options.start != 'number' || typeof options.end != 'number') {
|
211
|
-
const { data } = await
|
252
|
+
const { data } = await _fetch(url, init);
|
212
253
|
new ResourceCache(url, data.byteLength, options).add(data, 0);
|
213
254
|
return data;
|
214
255
|
}
|
@@ -220,17 +261,17 @@ export async function GET(url: string, options: RequestOptions, init: RequestIni
|
|
220
261
|
|
221
262
|
const { headers } = await fetch(req, { method: 'HEAD' });
|
222
263
|
const size = parseInt(headers.get('Content-Length') ?? '');
|
223
|
-
if (typeof size != 'number') throw { tag: 'size', message: 'Response is missing content-length header and no size was provided' } satisfies
|
264
|
+
if (typeof size != 'number') throw { tag: 'size', message: 'Response is missing content-length header and no size was provided' } satisfies Issue;
|
224
265
|
options.size = size;
|
225
266
|
}
|
226
267
|
|
227
268
|
const { size, start, end } = options;
|
228
|
-
const cache =
|
269
|
+
const cache = resourcesCache.get(url) ?? new ResourceCache(url, size, options);
|
229
270
|
|
230
271
|
req.headers.set('If-Range', new Date().toUTCString());
|
231
272
|
|
232
273
|
for (const { start: from, end: to } of cache.missing(start, end)) {
|
233
|
-
const { data, response } = await
|
274
|
+
const { data, response } = await _fetch(req, { headers: { Range: `bytes=${from}-${to}` } });
|
234
275
|
|
235
276
|
if (response.status == 206) {
|
236
277
|
cache.add(data, from);
|
@@ -250,17 +291,26 @@ export async function GET(url: string, options: RequestOptions, init: RequestIni
|
|
250
291
|
return region.data.subarray(start - region.offset, end - region.offset);
|
251
292
|
}
|
252
293
|
|
294
|
+
/**
|
295
|
+
* @deprecated Use `get`
|
296
|
+
*/
|
297
|
+
export const GET = get;
|
298
|
+
|
253
299
|
/**
|
254
300
|
* Synchronously gets a cached resource
|
255
301
|
* Assumes you pass valid start and end when using ranges
|
256
302
|
*/
|
257
|
-
export function getCached(url: string, options:
|
258
|
-
const cache =
|
303
|
+
export function getCached(url: string, options: GetOptions): { data?: Uint8Array; missing: CacheRange[] } {
|
304
|
+
const cache = resourcesCache.get(url);
|
259
305
|
|
260
306
|
/**
|
261
307
|
* @todo Make sure we have a size?
|
262
308
|
*/
|
263
|
-
if (!cache)
|
309
|
+
if (!cache) {
|
310
|
+
if (options.size) return { data: new Uint8Array(0), missing: [{ start: 0, end: options.size ?? 0 }] };
|
311
|
+
options.warn?.(url + ': Size not provided and cache is empty, can not determine missing range');
|
312
|
+
return { data: undefined, missing: [] };
|
313
|
+
}
|
264
314
|
|
265
315
|
const { start = 0, end = cache.size } = options;
|
266
316
|
|
@@ -285,3 +335,38 @@ export function getCached(url: string, options: RequestOptions): { data: Uint8Ar
|
|
285
335
|
|
286
336
|
return { data, missing: cache.missing(start, end) };
|
287
337
|
}
|
338
|
+
|
339
|
+
interface SetOptions extends Options {
|
340
|
+
/** The offset we are updating at */
|
341
|
+
offset?: number;
|
342
|
+
|
343
|
+
/** If a cache for the resource doesn't exist, this will be used as the full size */
|
344
|
+
size?: number;
|
345
|
+
}
|
346
|
+
|
347
|
+
/**
|
348
|
+
* Make a POST request to set (or create) data on the server and update the cache.
|
349
|
+
* @throws RequestError
|
350
|
+
*/
|
351
|
+
export async function set(url: string, data: Uint8Array, options: SetOptions, init: RequestInit = {}): Promise<void> {
|
352
|
+
if (!resourcesCache.has(url)) {
|
353
|
+
new ResourceCache(url, options.size ?? data.byteLength, options);
|
354
|
+
}
|
355
|
+
|
356
|
+
const cache = resourcesCache.get(url)!;
|
357
|
+
|
358
|
+
const { offset = 0 } = options;
|
359
|
+
|
360
|
+
if (!options.cacheOnly) await _fetch(new Request(url, init), { method: 'POST' }, true);
|
361
|
+
|
362
|
+
cache.add(data, offset).collect();
|
363
|
+
}
|
364
|
+
|
365
|
+
/**
|
366
|
+
* Make a DELETE request to remove the resource from the server and clear it from the cache.
|
367
|
+
* @throws RequestError
|
368
|
+
*/
|
369
|
+
export async function remove(url: string, options: Options = {}, init: RequestInit = {}): Promise<void> {
|
370
|
+
if (!options.cacheOnly) await _fetch(new Request(url, init), { method: 'DELETE' }, true);
|
371
|
+
resourcesCache.delete(url);
|
372
|
+
}
|