import * as wmill from "windmill-client"
import { S3Object } from "windmill-client"
const graph = "https://graph.microsoft.com/v1.0"
function driveUrl(drive_id: string | undefined) {
return drive_id != null && drive_id !== ""
? `${graph}/drives/${encodeURIComponent(drive_id)}`
: `${graph}/me/drive`
}
// An item is addressed by its id, or by a path from the drive root starting with "/"
function ref(item: string | undefined) {
if (item == null || item === "" || item === "/") return "root"
if (!item.startsWith("/")) return `items/${encodeURIComponent(item)}`
const path = item
.replace(/\/+$/, "")
.split("/")
.map(encodeURIComponent)
.join("/")
return `root:${path}:`
}
/**
* Upload File
* Upload a file from Windmill object storage to a folder in your OneDrive, or in another drive when `drive_id` is set. `folder` is an id, or a path from the drive root starting with "/" (e.g. `/Reports/2026`), the root when empty; `name` defaults to the object's file name. Files over 4 MB go through a resumable upload session in 10 MB chunks. Requires Files.ReadWrite (a connection default); a drive that is not your own needs Files.ReadWrite.All.
*/
export async function main(
auth: RT.MsOnedrive,
file: S3Object,
folder: string | undefined,
name: string | undefined,
conflict_behavior: "rename" | "replace" | "fail" = "rename",
drive_id: string | undefined
) {
const drive = driveUrl(drive_id)
// ponytail: the whole file is held in memory; stream chunks from loadS3FileStream if multi-GB uploads matter
const bytes = await wmill.loadS3File(file)
if (!bytes) throw new Error(`Could not load ${file.s3}`)
const fileName = encodeURIComponent(
name != null && name !== "" ? name : file.s3.split("/").pop()!
)
const parent = ref(folder)
const target = parent.startsWith("items/")
? `${drive}/${parent}:/${fileName}:`
: `${drive}/root:${parent === "root" ? "" : parent.slice(5, -1)}/${fileName}:`
if (bytes.length <= 4 * 1024 * 1024) {
const response = await fetch(
`${target}/[email protected]=${conflict_behavior}`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/octet-stream",
Accept: "application/json",
},
body: bytes,
}
)
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`)
}
return await response.json()
}
const session = await fetch(`${target}/createUploadSession`, {
method: "POST",
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
item: { "@microsoft.graph.conflictBehavior": conflict_behavior },
}),
})
if (!session.ok) {
throw new Error(`${session.status} ${await session.text()}`)
}
const { uploadUrl } = (await session.json()) as { uploadUrl: string }
// Chunks must be a multiple of 320 KiB; the upload URL is pre-authenticated
const chunk = 10 * 1024 * 1024
for (let start = 0; ; start += chunk) {
const end = Math.min(start + chunk, bytes.length)
const response = await fetch(uploadUrl, {
method: "PUT",
headers: {
"Content-Range": `bytes ${start}-${end - 1}/${bytes.length}`,
},
body: bytes.subarray(start, end),
})
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`)
}
if (end === bytes.length) return await response.json()
}
}
Submitted by hugo989 5 hours ago