@ultimat3/storage 31.1.0 → 31.2.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
@@ -58,7 +58,7 @@ const stored = [...sessions.objects().values()].map((bytes) => new TextDecoder()
58
58
 
59
59
  Server-side only, like both other drivers: nothing an island imports reaches it.
60
60
 
61
- Every call on a disk `defineStorage` resolves is a `storage.<verb>` event on `@ultimat3/core`'s instrumentation bus (`put`, `get`, `stat`, `copy`, `delete`, `list`; `{ disk, key, bytes?, durationMs, error? }`) inside a `storage.<verb>` client span. `disk()` hands out that instrumented view, so it is not `===` the driver you declared — compare `.name`. A driver you hold directly reports nothing.
61
+ Every call on a disk `defineStorage` resolves is a `storage.<verb>` event on `@ultimat3/core`'s instrumentation bus (`put`, `get`, `stat`, `copy`, `delete`, `list`; `{ disk, key, bytes?, durationMs, error? }`) inside a `storage.<verb>` client span. `disk()` hands out that instrumented view, so it is not `===` the driver you declared — compare `.name`. A driver you hold directly reports nothing. A method patched in a test — assigned on the disk, or `spyOn(driver, 'put')` on the driver you declared — runs on the next call and is still reported. `spyOn(disk, …)` does nothing: Bun's `spyOn` never writes through a Proxy (as of bun 1.4.4), so spy on the driver.
62
62
 
63
63
  `region` is what requests are signed for; unset, that is `auto`. An endpoint that expects another
64
64
  region refuses the first write, list or delete with `X_CONFIG_INVALID`, and the fix names the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/storage",
3
- "version": "31.1.0",
3
+ "version": "31.2.0",
4
4
  "description": "Named disks over Bun.file and Bun.s3: safe keys, signed URLs, sniffed uploads",
5
5
  "keywords": [
6
6
  "ultimate",
@@ -43,6 +43,6 @@
43
43
  "test": "bun test"
44
44
  },
45
45
  "dependencies": {
46
- "@ultimat3/core": "31.1.0"
46
+ "@ultimat3/core": "31.2.0"
47
47
  }
48
48
  }
@@ -51,15 +51,38 @@ function report(name: `storage.${StorageVerb}`, event: StorageEvent): void {
51
51
  * The driver, every contract method wrapped and every other member (a test disk's `objects()`,
52
52
  * `multipart`, `registerAs`) passed through bound to the driver. The driver's calls to its OWN
53
53
  * methods stay unwrapped: one caller's call is one event.
54
+ *
55
+ * A wrapper is reused only while the driver still holds the method it wraps. A cache keyed on the
56
+ * name alone kept answering the original after a test patched `put` to simulate an outage; keyed
57
+ * on the method, a patch — written through the disk or onto the declared driver — runs on the next
58
+ * call, still reported. Writing back a wrapper this disk handed out writes the method it wraps, so
59
+ * `const put = disk.put; …; disk.put = put` restores one event per call, never two.
54
60
  */
55
61
  export function instrumentedDisk(disk: string, driver: StorageDriver): StorageDriver {
56
- const wrapped = new Map<PropertyKey, unknown>();
57
- return new Proxy(driver, {
62
+ const wrapped = new Map<PropertyKey, { readonly method: unknown; readonly bound: unknown }>();
63
+ /** Each wrapper handed out, to the method it wraps: what a restore writes back. */
64
+ const unwrapped = new WeakMap<object, unknown>();
65
+ const raw = (value: unknown): unknown =>
66
+ typeof value === 'function' ? (unwrapped.get(value) ?? value) : value;
67
+ const view: StorageDriver = new Proxy(driver, {
68
+ // Only a write ON the disk patches the driver. An object inheriting from the disk
69
+ // (`Object.create(disk)`) reaches this trap as `receiver` and keeps the member as its own;
70
+ // written onto the driver, a child's `stat` calling `disk.stat` called itself.
71
+ set: (target, property, value, receiver) =>
72
+ receiver === view
73
+ ? Reflect.set(target, property, raw(value), target)
74
+ : Reflect.set(target, property, value, receiver),
75
+ defineProperty: (target, property, descriptor) =>
76
+ Reflect.defineProperty(
77
+ target,
78
+ property,
79
+ 'value' in descriptor ? { ...descriptor, value: raw(descriptor.value) } : descriptor,
80
+ ),
58
81
  get(target, property) {
59
- const known = wrapped.get(property);
60
- if (known !== undefined) return known;
61
82
  const value: unknown = Reflect.get(target, property, target);
62
83
  if (typeof value !== 'function') return value;
84
+ const known = wrapped.get(property);
85
+ if (known?.method === value) return known.bound;
63
86
  // A Proxy `get` is handed every name, `constructor` included: an own key or no verb.
64
87
  const verb =
65
88
  typeof property === 'string' && Object.hasOwn(VERB_OF, property)
@@ -73,10 +96,12 @@ export function instrumentedDisk(disk: string, driver: StorageDriver): StorageDr
73
96
  call(disk, verb, keyOf(property as string, args), driver.name, () =>
74
97
  Promise.resolve(method.apply(target, args)),
75
98
  );
76
- wrapped.set(property, bound);
99
+ wrapped.set(property, { method: value, bound });
100
+ unwrapped.set(bound, value);
77
101
  return bound;
78
102
  },
79
103
  });
104
+ return view;
80
105
  }
81
106
 
82
107
  async function call(