@veryfront/ext-blob-gcs 0.1.1184
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +117 -0
- package/esm/_dnt.polyfills.d.ts +12 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +15 -0
- package/esm/_dnt.shims.d.ts +11 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +68 -0
- package/esm/gcs-storage.d.ts +41 -0
- package/esm/gcs-storage.d.ts.map +1 -0
- package/esm/gcs-storage.js +860 -0
- package/esm/index.d.ts +15 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +75 -0
- package/esm/package.json +3 -0
- package/esm/resumable-upload.d.ts +19 -0
- package/esm/resumable-upload.d.ts.map +1 -0
- package/esm/resumable-upload.js +219 -0
- package/package.json +69 -0
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Cloud Storage implementation of Veryfront's first-party
|
|
3
|
+
* `BlobStorage` contract.
|
|
4
|
+
*
|
|
5
|
+
* @module extensions/ext-blob-gcs
|
|
6
|
+
*/
|
|
7
|
+
import "./_dnt.polyfills.js";
|
|
8
|
+
import { type Extension } from "veryfront/extensions";
|
|
9
|
+
import { type GCSBlobStorageConfig } from "./gcs-storage.js";
|
|
10
|
+
/** Create an explicitly configured GCS blob-storage extension. */
|
|
11
|
+
export declare const extBlobGCS: (config: GCSBlobStorageConfig) => Extension;
|
|
12
|
+
export default extBlobGCS;
|
|
13
|
+
export { GCSBlobStorage } from "./gcs-storage.js";
|
|
14
|
+
export type { GCSBlobStorageConfig, GCSBlobStorageDependencies, GCSFetch } from "./gcs-storage.js";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,qBAAqB,CAAC;AAG7B,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAE3E,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7E,kEAAkE;AAClE,eAAO,MAAM,UAAU,GAAI,QAAQ,oBAAoB,KAAG,SAgEzD,CAAC;AAEF,eAAe,UAAU,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC"}
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Cloud Storage implementation of Veryfront's first-party
|
|
3
|
+
* `BlobStorage` contract.
|
|
4
|
+
*
|
|
5
|
+
* @module extensions/ext-blob-gcs
|
|
6
|
+
*/
|
|
7
|
+
import "./_dnt.polyfills.js";
|
|
8
|
+
import { composeAbortSignals } from "veryfront/extensions";
|
|
9
|
+
import { BlobStorageContractName } from "veryfront/workflow/blob";
|
|
10
|
+
import { GCSBlobStorage } from "./gcs-storage.js";
|
|
11
|
+
/** Create an explicitly configured GCS blob-storage extension. */
|
|
12
|
+
export const extBlobGCS = (config) => {
|
|
13
|
+
const selectedConfig = { ...config };
|
|
14
|
+
let activeGeneration;
|
|
15
|
+
return {
|
|
16
|
+
name: "ext-blob-gcs",
|
|
17
|
+
version: "0.1.0",
|
|
18
|
+
contracts: {
|
|
19
|
+
provides: ["BlobStorage"],
|
|
20
|
+
},
|
|
21
|
+
capabilities: [
|
|
22
|
+
{
|
|
23
|
+
type: "net:outbound",
|
|
24
|
+
hosts: ["oauth2.googleapis.com", "storage.googleapis.com"],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
setup(ctx) {
|
|
28
|
+
if (activeGeneration)
|
|
29
|
+
throw new Error("ext-blob-gcs is already set up");
|
|
30
|
+
selectedConfig.signal?.throwIfAborted();
|
|
31
|
+
ctx.signal?.throwIfAborted();
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const signal = composeAbortSignals([
|
|
34
|
+
controller.signal,
|
|
35
|
+
...(selectedConfig.signal ? [selectedConfig.signal] : []),
|
|
36
|
+
...(ctx.signal ? [ctx.signal] : []),
|
|
37
|
+
]);
|
|
38
|
+
const generation = {
|
|
39
|
+
controller,
|
|
40
|
+
storage: new GCSBlobStorage({ ...selectedConfig, signal }),
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
signal.throwIfAborted();
|
|
44
|
+
ctx.provide(BlobStorageContractName, generation.storage);
|
|
45
|
+
signal.throwIfAborted();
|
|
46
|
+
activeGeneration = generation;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (!controller.signal.aborted) {
|
|
50
|
+
controller.abort(new DOMException("GCS blob extension setup failed.", "AbortError"));
|
|
51
|
+
}
|
|
52
|
+
generation.storage.close();
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
ctx.logger.debug("[ext-blob-gcs] BlobStorage registered");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Diagnostics must not invalidate a successfully registered provider.
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
teardown() {
|
|
63
|
+
const generation = activeGeneration;
|
|
64
|
+
if (!generation)
|
|
65
|
+
return;
|
|
66
|
+
activeGeneration = undefined;
|
|
67
|
+
if (!generation.controller.signal.aborted) {
|
|
68
|
+
generation.controller.abort(new DOMException("GCS blob extension is shutting down.", "AbortError"));
|
|
69
|
+
}
|
|
70
|
+
generation.storage.close();
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
export default extBlobGCS;
|
|
75
|
+
export { GCSBlobStorage } from "./gcs-storage.js";
|
package/esm/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const MIN_RESUMABLE_CHUNK_SIZE: number;
|
|
2
|
+
export declare const DEFAULT_RESUMABLE_CHUNK_SIZE: number;
|
|
3
|
+
export declare const MAX_RESUMABLE_CHUNK_SIZE: number;
|
|
4
|
+
export interface ResumableStreamUploadOptions {
|
|
5
|
+
readonly stream: ReadableStream;
|
|
6
|
+
readonly sessionUrl: URL;
|
|
7
|
+
readonly contentType: string;
|
|
8
|
+
readonly chunkSize: number;
|
|
9
|
+
readonly signal?: AbortSignal;
|
|
10
|
+
readonly request: (input: URL, init: RequestInit) => Promise<Response>;
|
|
11
|
+
readonly createHttpError: (response: Response) => Promise<Error>;
|
|
12
|
+
}
|
|
13
|
+
export interface ResumableStreamUploadResult {
|
|
14
|
+
readonly response: Response;
|
|
15
|
+
readonly size: number;
|
|
16
|
+
}
|
|
17
|
+
/** Upload an unknown-length byte stream using the GCS resumable protocol. */
|
|
18
|
+
export declare function uploadUnknownLengthStream(options: ResumableStreamUploadOptions): Promise<ResumableStreamUploadResult>;
|
|
19
|
+
//# sourceMappingURL=resumable-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumable-upload.d.ts","sourceRoot":"","sources":["../src/resumable-upload.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB,QAAa,CAAC;AACnD,eAAO,MAAM,4BAA4B,QAAkB,CAAC;AAC5D,eAAO,MAAM,wBAAwB,QAAmB,CAAC;AASzD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvE,QAAQ,CAAC,eAAe,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;CAClE;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkID,6EAA6E;AAC7E,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,2BAA2B,CAAC,CAyHtC"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { API_ERROR, INVALID_ARGUMENT } from "veryfront/errors";
|
|
2
|
+
export const MIN_RESUMABLE_CHUNK_SIZE = 256 * 1024;
|
|
3
|
+
export const DEFAULT_RESUMABLE_CHUNK_SIZE = 8 * 1024 * 1024;
|
|
4
|
+
export const MAX_RESUMABLE_CHUNK_SIZE = 64 * 1024 * 1024;
|
|
5
|
+
const MAX_CONSECUTIVE_NO_PROGRESS_RESPONSES = 3;
|
|
6
|
+
function invalidByteStream(detail, cause) {
|
|
7
|
+
return INVALID_ARGUMENT.create({ detail, cause });
|
|
8
|
+
}
|
|
9
|
+
function protocolError(detail) {
|
|
10
|
+
return API_ERROR.create({ detail });
|
|
11
|
+
}
|
|
12
|
+
async function cancelReader(reader, reason) {
|
|
13
|
+
try {
|
|
14
|
+
await reader.cancel(reason);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// The failure that stopped the upload remains authoritative.
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function* readByteChunks(stream, chunkSize, signal, cancellationReason) {
|
|
21
|
+
let reader;
|
|
22
|
+
try {
|
|
23
|
+
reader = stream.getReader();
|
|
24
|
+
}
|
|
25
|
+
catch (cause) {
|
|
26
|
+
throw invalidByteStream("GCSBlobStorage: upload stream must be unlocked", cause);
|
|
27
|
+
}
|
|
28
|
+
let readerFinished = false;
|
|
29
|
+
let readerCancelled = false;
|
|
30
|
+
let pending;
|
|
31
|
+
let pendingOffset = 0;
|
|
32
|
+
const cancel = async (reason) => {
|
|
33
|
+
if (readerFinished || readerCancelled)
|
|
34
|
+
return;
|
|
35
|
+
readerCancelled = true;
|
|
36
|
+
await cancelReader(reader, reason);
|
|
37
|
+
};
|
|
38
|
+
let abortCancellation;
|
|
39
|
+
const onAbort = () => {
|
|
40
|
+
abortCancellation ??= cancel(signal?.reason);
|
|
41
|
+
};
|
|
42
|
+
if (signal?.aborted)
|
|
43
|
+
onAbort();
|
|
44
|
+
else
|
|
45
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
46
|
+
const loadPendingBytes = async () => {
|
|
47
|
+
while (!readerFinished && (!pending || pendingOffset === pending.byteLength)) {
|
|
48
|
+
signal?.throwIfAborted();
|
|
49
|
+
const result = await reader.read();
|
|
50
|
+
if (result.done) {
|
|
51
|
+
readerFinished = true;
|
|
52
|
+
pending = undefined;
|
|
53
|
+
pendingOffset = 0;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (!(result.value instanceof Uint8Array)) {
|
|
57
|
+
throw invalidByteStream("GCSBlobStorage: ReadableStream chunks must be Uint8Array values");
|
|
58
|
+
}
|
|
59
|
+
if (result.value.byteLength === 0)
|
|
60
|
+
continue;
|
|
61
|
+
pending = result.value;
|
|
62
|
+
pendingOffset = 0;
|
|
63
|
+
}
|
|
64
|
+
return pending !== undefined && pendingOffset < pending.byteLength;
|
|
65
|
+
};
|
|
66
|
+
try {
|
|
67
|
+
if (!(await loadPendingBytes())) {
|
|
68
|
+
yield { bytes: new Uint8Array(), final: true };
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
while (true) {
|
|
72
|
+
const buffer = new Uint8Array(chunkSize);
|
|
73
|
+
let length = 0;
|
|
74
|
+
while (length < chunkSize && await loadPendingBytes()) {
|
|
75
|
+
const available = pending.byteLength - pendingOffset;
|
|
76
|
+
const copied = Math.min(chunkSize - length, available);
|
|
77
|
+
buffer.set(pending.subarray(pendingOffset, pendingOffset + copied), length);
|
|
78
|
+
length += copied;
|
|
79
|
+
pendingOffset += copied;
|
|
80
|
+
}
|
|
81
|
+
const final = !(await loadPendingBytes());
|
|
82
|
+
yield {
|
|
83
|
+
bytes: length === chunkSize ? buffer : buffer.slice(0, length),
|
|
84
|
+
final,
|
|
85
|
+
};
|
|
86
|
+
if (final)
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
await cancel(error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
signal?.removeEventListener("abort", onAbort);
|
|
96
|
+
await abortCancellation;
|
|
97
|
+
await cancel(cancellationReason());
|
|
98
|
+
reader.releaseLock();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function acknowledgedByteCount(response) {
|
|
102
|
+
const value = response.headers.get("range");
|
|
103
|
+
if (value === null)
|
|
104
|
+
return 0;
|
|
105
|
+
const match = /^bytes=0-(0|[1-9][0-9]*)$/.exec(value);
|
|
106
|
+
if (!match) {
|
|
107
|
+
throw protocolError("GCSBlobStorage: resumable upload returned an invalid Range header");
|
|
108
|
+
}
|
|
109
|
+
const lastByte = Number(match[1]);
|
|
110
|
+
if (!Number.isSafeInteger(lastByte)) {
|
|
111
|
+
throw protocolError("GCSBlobStorage: resumable upload returned an unsafe byte range");
|
|
112
|
+
}
|
|
113
|
+
return lastByte + 1;
|
|
114
|
+
}
|
|
115
|
+
async function discardResponseBody(response) {
|
|
116
|
+
try {
|
|
117
|
+
await response.body?.cancel();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// A valid acknowledgement remains authoritative over body cleanup.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Upload an unknown-length byte stream using the GCS resumable protocol. */
|
|
124
|
+
export async function uploadUnknownLengthStream(options) {
|
|
125
|
+
let acknowledged = 0;
|
|
126
|
+
let cancellationReason;
|
|
127
|
+
for await (const chunk of readByteChunks(options.stream, options.chunkSize, options.signal, () => cancellationReason)) {
|
|
128
|
+
const chunkStart = acknowledged;
|
|
129
|
+
const chunkEndExclusive = chunkStart + chunk.bytes.byteLength;
|
|
130
|
+
if (!Number.isSafeInteger(chunkEndExclusive)) {
|
|
131
|
+
const error = protocolError("GCSBlobStorage: upload stream exceeds the supported size range");
|
|
132
|
+
cancellationReason = error;
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
const totalSize = chunk.final ? chunkEndExclusive : undefined;
|
|
136
|
+
let chunkOffset = 0;
|
|
137
|
+
let noProgressResponses = 0;
|
|
138
|
+
while (true) {
|
|
139
|
+
const requestStart = chunkStart + chunkOffset;
|
|
140
|
+
const requestBytes = chunk.bytes.subarray(chunkOffset);
|
|
141
|
+
const headers = new Headers({
|
|
142
|
+
"content-length": String(requestBytes.byteLength),
|
|
143
|
+
"content-type": options.contentType,
|
|
144
|
+
});
|
|
145
|
+
headers.set("content-range", requestBytes.byteLength === 0
|
|
146
|
+
? `bytes */${totalSize}`
|
|
147
|
+
: `bytes ${requestStart}-${chunkEndExclusive - 1}/${totalSize ?? "*"}`);
|
|
148
|
+
let response;
|
|
149
|
+
try {
|
|
150
|
+
options.signal?.throwIfAborted();
|
|
151
|
+
response = await options.request(options.sessionUrl, {
|
|
152
|
+
method: "PUT",
|
|
153
|
+
headers,
|
|
154
|
+
body: requestBytes,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
cancellationReason = error;
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
if (response.status === 200 || response.status === 201) {
|
|
162
|
+
if (!chunk.final) {
|
|
163
|
+
await discardResponseBody(response);
|
|
164
|
+
const error = protocolError("GCSBlobStorage: resumable upload finalized before the source stream ended");
|
|
165
|
+
cancellationReason = error;
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
return { response, size: totalSize };
|
|
169
|
+
}
|
|
170
|
+
if (response.status !== 308) {
|
|
171
|
+
const error = await options.createHttpError(response);
|
|
172
|
+
cancellationReason = error;
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
let nextByte;
|
|
176
|
+
try {
|
|
177
|
+
nextByte = acknowledgedByteCount(response);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
cancellationReason = error;
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
await discardResponseBody(response);
|
|
185
|
+
}
|
|
186
|
+
if (nextByte < requestStart || nextByte > chunkEndExclusive) {
|
|
187
|
+
const error = protocolError("GCSBlobStorage: resumable upload acknowledged bytes outside the request range");
|
|
188
|
+
cancellationReason = error;
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
if (!chunk.final && nextByte % MIN_RESUMABLE_CHUNK_SIZE !== 0) {
|
|
192
|
+
const error = protocolError("GCSBlobStorage: resumable upload acknowledged an unaligned intermediate range");
|
|
193
|
+
cancellationReason = error;
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
if (nextByte === requestStart) {
|
|
197
|
+
noProgressResponses += 1;
|
|
198
|
+
if (noProgressResponses >= MAX_CONSECUTIVE_NO_PROGRESS_RESPONSES) {
|
|
199
|
+
const error = protocolError("GCSBlobStorage: resumable upload repeatedly made no progress");
|
|
200
|
+
cancellationReason = error;
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
noProgressResponses = 0;
|
|
206
|
+
acknowledged = nextByte;
|
|
207
|
+
chunkOffset = nextByte - chunkStart;
|
|
208
|
+
if (chunkOffset === chunk.bytes.byteLength) {
|
|
209
|
+
if (chunk.final) {
|
|
210
|
+
const error = protocolError("GCSBlobStorage: resumable upload accepted the final range without finalizing");
|
|
211
|
+
cancellationReason = error;
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
throw protocolError("GCSBlobStorage: upload stream ended without a final response");
|
|
219
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@veryfront/ext-blob-gcs",
|
|
3
|
+
"version": "0.1.1184",
|
|
4
|
+
"description": "Veryfront first-party extension package for ext-blob-gcs",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"veryfront",
|
|
7
|
+
"extension",
|
|
8
|
+
"ext-blob-gcs"
|
|
9
|
+
],
|
|
10
|
+
"author": "Veryfront",
|
|
11
|
+
"homepage": "https://github.com/veryfront/veryfront-code/tree/main/extensions/ext-blob-gcs",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/veryfront/veryfront-code.git",
|
|
15
|
+
"directory": "extensions/ext-blob-gcs"
|
|
16
|
+
},
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/veryfront/veryfront-code/issues"
|
|
20
|
+
},
|
|
21
|
+
"module": "./esm/index.js",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": "./esm/index.js",
|
|
25
|
+
"types": "./esm/index.d.ts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"veryfront": {
|
|
36
|
+
"extension": true,
|
|
37
|
+
"activation": "explicit",
|
|
38
|
+
"contracts": {
|
|
39
|
+
"provides": [
|
|
40
|
+
"BlobStorage"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"capabilities": [
|
|
44
|
+
{
|
|
45
|
+
"type": "net:outbound",
|
|
46
|
+
"hosts": [
|
|
47
|
+
"oauth2.googleapis.com",
|
|
48
|
+
"storage.googleapis.com"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@deno/shim-deno": "~0.18.0",
|
|
55
|
+
"@deno/shim-crypto": "~0.3.1",
|
|
56
|
+
"@deno/shim-timers": "~0.1.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"veryfront": "^0.1.1184"
|
|
60
|
+
},
|
|
61
|
+
"type": "module",
|
|
62
|
+
"types": "./esm/index.d.ts",
|
|
63
|
+
"files": [
|
|
64
|
+
"esm",
|
|
65
|
+
"LICENSE",
|
|
66
|
+
"NOTICE",
|
|
67
|
+
"README.md"
|
|
68
|
+
]
|
|
69
|
+
}
|