feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
69
apps/web/src/utils/index.ts
Normal file
69
apps/web/src/utils/index.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { handle } from "@turbostarter/api/utils";
|
||||
import { logger } from "@turbostarter/shared/logger";
|
||||
|
||||
|
||||
import { api } from "~/lib/api/client";
|
||||
|
||||
import type { SyntheticEvent } from "react";
|
||||
|
||||
export function onPromise<T>(promise: (event: SyntheticEvent) => Promise<T>) {
|
||||
return (event: SyntheticEvent) => {
|
||||
promise(event).catch((error) => {
|
||||
logger.error("Unexpected error", error);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
interface UploadParams {
|
||||
path: string;
|
||||
file: File;
|
||||
maxRetries?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a file to storage with retry logic
|
||||
*/
|
||||
export async function uploadWithRetry({
|
||||
path,
|
||||
file,
|
||||
maxRetries = 3,
|
||||
}: UploadParams): Promise<{ path: string }> {
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
try {
|
||||
const { url: uploadUrl } = await handle(api.storage.upload.$get)({
|
||||
query: { path },
|
||||
});
|
||||
|
||||
const response = await fetch(uploadUrl, {
|
||||
method: "PUT",
|
||||
body: file,
|
||||
headers: {
|
||||
"Content-Type": file.type,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
return { path };
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
logger.warn(
|
||||
`Upload attempt ${attempt + 1}/${maxRetries} failed:`,
|
||||
lastError.message,
|
||||
);
|
||||
|
||||
if (attempt < maxRetries - 1) {
|
||||
// Exponential backoff: 1s, 2s, 4s
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, Math.pow(2, attempt) * 1000),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError ?? new Error("Upload failed after all retries");
|
||||
}
|
||||
Reference in New Issue
Block a user