mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 21:59:38 +01:00
perf(skills): store repository packages outside Postgres
Convert legacy packages automatically, restore exact commits on cache misses, and expire unused recoverable packages while keeping warm turn checks metadata-only. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
65fa1cd617
commit
0bb36b2c6b
53 files changed
+13120
-203
No files matched your search
@@ -48,7 +48,7 @@ export function createLocalDiskStorageProvider(baseDir: string): StorageProvider
|
||||
|
||||
const tempPath = `${targetPath}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
try {
|
||||
await fs.writeFile(tempPath, input.body);
|
||||
await fs.writeFile(tempPath, input.body, { signal: input.signal });
|
||||
await fs.rename(tempPath, targetPath);
|
||||
} finally { await fs.rm(tempPath, { force: true }); }
|
||||
},
|
||||
@@ -66,7 +66,7 @@ export function createLocalDiskStorageProvider(baseDir: string): StorageProvider
|
||||
? input.range.end - input.range.start + 1
|
||||
: stat.size;
|
||||
return {
|
||||
stream: createReadStream(filePath, streamOptions),
|
||||
stream: createReadStream(filePath, { ...streamOptions, signal: input.signal }),
|
||||
contentLength,
|
||||
lastModified: stat.mtime,
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ export async function putS3Multipart(client: S3Client, bucket: string, key: stri
|
||||
// Scale part sizes to the S3 part-count limit while retaining stream backpressure.
|
||||
const partSize = Math.max(8 * 1024 * 1024, Math.ceil(input.contentLength / 10_000));
|
||||
try {
|
||||
uploadId = (await withS3FailureDiagnostics("create_multipart_upload", () => client.send(new CreateMultipartUploadCommand({ Bucket: bucket, Key: key, ContentType: input.contentType })))).UploadId;
|
||||
uploadId = (await withS3FailureDiagnostics("create_multipart_upload", () => client.send(new CreateMultipartUploadCommand({ Bucket: bucket, Key: key, ContentType: input.contentType }), { abortSignal: input.signal }))).UploadId;
|
||||
if (!uploadId) throw new Error("S3 did not return a multipart upload id");
|
||||
const parts: { ETag: string; PartNumber: number }[] = [];
|
||||
for (let offset = 0; offset < input.contentLength;) {
|
||||
@@ -35,14 +35,14 @@ export async function putS3Multipart(client: S3Client, bucket: string, key: stri
|
||||
}
|
||||
})());
|
||||
try {
|
||||
const result = await withS3FailureDiagnostics("upload_part", () => client.send(new UploadPartCommand({ Bucket: bucket, Key: key, UploadId: uploadId, PartNumber: parts.length + 1, Body: body, ContentLength: length })));
|
||||
const result = await withS3FailureDiagnostics("upload_part", () => client.send(new UploadPartCommand({ Bucket: bucket, Key: key, UploadId: uploadId, PartNumber: parts.length + 1, Body: body, ContentLength: length }), { abortSignal: input.signal }));
|
||||
if (remaining || !result.ETag) throw new Error("S3 upload part is incomplete");
|
||||
parts.push({ ETag: result.ETag, PartNumber: parts.length + 1 });
|
||||
} finally { body.destroy(); }
|
||||
offset += length;
|
||||
}
|
||||
if (pending.length || !(await iterator.next()).done) throw new Error("S3 upload source exceeds its declared size");
|
||||
await withS3FailureDiagnostics("complete_multipart_upload", () => client.send(new CompleteMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts } })));
|
||||
await withS3FailureDiagnostics("complete_multipart_upload", () => client.send(new CompleteMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts } }), { abortSignal: input.signal }));
|
||||
} catch (error) {
|
||||
if (uploadId) await client.send(new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId: uploadId })).catch(() => {});
|
||||
throw error;
|
||||
|
||||
@@ -95,6 +95,7 @@ export function createS3StorageProvider(config: S3ProviderConfig): StorageProvid
|
||||
ContentType: input.contentType,
|
||||
ContentLength: input.contentLength,
|
||||
}),
|
||||
{ abortSignal: input.signal },
|
||||
));
|
||||
},
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ export function createStorageService(provider: StorageProvider): StorageService
|
||||
body: input.body,
|
||||
contentType,
|
||||
contentLength: byteSize,
|
||||
signal: input.signal,
|
||||
});
|
||||
} catch (error) {
|
||||
await provider.deleteObject({ objectKey }).catch(() => {});
|
||||
@@ -127,7 +128,7 @@ export function createStorageService(provider: StorageProvider): StorageService
|
||||
|
||||
async getObject(companyId: string, objectKey: string, options) {
|
||||
ensureCompanyPrefix(companyId, objectKey);
|
||||
return provider.getObject({ objectKey, range: options?.range });
|
||||
return provider.getObject({ objectKey, range: options?.range, signal: options?.signal });
|
||||
},
|
||||
|
||||
async headObject(companyId: string, objectKey: string) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Readable } from "node:stream";
|
||||
|
||||
export interface PutObjectInput {
|
||||
objectKey: string;
|
||||
signal?: AbortSignal;
|
||||
// Readable bodies stream straight to the backend (contentLength must be the
|
||||
// exact byte size); Buffer stays supported for small payloads.
|
||||
body: Buffer | Readable;
|
||||
@@ -45,6 +46,7 @@ export interface StorageProvider {
|
||||
}
|
||||
|
||||
export type PutFileInput = {
|
||||
signal?: AbortSignal;
|
||||
/** Server-allocated, company-prefixed key for durable idempotent uploads. Never accept from client input. */
|
||||
objectKey?: string;
|
||||
companyId: string;
|
||||
@@ -65,7 +67,7 @@ export interface PutFileResult {
|
||||
export interface StorageService {
|
||||
provider: StorageProviderId;
|
||||
putFile(input: PutFileInput): Promise<PutFileResult>;
|
||||
getObject(companyId: string, objectKey: string, options?: Pick<GetObjectInput, "range">): Promise<GetObjectResult>;
|
||||
getObject(companyId: string, objectKey: string, options?: Pick<GetObjectInput, "range" | "signal">): Promise<GetObjectResult>;
|
||||
headObject(companyId: string, objectKey: string): Promise<HeadObjectResult>;
|
||||
deleteObject(companyId: string, objectKey: string): Promise<void>;
|
||||
}
|
||||
Reference in new issue
Block a user