@homurajs/storage 26.10.13 → 26.10.14

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
@@ -2,10 +2,18 @@
2
2
 
3
3
  对象存储/图床:三后端(local-fs / s3 兼容 / supabase)+ `/files/<key>` 代理路由。
4
4
 
5
+ > ⚠ **qw-pages 上三个后端都不可用**(2026-10-13 实测):
6
+ >
7
+ > - `supabase`:桶建不出来——anon 建桶被 RLS 拒(400 内含 403),平台受控迁移角色对 `storage` schema 也无权(`42501`)
8
+ > - `local`:默认 `./data/files` 在 Pages 不可写(应用根只读),且 `mkdir` 发生在**每次上传时**而非构造期 → 启动正常、`/_healthz` 报 ok、首次上传才 EACCES;改指 `QWENWORK_WRITABLE_DIR` 也只能当临时缓冲(跨发布清空)
9
+ > - `s3`:理论可行(凭据可经 `homura.config.env` 投递),但需 App 自行安装 optional peer,且 S3 端点出网未验证
10
+ >
11
+ > 详见 `docs/specs/module-qw-pages.md` 与 AGENTS.md 的支持矩阵。
12
+
5
13
  ```ts
6
- import { createLocalStorage } from '@homurajs/storage/local' // 独立形态
7
- // import { createS3Storage } from '@homurajs/storage/s3' // minio/oss(AWS SDK 动态加载)
8
- // import { createSupabaseStorage } from '@homurajs/storage/supabase' // qw-pages(手写 REST)
14
+ import { createLocalStorage } from '@homurajs/storage/local' // 独立形态
15
+ // import { createS3Storage } from '@homurajs/storage/s3' // minio/oss(AWS SDK 动态加载,需自装 peer)
16
+ // import { createSupabaseStorage, loadSupabaseStorageEnv } from '@homurajs/storage/supabase'
9
17
 
10
18
  const storage = createLocalStorage({ rootDir: './data/files' })
11
19
 
@@ -23,4 +31,17 @@ import { createFileProxyRoutes } from '@homurajs/storage/proxy'
23
31
  app.route('/', createFileProxyRoutes(storage))
24
32
  ```
25
33
 
26
- 使用示范见模板 `src/server/routes/files.ts`。
34
+ supabase 后端的 env 解析用**本包**的 loader,不要用 `@homurajs/db/pg` 的那个:
35
+
36
+ ```ts
37
+ import { createSupabaseStorage, loadSupabaseStorageEnv } from '@homurajs/storage/supabase'
38
+
39
+ // 只需 SUPABASE_URL + SUPABASE_ANON_KEY(qw-pages 会自动注入这两项)
40
+ const { platformUrl, apiKey } = loadSupabaseStorageEnv(process.env)
41
+ const storage = createSupabaseStorage({ platformUrl, apiKey, bucket: 'files' })
42
+ // ⚠ bucket 必须预先存在:本包与平台迁移都建不出桶(见上方警告)
43
+ ```
44
+
45
+ `@homurajs/db/pg` 的 `loadSupabaseEnv` 要 `SUPABASE_PROJECT_REF` + `SUPABASE_DB_PASSWORD`(PG 直连用,仅自托管 supabase 有),拿它来配对象存储会因为缺 PG 凭据而抛错——这个连带依赖已经拆掉。
46
+
47
+ 使用示范见模板 `src/server/routes/files.ts` 与 `app.ts`(buildStorage)。
@@ -8,4 +8,25 @@ export interface SupabaseStorageOptions {
8
8
  fetchImpl?: typeof fetch;
9
9
  }
10
10
  export declare function createSupabaseStorage(options: SupabaseStorageOptions): FileStorage;
11
+ export interface SupabaseStorageEnv {
12
+ /** 平台 URL(已去尾斜杠),如 https://supabase.<平台域> */
13
+ platformUrl: string;
14
+ /** anon key(public bucket 足够;private bucket 需 service role key,而平台不下发) */
15
+ apiKey: string;
16
+ }
17
+ /**
18
+ * 从 env 解析 supabase storage 所需的最小集:平台 URL + anon key。
19
+ *
20
+ * 与 @homurajs/db/pg 的 loadSupabaseEnv(PG 直连,需 PROJECT_REF + DB_PASSWORD)**刻意分离**:
21
+ * 2026-10-13 实测 qw-pages 只注入 SUPABASE_URL / SUPABASE_ANON_KEY / SUPABASE_INSTANCE_ID,
22
+ * 既无 project ref 也无 DB 密码,且平台没有自定义 env 注入口 → PG 直连在 Pages 上不可用。
23
+ * 旧实现让 storage 复用 PG 的 loader,导致「只想要对象存储」也被 PG 凭据缺失一起拖死。
24
+ *
25
+ * ⚠ Pages 上的硬限制(均为实测):
26
+ * - anon 无建桶权:POST /storage/v1/bucket → 400(内层 403 "new row violates row-level security policy")
27
+ * - 受控迁移角色对 storage schema 也无权:INSERT INTO storage.buckets → 42501 permission denied
28
+ * → **bucket 必须预先存在**,否则 upload/download 一律 "Bucket not found"。
29
+ * - GET /storage/v1/bucket 可达(200,实测返回空数组),说明端点与鉴权本身没问题。
30
+ */
31
+ export declare function loadSupabaseStorageEnv(env: NodeJS.ProcessEnv): SupabaseStorageEnv;
11
32
  //#endregion
package/dist/supabase.js CHANGED
@@ -105,5 +105,29 @@ function toBytes(data) {
105
105
  if (data instanceof Uint8Array) return data;
106
106
  throw new Error("supabase 后端不支持流式载荷:请先聚合为 Uint8Array");
107
107
  }
108
+ /**
109
+ * 从 env 解析 supabase storage 所需的最小集:平台 URL + anon key。
110
+ *
111
+ * 与 @homurajs/db/pg 的 loadSupabaseEnv(PG 直连,需 PROJECT_REF + DB_PASSWORD)**刻意分离**:
112
+ * 2026-10-13 实测 qw-pages 只注入 SUPABASE_URL / SUPABASE_ANON_KEY / SUPABASE_INSTANCE_ID,
113
+ * 既无 project ref 也无 DB 密码,且平台没有自定义 env 注入口 → PG 直连在 Pages 上不可用。
114
+ * 旧实现让 storage 复用 PG 的 loader,导致「只想要对象存储」也被 PG 凭据缺失一起拖死。
115
+ *
116
+ * ⚠ Pages 上的硬限制(均为实测):
117
+ * - anon 无建桶权:POST /storage/v1/bucket → 400(内层 403 "new row violates row-level security policy")
118
+ * - 受控迁移角色对 storage schema 也无权:INSERT INTO storage.buckets → 42501 permission denied
119
+ * → **bucket 必须预先存在**,否则 upload/download 一律 "Bucket not found"。
120
+ * - GET /storage/v1/bucket 可达(200,实测返回空数组),说明端点与鉴权本身没问题。
121
+ */
122
+ function loadSupabaseStorageEnv(env) {
123
+ const platformUrl = (env["SUPABASE_URL"] ?? env["SUPABASE_PLATFORM_URL"] ?? "").replace(/\/$/, "");
124
+ const apiKey = env["SUPABASE_ANON_KEY"] ?? "";
125
+ const missing = [!platformUrl ? "SUPABASE_URL" : null, !apiKey ? "SUPABASE_ANON_KEY" : null].filter((x) => x !== null);
126
+ if (missing.length > 0) throw new Error(`supabase storage 环境不完整:缺少 ${missing.join(" 与 ")}。(qw-pages 会自动注入这两项;独立部署需自行设置。SUPABASE_PROJECT_REF / SUPABASE_DB_PASSWORD 是 PG 直连用的,Pages 不下发,storage 也不需要)`);
127
+ return {
128
+ platformUrl,
129
+ apiKey
130
+ };
131
+ }
108
132
  //#endregion
109
- export { createSupabaseStorage };
133
+ export { createSupabaseStorage, loadSupabaseStorageEnv };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homurajs/storage",
3
- "version": "26.10.13",
3
+ "version": "26.10.14",
4
4
  "type": "module",
5
5
  "description": "homura 对象存储/图床适配:supabase-storage / s3 兼容(minio/oss) / local-fs 三后端 + 代理路由",
6
6
  "engines": {
@@ -36,7 +36,7 @@
36
36
  "registry": "https://registry.npmjs.org/"
37
37
  },
38
38
  "dependencies": {
39
- "@homurajs/core": "26.10.13"
39
+ "@homurajs/core": "26.10.14"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@aws-sdk/client-s3": ">=3.0.0",