@zlooks.cn/images-server 1.0.1
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/dist/assets/provider.d.ts +3 -0
- package/dist/assets/provider.d.ts.map +1 -0
- package/dist/assets/provider.js +6 -0
- package/dist/assets/provider.js.map +1 -0
- package/dist/assets/read-image-asset.d.ts +5 -0
- package/dist/assets/read-image-asset.d.ts.map +1 -0
- package/dist/assets/read-image-asset.js +88 -0
- package/dist/assets/read-image-asset.js.map +1 -0
- package/dist/assets/response-policy.d.ts +6 -0
- package/dist/assets/response-policy.d.ts.map +1 -0
- package/dist/assets/response-policy.js +49 -0
- package/dist/assets/response-policy.js.map +1 -0
- package/dist/caches/public-image-cache.d.ts +18 -0
- package/dist/caches/public-image-cache.d.ts.map +1 -0
- package/dist/caches/public-image-cache.js +58 -0
- package/dist/caches/public-image-cache.js.map +1 -0
- package/dist/command/index.d.ts +3 -0
- package/dist/command/index.d.ts.map +1 -0
- package/dist/command/index.js +23 -0
- package/dist/command/index.js.map +1 -0
- package/dist/domain/image-error.d.ts +5 -0
- package/dist/domain/image-error.d.ts.map +1 -0
- package/dist/domain/image-error.js +9 -0
- package/dist/domain/image-error.js.map +1 -0
- package/dist/domain/list-cursor.d.ts +10 -0
- package/dist/domain/list-cursor.d.ts.map +1 -0
- package/dist/domain/list-cursor.js +31 -0
- package/dist/domain/list-cursor.js.map +1 -0
- package/dist/domain/output-schema.d.ts +63 -0
- package/dist/domain/output-schema.d.ts.map +1 -0
- package/dist/domain/output-schema.js +25 -0
- package/dist/domain/output-schema.js.map +1 -0
- package/dist/domain/schema.d.ts +69 -0
- package/dist/domain/schema.d.ts.map +1 -0
- package/dist/domain/schema.js +44 -0
- package/dist/domain/schema.js.map +1 -0
- package/dist/identity.d.ts +3 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +3 -0
- package/dist/identity.js.map +1 -0
- package/dist/mcp/provider.d.ts +2 -0
- package/dist/mcp/provider.d.ts.map +1 -0
- package/dist/mcp/provider.js +96 -0
- package/dist/mcp/provider.js.map +1 -0
- package/dist/messages/public-asset/read.msg.d.ts +3 -0
- package/dist/messages/public-asset/read.msg.d.ts.map +1 -0
- package/dist/messages/public-asset/read.msg.js +10 -0
- package/dist/messages/public-asset/read.msg.js.map +1 -0
- package/dist/migrations/index.d.ts +3 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +3 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/models/delete-image.model.d.ts +21 -0
- package/dist/models/delete-image.model.d.ts.map +1 -0
- package/dist/models/delete-image.model.js +36 -0
- package/dist/models/delete-image.model.js.map +1 -0
- package/dist/models/list-image.model.d.ts +21 -0
- package/dist/models/list-image.model.d.ts.map +1 -0
- package/dist/models/list-image.model.js +15 -0
- package/dist/models/list-image.model.js.map +1 -0
- package/dist/models/read-image.model.d.ts +20 -0
- package/dist/models/read-image.model.d.ts.map +1 -0
- package/dist/models/read-image.model.js +15 -0
- package/dist/models/read-image.model.js.map +1 -0
- package/dist/models/upload-image.model.d.ts +22 -0
- package/dist/models/upload-image.model.d.ts.map +1 -0
- package/dist/models/upload-image.model.js +52 -0
- package/dist/models/upload-image.model.js.map +1 -0
- package/dist/persistence/entities.d.ts +22 -0
- package/dist/persistence/entities.d.ts.map +1 -0
- package/dist/persistence/entities.js +20 -0
- package/dist/persistence/entities.js.map +1 -0
- package/dist/persistence/image-query.d.ts +5 -0
- package/dist/persistence/image-query.d.ts.map +1 -0
- package/dist/persistence/image-query.js +44 -0
- package/dist/persistence/image-query.js.map +1 -0
- package/dist/persistence/image-store.d.ts +30 -0
- package/dist/persistence/image-store.d.ts.map +1 -0
- package/dist/persistence/image-store.js +63 -0
- package/dist/persistence/image-store.js.map +1 -0
- package/dist/presentation/image-projection.d.ts +24 -0
- package/dist/presentation/image-projection.d.ts.map +1 -0
- package/dist/presentation/image-projection.js +19 -0
- package/dist/presentation/image-projection.js.map +1 -0
- package/dist/runtime/recover-images.d.ts +5 -0
- package/dist/runtime/recover-images.d.ts.map +1 -0
- package/dist/runtime/recover-images.js +21 -0
- package/dist/runtime/recover-images.js.map +1 -0
- package/dist/services/application.service.d.ts +4 -0
- package/dist/services/application.service.d.ts.map +1 -0
- package/dist/services/application.service.js +22 -0
- package/dist/services/application.service.js.map +1 -0
- package/dist/services/config.service.d.ts +25 -0
- package/dist/services/config.service.d.ts.map +1 -0
- package/dist/services/config.service.js +19 -0
- package/dist/services/config.service.js.map +1 -0
- package/dist/services/database.service.d.ts +3 -0
- package/dist/services/database.service.d.ts.map +1 -0
- package/dist/services/database.service.js +23 -0
- package/dist/services/database.service.js.map +1 -0
- package/dist/services/images-runtime.service.d.ts +9 -0
- package/dist/services/images-runtime.service.d.ts.map +1 -0
- package/dist/services/images-runtime.service.js +16 -0
- package/dist/services/images-runtime.service.js.map +1 -0
- package/dist/services/images.boot.d.ts +3 -0
- package/dist/services/images.boot.d.ts.map +1 -0
- package/dist/services/images.boot.js +8 -0
- package/dist/services/images.boot.js.map +1 -0
- package/dist/services/logger.service.d.ts +4 -0
- package/dist/services/logger.service.d.ts.map +1 -0
- package/dist/services/logger.service.js +8 -0
- package/dist/services/logger.service.js.map +1 -0
- package/dist/services/mcp-provider.service.d.ts +3 -0
- package/dist/services/mcp-provider.service.d.ts.map +1 -0
- package/dist/services/mcp-provider.service.js +15 -0
- package/dist/services/mcp-provider.service.js.map +1 -0
- package/dist/services/public-assets.service.d.ts +4 -0
- package/dist/services/public-assets.service.d.ts.map +1 -0
- package/dist/services/public-assets.service.js +15 -0
- package/dist/services/public-assets.service.js.map +1 -0
- package/dist/services/redis.service.d.ts +3 -0
- package/dist/services/redis.service.d.ts.map +1 -0
- package/dist/services/redis.service.js +35 -0
- package/dist/services/redis.service.js.map +1 -0
- package/dist/storage/image-path.d.ts +3 -0
- package/dist/storage/image-path.d.ts.map +1 -0
- package/dist/storage/image-path.js +17 -0
- package/dist/storage/image-path.js.map +1 -0
- package/dist/storage/inspect-image.d.ts +9 -0
- package/dist/storage/inspect-image.d.ts.map +1 -0
- package/dist/storage/inspect-image.js +36 -0
- package/dist/storage/inspect-image.js.map +1 -0
- package/dist/storage/local-storage.d.ts +10 -0
- package/dist/storage/local-storage.d.ts.map +1 -0
- package/dist/storage/local-storage.js +65 -0
- package/dist/storage/local-storage.js.map +1 -0
- package/dist/storage/read-upload-bytes.d.ts +3 -0
- package/dist/storage/read-upload-bytes.d.ts.map +1 -0
- package/dist/storage/read-upload-bytes.js +11 -0
- package/dist/storage/read-upload-bytes.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const IMAGE_MAX_BYTES = 20 * 1024 * 1024;
|
|
3
|
+
export const BASE64_MAX_BYTES = 6 * 1024 * 1024;
|
|
4
|
+
export const mediaTypeSchema = z.enum(['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/avif']);
|
|
5
|
+
export const statusSchema = z.enum(['uploading', 'ready', 'failed', 'deleting', 'deleted']);
|
|
6
|
+
const idempotencyKey = z.string().min(1).max(128).regex(/^[a-zA-Z0-9._:-]+$/)
|
|
7
|
+
.describe('调用方为一次上传指定的稳定键,1..128 字符,仅字母、数字、点、下划线、冒号和连字符。同一调用方重试相同请求使用原键;不同输入不能复用该键。确认 failed 后新上传使用新键。');
|
|
8
|
+
const filename = z.string().trim().min(1).max(200).refine((value) => !/[\x00-\x1f\x7f/\\]/.test(value), '文件名不得包含路径或控制字符');
|
|
9
|
+
const uploadFields = { idempotencyKey, filename: filename.optional().describe('可选原始文件名,用于元数据和搜索;去除首尾空白后 1..200 字符,不得含路径分隔符或控制字符。不决定存储路径、扩展名或响应头。') };
|
|
10
|
+
export const uploadImageSchema = z.strictObject({ ...uploadFields,
|
|
11
|
+
source: z.literal('base64').describe('提交调用方持有的图片字节,使用 Base64 编码;不接受 URL、文件路径或远程导入。'),
|
|
12
|
+
base64: z.string().min(4).max(Math.ceil(BASE64_MAX_BYTES / 3) * 4).regex(/^[A-Za-z0-9+/]*={0,2}$/).refine((value) => value.length % 4 === 0, 'Invalid Base64 length').describe(`将原图的完整文件字节编码为规范标准 Base64,不能编码文件路径或 URL。字符仅 A-Z、a-z、0-9、+、/;仅末尾可按需使用 1 或 2 个 = 填充,必要的填充不能省略,总长度必须是 4 的倍数。不含空格、换行或 data:image/png;base64, 等 data: 前缀;不接受以 -、_ 替代 +、/ 的 URL-safe 编码。解码后原图最多 6 MiB(${BASE64_MAX_BYTES} 字节),编码文本最多 ${Math.ceil(BASE64_MAX_BYTES / 3) * 4} 个字符(不含 JSON 包装)。必须提交完整编码,不能截断、使用省略号或手工猜测补齐;真实图片格式由服务检测。`),
|
|
13
|
+
});
|
|
14
|
+
export const readImageSchema = z.strictObject({ id: z.uuid().describe('上传、查询或元数据 Resource 返回的不可变图片 UUID,不是文件路径或公开 URL。') });
|
|
15
|
+
export const deleteImageSchema = readImageSchema.extend({ expectedSha256: z.string().regex(/^[a-f0-9]{64}$/).describe('最后读取目标元数据得到的 64 位小写十六进制 SHA-256;先向用户确认目标,摘要不匹配时拒绝删除。与 id 一起标识幂等删除目标,无需删除幂等键。') });
|
|
16
|
+
const timestamp = z.iso.datetime({ offset: true }).transform((value) => new Date(value).toISOString());
|
|
17
|
+
const nonnegativeInt = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
18
|
+
const positiveInt = nonnegativeInt.min(1);
|
|
19
|
+
export const listImageSchema = z.strictObject({
|
|
20
|
+
createdFrom: timestamp.optional().describe('记录创建时间起点(包含),带时区 ISO-8601,例如 2026-09-01T00:00:00+08:00;不是拍摄时间或上传完成时间。'),
|
|
21
|
+
createdTo: timestamp.optional().describe('记录创建时间终点(不包含),带时区 ISO-8601,必须晚于 createdFrom。查询自然日/月请传下一日/月起点,不使用 23:59:59 近似。'),
|
|
22
|
+
mediaTypes: z.array(mediaTypeSchema).min(1).max(5).transform((v) => [...new Set(v)].sort()).optional().describe('真实图片 MIME type,1..5 项;数组内部 OR、自动去重。与其他筛选条件 AND,尚无格式元数据的记录不匹配。'),
|
|
23
|
+
filenameContains: z.string().trim().min(1).max(200).optional().describe('原始文件名包含文本,忽略大小写;去除首尾空白后 1..200 字符。百分号、下划线按字面量匹配,不是通配符或正则;无文件名记录不匹配。'),
|
|
24
|
+
minBytes: nonnegativeInt.optional().describe('原始文件字节数下界(包含),非负安全整数;无大小元数据的记录不匹配。'),
|
|
25
|
+
maxBytes: nonnegativeInt.optional().describe('原始文件字节数上界(包含),非负安全整数,不小于 minBytes。'),
|
|
26
|
+
minWidth: positiveInt.optional().describe('宽度像素下界(包含),正安全整数;无尺寸元数据的记录不匹配。'),
|
|
27
|
+
maxWidth: positiveInt.optional().describe('宽度像素上界(包含),正安全整数,不小于 minWidth。'),
|
|
28
|
+
minHeight: positiveInt.optional().describe('高度像素下界(包含),正安全整数;无尺寸元数据的记录不匹配。'),
|
|
29
|
+
maxHeight: positiveInt.optional().describe('高度像素上界(包含),正安全整数,不小于 minHeight。'),
|
|
30
|
+
statuses: z.array(statusSchema).min(1).max(5).default(['uploading', 'ready', 'failed', 'deleting']).transform((v) => [...new Set(v)].sort()).describe('状态集合,1..5 项,内部 OR、自动去重;省略时排除 deleted。uploading 上传中、ready 可读、failed 失败、deleting 清理中、deleted 已删除。'),
|
|
31
|
+
createdBy: z.string().min(1).max(128).optional().describe('创建者 MCP clientId(缺失时为 subject),精确匹配,1..128 字符。仅筛选,不是身份声明,不扩大 images:read 授权范围。'),
|
|
32
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional().describe('原始内容 SHA-256,64 位小写十六进制,精确匹配;可查同内容的多个独立记录,无摘要记录不匹配。'),
|
|
33
|
+
order: z.enum(['asc', 'desc']).default('desc').describe('按创建时间及同方向 UUID 排序:默认 desc(新到旧),asc(旧到新)。翻页时不能改变排序方向。'),
|
|
34
|
+
limit: z.number().int().min(1).max(100).default(20).describe('单页最多条数,1..100,默认 20;可在翻页时调整,不提供全量 total。'),
|
|
35
|
+
cursor: z.string().min(1).max(2048).optional().describe('首请求省略;下一页将上次结果的 nextCursor 原样作为 cursor。保留筛选条件和排序方向,不自行拼接;hasMore=false 时停止。游标不是快照或授权凭证。'),
|
|
36
|
+
}).superRefine((value, ctx) => {
|
|
37
|
+
for (const [lower, upper] of [['minBytes', 'maxBytes'], ['minWidth', 'maxWidth'], ['minHeight', 'maxHeight']]) {
|
|
38
|
+
if (value[lower] !== undefined && value[upper] !== undefined && value[lower] > value[upper])
|
|
39
|
+
ctx.addIssue({ code: 'custom', path: [upper], message: '最大值不得小于最小值' });
|
|
40
|
+
}
|
|
41
|
+
if (value.createdFrom && value.createdTo && value.createdFrom >= value.createdTo)
|
|
42
|
+
ctx.addIssue({ code: 'custom', path: ['createdTo'], message: '结束时间必须晚于开始时间' });
|
|
43
|
+
});
|
|
44
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/domain/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,WAAW,CAAC;AAClC,eAAO,MAAM,gBAAgB,4BAA4B,CAAC"}
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG,QAAQ,CAAC;AAClC,MAAM,CAAC,MAAM,gBAAgB,GAAG,yBAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/mcp/provider.ts"],"names":[],"mappings":"AAmBA,wBAAgB,oBAAoB,8CAqDnC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createInvocationContext } from '@hile/context';
|
|
2
|
+
import { defineMcpProvider, defineMcpResource, defineMcpTool } from '@hile/mcp';
|
|
3
|
+
import { loadModel } from '@hile/model';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { IMAGES_ID } from '../identity.js';
|
|
6
|
+
import { ImageError } from '../domain/image-error.js';
|
|
7
|
+
import { deleteImageSchema, listImageSchema, readImageSchema, uploadImageSchema } from '../domain/schema.js';
|
|
8
|
+
import { imageMetadataSchema, imagePageSchema } from '../domain/output-schema.js';
|
|
9
|
+
import uploadImageModel from '../models/upload-image.model.js';
|
|
10
|
+
import readImageModel from '../models/read-image.model.js';
|
|
11
|
+
import listImageModel from '../models/list-image.model.js';
|
|
12
|
+
import deleteImageModel from '../models/delete-image.model.js';
|
|
13
|
+
const readAccess = { scopes: ['images:read'] };
|
|
14
|
+
const writeAccess = { scopes: ['images:write'] };
|
|
15
|
+
const readonlyHints = { readOnlyHint: true, idempotentHint: true, destructiveHint: false };
|
|
16
|
+
export function createImagesProvider() {
|
|
17
|
+
return defineMcpProvider({
|
|
18
|
+
id: IMAGES_ID,
|
|
19
|
+
displayName: '图片存储',
|
|
20
|
+
tools: {
|
|
21
|
+
'upload-image': defineMcpTool({
|
|
22
|
+
name: 'upload-image', title: '上传图片', inputSchema: uploadImageSchema, outputSchema: imageMetadataSchema,
|
|
23
|
+
description: '通过 Base64(解码后至多 6 MiB)上传调用方持有的 JPEG、PNG、WebP、GIF 或 AVIF 原图;不接受 URL 导入或本地路径,不下载远程图片。对完整文件字节进行标准 Base64 编码,提交前按 base64 参数说明检查字符、填充、长度和大小,不要提交截断内容。总像素最多 40 MP(动画按全部帧计),不接受 SVG、不转换原始内容。只有持久化并提交 ready 后成功返回元数据与公开 URL。响应丢失时,同一调用方用原幂等键和完全相同输入重试,不会重复创建;IDEMPOTENCY_CONFLICT 表示同键输入不同。UPLOAD_NOT_READY 时先通过 images.read-image 或 images.list-image 确认状态:uploading 不另建重复上传,确认 failed 后新上传使用新幂等键,deleted 不会复活。文件名仅用于搜索,不能指定存储路径。',
|
|
24
|
+
access: writeAccess,
|
|
25
|
+
annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false },
|
|
26
|
+
execution: { timeoutMs: 60_000, retry: 'never' },
|
|
27
|
+
}, (input, context) => toolResult(context, 'images:write', async (invocation) => {
|
|
28
|
+
const actor = z.string().min(1).max(128).parse(context.principal?.clientId ?? context.principal?.subject);
|
|
29
|
+
return loadModel(uploadImageModel, { actor, request: uploadImageSchema.parse(input) }, invocation);
|
|
30
|
+
})),
|
|
31
|
+
'list-image': defineMcpTool({
|
|
32
|
+
name: 'list-image', title: '查询图片', inputSchema: listImageSchema, outputSchema: imagePageSchema,
|
|
33
|
+
description: '按创建时间、格式、文件名、字节数、宽高、状态、上传者及 SHA-256 组合查询;不同条件 AND,集合内部 OR。时间范围左闭右开,默认创建时间倒序;每页默认 20、最多 100 条。将返回的 nextCursor 作为 cursor 查询下一页,保持筛选条件及排序;hasMore=false 时停止。默认不返回 deleted,不提供 total;空结果不是错误。分页不是跨请求快照,INVALID_CURSOR 时检查条件,变更条件需从第一页重新查询。',
|
|
34
|
+
access: readAccess, annotations: readonlyHints,
|
|
35
|
+
execution: { timeoutMs: 10_000, retry: 'idempotent-failover' },
|
|
36
|
+
}, (input, context) => toolResult(context, 'images:read', (invocation) => loadModel(listImageModel, listImageSchema.parse(input), invocation))),
|
|
37
|
+
'read-image': defineMcpTool({
|
|
38
|
+
name: 'read-image', title: '读取图片元数据', inputSchema: readImageSchema, outputSchema: imageMetadataSchema,
|
|
39
|
+
description: '按图片 ID 读取元数据、当前状态和公开原图地址。不会返回图片二进制或本地路径;只有 ready 状态有公开 URL。可用于检查上传结果,或在删除前确认 SHA-256 与目标图片。',
|
|
40
|
+
access: readAccess, annotations: readonlyHints,
|
|
41
|
+
execution: { timeoutMs: 10_000, retry: 'idempotent-failover' },
|
|
42
|
+
}, (input, context) => toolResult(context, 'images:read', (invocation) => loadModel(readImageModel, readImageSchema.parse(input), invocation))),
|
|
43
|
+
'delete-image': defineMcpTool({
|
|
44
|
+
name: 'delete-image', title: '删除图片', inputSchema: deleteImageSchema, outputSchema: imageMetadataSchema,
|
|
45
|
+
description: '永久删除本地原图并禁用新的源站读取。先用 images.read-image 读取目标并向用户确认,再提交 id 和 expectedSha256,无需删除幂等键。只允许 ready、deleting 或已 deleted 的记录;HASH_CONFLICT 或 STATE_CONFLICT 时重新读取,不能猜测摘要重试。清理失败或响应丢失后用原 ID 与摘要重试;并发和重复删除安全,成功返回 deleted 元数据且保留首次 deletedAt。此操作不能撤销,也无法清除浏览器或第三方已缓存的副本。',
|
|
46
|
+
access: writeAccess,
|
|
47
|
+
annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: true },
|
|
48
|
+
execution: { timeoutMs: 15_000, retry: 'never' },
|
|
49
|
+
}, (input, context) => toolResult(context, 'images:write', (invocation) => loadModel(deleteImageModel, deleteImageSchema.parse(input), invocation))),
|
|
50
|
+
},
|
|
51
|
+
resources: {
|
|
52
|
+
image: defineMcpResource({
|
|
53
|
+
kind: 'template', name: 'image', uriTemplate: 'images://image/{id}',
|
|
54
|
+
title: '图片元数据', mimeType: 'application/json', access: readAccess,
|
|
55
|
+
description: '由应用按图片 UUID 读取的 JSON 元数据,与 images.read-image 返回字段一致。包含状态、原始文件名、媒体属性、SHA-256、创建者和时间;只有 ready 返回公开 URL。不返回图片二进制、Base64、本地路径或上传幂等键,也不触发上传、删除或恢复。图片展示使用返回的公开 URL;需要 images:read 权限。',
|
|
56
|
+
}, async (variables, context) => {
|
|
57
|
+
assertAccess(context, 'images:read');
|
|
58
|
+
try {
|
|
59
|
+
const request = readImageSchema.parse({ id: variables.id });
|
|
60
|
+
const result = await loadModel(readImageModel, request, createInvocationContext(context.executionContext, context.signal));
|
|
61
|
+
return { contents: [{ uri: `images://image/${request.id}`, mimeType: 'application/json', text: JSON.stringify(result) }] };
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
context.signal.throwIfAborted();
|
|
65
|
+
throw safeError(error);
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function assertAccess(context, scope) {
|
|
72
|
+
context.signal.throwIfAborted();
|
|
73
|
+
const scopes = context.principal?.scopes ?? [];
|
|
74
|
+
if (!scopes.includes('*') && !scopes.includes(scope))
|
|
75
|
+
throw new ImageError('FORBIDDEN', '没有图片操作权限');
|
|
76
|
+
}
|
|
77
|
+
async function toolResult(context, scope, run) {
|
|
78
|
+
assertAccess(context, scope);
|
|
79
|
+
try {
|
|
80
|
+
const result = await run(createInvocationContext(context.executionContext, context.signal));
|
|
81
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result };
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
context.signal.throwIfAborted();
|
|
85
|
+
const safe = safeError(error);
|
|
86
|
+
return { isError: true, content: [{ type: 'text', text: `${safe.code}: ${safe.message}` }] };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function safeError(error) {
|
|
90
|
+
if (error instanceof ImageError)
|
|
91
|
+
return error;
|
|
92
|
+
if (error instanceof z.ZodError)
|
|
93
|
+
return new ImageError('INVALID_INPUT', '图片请求参数无效');
|
|
94
|
+
return new ImageError('UNAVAILABLE', '图片服务暂时不可用,请读取状态后重试');
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/mcp/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.msg.d.ts","sourceRoot":"","sources":["../../../src/messages/public-asset/read.msg.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { loadService } from '@hile/core';
|
|
2
|
+
import { defineMicroMessage } from '@hile/micro';
|
|
3
|
+
import { readImageAsset } from '../../assets/read-image-asset.js';
|
|
4
|
+
import imagesRuntimeService from '../../services/images-runtime.service.js';
|
|
5
|
+
export default defineMicroMessage(async function* ({ data, invocation }) {
|
|
6
|
+
invocation.signal.throwIfAborted();
|
|
7
|
+
const { store, storage } = await loadService(imagesRuntimeService);
|
|
8
|
+
yield* readImageAsset(store, storage, data, invocation.signal);
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=read.msg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.msg.js","sourceRoot":"","sources":["../../../src/messages/public-asset/read.msg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,oBAAoB,MAAM,0CAA0C,CAAC;AAE5E,eAAe,kBAAkB,CAAC,KAAK,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE;IACrE,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,oBAAoB,CAAC,CAAC;IACnE,KAAK,CAAC,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGrE,eAAO,MAAM,gBAAgB,EAAE,SAAS,iBAAiB,EAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,MAAM,CAAC,MAAM,gBAAgB,GAAiC,EAAE,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
declare const _default: import("@hile/model").ModelDefinition<{
|
|
2
|
+
id: string;
|
|
3
|
+
expectedSha256: string;
|
|
4
|
+
}, {
|
|
5
|
+
id: string;
|
|
6
|
+
originalFilename: string | null;
|
|
7
|
+
status: "deleted" | "deleting" | "failed" | "ready" | "uploading";
|
|
8
|
+
url: string | null;
|
|
9
|
+
mediaType: string | null;
|
|
10
|
+
byteSize: number | null;
|
|
11
|
+
width: number | null;
|
|
12
|
+
height: number | null;
|
|
13
|
+
sha256: string | null;
|
|
14
|
+
createdBy: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
readyAt: string | null;
|
|
17
|
+
deletedAt: string | null;
|
|
18
|
+
failureCode: string | null;
|
|
19
|
+
}>;
|
|
20
|
+
export default _default;
|
|
21
|
+
//# sourceMappingURL=delete-image.model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-image.model.d.ts","sourceRoot":"","sources":["../../src/models/delete-image.model.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { loadService } from '@hile/core';
|
|
2
|
+
import { defineModel } from '@hile/model';
|
|
3
|
+
import { ImageError } from '../domain/image-error.js';
|
|
4
|
+
import { deleteImageSchema } from '../domain/schema.js';
|
|
5
|
+
import { imageProjection } from '../presentation/image-projection.js';
|
|
6
|
+
import imagesRuntimeService from '../services/images-runtime.service.js';
|
|
7
|
+
export default defineModel(async (input, invocation) => {
|
|
8
|
+
invocation.signal.throwIfAborted();
|
|
9
|
+
const request = deleteImageSchema.parse(input);
|
|
10
|
+
const { store, storage, origin } = await loadService(imagesRuntimeService);
|
|
11
|
+
invocation.signal.throwIfAborted();
|
|
12
|
+
const row = await store.read(request.id);
|
|
13
|
+
if (row.sha256 !== request.expectedSha256)
|
|
14
|
+
throw new ImageError('HASH_CONFLICT', '图片摘要不匹配,删除未执行');
|
|
15
|
+
if (row.status === 'deleted')
|
|
16
|
+
return imageProjection(row, origin());
|
|
17
|
+
if (row.status !== 'ready' && row.status !== 'deleting')
|
|
18
|
+
throw new ImageError('STATE_CONFLICT', '当前图片不可删除');
|
|
19
|
+
invocation.signal.throwIfAborted();
|
|
20
|
+
if (row.status === 'ready' && !await store.compareAndSet(row.id, 'ready', { status: 'deleting' }, invocation.signal)) {
|
|
21
|
+
const current = await store.read(row.id);
|
|
22
|
+
if (current.status === 'deleted')
|
|
23
|
+
return imageProjection(current, origin());
|
|
24
|
+
if (current.status !== 'deleting')
|
|
25
|
+
throw new ImageError('STATE_CONFLICT', '图片状态已改变,请重新读取');
|
|
26
|
+
}
|
|
27
|
+
// Once origin reads are disabled, finish cleanup even if the caller disconnects.
|
|
28
|
+
if (row.relativePath)
|
|
29
|
+
await storage.remove(row.relativePath);
|
|
30
|
+
await store.compareAndSet(row.id, 'deleting', { status: 'deleted', deletedAt: new Date() });
|
|
31
|
+
const deleted = await store.read(row.id);
|
|
32
|
+
if (deleted.status !== 'deleted')
|
|
33
|
+
throw new ImageError('STATE_CONFLICT', '图片状态已改变,请重新读取');
|
|
34
|
+
return imageProjection(deleted, origin());
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=delete-image.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-image.model.js","sourceRoot":"","sources":["../../src/models/delete-image.model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAyB,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAsB,MAAM,qCAAqC,CAAC;AAC1F,OAAO,oBAAoB,MAAM,uCAAuC,CAAC;AAEzE,eAAe,WAAW,CAAkC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IACtF,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC,oBAAoB,CAAC,CAAC;IAC3E,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACzC,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO,CAAC,cAAc;QAAE,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;IAClG,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;IACpE,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU;QAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;IAC5G,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACrH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzC,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5E,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU;YAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAC7F,CAAC;IACD,iFAAiF;IACjF,IAAI,GAAG,CAAC,YAAY;QAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC7D,MAAM,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5F,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACzC,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;QAAE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAC1F,OAAO,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAC5C,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ImagePage } from '../presentation/image-projection.js';
|
|
2
|
+
declare const _default: import("@hile/model").ModelDefinition<{
|
|
3
|
+
createdFrom?: string | undefined;
|
|
4
|
+
createdTo?: string | undefined;
|
|
5
|
+
mediaTypes?: ("image/avif" | "image/gif" | "image/jpeg" | "image/png" | "image/webp")[] | undefined;
|
|
6
|
+
filenameContains?: string | undefined;
|
|
7
|
+
minBytes?: number | undefined;
|
|
8
|
+
maxBytes?: number | undefined;
|
|
9
|
+
minWidth?: number | undefined;
|
|
10
|
+
maxWidth?: number | undefined;
|
|
11
|
+
minHeight?: number | undefined;
|
|
12
|
+
maxHeight?: number | undefined;
|
|
13
|
+
statuses?: ("deleted" | "deleting" | "failed" | "ready" | "uploading")[] | undefined;
|
|
14
|
+
createdBy?: string | undefined;
|
|
15
|
+
sha256?: string | undefined;
|
|
16
|
+
order?: "asc" | "desc" | undefined;
|
|
17
|
+
limit?: number | undefined;
|
|
18
|
+
cursor?: string | undefined;
|
|
19
|
+
}, ImagePage>;
|
|
20
|
+
export default _default;
|
|
21
|
+
//# sourceMappingURL=list-image.model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-image.model.d.ts","sourceRoot":"","sources":["../../src/models/list-image.model.ts"],"names":[],"mappings":"AAGA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,qCAAqC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { loadService } from '@hile/core';
|
|
2
|
+
import { defineModel } from '@hile/model';
|
|
3
|
+
import { listImageSchema } from '../domain/schema.js';
|
|
4
|
+
import { imageProjection } from '../presentation/image-projection.js';
|
|
5
|
+
import imagesRuntimeService from '../services/images-runtime.service.js';
|
|
6
|
+
export default defineModel(async (input, invocation) => {
|
|
7
|
+
invocation.signal.throwIfAborted();
|
|
8
|
+
const request = listImageSchema.parse(input);
|
|
9
|
+
const { store, origin } = await loadService(imagesRuntimeService);
|
|
10
|
+
invocation.signal.throwIfAborted();
|
|
11
|
+
const result = await store.list(request);
|
|
12
|
+
invocation.signal.throwIfAborted();
|
|
13
|
+
return { ...result, items: result.items.map((row) => imageProjection(row, origin())) };
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=list-image.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-image.model.js","sourceRoot":"","sources":["../../src/models/list-image.model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAyB,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAkB,MAAM,qCAAqC,CAAC;AACtF,OAAO,oBAAoB,MAAM,uCAAuC,CAAC;AAEzE,eAAe,WAAW,CAA8B,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IAClF,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC,oBAAoB,CAAC,CAAC;IAClE,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;AACzF,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare const _default: import("@hile/model").ModelDefinition<{
|
|
2
|
+
id: string;
|
|
3
|
+
}, {
|
|
4
|
+
id: string;
|
|
5
|
+
originalFilename: string | null;
|
|
6
|
+
status: "deleted" | "deleting" | "failed" | "ready" | "uploading";
|
|
7
|
+
url: string | null;
|
|
8
|
+
mediaType: string | null;
|
|
9
|
+
byteSize: number | null;
|
|
10
|
+
width: number | null;
|
|
11
|
+
height: number | null;
|
|
12
|
+
sha256: string | null;
|
|
13
|
+
createdBy: string;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
readyAt: string | null;
|
|
16
|
+
deletedAt: string | null;
|
|
17
|
+
failureCode: string | null;
|
|
18
|
+
}>;
|
|
19
|
+
export default _default;
|
|
20
|
+
//# sourceMappingURL=read-image.model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-image.model.d.ts","sourceRoot":"","sources":["../../src/models/read-image.model.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { loadService } from '@hile/core';
|
|
2
|
+
import { defineModel } from '@hile/model';
|
|
3
|
+
import { readImageSchema } from '../domain/schema.js';
|
|
4
|
+
import { imageProjection } from '../presentation/image-projection.js';
|
|
5
|
+
import imagesRuntimeService from '../services/images-runtime.service.js';
|
|
6
|
+
export default defineModel(async (input, invocation) => {
|
|
7
|
+
invocation.signal.throwIfAborted();
|
|
8
|
+
const request = readImageSchema.parse(input);
|
|
9
|
+
const { store, origin } = await loadService(imagesRuntimeService);
|
|
10
|
+
invocation.signal.throwIfAborted();
|
|
11
|
+
const row = await store.read(request.id);
|
|
12
|
+
invocation.signal.throwIfAborted();
|
|
13
|
+
return imageProjection(row, origin());
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=read-image.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-image.model.js","sourceRoot":"","sources":["../../src/models/read-image.model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAuB,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAsB,MAAM,qCAAqC,CAAC;AAC1F,OAAO,oBAAoB,MAAM,uCAAuC,CAAC;AAEzE,eAAe,WAAW,CAAgC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IACpF,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC,oBAAoB,CAAC,CAAC;IAClE,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACzC,UAAU,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;IACnC,OAAO,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;AACxC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type UploadImageInput } from '../domain/schema.js';
|
|
2
|
+
declare const _default: import("@hile/model").ModelDefinition<{
|
|
3
|
+
actor: string;
|
|
4
|
+
request: UploadImageInput;
|
|
5
|
+
}, {
|
|
6
|
+
id: string;
|
|
7
|
+
originalFilename: string | null;
|
|
8
|
+
status: "deleted" | "deleting" | "failed" | "ready" | "uploading";
|
|
9
|
+
url: string | null;
|
|
10
|
+
mediaType: string | null;
|
|
11
|
+
byteSize: number | null;
|
|
12
|
+
width: number | null;
|
|
13
|
+
height: number | null;
|
|
14
|
+
sha256: string | null;
|
|
15
|
+
createdBy: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
readyAt: string | null;
|
|
18
|
+
deletedAt: string | null;
|
|
19
|
+
failureCode: string | null;
|
|
20
|
+
}>;
|
|
21
|
+
export default _default;
|
|
22
|
+
//# sourceMappingURL=upload-image.model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-image.model.d.ts","sourceRoot":"","sources":["../../src/models/upload-image.model.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;;WAO3C,MAAM;aAAW,gBAAgB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { loadService } from '@hile/core';
|
|
3
|
+
import { defineModel } from '@hile/model';
|
|
4
|
+
import { ImageError } from '../domain/image-error.js';
|
|
5
|
+
import { uploadImageSchema } from '../domain/schema.js';
|
|
6
|
+
import { inspectImage } from '../storage/inspect-image.js';
|
|
7
|
+
import { imageRelativePath } from '../storage/image-path.js';
|
|
8
|
+
import { readUploadBytes } from '../storage/read-upload-bytes.js';
|
|
9
|
+
import { imageProjection } from '../presentation/image-projection.js';
|
|
10
|
+
import imagesRuntimeService from '../services/images-runtime.service.js';
|
|
11
|
+
export default defineModel(async (input, invocation) => {
|
|
12
|
+
invocation.signal.throwIfAborted();
|
|
13
|
+
const request = uploadImageSchema.parse(input.request);
|
|
14
|
+
const { store, storage, origin } = await loadService(imagesRuntimeService);
|
|
15
|
+
const signal = AbortSignal.any([invocation.signal, AbortSignal.timeout(45_000)]);
|
|
16
|
+
signal.throwIfAborted();
|
|
17
|
+
const hash = createHash('sha256').update(JSON.stringify(request)).digest('hex');
|
|
18
|
+
const reserved = await store.reserve(input.actor, request.idempotencyKey, hash, request.filename ?? null);
|
|
19
|
+
if (!reserved.created) {
|
|
20
|
+
if (reserved.row.status === 'ready')
|
|
21
|
+
return imageProjection(reserved.row, origin());
|
|
22
|
+
throw new ImageError('UPLOAD_NOT_READY', '该幂等请求未完成或已失效,请读取图片状态;失败后使用新幂等键');
|
|
23
|
+
}
|
|
24
|
+
const id = reserved.row.id;
|
|
25
|
+
try {
|
|
26
|
+
const bytes = await readUploadBytes(request, signal);
|
|
27
|
+
signal.throwIfAborted();
|
|
28
|
+
const metadata = await inspectImage(bytes);
|
|
29
|
+
signal.throwIfAborted();
|
|
30
|
+
const relativePath = imageRelativePath(id, metadata.extension, reserved.row.createdAt);
|
|
31
|
+
await store.transition(id, 'uploading', { ...metadata, relativePath }, signal);
|
|
32
|
+
await storage.publish(relativePath, bytes);
|
|
33
|
+
signal.throwIfAborted();
|
|
34
|
+
await store.transition(id, 'uploading', { status: 'ready', readyAt: new Date() }, signal);
|
|
35
|
+
return imageProjection(await store.read(id), origin());
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
// Re-read before cleanup: a lost DB reply may conceal a successful ready commit.
|
|
39
|
+
const current = await store.read(id);
|
|
40
|
+
if (current.status === 'ready')
|
|
41
|
+
return imageProjection(current, origin());
|
|
42
|
+
if (current.status === 'uploading') {
|
|
43
|
+
await store.transition(id, 'uploading', {
|
|
44
|
+
status: 'failed', failureCode: error instanceof ImageError ? error.code : 'UPLOAD_FAILED',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (current.relativePath)
|
|
48
|
+
await storage.remove(current.relativePath);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=upload-image.model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-image.model.js","sourceRoot":"","sources":["../../src/models/upload-image.model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EntitySchema } from 'typeorm';
|
|
2
|
+
export interface ImageRow {
|
|
3
|
+
id: string;
|
|
4
|
+
createdBy: string;
|
|
5
|
+
uploadIdempotencyKey: string;
|
|
6
|
+
uploadRequestFingerprint: string;
|
|
7
|
+
status: 'uploading' | 'ready' | 'failed' | 'deleting' | 'deleted';
|
|
8
|
+
originalFilename: string | null;
|
|
9
|
+
mediaType: string | null;
|
|
10
|
+
extension: string | null;
|
|
11
|
+
byteSize: number | null;
|
|
12
|
+
width: number | null;
|
|
13
|
+
height: number | null;
|
|
14
|
+
sha256: string | null;
|
|
15
|
+
relativePath: string | null;
|
|
16
|
+
failureCode: string | null;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
readyAt: Date | null;
|
|
19
|
+
deletedAt: Date | null;
|
|
20
|
+
}
|
|
21
|
+
export declare const ImageEntity: EntitySchema<ImageRow>;
|
|
22
|
+
//# sourceMappingURL=entities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../src/persistence/entities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,oBAAoB,EAAE,MAAM,CAAC;IAAC,wBAAwB,EAAE,MAAM,CAAC;IAC9F,MAAM,EAAE,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAClE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACpF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5F,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACxD,SAAS,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;CAC/D;AACD,eAAO,MAAM,WAAW,wBAiBtB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EntitySchema } from 'typeorm';
|
|
2
|
+
export const ImageEntity = new EntitySchema({
|
|
3
|
+
name: 'Image', tableName: 'images',
|
|
4
|
+
columns: {
|
|
5
|
+
id: { type: 'uuid', primary: true }, createdBy: { name: 'created_by', type: 'varchar', length: 128 },
|
|
6
|
+
uploadIdempotencyKey: { name: 'upload_idempotency_key', type: 'varchar', length: 128 },
|
|
7
|
+
uploadRequestFingerprint: { name: 'upload_request_fingerprint', type: 'varchar', length: 64 },
|
|
8
|
+
status: { type: 'varchar', length: 16 }, originalFilename: { name: 'original_filename', type: 'varchar', length: 200, nullable: true },
|
|
9
|
+
mediaType: { name: 'media_type', type: 'varchar', length: 64, nullable: true }, extension: { type: 'varchar', length: 8, nullable: true },
|
|
10
|
+
byteSize: { name: 'byte_size', type: 'integer', nullable: true }, width: { type: 'integer', nullable: true }, height: { type: 'integer', nullable: true },
|
|
11
|
+
sha256: { type: 'varchar', length: 64, nullable: true }, relativePath: { name: 'relative_path', type: 'varchar', length: 128, nullable: true },
|
|
12
|
+
failureCode: { name: 'failure_code', type: 'varchar', length: 64, nullable: true },
|
|
13
|
+
createdAt: { name: 'created_at', type: 'timestamptz', precision: 3, createDate: true },
|
|
14
|
+
readyAt: { name: 'ready_at', type: 'timestamptz', precision: 3, nullable: true },
|
|
15
|
+
deletedAt: { name: 'deleted_at', type: 'timestamptz', precision: 3, nullable: true },
|
|
16
|
+
},
|
|
17
|
+
uniques: [{ name: 'images_upload_actor_key', columns: ['createdBy', 'uploadIdempotencyKey'] }],
|
|
18
|
+
indices: [{ columns: ['createdAt', 'id'] }, { columns: ['status', 'createdAt', 'id'] }, { columns: ['createdBy', 'createdAt', 'id'] }, { columns: ['sha256'] }],
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.js","sourceRoot":"","sources":["../../src/persistence/entities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAUvC,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,YAAY,CAAW;IACpD,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ;IAClC,OAAO,EAAE;QACP,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE;QACpG,oBAAoB,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE;QACtF,wBAAwB,EAAE,EAAE,IAAI,EAAE,4BAA4B,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE;QAC7F,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE;QACtI,SAAS,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;QACzI,QAAQ,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;QACzJ,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE;QAC9I,WAAW,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;QAClF,SAAS,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE;QACtF,OAAO,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;QAChF,SAAS,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;KACrF;IACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,CAAC,WAAW,EAAE,sBAAsB,CAAC,EAAE,CAAC;IAC9F,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;CAChK,CAAC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type FindManyOptions } from 'typeorm';
|
|
2
|
+
import type { ListImageInput } from '../domain/schema.js';
|
|
3
|
+
import type { ImageRow } from './entities.js';
|
|
4
|
+
export declare function imageQueryOptions(query: ListImageInput): FindManyOptions<ImageRow>;
|
|
5
|
+
//# sourceMappingURL=image-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-query.d.ts","sourceRoot":"","sources":["../../src/persistence/image-query.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CAAC,QAAQ,CAAC,CAyBlF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { And, Equal, ILike, In, LessThan, LessThanOrEqual, MoreThan, MoreThanOrEqual, } from 'typeorm';
|
|
2
|
+
import { decodeCursor } from '../domain/list-cursor.js';
|
|
3
|
+
export function imageQueryOptions(query) {
|
|
4
|
+
const filter = { status: In(query.statuses) };
|
|
5
|
+
if (query.createdBy)
|
|
6
|
+
filter.createdBy = Equal(query.createdBy);
|
|
7
|
+
if (query.sha256)
|
|
8
|
+
filter.sha256 = Equal(query.sha256);
|
|
9
|
+
if (query.mediaTypes)
|
|
10
|
+
filter.mediaType = In(query.mediaTypes);
|
|
11
|
+
if (query.filenameContains) {
|
|
12
|
+
filter.originalFilename = ILike(`%${query.filenameContains.replace(/[\\%_]/g, '\\$&')}%`);
|
|
13
|
+
}
|
|
14
|
+
const times = [];
|
|
15
|
+
if (query.createdFrom)
|
|
16
|
+
times.push(MoreThanOrEqual(new Date(query.createdFrom)));
|
|
17
|
+
if (query.createdTo)
|
|
18
|
+
times.push(LessThan(new Date(query.createdTo)));
|
|
19
|
+
if (times.length)
|
|
20
|
+
filter.createdAt = And(...times);
|
|
21
|
+
addBounds(filter, 'byteSize', query.minBytes, query.maxBytes);
|
|
22
|
+
addBounds(filter, 'width', query.minWidth, query.maxWidth);
|
|
23
|
+
addBounds(filter, 'height', query.minHeight, query.maxHeight);
|
|
24
|
+
const cursor = decodeCursor(query);
|
|
25
|
+
const compare = query.order === 'asc' ? MoreThan : LessThan;
|
|
26
|
+
return {
|
|
27
|
+
where: cursor ? [
|
|
28
|
+
{ ...filter, createdAt: And(...times, compare(cursor.createdAt)) },
|
|
29
|
+
{ ...filter, createdAt: And(...times, Equal(cursor.createdAt)), id: compare(cursor.id) },
|
|
30
|
+
] : filter,
|
|
31
|
+
order: { createdAt: query.order, id: query.order },
|
|
32
|
+
take: query.limit + 1,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function addBounds(filter, column, min, max) {
|
|
36
|
+
const bounds = [];
|
|
37
|
+
if (min !== undefined)
|
|
38
|
+
bounds.push(MoreThanOrEqual(min));
|
|
39
|
+
if (max !== undefined)
|
|
40
|
+
bounds.push(LessThanOrEqual(max));
|
|
41
|
+
if (bounds.length)
|
|
42
|
+
filter[column] = And(...bounds);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=image-query.js.map
|