@cloudpdf/engine 3.0.0-next.7 → 3.0.0-next.9
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/index.cjs +151 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.js +152 -10
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -450,6 +450,13 @@ var CloudDocumentFormsService = class {
|
|
|
450
450
|
};
|
|
451
451
|
//#endregion
|
|
452
452
|
//#region src/document/CloudDocumentPagesService.ts
|
|
453
|
+
/** Detach a Uint8Array view into a standalone ArrayBuffer (the resource-map
|
|
454
|
+
* shape) without disturbing a larger buffer the caller still owns. */
|
|
455
|
+
function copyToExactBuffer(view) {
|
|
456
|
+
const copy = new ArrayBuffer(view.byteLength);
|
|
457
|
+
new Uint8Array(copy).set(view);
|
|
458
|
+
return copy;
|
|
459
|
+
}
|
|
453
460
|
/**
|
|
454
461
|
* Cloud-side document pages service. Mirrors `LocalDocumentPagesService`
|
|
455
462
|
* over HTTP: GET /pages for `list`, POST /pages/move for the reorder.
|
|
@@ -542,6 +549,46 @@ var CloudDocumentPagesService = class {
|
|
|
542
549
|
return result;
|
|
543
550
|
});
|
|
544
551
|
}
|
|
552
|
+
insert(bytes, destIndex) {
|
|
553
|
+
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
554
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
555
|
+
const buffer = bytes instanceof ArrayBuffer ? bytes : copyToExactBuffer(bytes);
|
|
556
|
+
const form = buildMutationForm(destIndex !== void 0 ? { destIndex } : {}, { source: {
|
|
557
|
+
bytes: buffer,
|
|
558
|
+
mimeType: "application/pdf",
|
|
559
|
+
name: "source.pdf"
|
|
560
|
+
} });
|
|
561
|
+
const result = await this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerPagesInsert(this.docId, this.layerName), form, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal);
|
|
562
|
+
if (result.cache) this.manifest.applyPageInsert(result.cache);
|
|
563
|
+
this.publisher.publishLocal({
|
|
564
|
+
type: "pages.inserted",
|
|
565
|
+
destIndex,
|
|
566
|
+
...result
|
|
567
|
+
});
|
|
568
|
+
return result;
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
insertBlank(spec, destIndex) {
|
|
572
|
+
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
573
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
574
|
+
const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesInsertBlank(this.docId, this.layerName), {
|
|
575
|
+
size: spec.size,
|
|
576
|
+
...spec.count !== void 0 ? { count: spec.count } : {},
|
|
577
|
+
...destIndex !== void 0 ? { destIndex } : {}
|
|
578
|
+
}, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal);
|
|
579
|
+
if (result.cache) this.manifest.applyPageInsert(result.cache);
|
|
580
|
+
this.publisher.publishLocal({
|
|
581
|
+
type: "pages.inserted",
|
|
582
|
+
destIndex,
|
|
583
|
+
...result
|
|
584
|
+
});
|
|
585
|
+
return result;
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
extract(pageObjectNumbers) {
|
|
589
|
+
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
590
|
+
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPagesExtract(this.docId, this.layerName), { pageObjectNumbers }, signal));
|
|
591
|
+
}
|
|
545
592
|
flatten(pageObjectNumbers, usage = "display") {
|
|
546
593
|
if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
|
|
547
594
|
return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
|
|
@@ -810,9 +857,7 @@ var CloudDocumentSecurityService = class {
|
|
|
810
857
|
* and `applyCdnAccess` short-circuits to origin; safe to call always.
|
|
811
858
|
*/
|
|
812
859
|
async postAccess(signal, body) {
|
|
813
|
-
const response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.access, {
|
|
814
|
-
docId: this.docId,
|
|
815
|
-
layerName: this.layerName,
|
|
860
|
+
const response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.access(this.docId, this.layerName), {
|
|
816
861
|
...body.password ? { password: body.password } : {},
|
|
817
862
|
mode: body.mode
|
|
818
863
|
}, (raw) => _embedpdf_engine_core_wire.AccessResponseSchema.parse(raw), signal);
|
|
@@ -1342,6 +1387,12 @@ function auditRowToEvent(row, mySessionId) {
|
|
|
1342
1387
|
origin,
|
|
1343
1388
|
...row.payload
|
|
1344
1389
|
};
|
|
1390
|
+
case "pages.insert":
|
|
1391
|
+
case "pages.insertBlank": return {
|
|
1392
|
+
type: "pages.inserted",
|
|
1393
|
+
origin,
|
|
1394
|
+
...row.payload
|
|
1395
|
+
};
|
|
1345
1396
|
case "pages.flatten": return {
|
|
1346
1397
|
type: "pages.flattened",
|
|
1347
1398
|
origin,
|
|
@@ -1584,6 +1635,7 @@ var CloudDocumentHandle = class {
|
|
|
1584
1635
|
apply: (meta, owns) => this.absorbMutation(meta, owns),
|
|
1585
1636
|
applyPageStructure: (cache) => this.absorbPageStructure(cache),
|
|
1586
1637
|
applyPageDelete: (cache, deletedPages) => this.absorbPageDelete(cache, deletedPages),
|
|
1638
|
+
applyPageInsert: (cache) => this.absorbPageInsert(cache),
|
|
1587
1639
|
applyMetadata: (cache) => this.absorbMetadata(cache),
|
|
1588
1640
|
applyAttachments: (cache) => this.absorbAttachments(cache)
|
|
1589
1641
|
};
|
|
@@ -1749,6 +1801,27 @@ var CloudDocumentHandle = class {
|
|
|
1749
1801
|
};
|
|
1750
1802
|
}
|
|
1751
1803
|
/**
|
|
1804
|
+
* Patch bookkeeping after a page insert — the mirror of
|
|
1805
|
+
* {@link absorbPageDelete}, with one asymmetry: delete can patch the
|
|
1806
|
+
* cached manifest losslessly (it only REMOVES rows), but an insert needs
|
|
1807
|
+
* manifest rows for the fresh PONs and the result doesn't carry them. So
|
|
1808
|
+
* this absorb flips the planes (insert changes the page SET, so like
|
|
1809
|
+
* delete it owns content + annotations alongside layout), raises the
|
|
1810
|
+
* version floor, and DROPS the cache — the next read refetches a manifest
|
|
1811
|
+
* that includes the new pages' rows. The UI never waits on that refetch:
|
|
1812
|
+
* the mutation result / event already carries the full new layout.
|
|
1813
|
+
*/
|
|
1814
|
+
absorbPageInsert(cache) {
|
|
1815
|
+
this.flipScopes([
|
|
1816
|
+
"layout",
|
|
1817
|
+
"content",
|
|
1818
|
+
"annotations"
|
|
1819
|
+
]);
|
|
1820
|
+
this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
|
|
1821
|
+
this.inflightManifest = null;
|
|
1822
|
+
this.manifestCache = null;
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1752
1825
|
* Patch the cached manifest after a metadata write. Symmetric with
|
|
1753
1826
|
* {@link absorbPageMove}: a metadata edit advances `docVersion` (so leaf
|
|
1754
1827
|
* URLs re-resolve) and `metadataVersion` (so the /metadata leaf re-fetches),
|
|
@@ -1884,6 +1957,9 @@ var CloudDocumentHandle = class {
|
|
|
1884
1957
|
case "pages.deleted":
|
|
1885
1958
|
if (event.cache) this.absorbPageDelete(event.cache, event.pageObjectNumbers);
|
|
1886
1959
|
return;
|
|
1960
|
+
case "pages.inserted":
|
|
1961
|
+
if (event.cache) this.absorbPageInsert(event.cache);
|
|
1962
|
+
return;
|
|
1887
1963
|
case "metadata.updated":
|
|
1888
1964
|
if (event.cache) this.absorbMetadata(event.cache);
|
|
1889
1965
|
return;
|
|
@@ -1935,7 +2011,7 @@ function fallbackUnknownHead(id) {
|
|
|
1935
2011
|
access: {
|
|
1936
2012
|
required: true,
|
|
1937
2013
|
reasons: ["permissions-unknown"],
|
|
1938
|
-
endpoint: _embedpdf_engine_core_wire.wirePaths.access
|
|
2014
|
+
endpoint: _embedpdf_engine_core_wire.wirePaths.access(id, _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME)
|
|
1939
2015
|
}
|
|
1940
2016
|
};
|
|
1941
2017
|
}
|
|
@@ -2043,6 +2119,8 @@ var HttpClient = class HttpClient {
|
|
|
2043
2119
|
_defineProperty(this, "base", void 0);
|
|
2044
2120
|
_defineProperty(this, "tokenFn", void 0);
|
|
2045
2121
|
_defineProperty(this, "fetchFn", void 0);
|
|
2122
|
+
_defineProperty(this, "docAffinityHeader", void 0);
|
|
2123
|
+
_defineProperty(this, "onRetry", void 0);
|
|
2046
2124
|
_defineProperty(this, "sessionId", void 0);
|
|
2047
2125
|
_defineProperty(this, "cdnBinding", null);
|
|
2048
2126
|
this.base = opts.baseUrl.replace(/\/$/, "");
|
|
@@ -2050,6 +2128,8 @@ var HttpClient = class HttpClient {
|
|
|
2050
2128
|
this.tokenFn = token === void 0 ? null : typeof token === "function" ? token : () => token;
|
|
2051
2129
|
this.sessionId = opts.sessionId ?? null;
|
|
2052
2130
|
this.fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
2131
|
+
this.docAffinityHeader = opts.docAffinityHeader ?? true;
|
|
2132
|
+
this.onRetry = opts.onRetry;
|
|
2053
2133
|
}
|
|
2054
2134
|
/** Normalized server base URL (no trailing slash). */
|
|
2055
2135
|
get baseUrl() {
|
|
@@ -2280,6 +2360,12 @@ var HttpClient = class HttpClient {
|
|
|
2280
2360
|
const res = await this.requestJson(path, "POST", body, signal);
|
|
2281
2361
|
return await this.parseJsonResponse(res, parser);
|
|
2282
2362
|
}
|
|
2363
|
+
/** POST a JSON body and return the raw binary response (pages.extract). */
|
|
2364
|
+
async postJsonBytes(path, body, signal) {
|
|
2365
|
+
const res = await this.requestJson(path, "POST", body, signal);
|
|
2366
|
+
if (!res.ok) await this.throwFromBody(res);
|
|
2367
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
2368
|
+
}
|
|
2283
2369
|
/** POST a raw binary body (FDF/XFDF import) and parse the JSON response. */
|
|
2284
2370
|
async postBytesJson(path, bytes, contentType, parser, signal) {
|
|
2285
2371
|
const headers = new Headers({ "Content-Type": contentType });
|
|
@@ -2326,7 +2412,7 @@ var HttpClient = class HttpClient {
|
|
|
2326
2412
|
});
|
|
2327
2413
|
}
|
|
2328
2414
|
async request(path, init) {
|
|
2329
|
-
const { url, extraHeader } = this.resolveOutgoing(path);
|
|
2415
|
+
const { url, extraHeader, routedToCdn } = this.resolveOutgoing(path);
|
|
2330
2416
|
const headers = new Headers(init.headers ?? {});
|
|
2331
2417
|
if (this.tokenFn) {
|
|
2332
2418
|
const token = await this.tokenFn();
|
|
@@ -2335,11 +2421,32 @@ var HttpClient = class HttpClient {
|
|
|
2335
2421
|
if (!headers.has("Accept")) headers.set("Accept", "application/json");
|
|
2336
2422
|
if (this.sessionId) headers.set("X-Engine-Session-Id", this.sessionId);
|
|
2337
2423
|
if (extraHeader) headers.set(extraHeader.name, extraHeader.value);
|
|
2338
|
-
|
|
2339
|
-
|
|
2424
|
+
if (this.docAffinityHeader && !routedToCdn) {
|
|
2425
|
+
const doc = /^\/v1\/docs\/([^/]+)\//.exec(path);
|
|
2426
|
+
if (doc) headers.set("X-CloudPDF-Doc", decodeURIComponent(doc[1]));
|
|
2427
|
+
}
|
|
2428
|
+
for (let attempt = 0;; attempt++) {
|
|
2429
|
+
const res = await this.execute(url, {
|
|
2340
2430
|
...init,
|
|
2341
2431
|
headers
|
|
2342
2432
|
});
|
|
2433
|
+
if (res.status !== 503 || attempt >= HttpClient.MAX_RETRIES || isStreamBody(init.body ?? null)) return res;
|
|
2434
|
+
const code = await peekErrorCode(res);
|
|
2435
|
+
if (code === null || !RETRYABLE_503.has(code)) return res;
|
|
2436
|
+
const hinted = Number(res.headers.get("retry-after"));
|
|
2437
|
+
const waitMs = (Number.isFinite(hinted) ? Math.min(Math.max(hinted, 0) * 1e3, HttpClient.RETRY_AFTER_CAP_MS) : HttpClient.RETRY_AFTER_DEFAULT_MS) * (.9 + .2 * Math.random());
|
|
2438
|
+
this.onRetry?.({
|
|
2439
|
+
path,
|
|
2440
|
+
code,
|
|
2441
|
+
attempt,
|
|
2442
|
+
waitMs
|
|
2443
|
+
});
|
|
2444
|
+
await sleepRacingAbort(waitMs, init.signal ?? null);
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
async execute(url, init) {
|
|
2448
|
+
try {
|
|
2449
|
+
return await this.fetchFn(url, init);
|
|
2343
2450
|
} catch (err) {
|
|
2344
2451
|
if (init.signal?.aborted) throw new _embedpdf_engine_core_runtime.AbortError(init.signal.reason ?? err);
|
|
2345
2452
|
if (err?.name === "AbortError") throw new _embedpdf_engine_core_runtime.AbortError(err.message);
|
|
@@ -2365,7 +2472,8 @@ var HttpClient = class HttpClient {
|
|
|
2365
2472
|
resolveOutgoing(path) {
|
|
2366
2473
|
if (!this.cdnBinding || !this.cdnBinding.cdn) return {
|
|
2367
2474
|
url: `${this.baseUrl}${path}`,
|
|
2368
|
-
extraHeader: null
|
|
2475
|
+
extraHeader: null,
|
|
2476
|
+
routedToCdn: false
|
|
2369
2477
|
};
|
|
2370
2478
|
const applied = (0, _embedpdf_engine_core_wire.applyCdnAccess)({
|
|
2371
2479
|
path,
|
|
@@ -2376,7 +2484,8 @@ var HttpClient = class HttpClient {
|
|
|
2376
2484
|
});
|
|
2377
2485
|
return {
|
|
2378
2486
|
url: applied.url,
|
|
2379
|
-
extraHeader: applied.routedToCdn && applied.authHeader ? applied.authHeader : null
|
|
2487
|
+
extraHeader: applied.routedToCdn && applied.authHeader ? applied.authHeader : null,
|
|
2488
|
+
routedToCdn: applied.routedToCdn
|
|
2380
2489
|
};
|
|
2381
2490
|
}
|
|
2382
2491
|
async throwFromBody(res) {
|
|
@@ -2396,6 +2505,9 @@ var HttpClient = class HttpClient {
|
|
|
2396
2505
|
throw new _embedpdf_engine_core_runtime.EngineError(code, `HTTP ${res.status}`, { details: { status: res.status } });
|
|
2397
2506
|
}
|
|
2398
2507
|
};
|
|
2508
|
+
_defineProperty(HttpClient, "MAX_RETRIES", 2);
|
|
2509
|
+
_defineProperty(HttpClient, "RETRY_AFTER_CAP_MS", 5e3);
|
|
2510
|
+
_defineProperty(HttpClient, "RETRY_AFTER_DEFAULT_MS", 1e3);
|
|
2399
2511
|
function mapStatusToCode(status) {
|
|
2400
2512
|
if (status === 401) return _embedpdf_engine_core_runtime.EngineErrorCode.Unauthenticated;
|
|
2401
2513
|
if (status === 403) return _embedpdf_engine_core_runtime.EngineErrorCode.Forbidden;
|
|
@@ -2410,6 +2522,36 @@ function trySerialized(value) {
|
|
|
2410
2522
|
const result = _embedpdf_engine_core_wire.EngineErrorPayloadSchema.safeParse(value);
|
|
2411
2523
|
return result.success ? result.data : null;
|
|
2412
2524
|
}
|
|
2525
|
+
const RETRYABLE_503 = /* @__PURE__ */ new Set(["EngineBusy", "EngineRestarting"]);
|
|
2526
|
+
function isStreamBody(body) {
|
|
2527
|
+
return typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
|
|
2528
|
+
}
|
|
2529
|
+
/** Read the error code from a 503 body without consuming the response. */
|
|
2530
|
+
async function peekErrorCode(res) {
|
|
2531
|
+
try {
|
|
2532
|
+
const code = (await res.clone().json())?.error?.code;
|
|
2533
|
+
return typeof code === "string" ? code : null;
|
|
2534
|
+
} catch {
|
|
2535
|
+
return null;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
function sleepRacingAbort(ms, signal) {
|
|
2539
|
+
return new Promise((resolve, reject) => {
|
|
2540
|
+
if (signal?.aborted) {
|
|
2541
|
+
reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason ?? "aborted"));
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
const timer = setTimeout(() => {
|
|
2545
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2546
|
+
resolve();
|
|
2547
|
+
}, ms);
|
|
2548
|
+
const onAbort = () => {
|
|
2549
|
+
clearTimeout(timer);
|
|
2550
|
+
reject(new _embedpdf_engine_core_runtime.AbortError(signal?.reason ?? "aborted"));
|
|
2551
|
+
};
|
|
2552
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2413
2555
|
//#endregion
|
|
2414
2556
|
//#region src/CloudEngine.ts
|
|
2415
2557
|
/**
|