Comparing version 4.9.0 to 4.10.0-v4nightly.20250220
@@ -28,2 +28,7 @@ import { TimelinePipe } from "@pnp/core"; | ||
headersCopyPattern?: RegExp; | ||
/** | ||
* Number of requests to include in each batch, if more than this number are added your requests will | ||
* be completed in multiple batches. This may affect ordered operations. | ||
*/ | ||
maxRequests?: number; | ||
} | ||
@@ -30,0 +35,0 @@ /** |
168
batching.js
@@ -74,9 +74,12 @@ import { getGUID, isUrlAbsolute, combine, CopyFrom, isFunc, hOP } from "@pnp/core"; | ||
const requests = []; | ||
const batchQuery = new BatchQueryable(base); | ||
// this id will be reused across multiple batches if the number of requests added to the batch | ||
// exceeds the configured maxRequests value | ||
const batchId = getGUID(); | ||
const batchQuery = new BatchQueryable(base); | ||
// this query is used to copy back the behaviors after the batch executes | ||
// it should not manipulated or have behaviors added. | ||
const refQuery = new BatchQueryable(base); | ||
const { headersCopyPattern } = { | ||
const { headersCopyPattern, maxRequests } = { | ||
headersCopyPattern: /Accept|Content-Type|IF-Match/i, | ||
maxRequests: 20, | ||
...props, | ||
@@ -92,93 +95,84 @@ }; | ||
} | ||
const batchBody = []; | ||
let currentChangeSetId = ""; | ||
for (let i = 0; i < requests.length; i++) { | ||
const [, url, init] = requests[i]; | ||
if (init.method === "GET") { | ||
if (currentChangeSetId.length > 0) { | ||
// end an existing change set | ||
batchBody.push(`--changeset_${currentChangeSetId}--\n\n`); | ||
currentChangeSetId = ""; | ||
} | ||
batchBody.push(`--batch_${batchId}\n`); | ||
} | ||
else { | ||
if (currentChangeSetId.length < 1) { | ||
// start new change set | ||
currentChangeSetId = getGUID(); | ||
// create a working copy of our requests | ||
const requestsWorkingCopy = requests.slice(); | ||
while (requestsWorkingCopy.length > 0) { | ||
const requestsChunk = requestsWorkingCopy.splice(0, maxRequests); | ||
const batchBody = []; | ||
let currentChangeSetId = ""; | ||
for (let i = 0; i < requestsChunk.length; i++) { | ||
const [, url, init] = requestsChunk[i]; | ||
if (init.method === "GET") { | ||
if (currentChangeSetId.length > 0) { | ||
// end an existing change set | ||
batchBody.push(`--changeset_${currentChangeSetId}--\n\n`); | ||
currentChangeSetId = ""; | ||
} | ||
batchBody.push(`--batch_${batchId}\n`); | ||
batchBody.push(`Content-Type: multipart/mixed; boundary="changeset_${currentChangeSetId}"\n\n`); | ||
} | ||
batchBody.push(`--changeset_${currentChangeSetId}\n`); | ||
} | ||
// common batch part prefix | ||
batchBody.push("Content-Type: application/http\n"); | ||
batchBody.push("Content-Transfer-Encoding: binary\n\n"); | ||
// these are the per-request headers | ||
const headers = new Headers(init.headers); | ||
// this is the url of the individual request within the batch | ||
const reqUrl = isUrlAbsolute(url) ? url : combine(batchQuery.requestBaseUrl, url); | ||
if (init.method !== "GET") { | ||
let method = init.method; | ||
if (headers.has("X-HTTP-Method")) { | ||
method = headers.get("X-HTTP-Method"); | ||
headers.delete("X-HTTP-Method"); | ||
else { | ||
if (currentChangeSetId.length < 1) { | ||
// start new change set | ||
currentChangeSetId = getGUID(); | ||
batchBody.push(`--batch_${batchId}\n`); | ||
batchBody.push(`Content-Type: multipart/mixed; boundary="changeset_${currentChangeSetId}"\n\n`); | ||
} | ||
batchBody.push(`--changeset_${currentChangeSetId}\n`); | ||
} | ||
batchBody.push(`${method} ${reqUrl} HTTP/1.1\n`); | ||
// common batch part prefix | ||
batchBody.push("Content-Type: application/http\n"); | ||
batchBody.push("Content-Transfer-Encoding: binary\n\n"); | ||
// these are the per-request headers | ||
const headers = new Headers(init.headers); | ||
// this is the url of the individual request within the batch | ||
const reqUrl = isUrlAbsolute(url) ? url : combine(batchQuery.requestBaseUrl, url); | ||
if (init.method !== "GET") { | ||
let method = init.method; | ||
if (headers.has("X-HTTP-Method")) { | ||
method = headers.get("X-HTTP-Method"); | ||
headers.delete("X-HTTP-Method"); | ||
} | ||
batchBody.push(`${method} ${reqUrl} HTTP/1.1\n`); | ||
} | ||
else { | ||
batchBody.push(`${init.method} ${reqUrl} HTTP/1.1\n`); | ||
} | ||
// lastly we apply any default headers we need that may not exist | ||
if (!headers.has("Accept")) { | ||
headers.append("Accept", "application/json"); | ||
} | ||
if (!headers.has("Content-Type")) { | ||
headers.append("Content-Type", "application/json;charset=utf-8"); | ||
} | ||
// write headers into batch body | ||
headers.forEach((value, name) => { | ||
if (headersCopyPattern.test(name)) { | ||
batchBody.push(`${name}: ${value}\n`); | ||
} | ||
}); | ||
batchBody.push("\n"); | ||
if (init.body) { | ||
batchBody.push(`${init.body}\n\n`); | ||
} | ||
} | ||
else { | ||
batchBody.push(`${init.method} ${reqUrl} HTTP/1.1\n`); | ||
if (currentChangeSetId.length > 0) { | ||
// Close the changeset | ||
batchBody.push(`--changeset_${currentChangeSetId}--\n\n`); | ||
currentChangeSetId = ""; | ||
} | ||
// lastly we apply any default headers we need that may not exist | ||
if (!headers.has("Accept")) { | ||
headers.append("Accept", "application/json"); | ||
} | ||
if (!headers.has("Content-Type")) { | ||
headers.append("Content-Type", "application/json;charset=utf-8"); | ||
} | ||
// write headers into batch body | ||
headers.forEach((value, name) => { | ||
if (headersCopyPattern.test(name)) { | ||
batchBody.push(`${name}: ${value}\n`); | ||
} | ||
batchBody.push(`--batch_${batchId}--\n`); | ||
const responses = await spPost(batchQuery, { | ||
body: batchBody.join(""), | ||
headers: { | ||
"Content-Type": `multipart/mixed; boundary=batch_${batchId}`, | ||
}, | ||
}); | ||
batchBody.push("\n"); | ||
if (init.body) { | ||
batchBody.push(`${init.body}\n\n`); | ||
if (responses.length !== requestsChunk.length) { | ||
throw Error("Could not properly parse responses to match requests in batch."); | ||
} | ||
} | ||
if (currentChangeSetId.length > 0) { | ||
// Close the changeset | ||
batchBody.push(`--changeset_${currentChangeSetId}--\n\n`); | ||
currentChangeSetId = ""; | ||
} | ||
batchBody.push(`--batch_${batchId}--\n`); | ||
const responses = await spPost(batchQuery, { | ||
body: batchBody.join(""), | ||
headers: { | ||
"Content-Type": `multipart/mixed; boundary=batch_${batchId}`, | ||
}, | ||
}); | ||
if (responses.length !== requests.length) { | ||
throw Error("Could not properly parse responses to match requests in batch."); | ||
} | ||
return new Promise((res, rej) => { | ||
try { | ||
for (let index = 0; index < responses.length; index++) { | ||
const [, , , resolve, reject] = requests[index]; | ||
try { | ||
resolve(responses[index]); | ||
} | ||
catch (e) { | ||
reject(e); | ||
} | ||
} | ||
// this small delay allows the promises to resolve correctly in order by dropping this resolve behind | ||
// the other work in the event loop. Feels hacky, but it works so 🤷 | ||
setTimeout(res, 0); | ||
for (let index = 0; index < responses.length; index++) { | ||
// resolve the child request's send promise with the parsed response | ||
requestsChunk[index][3](responses[index]); | ||
} | ||
catch (e) { | ||
setTimeout(() => rej(e), 0); | ||
} | ||
}).then(() => Promise.all(completePromises)).then(() => void (0)); | ||
} // end of while (requestsWorkingCopy.length > 0) | ||
await Promise.all(completePromises).then(() => void (0)); | ||
}; | ||
@@ -213,5 +207,5 @@ const register = (instance) => { | ||
// this is the promise that Queryable will see returned from .emit.send | ||
const promise = new Promise((resolve, reject) => { | ||
const promise = new Promise((resolve) => { | ||
// add the request information into the batch | ||
requests.push([this, url.toString(), init, resolve, reject]); | ||
requests.push([this, url.toString(), init, resolve]); | ||
}); | ||
@@ -218,0 +212,0 @@ this.log(`[batch:${batchId}] (${(new Date()).getTime()}) Adding request ${init.method} ${url.toString()} to batch.`, 0); |
@@ -5,3 +5,3 @@ import { stringIsNullOrEmpty } from "@pnp/core"; | ||
instance.on.pre(async function (url, init, result) { | ||
let clientTag = "PnPCoreJS:4.9.0:"; | ||
let clientTag = "PnPCoreJS:4.10.0:"; | ||
// make our best guess based on url to the method called | ||
@@ -8,0 +8,0 @@ const { pathname } = new URL(url); |
@@ -164,4 +164,8 @@ import { _SPCollection, _SPInstance, ISPInstance, IDeleteableWithETag, ISPQueryable } from "../spqueryable.js"; | ||
WelcomePage: string; | ||
ContentTypeOrder: string[]; | ||
UniqueContentTypeOrder: string[]; | ||
ContentTypeOrder: { | ||
StringValue: string; | ||
}[]; | ||
UniqueContentTypeOrder: { | ||
StringValue: string; | ||
}[]; | ||
StorageMetrics?: IStorageMetrics; | ||
@@ -168,0 +172,0 @@ } |
@@ -186,9 +186,9 @@ import { __decorate } from "tslib"; | ||
poster.on.pre(noInherit(async (url, init, result) => { | ||
const urlInfo = await Folder(this).using(BatchNever()).getParentInfos(); | ||
const uri = new URL(urlInfo.ParentWeb.Url); | ||
url = combine(urlInfo.ParentWeb.Url, `/_api/SP.MoveCopyUtil.${methodName}()`); | ||
const { ServerRelativeUrl: srcUrl, ["odata.id"]: absoluteUrl } = await Folder(this).using(BatchNever()).select("ServerRelativeUrl")(); | ||
const uri = new URL(extractWebUrl(absoluteUrl)); | ||
url = combine(uri.href, `/_api/SP.MoveCopyUtil.${methodName}()`); | ||
init = body({ | ||
destPath: toResourcePath(isUrlAbsolute(destUrl) ? destUrl : combine(uri.origin, destUrl)), | ||
options, | ||
srcPath: toResourcePath(combine(uri.origin, urlInfo.Folder.ServerRelativeUrl)), | ||
srcPath: toResourcePath(combine(uri.origin, srcUrl)), | ||
}, init); | ||
@@ -195,0 +195,0 @@ return [url, init, result]; |
@@ -5,2 +5,3 @@ export * from "./spqueryable.js"; | ||
export * from "./types.js"; | ||
export * from "./utils/create-change-token.js"; | ||
export * from "./utils/extract-web-url.js"; | ||
@@ -7,0 +8,0 @@ export * from "./utils/file-names.js"; |
@@ -5,2 +5,3 @@ export * from "./spqueryable.js"; | ||
export * from "./types.js"; | ||
export * from "./utils/create-change-token.js"; | ||
export * from "./utils/extract-web-url.js"; | ||
@@ -7,0 +8,0 @@ export * from "./utils/file-names.js"; |
{ | ||
"name": "@pnp/sp", | ||
"version": "4.9.0", | ||
"version": "4.10.0-v4nightly.20250220", | ||
"description": "pnp - provides a fluent api for working with SharePoint REST", | ||
@@ -9,4 +9,4 @@ "main": "./index.js", | ||
"tslib": "2.7.0", | ||
"@pnp/core": "4.9.0", | ||
"@pnp/queryable": "4.9.0" | ||
"@pnp/core": "4.10.0-v4nightly.20250220", | ||
"@pnp/queryable": "4.10.0-v4nightly.20250220" | ||
}, | ||
@@ -13,0 +13,0 @@ "type": "module", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1158933
599
18767
1
+ Added@pnp/core@4.10.0-v4nightly.20250220(transitive)
+ Added@pnp/queryable@4.10.0-v4nightly.20250220(transitive)
- Removed@pnp/core@4.9.0(transitive)
- Removed@pnp/queryable@4.9.0(transitive)