@ 12- apps/ storage (简体中文)
图像上传降落在 您的原籍国从来没有在桶.
一个后端工厂,一个前端工厂,一个司机端口,两个 执行,以及图像管道端口,这样本地依赖性将保留 宿主的选择.
收养这个? 此 README 是 API 参考;
ADOPTING.md是分步骤的接线,包括五个所需的配置值和为什么 他们每个人都没有违约.
整个套装的规矩是围绕
浏览器 POST 将其字节输入应用服务器. 没有预先签名的URL 模式,而且决不能有一个模式.
预先签名的桶 URL 使工作上传取决于桶 (简体中文)
规则 ——既非此寄存器所居住,也非任何部署的配置
运行。 A 类 PUT 携带 Content-Type 从来不是一个"简单"的要求,所以
浏览器发送 OPTIONS 先飞行; 一个新创建的桶接应它
与 403 没有 Access-Control-Allow-* 信头,上传从不
发送。 是什么使得失败模式特别糟糕:
- 服务器看不到任何东西. 没有请求到来——没有日志行,没有出错 利率,没有5xx.
- 浏览器不会说原因 . 一个被封锁的跨源取回是故意的
无法与离线网络区分
fetch层. - 所见之人不能修. 消息传到店主身上, 只有操作员才能配置一个桶.
无法返回的第二个原因: 存储的照片是 设定 对象 服务器来自字节,所以上传服务器从未看到无法生成 一个.
子路径导出
| 导入 | 目录 |
|---|---|
@12-apps/storage | Isomorphic core:格式允许列表,上传天花板,对象键语法,递解集,拒绝分类学,URL构建器,线条计. |
@12-apps/storage/server | createApiStorage(config),驱动端口,本地磁盘驱动端口,图像管端口兼有两种执行功能,可回收. |
@12-apps/storage/hono | storageRouter({ …config, resolveActor }) - 一通电话. hono 是一个可选的对等点. |
@12-apps/storage/s3 | createS3Driver(config) 每个S3兼容供应商. @aws-sdk/client-s3 是可选的对等方,只在这里解决. |
@12-apps/storage/react | createWebStorage({ apiBase }) ——绑定取取器,预览取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取取. |
出口地图位于 ./src 直接——在
运行时间.
两个终点
POST <mount>/uploads/image raw bytes in, { data: { imageKey } } out
GET <mount>/uploads/local/… the object back, or a redirect to where it lives
服务路线有两个行为 驱动程序 决定: 驱动程序
保留自己的字节执行器 read 和路线
桶后驱动程序没有, 路径会重新定向到 publicUrl时使用
成为路由内部读取的环境旗——同样的事实在一秒之内
位置,可以对实际使用的驱动程序有异议.
这照片真多
products/<scope>/<uuid>/full.webp the whole picture — the zoom
products/<scope>/<uuid>/card-320.webp 4:3, a card on a phone
products/<scope>/<uuid>/card-640.webp 4:3, a retina card / a phone hero
products/<scope>/<uuid>/card-1280.webp 4:3, a retina hero on a desktop
products/<scope>/<uuid>/thumb-128.webp 1:1, a 64px row thumbnail
products/<scope>/<uuid>/thumb-256.webp 1:1, the same row at 2×
在行上保存的密钥名称为未剪接的成员,以及 键的形状是
作物是怎样存在的数字 : …/<uuid>/full.<ext> 有他们,公寓
…/<uuid>.<ext> 没有。 没有其他记录——没有栏,因此
草案、变更请求、回收站
快照和一行的版本历史被复制成.
<scope> 是租户。 就是它使得交叉租户重新找回一个剖面
而非查询: 回收时拒绝使用范围不是呼叫者的密钥 在此之前
咨询任何参考探测器。 没有它的计划只能回答"也许"
租户删除此对象。” 他们的 点数
对于其他每个房客的客体来说也是.
无默认的五种值
createApiStorage 需求 driver, (中文(简体) ), maxBytes, (中文(简体) ), imagePipeline, (中文(简体) ),
unscopedKeys 财务报告和审定财务报表 references。每个安全值都是针对主机的,所以默认值
由谁打出第一个主机 默默继承
以它之后的每个人盟誓 安静,这是实际
参数。 第一次部署时会修正一个响亮的默认值;这些不:
| 配置 | 违约要花多少钱 |
|---|---|
driver | 一个容器的麻黄磁盘在生产中,每一次上传都报告成功,并在下一次部署时丢失了物体. |
maxBytes | 不管这个文件有多少, 在一个主机中,它的工具计划广告其他的东西. |
imagePipeline | 作物是否存在——从生产中钥匙的形状中发现. |
unscopedKeys | 一个租户的开垦能否达到另一个租户的字节. |
references | 恢复的行是否还有相片 . [] 不是中立的:这意味着 立即归还一切。 。 。 . |
logger 继续违约,这是合法可以做到的:
只有报告,永远不要增加,所以最糟糕的是默认的伐木者成本是沉默.
references 也默认,直到它没有——因为它的默认 销毁。 。 。 .
后端, 装满
注意没有 ??。 。 。 . 以下每个值均来自
环境,必须在那里,或者被写出来。 这是故意的:a
process.env.UPLOADS_DIR ?? \${ process.cwd ()}/. uploads' (中文(简体) )in this position is exactly the fail-open default the table above refuses to have.UPROADS DIR 导弹unset in production means uploads succeed, one container holds the object, the other 404s half therequests, and the next deploy discards both — with nothing in any log, because写 File 每次都成功.
import { createApiStorage } from '@12-apps/storage/server';
import { createLocalDiskDriver, passthroughImagePipeline } from '@12-apps/storage/server';
import { DEFAULT_MAX_UPLOAD_BYTES, STORAGE_PATHS } from '@12-apps/storage';
/** Fail at BOOT for a missing value, rather than at the next deploy. */
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required — it must be a MOUNTED VOLUME, not a container path.`);
return value;
}
const storage = createApiStorage({
driver: createLocalDiskDriver({
root: requireEnv('UPLOADS_DIR'),
publicPathPrefix: `/api${STORAGE_PATHS.serve}`,
}),
maxBytes: DEFAULT_MAX_UPLOAD_BYTES,
imagePipeline: passthroughImagePipeline(),
// 'reject' because this host is fresh: every key it will ever mint is scoped, so
// a key without a scope can only be someone else's or nobody's. A host with rows
// that predate the scope segment passes 'accept' — see ADOPTING.md rule 2.
unscopedKeys: 'reject',
// Which of YOUR tables can still need an object after the row stopped pointing at
// it. `[]` says "nothing here copies a key" — write it only if you have checked.
references: [
{ name: 'live-rows', referenced: (scope, key) => rowPointsAt(scope, key) },
],
});
对于 发展 页:1 ?? 就在这里 因为一个丢失的物体
手提电脑不花钱:
// DEV ONLY. Never in the block above.
root: process.env.UPLOADS_DIR ?? `${process.cwd()}/.uploads`,
storage 携带一个主机所需的一切:
| 成员 | 使用 |
|---|---|
routes | 框架中立说明. @12-apps/storage/hono 让他们上船. |
limits | 这个挂载所执行的——从它中提供工具计划,从不从你自己的常数中提供. |
schemas.inlineImage | 一个Zod计划,它的基数64上限 实值 maxBytes。 。 。 . |
schemas.objectKey | 计划一个 imageKey 字段. 没有 z.string() - 见下文. |
storeImage 编号 : storeInlineImage | 包含字节的宿主写入的写入路径(一个代理的唯一选项). |
urls.objectUrl 编号 : urls.imageSources 编号 : urls.versionedObjectUrl | 键 = URL,通过活动驱动程序. |
reclaim.* | 重取一个被替换的对象、 被替换的集或密钥, 仅刻出一个失败的写入 . |
一个 imageKey 字段不是一个字符串字段
objectUrl 返回已存在的绝对值 不变 ——深思熟虑
支付从 URL 列移出的主机的费用 。 所以一个接受
imageKey: z.string() 接受 https://tracker.example/p.png,每个买家
装入存储首页后将IP和用户代理发送到第三个
派对 没有上传,没有桶和司机参与,所以没有在存储器上
路径可以抓住它。 使用挂载本身的图谋 :
const body = z.object({
imageKey: storage.schemas.objectKey.nullish(),
image: storage.schemas.inlineImage.optional(),
}).superRefine(refineImageInput);
前端, 装满
import { createWebStorage } from '@12-apps/storage/react';
const storage = createWebStorage({ apiBase: '/api' });
// storage.page a standalone upload surface
// storage.ImageField the field a form drops in — a key comes out
// storage.useUpload the primitive, for a bespoke affordance
图像管道是一个端口
图像处理是指原生模块,原生模块是构建地表
每个消费者都会为每次部署付出代价 不论他们是否想要作物 这么说吧
sharp 这里既非依附关系,也非对等关系:
模块.
import sharp from 'sharp';
import { createSharpImagePipeline } from '@12-apps/storage/server';
imagePipeline: createSharpImagePipeline({ sharp });
passthroughImagePipeline() 是另一个选择: 字节按原样存储
没有庄稼,没有扁平的钥匙, 这正是每个目录的图像
在庄稼存在之前.
神奇数字检查不是管道的工作,这个读取器曾经说
这是。 它属于WRITER: storeImage 校正字节对齐
在任何管道看到它们之前,宣布类型为第一个声明。 那个
安排是故意的,因为不能同时相信这两项执行
带着它.. passthrough 核实为无所事事的一部分,同时 sharp 无法:
"did libvips decode this"和"这就是被宣布的格式"是
不同的问题,且 libvips 解码了大量的允许列表格式
除外。 把支票放进写作机里就意味着没有路可走
输入的管道 以及字节是否到达
结束点, 写作中的基数64, 或者通过直接 storeImage 电话.
第二家卖家是配置
createS3Driver 覆盖每个S3兼容的商店. AWS需要一个 region· ;
数字海洋空间,MinIO,R2和其余需要 endpoint附加时
forcePathStyle 给那些不能虚拟地址的人 没有
上传路径中的分支更改,没有写入主机代码.
包里没有写 process.env。 。 。 .