utilium 1.2.1 → 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.
@@ -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>;
@@ -1,4 +1,4 @@
1
- export interface ResourceCacheOptions {
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,12 +12,59 @@ 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
  }
16
- type CacheRange = {
21
+ /**
22
+ * @deprecated Use `CacheOptions`
23
+ */
24
+ export type ResourceCacheOptions = CacheOptions;
25
+ export type CacheRange = {
17
26
  start: number;
18
27
  end: number;
19
28
  };
20
- export type RequestError = {
29
+ export interface CacheRegion {
30
+ /** The region's offset from the start of the resource */
31
+ offset: number;
32
+ /** Ranges cached in this region. These are absolute! */
33
+ ranges: CacheRange[];
34
+ /** Data for this region */
35
+ data: Uint8Array;
36
+ }
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 = {
21
68
  tag: 'status';
22
69
  response: Response;
23
70
  } | {
@@ -28,7 +75,19 @@ export type RequestError = {
28
75
  tag: 'fetch' | 'size';
29
76
  message: string;
30
77
  } | Error;
31
- export interface RequestOptions extends ResourceCacheOptions {
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 {
32
91
  /**
33
92
  * When using range requests,
34
93
  * a HEAD request must normally be used to determine the full size of the resource.
@@ -39,20 +98,38 @@ export interface RequestOptions extends ResourceCacheOptions {
39
98
  start?: number;
40
99
  /** The end of the range */
41
100
  end?: number;
42
- /** Optionally provide a function for logging warnings */
43
- warn?(message: string): unknown;
44
101
  }
45
102
  /**
46
103
  * Make a GET request without worrying about ranges
47
104
  * @throws RequestError
48
105
  */
49
- export declare function GET(url: string, options: RequestOptions, init?: RequestInit): Promise<Uint8Array>;
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;
50
111
  /**
51
112
  * Synchronously gets a cached resource
52
113
  * Assumes you pass valid start and end when using ranges
53
114
  */
54
- export declare function getCached(url: string, options: RequestOptions): {
55
- data: Uint8Array;
115
+ export declare function getCached(url: string, options: GetOptions): {
116
+ data?: Uint8Array;
56
117
  missing: CacheRange[];
57
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>;
58
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
- /** The cache for a specific resource */
4
- class ResourceCache {
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
- requestsCache.set(url, this);
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, start, end = start + data.byteLength) {
97
- const region = this.regionAt(start);
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.ranges.push({ start, end });
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: start, ranges: [{ start, end }] };
106
- const insertIndex = this.regions.findIndex(region => region.offset > start);
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
- const requestsCache = new Map();
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 _fetchBuffer(input, init = {}) {
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 arrayBuffer = await response.arrayBuffer().catch((error) => {
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(arrayBuffer) };
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 GET(url, options, init = {}) {
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 _fetchBuffer(url, init);
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 = requestsCache.get(url) ?? new ResourceCache(url, size, options);
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 _fetchBuffer(req, { headers: { Range: `bytes=${from}-${to}` } });
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 = requestsCache.get(url);
192
+ const cache = resourcesCache.get(url);
179
193
  /**
180
194
  * @todo Make sure we have a size?
181
195
  */
182
- if (!cache)
183
- return { data: new Uint8Array(0), missing: [{ start: 0, end: options.size ?? 0 }] };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utilium",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Typescript utilities",
5
5
  "funding": {
6
6
  "type": "individual",
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 ResourceCacheOptions {
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,11 +19,22 @@ 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
 
24
- type CacheRange = { start: number; end: number };
30
+ /**
31
+ * @deprecated Use `CacheOptions`
32
+ */
33
+ export type ResourceCacheOptions = CacheOptions;
34
+
35
+ export type CacheRange = { start: number; end: number };
25
36
 
26
- interface CacheRegion {
37
+ export interface CacheRegion {
27
38
  /** The region's offset from the start of the resource */
28
39
  offset: number;
29
40
 
@@ -34,8 +45,11 @@ interface CacheRegion {
34
45
  data: Uint8Array;
35
46
  }
36
47
 
37
- /** The cache for a specific resource */
38
- class ResourceCache {
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: ResourceCacheOptions
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
- requestsCache.set(url, this);
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, start: number, end: number = start + data.byteLength): this {
137
- const region = this.regionAt(start);
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.ranges.push({ start, end });
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: start, ranges: [{ start, end }] };
149
- const insertIndex = this.regions.findIndex(region => region.offset > start);
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
- const requestsCache = new Map<string, ResourceCache | null>();
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
- export type RequestError = { tag: 'status'; response: Response } | { tag: 'buffer'; response: Response; message: string } | { tag: 'fetch' | 'size'; message: string } | Error;
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 _fetchBuffer(input: RequestInfo, init: RequestInit = {}): Promise<{ response: Response; data: Uint8Array }> {
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 RequestError;
205
+ throw { tag: 'fetch', message: error.message } satisfies Issue;
173
206
  });
174
207
 
175
- if (!response.ok) throw { tag: 'status', response } satisfies RequestError;
208
+ if (!response.ok) throw { tag: 'status', response } satisfies Issue;
176
209
 
177
- const arrayBuffer = await response.arrayBuffer().catch((error: Error) => {
178
- throw { tag: 'buffer', response, message: error.message } satisfies RequestError;
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(arrayBuffer) };
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
- export interface RequestOptions extends ResourceCacheOptions {
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 GET(url: string, options: RequestOptions, init: RequestInit = {}): Promise<Uint8Array> {
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 _fetchBuffer(url, init);
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 RequestError;
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 = requestsCache.get(url) ?? new ResourceCache(url, size, options);
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 _fetchBuffer(req, { headers: { Range: `bytes=${from}-${to}` } });
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: RequestOptions): { data: Uint8Array; missing: CacheRange[] } {
258
- const cache = requestsCache.get(url);
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) return { data: new Uint8Array(0), missing: [{ start: 0, end: options.size ?? 0 }] };
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
+ }