@ultimat3/storage
Advanced tools
+32
-2
@@ -17,2 +17,29 @@ # @ultimat3/storage — agent notes | ||
| - **Every byte ceiling and every TTL is screened where it is DECLARED, through core's | ||
| `finiteCount` and nothing else, `As of 2026-08-26`** — `uploadPolicy({ maxBytes })`, both | ||
| drivers' `maxPutBytes`, `createUploadGrant({ expiresInMs })`, `buildSignedUrl`'s two and the s3 | ||
| driver's presign TTL. There was briefly a private `assertFiniteSignedUrlBound` here saying the | ||
| same thing in its own words; a second finite-bound path is one that drifts from the shared | ||
| contract, which is exactly what `jobs`, `realtime` and `query` each proved with a copy of their | ||
| own. Both presigners now call the one function, which is what `driver-parity.test.ts` needs to | ||
| stay true. | ||
| Measured: `uploadPolicy({ maxBytes: Number.NaN })` accepted a 5,000,016-byte PNG through | ||
| `validateUpload`, because `size > NaN` is false — the one number deciding how much a caller may | ||
| store stopped deciding anything. Variant `quality` is core's `assertFiniteImageQuality` and NOT a | ||
| second screen: `variantKey` never reaches the encoder, so a copy that disagreed would mint | ||
| `q150` keys for bytes `transformImageBytes` then refuses. | ||
| - **The default is taken on `undefined` and on nothing else, `As of 2026-08-26`** — | ||
| `options.x === undefined ? D : options.x`, never `options.x ?? D`. `??` coalesces on `null` too, | ||
| so an explicitly blanked key in a decoded JSON config took the default BEFORE the screen above | ||
| could refuse it: the mirror of the `NaN` half, where one value slips past the guard and the other | ||
| past the default, and both end in a bound nobody chose. Every site above, `quality` included. | ||
| - **`MAX_KEY_LENGTH` is BYTES, and is measured in bytes, `As of 2026-08-26`.** S3's limit is "a | ||
| sequence of Unicode characters whose UTF-8 encoding is at most 1,024 bytes long", and `path.ts` | ||
| measured `key.length` — UTF-16 code units — while its message said "chars". A code-unit count is | ||
| never MORE than the UTF-8 byte count, so a non-ASCII key over the real limit passed this guard | ||
| and was refused by the store instead: 400 CJK characters is 400 units and 1,200 bytes. Keeping | ||
| local and remote disks interchangeable is the whole reason the ceiling exists, so it has to be | ||
| the store's ceiling. Same defect and same fix as `@ultimat3/cache`'s surrogate-key guard. | ||
| | Rule | | | ||
@@ -166,4 +193,7 @@ |---|---| | ||
| signing every grant with the published literal. All three reads now come off `options.env ?? | ||
| process.env`, so a bare `localDriver({ root })` is unchanged and additive. `driver-local.test.ts` | ||
| pins it by mutation — reverting any one read to `process.env` fails. | ||
| process.env`, so a bare `localDriver({ root })` is unchanged and additive. | ||
| `driver-local-boot.test.ts` pins it by mutation — reverting any one read to `process.env` fails. | ||
| That file is the CONSTRUCTION half, split off `driver-local.test.ts` at the line ceiling along | ||
| the seam the guard already draws: nothing in it writes a byte, so it needs no temporary | ||
| directory, and `driver-local.test.ts` keeps everything the disk actually does with an object. | ||
| - **The mounted read half is `@ultimat3/cli`'s `dev-storage.ts`, not this package.** `GET | ||
@@ -170,0 +200,0 @@ /_storage/:disk/*key` gates on `@ultimat3/policy`'s `evaluate()` (`storage:read`), which is tier |
+2
-2
| { | ||
| "name": "@ultimat3/storage", | ||
| "version": "16.0.0", | ||
| "version": "17.0.0", | ||
| "description": "Named disks over Bun.file and Bun.s3: safe keys, signed URLs, sniffed uploads", | ||
@@ -34,4 +34,4 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@ultimat3/core": "16.0.0" | ||
| "@ultimat3/core": "17.0.0" | ||
| } | ||
| } |
@@ -8,2 +8,3 @@ // Single responsibility: the dev-default disk — a real, working driver over `Bun.file` / | ||
| type Clock, | ||
| finiteCount, | ||
| isLocal, | ||
@@ -163,3 +164,10 @@ type ResolveEnvironmentOptions, | ||
| const root = options.root.replace(/\/+$/, ''); | ||
| const maxPutBytes = options.maxPutBytes ?? DEFAULT_MAX_UPLOAD_BYTES; | ||
| // `=== undefined`, never `??`: `??` coalesces on `null` too, so an explicitly blanked key in a | ||
| // decoded JSON config took the default instead of the refusal `finiteCount` is here to raise. | ||
| const maxPutBytes = finiteCount( | ||
| 'the local disk driver', | ||
| 'maxPutBytes', | ||
| options.maxPutBytes === undefined ? DEFAULT_MAX_UPLOAD_BYTES : options.maxPutBytes, | ||
| 1, | ||
| ); | ||
| const clock = options.clock ?? systemClock; | ||
@@ -166,0 +174,0 @@ // The segment is the disk's REGISTERED name, learned from `defineStorage` at boot — the driver |
+21
-3
@@ -6,3 +6,3 @@ // Single responsibility: the production disk over Bun's native S3 client. One driver covers | ||
| import { ConfigInvalidError, EnvMissingError, stringField } from '@ultimat3/core'; | ||
| import { ConfigInvalidError, EnvMissingError, finiteCount, stringField } from '@ultimat3/core'; | ||
| import { | ||
@@ -31,2 +31,3 @@ DEFAULT_CONTENT_TYPE, | ||
| import { assertSafeKey } from './path'; | ||
| import { DEFAULT_SIGNED_URL_TTL_MS } from './signed-url'; | ||
| import { DEFAULT_MAX_UPLOAD_BYTES } from './upload'; | ||
@@ -214,3 +215,10 @@ | ||
| export function s3Driver(options: S3DriverOptions): StorageDriver { | ||
| const maxPutBytes = options.maxPutBytes ?? DEFAULT_MAX_UPLOAD_BYTES; | ||
| // `=== undefined`, never `??`: `??` coalesces on `null` too, so an explicitly blanked key in a | ||
| // decoded JSON config took the default instead of the refusal `finiteCount` is here to raise. | ||
| const maxPutBytes = finiteCount( | ||
| 'the s3 driver', | ||
| 'maxPutBytes', | ||
| options.maxPutBytes === undefined ? DEFAULT_MAX_UPLOAD_BYTES : options.maxPutBytes, | ||
| 1, | ||
| ); | ||
| let client: S3ClientLike | undefined; | ||
@@ -362,3 +370,13 @@ const conn = (): S3ClientLike => { | ||
| async signedUrl(key: string, urlOptions?: SignedUrlOptions): Promise<string> { | ||
| const expiresInMs = urlOptions?.expiresInMs ?? 900_000; | ||
| // Screened with the SAME function `buildSignedUrl` applies on the local disk — one | ||
| // finite-bound path for both presigners, never a private copy per package: `Math.ceil(NaN / | ||
| // 1000)` is `NaN`, so `X-Amz-Expires=NaN` went to AWS and the app got a 403 on a link it | ||
| // believed it had just minted. `=== undefined` rather than `??`, so an explicit `null` is | ||
| // refused instead of silently taking the default. | ||
| const expiresInMs = finiteCount( | ||
| 'the s3 driver', | ||
| 'expiresInMs', | ||
| urlOptions?.expiresInMs === undefined ? DEFAULT_SIGNED_URL_TTL_MS : urlOptions.expiresInMs, | ||
| 1, | ||
| ); | ||
| return conn() | ||
@@ -365,0 +383,0 @@ .file(assertSafeKey(key)) |
+12
-2
@@ -8,3 +8,3 @@ // Single responsibility: minting ONE presigned PUT for ONE file, with the tenant prefix and the | ||
| import type { Clock } from '@ultimat3/core'; | ||
| import { systemClock } from '@ultimat3/core'; | ||
| import { finiteCount, systemClock } from '@ultimat3/core'; | ||
| import type { AttachmentTarget } from './attachment'; | ||
@@ -86,3 +86,13 @@ import { attachmentKey, pendingKey, quarantineKey, uploadName } from './attachment'; | ||
| const expiresInMs = input.expiresInMs ?? DEFAULT_SIGNED_URL_TTL_MS; | ||
| // Refused here as well as in the presigner one call down: `expiresAt: now + NaN` is `NaN` in the | ||
| // grant this function RETURNS, and the message a caller can act on names `createUploadGrant`'s | ||
| // own option rather than `buildSignedUrl`'s. | ||
| // `=== undefined`, never `??`, for the reason `buildSignedUrl` states: `??` coalesces on `null` | ||
| // too, so an explicit one took the default instead of this refusal. | ||
| const expiresInMs = finiteCount( | ||
| 'createUploadGrant', | ||
| 'expiresInMs', | ||
| input.expiresInMs === undefined ? DEFAULT_SIGNED_URL_TTL_MS : input.expiresInMs, | ||
| 1, | ||
| ); | ||
| const url = await input.disk.signedUrl(key, { | ||
@@ -89,0 +99,0 @@ method: 'PUT', |
+12
-2
@@ -7,2 +7,3 @@ // Single responsibility: the image transform contract — deterministic variant keys and srcset | ||
| import { | ||
| assertFiniteImageQuality, | ||
| blurDataUrl, | ||
@@ -94,3 +95,10 @@ type ImageFormat, | ||
| if (transform.fit !== undefined) parts.push(transform.fit); | ||
| const quality = transform.quality ?? DEFAULT_QUALITY; | ||
| // Core's screen, not a second one: this function never reaches the encoder, so without it a | ||
| // `q150` or a `qNaN` variant key is minted for bytes `transformImageBytes` then refuses — and | ||
| // an unbounded quality is an unbounded number of distinct keys in the bucket. | ||
| // `=== undefined`, never `??`: `??` coalesces on `null` too, so an explicit one was spelled | ||
| // into a key as the default rather than refused. | ||
| const quality = assertFiniteImageQuality( | ||
| transform.quality === undefined ? DEFAULT_QUALITY : transform.quality, | ||
| ); | ||
| if (quality !== DEFAULT_QUALITY) parts.push(`q${quality}`); | ||
@@ -178,3 +186,5 @@ if (parts.length === 0) parts.push('full'); | ||
| format: transform.format ?? 'webp', | ||
| quality: transform.quality ?? DEFAULT_QUALITY, | ||
| quality: assertFiniteImageQuality( | ||
| transform.quality === undefined ? DEFAULT_QUALITY : transform.quality, | ||
| ), | ||
| }); | ||
@@ -181,0 +191,0 @@ } |
+15
-3
@@ -8,4 +8,15 @@ // Single responsibility: object keys. Every key that reaches a driver passes through here, | ||
| /** S3's own limit; keeping local and remote disks interchangeable requires the same ceiling. */ | ||
| /** | ||
| * S3's own limit; keeping local and remote disks interchangeable requires the same ceiling. | ||
| * | ||
| * BYTES, and S3 says so — "a sequence of Unicode characters whose UTF-8 encoding is at most 1,024 | ||
| * bytes long". It was measured with `String.length`, which is UTF-16 code units, and a code-unit | ||
| * count is never MORE than the UTF-8 byte count — so every non-ASCII key over the real limit | ||
| * passed this guard and was refused by the store instead, which is the one thing a shared ceiling | ||
| * exists to prevent. `MAX_KEY_LENGTH` keeps its name: it is exported, and the number is unchanged. | ||
| */ | ||
| export const MAX_KEY_LENGTH = 1024; | ||
| /** What the wire counts. The key is at most ~1 KB, so this is cheaper than deciding not to. */ | ||
| const utf8Bytes = (value: string): number => new TextEncoder().encode(value).byteLength; | ||
| export const ORG_PREFIX = 'org'; | ||
@@ -36,4 +47,5 @@ | ||
| if (key.length === 0) return 'is empty'; | ||
| if (key.length > MAX_KEY_LENGTH) { | ||
| return `is ${key.length} chars, over the ${MAX_KEY_LENGTH} limit`; | ||
| const bytes = utf8Bytes(key); | ||
| if (bytes > MAX_KEY_LENGTH) { | ||
| return `is ${bytes} bytes, over the ${MAX_KEY_LENGTH}-byte limit`; | ||
| } | ||
@@ -40,0 +52,0 @@ if (hasControlByte(key)) return 'contains a NUL or control byte'; |
+25
-2
@@ -7,3 +7,3 @@ // Single responsibility: time-limited signed URLs for direct upload and download. | ||
| import { type Clock, systemClock, timingSafeEqual } from '@ultimat3/core'; | ||
| import { type Clock, finiteCount, systemClock, timingSafeEqual } from '@ultimat3/core'; | ||
| import type { SignedUrlMethod } from './driver'; | ||
@@ -106,9 +106,32 @@ import { assertSafeKey, isSafeKey } from './path'; | ||
| /** | ||
| * The mint's side of the screen `parseConstraints` already applies to the read. `finiteCount` from | ||
| * `@ultimat3/core` is that screen — a private copy of it lived here, and a second finite-bound path | ||
| * is one that drifts from the shared contract, which is what `jobs`, `realtime` and `query` each | ||
| * proved with a byte-identical copy of their own. The S3 driver calls the same function directly, | ||
| * because it presigns through the provider rather than through this one, and two presigners that | ||
| * refuse different inputs is the disagreement `driver-parity.test.ts` exists to catch. | ||
| * | ||
| * A URL is minted here and verified elsewhere, so a number this function writes and that one | ||
| * refuses is a link that is dead on arrival — the app's own call succeeded, and the only report is | ||
| * a 400 at a recipient who cannot fix it. `expiresInMs: NaN` is exactly that: `now + NaN` is | ||
| * `NaN`, `String` writes the literal `NaN` into `x-exp`, and `Number.isSafeInteger` on the other | ||
| * side says no. | ||
| */ | ||
| export async function buildSignedUrl(input: SignedUrlInput): Promise<string> { | ||
| const key = assertSafeKey(input.key); | ||
| const clock = input.clock ?? systemClock; | ||
| // `=== undefined`, never `??`: `??` coalesces on `null` too, so an explicit `null` would take | ||
| // the default instead of the refusal `finiteCount` is here to raise. | ||
| const expiresInMs = finiteCount( | ||
| 'buildSignedUrl', | ||
| 'expiresInMs', | ||
| input.expiresInMs === undefined ? DEFAULT_SIGNED_URL_TTL_MS : input.expiresInMs, | ||
| 1, | ||
| ); | ||
| if (input.maxBytes !== undefined) finiteCount('buildSignedUrl', 'maxBytes', input.maxBytes, 0); | ||
| const constraints: SignedUrlConstraints = { | ||
| key, | ||
| method: input.method ?? 'GET', | ||
| expiresAt: clock.now().getTime() + (input.expiresInMs ?? DEFAULT_SIGNED_URL_TTL_MS), | ||
| expiresAt: clock.now().getTime() + expiresInMs, | ||
| maxBytes: input.maxBytes, | ||
@@ -115,0 +138,0 @@ // An empty string is not a content type, and `canonicalRequest` renders it and `undefined` |
+13
-1
@@ -7,2 +7,3 @@ // Single responsibility: the constraint policy for direct-to-storage uploads — size, | ||
| import { finiteCount } from '@ultimat3/core'; | ||
| import { sha256Base64 } from './driver'; | ||
@@ -47,3 +48,14 @@ import { checksumMismatch, contentTypeMismatch, contentTypeNotAllowed, tooLarge } from './errors'; | ||
| return { | ||
| maxBytes: init.maxBytes ?? DEFAULT_MAX_UPLOAD_BYTES, | ||
| // Screened where it is DECLARED, because every reader of it is a comparison: `size > | ||
| // policy.maxBytes` is false for a `NaN` ceiling, so the cap that decides how much a caller may | ||
| // store stops deciding anything. Measured: a 5,000,016-byte PNG passed `validateUpload` under | ||
| // `uploadPolicy({ maxBytes: Number.NaN })`. | ||
| // `=== undefined`, never `??`: `??` coalesces on `null` too, so an explicitly blanked key in | ||
| // a decoded JSON config took the default instead of the refusal beside it. | ||
| maxBytes: finiteCount( | ||
| 'uploadPolicy', | ||
| 'maxBytes', | ||
| init.maxBytes === undefined ? DEFAULT_MAX_UPLOAD_BYTES : init.maxBytes, | ||
| 1, | ||
| ), | ||
| allowedContentTypes: init.allowedContentTypes ?? IMAGE_CONTENT_TYPES, | ||
@@ -50,0 +62,0 @@ requireChecksum: init.requireChecksum ?? false, |
187982
4.31%3086
3.07%+ Added
- Removed
Updated