@btravstack/storage 0.37.0 → 0.39.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 +12 -9
- package/dist/index.cjs +79 -30
- package/dist/index.d.cts +4 -2
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +64 -17
- package/dist/{storage-DlAQXJ1t.cjs → options-BhML_i5p.cjs} +36 -1
- package/dist/{storage-BmbyYPIE.mjs → options-DYPXkWb9.mjs} +20 -3
- package/dist/s3.cjs +87 -27
- package/dist/s3.d.cts +1 -1
- package/dist/s3.d.mts +1 -1
- package/dist/s3.mjs +84 -24
- package/dist/storage-CfN3KqWm.d.cts +154 -0
- package/dist/storage-CfN3KqWm.d.mts +154 -0
- package/package.json +8 -8
- package/dist/storage-uSBWFyB2.d.cts +0 -92
- package/dist/storage-uSBWFyB2.d.mts +0 -92
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ export const invoices = InvoiceProvider(Invoices)({
|
|
|
67
67
|
// is swallowed because an observer already recorded it and
|
|
68
68
|
// counted it one layer down.
|
|
69
69
|
.recoverErrCases((matcher) =>
|
|
70
|
-
matcher.with(P.tag("StorageUnavailable"), () => undefined),
|
|
70
|
+
matcher.with(P.tag("StorageUnavailable"), P.tag("InvalidStorageOptions"), P.tag("ChecksumMismatch"), () => undefined),
|
|
71
71
|
),
|
|
72
72
|
// Serving bytes through your own process is the anti-pattern this arm
|
|
73
73
|
// exists to avoid: the caller fetches the object directly, for a minute.
|
|
@@ -106,13 +106,16 @@ export const InvoicesApp = Module("InvoicesApp")({
|
|
|
106
106
|
|
|
107
107
|
## Options
|
|
108
108
|
|
|
109
|
-
| Option | Where | What it is
|
|
110
|
-
| ----------------------------------------------------------- | ---------------------------------- |
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
109
|
+
| Option | Where | What it is |
|
|
110
|
+
| ----------------------------------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------- |
|
|
111
|
+
| `sha256` / `metadata` | `put` / `putIfAbsent` | verify the write digest and atomically describe the object |
|
|
112
|
+
| `checksum` / `metadata` | `copy` | compute SHA-256 server side and replace the description |
|
|
113
|
+
| `kmsKeyId` | writes, copy and `presignedUpload` | select per-object SSE-KMS; upload clients send both signed encryption headers |
|
|
114
|
+
| `adapter` | `storage({ adapter })` | the adapter module providing `StorageBackend` — required |
|
|
115
|
+
| `STORAGE_S3_ENDPOINT` | environment, read by `s3Storage()` | the store's endpoint — required |
|
|
116
|
+
| `STORAGE_S3_BUCKET` | environment | the bucket — required |
|
|
117
|
+
| `STORAGE_S3_ACCESS_KEY_ID` / `STORAGE_S3_SECRET_ACCESS_KEY` | environment | the credentials — required |
|
|
118
|
+
| `STORAGE_S3_REGION` | environment | default `us-east-1` |
|
|
116
119
|
|
|
117
120
|
The full table — defaults, semantics and the reasoning — lives on
|
|
118
121
|
[the reference page](https://btravstack.github.io/btravstack/reference/storage),
|
|
@@ -121,7 +124,7 @@ which is this list's one detailed home.
|
|
|
121
124
|
## What it decides, and what it does not
|
|
122
125
|
|
|
123
126
|
**It decides** that a missing object is an ordinary answer rather than a fault
|
|
124
|
-
(counted `not_found`,
|
|
127
|
+
(counted `not_found`, without a fault line), that a held key under `putIfAbsent` is an ordinary answer (`ObjectExists`, counted `exists`), that `delete` is idempotent, that `copy` and `head` never move the bytes through the process, that writes can verify SHA-256 and publish user metadata atomically, that copies replace metadata and can compute SHA-256 server side, that presigned uploads can require a per-object KMS key, that
|
|
125
128
|
presigning asks the store nothing — and that an adapter which cannot presign
|
|
126
129
|
says so instead of minting a URL that would fail only in production.
|
|
127
130
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_options = require("./options-BhML_i5p.cjs");
|
|
3
|
+
let node_crypto = require("node:crypto");
|
|
3
4
|
let _btravstack_di = require("@btravstack/di");
|
|
4
5
|
let unthrown = require("unthrown");
|
|
5
6
|
let _btravstack_core = require("@btravstack/core");
|
|
@@ -11,48 +12,74 @@ let _btravstack_core = require("@btravstack/core");
|
|
|
11
12
|
* which would be the worst kind of double — one that passes locally and fails in
|
|
12
13
|
* the deployment for a reason no test could have shown.
|
|
13
14
|
*
|
|
15
|
+
* `kmsKeyId` is accepted and ignored: nothing is encrypted at rest here.
|
|
16
|
+
*
|
|
14
17
|
* ponytail: no size limit, so a process storing unbounded objects grows
|
|
15
18
|
* unbounded. The upgrade path is the S3 adapter.
|
|
16
19
|
*/
|
|
17
20
|
const memoryStorageBackend = () => {
|
|
18
21
|
const objects = /* @__PURE__ */ new Map();
|
|
22
|
+
const verify = (key, bytes, options) => require_options.validateOptions(key, options).flatMap(() => options.sha256 !== void 0 && (0, node_crypto.createHash)("sha256").update(bytes).digest("hex") !== options.sha256 ? (0, unthrown.Err)(new require_options.ChecksumMismatch({ key })) : (0, unthrown.Ok)());
|
|
19
23
|
return {
|
|
20
|
-
put: (key, bytes, options) => {
|
|
24
|
+
put: (key, bytes, options) => verify(key, bytes, options).map(() => {
|
|
21
25
|
objects.set(key, {
|
|
22
|
-
bytes,
|
|
23
|
-
contentType: options.contentType
|
|
26
|
+
bytes: Uint8Array.from(bytes),
|
|
27
|
+
contentType: options.contentType,
|
|
28
|
+
metadata: { ...options.metadata }
|
|
24
29
|
});
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (objects.has(key)) return (0, unthrown.ErrAsync)(new require_storage.ObjectExists({ key }));
|
|
30
|
+
}).toAsync(),
|
|
31
|
+
putIfAbsent: (key, bytes, options) => verify(key, bytes, options).flatMap(() => {
|
|
32
|
+
if (objects.has(key)) return (0, unthrown.Err)(new require_options.ObjectExists({ key }));
|
|
29
33
|
objects.set(key, {
|
|
30
|
-
bytes,
|
|
31
|
-
contentType: options.contentType
|
|
34
|
+
bytes: Uint8Array.from(bytes),
|
|
35
|
+
contentType: options.contentType,
|
|
36
|
+
metadata: { ...options.metadata }
|
|
37
|
+
});
|
|
38
|
+
return (0, unthrown.Ok)();
|
|
39
|
+
}).toAsync(),
|
|
40
|
+
copy: (from, to, options) => require_options.validateOptions(to, options).flatMap(() => {
|
|
41
|
+
const object = objects.get(from);
|
|
42
|
+
if (object === void 0) return (0, unthrown.Err)(new require_options.ObjectNotFound({ key: from }));
|
|
43
|
+
objects.set(to, {
|
|
44
|
+
bytes: object.bytes.slice(),
|
|
45
|
+
contentType: options.contentType,
|
|
46
|
+
metadata: { ...options.metadata }
|
|
47
|
+
});
|
|
48
|
+
return (0, unthrown.Ok)();
|
|
49
|
+
}).toAsync(),
|
|
50
|
+
head: (key) => {
|
|
51
|
+
const object = objects.get(key);
|
|
52
|
+
return object === void 0 ? (0, unthrown.ErrAsync)(new require_options.ObjectNotFound({ key })) : (0, unthrown.OkAsync)({
|
|
53
|
+
metadata: { ...object.metadata },
|
|
54
|
+
size: object.bytes.byteLength,
|
|
55
|
+
contentType: object.contentType,
|
|
56
|
+
sha256: (0, node_crypto.createHash)("sha256").update(object.bytes).digest("hex")
|
|
32
57
|
});
|
|
33
|
-
return (0, unthrown.OkAsync)();
|
|
34
58
|
},
|
|
35
59
|
get: (key) => {
|
|
36
60
|
const object = objects.get(key);
|
|
37
|
-
return object === void 0 ? (0, unthrown.ErrAsync)(new
|
|
61
|
+
return object === void 0 ? (0, unthrown.ErrAsync)(new require_options.ObjectNotFound({ key })) : (0, unthrown.OkAsync)({
|
|
62
|
+
contentType: object.contentType,
|
|
63
|
+
bytes: object.bytes.slice()
|
|
64
|
+
});
|
|
38
65
|
},
|
|
39
66
|
delete: (key) => {
|
|
40
67
|
objects.delete(key);
|
|
41
68
|
return (0, unthrown.OkAsync)();
|
|
42
69
|
},
|
|
43
|
-
presignedUrl: (key) => (0, unthrown.ErrAsync)(new
|
|
44
|
-
presignedUpload: (key) => (0, unthrown.ErrAsync)(new
|
|
70
|
+
presignedUrl: (key) => (0, unthrown.ErrAsync)(new require_options.PresignNotSupported({ key })),
|
|
71
|
+
presignedUpload: (key) => (0, unthrown.ErrAsync)(new require_options.PresignNotSupported({ key }))
|
|
45
72
|
};
|
|
46
73
|
};
|
|
47
74
|
/** The adapter as a provider, which is the shape `@btravstack/testing`'s `overridden` takes. */
|
|
48
|
-
const memoryStorageProvider = () => (0, _btravstack_di.Provider)(
|
|
75
|
+
const memoryStorageProvider = () => (0, _btravstack_di.Provider)(require_options.StorageBackend)({
|
|
49
76
|
inject: {},
|
|
50
77
|
sync: () => memoryStorageBackend()
|
|
51
78
|
});
|
|
52
79
|
/** The adapter as a module, which is the shape `storage({ adapter })` takes. */
|
|
53
80
|
const memoryStorage = () => (0, _btravstack_di.Module)("MemoryStorage")({
|
|
54
81
|
provides: [memoryStorageProvider()],
|
|
55
|
-
exports: [
|
|
82
|
+
exports: [require_options.StorageBackend]
|
|
56
83
|
});
|
|
57
84
|
//#endregion
|
|
58
85
|
//#region src/instrument.ts
|
|
@@ -60,8 +87,8 @@ const memoryStorage = () => (0, _btravstack_di.Module)("MemoryStorage")({
|
|
|
60
87
|
* **A missing object settles `ok` with `result: "not_found"`, not `error`.**
|
|
61
88
|
* Asking for something that is not there is an ordinary answer, and a dashboard
|
|
62
89
|
* that treats it as a fault teaches its readers to ignore the fault line.
|
|
63
|
-
* `StorageUnavailable` is what pages somebody
|
|
64
|
-
*
|
|
90
|
+
* `StorageUnavailable` is what pages somebody; invalid options and checksum
|
|
91
|
+
* mismatches also settle as errors. The two ordinary answers keep their own words in
|
|
65
92
|
* `reason`, so an operator is not left hunting an object sitting where they put
|
|
66
93
|
* it.
|
|
67
94
|
*/
|
|
@@ -103,9 +130,29 @@ const through = (observers) => (operation, key, call) => (0, _btravstack_core.ob
|
|
|
103
130
|
const instrument = (backend, observers) => {
|
|
104
131
|
const observe = through(observers);
|
|
105
132
|
return {
|
|
106
|
-
put: (key, bytes, options) =>
|
|
107
|
-
|
|
133
|
+
put: (key, bytes, options) => {
|
|
134
|
+
const snapshot = {
|
|
135
|
+
...options,
|
|
136
|
+
metadata: { ...options.metadata }
|
|
137
|
+
};
|
|
138
|
+
return observe("put", key, () => backend.put(key, bytes, snapshot));
|
|
139
|
+
},
|
|
140
|
+
putIfAbsent: (key, bytes, options) => {
|
|
141
|
+
const snapshot = {
|
|
142
|
+
...options,
|
|
143
|
+
metadata: { ...options.metadata }
|
|
144
|
+
};
|
|
145
|
+
return observe("put_if_absent", key, () => backend.putIfAbsent(key, bytes, snapshot));
|
|
146
|
+
},
|
|
108
147
|
get: (key) => observe("get", key, () => backend.get(key)),
|
|
148
|
+
copy: (from, to, options) => {
|
|
149
|
+
const snapshot = {
|
|
150
|
+
...options,
|
|
151
|
+
metadata: { ...options.metadata }
|
|
152
|
+
};
|
|
153
|
+
return observe("copy", from, () => backend.copy(from, to, snapshot));
|
|
154
|
+
},
|
|
155
|
+
head: (key) => observe("head", key, () => backend.head(key)),
|
|
109
156
|
delete: (key) => observe("delete", key, () => backend.delete(key)),
|
|
110
157
|
presignedUrl: (key, options) => observe("presigned_url", key, () => backend.presignedUrl(key, options)),
|
|
111
158
|
presignedUpload: (key, options) => observe("presigned_upload", key, () => backend.presignedUpload(key, options))
|
|
@@ -131,7 +178,7 @@ const instrument = (backend, observers) => {
|
|
|
131
178
|
*/
|
|
132
179
|
const storage = ({ adapter }) => {
|
|
133
180
|
const healthCheck = _btravstack_di.Provider.member(_btravstack_core.HealthChecks)({
|
|
134
|
-
inject: { backend:
|
|
181
|
+
inject: { backend: require_options.StorageBackend },
|
|
135
182
|
sync: ({ backend }) => ({
|
|
136
183
|
name: "storage",
|
|
137
184
|
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})` }))))
|
|
@@ -141,25 +188,27 @@ const storage = ({ adapter }) => {
|
|
|
141
188
|
imports: [adapter],
|
|
142
189
|
provides: [
|
|
143
190
|
_btravstack_core.noObserverMember,
|
|
144
|
-
(0, _btravstack_di.Provider)(
|
|
191
|
+
(0, _btravstack_di.Provider)(require_options.Storage)({
|
|
145
192
|
inject: {
|
|
146
|
-
backend:
|
|
193
|
+
backend: require_options.StorageBackend,
|
|
147
194
|
observers: _btravstack_core.Observers
|
|
148
195
|
},
|
|
149
196
|
sync: ({ backend, observers }) => instrument(backend, observers)
|
|
150
197
|
}),
|
|
151
198
|
healthCheck
|
|
152
199
|
],
|
|
153
|
-
exports: [
|
|
200
|
+
exports: [require_options.Storage, _btravstack_core.HealthChecks]
|
|
154
201
|
});
|
|
155
202
|
};
|
|
156
203
|
//#endregion
|
|
157
|
-
exports.
|
|
158
|
-
exports.
|
|
159
|
-
exports.
|
|
160
|
-
exports.
|
|
161
|
-
exports.
|
|
162
|
-
exports.
|
|
204
|
+
exports.ChecksumMismatch = require_options.ChecksumMismatch;
|
|
205
|
+
exports.InvalidStorageOptions = require_options.InvalidStorageOptions;
|
|
206
|
+
exports.ObjectExists = require_options.ObjectExists;
|
|
207
|
+
exports.ObjectNotFound = require_options.ObjectNotFound;
|
|
208
|
+
exports.PresignNotSupported = require_options.PresignNotSupported;
|
|
209
|
+
exports.Storage = require_options.Storage;
|
|
210
|
+
exports.StorageBackend = require_options.StorageBackend;
|
|
211
|
+
exports.StorageUnavailable = require_options.StorageUnavailable;
|
|
163
212
|
exports.memoryStorage = memoryStorage;
|
|
164
213
|
exports.memoryStorageBackend = memoryStorageBackend;
|
|
165
214
|
exports.memoryStorageProvider = memoryStorageProvider;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as ObjectHead, c as Storage, d as StorageUnavailable, f as StoredObject, i as ObjectExists, l as StorageBackend, n as CopyOptions, o as ObjectNotFound, p as WriteOptions, r as InvalidStorageOptions, s as PresignNotSupported, t as ChecksumMismatch, u as StorageService } from "./storage-CfN3KqWm.cjs";
|
|
2
2
|
import { Module, Provider } from "@btravstack/di";
|
|
3
3
|
import { HealthChecks } from "@btravstack/core";
|
|
4
4
|
//#region src/memory.d.ts
|
|
@@ -9,6 +9,8 @@ import { HealthChecks } from "@btravstack/core";
|
|
|
9
9
|
* which would be the worst kind of double — one that passes locally and fails in
|
|
10
10
|
* the deployment for a reason no test could have shown.
|
|
11
11
|
*
|
|
12
|
+
* `kmsKeyId` is accepted and ignored: nothing is encrypted at rest here.
|
|
13
|
+
*
|
|
12
14
|
* ponytail: no size limit, so a process storing unbounded objects grows
|
|
13
15
|
* unbounded. The upgrade path is the S3 adapter.
|
|
14
16
|
*/
|
|
@@ -47,4 +49,4 @@ type StorageOptions<E, N> = {
|
|
|
47
49
|
*/
|
|
48
50
|
export declare const storage: <E, N>({ adapter }: StorageOptions<E, N>) => Module<Storage | HealthChecks, E, N>;
|
|
49
51
|
//#endregion
|
|
50
|
-
export { ObjectExists, ObjectNotFound, PresignNotSupported, Storage, StorageBackend, type StorageOptions, type StorageService, StorageUnavailable, type StoredObject };
|
|
52
|
+
export { ChecksumMismatch, type CopyOptions, InvalidStorageOptions, ObjectExists, type ObjectHead, ObjectNotFound, PresignNotSupported, Storage, StorageBackend, type StorageOptions, type StorageService, StorageUnavailable, type StoredObject, type WriteOptions };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as ObjectHead, c as Storage, d as StorageUnavailable, f as StoredObject, i as ObjectExists, l as StorageBackend, n as CopyOptions, o as ObjectNotFound, p as WriteOptions, r as InvalidStorageOptions, s as PresignNotSupported, t as ChecksumMismatch, u as StorageService } from "./storage-CfN3KqWm.mjs";
|
|
2
2
|
import { Module, Provider } from "@btravstack/di";
|
|
3
3
|
import { HealthChecks } from "@btravstack/core";
|
|
4
4
|
//#region src/memory.d.ts
|
|
@@ -9,6 +9,8 @@ import { HealthChecks } from "@btravstack/core";
|
|
|
9
9
|
* which would be the worst kind of double — one that passes locally and fails in
|
|
10
10
|
* the deployment for a reason no test could have shown.
|
|
11
11
|
*
|
|
12
|
+
* `kmsKeyId` is accepted and ignored: nothing is encrypted at rest here.
|
|
13
|
+
*
|
|
12
14
|
* ponytail: no size limit, so a process storing unbounded objects grows
|
|
13
15
|
* unbounded. The upgrade path is the S3 adapter.
|
|
14
16
|
*/
|
|
@@ -47,4 +49,4 @@ type StorageOptions<E, N> = {
|
|
|
47
49
|
*/
|
|
48
50
|
export declare const storage: <E, N>({ adapter }: StorageOptions<E, N>) => Module<Storage | HealthChecks, E, N>;
|
|
49
51
|
//#endregion
|
|
50
|
-
export { ObjectExists, ObjectNotFound, PresignNotSupported, Storage, StorageBackend, type StorageOptions, type StorageService, StorageUnavailable, type StoredObject };
|
|
52
|
+
export { ChecksumMismatch, type CopyOptions, InvalidStorageOptions, ObjectExists, type ObjectHead, ObjectNotFound, PresignNotSupported, Storage, StorageBackend, type StorageOptions, type StorageService, StorageUnavailable, type StoredObject, type WriteOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as StorageBackend, i as
|
|
1
|
+
import { a as ObjectNotFound, c as StorageBackend, i as ObjectExists, l as StorageUnavailable, n as ChecksumMismatch, o as PresignNotSupported, r as InvalidStorageOptions, s as Storage, t as validateOptions } from "./options-DYPXkWb9.mjs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { Module, Provider } from "@btravstack/di";
|
|
3
4
|
import { Err, ErrAsync, Ok, OkAsync, P } from "unthrown";
|
|
4
5
|
import { HealthCheckFailed, HealthChecks, Observers, noObserverMember, observed } from "@btravstack/core";
|
|
@@ -10,30 +11,56 @@ import { HealthCheckFailed, HealthChecks, Observers, noObserverMember, observed
|
|
|
10
11
|
* which would be the worst kind of double — one that passes locally and fails in
|
|
11
12
|
* the deployment for a reason no test could have shown.
|
|
12
13
|
*
|
|
14
|
+
* `kmsKeyId` is accepted and ignored: nothing is encrypted at rest here.
|
|
15
|
+
*
|
|
13
16
|
* ponytail: no size limit, so a process storing unbounded objects grows
|
|
14
17
|
* unbounded. The upgrade path is the S3 adapter.
|
|
15
18
|
*/
|
|
16
19
|
const memoryStorageBackend = () => {
|
|
17
20
|
const objects = /* @__PURE__ */ new Map();
|
|
21
|
+
const verify = (key, bytes, options) => validateOptions(key, options).flatMap(() => options.sha256 !== void 0 && createHash("sha256").update(bytes).digest("hex") !== options.sha256 ? Err(new ChecksumMismatch({ key })) : Ok());
|
|
18
22
|
return {
|
|
19
|
-
put: (key, bytes, options) => {
|
|
23
|
+
put: (key, bytes, options) => verify(key, bytes, options).map(() => {
|
|
20
24
|
objects.set(key, {
|
|
21
|
-
bytes,
|
|
22
|
-
contentType: options.contentType
|
|
25
|
+
bytes: Uint8Array.from(bytes),
|
|
26
|
+
contentType: options.contentType,
|
|
27
|
+
metadata: { ...options.metadata }
|
|
23
28
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (objects.has(key)) return ErrAsync(new ObjectExists({ key }));
|
|
29
|
+
}).toAsync(),
|
|
30
|
+
putIfAbsent: (key, bytes, options) => verify(key, bytes, options).flatMap(() => {
|
|
31
|
+
if (objects.has(key)) return Err(new ObjectExists({ key }));
|
|
28
32
|
objects.set(key, {
|
|
29
|
-
bytes,
|
|
30
|
-
contentType: options.contentType
|
|
33
|
+
bytes: Uint8Array.from(bytes),
|
|
34
|
+
contentType: options.contentType,
|
|
35
|
+
metadata: { ...options.metadata }
|
|
36
|
+
});
|
|
37
|
+
return Ok();
|
|
38
|
+
}).toAsync(),
|
|
39
|
+
copy: (from, to, options) => validateOptions(to, options).flatMap(() => {
|
|
40
|
+
const object = objects.get(from);
|
|
41
|
+
if (object === void 0) return Err(new ObjectNotFound({ key: from }));
|
|
42
|
+
objects.set(to, {
|
|
43
|
+
bytes: object.bytes.slice(),
|
|
44
|
+
contentType: options.contentType,
|
|
45
|
+
metadata: { ...options.metadata }
|
|
46
|
+
});
|
|
47
|
+
return Ok();
|
|
48
|
+
}).toAsync(),
|
|
49
|
+
head: (key) => {
|
|
50
|
+
const object = objects.get(key);
|
|
51
|
+
return object === void 0 ? ErrAsync(new ObjectNotFound({ key })) : OkAsync({
|
|
52
|
+
metadata: { ...object.metadata },
|
|
53
|
+
size: object.bytes.byteLength,
|
|
54
|
+
contentType: object.contentType,
|
|
55
|
+
sha256: createHash("sha256").update(object.bytes).digest("hex")
|
|
31
56
|
});
|
|
32
|
-
return OkAsync();
|
|
33
57
|
},
|
|
34
58
|
get: (key) => {
|
|
35
59
|
const object = objects.get(key);
|
|
36
|
-
return object === void 0 ? ErrAsync(new ObjectNotFound({ key })) : OkAsync(
|
|
60
|
+
return object === void 0 ? ErrAsync(new ObjectNotFound({ key })) : OkAsync({
|
|
61
|
+
contentType: object.contentType,
|
|
62
|
+
bytes: object.bytes.slice()
|
|
63
|
+
});
|
|
37
64
|
},
|
|
38
65
|
delete: (key) => {
|
|
39
66
|
objects.delete(key);
|
|
@@ -59,8 +86,8 @@ const memoryStorage = () => Module("MemoryStorage")({
|
|
|
59
86
|
* **A missing object settles `ok` with `result: "not_found"`, not `error`.**
|
|
60
87
|
* Asking for something that is not there is an ordinary answer, and a dashboard
|
|
61
88
|
* that treats it as a fault teaches its readers to ignore the fault line.
|
|
62
|
-
* `StorageUnavailable` is what pages somebody
|
|
63
|
-
*
|
|
89
|
+
* `StorageUnavailable` is what pages somebody; invalid options and checksum
|
|
90
|
+
* mismatches also settle as errors. The two ordinary answers keep their own words in
|
|
64
91
|
* `reason`, so an operator is not left hunting an object sitting where they put
|
|
65
92
|
* it.
|
|
66
93
|
*/
|
|
@@ -102,9 +129,29 @@ const through = (observers) => (operation, key, call) => observed(observers, {
|
|
|
102
129
|
const instrument = (backend, observers) => {
|
|
103
130
|
const observe = through(observers);
|
|
104
131
|
return {
|
|
105
|
-
put: (key, bytes, options) =>
|
|
106
|
-
|
|
132
|
+
put: (key, bytes, options) => {
|
|
133
|
+
const snapshot = {
|
|
134
|
+
...options,
|
|
135
|
+
metadata: { ...options.metadata }
|
|
136
|
+
};
|
|
137
|
+
return observe("put", key, () => backend.put(key, bytes, snapshot));
|
|
138
|
+
},
|
|
139
|
+
putIfAbsent: (key, bytes, options) => {
|
|
140
|
+
const snapshot = {
|
|
141
|
+
...options,
|
|
142
|
+
metadata: { ...options.metadata }
|
|
143
|
+
};
|
|
144
|
+
return observe("put_if_absent", key, () => backend.putIfAbsent(key, bytes, snapshot));
|
|
145
|
+
},
|
|
107
146
|
get: (key) => observe("get", key, () => backend.get(key)),
|
|
147
|
+
copy: (from, to, options) => {
|
|
148
|
+
const snapshot = {
|
|
149
|
+
...options,
|
|
150
|
+
metadata: { ...options.metadata }
|
|
151
|
+
};
|
|
152
|
+
return observe("copy", from, () => backend.copy(from, to, snapshot));
|
|
153
|
+
},
|
|
154
|
+
head: (key) => observe("head", key, () => backend.head(key)),
|
|
108
155
|
delete: (key) => observe("delete", key, () => backend.delete(key)),
|
|
109
156
|
presignedUrl: (key, options) => observe("presigned_url", key, () => backend.presignedUrl(key, options)),
|
|
110
157
|
presignedUpload: (key, options) => observe("presigned_upload", key, () => backend.presignedUpload(key, options))
|
|
@@ -153,4 +200,4 @@ const storage = ({ adapter }) => {
|
|
|
153
200
|
});
|
|
154
201
|
};
|
|
155
202
|
//#endregion
|
|
156
|
-
export { ObjectExists, ObjectNotFound, PresignNotSupported, Storage, StorageBackend, StorageUnavailable, memoryStorage, memoryStorageBackend, memoryStorageProvider, storage };
|
|
203
|
+
export { ChecksumMismatch, InvalidStorageOptions, ObjectExists, ObjectNotFound, PresignNotSupported, Storage, StorageBackend, StorageUnavailable, memoryStorage, memoryStorageBackend, memoryStorageProvider, storage };
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
let _btravstack_di = require("@btravstack/di");
|
|
2
2
|
let unthrown = require("unthrown");
|
|
3
3
|
//#region src/storage.ts
|
|
4
|
-
/** The key is not there. Only `get`
|
|
4
|
+
/** The key is not there. Only `get`, `head` and `copy` (its source) answer it — see `delete` and `presignedUrl` below. */
|
|
5
5
|
var ObjectNotFound = class extends (0, unthrown.TaggedError)("ObjectNotFound") {};
|
|
6
6
|
/** An object already holds the key. Only `putIfAbsent` answers it. */
|
|
7
7
|
var ObjectExists = class extends (0, unthrown.TaggedError)("ObjectExists") {};
|
|
8
|
+
/** Invalid options are refused before any write or signature is attempted. */
|
|
9
|
+
var InvalidStorageOptions = class extends (0, unthrown.TaggedError)("InvalidStorageOptions") {};
|
|
10
|
+
/** The supplied SHA-256 did not match the body; no object was written. */
|
|
11
|
+
var ChecksumMismatch = class extends (0, unthrown.TaggedError)("ChecksumMismatch") {};
|
|
8
12
|
/** The store could not answer — the endpoint is down, the credentials were refused, the bucket is gone. */
|
|
9
13
|
var StorageUnavailable = class extends (0, unthrown.TaggedError)("StorageUnavailable") {};
|
|
10
14
|
/** The adapter cannot mint a time-limited URL, and says so rather than pretending. */
|
|
@@ -24,6 +28,31 @@ var Storage = class extends (0, _btravstack_di.Port)("Storage") {};
|
|
|
24
28
|
/** The port every adapter provides, and the one an application never depends on. */
|
|
25
29
|
var StorageBackend = class extends (0, _btravstack_di.Port)("StorageBackend") {};
|
|
26
30
|
//#endregion
|
|
31
|
+
//#region src/options.ts
|
|
32
|
+
const validateOptions = (key, options) => {
|
|
33
|
+
if (options.sha256 !== void 0 && !/^[a-f0-9]{64}$/.test(options.sha256)) return (0, unthrown.Err)(new InvalidStorageOptions({
|
|
34
|
+
key,
|
|
35
|
+
reason: "invalid_sha256"
|
|
36
|
+
}));
|
|
37
|
+
if (Object.keys(options.metadata ?? {}).some((name) => !/^[!#$%&'*+\-.^_`|~0-9a-z]+$/.test(name))) return (0, unthrown.Err)(new InvalidStorageOptions({
|
|
38
|
+
key,
|
|
39
|
+
reason: "invalid_metadata_key"
|
|
40
|
+
}));
|
|
41
|
+
return (0, unthrown.Ok)();
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
Object.defineProperty(exports, "ChecksumMismatch", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
get: function() {
|
|
47
|
+
return ChecksumMismatch;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(exports, "InvalidStorageOptions", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
get: function() {
|
|
53
|
+
return InvalidStorageOptions;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
27
56
|
Object.defineProperty(exports, "ObjectExists", {
|
|
28
57
|
enumerable: true,
|
|
29
58
|
get: function() {
|
|
@@ -60,3 +89,9 @@ Object.defineProperty(exports, "StorageUnavailable", {
|
|
|
60
89
|
return StorageUnavailable;
|
|
61
90
|
}
|
|
62
91
|
});
|
|
92
|
+
Object.defineProperty(exports, "validateOptions", {
|
|
93
|
+
enumerable: true,
|
|
94
|
+
get: function() {
|
|
95
|
+
return validateOptions;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Port } from "@btravstack/di";
|
|
2
|
-
import { TaggedError } from "unthrown";
|
|
2
|
+
import { Err, Ok, TaggedError } from "unthrown";
|
|
3
3
|
//#region src/storage.ts
|
|
4
|
-
/** The key is not there. Only `get`
|
|
4
|
+
/** The key is not there. Only `get`, `head` and `copy` (its source) answer it — see `delete` and `presignedUrl` below. */
|
|
5
5
|
var ObjectNotFound = class extends TaggedError("ObjectNotFound") {};
|
|
6
6
|
/** An object already holds the key. Only `putIfAbsent` answers it. */
|
|
7
7
|
var ObjectExists = class extends TaggedError("ObjectExists") {};
|
|
8
|
+
/** Invalid options are refused before any write or signature is attempted. */
|
|
9
|
+
var InvalidStorageOptions = class extends TaggedError("InvalidStorageOptions") {};
|
|
10
|
+
/** The supplied SHA-256 did not match the body; no object was written. */
|
|
11
|
+
var ChecksumMismatch = class extends TaggedError("ChecksumMismatch") {};
|
|
8
12
|
/** The store could not answer — the endpoint is down, the credentials were refused, the bucket is gone. */
|
|
9
13
|
var StorageUnavailable = class extends TaggedError("StorageUnavailable") {};
|
|
10
14
|
/** The adapter cannot mint a time-limited URL, and says so rather than pretending. */
|
|
@@ -24,4 +28,17 @@ var Storage = class extends Port("Storage") {};
|
|
|
24
28
|
/** The port every adapter provides, and the one an application never depends on. */
|
|
25
29
|
var StorageBackend = class extends Port("StorageBackend") {};
|
|
26
30
|
//#endregion
|
|
27
|
-
|
|
31
|
+
//#region src/options.ts
|
|
32
|
+
const validateOptions = (key, options) => {
|
|
33
|
+
if (options.sha256 !== void 0 && !/^[a-f0-9]{64}$/.test(options.sha256)) return Err(new InvalidStorageOptions({
|
|
34
|
+
key,
|
|
35
|
+
reason: "invalid_sha256"
|
|
36
|
+
}));
|
|
37
|
+
if (Object.keys(options.metadata ?? {}).some((name) => !/^[!#$%&'*+\-.^_`|~0-9a-z]+$/.test(name))) return Err(new InvalidStorageOptions({
|
|
38
|
+
key,
|
|
39
|
+
reason: "invalid_metadata_key"
|
|
40
|
+
}));
|
|
41
|
+
return Ok();
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { ObjectNotFound as a, StorageBackend as c, ObjectExists as i, StorageUnavailable as l, ChecksumMismatch as n, PresignNotSupported as o, InvalidStorageOptions as r, Storage as s, validateOptions as t };
|