@btravstack/storage 0.4.0 → 0.6.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/README.md +3 -3
- package/dist/index.cjs +29 -21
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +31 -23
- package/dist/s3.cjs +7 -1
- package/dist/s3.d.cts +1 -1
- package/dist/s3.d.mts +1 -1
- package/dist/s3.mjs +7 -1
- package/dist/{storage-C9j3zj7r.d.cts → storage-Cu27C7Oq.d.cts} +22 -1
- package/dist/{storage-C9j3zj7r.d.mts → storage-Cu27C7Oq.d.mts} +22 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> The object-storage port for [`@btravstack/core`](../core): one `Storage` an
|
|
4
4
|
> application depends on, an in-memory adapter, an S3-compatible one with
|
|
5
|
-
> presigned reads, and a span, a count and a log line per operation.
|
|
5
|
+
> presigned reads and writes, and a span, a count and a log line per operation.
|
|
6
6
|
|
|
7
7
|
📖 **[Documentation](https://btravstack.github.io/btravstack/reference/storage)** ·
|
|
8
8
|
[API Reference](https://btravstack.github.io/btravstack/api/storage/)
|
|
@@ -16,7 +16,7 @@ Four peer dependencies, plus `@aws-sdk/client-s3` and
|
|
|
16
16
|
S3 adapter. Instrumentation needs no peer of its own: the `Logger`, `Tracer`
|
|
17
17
|
and `Meter` it depends on are
|
|
18
18
|
[the kernel's ports](https://btravstack.github.io/btravstack/reference/core/observability).
|
|
19
|
-
Node `>=
|
|
19
|
+
Node `>=22`.
|
|
20
20
|
|
|
21
21
|
## A worked example
|
|
22
22
|
|
|
@@ -129,5 +129,5 @@ says so instead of minting a URL that would fail only in production.
|
|
|
129
129
|
|
|
130
130
|
**It does not decide** what your keys look like, whether a failed write should
|
|
131
131
|
fail your request, or how long a link should live. There is no streaming, no
|
|
132
|
-
|
|
132
|
+
no listing and no bucket management; the reasons are in
|
|
133
133
|
[`CLAUDE.md`](./CLAUDE.md).
|
package/dist/index.cjs
CHANGED
|
@@ -32,7 +32,8 @@ const memoryStorageBackend = () => {
|
|
|
32
32
|
objects.delete(key);
|
|
33
33
|
return (0, unthrown.OkAsync)();
|
|
34
34
|
},
|
|
35
|
-
presignedUrl: (key) => (0, unthrown.ErrAsync)(new require_storage.PresignNotSupported({ key }))
|
|
35
|
+
presignedUrl: (key) => (0, unthrown.ErrAsync)(new require_storage.PresignNotSupported({ key })),
|
|
36
|
+
presignedUpload: (key) => (0, unthrown.ErrAsync)(new require_storage.PresignNotSupported({ key }))
|
|
36
37
|
};
|
|
37
38
|
};
|
|
38
39
|
/** The adapter as a provider, which is the shape `@btravstack/testing`'s `overridden` takes. */
|
|
@@ -95,7 +96,8 @@ const instrument = (backend, logger, tracer, meter) => {
|
|
|
95
96
|
put: (key, bytes, options) => observed(() => backend.put(key, bytes, options), context("put", key)),
|
|
96
97
|
get: (key) => observed(() => backend.get(key), context("get", key)),
|
|
97
98
|
delete: (key) => observed(() => backend.delete(key), context("delete", key)),
|
|
98
|
-
presignedUrl: (key, options) => observed(() => backend.presignedUrl(key, options), context("presigned_url", key))
|
|
99
|
+
presignedUrl: (key, options) => observed(() => backend.presignedUrl(key, options), context("presigned_url", key)),
|
|
100
|
+
presignedUpload: (key, options) => observed(() => backend.presignedUpload(key, options), context("presigned_upload", key))
|
|
99
101
|
};
|
|
100
102
|
};
|
|
101
103
|
//#endregion
|
|
@@ -113,25 +115,31 @@ const instrument = (backend, logger, tracer, meter) => {
|
|
|
113
115
|
* application depends on must not be the one an adapter provides, which is also
|
|
114
116
|
* why instrumentation is a flag on the composition rather than a wrapper.
|
|
115
117
|
*/
|
|
116
|
-
const storage = ({ adapter, instrumented }) =>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
_btravstack_core.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
118
|
+
const storage = ({ adapter, instrumented }) => {
|
|
119
|
+
const healthCheck = _btravstack_di.Provider.member(_btravstack_core.HealthChecks)({ backend: require_storage.StorageBackend }, { sync: ({ backend }) => ({
|
|
120
|
+
name: "storage",
|
|
121
|
+
check: () => backend.get("btravstack:health").map(() => void 0).flatMapErrCases((matcher) => matcher.with(unthrown.P.tag("ObjectNotFound"), () => (0, unthrown.Ok)(void 0)).with(unthrown.P.tag("StorageUnavailable"), ({ reason }) => (0, unthrown.Err)(new _btravstack_core.HealthCheckFailed({ reason: `storage unavailable (${reason})` }))))
|
|
122
|
+
}) });
|
|
123
|
+
return instrumented !== false ? (0, _btravstack_di.Module)("InstrumentedStorage")({
|
|
124
|
+
needs: [
|
|
125
|
+
_btravstack_core.Logger,
|
|
126
|
+
_btravstack_core.Meter,
|
|
127
|
+
_btravstack_core.Tracer
|
|
128
|
+
],
|
|
129
|
+
imports: [adapter],
|
|
130
|
+
provides: [(0, _btravstack_di.Provider)(require_storage.Storage)({
|
|
131
|
+
backend: require_storage.StorageBackend,
|
|
132
|
+
logger: _btravstack_core.Logger,
|
|
133
|
+
tracer: _btravstack_core.Tracer,
|
|
134
|
+
meter: _btravstack_core.Meter
|
|
135
|
+
}, { sync: ({ backend, logger, tracer, meter }) => instrument(backend, logger, tracer, meter) }), healthCheck],
|
|
136
|
+
exports: [require_storage.Storage, _btravstack_core.HealthChecks]
|
|
137
|
+
}) : (0, _btravstack_di.Module)("Storage")({
|
|
138
|
+
imports: [adapter],
|
|
139
|
+
provides: [(0, _btravstack_di.Provider)(require_storage.Storage)({ backend: require_storage.StorageBackend }, { sync: ({ backend }) => backend }), healthCheck],
|
|
140
|
+
exports: [require_storage.Storage, _btravstack_core.HealthChecks]
|
|
141
|
+
});
|
|
142
|
+
};
|
|
135
143
|
//#endregion
|
|
136
144
|
exports.ObjectNotFound = require_storage.ObjectNotFound;
|
|
137
145
|
exports.PresignNotSupported = require_storage.PresignNotSupported;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as StorageService, i as StorageBackend, n as PresignNotSupported, o as StorageUnavailable, r as Storage, s as StoredObject, t as ObjectNotFound } from "./storage-
|
|
1
|
+
import { a as StorageService, i as StorageBackend, n as PresignNotSupported, o as StorageUnavailable, r as Storage, s as StoredObject, t as ObjectNotFound } from "./storage-Cu27C7Oq.cjs";
|
|
2
2
|
import { Module, Provider } from "@btravstack/di";
|
|
3
|
-
import { Logger, Meter, Tracer } from "@btravstack/core";
|
|
3
|
+
import { HealthChecks, Logger, Meter, Tracer } from "@btravstack/core";
|
|
4
4
|
//#region src/memory.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* The in-process adapter: a `Map`, and an honest refusal to presign.
|
|
@@ -51,6 +51,6 @@ type StorageOptions<E, N, Instrumented extends boolean> = {
|
|
|
51
51
|
* application depends on must not be the one an adapter provides, which is also
|
|
52
52
|
* why instrumentation is a flag on the composition rather than a wrapper.
|
|
53
53
|
*/
|
|
54
|
-
declare const storage: <E, N, Instrumented extends boolean = true>({ adapter, instrumented }: StorageOptions<E, N, Instrumented>) => Module<Storage, E, Instrumented extends true ? N | Logger | Meter | Tracer : N>;
|
|
54
|
+
declare const storage: <E, N, Instrumented extends boolean = true>({ adapter, instrumented }: StorageOptions<E, N, Instrumented>) => Module<Storage | HealthChecks, E, Instrumented extends true ? N | Logger | Meter | Tracer : N>;
|
|
55
55
|
//#endregion
|
|
56
56
|
export { ObjectNotFound, PresignNotSupported, Storage, StorageBackend, type StorageOptions, type StorageService, StorageUnavailable, type StoredObject, memoryStorage, memoryStorageBackend, memoryStorageProvider, storage };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as StorageService, i as StorageBackend, n as PresignNotSupported, o as StorageUnavailable, r as Storage, s as StoredObject, t as ObjectNotFound } from "./storage-
|
|
1
|
+
import { a as StorageService, i as StorageBackend, n as PresignNotSupported, o as StorageUnavailable, r as Storage, s as StoredObject, t as ObjectNotFound } from "./storage-Cu27C7Oq.mjs";
|
|
2
2
|
import { Module, Provider } from "@btravstack/di";
|
|
3
|
-
import { Logger, Meter, Tracer } from "@btravstack/core";
|
|
3
|
+
import { HealthChecks, Logger, Meter, Tracer } from "@btravstack/core";
|
|
4
4
|
//#region src/memory.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* The in-process adapter: a `Map`, and an honest refusal to presign.
|
|
@@ -51,6 +51,6 @@ type StorageOptions<E, N, Instrumented extends boolean> = {
|
|
|
51
51
|
* application depends on must not be the one an adapter provides, which is also
|
|
52
52
|
* why instrumentation is a flag on the composition rather than a wrapper.
|
|
53
53
|
*/
|
|
54
|
-
declare const storage: <E, N, Instrumented extends boolean = true>({ adapter, instrumented }: StorageOptions<E, N, Instrumented>) => Module<Storage, E, Instrumented extends true ? N | Logger | Meter | Tracer : N>;
|
|
54
|
+
declare const storage: <E, N, Instrumented extends boolean = true>({ adapter, instrumented }: StorageOptions<E, N, Instrumented>) => Module<Storage | HealthChecks, E, Instrumented extends true ? N | Logger | Meter | Tracer : N>;
|
|
55
55
|
//#endregion
|
|
56
56
|
export { ObjectNotFound, PresignNotSupported, Storage, StorageBackend, type StorageOptions, type StorageService, StorageUnavailable, type StoredObject, memoryStorage, memoryStorageBackend, memoryStorageProvider, storage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as StorageUnavailable, i as StorageBackend, n as PresignNotSupported, r as Storage, t as ObjectNotFound } from "./storage-CRkwIhac.mjs";
|
|
2
2
|
import { Module, Provider } from "@btravstack/di";
|
|
3
|
-
import { ErrAsync, OkAsync } from "unthrown";
|
|
4
|
-
import { Logger, Meter, SPAN_STATUS, Tracer } from "@btravstack/core";
|
|
3
|
+
import { Err, ErrAsync, Ok, OkAsync, P } from "unthrown";
|
|
4
|
+
import { HealthCheckFailed, HealthChecks, Logger, Meter, SPAN_STATUS, Tracer } from "@btravstack/core";
|
|
5
5
|
//#region src/memory.ts
|
|
6
6
|
/**
|
|
7
7
|
* The in-process adapter: a `Map`, and an honest refusal to presign.
|
|
@@ -31,7 +31,8 @@ const memoryStorageBackend = () => {
|
|
|
31
31
|
objects.delete(key);
|
|
32
32
|
return OkAsync();
|
|
33
33
|
},
|
|
34
|
-
presignedUrl: (key) => ErrAsync(new PresignNotSupported({ key }))
|
|
34
|
+
presignedUrl: (key) => ErrAsync(new PresignNotSupported({ key })),
|
|
35
|
+
presignedUpload: (key) => ErrAsync(new PresignNotSupported({ key }))
|
|
35
36
|
};
|
|
36
37
|
};
|
|
37
38
|
/** The adapter as a provider, which is the shape `@btravstack/testing`'s `overridden` takes. */
|
|
@@ -94,7 +95,8 @@ const instrument = (backend, logger, tracer, meter) => {
|
|
|
94
95
|
put: (key, bytes, options) => observed(() => backend.put(key, bytes, options), context("put", key)),
|
|
95
96
|
get: (key) => observed(() => backend.get(key), context("get", key)),
|
|
96
97
|
delete: (key) => observed(() => backend.delete(key), context("delete", key)),
|
|
97
|
-
presignedUrl: (key, options) => observed(() => backend.presignedUrl(key, options), context("presigned_url", key))
|
|
98
|
+
presignedUrl: (key, options) => observed(() => backend.presignedUrl(key, options), context("presigned_url", key)),
|
|
99
|
+
presignedUpload: (key, options) => observed(() => backend.presignedUpload(key, options), context("presigned_upload", key))
|
|
98
100
|
};
|
|
99
101
|
};
|
|
100
102
|
//#endregion
|
|
@@ -112,24 +114,30 @@ const instrument = (backend, logger, tracer, meter) => {
|
|
|
112
114
|
* application depends on must not be the one an adapter provides, which is also
|
|
113
115
|
* why instrumentation is a flag on the composition rather than a wrapper.
|
|
114
116
|
*/
|
|
115
|
-
const storage = ({ adapter, instrumented }) =>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
117
|
+
const storage = ({ adapter, instrumented }) => {
|
|
118
|
+
const healthCheck = Provider.member(HealthChecks)({ backend: StorageBackend }, { sync: ({ backend }) => ({
|
|
119
|
+
name: "storage",
|
|
120
|
+
check: () => backend.get("btravstack:health").map(() => void 0).flatMapErrCases((matcher) => matcher.with(P.tag("ObjectNotFound"), () => Ok(void 0)).with(P.tag("StorageUnavailable"), ({ reason }) => Err(new HealthCheckFailed({ reason: `storage unavailable (${reason})` }))))
|
|
121
|
+
}) });
|
|
122
|
+
return instrumented !== false ? Module("InstrumentedStorage")({
|
|
123
|
+
needs: [
|
|
124
|
+
Logger,
|
|
125
|
+
Meter,
|
|
126
|
+
Tracer
|
|
127
|
+
],
|
|
128
|
+
imports: [adapter],
|
|
129
|
+
provides: [Provider(Storage)({
|
|
130
|
+
backend: StorageBackend,
|
|
131
|
+
logger: Logger,
|
|
132
|
+
tracer: Tracer,
|
|
133
|
+
meter: Meter
|
|
134
|
+
}, { sync: ({ backend, logger, tracer, meter }) => instrument(backend, logger, tracer, meter) }), healthCheck],
|
|
135
|
+
exports: [Storage, HealthChecks]
|
|
136
|
+
}) : Module("Storage")({
|
|
137
|
+
imports: [adapter],
|
|
138
|
+
provides: [Provider(Storage)({ backend: StorageBackend }, { sync: ({ backend }) => backend }), healthCheck],
|
|
139
|
+
exports: [Storage, HealthChecks]
|
|
140
|
+
});
|
|
141
|
+
};
|
|
134
142
|
//#endregion
|
|
135
143
|
export { ObjectNotFound, PresignNotSupported, Storage, StorageBackend, StorageUnavailable, memoryStorage, memoryStorageBackend, memoryStorageProvider, storage };
|
package/dist/s3.cjs
CHANGED
|
@@ -63,7 +63,13 @@ const s3StorageBackend = (client, bucket) => ({
|
|
|
63
63
|
presignedUrl: (key, options) => (0, unthrown.fromPromise)((0, _aws_sdk_s3_request_presigner.getSignedUrl)(client, new _aws_sdk_client_s3.GetObjectCommand({
|
|
64
64
|
Bucket: bucket,
|
|
65
65
|
Key: key
|
|
66
|
-
}), { expiresIn: Math.ceil(options.ttlMs / 1e3) }), (cause) => unavailable("presignedUrl", key, cause))
|
|
66
|
+
}), { expiresIn: Math.ceil(options.ttlMs / 1e3) }), (cause) => unavailable("presignedUrl", key, cause)),
|
|
67
|
+
presignedUpload: (key, options) => (0, unthrown.fromPromise)((0, _aws_sdk_s3_request_presigner.getSignedUrl)(client, new _aws_sdk_client_s3.PutObjectCommand({
|
|
68
|
+
Bucket: bucket,
|
|
69
|
+
Key: key,
|
|
70
|
+
ContentType: options.contentType,
|
|
71
|
+
ContentLength: options.contentLength
|
|
72
|
+
}), { expiresIn: Math.ceil(options.ttlMs / 1e3) }), (cause) => unavailable("presignedUpload", key, cause))
|
|
67
73
|
});
|
|
68
74
|
/**
|
|
69
75
|
* The S3 adapter: one client, built with the scope and destroyed with it, and
|
package/dist/s3.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as StorageService, i as StorageBackend } from "./storage-
|
|
1
|
+
import { a as StorageService, i as StorageBackend } from "./storage-Cu27C7Oq.cjs";
|
|
2
2
|
import { Module, Scope } from "@btravstack/di";
|
|
3
3
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
4
4
|
import { ConfigInvalid, Env } from "@btravstack/config";
|
package/dist/s3.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as StorageService, i as StorageBackend } from "./storage-
|
|
1
|
+
import { a as StorageService, i as StorageBackend } from "./storage-Cu27C7Oq.mjs";
|
|
2
2
|
import { Module, Scope } from "@btravstack/di";
|
|
3
3
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
4
4
|
import { ConfigInvalid, Env } from "@btravstack/config";
|
package/dist/s3.mjs
CHANGED
|
@@ -62,7 +62,13 @@ const s3StorageBackend = (client, bucket) => ({
|
|
|
62
62
|
presignedUrl: (key, options) => fromPromise(getSignedUrl(client, new GetObjectCommand({
|
|
63
63
|
Bucket: bucket,
|
|
64
64
|
Key: key
|
|
65
|
-
}), { expiresIn: Math.ceil(options.ttlMs / 1e3) }), (cause) => unavailable("presignedUrl", key, cause))
|
|
65
|
+
}), { expiresIn: Math.ceil(options.ttlMs / 1e3) }), (cause) => unavailable("presignedUrl", key, cause)),
|
|
66
|
+
presignedUpload: (key, options) => fromPromise(getSignedUrl(client, new PutObjectCommand({
|
|
67
|
+
Bucket: bucket,
|
|
68
|
+
Key: key,
|
|
69
|
+
ContentType: options.contentType,
|
|
70
|
+
ContentLength: options.contentLength
|
|
71
|
+
}), { expiresIn: Math.ceil(options.ttlMs / 1e3) }), (cause) => unavailable("presignedUpload", key, cause))
|
|
66
72
|
});
|
|
67
73
|
/**
|
|
68
74
|
* The S3 adapter: one client, built with the scope and destroyed with it, and
|
|
@@ -13,7 +13,7 @@ declare class ObjectNotFound extends ObjectNotFound_base<{
|
|
|
13
13
|
declare const StorageUnavailable_base: import("unthrown").TaggedErrorConstructor<"StorageUnavailable">;
|
|
14
14
|
/** The store could not answer — the endpoint is down, the credentials were refused, the bucket is gone. */
|
|
15
15
|
declare class StorageUnavailable extends StorageUnavailable_base<{
|
|
16
|
-
readonly operation: "put" | "get" | "delete" | "presignedUrl";
|
|
16
|
+
readonly operation: "put" | "get" | "delete" | "presignedUrl" | "presignedUpload";
|
|
17
17
|
readonly key: string;
|
|
18
18
|
readonly reason: string;
|
|
19
19
|
}> {}
|
|
@@ -40,6 +40,27 @@ type StorageService = {
|
|
|
40
40
|
readonly presignedUrl: (key: string, options: {
|
|
41
41
|
readonly ttlMs: number;
|
|
42
42
|
}) => AsyncResult<string, PresignNotSupported | StorageUnavailable>;
|
|
43
|
+
/**
|
|
44
|
+
* A URL that writes the object for `ttlMs`, without the caller holding
|
|
45
|
+
* credentials — so the bytes go from the client to the store and never
|
|
46
|
+
* through this process.
|
|
47
|
+
*
|
|
48
|
+
* **`contentType` and `contentLength` are signed, not advisory.** They are
|
|
49
|
+
* part of the signature, so a client sending different ones is refused by the
|
|
50
|
+
* store: the URL grants exactly one write, of exactly that many bytes, of
|
|
51
|
+
* exactly that type. `contentLength` is therefore required — an unsigned
|
|
52
|
+
* length would hand out an unbounded write, and there is no ceiling a
|
|
53
|
+
* presigned PUT can express other than the exact number.
|
|
54
|
+
*
|
|
55
|
+
* The application still decides every input: whether this caller may write
|
|
56
|
+
* this key, how long the URL lives, and what size it is willing to accept.
|
|
57
|
+
* The adapter computes a signature over that decision and nothing more.
|
|
58
|
+
*/
|
|
59
|
+
readonly presignedUpload: (key: string, options: {
|
|
60
|
+
readonly ttlMs: number;
|
|
61
|
+
readonly contentType: string;
|
|
62
|
+
readonly contentLength: number;
|
|
63
|
+
}) => AsyncResult<string, PresignNotSupported | StorageUnavailable>;
|
|
43
64
|
};
|
|
44
65
|
declare const Storage_base: import("@btravstack/di").PortClass<"Storage">;
|
|
45
66
|
/**
|
|
@@ -13,7 +13,7 @@ declare class ObjectNotFound extends ObjectNotFound_base<{
|
|
|
13
13
|
declare const StorageUnavailable_base: import("unthrown").TaggedErrorConstructor<"StorageUnavailable">;
|
|
14
14
|
/** The store could not answer — the endpoint is down, the credentials were refused, the bucket is gone. */
|
|
15
15
|
declare class StorageUnavailable extends StorageUnavailable_base<{
|
|
16
|
-
readonly operation: "put" | "get" | "delete" | "presignedUrl";
|
|
16
|
+
readonly operation: "put" | "get" | "delete" | "presignedUrl" | "presignedUpload";
|
|
17
17
|
readonly key: string;
|
|
18
18
|
readonly reason: string;
|
|
19
19
|
}> {}
|
|
@@ -40,6 +40,27 @@ type StorageService = {
|
|
|
40
40
|
readonly presignedUrl: (key: string, options: {
|
|
41
41
|
readonly ttlMs: number;
|
|
42
42
|
}) => AsyncResult<string, PresignNotSupported | StorageUnavailable>;
|
|
43
|
+
/**
|
|
44
|
+
* A URL that writes the object for `ttlMs`, without the caller holding
|
|
45
|
+
* credentials — so the bytes go from the client to the store and never
|
|
46
|
+
* through this process.
|
|
47
|
+
*
|
|
48
|
+
* **`contentType` and `contentLength` are signed, not advisory.** They are
|
|
49
|
+
* part of the signature, so a client sending different ones is refused by the
|
|
50
|
+
* store: the URL grants exactly one write, of exactly that many bytes, of
|
|
51
|
+
* exactly that type. `contentLength` is therefore required — an unsigned
|
|
52
|
+
* length would hand out an unbounded write, and there is no ceiling a
|
|
53
|
+
* presigned PUT can express other than the exact number.
|
|
54
|
+
*
|
|
55
|
+
* The application still decides every input: whether this caller may write
|
|
56
|
+
* this key, how long the URL lives, and what size it is willing to accept.
|
|
57
|
+
* The adapter computes a signature over that decision and nothing more.
|
|
58
|
+
*/
|
|
59
|
+
readonly presignedUpload: (key: string, options: {
|
|
60
|
+
readonly ttlMs: number;
|
|
61
|
+
readonly contentType: string;
|
|
62
|
+
readonly contentLength: number;
|
|
63
|
+
}) => AsyncResult<string, PresignNotSupported | StorageUnavailable>;
|
|
43
64
|
};
|
|
44
65
|
declare const Storage_base: import("@btravstack/di").PortClass<"Storage">;
|
|
45
66
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/storage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A Storage port for @btravstack/core: an in-memory adapter, an S3-compatible adapter with presigned reads, and a composition that spans, counts and logs every operation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dependency-injection",
|
|
@@ -67,19 +67,19 @@
|
|
|
67
67
|
"typescript": "7.0.2",
|
|
68
68
|
"unthrown": "5.7.0",
|
|
69
69
|
"vitest": "4.1.11",
|
|
70
|
-
"@btravstack/config": "0.
|
|
71
|
-
"@btravstack/
|
|
72
|
-
"@btravstack/di": "0.4.0",
|
|
70
|
+
"@btravstack/config": "0.6.0",
|
|
71
|
+
"@btravstack/di": "0.6.0",
|
|
73
72
|
"@btravstack/internal-test-infra": "0.0.0",
|
|
74
|
-
"@btravstack/
|
|
73
|
+
"@btravstack/core": "0.6.0",
|
|
74
|
+
"@btravstack/observability": "0.6.0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"@aws-sdk/client-s3": "^3.1000.0",
|
|
78
78
|
"@aws-sdk/s3-request-presigner": "^3.1000.0",
|
|
79
79
|
"unthrown": "^5.0.0",
|
|
80
|
-
"@btravstack/config": "^0.
|
|
81
|
-
"@btravstack/core": "^0.
|
|
82
|
-
"@btravstack/di": "^0.
|
|
80
|
+
"@btravstack/config": "^0.6.0",
|
|
81
|
+
"@btravstack/core": "^0.6.0",
|
|
82
|
+
"@btravstack/di": "^0.6.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependenciesMeta": {
|
|
85
85
|
"@aws-sdk/client-s3": {
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
92
|
"engines": {
|
|
93
|
-
"node": ">=
|
|
93
|
+
"node": ">=22"
|
|
94
94
|
},
|
|
95
95
|
"scripts": {
|
|
96
96
|
"build": "tsdown src/index.ts src/s3.ts --format cjs,esm --dts --clean",
|