@btravstack/storage 0.8.0 → 0.10.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 CHANGED
@@ -57,35 +57,33 @@ class Invoices extends InvoicePort("ReadmeInvoices")<{
57
57
  const keyFor = (tenantId: string, id: string) =>
58
58
  `invoices/${tenantId}/${id}.pdf`;
59
59
 
60
- export const invoices = InvoiceProvider(Invoices)(
61
- { storage: Storage },
62
- {
63
- sync: ({ storage }) => ({
64
- store: (tenantId, id, pdf) =>
65
- storage
66
- .put(keyFor(tenantId, id), pdf, { contentType: "application/pdf" })
67
- // A document that failed to store is YOUR decision to make; here it
68
- // is swallowed because the instrumented store already logged and
69
- // counted it one layer down.
70
- .recoverErrCases((matcher) =>
71
- matcher.with(P.tag("StorageUnavailable"), () => undefined),
72
- ),
73
- // Serving bytes through your own process is the anti-pattern this arm
74
- // exists to avoid: the caller fetches the object directly, for a minute.
75
- link: (tenantId, id) =>
76
- storage
77
- .presignedUrl(keyFor(tenantId, id), { ttlMs: 60_000 })
78
- .recoverErrCases((matcher) =>
79
- matcher
80
- .with(P.tag("StorageUnavailable"), () => undefined)
81
- .with(P.tag("PresignNotSupported"), () => undefined),
82
- ),
83
- }),
84
- },
85
- );
60
+ export const invoices = InvoiceProvider(Invoices)({
61
+ inject: { storage: Storage },
62
+ sync: ({ storage }) => ({
63
+ store: (tenantId, id, pdf) =>
64
+ storage
65
+ .put(keyFor(tenantId, id), pdf, { contentType: "application/pdf" })
66
+ // A document that failed to store is YOUR decision to make; here it
67
+ // is swallowed because an observer already recorded it and
68
+ // counted it one layer down.
69
+ .recoverErrCases((matcher) =>
70
+ matcher.with(P.tag("StorageUnavailable"), () => undefined),
71
+ ),
72
+ // Serving bytes through your own process is the anti-pattern this arm
73
+ // exists to avoid: the caller fetches the object directly, for a minute.
74
+ link: (tenantId, id) =>
75
+ storage
76
+ .presignedUrl(keyFor(tenantId, id), { ttlMs: 60_000 })
77
+ .recoverErrCases((matcher) =>
78
+ matcher
79
+ .with(P.tag("StorageUnavailable"), () => undefined)
80
+ .with(P.tag("PresignNotSupported"), () => undefined),
81
+ ),
82
+ }),
83
+ });
86
84
  ```
87
85
 
88
- The composition — the adapter, and whether operations are instrumented,
86
+ The composition — the adapter is the only decision,
89
87
  decided here and nowhere else:
90
88
 
91
89
  ```ts
@@ -95,7 +93,8 @@ import { s3Storage } from "@btravstack/storage/s3";
95
93
  export const InvoicesApp = Module("InvoicesApp")({
96
94
  imports: [
97
95
  // Instrumented by default, which is why `observability()` and `otel()`
98
- // are below. Pass `instrumented: false` for the same graph without them.
96
+ // Every call is reported through `Observers`. Drop the two modules
97
+ // below and this composition is unchanged — it just reports to nobody.
99
98
  storage({ adapter: s3Storage() }),
100
99
  observability(),
101
100
  otel(),
@@ -107,14 +106,13 @@ export const InvoicesApp = Module("InvoicesApp")({
107
106
 
108
107
  ## Options
109
108
 
110
- | Option | Where | What it is |
111
- | ----------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------- |
112
- | `adapter` | `storage({ adapter })` | the adapter module providing `StorageBackend` — required |
113
- | `instrumented` | `storage({ instrumented })` | span, count and log every operation (default `true`; `false` opts out) |
114
- | `STORAGE_S3_ENDPOINT` | environment, read by `s3Storage()` | the store's endpoint — required |
115
- | `STORAGE_S3_BUCKET` | environment | the bucket — required |
116
- | `STORAGE_S3_ACCESS_KEY_ID` / `STORAGE_S3_SECRET_ACCESS_KEY` | environment | the credentials — required |
117
- | `STORAGE_S3_REGION` | environment | default `us-east-1` |
109
+ | Option | Where | What it is |
110
+ | ----------------------------------------------------------- | ---------------------------------- | -------------------------------------------------------- |
111
+ | `adapter` | `storage({ adapter })` | the adapter module providing `StorageBackend` — required |
112
+ | `STORAGE_S3_ENDPOINT` | environment, read by `s3Storage()` | the store's endpoint — required |
113
+ | `STORAGE_S3_BUCKET` | environment | the bucket — required |
114
+ | `STORAGE_S3_ACCESS_KEY_ID` / `STORAGE_S3_SECRET_ACCESS_KEY` | environment | the credentials — required |
115
+ | `STORAGE_S3_REGION` | environment | default `us-east-1` |
118
116
 
119
117
  The full table — defaults, semantics and the reasoning — lives on
120
118
  [the reference page](https://btravstack.github.io/btravstack/reference/storage),
package/dist/index.cjs CHANGED
@@ -37,7 +37,10 @@ const memoryStorageBackend = () => {
37
37
  };
38
38
  };
39
39
  /** The adapter as a provider, which is the shape `@btravstack/testing`'s `overridden` takes. */
40
- const memoryStorageProvider = () => (0, _btravstack_di.Provider)(require_storage.StorageBackend)({}, { sync: () => memoryStorageBackend() });
40
+ const memoryStorageProvider = () => (0, _btravstack_di.Provider)(require_storage.StorageBackend)({
41
+ inject: {},
42
+ sync: () => memoryStorageBackend()
43
+ });
41
44
  /** The adapter as a module, which is the shape `storage({ adapter })` takes. */
42
45
  const memoryStorage = () => (0, _btravstack_di.Module)("MemoryStorage")({
43
46
  provides: [memoryStorageProvider()],
@@ -46,60 +49,49 @@ const memoryStorage = () => (0, _btravstack_di.Module)("MemoryStorage")({
46
49
  //#endregion
47
50
  //#region src/instrument.ts
48
51
  /**
49
- * One operation, wrapped: a span around it, a count of how it came out, and a
50
- * log line if it failed.
52
+ * One operation, handed to every observer the graph composed.
51
53
  *
52
- * **A missing object is counted `not_found` and logged at `info`, not
53
- * `error`.** Asking for something that is not there is an ordinary answer, and
54
- * a dashboard that treats it as a fault teaches its readers to ignore the fault
55
- * line. `StorageUnavailable` is what pages somebody.
54
+ * **A missing object settles `ok` with `result: "not_found"`, not `error`.**
55
+ * Asking for something that is not there is an ordinary answer, and a dashboard
56
+ * that treats it as a fault teaches its readers to ignore the fault line.
57
+ * `StorageUnavailable` is what pages somebody, and it is the only one that
58
+ * settles as an error. The two ordinary answers keep their own words in
59
+ * `reason`, so an operator is not left hunting an object sitting where they put
60
+ * it.
56
61
  */
57
- const observed = (call, { operation, key, logger, tracer, operations }) => {
58
- const span = tracer.startSpan(`storage.${operation}`);
59
- span.setAttributes({ "btravstack.storage.key": key });
60
- return call().tap(() => {
61
- operations.add(1, {
62
- operation,
63
- outcome: "ok"
64
- });
65
- span.end();
66
- }).tapFailure((failure) => {
62
+ const observed = (call, observers, operation, key) => {
63
+ const settle = (0, _btravstack_core.observe)(observers, {
64
+ component: "storage",
65
+ name: operation,
66
+ attributes: { operation },
67
+ details: { "btravstack.storage.key": key }
68
+ });
69
+ return call().tap(() => settle({
70
+ outcome: "ok",
71
+ attributes: { result: "ok" }
72
+ })).tapFailure((failure) => {
67
73
  const cause = failure.tag === "Err" ? failure.error : failure.cause;
68
- const message = failure.tag !== "Err" ? void 0 : failure.error._tag === "ObjectNotFound" ? "the object was not there" : failure.error._tag === "PresignNotSupported" ? "this store cannot mint a url" : void 0;
69
- operations.add(1, {
70
- operation,
71
- outcome: message === void 0 ? "error" : "not_found"
72
- });
73
- if (message === void 0) {
74
- logger.error("the store could not answer", {
75
- operation,
76
- key
77
- }, cause);
78
- span.setStatus({ code: _btravstack_core.SPAN_STATUS.error });
79
- } else logger.info(message, {
80
- operation,
81
- key
74
+ const ordinary = failure.tag !== "Err" ? void 0 : failure.error._tag === "ObjectNotFound" ? "the object was not there" : failure.error._tag === "PresignNotSupported" ? "this store cannot mint a url" : void 0;
75
+ settle(ordinary === void 0 ? {
76
+ outcome: "error",
77
+ attributes: { result: "error" },
78
+ cause
79
+ } : {
80
+ outcome: "ok",
81
+ attributes: {
82
+ result: "not_found",
83
+ reason: ordinary
84
+ }
82
85
  });
83
- span.end();
84
86
  });
85
87
  };
86
- const instrument = (backend, logger, tracer, meter) => {
87
- const operations = meter.createCounter("btravstack.storage.operations", { description: "Storage operations, by operation and outcome" });
88
- const context = (operation, key) => ({
89
- operation,
90
- key,
91
- logger,
92
- tracer,
93
- operations
94
- });
95
- return {
96
- put: (key, bytes, options) => observed(() => backend.put(key, bytes, options), context("put", key)),
97
- get: (key) => observed(() => backend.get(key), context("get", key)),
98
- delete: (key) => observed(() => backend.delete(key), context("delete", 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))
101
- };
102
- };
88
+ const instrument = (backend, observers) => ({
89
+ put: (key, bytes, options) => observed(() => backend.put(key, bytes, options), observers, "put", key),
90
+ get: (key) => observed(() => backend.get(key), observers, "get", key),
91
+ delete: (key) => observed(() => backend.delete(key), observers, "delete", key),
92
+ presignedUrl: (key, options) => observed(() => backend.presignedUrl(key, options), observers, "presigned_url", key),
93
+ presignedUpload: (key, options) => observed(() => backend.presignedUpload(key, options), observers, "presigned_upload", key)
94
+ });
103
95
  //#endregion
104
96
  //#region src/module.ts
105
97
  /**
@@ -115,28 +107,30 @@ const instrument = (backend, logger, tracer, meter) => {
115
107
  * application depends on must not be the one an adapter provides, which is also
116
108
  * why instrumentation is a flag on the composition rather than a wrapper.
117
109
  */
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")({
110
+ const storage = ({ adapter }) => {
111
+ const healthCheck = _btravstack_di.Provider.member(_btravstack_core.HealthChecks)({
112
+ inject: { backend: require_storage.StorageBackend },
113
+ sync: ({ backend }) => ({
114
+ name: "storage",
115
+ 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})` }))))
116
+ })
117
+ });
118
+ return (0, _btravstack_di.Module)("Storage")({
138
119
  imports: [adapter],
139
- provides: [(0, _btravstack_di.Provider)(require_storage.Storage)({ backend: require_storage.StorageBackend }, { sync: ({ backend }) => backend }), healthCheck],
120
+ provides: [
121
+ _btravstack_di.Provider.member(_btravstack_core.Observers)({
122
+ inject: {},
123
+ value: _btravstack_core.noObserver
124
+ }),
125
+ (0, _btravstack_di.Provider)(require_storage.Storage)({
126
+ inject: {
127
+ backend: require_storage.StorageBackend,
128
+ observers: _btravstack_core.Observers
129
+ },
130
+ sync: ({ backend, observers }) => instrument(backend, observers)
131
+ }),
132
+ healthCheck
133
+ ],
140
134
  exports: [require_storage.Storage, _btravstack_core.HealthChecks]
141
135
  });
142
136
  };
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
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 { HealthChecks, Logger, Meter, Tracer } from "@btravstack/core";
3
+ import { HealthChecks } 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.
@@ -21,22 +21,13 @@ declare const memoryStorageProvider: () => Provider<StorageBackend, never, never
21
21
  declare const memoryStorage: () => Module<StorageBackend, never, never>;
22
22
  //#endregion
23
23
  //#region src/module.d.ts
24
- type StorageOptions<E, N, Instrumented extends boolean> = {
24
+ type StorageOptions<E, N> = {
25
25
  /**
26
- * The adapter module: `memoryStorage()` from this entry point,
27
- * `s3Storage()` from `@btravstack/storage/s3`, or one an application
28
- * wrote itself over `StorageBackend`.
26
+ * The adapter module: `memoryStorage()` from this entry point, `s3Storage()`
27
+ * from `@btravstack/storage/s3`, or one an application wrote itself over
28
+ * `StorageBackend`.
29
29
  */
30
30
  readonly adapter: Module<StorageBackend, E, N>;
31
- /**
32
- * Span, count and log every operation. **Default `true`**, `false` opts out.
33
- *
34
- * On by default because telemetry that is missing is discovered during an
35
- * incident. The cost is stated rather than hidden: instrumenting puts
36
- * `Logger`, `Meter` and `Tracer` in this module's `Needs`, so a root without
37
- * `observability()` and `otel()` gets a compile error naming all three.
38
- */
39
- readonly instrumented?: Instrumented;
40
31
  };
41
32
  /**
42
33
  * The storage starter: an adapter, and `Storage` provided from it —
@@ -51,6 +42,6 @@ type StorageOptions<E, N, Instrumented extends boolean> = {
51
42
  * application depends on must not be the one an adapter provides, which is also
52
43
  * why instrumentation is a flag on the composition rather than a wrapper.
53
44
  */
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>;
45
+ declare const storage: <E, N>({ adapter }: StorageOptions<E, N>) => Module<Storage | HealthChecks, E, N>;
55
46
  //#endregion
56
47
  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
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 { HealthChecks, Logger, Meter, Tracer } from "@btravstack/core";
3
+ import { HealthChecks } 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.
@@ -21,22 +21,13 @@ declare const memoryStorageProvider: () => Provider<StorageBackend, never, never
21
21
  declare const memoryStorage: () => Module<StorageBackend, never, never>;
22
22
  //#endregion
23
23
  //#region src/module.d.ts
24
- type StorageOptions<E, N, Instrumented extends boolean> = {
24
+ type StorageOptions<E, N> = {
25
25
  /**
26
- * The adapter module: `memoryStorage()` from this entry point,
27
- * `s3Storage()` from `@btravstack/storage/s3`, or one an application
28
- * wrote itself over `StorageBackend`.
26
+ * The adapter module: `memoryStorage()` from this entry point, `s3Storage()`
27
+ * from `@btravstack/storage/s3`, or one an application wrote itself over
28
+ * `StorageBackend`.
29
29
  */
30
30
  readonly adapter: Module<StorageBackend, E, N>;
31
- /**
32
- * Span, count and log every operation. **Default `true`**, `false` opts out.
33
- *
34
- * On by default because telemetry that is missing is discovered during an
35
- * incident. The cost is stated rather than hidden: instrumenting puts
36
- * `Logger`, `Meter` and `Tracer` in this module's `Needs`, so a root without
37
- * `observability()` and `otel()` gets a compile error naming all three.
38
- */
39
- readonly instrumented?: Instrumented;
40
31
  };
41
32
  /**
42
33
  * The storage starter: an adapter, and `Storage` provided from it —
@@ -51,6 +42,6 @@ type StorageOptions<E, N, Instrumented extends boolean> = {
51
42
  * application depends on must not be the one an adapter provides, which is also
52
43
  * why instrumentation is a flag on the composition rather than a wrapper.
53
44
  */
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>;
45
+ declare const storage: <E, N>({ adapter }: StorageOptions<E, N>) => Module<Storage | HealthChecks, E, N>;
55
46
  //#endregion
56
47
  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
3
  import { Err, ErrAsync, Ok, OkAsync, P } from "unthrown";
4
- import { HealthCheckFailed, HealthChecks, Logger, Meter, SPAN_STATUS, Tracer } from "@btravstack/core";
4
+ import { HealthCheckFailed, HealthChecks, Observers, noObserver, observe } 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.
@@ -36,7 +36,10 @@ const memoryStorageBackend = () => {
36
36
  };
37
37
  };
38
38
  /** The adapter as a provider, which is the shape `@btravstack/testing`'s `overridden` takes. */
39
- const memoryStorageProvider = () => Provider(StorageBackend)({}, { sync: () => memoryStorageBackend() });
39
+ const memoryStorageProvider = () => Provider(StorageBackend)({
40
+ inject: {},
41
+ sync: () => memoryStorageBackend()
42
+ });
40
43
  /** The adapter as a module, which is the shape `storage({ adapter })` takes. */
41
44
  const memoryStorage = () => Module("MemoryStorage")({
42
45
  provides: [memoryStorageProvider()],
@@ -45,60 +48,49 @@ const memoryStorage = () => Module("MemoryStorage")({
45
48
  //#endregion
46
49
  //#region src/instrument.ts
47
50
  /**
48
- * One operation, wrapped: a span around it, a count of how it came out, and a
49
- * log line if it failed.
51
+ * One operation, handed to every observer the graph composed.
50
52
  *
51
- * **A missing object is counted `not_found` and logged at `info`, not
52
- * `error`.** Asking for something that is not there is an ordinary answer, and
53
- * a dashboard that treats it as a fault teaches its readers to ignore the fault
54
- * line. `StorageUnavailable` is what pages somebody.
53
+ * **A missing object settles `ok` with `result: "not_found"`, not `error`.**
54
+ * Asking for something that is not there is an ordinary answer, and a dashboard
55
+ * that treats it as a fault teaches its readers to ignore the fault line.
56
+ * `StorageUnavailable` is what pages somebody, and it is the only one that
57
+ * settles as an error. The two ordinary answers keep their own words in
58
+ * `reason`, so an operator is not left hunting an object sitting where they put
59
+ * it.
55
60
  */
56
- const observed = (call, { operation, key, logger, tracer, operations }) => {
57
- const span = tracer.startSpan(`storage.${operation}`);
58
- span.setAttributes({ "btravstack.storage.key": key });
59
- return call().tap(() => {
60
- operations.add(1, {
61
- operation,
62
- outcome: "ok"
63
- });
64
- span.end();
65
- }).tapFailure((failure) => {
61
+ const observed = (call, observers, operation, key) => {
62
+ const settle = observe(observers, {
63
+ component: "storage",
64
+ name: operation,
65
+ attributes: { operation },
66
+ details: { "btravstack.storage.key": key }
67
+ });
68
+ return call().tap(() => settle({
69
+ outcome: "ok",
70
+ attributes: { result: "ok" }
71
+ })).tapFailure((failure) => {
66
72
  const cause = failure.tag === "Err" ? failure.error : failure.cause;
67
- const message = failure.tag !== "Err" ? void 0 : failure.error._tag === "ObjectNotFound" ? "the object was not there" : failure.error._tag === "PresignNotSupported" ? "this store cannot mint a url" : void 0;
68
- operations.add(1, {
69
- operation,
70
- outcome: message === void 0 ? "error" : "not_found"
71
- });
72
- if (message === void 0) {
73
- logger.error("the store could not answer", {
74
- operation,
75
- key
76
- }, cause);
77
- span.setStatus({ code: SPAN_STATUS.error });
78
- } else logger.info(message, {
79
- operation,
80
- key
73
+ const ordinary = failure.tag !== "Err" ? void 0 : failure.error._tag === "ObjectNotFound" ? "the object was not there" : failure.error._tag === "PresignNotSupported" ? "this store cannot mint a url" : void 0;
74
+ settle(ordinary === void 0 ? {
75
+ outcome: "error",
76
+ attributes: { result: "error" },
77
+ cause
78
+ } : {
79
+ outcome: "ok",
80
+ attributes: {
81
+ result: "not_found",
82
+ reason: ordinary
83
+ }
81
84
  });
82
- span.end();
83
85
  });
84
86
  };
85
- const instrument = (backend, logger, tracer, meter) => {
86
- const operations = meter.createCounter("btravstack.storage.operations", { description: "Storage operations, by operation and outcome" });
87
- const context = (operation, key) => ({
88
- operation,
89
- key,
90
- logger,
91
- tracer,
92
- operations
93
- });
94
- return {
95
- put: (key, bytes, options) => observed(() => backend.put(key, bytes, options), context("put", key)),
96
- get: (key) => observed(() => backend.get(key), context("get", key)),
97
- delete: (key) => observed(() => backend.delete(key), context("delete", 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))
100
- };
101
- };
87
+ const instrument = (backend, observers) => ({
88
+ put: (key, bytes, options) => observed(() => backend.put(key, bytes, options), observers, "put", key),
89
+ get: (key) => observed(() => backend.get(key), observers, "get", key),
90
+ delete: (key) => observed(() => backend.delete(key), observers, "delete", key),
91
+ presignedUrl: (key, options) => observed(() => backend.presignedUrl(key, options), observers, "presigned_url", key),
92
+ presignedUpload: (key, options) => observed(() => backend.presignedUpload(key, options), observers, "presigned_upload", key)
93
+ });
102
94
  //#endregion
103
95
  //#region src/module.ts
104
96
  /**
@@ -114,28 +106,30 @@ const instrument = (backend, logger, tracer, meter) => {
114
106
  * application depends on must not be the one an adapter provides, which is also
115
107
  * why instrumentation is a flag on the composition rather than a wrapper.
116
108
  */
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")({
109
+ const storage = ({ adapter }) => {
110
+ const healthCheck = Provider.member(HealthChecks)({
111
+ inject: { backend: StorageBackend },
112
+ sync: ({ backend }) => ({
113
+ name: "storage",
114
+ 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})` }))))
115
+ })
116
+ });
117
+ return Module("Storage")({
137
118
  imports: [adapter],
138
- provides: [Provider(Storage)({ backend: StorageBackend }, { sync: ({ backend }) => backend }), healthCheck],
119
+ provides: [
120
+ Provider.member(Observers)({
121
+ inject: {},
122
+ value: noObserver
123
+ }),
124
+ Provider(Storage)({
125
+ inject: {
126
+ backend: StorageBackend,
127
+ observers: Observers
128
+ },
129
+ sync: ({ backend, observers }) => instrument(backend, observers)
130
+ }),
131
+ healthCheck
132
+ ],
139
133
  exports: [Storage, HealthChecks]
140
134
  });
141
135
  };
package/dist/s3.cjs CHANGED
@@ -81,7 +81,8 @@ const s3Storage = () => (0, _btravstack_di.Module)("S3Storage")({
81
81
  needs: [_btravstack_config.Env],
82
82
  provides: [
83
83
  _btravstack_config.Config.provider(StorageConfig)(s3Schema),
84
- (0, _btravstack_di.Provider)(S3Connection)({ config: StorageConfig }, {
84
+ (0, _btravstack_di.Provider)(S3Connection)({
85
+ inject: { config: StorageConfig },
85
86
  acquire: ({ config }) => (0, unthrown.fromSafePromise)(Promise.resolve({
86
87
  client: new _aws_sdk_client_s3.S3Client({
87
88
  endpoint: config.endpoint,
@@ -98,7 +99,10 @@ const s3Storage = () => (0, _btravstack_di.Module)("S3Storage")({
98
99
  client.destroy();
99
100
  }
100
101
  }),
101
- (0, _btravstack_di.Provider)(require_storage.StorageBackend)({ connection: S3Connection }, { sync: ({ connection }) => s3StorageBackend(connection.client, connection.bucket) })
102
+ (0, _btravstack_di.Provider)(require_storage.StorageBackend)({
103
+ inject: { connection: S3Connection },
104
+ sync: ({ connection }) => s3StorageBackend(connection.client, connection.bucket)
105
+ })
102
106
  ],
103
107
  exports: [require_storage.StorageBackend]
104
108
  });
package/dist/s3.mjs CHANGED
@@ -80,7 +80,8 @@ const s3Storage = () => Module("S3Storage")({
80
80
  needs: [Env],
81
81
  provides: [
82
82
  Config.provider(StorageConfig)(s3Schema),
83
- Provider(S3Connection)({ config: StorageConfig }, {
83
+ Provider(S3Connection)({
84
+ inject: { config: StorageConfig },
84
85
  acquire: ({ config }) => fromSafePromise(Promise.resolve({
85
86
  client: new S3Client({
86
87
  endpoint: config.endpoint,
@@ -97,7 +98,10 @@ const s3Storage = () => Module("S3Storage")({
97
98
  client.destroy();
98
99
  }
99
100
  }),
100
- Provider(StorageBackend)({ connection: S3Connection }, { sync: ({ connection }) => s3StorageBackend(connection.client, connection.bucket) })
101
+ Provider(StorageBackend)({
102
+ inject: { connection: S3Connection },
103
+ sync: ({ connection }) => s3StorageBackend(connection.client, connection.bucket)
104
+ })
101
105
  ],
102
106
  exports: [StorageBackend]
103
107
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/storage",
3
- "version": "0.8.0",
3
+ "version": "0.10.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.8.0",
71
- "@btravstack/core": "0.8.0",
72
- "@btravstack/di": "0.8.0",
73
- "@btravstack/observability": "0.8.0",
74
- "@btravstack/internal-test-infra": "0.0.0"
70
+ "@btravstack/config": "0.10.0",
71
+ "@btravstack/core": "0.10.0",
72
+ "@btravstack/di": "0.10.0",
73
+ "@btravstack/internal-test-infra": "0.0.0",
74
+ "@btravstack/observability": "0.10.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.8.0",
81
- "@btravstack/core": "^0.8.0",
82
- "@btravstack/di": "^0.8.0"
80
+ "@btravstack/config": "^0.10.0",
81
+ "@btravstack/core": "^0.10.0",
82
+ "@btravstack/di": "^0.10.0"
83
83
  },
84
84
  "peerDependenciesMeta": {
85
85
  "@aws-sdk/client-s3": {