|
| 1 | +--- |
| 2 | +"@objectstack/runtime": minor |
| 3 | +"@objectstack/hono": minor |
| 4 | +"@objectstack/plugin-dev": patch |
| 5 | +--- |
| 6 | + |
| 7 | +fix(runtime,hono,plugin-dev): retire the dispatcher's `/storage` bridge — it never spoke the storage contract (#4087) |
| 8 | + |
| 9 | +`POST /api/v1/storage/upload` and `GET /api/v1/storage/file/:id` were a |
| 10 | +dispatcher-side bridge to the `file-storage` service slot, written against a |
| 11 | +service shape that does not exist: |
| 12 | + |
| 13 | +- **Upload** called the contract's `upload(key, data, options?)` as |
| 14 | + `upload(file, { request })` — the parsed file object landed in the `key` |
| 15 | + slot and `{ request }` in `data`. That is a `TypeError` against every |
| 16 | + implementation in the repo (`S3StorageAdapter`, `LocalStorageAdapter`, |
| 17 | + `SwappableStorageService`, plugin-dev's in-memory one), not a |
| 18 | + near-miss: `Buffer.from({}) → ERR_INVALID_ARG_TYPE`, or an object used as |
| 19 | + an S3 object key / `path.join` segment. |
| 20 | +- **Download** branched on `result.url` / `result.redirect` / `result.stream` |
| 21 | + / `result.mimeType` while the contract's `download(key)` resolves a |
| 22 | + `Buffer`, so every branch fell through and the route answered a |
| 23 | + JSON-serialized Buffer. |
| 24 | + |
| 25 | +Both routes are removed, along with `HttpDispatcher.handleStorage()`, the |
| 26 | +`/storage` domain registration, the dispatcher-plugin mounts and the two route |
| 27 | +ledger rows. |
| 28 | + |
| 29 | +**Migration.** There is nothing to migrate off in practice — neither route |
| 30 | +could complete a request. (They were reachable: `service-storage` mounts |
| 31 | +`/storage/upload/presigned`, not `/storage/upload`, so nothing shadowed them. |
| 32 | +They simply had no caller — no SDK method builds those URLs.) |
| 33 | +`/api/v1/storage` is `@objectstack/service-storage`'s surface and always was |
| 34 | +the working one: |
| 35 | + |
| 36 | +- Upload — FROM `POST /api/v1/storage/upload` TO the presigned protocol |
| 37 | + (`POST /storage/upload/presigned` → direct `PUT` to the returned URL → |
| 38 | + `POST /storage/upload/complete`), or `client.storage.upload(file)`, which |
| 39 | + runs all three steps. |
| 40 | +- Download — FROM `GET /api/v1/storage/file/:id` TO |
| 41 | + `GET /storage/files/:fileId/url` (`client.storage.getDownloadUrl(fileId)`) |
| 42 | + for a signed URL, or `GET /storage/files/:fileId` for a stable browser URL |
| 43 | + that 302s to it. |
| 44 | + |
| 45 | +Install `@objectstack/service-storage` to get those routes; without it |
| 46 | +`/api/v1/storage` now has no handler, which is the same answer every other |
| 47 | +uninstalled capability gives. |
| 48 | + |
| 49 | +Two follow-on corrections keep `declared === enforced`: |
| 50 | + |
| 51 | +- `@objectstack/hono` no longer mounts `app.all('<prefix>/storage/*')`. That |
| 52 | + wildcard claimed the whole `/storage` subtree for the two dead routes, so |
| 53 | + every other path under it — service-storage's protocol above all — got the |
| 54 | + bridge's own 404 rather than falling through. Storage is ordinary catch-all |
| 55 | + traffic now. |
| 56 | +- Discovery advertises `routes.storage` only when the `file-storage` slot's |
| 57 | + occupant actually mounts HTTP handlers (`handlerReady`, the predicate #4000 |
| 58 | + gave analytics), and plugin-dev's in-memory implementation now self-declares |
| 59 | + `handlerReady: false` — with the bridge gone, nothing routes HTTP to it. It |
| 60 | + keeps working for in-process callers; it is simply no longer advertised as a |
| 61 | + reachable HTTP capability. |
0 commit comments