@finueva/drive 0.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/CHANGELOG.md +8 -0
- package/LICENSE +15 -0
- package/README.md +91 -0
- package/THIRD_PARTY_NOTICES.md +55 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +1337 -0
- package/dist/contract.d.ts +2101 -0
- package/dist/core.d.ts +31 -0
- package/dist/core.js +53 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1337 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +1344 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +0 -0
- package/package.json +72 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,1337 @@
|
|
|
1
|
+
//#region ../../node_modules/.pnpm/openapi-fetch@0.17.0/node_modules/openapi-fetch/dist/index.mjs
|
|
2
|
+
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
3
|
+
const supportsRequestInitExt = () => {
|
|
4
|
+
return typeof process === "object" && Number.parseInt(process?.versions?.node?.substring(0, 2)) >= 18 && process.versions.undici;
|
|
5
|
+
};
|
|
6
|
+
function randomID() {
|
|
7
|
+
return Math.random().toString(36).slice(2, 11);
|
|
8
|
+
}
|
|
9
|
+
function createClient(clientOptions) {
|
|
10
|
+
let { baseUrl = "", Request: CustomRequest = globalThis.Request, fetch: baseFetch = globalThis.fetch, querySerializer: globalQuerySerializer, bodySerializer: globalBodySerializer, pathSerializer: globalPathSerializer, headers: baseHeaders, requestInitExt = void 0, ...baseOptions } = { ...clientOptions };
|
|
11
|
+
requestInitExt = supportsRequestInitExt() ? requestInitExt : void 0;
|
|
12
|
+
baseUrl = removeTrailingSlash(baseUrl);
|
|
13
|
+
const globalMiddlewares = [];
|
|
14
|
+
async function coreFetch(schemaPath, fetchOptions) {
|
|
15
|
+
const { baseUrl: localBaseUrl, fetch = baseFetch, Request = CustomRequest, headers, params = {}, parseAs = "json", querySerializer: requestQuerySerializer, bodySerializer = globalBodySerializer ?? defaultBodySerializer, pathSerializer: requestPathSerializer, body, middleware: requestMiddlewares = [], ...init } = fetchOptions || {};
|
|
16
|
+
let finalBaseUrl = baseUrl;
|
|
17
|
+
if (localBaseUrl) finalBaseUrl = removeTrailingSlash(localBaseUrl) ?? baseUrl;
|
|
18
|
+
let querySerializer = typeof globalQuerySerializer === "function" ? globalQuerySerializer : createQuerySerializer(globalQuerySerializer);
|
|
19
|
+
if (requestQuerySerializer) querySerializer = typeof requestQuerySerializer === "function" ? requestQuerySerializer : createQuerySerializer({
|
|
20
|
+
...typeof globalQuerySerializer === "object" ? globalQuerySerializer : {},
|
|
21
|
+
...requestQuerySerializer
|
|
22
|
+
});
|
|
23
|
+
const pathSerializer = requestPathSerializer || globalPathSerializer || defaultPathSerializer;
|
|
24
|
+
const serializedBody = body === void 0 ? void 0 : bodySerializer(body, mergeHeaders(baseHeaders, headers, params.header));
|
|
25
|
+
const finalHeaders = mergeHeaders(serializedBody === void 0 || serializedBody instanceof FormData ? {} : { "Content-Type": "application/json" }, baseHeaders, headers, params.header);
|
|
26
|
+
const finalMiddlewares = [...globalMiddlewares, ...requestMiddlewares];
|
|
27
|
+
const requestInit = {
|
|
28
|
+
redirect: "follow",
|
|
29
|
+
...baseOptions,
|
|
30
|
+
...init,
|
|
31
|
+
body: serializedBody,
|
|
32
|
+
headers: finalHeaders
|
|
33
|
+
};
|
|
34
|
+
let id;
|
|
35
|
+
let options;
|
|
36
|
+
let request = new Request(createFinalURL(schemaPath, {
|
|
37
|
+
baseUrl: finalBaseUrl,
|
|
38
|
+
params,
|
|
39
|
+
querySerializer,
|
|
40
|
+
pathSerializer
|
|
41
|
+
}), requestInit);
|
|
42
|
+
let response;
|
|
43
|
+
for (const key in init) if (!(key in request)) request[key] = init[key];
|
|
44
|
+
if (finalMiddlewares.length) {
|
|
45
|
+
id = randomID();
|
|
46
|
+
options = Object.freeze({
|
|
47
|
+
baseUrl: finalBaseUrl,
|
|
48
|
+
fetch,
|
|
49
|
+
parseAs,
|
|
50
|
+
querySerializer,
|
|
51
|
+
bodySerializer,
|
|
52
|
+
pathSerializer
|
|
53
|
+
});
|
|
54
|
+
for (const m of finalMiddlewares) if (m && typeof m === "object" && typeof m.onRequest === "function") {
|
|
55
|
+
const result = await m.onRequest({
|
|
56
|
+
request,
|
|
57
|
+
schemaPath,
|
|
58
|
+
params,
|
|
59
|
+
options,
|
|
60
|
+
id
|
|
61
|
+
});
|
|
62
|
+
if (result) if (result instanceof Request) request = result;
|
|
63
|
+
else if (result instanceof Response) {
|
|
64
|
+
response = result;
|
|
65
|
+
break;
|
|
66
|
+
} else throw new Error("onRequest: must return new Request() or Response() when modifying the request");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!response) {
|
|
70
|
+
try {
|
|
71
|
+
response = await fetch(request, requestInitExt);
|
|
72
|
+
} catch (error2) {
|
|
73
|
+
let errorAfterMiddleware = error2;
|
|
74
|
+
if (finalMiddlewares.length) for (let i = finalMiddlewares.length - 1; i >= 0; i--) {
|
|
75
|
+
const m = finalMiddlewares[i];
|
|
76
|
+
if (m && typeof m === "object" && typeof m.onError === "function") {
|
|
77
|
+
const result = await m.onError({
|
|
78
|
+
request,
|
|
79
|
+
error: errorAfterMiddleware,
|
|
80
|
+
schemaPath,
|
|
81
|
+
params,
|
|
82
|
+
options,
|
|
83
|
+
id
|
|
84
|
+
});
|
|
85
|
+
if (result) {
|
|
86
|
+
if (result instanceof Response) {
|
|
87
|
+
errorAfterMiddleware = void 0;
|
|
88
|
+
response = result;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (result instanceof Error) {
|
|
92
|
+
errorAfterMiddleware = result;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
throw new Error("onError: must return new Response() or instance of Error");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (errorAfterMiddleware) throw errorAfterMiddleware;
|
|
100
|
+
}
|
|
101
|
+
if (finalMiddlewares.length) for (let i = finalMiddlewares.length - 1; i >= 0; i--) {
|
|
102
|
+
const m = finalMiddlewares[i];
|
|
103
|
+
if (m && typeof m === "object" && typeof m.onResponse === "function") {
|
|
104
|
+
const result = await m.onResponse({
|
|
105
|
+
request,
|
|
106
|
+
response,
|
|
107
|
+
schemaPath,
|
|
108
|
+
params,
|
|
109
|
+
options,
|
|
110
|
+
id
|
|
111
|
+
});
|
|
112
|
+
if (result) {
|
|
113
|
+
if (!(result instanceof Response)) throw new Error("onResponse: must return new Response() when modifying the response");
|
|
114
|
+
response = result;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const contentLength = response.headers.get("Content-Length");
|
|
120
|
+
if (response.status === 204 || request.method === "HEAD" || contentLength === "0" && !response.headers.get("Transfer-Encoding")?.includes("chunked")) return response.ok ? {
|
|
121
|
+
data: void 0,
|
|
122
|
+
response
|
|
123
|
+
} : {
|
|
124
|
+
error: void 0,
|
|
125
|
+
response
|
|
126
|
+
};
|
|
127
|
+
if (response.ok) {
|
|
128
|
+
const getResponseData = async () => {
|
|
129
|
+
if (parseAs === "stream") return response.body;
|
|
130
|
+
if (parseAs === "json" && !contentLength) {
|
|
131
|
+
const raw = await response.text();
|
|
132
|
+
return raw ? JSON.parse(raw) : void 0;
|
|
133
|
+
}
|
|
134
|
+
return await response[parseAs]();
|
|
135
|
+
};
|
|
136
|
+
return {
|
|
137
|
+
data: await getResponseData(),
|
|
138
|
+
response
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
let error = await response.text();
|
|
142
|
+
try {
|
|
143
|
+
error = JSON.parse(error);
|
|
144
|
+
} catch {}
|
|
145
|
+
return {
|
|
146
|
+
error,
|
|
147
|
+
response
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
request(method, url, init) {
|
|
152
|
+
return coreFetch(url, {
|
|
153
|
+
...init,
|
|
154
|
+
method: method.toUpperCase()
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
GET(url, init) {
|
|
158
|
+
return coreFetch(url, {
|
|
159
|
+
...init,
|
|
160
|
+
method: "GET"
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
PUT(url, init) {
|
|
164
|
+
return coreFetch(url, {
|
|
165
|
+
...init,
|
|
166
|
+
method: "PUT"
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
POST(url, init) {
|
|
170
|
+
return coreFetch(url, {
|
|
171
|
+
...init,
|
|
172
|
+
method: "POST"
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
DELETE(url, init) {
|
|
176
|
+
return coreFetch(url, {
|
|
177
|
+
...init,
|
|
178
|
+
method: "DELETE"
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
OPTIONS(url, init) {
|
|
182
|
+
return coreFetch(url, {
|
|
183
|
+
...init,
|
|
184
|
+
method: "OPTIONS"
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
HEAD(url, init) {
|
|
188
|
+
return coreFetch(url, {
|
|
189
|
+
...init,
|
|
190
|
+
method: "HEAD"
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
PATCH(url, init) {
|
|
194
|
+
return coreFetch(url, {
|
|
195
|
+
...init,
|
|
196
|
+
method: "PATCH"
|
|
197
|
+
});
|
|
198
|
+
},
|
|
199
|
+
TRACE(url, init) {
|
|
200
|
+
return coreFetch(url, {
|
|
201
|
+
...init,
|
|
202
|
+
method: "TRACE"
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
use(...middleware) {
|
|
206
|
+
for (const m of middleware) {
|
|
207
|
+
if (!m) continue;
|
|
208
|
+
if (typeof m !== "object" || !("onRequest" in m || "onResponse" in m || "onError" in m)) throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");
|
|
209
|
+
globalMiddlewares.push(m);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
eject(...middleware) {
|
|
213
|
+
for (const m of middleware) {
|
|
214
|
+
const i = globalMiddlewares.indexOf(m);
|
|
215
|
+
if (i !== -1) globalMiddlewares.splice(i, 1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function serializePrimitiveParam(name, value, options) {
|
|
221
|
+
if (value === void 0 || value === null) return "";
|
|
222
|
+
if (typeof value === "object") throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
|
|
223
|
+
return `${name}=${options?.allowReserved === true ? value : encodeURIComponent(value)}`;
|
|
224
|
+
}
|
|
225
|
+
function serializeObjectParam(name, value, options) {
|
|
226
|
+
if (!value || typeof value !== "object") return "";
|
|
227
|
+
const values = [];
|
|
228
|
+
const joiner = {
|
|
229
|
+
simple: ",",
|
|
230
|
+
label: ".",
|
|
231
|
+
matrix: ";"
|
|
232
|
+
}[options.style] || "&";
|
|
233
|
+
if (options.style !== "deepObject" && options.explode === false) {
|
|
234
|
+
for (const k in value) values.push(k, options.allowReserved === true ? value[k] : encodeURIComponent(value[k]));
|
|
235
|
+
const final2 = values.join(",");
|
|
236
|
+
switch (options.style) {
|
|
237
|
+
case "form": return `${name}=${final2}`;
|
|
238
|
+
case "label": return `.${final2}`;
|
|
239
|
+
case "matrix": return `;${name}=${final2}`;
|
|
240
|
+
default: return final2;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const k in value) {
|
|
244
|
+
const finalName = options.style === "deepObject" ? `${name}[${k}]` : k;
|
|
245
|
+
values.push(serializePrimitiveParam(finalName, value[k], options));
|
|
246
|
+
}
|
|
247
|
+
const final = values.join(joiner);
|
|
248
|
+
return options.style === "label" || options.style === "matrix" ? `${joiner}${final}` : final;
|
|
249
|
+
}
|
|
250
|
+
function serializeArrayParam(name, value, options) {
|
|
251
|
+
if (!Array.isArray(value)) return "";
|
|
252
|
+
if (options.explode === false) {
|
|
253
|
+
const joiner2 = {
|
|
254
|
+
form: ",",
|
|
255
|
+
spaceDelimited: "%20",
|
|
256
|
+
pipeDelimited: "|"
|
|
257
|
+
}[options.style] || ",";
|
|
258
|
+
const final = (options.allowReserved === true ? value : value.map((v) => encodeURIComponent(v))).join(joiner2);
|
|
259
|
+
switch (options.style) {
|
|
260
|
+
case "simple": return final;
|
|
261
|
+
case "label": return `.${final}`;
|
|
262
|
+
case "matrix": return `;${name}=${final}`;
|
|
263
|
+
default: return `${name}=${final}`;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const joiner = {
|
|
267
|
+
simple: ",",
|
|
268
|
+
label: ".",
|
|
269
|
+
matrix: ";"
|
|
270
|
+
}[options.style] || "&";
|
|
271
|
+
const values = [];
|
|
272
|
+
for (const v of value) if (options.style === "simple" || options.style === "label") values.push(options.allowReserved === true ? v : encodeURIComponent(v));
|
|
273
|
+
else values.push(serializePrimitiveParam(name, v, options));
|
|
274
|
+
return options.style === "label" || options.style === "matrix" ? `${joiner}${values.join(joiner)}` : values.join(joiner);
|
|
275
|
+
}
|
|
276
|
+
function createQuerySerializer(options) {
|
|
277
|
+
return function querySerializer(queryParams) {
|
|
278
|
+
const search = [];
|
|
279
|
+
if (queryParams && typeof queryParams === "object") for (const name in queryParams) {
|
|
280
|
+
const value = queryParams[name];
|
|
281
|
+
if (value === void 0 || value === null) continue;
|
|
282
|
+
if (Array.isArray(value)) {
|
|
283
|
+
if (value.length === 0) continue;
|
|
284
|
+
search.push(serializeArrayParam(name, value, {
|
|
285
|
+
style: "form",
|
|
286
|
+
explode: true,
|
|
287
|
+
...options?.array,
|
|
288
|
+
allowReserved: options?.allowReserved || false
|
|
289
|
+
}));
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (typeof value === "object") {
|
|
293
|
+
search.push(serializeObjectParam(name, value, {
|
|
294
|
+
style: "deepObject",
|
|
295
|
+
explode: true,
|
|
296
|
+
...options?.object,
|
|
297
|
+
allowReserved: options?.allowReserved || false
|
|
298
|
+
}));
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
search.push(serializePrimitiveParam(name, value, options));
|
|
302
|
+
}
|
|
303
|
+
return search.join("&");
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function defaultPathSerializer(pathname, pathParams) {
|
|
307
|
+
let nextURL = pathname;
|
|
308
|
+
for (const match of pathname.match(PATH_PARAM_RE) ?? []) {
|
|
309
|
+
let name = match.substring(1, match.length - 1);
|
|
310
|
+
let explode = false;
|
|
311
|
+
let style = "simple";
|
|
312
|
+
if (name.endsWith("*")) {
|
|
313
|
+
explode = true;
|
|
314
|
+
name = name.substring(0, name.length - 1);
|
|
315
|
+
}
|
|
316
|
+
if (name.startsWith(".")) {
|
|
317
|
+
style = "label";
|
|
318
|
+
name = name.substring(1);
|
|
319
|
+
} else if (name.startsWith(";")) {
|
|
320
|
+
style = "matrix";
|
|
321
|
+
name = name.substring(1);
|
|
322
|
+
}
|
|
323
|
+
if (!pathParams || pathParams[name] === void 0 || pathParams[name] === null) continue;
|
|
324
|
+
const value = pathParams[name];
|
|
325
|
+
if (Array.isArray(value)) {
|
|
326
|
+
nextURL = nextURL.replace(match, serializeArrayParam(name, value, {
|
|
327
|
+
style,
|
|
328
|
+
explode
|
|
329
|
+
}));
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (typeof value === "object") {
|
|
333
|
+
nextURL = nextURL.replace(match, serializeObjectParam(name, value, {
|
|
334
|
+
style,
|
|
335
|
+
explode
|
|
336
|
+
}));
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (style === "matrix") {
|
|
340
|
+
nextURL = nextURL.replace(match, `;${serializePrimitiveParam(name, value)}`);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
nextURL = nextURL.replace(match, style === "label" ? `.${encodeURIComponent(value)}` : encodeURIComponent(value));
|
|
344
|
+
}
|
|
345
|
+
return nextURL;
|
|
346
|
+
}
|
|
347
|
+
function defaultBodySerializer(body, headers) {
|
|
348
|
+
if (body instanceof FormData) return body;
|
|
349
|
+
if (headers) {
|
|
350
|
+
if ((headers.get instanceof Function ? headers.get("Content-Type") ?? headers.get("content-type") : headers["Content-Type"] ?? headers["content-type"]) === "application/x-www-form-urlencoded") return new URLSearchParams(body).toString();
|
|
351
|
+
}
|
|
352
|
+
return JSON.stringify(body);
|
|
353
|
+
}
|
|
354
|
+
function createFinalURL(pathname, options) {
|
|
355
|
+
let finalURL = `${options.baseUrl}${pathname}`;
|
|
356
|
+
if (options.params?.path) finalURL = options.pathSerializer(finalURL, options.params.path);
|
|
357
|
+
let search = options.querySerializer(options.params.query ?? {});
|
|
358
|
+
if (search.startsWith("?")) search = search.substring(1);
|
|
359
|
+
if (search) finalURL += `?${search}`;
|
|
360
|
+
return finalURL;
|
|
361
|
+
}
|
|
362
|
+
function mergeHeaders(...allHeaders) {
|
|
363
|
+
const finalHeaders = new Headers();
|
|
364
|
+
for (const h of allHeaders) {
|
|
365
|
+
if (!h || typeof h !== "object") continue;
|
|
366
|
+
const iterator = h instanceof Headers ? h.entries() : Object.entries(h);
|
|
367
|
+
for (const [k, v] of iterator) if (v === null) finalHeaders.delete(k);
|
|
368
|
+
else if (Array.isArray(v)) for (const v2 of v) finalHeaders.append(k, v2);
|
|
369
|
+
else if (v !== void 0) finalHeaders.set(k, v);
|
|
370
|
+
}
|
|
371
|
+
return finalHeaders;
|
|
372
|
+
}
|
|
373
|
+
function removeTrailingSlash(url) {
|
|
374
|
+
if (url.endsWith("/")) return url.substring(0, url.length - 1);
|
|
375
|
+
return url;
|
|
376
|
+
}
|
|
377
|
+
String.fromCharCode;
|
|
378
|
+
const TRAILING_SLASH_RE = /\/$|\/\?|\/#/;
|
|
379
|
+
const JOIN_LEADING_SLASH_RE = /^\.?\//;
|
|
380
|
+
function hasTrailingSlash(input = "", respectQueryAndFragment) {
|
|
381
|
+
if (!respectQueryAndFragment) return input.endsWith("/");
|
|
382
|
+
return TRAILING_SLASH_RE.test(input);
|
|
383
|
+
}
|
|
384
|
+
function withTrailingSlash(input = "", respectQueryAndFragment) {
|
|
385
|
+
if (!respectQueryAndFragment) return input.endsWith("/") ? input : input + "/";
|
|
386
|
+
if (hasTrailingSlash(input, true)) return input || "/";
|
|
387
|
+
let path = input;
|
|
388
|
+
let fragment = "";
|
|
389
|
+
const fragmentIndex = input.indexOf("#");
|
|
390
|
+
if (fragmentIndex !== -1) {
|
|
391
|
+
path = input.slice(0, fragmentIndex);
|
|
392
|
+
fragment = input.slice(fragmentIndex);
|
|
393
|
+
if (!path) return fragment;
|
|
394
|
+
}
|
|
395
|
+
const [s0, ...s] = path.split("?");
|
|
396
|
+
return s0 + "/" + (s.length > 0 ? `?${s.join("?")}` : "") + fragment;
|
|
397
|
+
}
|
|
398
|
+
function isNonEmptyURL(url) {
|
|
399
|
+
return url && url !== "/";
|
|
400
|
+
}
|
|
401
|
+
function joinURL(base, ...input) {
|
|
402
|
+
let url = base || "";
|
|
403
|
+
for (const segment of input.filter((url2) => isNonEmptyURL(url2))) if (url) {
|
|
404
|
+
const _segment = segment.replace(JOIN_LEADING_SLASH_RE, "");
|
|
405
|
+
url = withTrailingSlash(url) + _segment;
|
|
406
|
+
} else url = segment;
|
|
407
|
+
return url;
|
|
408
|
+
}
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/core.ts
|
|
411
|
+
const DRIVE_API_ERROR_CODES = Object.freeze([
|
|
412
|
+
"authentication_required",
|
|
413
|
+
"identity_authority_unavailable",
|
|
414
|
+
"permission_denied",
|
|
415
|
+
"resource_not_found",
|
|
416
|
+
"invalid_request",
|
|
417
|
+
"invalid_pagination_cursor",
|
|
418
|
+
"invalid_idempotency_key",
|
|
419
|
+
"precondition_failed",
|
|
420
|
+
"idempotency_conflict",
|
|
421
|
+
"quota_exceeded",
|
|
422
|
+
"service_unavailable",
|
|
423
|
+
"internal_error"
|
|
424
|
+
]);
|
|
425
|
+
const DRIVE_API_ERROR_MESSAGES = Object.freeze({
|
|
426
|
+
authentication_required: "Authentication is required.",
|
|
427
|
+
identity_authority_unavailable: "Authentication is temporarily unavailable.",
|
|
428
|
+
permission_denied: "The requested action is not permitted.",
|
|
429
|
+
resource_not_found: "The requested resource was not found.",
|
|
430
|
+
invalid_request: "The request is invalid.",
|
|
431
|
+
invalid_pagination_cursor: "The pagination cursor is invalid.",
|
|
432
|
+
invalid_idempotency_key: "The idempotency key is invalid.",
|
|
433
|
+
precondition_failed: "The request precondition failed.",
|
|
434
|
+
idempotency_conflict: "The idempotency key conflicts with an earlier request.",
|
|
435
|
+
quota_exceeded: "The workspace storage quota would be exceeded.",
|
|
436
|
+
service_unavailable: "The service is temporarily unavailable.",
|
|
437
|
+
internal_error: "The request could not be completed."
|
|
438
|
+
});
|
|
439
|
+
const SDK_MESSAGES = Object.freeze({
|
|
440
|
+
invalid_input: "The Drive SDK input is invalid.",
|
|
441
|
+
credential_unavailable: "A Drive credential could not be obtained.",
|
|
442
|
+
network_error: "The Drive request could not reach the service.",
|
|
443
|
+
timeout: "The Drive request timed out.",
|
|
444
|
+
aborted: "The Drive request was aborted.",
|
|
445
|
+
invalid_response: "Drive returned an invalid response."
|
|
446
|
+
});
|
|
447
|
+
var DriveError = class extends Error {
|
|
448
|
+
code;
|
|
449
|
+
status;
|
|
450
|
+
requestId;
|
|
451
|
+
details;
|
|
452
|
+
constructor(code, options = {}) {
|
|
453
|
+
super(code in DRIVE_API_ERROR_MESSAGES ? DRIVE_API_ERROR_MESSAGES[code] : SDK_MESSAGES[code]);
|
|
454
|
+
this.name = "DriveError";
|
|
455
|
+
this.code = code;
|
|
456
|
+
this.status = options.status ?? null;
|
|
457
|
+
this.requestId = options.requestId ?? null;
|
|
458
|
+
this.details = Object.freeze({});
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
//#endregion
|
|
462
|
+
//#region src/internal/client.ts
|
|
463
|
+
const ERROR_BODY_LIMIT = 16 * 1024;
|
|
464
|
+
const SUCCESS_BODY_LIMIT = 256 * 1024;
|
|
465
|
+
const RESPONSE_CHUNK_LIMIT = 256;
|
|
466
|
+
const RESPONSE_DEADLINE_MILLISECONDS = 5e3;
|
|
467
|
+
const DEFAULT_REQUEST_TIMEOUT_MILLISECONDS = 3e4;
|
|
468
|
+
const MAX_REQUEST_TIMEOUT_MILLISECONDS = 12e4;
|
|
469
|
+
const REQUEST_ID = /^[A-Za-z0-9_-]{16,128}$/;
|
|
470
|
+
const COLLECTION_ETAG = /^"drive\.permission-collection\.v1\.(0|[1-9][0-9]*)"$/;
|
|
471
|
+
const GRANT_ETAG = /^"drive\.permission-grant\.v1\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)"$/;
|
|
472
|
+
const LIST_ETAG = /^"drive\.permission-list\.v1\.[0-9a-f]{64}"$/;
|
|
473
|
+
const ITEM_ID = /^drive\.item\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
474
|
+
const WORKSPACE_ID = /^drive\.workspace\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
475
|
+
const VOLUME_ID = /^drive\.volume\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
476
|
+
const GRANT_ID = /^drive\.grant\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
477
|
+
const UPLOAD_ID = /^drive\.upload\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
478
|
+
const CURSOR = /^drive\.cursor\.v1\.[A-Za-z0-9_-]+$/;
|
|
479
|
+
const AUTH_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
480
|
+
const PERMISSION_ACTIONS = Object.freeze([
|
|
481
|
+
"items:content:read",
|
|
482
|
+
"items:create",
|
|
483
|
+
"items:delete",
|
|
484
|
+
"items:metadata:read",
|
|
485
|
+
"items:permissions:read",
|
|
486
|
+
"items:update"
|
|
487
|
+
]);
|
|
488
|
+
const EFFECTIVE_ACTIONS = Object.freeze([
|
|
489
|
+
"items:content:read",
|
|
490
|
+
"items:create",
|
|
491
|
+
"items:delete",
|
|
492
|
+
"items:metadata:read",
|
|
493
|
+
"items:permissions:manage",
|
|
494
|
+
"items:permissions:read",
|
|
495
|
+
"items:update"
|
|
496
|
+
]);
|
|
497
|
+
const IDEMPOTENCY_KEY = /^[A-Za-z0-9_-]{16,128}$/;
|
|
498
|
+
const MAX_UPLOAD_BYTE_LENGTH = 33554432e4;
|
|
499
|
+
const UPLOAD_QUOTA_EXPIRY_MILLISECONDS = 864e5;
|
|
500
|
+
function createDriveClientInternal(config) {
|
|
501
|
+
let runtime;
|
|
502
|
+
try {
|
|
503
|
+
runtime = Object.freeze({
|
|
504
|
+
baseUrl: parseBaseUrl(config.baseUrl),
|
|
505
|
+
fetchImplementation: config.fetch ?? globalThis.fetch,
|
|
506
|
+
getCredential: config.getCredential,
|
|
507
|
+
timeoutMilliseconds: parseTimeoutMilliseconds(config.timeoutMilliseconds)
|
|
508
|
+
});
|
|
509
|
+
} catch {
|
|
510
|
+
throw new DriveError("invalid_input");
|
|
511
|
+
}
|
|
512
|
+
if (typeof runtime.fetchImplementation !== "function" || typeof runtime.getCredential !== "function") throw new DriveError("invalid_input");
|
|
513
|
+
return Object.freeze({
|
|
514
|
+
listFolderChildren: (input) => listFolderChildren(runtime, input),
|
|
515
|
+
createFolder: (input) => createFolder(runtime, input),
|
|
516
|
+
listItemPermissions: (input) => listItemPermissions(runtime, input),
|
|
517
|
+
readEffectiveItemPermissions: (input) => readEffectiveItemPermissions(runtime, input),
|
|
518
|
+
createItemPermission: (input) => createItemPermission(runtime, input),
|
|
519
|
+
updateItemPermission: (input) => updateItemPermission(runtime, input),
|
|
520
|
+
revokeItemPermission: (input) => revokeItemPermission(runtime, input),
|
|
521
|
+
createUpload: (input) => createUpload(runtime, input),
|
|
522
|
+
abortUpload: (input) => abortUpload(runtime, input)
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
async function listFolderChildren(runtime, input) {
|
|
526
|
+
const validated = validatePageInput(input);
|
|
527
|
+
return execute(runtime, validated.signal, 409, (api, requestOptions) => api.GET("/api/v1/workspaces/{workspaceId}/items/{itemId}/children", {
|
|
528
|
+
...operationOptions("listFolderChildren", { params: {
|
|
529
|
+
path: resourcePath(validated),
|
|
530
|
+
query: pageQuery(validated)
|
|
531
|
+
} }),
|
|
532
|
+
...requestOptions
|
|
533
|
+
}), [200], (value) => isFolderPage(value, validated.workspaceId, validated.itemId));
|
|
534
|
+
}
|
|
535
|
+
async function createFolder(runtime, input) {
|
|
536
|
+
const validated = validateCreateFolderInput(input);
|
|
537
|
+
return execute(runtime, validated.signal, 409, (api, requestOptions) => api.POST("/api/v1/workspaces/{workspaceId}/items/{itemId}/children", {
|
|
538
|
+
...operationOptions("createFolder", {
|
|
539
|
+
params: {
|
|
540
|
+
path: resourcePath(validated),
|
|
541
|
+
header: { "Idempotency-Key": validated.idempotencyKey }
|
|
542
|
+
},
|
|
543
|
+
body: { name: validated.name }
|
|
544
|
+
}),
|
|
545
|
+
...requestOptions
|
|
546
|
+
}), [200, 201], (value) => isFolderItem(value) && value.workspaceId === validated.workspaceId && value.parentItemId === validated.itemId && value.kind === "folder" && value.name === validated.name);
|
|
547
|
+
}
|
|
548
|
+
async function listItemPermissions(runtime, input) {
|
|
549
|
+
const validated = validatePageInput(input);
|
|
550
|
+
return execute(runtime, validated.signal, 412, (api, requestOptions) => api.GET("/api/v1/workspaces/{workspaceId}/items/{itemId}/permissions", {
|
|
551
|
+
...operationOptions("listItemPermissions", { params: {
|
|
552
|
+
path: resourcePath(validated),
|
|
553
|
+
query: pageQuery(validated)
|
|
554
|
+
} }),
|
|
555
|
+
...requestOptions
|
|
556
|
+
}), [200], (value, _status, headers) => isPermissionPage(value, headers, validated.itemId));
|
|
557
|
+
}
|
|
558
|
+
async function readEffectiveItemPermissions(runtime, input) {
|
|
559
|
+
const validated = validateResourceInput(input);
|
|
560
|
+
return execute(runtime, validated.signal, 412, (api, requestOptions) => api.GET("/api/v1/workspaces/{workspaceId}/items/{itemId}/permissions/effective", {
|
|
561
|
+
...operationOptions("readEffectiveItemPermissions", { params: { path: resourcePath(validated) } }),
|
|
562
|
+
...requestOptions
|
|
563
|
+
}), [200], (value) => isEffectivePermissionResponse(value, validated.itemId));
|
|
564
|
+
}
|
|
565
|
+
async function createItemPermission(runtime, input) {
|
|
566
|
+
const validated = validateCreatePermissionInput(input);
|
|
567
|
+
return execute(runtime, validated.signal, 412, (api, requestOptions) => api.POST("/api/v1/workspaces/{workspaceId}/items/{itemId}/permissions", {
|
|
568
|
+
...operationOptions("createItemPermission", {
|
|
569
|
+
params: {
|
|
570
|
+
path: resourcePath(validated),
|
|
571
|
+
header: {
|
|
572
|
+
"Idempotency-Key": validated.idempotencyKey,
|
|
573
|
+
"If-Match": validated.ifMatch
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
body: {
|
|
577
|
+
grantee: validated.grantee,
|
|
578
|
+
actions: validated.actions,
|
|
579
|
+
expiresAtEpochMilliseconds: validated.expiresAtEpochMilliseconds
|
|
580
|
+
}
|
|
581
|
+
}),
|
|
582
|
+
...requestOptions
|
|
583
|
+
}), [200, 201], (value, status, headers) => isPermissionMutation(value, validated.itemId) && (status === 201 && value.outcome === "committed" || status === 200 && value.outcome === "replayed") && (status === 200 || value.grant.availability !== "revoked") && mutationValidatorsAreCoherent(headers));
|
|
584
|
+
}
|
|
585
|
+
async function updateItemPermission(runtime, input) {
|
|
586
|
+
const validated = validateUpdatePermissionInput(input);
|
|
587
|
+
const body = {};
|
|
588
|
+
if (validated.actions !== void 0) body.actions = validated.actions;
|
|
589
|
+
if (validated.expiresAtEpochMilliseconds !== void 0) body.expiresAtEpochMilliseconds = validated.expiresAtEpochMilliseconds;
|
|
590
|
+
return execute(runtime, validated.signal, 412, (api, requestOptions) => api.PATCH("/api/v1/workspaces/{workspaceId}/items/{itemId}/permissions/{grantId}", {
|
|
591
|
+
...operationOptions("updateItemPermission", {
|
|
592
|
+
params: {
|
|
593
|
+
path: {
|
|
594
|
+
...resourcePath(validated),
|
|
595
|
+
grantId: validated.grantId
|
|
596
|
+
},
|
|
597
|
+
header: {
|
|
598
|
+
"Idempotency-Key": validated.idempotencyKey,
|
|
599
|
+
"If-Match": validated.ifMatch
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
body
|
|
603
|
+
}),
|
|
604
|
+
...requestOptions
|
|
605
|
+
}), [200], (value, _status, headers) => isPermissionMutation(value, validated.itemId, validated.grantId) && value.grant.availability !== "revoked" && mutationValidatorsAreCoherent(headers));
|
|
606
|
+
}
|
|
607
|
+
async function revokeItemPermission(runtime, input) {
|
|
608
|
+
const validated = validateGrantMutationInput(input);
|
|
609
|
+
return execute(runtime, validated.signal, 412, (api, requestOptions) => api.DELETE("/api/v1/workspaces/{workspaceId}/items/{itemId}/permissions/{grantId}", {
|
|
610
|
+
...operationOptions("revokeItemPermission", { params: {
|
|
611
|
+
path: {
|
|
612
|
+
...resourcePath(validated),
|
|
613
|
+
grantId: validated.grantId
|
|
614
|
+
},
|
|
615
|
+
header: {
|
|
616
|
+
"Idempotency-Key": validated.idempotencyKey,
|
|
617
|
+
"If-Match": validated.ifMatch
|
|
618
|
+
}
|
|
619
|
+
} }),
|
|
620
|
+
...requestOptions
|
|
621
|
+
}), [200], (value, _status, headers) => isPermissionMutation(value, validated.itemId, validated.grantId) && value.grant.availability === "revoked" && parseCollectionEtag(headers.get("drive-permission-collection-etag") ?? "") !== void 0 && headers.get("drive-permission-grant-etag") === null);
|
|
622
|
+
}
|
|
623
|
+
async function createUpload(runtime, input) {
|
|
624
|
+
const validated = validateCreateUploadInput(input);
|
|
625
|
+
return execute(runtime, validated.signal, 409, (api, requestOptions) => api.POST("/api/v1/workspaces/{workspaceId}/uploads", {
|
|
626
|
+
...operationOptions("createUpload", {
|
|
627
|
+
params: {
|
|
628
|
+
path: { workspaceId: validated.workspaceId },
|
|
629
|
+
header: { "Idempotency-Key": validated.idempotencyKey }
|
|
630
|
+
},
|
|
631
|
+
body: {
|
|
632
|
+
parentItemId: validated.parentItemId,
|
|
633
|
+
expectedByteLength: validated.expectedByteLength
|
|
634
|
+
}
|
|
635
|
+
}),
|
|
636
|
+
...requestOptions
|
|
637
|
+
}), [200, 201], (value) => isUploadQuotaReservation(value) && value.workspaceId === validated.workspaceId && value.parentItemId === validated.parentItemId && value.expectedByteLength === validated.expectedByteLength && value.state === "reserved" && value.terminalReason === null && value.terminalAtEpochMilliseconds === null, true);
|
|
638
|
+
}
|
|
639
|
+
async function abortUpload(runtime, input) {
|
|
640
|
+
const validated = validateAbortUploadInput(input);
|
|
641
|
+
return execute(runtime, validated.signal, 409, (api, requestOptions) => api.DELETE("/api/v1/workspaces/{workspaceId}/uploads/{uploadId}", {
|
|
642
|
+
...operationOptions("abortUpload", { params: { path: {
|
|
643
|
+
workspaceId: validated.workspaceId,
|
|
644
|
+
uploadId: validated.uploadId
|
|
645
|
+
} } }),
|
|
646
|
+
...requestOptions
|
|
647
|
+
}), [200], (value) => isUploadQuotaReservation(value) && value.workspaceId === validated.workspaceId && value.uploadId === validated.uploadId && value.state === "released" && value.terminalReason === "aborted" && value.terminalAtEpochMilliseconds !== null);
|
|
648
|
+
}
|
|
649
|
+
async function execute(runtime, signal, preconditionStatus, send, successStatuses, guard, allowsQuotaExceeded = false) {
|
|
650
|
+
const operation = createOperationSignal(signal, runtime.timeoutMilliseconds);
|
|
651
|
+
try {
|
|
652
|
+
const operationSignal = operation.signal;
|
|
653
|
+
throwIfAborted(operationSignal);
|
|
654
|
+
const credential = await obtainCredential(runtime.getCredential, operationSignal);
|
|
655
|
+
let capturedResponse;
|
|
656
|
+
await send(createClient({
|
|
657
|
+
baseUrl: runtime.baseUrl,
|
|
658
|
+
fetch: async (request) => {
|
|
659
|
+
try {
|
|
660
|
+
const response = await raceWithAbort(runtime.fetchImplementation(request), operationSignal);
|
|
661
|
+
if (!(response instanceof Response)) throw new DriveError("invalid_response");
|
|
662
|
+
capturedResponse = response;
|
|
663
|
+
} catch (error) {
|
|
664
|
+
if (error instanceof DriveError) throw error;
|
|
665
|
+
throw operationSignal.aborted ? new DriveError("aborted") : new DriveError("network_error");
|
|
666
|
+
}
|
|
667
|
+
return new Response(null, { status: 204 });
|
|
668
|
+
}
|
|
669
|
+
}), {
|
|
670
|
+
cache: "no-store",
|
|
671
|
+
credentials: "omit",
|
|
672
|
+
headers: new Headers({ authorization: `Bearer ${credential}` }),
|
|
673
|
+
signal: operationSignal
|
|
674
|
+
});
|
|
675
|
+
if (!capturedResponse) throw new DriveError("network_error");
|
|
676
|
+
const response = capturedResponse;
|
|
677
|
+
const requestId = safeRequestId(response.headers.get("x-request-id"));
|
|
678
|
+
if (requestId === null) {
|
|
679
|
+
cancelResponse(response);
|
|
680
|
+
throw new DriveError("invalid_response", { status: response.status });
|
|
681
|
+
}
|
|
682
|
+
if (!successStatuses.includes(response.status)) throw await normalizeError(response, requestId, operationSignal, preconditionStatus, allowsQuotaExceeded);
|
|
683
|
+
const { value } = await parseBoundedJson(response, SUCCESS_BODY_LIMIT, response.status, requestId, operationSignal);
|
|
684
|
+
const status = response.status;
|
|
685
|
+
if (!guard(value, status, response.headers)) throw new DriveError("invalid_response", {
|
|
686
|
+
status,
|
|
687
|
+
requestId
|
|
688
|
+
});
|
|
689
|
+
return Object.freeze({
|
|
690
|
+
data: value,
|
|
691
|
+
status,
|
|
692
|
+
requestId,
|
|
693
|
+
validators: readValidators(response.headers)
|
|
694
|
+
});
|
|
695
|
+
} catch (error) {
|
|
696
|
+
if (operation.didTimeout()) throw new DriveError("timeout");
|
|
697
|
+
if (error instanceof DriveError) throw error;
|
|
698
|
+
throw operation.signal.aborted ? new DriveError("aborted") : new DriveError("network_error");
|
|
699
|
+
} finally {
|
|
700
|
+
operation.dispose();
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function parseTimeoutMilliseconds(value) {
|
|
704
|
+
if (value === void 0) return DEFAULT_REQUEST_TIMEOUT_MILLISECONDS;
|
|
705
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > MAX_REQUEST_TIMEOUT_MILLISECONDS) throw new DriveError("invalid_input");
|
|
706
|
+
return value;
|
|
707
|
+
}
|
|
708
|
+
function createOperationSignal(callerSignal, timeoutMilliseconds) {
|
|
709
|
+
const controller = new AbortController();
|
|
710
|
+
let timedOut = false;
|
|
711
|
+
const onAbort = () => controller.abort();
|
|
712
|
+
if (callerSignal?.aborted) controller.abort();
|
|
713
|
+
else callerSignal?.addEventListener("abort", onAbort, { once: true });
|
|
714
|
+
const timeout = setTimeout(() => {
|
|
715
|
+
timedOut = true;
|
|
716
|
+
controller.abort();
|
|
717
|
+
}, timeoutMilliseconds);
|
|
718
|
+
return {
|
|
719
|
+
signal: controller.signal,
|
|
720
|
+
didTimeout: () => timedOut,
|
|
721
|
+
dispose: () => {
|
|
722
|
+
clearTimeout(timeout);
|
|
723
|
+
callerSignal?.removeEventListener("abort", onAbort);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function parseBaseUrl(value) {
|
|
728
|
+
if (typeof value !== "string") throw new TypeError("Drive base URL is invalid");
|
|
729
|
+
let url;
|
|
730
|
+
try {
|
|
731
|
+
url = new URL(value);
|
|
732
|
+
} catch {
|
|
733
|
+
throw new TypeError("Drive base URL is invalid");
|
|
734
|
+
}
|
|
735
|
+
const localHttp = url.protocol === "http:" && [
|
|
736
|
+
"localhost",
|
|
737
|
+
"127.0.0.1",
|
|
738
|
+
"[::1]"
|
|
739
|
+
].includes(url.hostname);
|
|
740
|
+
if (url.protocol !== "https:" && !localHttp || url.username || url.password || url.search || url.hash || !url.host) throw new TypeError("Drive base URL is invalid");
|
|
741
|
+
return joinURL(url.origin, url.pathname);
|
|
742
|
+
}
|
|
743
|
+
async function obtainCredential(provider, signal) {
|
|
744
|
+
throwIfAborted(signal);
|
|
745
|
+
let credential;
|
|
746
|
+
try {
|
|
747
|
+
credential = await raceWithAbort(provider({ signal }), signal);
|
|
748
|
+
} catch (error) {
|
|
749
|
+
if (error instanceof DriveError && error.code === "aborted") throw error;
|
|
750
|
+
throw signal.aborted ? new DriveError("aborted") : new DriveError("credential_unavailable");
|
|
751
|
+
}
|
|
752
|
+
if (typeof credential !== "string" || credential.length < 1 || credential.length > 16384 || !/^[\x21-\x7e]+$/.test(credential)) throw new DriveError("credential_unavailable");
|
|
753
|
+
return credential;
|
|
754
|
+
}
|
|
755
|
+
function throwIfAborted(signal) {
|
|
756
|
+
if (signal.aborted) throw new DriveError("aborted");
|
|
757
|
+
}
|
|
758
|
+
async function raceWithAbort(promise, signal) {
|
|
759
|
+
throwIfAborted(signal);
|
|
760
|
+
let removeAbort = () => {};
|
|
761
|
+
const aborted = new Promise((_, reject) => {
|
|
762
|
+
const onAbort = () => reject(new DriveError("aborted"));
|
|
763
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
764
|
+
removeAbort = () => signal.removeEventListener("abort", onAbort);
|
|
765
|
+
});
|
|
766
|
+
try {
|
|
767
|
+
return await Promise.race([promise, aborted]);
|
|
768
|
+
} finally {
|
|
769
|
+
removeAbort();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function validateResourceInput(value) {
|
|
773
|
+
const input = captureRecord(value, ["workspaceId", "itemId"], ["signal"]);
|
|
774
|
+
const workspaceId = input.workspaceId;
|
|
775
|
+
const itemId = input.itemId;
|
|
776
|
+
const signal = validateSignal(input.signal);
|
|
777
|
+
if (typeof workspaceId !== "string" || !WORKSPACE_ID.test(workspaceId) || typeof itemId !== "string" || !ITEM_ID.test(itemId)) throw new DriveError("invalid_input");
|
|
778
|
+
return {
|
|
779
|
+
workspaceId,
|
|
780
|
+
itemId,
|
|
781
|
+
...signal === void 0 ? {} : { signal }
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
function validatePageInput(value) {
|
|
785
|
+
const input = captureRecord(value, ["workspaceId", "itemId"], [
|
|
786
|
+
"signal",
|
|
787
|
+
"limit",
|
|
788
|
+
"cursor"
|
|
789
|
+
]);
|
|
790
|
+
const resource = validateResourceInput({
|
|
791
|
+
workspaceId: input.workspaceId,
|
|
792
|
+
itemId: input.itemId,
|
|
793
|
+
...input.signal === void 0 ? {} : { signal: input.signal }
|
|
794
|
+
});
|
|
795
|
+
const limit = input.limit;
|
|
796
|
+
const cursor = input.cursor;
|
|
797
|
+
if (limit !== void 0 && (!Number.isSafeInteger(limit) || typeof limit !== "number" || limit < 1 || limit > 50) || cursor !== void 0 && (typeof cursor !== "string" || cursor.length > 699 || !CURSOR.test(cursor))) throw new DriveError("invalid_input");
|
|
798
|
+
return {
|
|
799
|
+
...resource,
|
|
800
|
+
...limit === void 0 ? {} : { limit },
|
|
801
|
+
...cursor === void 0 ? {} : { cursor }
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function validateCreateFolderInput(value) {
|
|
805
|
+
const input = captureRecord(value, [
|
|
806
|
+
"workspaceId",
|
|
807
|
+
"itemId",
|
|
808
|
+
"name",
|
|
809
|
+
"idempotencyKey"
|
|
810
|
+
], ["signal"]);
|
|
811
|
+
const resource = validateResourceInput({
|
|
812
|
+
workspaceId: input.workspaceId,
|
|
813
|
+
itemId: input.itemId,
|
|
814
|
+
...input.signal === void 0 ? {} : { signal: input.signal }
|
|
815
|
+
});
|
|
816
|
+
if (!isSafeFilename(input.name) || !isIdempotencyKey(input.idempotencyKey)) throw new DriveError("invalid_input");
|
|
817
|
+
return {
|
|
818
|
+
...resource,
|
|
819
|
+
name: input.name,
|
|
820
|
+
idempotencyKey: input.idempotencyKey
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
function validateCreatePermissionInput(value) {
|
|
824
|
+
const input = captureRecord(value, [
|
|
825
|
+
"workspaceId",
|
|
826
|
+
"itemId",
|
|
827
|
+
"grantee",
|
|
828
|
+
"actions",
|
|
829
|
+
"expiresAtEpochMilliseconds",
|
|
830
|
+
"ifMatch",
|
|
831
|
+
"idempotencyKey"
|
|
832
|
+
], ["signal"]);
|
|
833
|
+
const resource = validateResourceInput({
|
|
834
|
+
workspaceId: input.workspaceId,
|
|
835
|
+
itemId: input.itemId,
|
|
836
|
+
...input.signal === void 0 ? {} : { signal: input.signal }
|
|
837
|
+
});
|
|
838
|
+
const grantee = validateGrantee(input.grantee);
|
|
839
|
+
const actions = validateInputActions(input.actions);
|
|
840
|
+
const expiry = input.expiresAtEpochMilliseconds;
|
|
841
|
+
if (!isExpiry(expiry) || typeof input.ifMatch !== "string" || parseCollectionEtag(input.ifMatch) === void 0 || !isIdempotencyKey(input.idempotencyKey)) throw new DriveError("invalid_input");
|
|
842
|
+
return {
|
|
843
|
+
...resource,
|
|
844
|
+
grantee,
|
|
845
|
+
actions,
|
|
846
|
+
expiresAtEpochMilliseconds: expiry,
|
|
847
|
+
ifMatch: input.ifMatch,
|
|
848
|
+
idempotencyKey: input.idempotencyKey
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function validateGrantMutationInput(value) {
|
|
852
|
+
const input = captureRecord(value, [
|
|
853
|
+
"workspaceId",
|
|
854
|
+
"itemId",
|
|
855
|
+
"grantId",
|
|
856
|
+
"ifMatch",
|
|
857
|
+
"idempotencyKey"
|
|
858
|
+
], ["signal"]);
|
|
859
|
+
const resource = validateResourceInput({
|
|
860
|
+
workspaceId: input.workspaceId,
|
|
861
|
+
itemId: input.itemId,
|
|
862
|
+
...input.signal === void 0 ? {} : { signal: input.signal }
|
|
863
|
+
});
|
|
864
|
+
if (typeof input.grantId !== "string" || !GRANT_ID.test(input.grantId) || typeof input.ifMatch !== "string" || parseGrantEtag(input.ifMatch) === void 0 || !isIdempotencyKey(input.idempotencyKey)) throw new DriveError("invalid_input");
|
|
865
|
+
return {
|
|
866
|
+
...resource,
|
|
867
|
+
grantId: input.grantId,
|
|
868
|
+
ifMatch: input.ifMatch,
|
|
869
|
+
idempotencyKey: input.idempotencyKey
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function validateUpdatePermissionInput(value) {
|
|
873
|
+
const input = captureRecord(value, [
|
|
874
|
+
"workspaceId",
|
|
875
|
+
"itemId",
|
|
876
|
+
"grantId",
|
|
877
|
+
"ifMatch",
|
|
878
|
+
"idempotencyKey"
|
|
879
|
+
], [
|
|
880
|
+
"signal",
|
|
881
|
+
"actions",
|
|
882
|
+
"expiresAtEpochMilliseconds"
|
|
883
|
+
]);
|
|
884
|
+
const mutation = validateGrantMutationInput({
|
|
885
|
+
workspaceId: input.workspaceId,
|
|
886
|
+
itemId: input.itemId,
|
|
887
|
+
grantId: input.grantId,
|
|
888
|
+
ifMatch: input.ifMatch,
|
|
889
|
+
idempotencyKey: input.idempotencyKey,
|
|
890
|
+
...input.signal === void 0 ? {} : { signal: input.signal }
|
|
891
|
+
});
|
|
892
|
+
const actions = input.actions === void 0 ? void 0 : validateInputActions(input.actions);
|
|
893
|
+
const expiry = input.expiresAtEpochMilliseconds;
|
|
894
|
+
if (actions === void 0 && expiry === void 0 || expiry !== void 0 && !isExpiry(expiry)) throw new DriveError("invalid_input");
|
|
895
|
+
return {
|
|
896
|
+
...mutation,
|
|
897
|
+
...actions === void 0 ? {} : { actions },
|
|
898
|
+
...expiry === void 0 ? {} : { expiresAtEpochMilliseconds: expiry }
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function validateCreateUploadInput(value) {
|
|
902
|
+
const input = captureRecord(value, [
|
|
903
|
+
"workspaceId",
|
|
904
|
+
"parentItemId",
|
|
905
|
+
"expectedByteLength",
|
|
906
|
+
"idempotencyKey"
|
|
907
|
+
], ["signal"]);
|
|
908
|
+
const signal = validateSignal(input.signal);
|
|
909
|
+
if (typeof input.workspaceId !== "string" || !WORKSPACE_ID.test(input.workspaceId) || typeof input.parentItemId !== "string" || !ITEM_ID.test(input.parentItemId) || typeof input.expectedByteLength !== "number" || !Number.isSafeInteger(input.expectedByteLength) || input.expectedByteLength < 0 || input.expectedByteLength > MAX_UPLOAD_BYTE_LENGTH || !isIdempotencyKey(input.idempotencyKey)) throw new DriveError("invalid_input");
|
|
910
|
+
return {
|
|
911
|
+
workspaceId: input.workspaceId,
|
|
912
|
+
parentItemId: input.parentItemId,
|
|
913
|
+
expectedByteLength: input.expectedByteLength,
|
|
914
|
+
idempotencyKey: input.idempotencyKey,
|
|
915
|
+
...signal === void 0 ? {} : { signal }
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
function validateAbortUploadInput(value) {
|
|
919
|
+
const input = captureRecord(value, ["workspaceId", "uploadId"], ["signal"]);
|
|
920
|
+
const signal = validateSignal(input.signal);
|
|
921
|
+
if (typeof input.workspaceId !== "string" || !WORKSPACE_ID.test(input.workspaceId) || typeof input.uploadId !== "string" || !UPLOAD_ID.test(input.uploadId)) throw new DriveError("invalid_input");
|
|
922
|
+
return {
|
|
923
|
+
workspaceId: input.workspaceId,
|
|
924
|
+
uploadId: input.uploadId,
|
|
925
|
+
...signal === void 0 ? {} : { signal }
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
function captureRecord(value, required, optional) {
|
|
929
|
+
try {
|
|
930
|
+
if (typeof value !== "object" && typeof value !== "function" || value === null || Array.isArray(value)) throw new DriveError("invalid_input");
|
|
931
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
932
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
933
|
+
const allowed = new Set([...required, ...optional]);
|
|
934
|
+
if (keys.some((key) => typeof key !== "string" || !allowed.has(key)) || required.some((key) => !Object.hasOwn(descriptors, key))) throw new DriveError("invalid_input");
|
|
935
|
+
const result = {};
|
|
936
|
+
for (const key of keys) {
|
|
937
|
+
if (typeof key !== "string") throw new DriveError("invalid_input");
|
|
938
|
+
const descriptor = descriptors[key];
|
|
939
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) throw new DriveError("invalid_input");
|
|
940
|
+
result[key] = descriptor.value;
|
|
941
|
+
}
|
|
942
|
+
return result;
|
|
943
|
+
} catch (error) {
|
|
944
|
+
if (error instanceof DriveError) throw error;
|
|
945
|
+
throw new DriveError("invalid_input");
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function validateSignal(value) {
|
|
949
|
+
if (value === void 0) return void 0;
|
|
950
|
+
if (!(value instanceof AbortSignal)) throw new DriveError("invalid_input");
|
|
951
|
+
return value;
|
|
952
|
+
}
|
|
953
|
+
function validateGrantee(value) {
|
|
954
|
+
const type = captureRecord(value, ["type"], ["userId", "teamId"]);
|
|
955
|
+
if (type.type === "user") {
|
|
956
|
+
const user = captureRecord(value, ["type", "userId"], []);
|
|
957
|
+
if (typeof user.userId === "string" && AUTH_ID.test(user.userId)) return {
|
|
958
|
+
type: "user",
|
|
959
|
+
userId: user.userId
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
if (type.type === "team") {
|
|
963
|
+
const team = captureRecord(value, ["type", "teamId"], []);
|
|
964
|
+
if (typeof team.teamId === "string" && AUTH_ID.test(team.teamId)) return {
|
|
965
|
+
type: "team",
|
|
966
|
+
teamId: team.teamId
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
throw new DriveError("invalid_input");
|
|
970
|
+
}
|
|
971
|
+
function validateInputActions(value) {
|
|
972
|
+
const actions = captureArray(value);
|
|
973
|
+
if (actions.length < 1 || actions.length > PERMISSION_ACTIONS.length || new Set(actions).size !== actions.length || !actions.every((action) => PERMISSION_ACTIONS.includes(action))) throw new DriveError("invalid_input");
|
|
974
|
+
return actions;
|
|
975
|
+
}
|
|
976
|
+
function captureArray(value) {
|
|
977
|
+
try {
|
|
978
|
+
if (!Array.isArray(value)) throw new DriveError("invalid_input");
|
|
979
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
980
|
+
if (Reflect.ownKeys(descriptors).some((key) => typeof key !== "string")) throw new DriveError("invalid_input");
|
|
981
|
+
const result = [];
|
|
982
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
983
|
+
const descriptor = descriptors[String(index)];
|
|
984
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) throw new DriveError("invalid_input");
|
|
985
|
+
result.push(descriptor.value);
|
|
986
|
+
}
|
|
987
|
+
if (Reflect.ownKeys(descriptors).length !== value.length + 1) throw new DriveError("invalid_input");
|
|
988
|
+
return result;
|
|
989
|
+
} catch (error) {
|
|
990
|
+
if (error instanceof DriveError) throw error;
|
|
991
|
+
throw new DriveError("invalid_input");
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function isIdempotencyKey(value) {
|
|
995
|
+
return typeof value === "string" && IDEMPOTENCY_KEY.test(value);
|
|
996
|
+
}
|
|
997
|
+
function isExpiry(value) {
|
|
998
|
+
return value === null || isSafeInteger(value);
|
|
999
|
+
}
|
|
1000
|
+
function resourcePath(input) {
|
|
1001
|
+
return {
|
|
1002
|
+
workspaceId: input.workspaceId,
|
|
1003
|
+
itemId: input.itemId
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function pageQuery(input) {
|
|
1007
|
+
return {
|
|
1008
|
+
...input.limit === void 0 ? {} : { limit: input.limit },
|
|
1009
|
+
...input.cursor === void 0 ? {} : { cursor: input.cursor }
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
function operationOptions(_operationId, options) {
|
|
1013
|
+
return options;
|
|
1014
|
+
}
|
|
1015
|
+
async function normalizeError(response, headerRequestId, signal, preconditionStatus, allowsQuotaExceeded) {
|
|
1016
|
+
let parsed;
|
|
1017
|
+
try {
|
|
1018
|
+
parsed = await parseBoundedJson(response, ERROR_BODY_LIMIT, response.status, headerRequestId, signal);
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
if (error instanceof DriveError && error.code === "aborted") return error;
|
|
1021
|
+
return new DriveError("invalid_response", {
|
|
1022
|
+
status: response.status,
|
|
1023
|
+
requestId: headerRequestId
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
const value = parsed.value;
|
|
1027
|
+
if (!isExactRecord(value, ["error"]) || !isExactRecord(value.error, [
|
|
1028
|
+
"code",
|
|
1029
|
+
"message",
|
|
1030
|
+
"requestId",
|
|
1031
|
+
"details"
|
|
1032
|
+
])) return new DriveError("invalid_response", {
|
|
1033
|
+
status: response.status,
|
|
1034
|
+
requestId: headerRequestId
|
|
1035
|
+
});
|
|
1036
|
+
const envelope = value.error;
|
|
1037
|
+
if (typeof envelope.code !== "string" || !DRIVE_API_ERROR_CODES.includes(envelope.code) || envelope.message !== DRIVE_API_ERROR_MESSAGES[envelope.code] || !REQUEST_ID.test(String(envelope.requestId)) || envelope.requestId !== headerRequestId || !isExactRecord(envelope.details, []) || !errorStatusMatches(envelope.code, response.status, preconditionStatus, allowsQuotaExceeded) || parsed.text !== JSON.stringify(value)) return new DriveError("invalid_response", {
|
|
1038
|
+
status: response.status,
|
|
1039
|
+
requestId: headerRequestId
|
|
1040
|
+
});
|
|
1041
|
+
return new DriveError(envelope.code, {
|
|
1042
|
+
status: response.status,
|
|
1043
|
+
requestId: envelope.requestId
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
function errorStatusMatches(code, status, preconditionStatus, allowsQuotaExceeded) {
|
|
1047
|
+
if (code === "quota_exceeded" && !allowsQuotaExceeded) return false;
|
|
1048
|
+
return {
|
|
1049
|
+
authentication_required: [401],
|
|
1050
|
+
identity_authority_unavailable: [503],
|
|
1051
|
+
permission_denied: [403],
|
|
1052
|
+
resource_not_found: [404],
|
|
1053
|
+
invalid_request: [422],
|
|
1054
|
+
invalid_pagination_cursor: [422],
|
|
1055
|
+
invalid_idempotency_key: [422],
|
|
1056
|
+
precondition_failed: [preconditionStatus],
|
|
1057
|
+
idempotency_conflict: [409],
|
|
1058
|
+
quota_exceeded: [409],
|
|
1059
|
+
service_unavailable: [503],
|
|
1060
|
+
internal_error: [500]
|
|
1061
|
+
}[code].includes(status);
|
|
1062
|
+
}
|
|
1063
|
+
async function parseBoundedJson(response, maximumBytes, status, requestId, signal) {
|
|
1064
|
+
if (!response.body) throw new DriveError("invalid_response", {
|
|
1065
|
+
status,
|
|
1066
|
+
requestId
|
|
1067
|
+
});
|
|
1068
|
+
const reader = response.body.getReader();
|
|
1069
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
1070
|
+
let size = 0;
|
|
1071
|
+
let chunks = 0;
|
|
1072
|
+
let text = "";
|
|
1073
|
+
const contentLength = response.headers.get("content-length");
|
|
1074
|
+
if (contentLength !== null && (!/^(?:0|[1-9][0-9]*)$/.test(contentLength) || Number(contentLength) > maximumBytes)) {
|
|
1075
|
+
reader.cancel().catch(() => {});
|
|
1076
|
+
throw new DriveError("invalid_response", {
|
|
1077
|
+
status,
|
|
1078
|
+
requestId
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
const deadline = new AbortController();
|
|
1082
|
+
const timeout = setTimeout(() => deadline.abort(), RESPONSE_DEADLINE_MILLISECONDS);
|
|
1083
|
+
try {
|
|
1084
|
+
while (true) {
|
|
1085
|
+
if (signal?.aborted) throw new DriveError("aborted");
|
|
1086
|
+
const { done, value } = await readResponseChunk(reader, signal, deadline.signal);
|
|
1087
|
+
if (done) break;
|
|
1088
|
+
chunks += 1;
|
|
1089
|
+
size += value.byteLength;
|
|
1090
|
+
if (chunks > RESPONSE_CHUNK_LIMIT || size > maximumBytes) throw new DriveError("invalid_response", {
|
|
1091
|
+
status,
|
|
1092
|
+
requestId
|
|
1093
|
+
});
|
|
1094
|
+
text += decoder.decode(value, { stream: true });
|
|
1095
|
+
}
|
|
1096
|
+
text += decoder.decode();
|
|
1097
|
+
return {
|
|
1098
|
+
value: JSON.parse(text),
|
|
1099
|
+
text
|
|
1100
|
+
};
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
if (error instanceof DriveError) {
|
|
1103
|
+
if (error.code === "aborted" || signal?.aborted) throw new DriveError("aborted");
|
|
1104
|
+
throw error;
|
|
1105
|
+
}
|
|
1106
|
+
throw new DriveError("invalid_response", {
|
|
1107
|
+
status,
|
|
1108
|
+
requestId
|
|
1109
|
+
});
|
|
1110
|
+
} finally {
|
|
1111
|
+
clearTimeout(timeout);
|
|
1112
|
+
reader.cancel().catch(() => {});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
async function readResponseChunk(reader, operationSignal, deadlineSignal) {
|
|
1116
|
+
if (operationSignal?.aborted) throw new DriveError("aborted");
|
|
1117
|
+
if (deadlineSignal.aborted) throw new DriveError("invalid_response");
|
|
1118
|
+
let removeOperationAbort = () => {};
|
|
1119
|
+
let removeDeadlineAbort = () => {};
|
|
1120
|
+
const operationAbort = new Promise((_, reject) => {
|
|
1121
|
+
if (!operationSignal) return;
|
|
1122
|
+
const onAbort = () => reject(new DriveError("aborted"));
|
|
1123
|
+
operationSignal.addEventListener("abort", onAbort, { once: true });
|
|
1124
|
+
removeOperationAbort = () => operationSignal.removeEventListener("abort", onAbort);
|
|
1125
|
+
});
|
|
1126
|
+
const deadlineAbort = new Promise((_, reject) => {
|
|
1127
|
+
const onAbort = () => reject(new DriveError("invalid_response"));
|
|
1128
|
+
deadlineSignal.addEventListener("abort", onAbort, { once: true });
|
|
1129
|
+
removeDeadlineAbort = () => deadlineSignal.removeEventListener("abort", onAbort);
|
|
1130
|
+
});
|
|
1131
|
+
try {
|
|
1132
|
+
return await Promise.race([
|
|
1133
|
+
reader.read(),
|
|
1134
|
+
operationAbort,
|
|
1135
|
+
deadlineAbort
|
|
1136
|
+
]);
|
|
1137
|
+
} finally {
|
|
1138
|
+
removeOperationAbort();
|
|
1139
|
+
removeDeadlineAbort();
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
function cancelResponse(response) {
|
|
1143
|
+
response.body?.cancel().catch(() => {});
|
|
1144
|
+
}
|
|
1145
|
+
function safeRequestId(value) {
|
|
1146
|
+
return value !== null && REQUEST_ID.test(value) ? value : null;
|
|
1147
|
+
}
|
|
1148
|
+
function readValidators(headers) {
|
|
1149
|
+
const etag = headers.get("etag");
|
|
1150
|
+
const permissionCollectionEtag = headers.get("drive-permission-collection-etag");
|
|
1151
|
+
const permissionGrantEtag = headers.get("drive-permission-grant-etag");
|
|
1152
|
+
return Object.freeze({
|
|
1153
|
+
...etag !== null && LIST_ETAG.test(etag) ? { etag } : {},
|
|
1154
|
+
...permissionCollectionEtag !== null && parseCollectionEtag(permissionCollectionEtag) !== void 0 ? { permissionCollectionEtag } : {},
|
|
1155
|
+
...permissionGrantEtag !== null && parseGrantEtag(permissionGrantEtag) !== void 0 ? { permissionGrantEtag } : {}
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
function isFolderPage(value, workspaceId, parentItemId) {
|
|
1159
|
+
if (!(isExactRecord(value, ["items", "nextCursor"]) && Array.isArray(value.items) && value.items.length <= 50 && value.items.every(isFolderItem) && (value.nextCursor === null || typeof value.nextCursor === "string" && value.nextCursor.length <= 699 && CURSOR.test(value.nextCursor)))) return false;
|
|
1160
|
+
return value.items.every((item) => item.workspaceId === workspaceId && item.parentItemId === parentItemId) && isStrictlyOrdered(value.items, (item) => item.itemId);
|
|
1161
|
+
}
|
|
1162
|
+
function isFolderItem(value) {
|
|
1163
|
+
return isExactRecord(value, [
|
|
1164
|
+
"workspaceId",
|
|
1165
|
+
"volumeId",
|
|
1166
|
+
"itemId",
|
|
1167
|
+
"parentItemId",
|
|
1168
|
+
"kind",
|
|
1169
|
+
"name",
|
|
1170
|
+
"visibility",
|
|
1171
|
+
"revision"
|
|
1172
|
+
]) && typeof value.workspaceId === "string" && WORKSPACE_ID.test(value.workspaceId) && typeof value.volumeId === "string" && VOLUME_ID.test(value.volumeId) && typeof value.itemId === "string" && ITEM_ID.test(value.itemId) && typeof value.parentItemId === "string" && ITEM_ID.test(value.parentItemId) && (value.kind === "file" || value.kind === "folder") && isSafeFilename(value.name) && (value.visibility === "workspace" || value.visibility === "restricted") && isSafeInteger(value.revision);
|
|
1173
|
+
}
|
|
1174
|
+
function isPermissionPage(value, headers, targetItemId) {
|
|
1175
|
+
if (!(isExactRecord(value, [
|
|
1176
|
+
"items",
|
|
1177
|
+
"nextCursor",
|
|
1178
|
+
"collectionRevision",
|
|
1179
|
+
"collectionEtag"
|
|
1180
|
+
]) && Array.isArray(value.items) && value.items.length <= 50 && value.items.every(isPermissionGrant) && (value.nextCursor === null || typeof value.nextCursor === "string" && value.nextCursor.length <= 699 && CURSOR.test(value.nextCursor)) && isSafeInteger(value.collectionRevision) && typeof value.collectionEtag === "string" && parseCollectionEtag(value.collectionEtag) === value.collectionRevision)) return false;
|
|
1181
|
+
const collectionHeader = headers.get("drive-permission-collection-etag");
|
|
1182
|
+
const representationEtag = headers.get("etag");
|
|
1183
|
+
const collectionRevision = value.collectionRevision;
|
|
1184
|
+
if (!isSafeInteger(collectionRevision)) return false;
|
|
1185
|
+
return collectionHeader === value.collectionEtag && representationEtag !== null && LIST_ETAG.test(representationEtag) && value.items.every((grant) => (grant.source === "direct" ? grant.sourceItemId === targetItemId : grant.sourceItemId !== targetItemId) && grant.etag === permissionGrantEtag(collectionRevision, grant.revision)) && isStrictlyOrdered(value.items, (grant) => `${grant.sourceItemId}\0${grant.grantId}`);
|
|
1186
|
+
}
|
|
1187
|
+
function isPermissionGrant(value) {
|
|
1188
|
+
return isExactRecord(value, [
|
|
1189
|
+
"source",
|
|
1190
|
+
"sourceItemId",
|
|
1191
|
+
"grantId",
|
|
1192
|
+
"grantee",
|
|
1193
|
+
"actions",
|
|
1194
|
+
"availability",
|
|
1195
|
+
"expiresAtEpochMilliseconds",
|
|
1196
|
+
"revision",
|
|
1197
|
+
"etag"
|
|
1198
|
+
]) && (value.source === "direct" || value.source === "inherited") && typeof value.sourceItemId === "string" && ITEM_ID.test(value.sourceItemId) && typeof value.grantId === "string" && GRANT_ID.test(value.grantId) && isGrantee(value.grantee) && isPermissionActions(value.actions) && [
|
|
1199
|
+
"active",
|
|
1200
|
+
"expired",
|
|
1201
|
+
"revoked"
|
|
1202
|
+
].includes(String(value.availability)) && (value.expiresAtEpochMilliseconds === null || isSafeInteger(value.expiresAtEpochMilliseconds)) && isSafeInteger(value.revision) && typeof value.etag === "string" && GRANT_ETAG.test(value.etag);
|
|
1203
|
+
}
|
|
1204
|
+
function isGrantee(value) {
|
|
1205
|
+
return isExactRecord(value, ["type", "userId"]) && value.type === "user" && typeof value.userId === "string" && AUTH_ID.test(value.userId) || isExactRecord(value, ["type", "teamId"]) && value.type === "team" && typeof value.teamId === "string" && AUTH_ID.test(value.teamId);
|
|
1206
|
+
}
|
|
1207
|
+
function isPermissionActions(value) {
|
|
1208
|
+
return Array.isArray(value) && value.length >= 1 && value.length <= 6 && new Set(value).size === value.length && value.every((action) => PERMISSION_ACTIONS.includes(action)) && value.every((action, index) => index === 0 || PERMISSION_ACTIONS.indexOf(action) > PERMISSION_ACTIONS.indexOf(value[index - 1]));
|
|
1209
|
+
}
|
|
1210
|
+
function isPermissionMutation(value, targetItemId, expectedGrantId) {
|
|
1211
|
+
if (!isExactRecord(value, [
|
|
1212
|
+
"outcome",
|
|
1213
|
+
"grant",
|
|
1214
|
+
"collectionRevision"
|
|
1215
|
+
]) || value.outcome !== "committed" && value.outcome !== "replayed" || !isSafeInteger(value.collectionRevision)) return false;
|
|
1216
|
+
const grant = value.grant;
|
|
1217
|
+
return isExactRecord(grant, [
|
|
1218
|
+
"source",
|
|
1219
|
+
"sourceItemId",
|
|
1220
|
+
"grantId",
|
|
1221
|
+
"grantee",
|
|
1222
|
+
"actions",
|
|
1223
|
+
"availability",
|
|
1224
|
+
"expiresAtEpochMilliseconds",
|
|
1225
|
+
"revision"
|
|
1226
|
+
]) && grant.source === "direct" && typeof grant.sourceItemId === "string" && grant.sourceItemId === targetItemId && typeof grant.grantId === "string" && GRANT_ID.test(grant.grantId) && (expectedGrantId === void 0 || grant.grantId === expectedGrantId) && isGrantee(grant.grantee) && isPermissionActions(grant.actions) && [
|
|
1227
|
+
"active",
|
|
1228
|
+
"expired",
|
|
1229
|
+
"revoked"
|
|
1230
|
+
].includes(String(grant.availability)) && (grant.expiresAtEpochMilliseconds === null || isSafeInteger(grant.expiresAtEpochMilliseconds)) && isSafeInteger(grant.revision);
|
|
1231
|
+
}
|
|
1232
|
+
function isEffectivePermissionResponse(value, targetItemId) {
|
|
1233
|
+
if (!isExactRecord(value, ["actions"]) || !Array.isArray(value.actions) || value.actions.length !== 7) return false;
|
|
1234
|
+
for (let index = 0; index < value.actions.length; index += 1) {
|
|
1235
|
+
const action = value.actions[index];
|
|
1236
|
+
if (action === void 0 || !isExactRecord(action, [
|
|
1237
|
+
"action",
|
|
1238
|
+
"allowed",
|
|
1239
|
+
"basis",
|
|
1240
|
+
"sourceItemId",
|
|
1241
|
+
"grantId"
|
|
1242
|
+
]) || action.action !== EFFECTIVE_ACTIONS[index] || typeof action.allowed !== "boolean" || !(action.basis === null || [
|
|
1243
|
+
"workspace_visibility",
|
|
1244
|
+
"restricted_administrator_bypass",
|
|
1245
|
+
"direct_grant",
|
|
1246
|
+
"inherited_grant"
|
|
1247
|
+
].includes(String(action.basis))) || !(action.sourceItemId === null || typeof action.sourceItemId === "string" && ITEM_ID.test(action.sourceItemId)) || !(action.grantId === null || typeof action.grantId === "string" && GRANT_ID.test(action.grantId)) || !effectiveSourceIsCoherent(action, targetItemId)) return false;
|
|
1248
|
+
}
|
|
1249
|
+
return true;
|
|
1250
|
+
}
|
|
1251
|
+
function isUploadQuotaReservation(value) {
|
|
1252
|
+
return isExactRecord(value, [
|
|
1253
|
+
"workspaceId",
|
|
1254
|
+
"uploadId",
|
|
1255
|
+
"parentItemId",
|
|
1256
|
+
"expectedByteLength",
|
|
1257
|
+
"state",
|
|
1258
|
+
"terminalReason",
|
|
1259
|
+
"createdAtEpochMilliseconds",
|
|
1260
|
+
"expiresAtEpochMilliseconds",
|
|
1261
|
+
"terminalAtEpochMilliseconds"
|
|
1262
|
+
]) && typeof value.workspaceId === "string" && WORKSPACE_ID.test(value.workspaceId) && typeof value.uploadId === "string" && UPLOAD_ID.test(value.uploadId) && typeof value.parentItemId === "string" && ITEM_ID.test(value.parentItemId) && typeof value.expectedByteLength === "number" && Number.isSafeInteger(value.expectedByteLength) && value.expectedByteLength >= 0 && value.expectedByteLength <= MAX_UPLOAD_BYTE_LENGTH && (value.state === "reserved" || value.state === "converted" || value.state === "released") && (value.terminalReason === null || value.terminalReason === "completed" || value.terminalReason === "aborted" || value.terminalReason === "failed" || value.terminalReason === "expired") && isSafeInteger(value.createdAtEpochMilliseconds) && isSafeInteger(value.expiresAtEpochMilliseconds) && value.expiresAtEpochMilliseconds - value.createdAtEpochMilliseconds === UPLOAD_QUOTA_EXPIRY_MILLISECONDS && (value.terminalAtEpochMilliseconds === null || isSafeInteger(value.terminalAtEpochMilliseconds) && value.terminalAtEpochMilliseconds >= value.createdAtEpochMilliseconds);
|
|
1263
|
+
}
|
|
1264
|
+
function effectiveSourceIsCoherent(action, targetItemId) {
|
|
1265
|
+
if (!action.allowed) return action.basis === null && action.sourceItemId === null && action.grantId === null;
|
|
1266
|
+
if (action.basis === "workspace_visibility" || action.basis === "restricted_administrator_bypass") return action.sourceItemId === null && action.grantId === null;
|
|
1267
|
+
if (action.basis === "direct_grant") return action.sourceItemId === targetItemId && typeof action.grantId === "string";
|
|
1268
|
+
if (action.basis === "inherited_grant") return typeof action.sourceItemId === "string" && action.sourceItemId !== targetItemId && typeof action.grantId === "string";
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
function mutationValidatorsAreCoherent(headers) {
|
|
1272
|
+
const collection = headers.get("drive-permission-collection-etag");
|
|
1273
|
+
const grant = headers.get("drive-permission-grant-etag");
|
|
1274
|
+
const collectionRevision = collection === null ? void 0 : parseCollectionEtag(collection);
|
|
1275
|
+
if (collectionRevision === void 0) return false;
|
|
1276
|
+
if (grant === null) return true;
|
|
1277
|
+
const currentGrant = parseGrantEtag(grant);
|
|
1278
|
+
return currentGrant !== void 0 && currentGrant[0] === collectionRevision;
|
|
1279
|
+
}
|
|
1280
|
+
function parseCollectionEtag(value) {
|
|
1281
|
+
const match = COLLECTION_ETAG.exec(value);
|
|
1282
|
+
if (!match) return void 0;
|
|
1283
|
+
const revision = Number(match[1]);
|
|
1284
|
+
return Number.isSafeInteger(revision) ? revision : void 0;
|
|
1285
|
+
}
|
|
1286
|
+
function parseGrantEtag(value) {
|
|
1287
|
+
const match = GRANT_ETAG.exec(value);
|
|
1288
|
+
if (!match) return void 0;
|
|
1289
|
+
const collectionRevision = Number(match[1]);
|
|
1290
|
+
const grantRevision = Number(match[2]);
|
|
1291
|
+
return Number.isSafeInteger(collectionRevision) && Number.isSafeInteger(grantRevision) ? [collectionRevision, grantRevision] : void 0;
|
|
1292
|
+
}
|
|
1293
|
+
function permissionGrantEtag(collectionRevision, grantRevision) {
|
|
1294
|
+
return `"drive.permission-grant.v1.${collectionRevision}.${grantRevision}"`;
|
|
1295
|
+
}
|
|
1296
|
+
function isSafeFilename(value) {
|
|
1297
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 510 || value.normalize("NFC") !== value || value.endsWith(".") || value.endsWith(" ") || filenameIsReserved(value)) return false;
|
|
1298
|
+
let codePoints = 0;
|
|
1299
|
+
let bytes = 0;
|
|
1300
|
+
for (const character of value) {
|
|
1301
|
+
const point = character.codePointAt(0);
|
|
1302
|
+
if (point === void 0 || point === 47 || point === 92 || point <= 31 || point >= 127 && point <= 159 || point === 8232 || point === 8233 || point >= 55296 && point <= 57343) return false;
|
|
1303
|
+
codePoints += 1;
|
|
1304
|
+
bytes += point <= 127 ? 1 : point <= 2047 ? 2 : point <= 65535 ? 3 : 4;
|
|
1305
|
+
if (codePoints > 255 || bytes > 255) return false;
|
|
1306
|
+
}
|
|
1307
|
+
return true;
|
|
1308
|
+
}
|
|
1309
|
+
function filenameIsReserved(value) {
|
|
1310
|
+
const base = value.split(".", 1)[0].toUpperCase();
|
|
1311
|
+
return value === "." || value === ".." || [
|
|
1312
|
+
"CON",
|
|
1313
|
+
"PRN",
|
|
1314
|
+
"AUX",
|
|
1315
|
+
"NUL",
|
|
1316
|
+
"CLOCK$",
|
|
1317
|
+
"CONIN$",
|
|
1318
|
+
"CONOUT$"
|
|
1319
|
+
].includes(base) || /^COM[1-9]$/.test(base) || /^LPT[1-9]$/.test(base);
|
|
1320
|
+
}
|
|
1321
|
+
function isStrictlyOrdered(values, key) {
|
|
1322
|
+
return values.every((value, index) => index === 0 || key(values[index - 1]) < key(value));
|
|
1323
|
+
}
|
|
1324
|
+
function isSafeInteger(value) {
|
|
1325
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1326
|
+
}
|
|
1327
|
+
function isExactRecord(value, keys) {
|
|
1328
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1329
|
+
return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
1330
|
+
}
|
|
1331
|
+
//#endregion
|
|
1332
|
+
//#region src/client.ts
|
|
1333
|
+
function createDriveClient(config) {
|
|
1334
|
+
return createDriveClientInternal(config);
|
|
1335
|
+
}
|
|
1336
|
+
//#endregion
|
|
1337
|
+
export { createDriveClient };
|