@yefengr/pi-reach 0.0.4 → 0.0.5
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/attachments/reservation.fixture.d.ts +1 -0
- package/dist/attachments/reservation.fixture.js +25 -0
- package/dist/attachments/reservation.fixture.js.map +1 -0
- package/dist/attachments/reservations.d.ts +21 -0
- package/dist/attachments/reservations.js +189 -0
- package/dist/attachments/reservations.js.map +1 -0
- package/dist/attachments/safe-files.d.ts +7 -0
- package/dist/attachments/safe-files.js +66 -0
- package/dist/attachments/safe-files.js.map +1 -0
- package/dist/attachments/store.d.ts +55 -0
- package/dist/attachments/store.js +518 -0
- package/dist/attachments/store.js.map +1 -0
- package/dist/attachments/types.d.ts +48 -0
- package/dist/attachments/types.js +20 -0
- package/dist/attachments/types.js.map +1 -0
- package/dist/index.js +30 -24
- package/dist/index.js.map +1 -1
- package/dist/runtime/attachment_binding.d.ts +17 -0
- package/dist/runtime/attachment_binding.js +70 -0
- package/dist/runtime/attachment_binding.js.map +1 -0
- package/dist/runtime/create_owner_binding.d.ts +27 -0
- package/dist/runtime/create_owner_binding.js +22 -0
- package/dist/runtime/create_owner_binding.js.map +1 -0
- package/dist/timeline/attachment_content.d.ts +21 -0
- package/dist/timeline/attachment_content.js +83 -0
- package/dist/timeline/attachment_content.js.map +1 -0
- package/dist/timeline/attachment_delivery.d.ts +65 -0
- package/dist/timeline/attachment_delivery.js +150 -0
- package/dist/timeline/attachment_delivery.js.map +1 -0
- package/dist/timeline/runtime.d.ts +4 -2
- package/dist/timeline/runtime.js +33 -34
- package/dist/timeline/runtime.js.map +1 -1
- package/dist/timeline/user_delivery.d.ts +7 -0
- package/dist/timeline/user_delivery.js +21 -12
- package/dist/timeline/user_delivery.js.map +1 -1
- package/dist/timeline/user_delivery_binding.d.ts +6 -1
- package/dist/timeline/user_delivery_binding.js +17 -1
- package/dist/timeline/user_delivery_binding.js.map +1 -1
- package/dist/timeline/v2_service.d.ts +6 -1
- package/dist/timeline/v2_service.js +16 -8
- package/dist/timeline/v2_service.js.map +1 -1
- package/dist/vendor/protocol/session/attachments.d.ts +78 -0
- package/dist/vendor/protocol/session/attachments.js +150 -0
- package/dist/vendor/protocol/session/frames.d.ts +500 -0
- package/dist/vendor/protocol/session/frames.js +37 -4
- package/dist/vendor/protocol/session/index.d.ts +1 -0
- package/dist/vendor/protocol/session/index.js +1 -0
- package/dist/vendor/protocol/session/schema.d.ts +2 -2
- package/dist/vendor/protocol/session/schema.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AttachmentStore, AttachmentStoreError } from "./store.js";
|
|
3
|
+
// 仅由隔离测试的 IPC 子进程调用;不使用默认附件目录。
|
|
4
|
+
const rootDir = process.argv[2];
|
|
5
|
+
const available = BigInt(process.argv[3]);
|
|
6
|
+
const allocationUnitBytes = Number(process.argv[4] ?? 1);
|
|
7
|
+
const contents = "x".repeat(Number(process.argv[5] ?? 4));
|
|
8
|
+
if (!rootDir || !process.send)
|
|
9
|
+
throw new Error("Isolated fixture requires root and IPC");
|
|
10
|
+
const store = new AttachmentStore({
|
|
11
|
+
rootDir, runtimeId: `fixture-${process.pid}`, minFreeBytes: 2,
|
|
12
|
+
testHooks: { availableBytes: () => available, allocationUnitBytes },
|
|
13
|
+
});
|
|
14
|
+
const scope = { ownerId: "fixture-owner", sessionId: "session", uploadScope: store.scopeFor("session") };
|
|
15
|
+
process.on("message", (message) => {
|
|
16
|
+
if (message !== "begin")
|
|
17
|
+
return;
|
|
18
|
+
void store.begin(scope, {
|
|
19
|
+
uploadId: "fixture-upload", fileName: "fixture.bin", mimeType: "application/octet-stream", byteLength: contents.length,
|
|
20
|
+
sha256: createHash("sha256").update(contents).digest("hex"),
|
|
21
|
+
}).then(() => process.send?.({ type: "result", accepted: true }), (error) => process.send?.({ type: "result", accepted: false,
|
|
22
|
+
code: error instanceof AttachmentStoreError ? error.code : "unexpected" }));
|
|
23
|
+
});
|
|
24
|
+
process.send({ type: "ready" });
|
|
25
|
+
//# sourceMappingURL=reservation.fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reservation.fixture.js","sourceRoot":"","sources":["../../src/attachments/reservation.fixture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEnE,+BAA+B;AAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAChC,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,MAAM,mBAAmB,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACzD,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC1D,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI;IAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;AACzF,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC;IAChC,OAAO,EAAE,SAAS,EAAE,WAAW,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,CAAC;IAC7D,SAAS,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,mBAAmB,EAAE;CACpE,CAAC,CAAC;AACH,MAAM,KAAK,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;AACzG,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAgB,EAAE,EAAE;IACzC,IAAI,OAAO,KAAK,OAAO;QAAE,OAAO;IAChC,KAAK,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE;QACtB,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,0BAA0B,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM;QACtH,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;KAC5D,CAAC,CAAC,IAAI,CACL,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EACxD,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK;QAClE,IAAI,EAAE,KAAK,YAAY,oBAAoB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAC7E,CAAC;AACJ,CAAC,CAAC,CAAC;AACH,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type AttachmentStoreTestHooks } from "./types.js";
|
|
2
|
+
export declare class DiskReservation {
|
|
3
|
+
private readonly root;
|
|
4
|
+
private readonly byteLength;
|
|
5
|
+
private readonly floor;
|
|
6
|
+
private readonly hooks;
|
|
7
|
+
private readonly directory;
|
|
8
|
+
private readonly path;
|
|
9
|
+
private readonly record;
|
|
10
|
+
private published;
|
|
11
|
+
private allocationUnit;
|
|
12
|
+
constructor(root: string, runtimeId: string, uploadId: string, byteLength: number, floor: bigint, hooks?: AttachmentStoreTestHooks);
|
|
13
|
+
publish(): Promise<void>;
|
|
14
|
+
private replace;
|
|
15
|
+
private assertOwned;
|
|
16
|
+
private totalRemaining;
|
|
17
|
+
check(): Promise<void>;
|
|
18
|
+
private available;
|
|
19
|
+
reduce(remainingBytes: number): Promise<void>;
|
|
20
|
+
remove(): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { link, opendir, rename, statfs, unlink } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { ATTACHMENT_MAX_FILE_BYTES } from "../vendor/protocol/session/index.js";
|
|
5
|
+
import { assertDirectories, assertRegularFile, createPrivateFile, ensurePrivateDirectory, readPrivateFile } from "./safe-files.js";
|
|
6
|
+
import { AttachmentStoreError, nodeErrorHasCode } from "./types.js";
|
|
7
|
+
const RECORD_MAX_BYTES = 4096;
|
|
8
|
+
const RECORD_MAX_COUNT = 4096;
|
|
9
|
+
const CHECK_ATTEMPTS = 3;
|
|
10
|
+
const CHECK_RETRY_MS = 5;
|
|
11
|
+
const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
12
|
+
const RECORD_NAME = new RegExp(`^${UUID}\\.json$`);
|
|
13
|
+
const PENDING_NAME = new RegExp(`^${UUID}\\.pending$`);
|
|
14
|
+
function roundAllocation(bytes, unit) {
|
|
15
|
+
const rounded = ((BigInt(bytes) + unit - 1n) / unit) * unit;
|
|
16
|
+
if (rounded > BigInt(Number.MAX_SAFE_INTEGER))
|
|
17
|
+
throw new AttachmentStoreError("io_error");
|
|
18
|
+
return Number(rounded);
|
|
19
|
+
}
|
|
20
|
+
function parseRecord(raw, unit) {
|
|
21
|
+
let data;
|
|
22
|
+
try {
|
|
23
|
+
data = JSON.parse(raw);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new AttachmentStoreError("io_error");
|
|
27
|
+
}
|
|
28
|
+
if (typeof data !== "object" || data === null)
|
|
29
|
+
throw new AttachmentStoreError("io_error");
|
|
30
|
+
const item = data;
|
|
31
|
+
if (Object.keys(item).sort().join(",") !== "pid,remainingBytes,runtimeId,uploadId" ||
|
|
32
|
+
!Number.isSafeInteger(item.pid) || (item.pid ?? 0) <= 0 ||
|
|
33
|
+
typeof item.runtimeId !== "string" || item.runtimeId.length < 1 || item.runtimeId.length > 256 ||
|
|
34
|
+
typeof item.uploadId !== "string" || item.uploadId.length < 1 || item.uploadId.length > 256 ||
|
|
35
|
+
!Number.isSafeInteger(item.remainingBytes) || (item.remainingBytes ?? -1) < 0 ||
|
|
36
|
+
(item.remainingBytes ?? Infinity) > roundAllocation(ATTACHMENT_MAX_FILE_BYTES, unit) ||
|
|
37
|
+
BigInt(item.remainingBytes ?? 0) % unit !== 0n) {
|
|
38
|
+
throw new AttachmentStoreError("io_error");
|
|
39
|
+
}
|
|
40
|
+
return item;
|
|
41
|
+
}
|
|
42
|
+
function provenDead(pid) {
|
|
43
|
+
try {
|
|
44
|
+
process.kill(pid, 0);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return nodeErrorHasCode(error, "ESRCH");
|
|
48
|
+
}
|
|
49
|
+
// PID 复用和 EPERM 都保守占预算,不按年龄删除记录或原件。
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
export class DiskReservation {
|
|
53
|
+
root;
|
|
54
|
+
byteLength;
|
|
55
|
+
floor;
|
|
56
|
+
hooks;
|
|
57
|
+
directory;
|
|
58
|
+
path;
|
|
59
|
+
record;
|
|
60
|
+
published = false;
|
|
61
|
+
allocationUnit = 1n;
|
|
62
|
+
constructor(root, runtimeId, uploadId, byteLength, floor, hooks = {}) {
|
|
63
|
+
this.root = root;
|
|
64
|
+
this.byteLength = byteLength;
|
|
65
|
+
this.floor = floor;
|
|
66
|
+
this.hooks = hooks;
|
|
67
|
+
this.directory = join(root, ".reservations");
|
|
68
|
+
this.path = join(this.directory, `${randomUUID()}.json`);
|
|
69
|
+
this.record = { pid: process.pid, runtimeId, uploadId, remainingBytes: byteLength };
|
|
70
|
+
}
|
|
71
|
+
async publish() {
|
|
72
|
+
ensurePrivateDirectory(this.directory);
|
|
73
|
+
this.allocationUnit = this.hooks.availableBytes ? BigInt(this.hooks.allocationUnitBytes ?? 1) :
|
|
74
|
+
(await statfs(this.root, { bigint: true })).bsize;
|
|
75
|
+
if (this.allocationUnit <= 0n)
|
|
76
|
+
throw new AttachmentStoreError("io_error");
|
|
77
|
+
this.record.remainingBytes = roundAllocation(this.record.remainingBytes, this.allocationUnit);
|
|
78
|
+
await this.replace(this.record, true);
|
|
79
|
+
this.published = true;
|
|
80
|
+
try {
|
|
81
|
+
await this.check();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
await this.remove();
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async replace(record, initial) {
|
|
89
|
+
assertDirectories(this.directory);
|
|
90
|
+
const pending = join(this.directory, `${randomUUID()}.pending`);
|
|
91
|
+
const handle = await createPrivateFile(pending);
|
|
92
|
+
try {
|
|
93
|
+
await handle.writeFile(JSON.stringify(record));
|
|
94
|
+
await handle.sync();
|
|
95
|
+
await handle.close();
|
|
96
|
+
if (initial) {
|
|
97
|
+
// link 为独占发布;pending 从不计入 credits,也从不准许写原件。
|
|
98
|
+
await link(pending, this.path);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
await this.assertOwned();
|
|
102
|
+
await rename(pending, this.path);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await handle.close().catch(() => undefined);
|
|
107
|
+
await unlink(pending).catch((error) => {
|
|
108
|
+
if (!nodeErrorHasCode(error, "ENOENT"))
|
|
109
|
+
throw error;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async assertOwned() {
|
|
114
|
+
if (!this.published)
|
|
115
|
+
throw new AttachmentStoreError("invalid_upload");
|
|
116
|
+
assertDirectories(this.directory);
|
|
117
|
+
const current = parseRecord(await readPrivateFile(this.path, RECORD_MAX_BYTES), this.allocationUnit);
|
|
118
|
+
if (current.pid !== this.record.pid || current.runtimeId !== this.record.runtimeId ||
|
|
119
|
+
current.uploadId !== this.record.uploadId || current.remainingBytes !== this.record.remainingBytes) {
|
|
120
|
+
throw new AttachmentStoreError("invalid_upload");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async totalRemaining() {
|
|
124
|
+
let total = 0n;
|
|
125
|
+
let activeCount = 0;
|
|
126
|
+
// 流式枚举,不把历史死亡账本的总数当作活跃上传配额。
|
|
127
|
+
for await (const entry of await opendir(this.directory)) {
|
|
128
|
+
const name = entry.name;
|
|
129
|
+
const path = join(this.directory, name);
|
|
130
|
+
try {
|
|
131
|
+
assertRegularFile(path);
|
|
132
|
+
if (PENDING_NAME.test(name))
|
|
133
|
+
continue;
|
|
134
|
+
if (!RECORD_NAME.test(name))
|
|
135
|
+
throw new AttachmentStoreError("io_error");
|
|
136
|
+
const record = parseRecord(await readPrivateFile(path, RECORD_MAX_BYTES), this.allocationUnit);
|
|
137
|
+
if (provenDead(record.pid))
|
|
138
|
+
continue;
|
|
139
|
+
if (++activeCount > RECORD_MAX_COUNT)
|
|
140
|
+
throw new AttachmentStoreError("no_space", true);
|
|
141
|
+
total += BigInt(record.remainingBytes);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
// 删除只发生在该 reservation 已不再可能写入之后。
|
|
145
|
+
if (!nodeErrorHasCode(error, "ENOENT"))
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return total;
|
|
150
|
+
}
|
|
151
|
+
async check() {
|
|
152
|
+
for (let attempt = 0; attempt < CHECK_ATTEMPTS; attempt++) {
|
|
153
|
+
await this.assertOwned();
|
|
154
|
+
const remaining = await this.totalRemaining();
|
|
155
|
+
// 必须先扫描 credits,再读取空间。旧 credits 与新空间只会高估需求。
|
|
156
|
+
const available = this.hooks.availableBytes ? await this.hooks.availableBytes() : await this.available();
|
|
157
|
+
if (remaining + this.floor <= available)
|
|
158
|
+
return;
|
|
159
|
+
if (attempt + 1 < CHECK_ATTEMPTS)
|
|
160
|
+
await new Promise((resolve) => setTimeout(resolve, CHECK_RETRY_MS));
|
|
161
|
+
}
|
|
162
|
+
throw new AttachmentStoreError("no_space", true);
|
|
163
|
+
}
|
|
164
|
+
async available() {
|
|
165
|
+
const stats = await statfs(this.root, { bigint: true });
|
|
166
|
+
return stats.bavail * stats.bsize;
|
|
167
|
+
}
|
|
168
|
+
async reduce(remainingBytes) {
|
|
169
|
+
if (!Number.isSafeInteger(remainingBytes) || remainingBytes < 0 || remainingBytes > this.byteLength) {
|
|
170
|
+
throw new AttachmentStoreError("invalid_upload");
|
|
171
|
+
}
|
|
172
|
+
// 已落盘的尾块可承载后续字节,不重复预留同一个分配单元。
|
|
173
|
+
const credits = roundAllocation(this.byteLength, this.allocationUnit) -
|
|
174
|
+
roundAllocation(this.byteLength - remainingBytes, this.allocationUnit);
|
|
175
|
+
if (credits > this.record.remainingBytes)
|
|
176
|
+
throw new AttachmentStoreError("invalid_upload");
|
|
177
|
+
const next = { ...this.record, remainingBytes: credits };
|
|
178
|
+
await this.replace(next, false);
|
|
179
|
+
this.record.remainingBytes = credits;
|
|
180
|
+
}
|
|
181
|
+
async remove() {
|
|
182
|
+
if (!this.published)
|
|
183
|
+
return;
|
|
184
|
+
await this.assertOwned();
|
|
185
|
+
await unlink(this.path);
|
|
186
|
+
this.published = false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=reservations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reservations.js","sourceRoot":"","sources":["../../src/attachments/reservations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Stats } from "node:fs";
|
|
2
|
+
import { type FileHandle } from "node:fs/promises";
|
|
3
|
+
export declare function assertDirectories(directory: string): void;
|
|
4
|
+
export declare function ensurePrivateDirectory(directory: string): void;
|
|
5
|
+
export declare function assertRegularFile(path: string): Stats;
|
|
6
|
+
export declare function createPrivateFile(path: string): Promise<FileHandle>;
|
|
7
|
+
export declare function readPrivateFile(path: string, maximumBytes: number): Promise<string>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { constants, chmodSync, lstatSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
import { parse, resolve, sep } from "node:path";
|
|
4
|
+
import { AttachmentStoreError, nodeErrorHasCode } from "./types.js";
|
|
5
|
+
export function assertDirectories(directory) {
|
|
6
|
+
const absolute = resolve(directory);
|
|
7
|
+
let current = parse(absolute).root;
|
|
8
|
+
for (const part of absolute.slice(current.length).split(sep).filter(Boolean)) {
|
|
9
|
+
current = resolve(current, part);
|
|
10
|
+
const stat = lstatSync(current);
|
|
11
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
12
|
+
throw new AttachmentStoreError("invalid_upload");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function ensurePrivateDirectory(directory) {
|
|
16
|
+
const absolute = resolve(directory);
|
|
17
|
+
let current = parse(absolute).root;
|
|
18
|
+
for (const part of absolute.slice(current.length).split(sep).filter(Boolean)) {
|
|
19
|
+
current = resolve(current, part);
|
|
20
|
+
try {
|
|
21
|
+
mkdirSync(current, { mode: 0o700 });
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (!nodeErrorHasCode(error, "EEXIST"))
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
const stat = lstatSync(current);
|
|
28
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
29
|
+
throw new AttachmentStoreError("invalid_upload");
|
|
30
|
+
}
|
|
31
|
+
chmodSync(absolute, 0o700);
|
|
32
|
+
}
|
|
33
|
+
export function assertRegularFile(path) {
|
|
34
|
+
const stat = lstatSync(path);
|
|
35
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
36
|
+
throw new AttachmentStoreError("invalid_upload");
|
|
37
|
+
return stat;
|
|
38
|
+
}
|
|
39
|
+
export async function createPrivateFile(path) {
|
|
40
|
+
return open(path, constants.O_CREAT | constants.O_EXCL | constants.O_RDWR | constants.O_NOFOLLOW, 0o600);
|
|
41
|
+
}
|
|
42
|
+
export async function readPrivateFile(path, maximumBytes) {
|
|
43
|
+
assertRegularFile(path);
|
|
44
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
45
|
+
try {
|
|
46
|
+
const stat = await handle.stat();
|
|
47
|
+
if (!stat.isFile() || stat.size > maximumBytes)
|
|
48
|
+
throw new AttachmentStoreError("io_error");
|
|
49
|
+
// 限长读取,不能在 stat 后使用无界 readFile。
|
|
50
|
+
const buffer = Buffer.alloc(maximumBytes + 1);
|
|
51
|
+
let length = 0;
|
|
52
|
+
while (length < buffer.length) {
|
|
53
|
+
const read = await handle.read(buffer, length, buffer.length - length, length);
|
|
54
|
+
if (read.bytesRead === 0)
|
|
55
|
+
break;
|
|
56
|
+
length += read.bytesRead;
|
|
57
|
+
}
|
|
58
|
+
if (length > maximumBytes)
|
|
59
|
+
throw new AttachmentStoreError("io_error");
|
|
60
|
+
return buffer.subarray(0, length).toString("utf8");
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await handle.close();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=safe-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-files.js","sourceRoot":"","sources":["../../src/attachments/safe-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type AttachmentDescriptor } from "../vendor/protocol/session/index.js";
|
|
2
|
+
import { type AttachmentBeginInput, type AttachmentScope, type AttachmentStoreOptions, type AttachmentUploadState } from "./types.js";
|
|
3
|
+
export { AttachmentStoreError } from "./types.js";
|
|
4
|
+
export type { AttachmentBeginInput, AttachmentScope, AttachmentStoreOptions, AttachmentStoreTestHooks, AttachmentUploadState, } from "./types.js";
|
|
5
|
+
export declare class AttachmentStore {
|
|
6
|
+
private readonly options;
|
|
7
|
+
private readonly root;
|
|
8
|
+
private readonly floor;
|
|
9
|
+
private uploads;
|
|
10
|
+
private byAttachment;
|
|
11
|
+
private readonly resources;
|
|
12
|
+
private readonly metadataLimit;
|
|
13
|
+
private readonly ownerPending;
|
|
14
|
+
private readonly cleanups;
|
|
15
|
+
private sessionId?;
|
|
16
|
+
private lease?;
|
|
17
|
+
private disposed;
|
|
18
|
+
private disposing?;
|
|
19
|
+
constructor(options: AttachmentStoreOptions);
|
|
20
|
+
scopeFor(sessionId: string): string;
|
|
21
|
+
resetScope(sessionId: string): void;
|
|
22
|
+
private validScope;
|
|
23
|
+
private checkScope;
|
|
24
|
+
private key;
|
|
25
|
+
private newUpload;
|
|
26
|
+
private checkMetadataBudget;
|
|
27
|
+
/** 仅测试观察数量,不包含路径、元信息或原始错误。 */
|
|
28
|
+
debugCounts(): {
|
|
29
|
+
records: number;
|
|
30
|
+
attachments: number;
|
|
31
|
+
resources: number;
|
|
32
|
+
cleanups: number;
|
|
33
|
+
pending: number;
|
|
34
|
+
};
|
|
35
|
+
private state;
|
|
36
|
+
private enqueue;
|
|
37
|
+
private get;
|
|
38
|
+
private assertFile;
|
|
39
|
+
private checkedState;
|
|
40
|
+
begin(scope: AttachmentScope, input: AttachmentBeginInput): Promise<AttachmentUploadState>;
|
|
41
|
+
write(scope: AttachmentScope, uploadId: string, offset: number, bytes: Uint8Array): Promise<AttachmentUploadState>;
|
|
42
|
+
private compareChunk;
|
|
43
|
+
finish(scope: AttachmentScope, uploadId: string): Promise<AttachmentUploadState>;
|
|
44
|
+
status(scope: AttachmentScope, uploadId: string): Promise<AttachmentUploadState>;
|
|
45
|
+
cancel(scope: AttachmentScope, uploadId: string): Promise<AttachmentUploadState>;
|
|
46
|
+
discard(scope: AttachmentScope, attachmentId: string): Promise<"cancelled" | "retained">;
|
|
47
|
+
resolve(scope: AttachmentScope, attachmentIds: readonly string[]): ReadonlyArray<{
|
|
48
|
+
descriptor: AttachmentDescriptor;
|
|
49
|
+
path: string;
|
|
50
|
+
}>;
|
|
51
|
+
retain(scope: AttachmentScope, attachmentIds: readonly string[]): void;
|
|
52
|
+
private cleanup;
|
|
53
|
+
private scheduleCleanup;
|
|
54
|
+
dispose(): Promise<void>;
|
|
55
|
+
}
|