@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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { AbortError, AbortablePromise, AbortablePromise as AbortablePromise$1, AnnotationAppearanceImageOptions, AnnotationAppearanceImagesResult, AnnotationAppearanceRenderOptions, AnnotationAppearancesResult, AnnotationCreateResult, AnnotationDeleteResult, AnnotationDraft, AnnotationListPageSnapshot, AnnotationListSnapshotAllPages, AnnotationMoveResult, AnnotationPatch, AnnotationRef, AnnotationUpdateResult, AttachmentContent, AttachmentsCache, DocumentAccessInfo, DocumentActionsService, DocumentAnnotationsService, DocumentAnnotationsService as DocumentAnnotationsService$1, DocumentAttachmentsService, DocumentCapabilities, DocumentEventStream, DocumentFormsService, DocumentHandle, DocumentHandle as DocumentHandle$1, DocumentIdentity, DocumentMetadata, DocumentPagesService, DocumentPagesService as DocumentPagesService$1, DocumentRedactionService, DocumentRenderService, DocumentSearchService, DocumentSecurityService, DocumentSecurityService as DocumentSecurityService$1, DocumentSecurityState, DocumentUnlockInput, DocumentUnlockResult, Engine, Engine as Engine$1, EngineError, EngineError as EngineError$1, EngineErrorCode, EngineFactory, MetadataCache, MetadataPatch, MetadataService, MetadataService as MetadataService$1, MetadataUpdateResult, MutationMeta, OpenInput, OpenInput as OpenInput$1, OpenInputShare, OpenOptions, OpenOptions as OpenOptions$1, PageAnnotationsService, PageAnnotationsService as PageAnnotationsService$1, PageDeleteResult, PageFlattenResult, PageFlattenUsage, PageGeometryService, PageGeometryService as PageGeometryService$1, PageGeometrySnapshot, PageHandle, PageHandle as PageHandle$1, PageImageHandle, PageImageOptions, PageListSnapshot, PageMoveResult, PageObjectNumber, PageRaster, PageRenderOptions, PageRenderService, PageRenderService as PageRenderService$1, PageRotateResult, PageRotation, PageStructureCache, PageTextService, PageTextService as PageTextService$1, PageTextSnapshot, PdfSaveMode, PdfSaveMode as PdfSaveMode$1, SearchRequest, SearchSlice, TokenSource, WeakAnnotationEditSession } from "@embedpdf/engine-core/runtime";
1
+ import { AbortError, AbortablePromise, AbortablePromise as AbortablePromise$1, AnnotationAppearanceImageOptions, AnnotationAppearanceImagesResult, AnnotationAppearanceRenderOptions, AnnotationAppearancesResult, AnnotationCreateResult, AnnotationDeleteResult, AnnotationDraft, AnnotationListPageSnapshot, AnnotationListSnapshotAllPages, AnnotationMoveResult, AnnotationPatch, AnnotationRef, AnnotationUpdateResult, AttachmentContent, AttachmentsCache, DocumentAccessInfo, DocumentActionsService, DocumentAnnotationsService, DocumentAnnotationsService as DocumentAnnotationsService$1, DocumentAttachmentsService, DocumentCapabilities, DocumentEventStream, DocumentFormsService, DocumentHandle, DocumentHandle as DocumentHandle$1, DocumentIdentity, DocumentMetadata, DocumentPagesService, DocumentPagesService as DocumentPagesService$1, DocumentRedactionService, DocumentRenderService, DocumentSearchService, DocumentSecurityService, DocumentSecurityService as DocumentSecurityService$1, DocumentSecurityState, DocumentUnlockInput, DocumentUnlockResult, Engine, Engine as Engine$1, EngineError, EngineError as EngineError$1, EngineErrorCode, EngineFactory, MetadataCache, MetadataPatch, MetadataService, MetadataService as MetadataService$1, MetadataUpdateResult, MutationMeta, OpenInput, OpenInput as OpenInput$1, OpenInputShare, OpenOptions, OpenOptions as OpenOptions$1, PageAnnotationsService, PageAnnotationsService as PageAnnotationsService$1, PageDeleteResult, PageFlattenResult, PageFlattenUsage, PageGeometryService, PageGeometryService as PageGeometryService$1, PageGeometrySnapshot, PageHandle, PageHandle as PageHandle$1, PageImageHandle, PageImageOptions, PageInsertBlankSpec, PageInsertResult, PageListSnapshot, PageMoveResult, PageObjectNumber, PageRaster, PageRenderOptions, PageRenderService, PageRenderService as PageRenderService$1, PageRotateResult, PageRotation, PageStructureCache, PageTextService, PageTextService as PageTextService$1, PageTextSnapshot, PdfSaveMode, PdfSaveMode as PdfSaveMode$1, SearchRequest, SearchSlice, TokenSource, WeakAnnotationEditSession } from "@embedpdf/engine-core/runtime";
2
2
  import { CdnAccessInfoForApply, DocumentHead, DocumentManifest, LayerScopePlane } from "@embedpdf/engine-core/wire";
3
3
  import { SessionEventPublisher } from "@embedpdf/engine-services";
4
4
  //#region src/transport/HttpClient.d.ts
@@ -21,6 +21,30 @@ interface HttpClientOptions {
21
21
  sessionId?: string;
22
22
  /** Replace the global fetch (e.g. in Node tests with undici). */
23
23
  fetch?: typeof globalThis.fetch;
24
+ /**
25
+ * Document affinity key: `X-CloudPDF-Doc` is sent BY DEFAULT on
26
+ * origin-bound doc requests so consistent-hash load balancers can pin
27
+ * a document's traffic to one warm replica. Routing hints are
28
+ * unconditional client behavior — whether they are USED is the
29
+ * operator's choice at the load balancer, never the app's. Derived
30
+ * from the request path (`/v1/docs/:docId/…` — the same extraction
31
+ * the Helm chart's uri-mode fallback uses); never sent on
32
+ * CDN-rewritten requests.
33
+ *
34
+ * `false` is an ESCAPE HATCH, not a feature toggle: use it only
35
+ * against a stale server whose CORS allowlist predates the header
36
+ * (browser preflights would fail) or behind a proxy that rejects
37
+ * unknown request headers.
38
+ */
39
+ docAffinityHeader?: boolean;
40
+ /** Observability: fired once per transport-level retry (engine busy /
41
+ * restarting). Lets apps count sheds without wrapping every call. */
42
+ onRetry?: (info: {
43
+ path: string;
44
+ code: string;
45
+ attempt: number;
46
+ waitMs: number;
47
+ }) => void;
24
48
  }
25
49
  /**
26
50
  * Per-document binding for CDN URL rewriting. `CloudDocumentHandle`
@@ -51,6 +75,11 @@ declare class HttpClient {
51
75
  private readonly base;
52
76
  private readonly tokenFn;
53
77
  private readonly fetchFn;
78
+ private readonly docAffinityHeader;
79
+ private readonly onRetry;
80
+ private static readonly MAX_RETRIES;
81
+ private static readonly RETRY_AFTER_CAP_MS;
82
+ private static readonly RETRY_AFTER_DEFAULT_MS;
54
83
  private readonly sessionId;
55
84
  private cdnBinding;
56
85
  constructor(opts: HttpClientOptions);
@@ -150,6 +179,8 @@ declare class HttpClient {
150
179
  /** PATCH counterpart of {@link postMultipartJson} (binary-carrying mutations). */
151
180
  patchMultipartJson<T>(path: string, body: FormData, parser: (raw: unknown) => T, signal: AbortSignal): Promise<T>;
152
181
  postJson<T>(path: string, body: unknown, parser: (raw: unknown) => T, signal: AbortSignal): Promise<T>;
182
+ /** POST a JSON body and return the raw binary response (pages.extract). */
183
+ postJsonBytes(path: string, body: unknown, signal: AbortSignal): Promise<Uint8Array>;
153
184
  /** POST a raw binary body (FDF/XFDF import) and parse the JSON response. */
154
185
  postBytesJson<T>(path: string, bytes: Uint8Array, contentType: string, parser: (raw: unknown) => T, signal: AbortSignal): Promise<T>;
155
186
  patchJson<T>(path: string, body: unknown, parser: (raw: unknown) => T, signal: AbortSignal): Promise<T>;
@@ -158,6 +189,7 @@ declare class HttpClient {
158
189
  postEmpty(path: string, body: unknown, signal: AbortSignal): Promise<void>;
159
190
  private requestJson;
160
191
  private request;
192
+ private execute;
161
193
  private parseJsonResponse;
162
194
  /**
163
195
  * Decide where this request actually goes. With no CDN binding (or
@@ -264,6 +296,10 @@ interface ManifestAccessor {
264
296
  /** Same advance for a page delete, additionally dropping the deleted
265
297
  * pages' manifest rows so per-page leaf URLs stop resolving locally. */
266
298
  applyPageDelete(cache: PageStructureCache, deletedPages: PageObjectNumber[]): void;
299
+ /** Page insert: the cached manifest has no rows for the fresh PONs (the
300
+ * result carries only their object numbers), so the absorb drops the
301
+ * cache for a lazy refetch instead of patching. */
302
+ applyPageInsert(cache: PageStructureCache): void;
267
303
  /** Advance the cached manifest's docVersion + metadataVersion after a metadata write. */
268
304
  applyMetadata(cache: MetadataCache): void;
269
305
  /** Advance the cached manifest's docVersion + attachmentsVersion after an
@@ -365,6 +401,18 @@ declare class CloudDocumentHandle implements DocumentHandle$1 {
365
401
  * 404 anyway — this keeps the failure local and instant).
366
402
  */
367
403
  private absorbPageDelete;
404
+ /**
405
+ * Patch bookkeeping after a page insert — the mirror of
406
+ * {@link absorbPageDelete}, with one asymmetry: delete can patch the
407
+ * cached manifest losslessly (it only REMOVES rows), but an insert needs
408
+ * manifest rows for the fresh PONs and the result doesn't carry them. So
409
+ * this absorb flips the planes (insert changes the page SET, so like
410
+ * delete it owns content + annotations alongside layout), raises the
411
+ * version floor, and DROPS the cache — the next read refetches a manifest
412
+ * that includes the new pages' rows. The UI never waits on that refetch:
413
+ * the mutation result / event already carries the full new layout.
414
+ */
415
+ private absorbPageInsert;
368
416
  /**
369
417
  * Patch the cached manifest after a metadata write. Symmetric with
370
418
  * {@link absorbPageMove}: a metadata edit advances `docVersion` (so leaf
@@ -448,6 +496,9 @@ declare class CloudDocumentPagesService implements DocumentPagesService$1 {
448
496
  move(pageObjectNumbers: PageObjectNumber[], destIndex: number): AbortablePromise$1<PageMoveResult>;
449
497
  rotate(pageObjectNumbers: PageObjectNumber[], rotation: PageRotation): AbortablePromise$1<PageRotateResult>;
450
498
  delete(pageObjectNumbers: PageObjectNumber[]): AbortablePromise$1<PageDeleteResult>;
499
+ insert(bytes: Uint8Array | ArrayBuffer, destIndex?: number): AbortablePromise$1<PageInsertResult>;
500
+ insertBlank(spec: PageInsertBlankSpec, destIndex?: number): AbortablePromise$1<PageInsertResult>;
501
+ extract(pageObjectNumbers: PageObjectNumber[]): AbortablePromise$1<Uint8Array>;
451
502
  flatten(pageObjectNumbers: PageObjectNumber[], usage?: PageFlattenUsage): AbortablePromise$1<PageFlattenResult>;
452
503
  }
453
504
  //#endregion
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AbortError, AbortablePromise, AbortablePromise as AbortablePromise$1, AnnotationAppearanceImageOptions, AnnotationAppearanceImagesResult, AnnotationAppearanceRenderOptions, AnnotationAppearancesResult, AnnotationCreateResult, AnnotationDeleteResult, AnnotationDraft, AnnotationListPageSnapshot, AnnotationListSnapshotAllPages, AnnotationMoveResult, AnnotationPatch, AnnotationRef, AnnotationUpdateResult, AttachmentContent, AttachmentsCache, DocumentAccessInfo, DocumentActionsService, DocumentAnnotationsService, DocumentAnnotationsService as DocumentAnnotationsService$1, DocumentAttachmentsService, DocumentCapabilities, DocumentEventStream, DocumentFormsService, DocumentHandle, DocumentHandle as DocumentHandle$1, DocumentIdentity, DocumentMetadata, DocumentPagesService, DocumentPagesService as DocumentPagesService$1, DocumentRedactionService, DocumentRenderService, DocumentSearchService, DocumentSecurityService, DocumentSecurityService as DocumentSecurityService$1, DocumentSecurityState, DocumentUnlockInput, DocumentUnlockResult, Engine, Engine as Engine$1, EngineError, EngineError as EngineError$1, EngineErrorCode, EngineFactory, MetadataCache, MetadataPatch, MetadataService, MetadataService as MetadataService$1, MetadataUpdateResult, MutationMeta, OpenInput, OpenInput as OpenInput$1, OpenInputShare, OpenOptions, OpenOptions as OpenOptions$1, PageAnnotationsService, PageAnnotationsService as PageAnnotationsService$1, PageDeleteResult, PageFlattenResult, PageFlattenUsage, PageGeometryService, PageGeometryService as PageGeometryService$1, PageGeometrySnapshot, PageHandle, PageHandle as PageHandle$1, PageImageHandle, PageImageOptions, PageListSnapshot, PageMoveResult, PageObjectNumber, PageRaster, PageRenderOptions, PageRenderService, PageRenderService as PageRenderService$1, PageRotateResult, PageRotation, PageStructureCache, PageTextService, PageTextService as PageTextService$1, PageTextSnapshot, PdfSaveMode, PdfSaveMode as PdfSaveMode$1, SearchRequest, SearchSlice, TokenSource, WeakAnnotationEditSession } from "@embedpdf/engine-core/runtime";
1
+ import { AbortError, AbortablePromise, AbortablePromise as AbortablePromise$1, AnnotationAppearanceImageOptions, AnnotationAppearanceImagesResult, AnnotationAppearanceRenderOptions, AnnotationAppearancesResult, AnnotationCreateResult, AnnotationDeleteResult, AnnotationDraft, AnnotationListPageSnapshot, AnnotationListSnapshotAllPages, AnnotationMoveResult, AnnotationPatch, AnnotationRef, AnnotationUpdateResult, AttachmentContent, AttachmentsCache, DocumentAccessInfo, DocumentActionsService, DocumentAnnotationsService, DocumentAnnotationsService as DocumentAnnotationsService$1, DocumentAttachmentsService, DocumentCapabilities, DocumentEventStream, DocumentFormsService, DocumentHandle, DocumentHandle as DocumentHandle$1, DocumentIdentity, DocumentMetadata, DocumentPagesService, DocumentPagesService as DocumentPagesService$1, DocumentRedactionService, DocumentRenderService, DocumentSearchService, DocumentSecurityService, DocumentSecurityService as DocumentSecurityService$1, DocumentSecurityState, DocumentUnlockInput, DocumentUnlockResult, Engine, Engine as Engine$1, EngineError, EngineError as EngineError$1, EngineErrorCode, EngineFactory, MetadataCache, MetadataPatch, MetadataService, MetadataService as MetadataService$1, MetadataUpdateResult, MutationMeta, OpenInput, OpenInput as OpenInput$1, OpenInputShare, OpenOptions, OpenOptions as OpenOptions$1, PageAnnotationsService, PageAnnotationsService as PageAnnotationsService$1, PageDeleteResult, PageFlattenResult, PageFlattenUsage, PageGeometryService, PageGeometryService as PageGeometryService$1, PageGeometrySnapshot, PageHandle, PageHandle as PageHandle$1, PageImageHandle, PageImageOptions, PageInsertBlankSpec, PageInsertResult, PageListSnapshot, PageMoveResult, PageObjectNumber, PageRaster, PageRenderOptions, PageRenderService, PageRenderService as PageRenderService$1, PageRotateResult, PageRotation, PageStructureCache, PageTextService, PageTextService as PageTextService$1, PageTextSnapshot, PdfSaveMode, PdfSaveMode as PdfSaveMode$1, SearchRequest, SearchSlice, TokenSource, WeakAnnotationEditSession } from "@embedpdf/engine-core/runtime";
2
2
  import { CdnAccessInfoForApply, DocumentHead, DocumentManifest, LayerScopePlane } from "@embedpdf/engine-core/wire";
3
3
  import { SessionEventPublisher } from "@embedpdf/engine-services";
4
4
  //#region src/transport/HttpClient.d.ts
@@ -21,6 +21,30 @@ interface HttpClientOptions {
21
21
  sessionId?: string;
22
22
  /** Replace the global fetch (e.g. in Node tests with undici). */
23
23
  fetch?: typeof globalThis.fetch;
24
+ /**
25
+ * Document affinity key: `X-CloudPDF-Doc` is sent BY DEFAULT on
26
+ * origin-bound doc requests so consistent-hash load balancers can pin
27
+ * a document's traffic to one warm replica. Routing hints are
28
+ * unconditional client behavior — whether they are USED is the
29
+ * operator's choice at the load balancer, never the app's. Derived
30
+ * from the request path (`/v1/docs/:docId/…` — the same extraction
31
+ * the Helm chart's uri-mode fallback uses); never sent on
32
+ * CDN-rewritten requests.
33
+ *
34
+ * `false` is an ESCAPE HATCH, not a feature toggle: use it only
35
+ * against a stale server whose CORS allowlist predates the header
36
+ * (browser preflights would fail) or behind a proxy that rejects
37
+ * unknown request headers.
38
+ */
39
+ docAffinityHeader?: boolean;
40
+ /** Observability: fired once per transport-level retry (engine busy /
41
+ * restarting). Lets apps count sheds without wrapping every call. */
42
+ onRetry?: (info: {
43
+ path: string;
44
+ code: string;
45
+ attempt: number;
46
+ waitMs: number;
47
+ }) => void;
24
48
  }
25
49
  /**
26
50
  * Per-document binding for CDN URL rewriting. `CloudDocumentHandle`
@@ -51,6 +75,11 @@ declare class HttpClient {
51
75
  private readonly base;
52
76
  private readonly tokenFn;
53
77
  private readonly fetchFn;
78
+ private readonly docAffinityHeader;
79
+ private readonly onRetry;
80
+ private static readonly MAX_RETRIES;
81
+ private static readonly RETRY_AFTER_CAP_MS;
82
+ private static readonly RETRY_AFTER_DEFAULT_MS;
54
83
  private readonly sessionId;
55
84
  private cdnBinding;
56
85
  constructor(opts: HttpClientOptions);
@@ -150,6 +179,8 @@ declare class HttpClient {
150
179
  /** PATCH counterpart of {@link postMultipartJson} (binary-carrying mutations). */
151
180
  patchMultipartJson<T>(path: string, body: FormData, parser: (raw: unknown) => T, signal: AbortSignal): Promise<T>;
152
181
  postJson<T>(path: string, body: unknown, parser: (raw: unknown) => T, signal: AbortSignal): Promise<T>;
182
+ /** POST a JSON body and return the raw binary response (pages.extract). */
183
+ postJsonBytes(path: string, body: unknown, signal: AbortSignal): Promise<Uint8Array>;
153
184
  /** POST a raw binary body (FDF/XFDF import) and parse the JSON response. */
154
185
  postBytesJson<T>(path: string, bytes: Uint8Array, contentType: string, parser: (raw: unknown) => T, signal: AbortSignal): Promise<T>;
155
186
  patchJson<T>(path: string, body: unknown, parser: (raw: unknown) => T, signal: AbortSignal): Promise<T>;
@@ -158,6 +189,7 @@ declare class HttpClient {
158
189
  postEmpty(path: string, body: unknown, signal: AbortSignal): Promise<void>;
159
190
  private requestJson;
160
191
  private request;
192
+ private execute;
161
193
  private parseJsonResponse;
162
194
  /**
163
195
  * Decide where this request actually goes. With no CDN binding (or
@@ -264,6 +296,10 @@ interface ManifestAccessor {
264
296
  /** Same advance for a page delete, additionally dropping the deleted
265
297
  * pages' manifest rows so per-page leaf URLs stop resolving locally. */
266
298
  applyPageDelete(cache: PageStructureCache, deletedPages: PageObjectNumber[]): void;
299
+ /** Page insert: the cached manifest has no rows for the fresh PONs (the
300
+ * result carries only their object numbers), so the absorb drops the
301
+ * cache for a lazy refetch instead of patching. */
302
+ applyPageInsert(cache: PageStructureCache): void;
267
303
  /** Advance the cached manifest's docVersion + metadataVersion after a metadata write. */
268
304
  applyMetadata(cache: MetadataCache): void;
269
305
  /** Advance the cached manifest's docVersion + attachmentsVersion after an
@@ -365,6 +401,18 @@ declare class CloudDocumentHandle implements DocumentHandle$1 {
365
401
  * 404 anyway — this keeps the failure local and instant).
366
402
  */
367
403
  private absorbPageDelete;
404
+ /**
405
+ * Patch bookkeeping after a page insert — the mirror of
406
+ * {@link absorbPageDelete}, with one asymmetry: delete can patch the
407
+ * cached manifest losslessly (it only REMOVES rows), but an insert needs
408
+ * manifest rows for the fresh PONs and the result doesn't carry them. So
409
+ * this absorb flips the planes (insert changes the page SET, so like
410
+ * delete it owns content + annotations alongside layout), raises the
411
+ * version floor, and DROPS the cache — the next read refetches a manifest
412
+ * that includes the new pages' rows. The UI never waits on that refetch:
413
+ * the mutation result / event already carries the full new layout.
414
+ */
415
+ private absorbPageInsert;
368
416
  /**
369
417
  * Patch the cached manifest after a metadata write. Symmetric with
370
418
  * {@link absorbPageMove}: a metadata edit advances `docVersion` (so leaf
@@ -448,6 +496,9 @@ declare class CloudDocumentPagesService implements DocumentPagesService$1 {
448
496
  move(pageObjectNumbers: PageObjectNumber[], destIndex: number): AbortablePromise$1<PageMoveResult>;
449
497
  rotate(pageObjectNumbers: PageObjectNumber[], rotation: PageRotation): AbortablePromise$1<PageRotateResult>;
450
498
  delete(pageObjectNumbers: PageObjectNumber[]): AbortablePromise$1<PageDeleteResult>;
499
+ insert(bytes: Uint8Array | ArrayBuffer, destIndex?: number): AbortablePromise$1<PageInsertResult>;
500
+ insertBlank(spec: PageInsertBlankSpec, destIndex?: number): AbortablePromise$1<PageInsertResult>;
501
+ extract(pageObjectNumbers: PageObjectNumber[]): AbortablePromise$1<Uint8Array>;
451
502
  flatten(pageObjectNumbers: PageObjectNumber[], usage?: PageFlattenUsage): AbortablePromise$1<PageFlattenResult>;
452
503
  }
453
504
  //#endregion
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AbortError, AbortError as AbortError$1, AbortablePromise, AbortablePromise as AbortablePromise$1, CONTINUOUS_RENDER_POLICY, DEFAULT_PDF_SAVE_MODE, EngineError, EngineError as EngineError$1, EngineErrorCode, EngineErrorCode as EngineErrorCode$1, canonicalSearchQuery, checkCapability, createPageImageHandle, decodePdfBits, encodeFieldRefKey, encodeStableIdKey, expandRawScope, normalizeAnnotationDraft, normalizeAnnotationPatch, normalizeAttachmentFileSource, passwordPromptFromState, searchContentEpoch, securityStateFromHead } from "@embedpdf/engine-core/runtime";
2
- import { AccessResponseSchema, AnnotationAppearanceManifestSchema, AnnotationCreateResultSchema, AnnotationDeleteResultSchema, AnnotationListPageSnapshotSchema, AnnotationMoveResultSchema, AnnotationUpdateResultSchema, AttachmentCreateResultSchema, AttachmentDeleteResultSchema, DEFAULT_LAYER_NAME, DocumentActionsSnapshotSchema, DocumentHeadSchema, DocumentManifestSchema, DocumentMetadataSchema, EmbeddedFileItemSchema, EngineErrorPayloadSchema, FormEffectsResultSchema, FormFieldCreateResultSchema, FormFieldDTOSchema, FormFieldDeleteResultSchema, FormFieldUpdateResultSchema, FormImportResultSchema, FormRepairResultSchema, FormSetValueResultSchema, FormSnapshotSchema, FormWidgetLinkResultSchema, MetadataUpdateResultSchema, PageDeleteResultSchema, PageFlattenResultSchema, PageGeometrySnapshotSchema, PageListSnapshotSchema, PageMoveResultSchema, PageRotateResultSchema, PageTextSnapshotSchema, RedactionApplyResultSchema, SearchSliceSchema, WeakAnnotationSessionResponseSchema, annotationAppearancesImageOptionsToWire, applyCdnAccess, decodeSearchToken, decodeTokenText, encodeSearchToken, renderImageOptionsToWire, wirePaths } from "@embedpdf/engine-core/wire";
2
+ import { AccessResponseSchema, AnnotationAppearanceManifestSchema, AnnotationCreateResultSchema, AnnotationDeleteResultSchema, AnnotationListPageSnapshotSchema, AnnotationMoveResultSchema, AnnotationUpdateResultSchema, AttachmentCreateResultSchema, AttachmentDeleteResultSchema, DEFAULT_LAYER_NAME, DocumentActionsSnapshotSchema, DocumentHeadSchema, DocumentManifestSchema, DocumentMetadataSchema, EmbeddedFileItemSchema, EngineErrorPayloadSchema, FormEffectsResultSchema, FormFieldCreateResultSchema, FormFieldDTOSchema, FormFieldDeleteResultSchema, FormFieldUpdateResultSchema, FormImportResultSchema, FormRepairResultSchema, FormSetValueResultSchema, FormSnapshotSchema, FormWidgetLinkResultSchema, MetadataUpdateResultSchema, PageDeleteResultSchema, PageFlattenResultSchema, PageGeometrySnapshotSchema, PageInsertResultSchema, PageListSnapshotSchema, PageMoveResultSchema, PageRotateResultSchema, PageTextSnapshotSchema, RedactionApplyResultSchema, SearchSliceSchema, WeakAnnotationSessionResponseSchema, annotationAppearancesImageOptionsToWire, applyCdnAccess, decodeSearchToken, decodeTokenText, encodeSearchToken, renderImageOptionsToWire, wirePaths } from "@embedpdf/engine-core/wire";
3
3
  import { EventHub, SessionEventPublisher, generateUuid } from "@embedpdf/engine-services";
4
4
  //#region src/document/planes.ts
5
5
  /**
@@ -449,6 +449,13 @@ var CloudDocumentFormsService = class {
449
449
  };
450
450
  //#endregion
451
451
  //#region src/document/CloudDocumentPagesService.ts
452
+ /** Detach a Uint8Array view into a standalone ArrayBuffer (the resource-map
453
+ * shape) without disturbing a larger buffer the caller still owns. */
454
+ function copyToExactBuffer(view) {
455
+ const copy = new ArrayBuffer(view.byteLength);
456
+ new Uint8Array(copy).set(view);
457
+ return copy;
458
+ }
452
459
  /**
453
460
  * Cloud-side document pages service. Mirrors `LocalDocumentPagesService`
454
461
  * over HTTP: GET /pages for `list`, POST /pages/move for the reorder.
@@ -541,6 +548,46 @@ var CloudDocumentPagesService = class {
541
548
  return result;
542
549
  });
543
550
  }
551
+ insert(bytes, destIndex) {
552
+ if (this.isClosed()) return AbortablePromise$1.rejectReason(new EngineError$1(EngineErrorCode$1.DocNotOpen, `document ${this.docId} is closed`));
553
+ return AbortablePromise$1.run(async (signal) => {
554
+ const buffer = bytes instanceof ArrayBuffer ? bytes : copyToExactBuffer(bytes);
555
+ const form = buildMutationForm(destIndex !== void 0 ? { destIndex } : {}, { source: {
556
+ bytes: buffer,
557
+ mimeType: "application/pdf",
558
+ name: "source.pdf"
559
+ } });
560
+ const result = await this.http.postMultipartJson(wirePaths.layerPagesInsert(this.docId, this.layerName), form, (raw) => PageInsertResultSchema.parse(raw), signal);
561
+ if (result.cache) this.manifest.applyPageInsert(result.cache);
562
+ this.publisher.publishLocal({
563
+ type: "pages.inserted",
564
+ destIndex,
565
+ ...result
566
+ });
567
+ return result;
568
+ });
569
+ }
570
+ insertBlank(spec, destIndex) {
571
+ if (this.isClosed()) return AbortablePromise$1.rejectReason(new EngineError$1(EngineErrorCode$1.DocNotOpen, `document ${this.docId} is closed`));
572
+ return AbortablePromise$1.run(async (signal) => {
573
+ const result = await this.http.postJson(wirePaths.layerPagesInsertBlank(this.docId, this.layerName), {
574
+ size: spec.size,
575
+ ...spec.count !== void 0 ? { count: spec.count } : {},
576
+ ...destIndex !== void 0 ? { destIndex } : {}
577
+ }, (raw) => PageInsertResultSchema.parse(raw), signal);
578
+ if (result.cache) this.manifest.applyPageInsert(result.cache);
579
+ this.publisher.publishLocal({
580
+ type: "pages.inserted",
581
+ destIndex,
582
+ ...result
583
+ });
584
+ return result;
585
+ });
586
+ }
587
+ extract(pageObjectNumbers) {
588
+ if (this.isClosed()) return AbortablePromise$1.rejectReason(new EngineError$1(EngineErrorCode$1.DocNotOpen, `document ${this.docId} is closed`));
589
+ return AbortablePromise$1.run(async (signal) => this.http.postJsonBytes(wirePaths.layerPagesExtract(this.docId, this.layerName), { pageObjectNumbers }, signal));
590
+ }
544
591
  flatten(pageObjectNumbers, usage = "display") {
545
592
  if (this.isClosed()) return AbortablePromise$1.rejectReason(new EngineError$1(EngineErrorCode$1.DocNotOpen, `document ${this.docId} is closed`));
546
593
  return AbortablePromise$1.run(async (signal) => {
@@ -809,9 +856,7 @@ var CloudDocumentSecurityService = class {
809
856
  * and `applyCdnAccess` short-circuits to origin; safe to call always.
810
857
  */
811
858
  async postAccess(signal, body) {
812
- const response = await this.http.postJson(wirePaths.access, {
813
- docId: this.docId,
814
- layerName: this.layerName,
859
+ const response = await this.http.postJson(wirePaths.access(this.docId, this.layerName), {
815
860
  ...body.password ? { password: body.password } : {},
816
861
  mode: body.mode
817
862
  }, (raw) => AccessResponseSchema.parse(raw), signal);
@@ -1341,6 +1386,12 @@ function auditRowToEvent(row, mySessionId) {
1341
1386
  origin,
1342
1387
  ...row.payload
1343
1388
  };
1389
+ case "pages.insert":
1390
+ case "pages.insertBlank": return {
1391
+ type: "pages.inserted",
1392
+ origin,
1393
+ ...row.payload
1394
+ };
1344
1395
  case "pages.flatten": return {
1345
1396
  type: "pages.flattened",
1346
1397
  origin,
@@ -1583,6 +1634,7 @@ var CloudDocumentHandle = class {
1583
1634
  apply: (meta, owns) => this.absorbMutation(meta, owns),
1584
1635
  applyPageStructure: (cache) => this.absorbPageStructure(cache),
1585
1636
  applyPageDelete: (cache, deletedPages) => this.absorbPageDelete(cache, deletedPages),
1637
+ applyPageInsert: (cache) => this.absorbPageInsert(cache),
1586
1638
  applyMetadata: (cache) => this.absorbMetadata(cache),
1587
1639
  applyAttachments: (cache) => this.absorbAttachments(cache)
1588
1640
  };
@@ -1748,6 +1800,27 @@ var CloudDocumentHandle = class {
1748
1800
  };
1749
1801
  }
1750
1802
  /**
1803
+ * Patch bookkeeping after a page insert — the mirror of
1804
+ * {@link absorbPageDelete}, with one asymmetry: delete can patch the
1805
+ * cached manifest losslessly (it only REMOVES rows), but an insert needs
1806
+ * manifest rows for the fresh PONs and the result doesn't carry them. So
1807
+ * this absorb flips the planes (insert changes the page SET, so like
1808
+ * delete it owns content + annotations alongside layout), raises the
1809
+ * version floor, and DROPS the cache — the next read refetches a manifest
1810
+ * that includes the new pages' rows. The UI never waits on that refetch:
1811
+ * the mutation result / event already carries the full new layout.
1812
+ */
1813
+ absorbPageInsert(cache) {
1814
+ this.flipScopes([
1815
+ "layout",
1816
+ "content",
1817
+ "annotations"
1818
+ ]);
1819
+ this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
1820
+ this.inflightManifest = null;
1821
+ this.manifestCache = null;
1822
+ }
1823
+ /**
1751
1824
  * Patch the cached manifest after a metadata write. Symmetric with
1752
1825
  * {@link absorbPageMove}: a metadata edit advances `docVersion` (so leaf
1753
1826
  * URLs re-resolve) and `metadataVersion` (so the /metadata leaf re-fetches),
@@ -1883,6 +1956,9 @@ var CloudDocumentHandle = class {
1883
1956
  case "pages.deleted":
1884
1957
  if (event.cache) this.absorbPageDelete(event.cache, event.pageObjectNumbers);
1885
1958
  return;
1959
+ case "pages.inserted":
1960
+ if (event.cache) this.absorbPageInsert(event.cache);
1961
+ return;
1886
1962
  case "metadata.updated":
1887
1963
  if (event.cache) this.absorbMetadata(event.cache);
1888
1964
  return;
@@ -1934,7 +2010,7 @@ function fallbackUnknownHead(id) {
1934
2010
  access: {
1935
2011
  required: true,
1936
2012
  reasons: ["permissions-unknown"],
1937
- endpoint: wirePaths.access
2013
+ endpoint: wirePaths.access(id, DEFAULT_LAYER_NAME)
1938
2014
  }
1939
2015
  };
1940
2016
  }
@@ -2042,6 +2118,8 @@ var HttpClient = class HttpClient {
2042
2118
  _defineProperty(this, "base", void 0);
2043
2119
  _defineProperty(this, "tokenFn", void 0);
2044
2120
  _defineProperty(this, "fetchFn", void 0);
2121
+ _defineProperty(this, "docAffinityHeader", void 0);
2122
+ _defineProperty(this, "onRetry", void 0);
2045
2123
  _defineProperty(this, "sessionId", void 0);
2046
2124
  _defineProperty(this, "cdnBinding", null);
2047
2125
  this.base = opts.baseUrl.replace(/\/$/, "");
@@ -2049,6 +2127,8 @@ var HttpClient = class HttpClient {
2049
2127
  this.tokenFn = token === void 0 ? null : typeof token === "function" ? token : () => token;
2050
2128
  this.sessionId = opts.sessionId ?? null;
2051
2129
  this.fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
2130
+ this.docAffinityHeader = opts.docAffinityHeader ?? true;
2131
+ this.onRetry = opts.onRetry;
2052
2132
  }
2053
2133
  /** Normalized server base URL (no trailing slash). */
2054
2134
  get baseUrl() {
@@ -2279,6 +2359,12 @@ var HttpClient = class HttpClient {
2279
2359
  const res = await this.requestJson(path, "POST", body, signal);
2280
2360
  return await this.parseJsonResponse(res, parser);
2281
2361
  }
2362
+ /** POST a JSON body and return the raw binary response (pages.extract). */
2363
+ async postJsonBytes(path, body, signal) {
2364
+ const res = await this.requestJson(path, "POST", body, signal);
2365
+ if (!res.ok) await this.throwFromBody(res);
2366
+ return new Uint8Array(await res.arrayBuffer());
2367
+ }
2282
2368
  /** POST a raw binary body (FDF/XFDF import) and parse the JSON response. */
2283
2369
  async postBytesJson(path, bytes, contentType, parser, signal) {
2284
2370
  const headers = new Headers({ "Content-Type": contentType });
@@ -2325,7 +2411,7 @@ var HttpClient = class HttpClient {
2325
2411
  });
2326
2412
  }
2327
2413
  async request(path, init) {
2328
- const { url, extraHeader } = this.resolveOutgoing(path);
2414
+ const { url, extraHeader, routedToCdn } = this.resolveOutgoing(path);
2329
2415
  const headers = new Headers(init.headers ?? {});
2330
2416
  if (this.tokenFn) {
2331
2417
  const token = await this.tokenFn();
@@ -2334,11 +2420,32 @@ var HttpClient = class HttpClient {
2334
2420
  if (!headers.has("Accept")) headers.set("Accept", "application/json");
2335
2421
  if (this.sessionId) headers.set("X-Engine-Session-Id", this.sessionId);
2336
2422
  if (extraHeader) headers.set(extraHeader.name, extraHeader.value);
2337
- try {
2338
- return await this.fetchFn(url, {
2423
+ if (this.docAffinityHeader && !routedToCdn) {
2424
+ const doc = /^\/v1\/docs\/([^/]+)\//.exec(path);
2425
+ if (doc) headers.set("X-CloudPDF-Doc", decodeURIComponent(doc[1]));
2426
+ }
2427
+ for (let attempt = 0;; attempt++) {
2428
+ const res = await this.execute(url, {
2339
2429
  ...init,
2340
2430
  headers
2341
2431
  });
2432
+ if (res.status !== 503 || attempt >= HttpClient.MAX_RETRIES || isStreamBody(init.body ?? null)) return res;
2433
+ const code = await peekErrorCode(res);
2434
+ if (code === null || !RETRYABLE_503.has(code)) return res;
2435
+ const hinted = Number(res.headers.get("retry-after"));
2436
+ 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());
2437
+ this.onRetry?.({
2438
+ path,
2439
+ code,
2440
+ attempt,
2441
+ waitMs
2442
+ });
2443
+ await sleepRacingAbort(waitMs, init.signal ?? null);
2444
+ }
2445
+ }
2446
+ async execute(url, init) {
2447
+ try {
2448
+ return await this.fetchFn(url, init);
2342
2449
  } catch (err) {
2343
2450
  if (init.signal?.aborted) throw new AbortError$1(init.signal.reason ?? err);
2344
2451
  if (err?.name === "AbortError") throw new AbortError$1(err.message);
@@ -2364,7 +2471,8 @@ var HttpClient = class HttpClient {
2364
2471
  resolveOutgoing(path) {
2365
2472
  if (!this.cdnBinding || !this.cdnBinding.cdn) return {
2366
2473
  url: `${this.baseUrl}${path}`,
2367
- extraHeader: null
2474
+ extraHeader: null,
2475
+ routedToCdn: false
2368
2476
  };
2369
2477
  const applied = applyCdnAccess({
2370
2478
  path,
@@ -2375,7 +2483,8 @@ var HttpClient = class HttpClient {
2375
2483
  });
2376
2484
  return {
2377
2485
  url: applied.url,
2378
- extraHeader: applied.routedToCdn && applied.authHeader ? applied.authHeader : null
2486
+ extraHeader: applied.routedToCdn && applied.authHeader ? applied.authHeader : null,
2487
+ routedToCdn: applied.routedToCdn
2379
2488
  };
2380
2489
  }
2381
2490
  async throwFromBody(res) {
@@ -2395,6 +2504,9 @@ var HttpClient = class HttpClient {
2395
2504
  throw new EngineError$1(code, `HTTP ${res.status}`, { details: { status: res.status } });
2396
2505
  }
2397
2506
  };
2507
+ _defineProperty(HttpClient, "MAX_RETRIES", 2);
2508
+ _defineProperty(HttpClient, "RETRY_AFTER_CAP_MS", 5e3);
2509
+ _defineProperty(HttpClient, "RETRY_AFTER_DEFAULT_MS", 1e3);
2398
2510
  function mapStatusToCode(status) {
2399
2511
  if (status === 401) return EngineErrorCode$1.Unauthenticated;
2400
2512
  if (status === 403) return EngineErrorCode$1.Forbidden;
@@ -2409,6 +2521,36 @@ function trySerialized(value) {
2409
2521
  const result = EngineErrorPayloadSchema.safeParse(value);
2410
2522
  return result.success ? result.data : null;
2411
2523
  }
2524
+ const RETRYABLE_503 = /* @__PURE__ */ new Set(["EngineBusy", "EngineRestarting"]);
2525
+ function isStreamBody(body) {
2526
+ return typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
2527
+ }
2528
+ /** Read the error code from a 503 body without consuming the response. */
2529
+ async function peekErrorCode(res) {
2530
+ try {
2531
+ const code = (await res.clone().json())?.error?.code;
2532
+ return typeof code === "string" ? code : null;
2533
+ } catch {
2534
+ return null;
2535
+ }
2536
+ }
2537
+ function sleepRacingAbort(ms, signal) {
2538
+ return new Promise((resolve, reject) => {
2539
+ if (signal?.aborted) {
2540
+ reject(new AbortError$1(signal.reason ?? "aborted"));
2541
+ return;
2542
+ }
2543
+ const timer = setTimeout(() => {
2544
+ signal?.removeEventListener("abort", onAbort);
2545
+ resolve();
2546
+ }, ms);
2547
+ const onAbort = () => {
2548
+ clearTimeout(timer);
2549
+ reject(new AbortError$1(signal?.reason ?? "aborted"));
2550
+ };
2551
+ signal?.addEventListener("abort", onAbort, { once: true });
2552
+ });
2553
+ }
2412
2554
  //#endregion
2413
2555
  //#region src/CloudEngine.ts
2414
2556
  /**