@velajs/storage 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -1
- package/dist/body-BMUcBeQ0.js +93 -0
- package/dist/body-BMUcBeQ0.js.map +1 -0
- package/dist/client/index.d.ts +49 -46
- package/dist/client/index.js +207 -228
- package/dist/client/index.js.map +1 -0
- package/dist/drivers/memory/index.d.ts +17 -13
- package/dist/drivers/memory/index.js +190 -197
- package/dist/drivers/memory/index.js.map +1 -0
- package/dist/drivers/r2/index.d.ts +7 -4
- package/dist/drivers/r2/index.js +162 -160
- package/dist/drivers/r2/index.js.map +1 -0
- package/dist/drivers/r2-http/index.d.ts +23 -19
- package/dist/drivers/r2-http/index.js +52 -45
- package/dist/drivers/r2-http/index.js.map +1 -0
- package/dist/drivers/s3/index.d.ts +79 -5
- package/dist/drivers/s3/index.js +2 -3
- package/dist/index.d.ts +271 -17
- package/dist/index.js +1078 -16
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +177 -15
- package/dist/middleware/index.js +553 -8
- package/dist/middleware/index.js.map +1 -0
- package/dist/protocol.types-C5PPBD12.d.ts +80 -0
- package/dist/r2.types-Bw4Ft83t.d.ts +71 -0
- package/dist/retry-DXtO6R9D.js +119 -0
- package/dist/retry-DXtO6R9D.js.map +1 -0
- package/dist/s3-DrE9NrnD.js +557 -0
- package/dist/s3-DrE9NrnD.js.map +1 -0
- package/dist/storage.error-DVCOkJhc.js +49 -0
- package/dist/storage.error-DVCOkJhc.js.map +1 -0
- package/dist/storage.types-BJN7Yp1J.d.ts +288 -0
- package/dist/storagesdk/index.d.ts +73 -69
- package/dist/storagesdk/index.js +117 -130
- package/dist/storagesdk/index.js.map +1 -0
- package/dist/stored-file-2Yl07eRc.js +105 -0
- package/dist/stored-file-2Yl07eRc.js.map +1 -0
- package/dist/testing/index.d.ts +12 -8
- package/dist/testing/index.js +36 -53
- package/dist/testing/index.js.map +1 -0
- package/package.json +62 -48
- package/dist/base64.d.ts +0 -8
- package/dist/base64.js +0 -25
- package/dist/decorators/inject-storage.decorator.d.ts +0 -17
- package/dist/decorators/inject-storage.decorator.js +0 -21
- package/dist/drivers/r2/r2.types.d.ts +0 -67
- package/dist/drivers/r2/r2.types.js +0 -5
- package/dist/drivers/s3/s3-client.d.ts +0 -33
- package/dist/drivers/s3/s3-client.js +0 -154
- package/dist/drivers/s3/s3.driver.d.ts +0 -4
- package/dist/drivers/s3/s3.driver.js +0 -407
- package/dist/drivers/s3/s3.types.d.ts +0 -28
- package/dist/drivers/s3/s3.types.js +0 -1
- package/dist/drivers/s3/xml.d.ts +0 -6
- package/dist/drivers/s3/xml.js +0 -52
- package/dist/http/authorizer.types.d.ts +0 -59
- package/dist/http/authorizer.types.js +0 -1
- package/dist/http/http-helpers.d.ts +0 -22
- package/dist/http/http-helpers.js +0 -80
- package/dist/http/protocol.types.d.ts +0 -76
- package/dist/http/protocol.types.js +0 -1
- package/dist/internal/body.d.ts +0 -18
- package/dist/internal/body.js +0 -90
- package/dist/internal/retry.d.ts +0 -41
- package/dist/internal/retry.js +0 -127
- package/dist/internal/stored-file.d.ts +0 -27
- package/dist/internal/stored-file.js +0 -114
- package/dist/middleware/cache.d.ts +0 -50
- package/dist/middleware/cache.js +0 -101
- package/dist/middleware/compose.d.ts +0 -12
- package/dist/middleware/compose.js +0 -11
- package/dist/middleware/compression.d.ts +0 -16
- package/dist/middleware/compression.js +0 -101
- package/dist/middleware/encryption.d.ts +0 -14
- package/dist/middleware/encryption.js +0 -134
- package/dist/middleware/failover.d.ts +0 -18
- package/dist/middleware/failover.js +0 -52
- package/dist/middleware/retry.d.ts +0 -14
- package/dist/middleware/retry.js +0 -47
- package/dist/middleware/versioning.d.ts +0 -36
- package/dist/middleware/versioning.js +0 -89
- package/dist/middleware/wrap.d.ts +0 -11
- package/dist/middleware/wrap.js +0 -46
- package/dist/object-key.d.ts +0 -17
- package/dist/object-key.js +0 -42
- package/dist/storage.controller.d.ts +0 -21
- package/dist/storage.controller.js +0 -386
- package/dist/storage.error.d.ts +0 -38
- package/dist/storage.error.js +0 -52
- package/dist/storage.facade.d.ts +0 -47
- package/dist/storage.facade.js +0 -443
- package/dist/storage.module.d.ts +0 -46
- package/dist/storage.module.js +0 -113
- package/dist/storage.service.d.ts +0 -42
- package/dist/storage.service.js +0 -86
- package/dist/storage.tokens.d.ts +0 -13
- package/dist/storage.tokens.js +0 -26
- package/dist/storage.types.d.ts +0 -245
- package/dist/storage.types.js +0 -1
package/dist/storagesdk/index.js
CHANGED
|
@@ -1,136 +1,123 @@
|
|
|
1
|
-
import { byteLengthOf } from "../
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { t as byteLengthOf } from "../body-BMUcBeQ0.js";
|
|
2
|
+
import { t as StorageError } from "../storage.error-DVCOkJhc.js";
|
|
3
|
+
import { t as createStoredFile } from "../stored-file-2Yl07eRc.js";
|
|
4
|
+
//#region src/storagesdk/index.ts
|
|
4
5
|
function toMillis(d) {
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
if (d == null) return void 0;
|
|
7
|
+
return d instanceof Date ? d.getTime() : d;
|
|
7
8
|
}
|
|
8
9
|
function metaOf(item) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
return {
|
|
11
|
+
key: item.path,
|
|
12
|
+
size: item.size,
|
|
13
|
+
type: item.contentType || "application/octet-stream",
|
|
14
|
+
etag: item.etag,
|
|
15
|
+
lastModified: toMillis(item.lastModified),
|
|
16
|
+
metadata: item.metadata
|
|
17
|
+
};
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
async signedUploadUrl (key, opts) {
|
|
121
|
-
if (!adapter.signedUploadUrl) {
|
|
122
|
-
throw new StorageError('Unsupported', 'storagesdk adapter cannot presign uploads');
|
|
123
|
-
}
|
|
124
|
-
return adapter.signedUploadUrl(key, {
|
|
125
|
-
expiresIn: opts.expiresIn,
|
|
126
|
-
contentType: opts.contentType
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
if (adapter.move) {
|
|
131
|
-
driver.move = (from, to, opts)=>adapter.move(from, to, {
|
|
132
|
-
signal: opts?.signal
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
return driver;
|
|
20
|
+
* Adapt a `@storagesdk/adapters` adapter to a {@link StorageDriver}.
|
|
21
|
+
*
|
|
22
|
+
* storagesdk's shape differs from ours (path-based; `download` returns buffered
|
|
23
|
+
* bytes rather than a stream; no first-class multipart or capability flags),
|
|
24
|
+
* so this is an explicit mapping. Multipart / presigned-multipart are not
|
|
25
|
+
* exposed (the facade will gate them as `Unsupported`).
|
|
26
|
+
*/
|
|
27
|
+
function storageSdkDriver(adapter, options) {
|
|
28
|
+
const hasUploadUrl = typeof adapter.signedUploadUrl === "function";
|
|
29
|
+
const driver = {
|
|
30
|
+
name: `storagesdk:${adapter.name ?? "adapter"}`,
|
|
31
|
+
raw: adapter.raw,
|
|
32
|
+
supportsRange: options?.supportsRange ?? false,
|
|
33
|
+
supportsDelimiter: options?.supportsDelimiter ?? false,
|
|
34
|
+
supportsMetadata: options?.supportsMetadata ?? true,
|
|
35
|
+
supportsCacheControl: options?.supportsCacheControl ?? false,
|
|
36
|
+
supportsServerSideCopy: true,
|
|
37
|
+
reportsUploadProgress: false,
|
|
38
|
+
signedUrl: {
|
|
39
|
+
supported: typeof adapter.url === "function",
|
|
40
|
+
upload: hasUploadUrl
|
|
41
|
+
},
|
|
42
|
+
async upload(key, body, opts) {
|
|
43
|
+
const r = await adapter.upload(key, body, {
|
|
44
|
+
contentType: opts?.contentType,
|
|
45
|
+
metadata: opts?.metadata,
|
|
46
|
+
signal: opts?.signal
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
key,
|
|
50
|
+
size: r?.size ?? byteLengthOf(body) ?? 0,
|
|
51
|
+
contentType: opts?.contentType ?? r?.contentType ?? "application/octet-stream",
|
|
52
|
+
etag: r?.etag,
|
|
53
|
+
lastModified: toMillis(r?.lastModified)
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async download(key, opts) {
|
|
57
|
+
const item = await adapter.download(key, { signal: opts?.signal });
|
|
58
|
+
return createStoredFile(metaOf(item), {
|
|
59
|
+
kind: "bytes",
|
|
60
|
+
bytes: item.body
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
async head(key, opts) {
|
|
64
|
+
return createStoredFile(metaOf(await adapter.head(key, { signal: opts?.signal })), {
|
|
65
|
+
kind: "lazy",
|
|
66
|
+
fetch: async () => new Response((await adapter.download(key)).body)
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
async exists(key, opts) {
|
|
70
|
+
if (adapter.exists) return adapter.exists(key, { signal: opts?.signal });
|
|
71
|
+
try {
|
|
72
|
+
await adapter.head(key, { signal: opts?.signal });
|
|
73
|
+
return true;
|
|
74
|
+
} catch (e) {
|
|
75
|
+
if (e instanceof StorageError && e.code === "NotFound") return false;
|
|
76
|
+
if (e && typeof e === "object" && e.code === "NotFound") return false;
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
async delete(key, opts) {
|
|
81
|
+
await adapter.delete(key, { signal: opts?.signal });
|
|
82
|
+
},
|
|
83
|
+
async copy(from, to, opts) {
|
|
84
|
+
await adapter.copy(from, to, { signal: opts?.signal });
|
|
85
|
+
},
|
|
86
|
+
async list(opts) {
|
|
87
|
+
const r = await adapter.list({
|
|
88
|
+
prefix: opts?.prefix,
|
|
89
|
+
cursor: opts?.cursor,
|
|
90
|
+
limit: opts?.limit,
|
|
91
|
+
delimiter: opts?.delimiter,
|
|
92
|
+
signal: opts?.signal
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
items: r.items.map((item) => createStoredFile(metaOf(item), {
|
|
96
|
+
kind: "lazy",
|
|
97
|
+
fetch: async () => new Response((await adapter.download(item.path)).body)
|
|
98
|
+
})),
|
|
99
|
+
prefixes: r.prefixes,
|
|
100
|
+
cursor: r.cursor
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
async url(key, opts) {
|
|
104
|
+
return adapter.url(key, {
|
|
105
|
+
expiresIn: opts?.expiresIn,
|
|
106
|
+
signal: opts?.signal
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
async signedUploadUrl(key, opts) {
|
|
110
|
+
if (!adapter.signedUploadUrl) throw new StorageError("Unsupported", "storagesdk adapter cannot presign uploads");
|
|
111
|
+
return adapter.signedUploadUrl(key, {
|
|
112
|
+
expiresIn: opts.expiresIn,
|
|
113
|
+
contentType: opts.contentType
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
if (adapter.move) driver.move = (from, to, opts) => adapter.move(from, to, { signal: opts?.signal });
|
|
118
|
+
return driver;
|
|
136
119
|
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { storageSdkDriver };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/storagesdk/index.ts"],"sourcesContent":["import { byteLengthOf } from '../internal/body';\nimport { createStoredFile } from '../internal/stored-file';\nimport { StorageError } from '../storage.error';\nimport type {\n Body,\n DownloadOptions,\n ListOptions,\n ListResult,\n OperationOptions,\n SignUploadOptions,\n StorageDriver,\n StoredFile,\n UploadOptions,\n UploadResult,\n UrlOptions,\n} from '../storage.types';\n\n// Opt-in bridge for `@storagesdk/adapters`. It is defined against a STRUCTURAL\n// interface (below) — never an import of `@storagesdk/core` — so this file\n// stays edge-clean and adds no dependency. Bring your own storagesdk adapter;\n// it is structurally assignable. NOTE: whichever storagesdk adapter you\n// construct may pull the AWS SDK / node built-ins, so this subpath is for\n// Node/Bun deployments, never the edge path. It is never re-exported from `.`.\n\n/** Metadata for one storagesdk object (path-based). */\nexport interface StorageSdkItemMeta {\n path: string;\n size: number;\n contentType: string;\n etag: string;\n lastModified: Date | number;\n metadata?: Record<string, string>;\n}\n\n/** A storagesdk download result: metadata plus the buffered body. */\nexport interface StorageSdkItem extends StorageSdkItemMeta {\n body: Uint8Array<ArrayBuffer>;\n}\n\nexport interface StorageSdkListResult {\n items: StorageSdkItemMeta[];\n cursor?: string;\n prefixes?: string[];\n}\n\n/** The structural subset of a `@storagesdk/adapters` adapter we consume. */\nexport interface StorageSdkAdapterLike<Raw = unknown> {\n readonly name?: string;\n readonly raw?: Raw;\n download(path: string, opts?: { signal?: AbortSignal }): Promise<StorageSdkItem>;\n head(path: string, opts?: { signal?: AbortSignal }): Promise<StorageSdkItemMeta>;\n list(opts?: {\n prefix?: string;\n cursor?: string;\n limit?: number;\n delimiter?: string;\n signal?: AbortSignal;\n }): Promise<StorageSdkListResult>;\n url(path: string, opts?: { expiresIn?: number; signal?: AbortSignal }): Promise<string>;\n upload(\n path: string,\n body: Body,\n opts?: { contentType?: string; metadata?: Record<string, string>; signal?: AbortSignal },\n ): Promise<StorageSdkItemMeta | void>;\n delete(path: string, opts?: { signal?: AbortSignal }): Promise<void>;\n copy(from: string, to: string, opts?: { signal?: AbortSignal }): Promise<void>;\n move?(from: string, to: string, opts?: { signal?: AbortSignal }): Promise<void>;\n exists?(path: string, opts?: { signal?: AbortSignal }): Promise<boolean>;\n signedUploadUrl?(\n path: string,\n opts: { expiresIn: number; contentType?: string },\n ): Promise<{ method: 'PUT'; url: string; headers?: Record<string, string> }>;\n}\n\nexport interface StorageSdkBridgeOptions {\n /** Advertise range support (only if your adapter honors `range`). Default false. */\n supportsRange?: boolean;\n /** Advertise delimiter/prefix support. Default false. */\n supportsDelimiter?: boolean;\n /** Advertise arbitrary metadata support. Default true. */\n supportsMetadata?: boolean;\n /** Advertise cache-control support. Default false. */\n supportsCacheControl?: boolean;\n}\n\nfunction toMillis(d: Date | number | undefined): number | undefined {\n if (d == null) return undefined;\n return d instanceof Date ? d.getTime() : d;\n}\n\nfunction metaOf(item: StorageSdkItemMeta) {\n return {\n key: item.path,\n size: item.size,\n type: item.contentType || 'application/octet-stream',\n etag: item.etag,\n lastModified: toMillis(item.lastModified),\n metadata: item.metadata,\n };\n}\n\n/**\n * Adapt a `@storagesdk/adapters` adapter to a {@link StorageDriver}.\n *\n * storagesdk's shape differs from ours (path-based; `download` returns buffered\n * bytes rather than a stream; no first-class multipart or capability flags),\n * so this is an explicit mapping. Multipart / presigned-multipart are not\n * exposed (the facade will gate them as `Unsupported`).\n */\nexport function storageSdkDriver<Raw = unknown>(\n adapter: StorageSdkAdapterLike<Raw>,\n options?: StorageSdkBridgeOptions,\n): StorageDriver<Raw> {\n const hasUploadUrl = typeof adapter.signedUploadUrl === 'function';\n\n const driver: StorageDriver<Raw> = {\n name: `storagesdk:${adapter.name ?? 'adapter'}`,\n raw: adapter.raw as Raw,\n supportsRange: options?.supportsRange ?? false,\n supportsDelimiter: options?.supportsDelimiter ?? false,\n supportsMetadata: options?.supportsMetadata ?? true,\n supportsCacheControl: options?.supportsCacheControl ?? false,\n supportsServerSideCopy: true,\n reportsUploadProgress: false,\n signedUrl: { supported: typeof adapter.url === 'function', upload: hasUploadUrl },\n\n async upload(key: string, body: Body, opts?: UploadOptions): Promise<UploadResult> {\n const r = await adapter.upload(key, body, {\n contentType: opts?.contentType,\n metadata: opts?.metadata,\n signal: opts?.signal,\n });\n return {\n key,\n size: r?.size ?? byteLengthOf(body) ?? 0,\n contentType: opts?.contentType ?? r?.contentType ?? 'application/octet-stream',\n etag: r?.etag,\n lastModified: toMillis(r?.lastModified),\n };\n },\n\n async download(key: string, opts?: DownloadOptions): Promise<StoredFile> {\n const item = await adapter.download(key, { signal: opts?.signal });\n return createStoredFile(metaOf(item), { kind: 'bytes', bytes: item.body });\n },\n\n async head(key: string, opts?: OperationOptions): Promise<StoredFile> {\n const item = await adapter.head(key, { signal: opts?.signal });\n return createStoredFile(metaOf(item), {\n kind: 'lazy',\n fetch: async () => new Response((await adapter.download(key)).body),\n });\n },\n\n async exists(key: string, opts?: OperationOptions): Promise<boolean> {\n if (adapter.exists) return adapter.exists(key, { signal: opts?.signal });\n try {\n await adapter.head(key, { signal: opts?.signal });\n return true;\n } catch (e) {\n if (e instanceof StorageError && e.code === 'NotFound') return false;\n if (e && typeof e === 'object' && (e as { code?: string }).code === 'NotFound')\n return false;\n throw e;\n }\n },\n\n async delete(key: string, opts?: OperationOptions): Promise<void> {\n await adapter.delete(key, { signal: opts?.signal });\n },\n\n async copy(from: string, to: string, opts?: OperationOptions): Promise<void> {\n await adapter.copy(from, to, { signal: opts?.signal });\n },\n\n async list(opts?: ListOptions): Promise<ListResult> {\n const r = await adapter.list({\n prefix: opts?.prefix,\n cursor: opts?.cursor,\n limit: opts?.limit,\n delimiter: opts?.delimiter,\n signal: opts?.signal,\n });\n return {\n items: r.items.map((item) =>\n createStoredFile(metaOf(item), {\n kind: 'lazy',\n fetch: async () => new Response((await adapter.download(item.path)).body),\n }),\n ),\n prefixes: r.prefixes,\n cursor: r.cursor,\n };\n },\n\n async url(key: string, opts?: UrlOptions): Promise<string> {\n return adapter.url(key, { expiresIn: opts?.expiresIn, signal: opts?.signal });\n },\n\n async signedUploadUrl(key: string, opts: SignUploadOptions) {\n if (!adapter.signedUploadUrl) {\n throw new StorageError('Unsupported', 'storagesdk adapter cannot presign uploads');\n }\n return adapter.signedUploadUrl(key, {\n expiresIn: opts.expiresIn,\n contentType: opts.contentType,\n });\n },\n };\n\n if (adapter.move) {\n driver.move = (from, to, opts) => adapter.move!(from, to, { signal: opts?.signal });\n }\n return driver;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { t as StorageError } from "./storage.error-DVCOkJhc.js";
|
|
2
|
+
//#region src/internal/stored-file.ts
|
|
3
|
+
function nameFromKey(key) {
|
|
4
|
+
const i = key.lastIndexOf("/");
|
|
5
|
+
return i === -1 ? key : key.slice(i + 1);
|
|
6
|
+
}
|
|
7
|
+
function bytesToStream(bytes) {
|
|
8
|
+
return new ReadableStream({ start(controller) {
|
|
9
|
+
controller.enqueue(bytes);
|
|
10
|
+
controller.close();
|
|
11
|
+
} });
|
|
12
|
+
}
|
|
13
|
+
function toArrayBuffer(bytes) {
|
|
14
|
+
const ab = new ArrayBuffer(bytes.byteLength);
|
|
15
|
+
new Uint8Array(ab).set(bytes);
|
|
16
|
+
return ab;
|
|
17
|
+
}
|
|
18
|
+
var StoredFileImpl = class {
|
|
19
|
+
key;
|
|
20
|
+
name;
|
|
21
|
+
size;
|
|
22
|
+
type;
|
|
23
|
+
lastModified;
|
|
24
|
+
etag;
|
|
25
|
+
metadata;
|
|
26
|
+
#source;
|
|
27
|
+
#bytes;
|
|
28
|
+
#streamTaken = false;
|
|
29
|
+
constructor(meta, source) {
|
|
30
|
+
this.key = meta.key;
|
|
31
|
+
this.name = meta.name ?? nameFromKey(meta.key);
|
|
32
|
+
this.size = meta.size;
|
|
33
|
+
this.type = meta.type ?? "application/octet-stream";
|
|
34
|
+
this.lastModified = meta.lastModified;
|
|
35
|
+
this.etag = meta.etag;
|
|
36
|
+
this.metadata = meta.metadata;
|
|
37
|
+
this.#source = source;
|
|
38
|
+
if (source.kind === "bytes") this.#bytes = source.bytes;
|
|
39
|
+
}
|
|
40
|
+
async #buffer() {
|
|
41
|
+
if (this.#bytes) return this.#bytes;
|
|
42
|
+
const s = this.#source;
|
|
43
|
+
if (s.kind === "bytes") {
|
|
44
|
+
this.#bytes = s.bytes;
|
|
45
|
+
return this.#bytes;
|
|
46
|
+
}
|
|
47
|
+
if (s.kind === "stream") {
|
|
48
|
+
if (this.#streamTaken) throw new StorageError("Provider", `body of ${this.key} already consumed`);
|
|
49
|
+
this.#streamTaken = true;
|
|
50
|
+
this.#bytes = new Uint8Array(await new Response(s.stream).arrayBuffer());
|
|
51
|
+
return this.#bytes;
|
|
52
|
+
}
|
|
53
|
+
const res = await s.fetch();
|
|
54
|
+
if (!res.ok) throw StorageError.fromStatus(res.status, `download failed: ${this.key}`);
|
|
55
|
+
this.#bytes = new Uint8Array(await res.arrayBuffer());
|
|
56
|
+
return this.#bytes;
|
|
57
|
+
}
|
|
58
|
+
async arrayBuffer() {
|
|
59
|
+
return toArrayBuffer(await this.#buffer());
|
|
60
|
+
}
|
|
61
|
+
async text() {
|
|
62
|
+
return new TextDecoder().decode(await this.#buffer());
|
|
63
|
+
}
|
|
64
|
+
async blob() {
|
|
65
|
+
const b = await this.#buffer();
|
|
66
|
+
return new Blob([toArrayBuffer(b)], { type: this.type });
|
|
67
|
+
}
|
|
68
|
+
stream() {
|
|
69
|
+
if (this.#bytes) return bytesToStream(this.#bytes);
|
|
70
|
+
const s = this.#source;
|
|
71
|
+
if (s.kind === "bytes") return bytesToStream(s.bytes);
|
|
72
|
+
if (s.kind === "stream") {
|
|
73
|
+
if (this.#streamTaken) throw new StorageError("Provider", `body of ${this.key} already consumed`);
|
|
74
|
+
this.#streamTaken = true;
|
|
75
|
+
return s.stream;
|
|
76
|
+
}
|
|
77
|
+
let reader;
|
|
78
|
+
const fetchFn = s.fetch;
|
|
79
|
+
const key = this.key;
|
|
80
|
+
return new ReadableStream({
|
|
81
|
+
async pull(controller) {
|
|
82
|
+
let r = reader;
|
|
83
|
+
if (!r) {
|
|
84
|
+
const res = await fetchFn();
|
|
85
|
+
if (!res.ok || !res.body) throw StorageError.fromStatus(res.status, `download failed: ${key}`);
|
|
86
|
+
r = res.body.getReader();
|
|
87
|
+
reader = r;
|
|
88
|
+
}
|
|
89
|
+
const { done, value } = await r.read();
|
|
90
|
+
if (done) controller.close();
|
|
91
|
+
else controller.enqueue(value);
|
|
92
|
+
},
|
|
93
|
+
cancel(reason) {
|
|
94
|
+
return reader?.cancel(reason);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function createStoredFile(meta, source) {
|
|
100
|
+
return new StoredFileImpl(meta, source);
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { createStoredFile as t };
|
|
104
|
+
|
|
105
|
+
//# sourceMappingURL=stored-file-2Yl07eRc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stored-file-2Yl07eRc.js","names":["#source","#bytes","#buffer","#streamTaken"],"sources":["../src/internal/stored-file.ts"],"sourcesContent":["import { StorageError } from '../storage.error';\nimport type { StoredFile } from '../storage.types';\n\nexport interface StoredFileMeta {\n key: string;\n name?: string;\n size: number;\n type?: string;\n lastModified?: number;\n etag?: string;\n metadata?: Record<string, string>;\n}\n\n/**\n * Where a {@link StoredFile}'s bytes come from:\n * - `bytes` — already in memory (memory driver, buffered reads).\n * - `stream` — a single-use byte stream (a live download response body).\n * - `lazy` — a thunk that issues the GET on first body access (head()).\n */\nexport type StoredFileSource =\n | { kind: 'bytes'; bytes: Uint8Array }\n | { kind: 'stream'; stream: ReadableStream<Uint8Array> }\n | { kind: 'lazy'; fetch: () => Promise<Response> };\n\nfunction nameFromKey(key: string): string {\n const i = key.lastIndexOf('/');\n return i === -1 ? key : key.slice(i + 1);\n}\n\nfunction bytesToStream(bytes: Uint8Array): ReadableStream<Uint8Array> {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n });\n}\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n const ab = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(ab).set(bytes);\n return ab;\n}\n\nclass StoredFileImpl implements StoredFile {\n readonly key: string;\n readonly name: string;\n readonly size: number;\n readonly type: string;\n readonly lastModified?: number;\n readonly etag?: string;\n readonly metadata?: Record<string, string>;\n\n #source: StoredFileSource;\n #bytes: Uint8Array | undefined;\n #streamTaken = false;\n\n constructor(meta: StoredFileMeta, source: StoredFileSource) {\n this.key = meta.key;\n this.name = meta.name ?? nameFromKey(meta.key);\n this.size = meta.size;\n this.type = meta.type ?? 'application/octet-stream';\n this.lastModified = meta.lastModified;\n this.etag = meta.etag;\n this.metadata = meta.metadata;\n this.#source = source;\n if (source.kind === 'bytes') this.#bytes = source.bytes;\n }\n\n async #buffer(): Promise<Uint8Array> {\n if (this.#bytes) return this.#bytes;\n const s = this.#source;\n if (s.kind === 'bytes') {\n this.#bytes = s.bytes;\n return this.#bytes;\n }\n if (s.kind === 'stream') {\n if (this.#streamTaken) {\n throw new StorageError('Provider', `body of ${this.key} already consumed`);\n }\n this.#streamTaken = true;\n this.#bytes = new Uint8Array(await new Response(s.stream).arrayBuffer());\n return this.#bytes;\n }\n // s.kind === 'lazy'\n const res = await s.fetch();\n if (!res.ok) throw StorageError.fromStatus(res.status, `download failed: ${this.key}`);\n this.#bytes = new Uint8Array(await res.arrayBuffer());\n return this.#bytes;\n }\n\n async arrayBuffer(): Promise<ArrayBuffer> {\n return toArrayBuffer(await this.#buffer());\n }\n\n async text(): Promise<string> {\n return new TextDecoder().decode(await this.#buffer());\n }\n\n async blob(): Promise<Blob> {\n const b = await this.#buffer();\n return new Blob([toArrayBuffer(b)], { type: this.type });\n }\n\n stream(): ReadableStream<Uint8Array> {\n if (this.#bytes) return bytesToStream(this.#bytes);\n const s = this.#source;\n if (s.kind === 'bytes') return bytesToStream(s.bytes);\n if (s.kind === 'stream') {\n if (this.#streamTaken) {\n throw new StorageError('Provider', `body of ${this.key} already consumed`);\n }\n this.#streamTaken = true;\n return s.stream;\n }\n // lazy: fetch on first pull\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n const fetchFn = s.fetch;\n const key = this.key;\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n let r = reader;\n if (!r) {\n const res = await fetchFn();\n if (!res.ok || !res.body) {\n throw StorageError.fromStatus(res.status, `download failed: ${key}`);\n }\n r = res.body.getReader();\n reader = r;\n }\n const { done, value } = await r.read();\n if (done) controller.close();\n else controller.enqueue(value);\n },\n cancel(reason) {\n return reader?.cancel(reason);\n },\n });\n }\n}\n\nexport function createStoredFile(meta: StoredFileMeta, source: StoredFileSource): StoredFile {\n return new StoredFileImpl(meta, source);\n}\n"],"mappings":"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"}
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { c as ListOptions, f as OperationOptions, l as ListResult } from "../storage.types-BJN7Yp1J.js";
|
|
2
|
+
//#region src/testing/index.d.ts
|
|
2
3
|
/**
|
|
3
4
|
* The read surface the assertions need. Satisfied by `StorageService`, the
|
|
4
5
|
* `Storage` facade, and a bare `StorageDriver` — anything memory-backed works.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
interface StorageTarget {
|
|
8
|
+
exists(key: string, opts?: OperationOptions): Promise<boolean>;
|
|
9
|
+
list(opts?: ListOptions): Promise<ListResult>;
|
|
9
10
|
}
|
|
10
11
|
/** Assert an object exists at `key`; throws a diagnostic `Error` otherwise. */
|
|
11
|
-
|
|
12
|
+
declare function assertExists(storage: StorageTarget, key: string): Promise<void>;
|
|
12
13
|
/** Assert nothing is stored at `key`; throws a diagnostic `Error` otherwise. */
|
|
13
|
-
|
|
14
|
+
declare function assertMissing(storage: StorageTarget, key: string): Promise<void>;
|
|
14
15
|
/** Assert the disk holds exactly `expected` objects. */
|
|
15
|
-
|
|
16
|
+
declare function assertCount(storage: StorageTarget, expected: number): Promise<void>;
|
|
16
17
|
/** Assert exactly `expected` objects exist under `prefix`. */
|
|
17
|
-
|
|
18
|
+
declare function assertCount(storage: StorageTarget, prefix: string, expected: number): Promise<void>;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { StorageTarget, assertCount, assertExists, assertMissing };
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/testing/index.js
CHANGED
|
@@ -1,57 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// import { memoryDriver } from '@velajs/storage/drivers/memory';
|
|
9
|
-
// import { assertExists, assertCount } from '@velajs/storage/testing';
|
|
10
|
-
//
|
|
11
|
-
// const storage = createStorage({ driver: memoryDriver() });
|
|
12
|
-
// await storage.upload('avatar.png', bytes);
|
|
13
|
-
// await assertExists(storage, 'avatar.png');
|
|
14
|
-
// await assertCount(storage, 'avatars/', 1);
|
|
15
|
-
//
|
|
16
|
-
// For DI/integration tests, register the same driver on the module instead —
|
|
17
|
-
// `StorageModule.forRoot({ driver: memoryDriver() })` — then inject the
|
|
18
|
-
// `StorageService` and pass it to these helpers. Both a `Storage` facade and a
|
|
19
|
-
// `StorageService` satisfy the {@link StorageTarget} surface below.
|
|
20
|
-
//
|
|
21
|
-
// The helpers are thin and framework-free: they throw a plain `Error` with a
|
|
22
|
-
// diagnostic message on failure (no vitest/`expect` dependency), and drive the
|
|
23
|
-
// public `exists`/`list` API so prefix scoping and drivers stay honest.
|
|
24
|
-
/** Assert an object exists at `key`; throws a diagnostic `Error` otherwise. */ export async function assertExists(storage, key) {
|
|
25
|
-
if (!await storage.exists(key)) {
|
|
26
|
-
const keys = await collectKeys(storage);
|
|
27
|
-
throw new Error(`assertExists: expected an object at "${key}" to exist, but it is missing. ` + `Stored: ${keys.length ? keys.join(', ') : '(none)'}`);
|
|
28
|
-
}
|
|
1
|
+
//#region src/testing/index.ts
|
|
2
|
+
/** Assert an object exists at `key`; throws a diagnostic `Error` otherwise. */
|
|
3
|
+
async function assertExists(storage, key) {
|
|
4
|
+
if (!await storage.exists(key)) {
|
|
5
|
+
const keys = await collectKeys(storage);
|
|
6
|
+
throw new Error(`assertExists: expected an object at "${key}" to exist, but it is missing. Stored: ${keys.length ? keys.join(", ") : "(none)"}`);
|
|
7
|
+
}
|
|
29
8
|
}
|
|
30
|
-
/** Assert nothing is stored at `key`; throws a diagnostic `Error` otherwise. */
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
9
|
+
/** Assert nothing is stored at `key`; throws a diagnostic `Error` otherwise. */
|
|
10
|
+
async function assertMissing(storage, key) {
|
|
11
|
+
if (await storage.exists(key)) throw new Error(`assertMissing: expected no object at "${key}", but one exists`);
|
|
34
12
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
13
|
+
async function assertCount(storage, prefixOrExpected, maybeExpected) {
|
|
14
|
+
const prefix = typeof prefixOrExpected === "string" ? prefixOrExpected : void 0;
|
|
15
|
+
const expected = typeof prefixOrExpected === "number" ? prefixOrExpected : maybeExpected;
|
|
16
|
+
const keys = await collectKeys(storage, prefix);
|
|
17
|
+
if (keys.length !== expected) {
|
|
18
|
+
const scope = prefix ? ` under prefix "${prefix}"` : "";
|
|
19
|
+
const found = keys.length ? `: ${keys.join(", ")}` : "";
|
|
20
|
+
throw new Error(`assertCount: expected ${expected} object(s)${scope}, but found ${keys.length}${found}`);
|
|
21
|
+
}
|
|
44
22
|
}
|
|
45
|
-
/** Walk every listing page (following the cursor) and collect the object keys. */
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
23
|
+
/** Walk every listing page (following the cursor) and collect the object keys. */
|
|
24
|
+
async function collectKeys(storage, prefix) {
|
|
25
|
+
const keys = [];
|
|
26
|
+
let cursor;
|
|
27
|
+
do {
|
|
28
|
+
const page = await storage.list({
|
|
29
|
+
prefix,
|
|
30
|
+
cursor
|
|
31
|
+
});
|
|
32
|
+
for (const item of page.items) keys.push(item.key);
|
|
33
|
+
cursor = page.cursor;
|
|
34
|
+
} while (cursor);
|
|
35
|
+
return keys;
|
|
57
36
|
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { assertCount, assertExists, assertMissing };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/testing/index.ts"],"sourcesContent":["// @velajs/storage/testing — assertion helpers for tests.\n//\n// Vela has no separate storage fake: the in-package **memory driver IS the\n// fake**. Build a test disk in one obvious line and assert against it —\n// no bootstrap, no subclass:\n//\n// import { createStorage } from '@velajs/storage';\n// import { memoryDriver } from '@velajs/storage/drivers/memory';\n// import { assertExists, assertCount } from '@velajs/storage/testing';\n//\n// const storage = createStorage({ driver: memoryDriver() });\n// await storage.upload('avatar.png', bytes);\n// await assertExists(storage, 'avatar.png');\n// await assertCount(storage, 'avatars/', 1);\n//\n// For DI/integration tests, register the same driver on the module instead —\n// `StorageModule.forRoot({ driver: memoryDriver() })` — then inject the\n// `StorageService` and pass it to these helpers. Both a `Storage` facade and a\n// `StorageService` satisfy the {@link StorageTarget} surface below.\n//\n// The helpers are thin and framework-free: they throw a plain `Error` with a\n// diagnostic message on failure (no vitest/`expect` dependency), and drive the\n// public `exists`/`list` API so prefix scoping and drivers stay honest.\n\nimport type { ListOptions, ListResult, OperationOptions } from '../storage.types';\n\n/**\n * The read surface the assertions need. Satisfied by `StorageService`, the\n * `Storage` facade, and a bare `StorageDriver` — anything memory-backed works.\n */\nexport interface StorageTarget {\n exists(key: string, opts?: OperationOptions): Promise<boolean>;\n list(opts?: ListOptions): Promise<ListResult>;\n}\n\n/** Assert an object exists at `key`; throws a diagnostic `Error` otherwise. */\nexport async function assertExists(storage: StorageTarget, key: string): Promise<void> {\n if (!(await storage.exists(key))) {\n const keys = await collectKeys(storage);\n throw new Error(\n `assertExists: expected an object at \"${key}\" to exist, but it is missing. ` +\n `Stored: ${keys.length ? keys.join(', ') : '(none)'}`,\n );\n }\n}\n\n/** Assert nothing is stored at `key`; throws a diagnostic `Error` otherwise. */\nexport async function assertMissing(storage: StorageTarget, key: string): Promise<void> {\n if (await storage.exists(key)) {\n throw new Error(`assertMissing: expected no object at \"${key}\", but one exists`);\n }\n}\n\n/** Assert the disk holds exactly `expected` objects. */\nexport function assertCount(storage: StorageTarget, expected: number): Promise<void>;\n/** Assert exactly `expected` objects exist under `prefix`. */\nexport function assertCount(\n storage: StorageTarget,\n prefix: string,\n expected: number,\n): Promise<void>;\nexport async function assertCount(\n storage: StorageTarget,\n prefixOrExpected: string | number,\n maybeExpected?: number,\n): Promise<void> {\n const prefix = typeof prefixOrExpected === 'string' ? prefixOrExpected : undefined;\n const expected = typeof prefixOrExpected === 'number' ? prefixOrExpected : maybeExpected!;\n const keys = await collectKeys(storage, prefix);\n if (keys.length !== expected) {\n const scope = prefix ? ` under prefix \"${prefix}\"` : '';\n const found = keys.length ? `: ${keys.join(', ')}` : '';\n throw new Error(\n `assertCount: expected ${expected} object(s)${scope}, but found ${keys.length}${found}`,\n );\n }\n}\n\n/** Walk every listing page (following the cursor) and collect the object keys. */\nasync function collectKeys(storage: StorageTarget, prefix?: string): Promise<string[]> {\n const keys: string[] = [];\n let cursor: string | undefined;\n do {\n const page = await storage.list({ prefix, cursor });\n for (const item of page.items) keys.push(item.key);\n cursor = page.cursor;\n } while (cursor);\n return keys;\n}\n"],"mappings":";;AAoCA,eAAsB,aAAa,SAAwB,KAA4B;CACrF,IAAI,CAAE,MAAM,QAAQ,OAAO,GAAG,GAAI;EAChC,MAAM,OAAO,MAAM,YAAY,OAAO;EACtC,MAAM,IAAI,MACR,wCAAwC,IAAI,yCAC/B,KAAK,SAAS,KAAK,KAAK,IAAI,IAAI,UAC/C;CACF;AACF;;AAGA,eAAsB,cAAc,SAAwB,KAA4B;CACtF,IAAI,MAAM,QAAQ,OAAO,GAAG,GAC1B,MAAM,IAAI,MAAM,yCAAyC,IAAI,kBAAkB;AAEnF;AAUA,eAAsB,YACpB,SACA,kBACA,eACe;CACf,MAAM,SAAS,OAAO,qBAAqB,WAAW,mBAAmB,KAAA;CACzE,MAAM,WAAW,OAAO,qBAAqB,WAAW,mBAAmB;CAC3E,MAAM,OAAO,MAAM,YAAY,SAAS,MAAM;CAC9C,IAAI,KAAK,WAAW,UAAU;EAC5B,MAAM,QAAQ,SAAS,kBAAkB,OAAO,KAAK;EACrD,MAAM,QAAQ,KAAK,SAAS,KAAK,KAAK,KAAK,IAAI,MAAM;EACrD,MAAM,IAAI,MACR,yBAAyB,SAAS,YAAY,MAAM,cAAc,KAAK,SAAS,OAClF;CACF;AACF;;AAGA,eAAe,YAAY,SAAwB,QAAoC;CACrF,MAAM,OAAiB,CAAC;CACxB,IAAI;CACJ,GAAG;EACD,MAAM,OAAO,MAAM,QAAQ,KAAK;GAAE;GAAQ;EAAO,CAAC;EAClD,KAAK,MAAM,QAAQ,KAAK,OAAO,KAAK,KAAK,KAAK,GAAG;EACjD,SAAS,KAAK;CAChB,SAAS;CACT,OAAO;AACT"}
|