@ultimat3/storage 22.0.0 → 22.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
@@ -179,6 +179,13 @@ and hands over, hashed again here and refused on any disagreement.
179
179
  Neither owns a `Request`, a `Response` or a status number — mounting is the host's job, and
180
180
  `@ultimat3/http` is the only layer that turns an `X_*` code into a status.
181
181
 
182
+ **Step 3 ships.** `As of 2026-09-24` `@ultimat3/cli` mounts `PUT /_storage/:disk/*key` beside the
183
+ `GET` in `x dev` and `runRole` (#523), so an app writes steps 1 and 2 only. The route calls
184
+ `signedUploadConstraints({ url, disk, orgId })` first. That is the verified PUT constraints with
185
+ no bytes, and the signed `maxBytes` caps the body read. Then it calls `acceptSignedUpload` with a
186
+ policy built from the grant's own signed type and ceiling. The routes serve `definedStorage()`,
187
+ the process's one registry, which is the app's `defineStorage` when it declared one (#524).
188
+
182
189
  ## Attachments and orphans
183
190
 
184
191
  An upload happens **before** the row it belongs to exists, so it lands at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/storage",
3
- "version": "22.0.0",
3
+ "version": "22.2.0",
4
4
  "description": "Named disks over Bun.file and Bun.s3: safe keys, signed URLs, sniffed uploads",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,6 +31,6 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/core": "22.0.0"
34
+ "@ultimat3/core": "22.2.0"
35
35
  }
36
36
  }
package/src/accept.ts CHANGED
@@ -123,6 +123,19 @@ async function constraintsFor(
123
123
  return constraints;
124
124
  }
125
125
 
126
+ /**
127
+ * The verified half of a PUT, without the bytes: the signature, the expiry, the method and the
128
+ * tenant, answered as the constraints the grant signed. A route reads this BEFORE the body so the
129
+ * signed `maxBytes` — not an HTTP-wide default, and never an unverified query parameter — is the
130
+ * cap on how much of the body it is willing to hold. `acceptSignedUpload` asks again; an HMAC is
131
+ * cheap and a second statement of the accept rules would not be.
132
+ */
133
+ export async function signedUploadConstraints(
134
+ input: SignedRequestInput,
135
+ ): Promise<SignedUrlConstraints> {
136
+ return constraintsFor(input, 'PUT');
137
+ }
138
+
126
139
  /**
127
140
  * Take a presigned PUT and write it, or refuse. Four gates the client cannot move because all
128
141
  * four are inside the signature or inside the bytes: the signature itself, the expiry, the byte
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // every consumer imports from here, so this list is the package's contract.
3
3
 
4
4
  export type { AcceptSignedUploadInput, SignedRequestInput } from './accept';
5
- export { acceptSignedUpload, readSignedObject } from './accept';
5
+ export { acceptSignedUpload, readSignedObject, signedUploadConstraints } from './accept';
6
6
  export type {
7
7
  AttachmentTarget,
8
8
  PromoteAttachmentInput,
@@ -154,7 +154,7 @@ export {
154
154
  verifySignedUrl,
155
155
  } from './signed-url';
156
156
  export type { Storage, StorageConfig } from './storage';
157
- export { defineStorage, disk, resetStorage, storage } from './storage';
157
+ export { definedStorage, defineStorage, disk, resetStorage, storage } from './storage';
158
158
  export type { UploadCandidate, UploadPolicy, UploadPolicyInit, ValidatedUpload } from './upload';
159
159
  export {
160
160
  contentTypeMatches,
package/src/storage.ts CHANGED
@@ -88,6 +88,16 @@ export function storage(): Storage {
88
88
  return current;
89
89
  }
90
90
 
91
+ /**
92
+ * The configured storage, or `undefined` before any `defineStorage` — the question a host asks
93
+ * when it must serve whatever the app installed and fall back to its own disk otherwise. The LAST
94
+ * call wins, by `defineStorage`'s own contract: there is one registry, so the app's declaration
95
+ * replaces a host's default rather than sitting beside it.
96
+ */
97
+ export function definedStorage(): Storage | undefined {
98
+ return current;
99
+ }
100
+
91
101
  /** Shorthand for the common call. `disk()` alone resolves the default disk. */
92
102
  export function disk(name?: string): StorageDriver {
93
103
  return storage().disk(name);