@cloudpdf/engine 3.0.0-next.13 → 3.0.0-next.15

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 CHANGED
@@ -1,11 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
3
3
  let _embedpdf_engine_core_wire = require("@embedpdf/engine-core/wire");
4
- let _embedpdf_engine_services = require("@embedpdf/engine-services");
5
4
  //#region src/document/planes.ts
6
5
  /**
7
- * Plane-scope resolution — THE one rule every cloud service uses to pick a
8
- * path family: a read resolves at the DOC-LEVEL (shared base) path iff EVERY
6
+ * Plane-scope resolution — the one rule every cloud service uses to pick a
7
+ * path family: a read resolves at the doc-level (shared base) path iff every
9
8
  * plane it depends on is inherited (`'base'`) by this layer. One owned plane
10
9
  * → the layer-scoped path.
11
10
  *
@@ -27,7 +26,7 @@ var CloudDocumentActionsService = class {
27
26
  this.isClosed = isClosed;
28
27
  this.manifest = manifest;
29
28
  }
30
- read() {
29
+ get() {
31
30
  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`));
32
31
  return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJsonWithRefresh(async (currentSignal) => {
33
32
  const manifest = await this.manifest.get(currentSignal);
@@ -38,42 +37,48 @@ var CloudDocumentActionsService = class {
38
37
  }
39
38
  };
40
39
  //#endregion
41
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/typeof.js
42
- function _typeof(o) {
43
- "@babel/helpers - typeof";
44
- return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
45
- return typeof o;
46
- } : function(o) {
47
- return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
48
- }, _typeof(o);
49
- }
50
- //#endregion
51
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPrimitive.js
52
- function toPrimitive(t, r) {
53
- if ("object" != _typeof(t) || !t) return t;
54
- var e = t[Symbol.toPrimitive];
55
- if (void 0 !== e) {
56
- var i = e.call(t, r || "default");
57
- if ("object" != _typeof(i)) return i;
58
- throw new TypeError("@@toPrimitive must return a primitive value.");
40
+ //#region src/transport/bundleMultipart.ts
41
+ /** The server holds the bundle limits; the client only checks what arrived. */
42
+ const NO_BUNDLE_LIMITS = {
43
+ bundleBytes: Infinity,
44
+ manifestBytes: Infinity,
45
+ items: Infinity,
46
+ pages: Infinity,
47
+ resources: Infinity,
48
+ resourceBytes: Infinity,
49
+ imagePixels: Infinity
50
+ };
51
+ /**
52
+ * An exported bundle as the server sends it: the `body` part (the bundle
53
+ * without its bytes, as JSON) and one `resource:<id>` part per resource.
54
+ * The caller checks the bundle.
55
+ */
56
+ async function readBundleParts(kind, form) {
57
+ const body = form.get("body");
58
+ if (typeof body !== "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `${kind} export has no body part`);
59
+ const resources = {};
60
+ const parts = [];
61
+ form.forEach((value, name) => parts.push([name, value]));
62
+ for (const [name, value] of parts) {
63
+ if (!name.startsWith("resource:")) continue;
64
+ if (typeof value === "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `${kind} export part ${name} is text`);
65
+ resources[name.slice(9)] = new Uint8Array(await value.arrayBuffer());
59
66
  }
60
- return ("string" === r ? String : Number)(t);
61
- }
62
- //#endregion
63
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPropertyKey.js
64
- function toPropertyKey(t) {
65
- var i = toPrimitive(t, "string");
66
- return "symbol" == _typeof(i) ? i : i + "";
67
+ return {
68
+ body: JSON.parse(body),
69
+ resources
70
+ };
67
71
  }
68
- //#endregion
69
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/defineProperty.js
70
- function _defineProperty(e, r, t) {
71
- return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
72
- value: t,
73
- enumerable: !0,
74
- configurable: !0,
75
- writable: !0
76
- }) : e[r] = t, e;
72
+ /**
73
+ * A bundle import as one multipart body: the `body` part (the bundle
74
+ * without its bytes, and the options), then each resource once, as a
75
+ * `resource:<id>` part.
76
+ */
77
+ function bundleImportForm(body, resources) {
78
+ const form = new FormData();
79
+ form.append("body", JSON.stringify(body));
80
+ for (const [id, bytes] of Object.entries(resources)) form.append(`resource:${id}`, new Blob([bytes]), id);
81
+ return form;
77
82
  }
78
83
  //#endregion
79
84
  //#region src/document/CloudDocumentAnnotationsService.ts
@@ -81,29 +86,41 @@ function _defineProperty(e, r, t) {
81
86
  * version (404 on the immutable leaf → refresh the manifest → retry). */
82
87
  const MAX_COHERENCE_RESTARTS = 2;
83
88
  var CloudDocumentAnnotationsService = class {
84
- constructor(http, docId, layerName, isClosed, manifest) {
89
+ constructor(http, docId, layerName, isClosed, manifest, publisher, writes, importLimits) {
85
90
  this.http = http;
86
91
  this.docId = docId;
87
92
  this.layerName = layerName;
88
93
  this.isClosed = isClosed;
89
94
  this.manifest = manifest;
95
+ this.publisher = publisher;
96
+ this.writes = writes;
97
+ this.importLimits = importLimits;
90
98
  }
91
99
  /**
92
- * One COHERENT whole-document snapshot: a single read of the immutable
93
- * `annotations/items@annotationsVersion=N` leaf at the manifest's pin —
94
- * materialized server-side by one raw (no page-load) sweep, CDN-cacheable
95
- * because the pin bumps only when annotation list bodies actually change.
96
- * A stale pin mid-read (a concurrent mutation → 404) refreshes the
97
- * manifest and retries, so the result always belongs to one document
98
- * moment (the torn read this method exists to prevent).
100
+ * Every page: one coherent whole-document snapshot, a single read of the
101
+ * immutable `annotations/items@annotationsVersion=N` leaf at the
102
+ * manifest's pin — materialized server-side by one raw (no page-load)
103
+ * sweep, CDN-cacheable because the pin bumps only when annotation list
104
+ * bodies actually change. A stale pin mid-read (a concurrent mutation →
105
+ * 404) refreshes the manifest and retries, so the result always belongs
106
+ * to one document moment (the torn read this method exists to prevent).
107
+ *
108
+ * Some pages: each page's versioned leaf, the same read as
109
+ * `page.annotations.list()`, in the order asked.
99
110
  */
100
- listRawAll() {
111
+ list(options = {}) {
101
112
  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`));
113
+ const { pages } = options;
114
+ if (pages !== void 0) return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => (0, _embedpdf_engine_core_runtime.concatAnnotationLists)(await Promise.all(pages.map((page) => this.readPage(page, signal)))));
102
115
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
103
116
  for (let attempt = 0;; attempt++) {
104
117
  const manifest = attempt === 0 ? await this.manifest.get(signal) : await this.manifest.refresh(signal);
105
118
  try {
106
- return await this.readBulkAt(manifest, signal);
119
+ const list = await this.readBulkAt(manifest, signal);
120
+ return {
121
+ ...list,
122
+ auditHead: list.auditHead ?? manifest.auditHead
123
+ };
107
124
  } catch (err) {
108
125
  if (!_embedpdf_engine_core_runtime.EngineError.is(err, _embedpdf_engine_core_runtime.EngineErrorCode.NotFound) || attempt >= MAX_COHERENCE_RESTARTS) throw err;
109
126
  }
@@ -117,90 +134,120 @@ var CloudDocumentAnnotationsService = class {
117
134
  * that window over the body is all no-ops. */
118
135
  async readBulkAt(manifest, signal) {
119
136
  const path = planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docAnnotationsAll(this.docId, manifest.annotationsVersion) : _embedpdf_engine_core_wire.wirePaths.layerAnnotationsAll(this.docId, this.layerName, manifest.annotationsVersion);
120
- const body = await this.http.getJson(path, (raw) => _embedpdf_engine_core_wire.AnnotationListSnapshotAllPagesSchema.parse(raw), signal);
121
- return {
122
- ...body,
123
- auditHead: body.auditHead ?? manifest.auditHead
124
- };
137
+ return this.http.getJson(path, (raw) => _embedpdf_engine_core_wire.AnnotationListSchema.parse(raw), signal);
125
138
  }
126
- listRaw(pageObjectNumber) {
127
- 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`));
128
- return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJsonWithRefresh(async (s) => this.versionedPagePath(await this.manifest.get(s), pageObjectNumber), (raw) => _embedpdf_engine_core_wire.AnnotationListPageSnapshotSchema.parse(raw), async (s) => {
139
+ /** One page's versioned leaf, with the standard stale-pin retry
140
+ * (404 → refresh the manifest → once). */
141
+ readPage(page, signal) {
142
+ return this.http.getJsonWithRefresh(async (s) => this.versionedPagePath(await this.manifest.get(s), page.objectNumber), (raw) => _embedpdf_engine_core_wire.AnnotationListSchema.parse(raw), async (s) => {
129
143
  await this.manifest.refresh(s);
130
- }, signal));
144
+ }, signal);
131
145
  }
132
- /** Versioned leaf URL for one manifest page entry, with the same
133
- * plane routing as `CloudPageAnnotationsService.list()`: an inherited
134
- * `annotations` plane reads the doc-level base leaf. */
135
- pagePathAt(manifest, page) {
136
- return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotations(this.docId, page.state.pageObjectNumber, page.cache.annotationVersion) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotations(this.docId, this.layerName, page.state.pageObjectNumber, page.cache.annotationVersion);
137
- }
138
- versionedPagePath(manifest, pageObjectNumber) {
139
- const page = manifest.pages.find((p) => p.state.pageObjectNumber === pageObjectNumber);
140
- if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
141
- return this.pagePathAt(manifest, page);
142
- }
143
- beginWeakEdit(pageObjectNumbers) {
146
+ /**
147
+ * One versioned, CDN-cacheable read at the manifest's annotation and
148
+ * layout pins, with the stale-pin retry (404 → refresh the manifest →
149
+ * once). The response is the bundle without its bytes and one part per
150
+ * resource; every part is checked against its id before it is returned.
151
+ */
152
+ export(selection = {}) {
144
153
  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`));
145
154
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
146
- const response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerWeakAnnotationSession(this.docId, this.layerName), { pageObjectNumbers }, (raw) => _embedpdf_engine_core_wire.WeakAnnotationSessionResponseSchema.parse(raw), signal);
147
- return new CloudWeakAnnotationEditSession(this.http, this.docId, this.layerName, () => this.isClosed(), response);
155
+ const read = async (s) => {
156
+ const manifest = await this.manifest.get(s);
157
+ const path = this.exportPathAt(manifest, selection);
158
+ if (path) return this.http.getFormData(path, s);
159
+ return this.http.postJsonFormData(_embedpdf_engine_core_wire.wirePaths.layerAnnotationsExportRequest(this.docId, this.layerName), {
160
+ annotationsVersion: manifest.annotationsVersion,
161
+ layoutVersion: manifest.layoutVersion,
162
+ selection
163
+ }, s);
164
+ };
165
+ let form;
166
+ try {
167
+ form = await read(signal);
168
+ } catch (error) {
169
+ if (!_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.NotFound)) throw error;
170
+ await this.manifest.refresh(signal);
171
+ form = await read(signal);
172
+ }
173
+ const { body, resources } = await readBundleParts("annotation", form);
174
+ const bundle = {
175
+ ...body,
176
+ resources
177
+ };
178
+ await (0, _embedpdf_engine_core_runtime.assertAnnotationBundle)(bundle, NO_BUNDLE_LIMITS);
179
+ return bundle;
148
180
  });
149
181
  }
150
- };
151
- var CloudWeakAnnotationEditSession = class {
152
- constructor(http, docId, layerName, isClosed, response) {
153
- this.http = http;
154
- this.docId = docId;
155
- this.layerName = layerName;
156
- this.isClosed = isClosed;
157
- _defineProperty(this, "response", void 0);
158
- _defineProperty(this, "released", false);
159
- this.response = response;
160
- }
161
- get id() {
162
- return this.response.sessionId;
163
- }
164
- get expiresAt() {
165
- return this.response.expiresAt;
166
- }
167
- get heartbeatIntervalMs() {
168
- return this.response.heartbeatIntervalMs;
169
- }
170
- get pageObjectNumbers() {
171
- return this.response.pageObjectNumbers;
172
- }
173
- covers(pageObjectNumber) {
174
- return this.response.pageObjectNumbers.includes(pageObjectNumber);
175
- }
176
- updatePages(pageObjectNumbers) {
177
- if (this.isClosed() || this.released) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `weak annotation session ${this.id} is closed`));
182
+ /**
183
+ * One request: the bundle's manifest and each resource once, as parts of
184
+ * one multipart POST under the import's `opId` as `Idempotency-Key`, so a
185
+ * retry applies once. The server holds the limits; the same numbers are checked
186
+ * here first, so a bundle past one fails before its bytes move.
187
+ */
188
+ import(bundle, options = {}) {
189
+ 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`));
178
190
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
179
- this.response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerWeakAnnotationSessionPages(this.docId, this.layerName, this.id), { pageObjectNumbers }, (raw) => _embedpdf_engine_core_wire.WeakAnnotationSessionResponseSchema.parse(raw), signal);
191
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
192
+ return this.writes.run(opId, signal, async (write) => {
193
+ const { resources, ...rest } = bundle;
194
+ (0, _embedpdf_engine_core_runtime.assertAnnotationBundleManifest)(bundle, new Map(Object.entries(resources).map(([id, bytes]) => [id, bytes.length])), await this.importLimits());
195
+ const form = bundleImportForm({
196
+ bundle: rest,
197
+ options: {
198
+ ...options.pages !== void 0 ? { pages: options.pages } : {},
199
+ ...options.attribution !== void 0 ? { attribution: options.attribution } : {}
200
+ }
201
+ }, resources);
202
+ const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerAnnotationsImport(this.docId, this.layerName), form, (raw) => _embedpdf_engine_core_wire.AnnotationImportResultSchema.parse(raw), signal, sent));
203
+ this.manifest.apply(result.meta, ["annotations"]);
204
+ this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.annotationImportFacts)(result).map((fact) => ({
205
+ type: "annotations.created",
206
+ ...fact
207
+ })));
208
+ return result;
209
+ });
180
210
  });
181
211
  }
182
- heartbeat() {
183
- if (this.isClosed() || this.released) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `weak annotation session ${this.id} is closed`));
184
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
185
- this.response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerWeakAnnotationSessionHeartbeat(this.docId, this.layerName, this.id), {}, (raw) => _embedpdf_engine_core_wire.WeakAnnotationSessionResponseSchema.parse(raw), signal);
186
- });
212
+ /**
213
+ * The cacheable export URL for `selection` at the manifest's pins — the
214
+ * base leaf while the layer inherits both planes the bundle depends on —
215
+ * or `null` when a URL can't carry it: a URL has room for a few hundred
216
+ * refs.
217
+ */
218
+ exportPathAt(manifest, selection) {
219
+ const token = {
220
+ annotationsVersion: manifest.annotationsVersion,
221
+ layoutVersion: manifest.layoutVersion,
222
+ selection
223
+ };
224
+ try {
225
+ return planesInherited(manifest, ["annotations", "layout"]) ? _embedpdf_engine_core_wire.wirePaths.docAnnotationsExport(this.docId, token) : _embedpdf_engine_core_wire.wirePaths.layerAnnotationsExport(this.docId, this.layerName, token);
226
+ } catch {
227
+ return null;
228
+ }
187
229
  }
188
- release() {
189
- if (this.released) return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue(void 0);
190
- this.released = true;
191
- return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.deleteEmpty(_embedpdf_engine_core_wire.wirePaths.layerWeakAnnotationSessionRelease(this.docId, this.layerName, this.id), signal));
230
+ /** Versioned leaf URL for one manifest page entry, with the same
231
+ * plane routing as `CloudPageAnnotationsService.list()`: an inherited
232
+ * `annotations` plane reads the doc-level base leaf. */
233
+ pagePathAt(manifest, page) {
234
+ const ref = page.page;
235
+ return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotations(this.docId, ref, page.cache.annotationVersion) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotations(this.docId, this.layerName, ref, page.cache.annotationVersion);
236
+ }
237
+ versionedPagePath(manifest, pageObjectNumber) {
238
+ const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
239
+ if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
240
+ return this.pagePathAt(manifest, page);
192
241
  }
193
242
  };
194
243
  //#endregion
195
244
  //#region src/document/buildMutationForm.ts
196
245
  /**
197
- * Multipart envelope for mutations that carry binaries: part `body` holds
198
- * the exact JSON the plain request would have been, plus one
199
- * `resource:{key}` file part per binary payload. Mirrors the appearance
200
- * response protocol (`manifest` part + named image parts) in reverse.
201
- * Shared by the annotation mutations (stamp images, attachment files) and
202
- * the document-level `attachments.create` — one envelope, one parser on
203
- * the server side.
246
+ * The multipart envelope of a write that carries files, in the shape every
247
+ * multipart message has: the `body` part holds the JSON the plain request
248
+ * would have been, which names each file by role
249
+ * (`resources: { <role>: <key> }`), and each file is the part
250
+ * `resource:<key>`.
204
251
  */
205
252
  function buildMutationForm(body, resources) {
206
253
  const form = new FormData();
@@ -208,21 +255,46 @@ function buildMutationForm(body, resources) {
208
255
  for (const [key, resource] of Object.entries(resources)) form.append(`resource:${key}`, new Blob([resource.bytes], { type: resource.mimeType ?? "application/octet-stream" }), resource.name ?? key);
209
256
  return form;
210
257
  }
211
- function hasResources(resources) {
212
- return Object.keys(resources).length > 0;
258
+ /**
259
+ * The envelope of a write with at most one file per role (an annotation's
260
+ * drawing or attached file, a page insert's source PDF, a signature's
261
+ * mark): each file's key is its role's name, and the body names it so,
262
+ * `resources: { <role>: '<role>' }`.
263
+ */
264
+ function buildRoleMutationForm(body, files) {
265
+ const parts = {};
266
+ const resources = {};
267
+ for (const [role, file] of Object.entries(files)) {
268
+ if (!file) continue;
269
+ parts[role] = file;
270
+ resources[role] = role;
271
+ }
272
+ return buildMutationForm(Object.keys(resources).length > 0 ? {
273
+ ...body,
274
+ resources
275
+ } : body, parts);
276
+ }
277
+ /** An annotation write's envelope: its drawing and attached file, by role. */
278
+ function buildAnnotationMutationForm(body, resources) {
279
+ return buildRoleMutationForm(body, {
280
+ appearance: resources.appearance && { bytes: resources.appearance },
281
+ file: resources.file && { bytes: resources.file }
282
+ });
213
283
  }
214
284
  //#endregion
215
285
  //#region src/document/parseAttachmentContent.ts
216
286
  /** Header carrying the file's name, token-text encoded (names are
217
287
  * arbitrary UTF-8; HTTP header values are not). */
218
288
  const FILE_NAME_HEADER = "X-EmbedPDF-File-Name";
289
+ /** Header carrying the file's declared type; absent when it has none. */
290
+ const FILE_TYPE_HEADER = "X-EmbedPDF-File-Type";
219
291
  /**
220
292
  * Project an attachment-file response into `AttachmentContent`. The body
221
- * IS the decoded bytes; the metadata rides as headers — `Content-Type`
222
- * for the declared mime type, `X-EmbedPDF-File-Name` for the file name.
223
- * Shared by the document-level `attachments.download()` and the
224
- * annotation-level `annotations.downloadFile()` reads — one wire shape
225
- * for both homes a file can live in.
293
+ * is the decoded bytes; the metadata rides as headers — `X-EmbedPDF-File-Type`
294
+ * for the declared type (`Content-Type` is only what HTTP needs), and
295
+ * `X-EmbedPDF-File-Name` for the file name.
296
+ * Used by the document-level `attachments.download()`; the annotation-level
297
+ * `annotations.downloadResource(ref, 'file')` reads the same response's bytes.
226
298
  */
227
299
  function parseAttachmentContent(file) {
228
300
  const encodedName = file.headers.get(FILE_NAME_HEADER);
@@ -233,17 +305,15 @@ function parseAttachmentContent(file) {
233
305
  } catch (err) {
234
306
  throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `malformed ${FILE_NAME_HEADER} header: ${err?.message ?? err}`, { cause: err });
235
307
  }
236
- const mimeType = file.headers.get("Content-Type");
237
308
  return {
238
309
  bytes: file.bytes,
239
310
  name,
240
- ...mimeType !== null ? { mimeType } : {}
311
+ mimeType: file.headers.get(FILE_TYPE_HEADER)
241
312
  };
242
313
  }
243
314
  //#endregion
244
315
  //#region src/document/CloudDocumentAttachmentsService.ts
245
316
  /** The `/attachments@…` listing leaf is a bare array of name-tree entries. */
246
- const EmbeddedFileListSchema = _embedpdf_engine_core_wire.EmbeddedFileItemSchema.array();
247
317
  /**
248
318
  * Cloud-side document-level attachments (the catalog's `/EmbeddedFiles`
249
319
  * name tree). Reads use immutable leaves pinned by the manifest's
@@ -254,13 +324,14 @@ const EmbeddedFileListSchema = _embedpdf_engine_core_wire.EmbeddedFileItemSchema
254
324
  * never cached.
255
325
  */
256
326
  var CloudDocumentAttachmentsService = class {
257
- constructor(http, docId, layerName, isClosed, manifest, publisher) {
327
+ constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
258
328
  this.http = http;
259
329
  this.docId = docId;
260
330
  this.layerName = layerName;
261
331
  this.isClosed = isClosed;
262
332
  this.manifest = manifest;
263
333
  this.publisher = publisher;
334
+ this.writes = writes;
264
335
  }
265
336
  /**
266
337
  * Snapshot of the name tree, in tree (key-sorted) order. Pulls
@@ -276,7 +347,7 @@ var CloudDocumentAttachmentsService = class {
276
347
  const manifest = await this.manifest.get(s);
277
348
  return planesInherited(manifest, ["attachments"]) ? _embedpdf_engine_core_wire.wirePaths.docAttachments(this.docId, manifest.attachmentsVersion) : _embedpdf_engine_core_wire.wirePaths.layerAttachments(this.docId, this.layerName, manifest.attachmentsVersion);
278
349
  };
279
- return this.http.getJsonWithRefresh(buildPath, (raw) => EmbeddedFileListSchema.parse(raw), async (s) => {
350
+ return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.AttachmentListSchema.parse(raw), async (s) => {
280
351
  await this.manifest.refresh(s);
281
352
  }, signal);
282
353
  });
@@ -300,195 +371,585 @@ var CloudDocumentAttachmentsService = class {
300
371
  });
301
372
  }
302
373
  /**
303
- * Create an embedded file in the name tree. Same splitter the
304
- * file-attachment annotation draft uses — metadata into the `body`
305
- * JSON part, bytes into the `resource:r0` file part — POSTed as the
306
- * standard multipart mutation envelope.
374
+ * Create an embedded file in the name tree: metadata into the `body`
375
+ * part, naming its file `resources: { file: 'file' }`, and the bytes in
376
+ * the part `resource:file`.
307
377
  */
308
- create(file) {
378
+ create(file, options) {
309
379
  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`));
310
380
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
311
- const { wireFile, resource } = await (0, _embedpdf_engine_core_runtime.normalizeAttachmentFileSource)(file, "r0");
312
- const result = await this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerAttachmentsCollection(this.docId, this.layerName), buildMutationForm(wireFile, { r0: resource }), (raw) => _embedpdf_engine_core_wire.AttachmentCreateResultSchema.parse(raw), signal);
313
- if (result.cache) this.manifest.applyAttachments(result.cache);
314
- this.publisher.publishLocal({
315
- type: "attachment.created",
316
- ...result
381
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
382
+ return this.writes.run(opId, signal, async (write) => {
383
+ const { wireFile, resource } = await (0, _embedpdf_engine_core_runtime.normalizeAttachmentFileSource)(file, "file");
384
+ const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerAttachmentsCollection(this.docId, this.layerName), buildMutationForm(wireFile, { file: resource }), (raw) => _embedpdf_engine_core_wire.AttachmentCreateResultSchema.parse(raw), signal, sent));
385
+ this.manifest.apply(result.meta, ["attachments"]);
386
+ this.publisher.publishWrite(opId, {
387
+ type: "attachments.created",
388
+ ...result
389
+ });
390
+ return result;
317
391
  });
318
- return result;
319
392
  });
320
393
  }
321
394
  /** Delete an embedded file from the name tree by its durable key. */
322
- delete(ref) {
395
+ delete(ref, options) {
323
396
  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`));
324
397
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
325
- const result = await this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerAttachmentItem(this.docId, this.layerName, ref.key), (raw) => _embedpdf_engine_core_wire.AttachmentDeleteResultSchema.parse(raw), signal);
326
- if (result.cache) this.manifest.applyAttachments(result.cache);
327
- this.publisher.publishLocal({
328
- type: "attachment.deleted",
329
- ...result
398
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
399
+ return this.writes.run(opId, signal, async (write) => {
400
+ const result = await write.send((sent) => this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerAttachmentItem(this.docId, this.layerName, ref.key), (raw) => _embedpdf_engine_core_wire.AttachmentDeleteResultSchema.parse(raw), signal, sent));
401
+ this.manifest.apply(result.meta, ["attachments"]);
402
+ this.publisher.publishWrite(opId, {
403
+ type: "attachments.deleted",
404
+ deleted: (0, _embedpdf_engine_core_runtime.deletedAttachmentOf)(result),
405
+ ...result
406
+ });
407
+ return result;
330
408
  });
331
- return result;
332
409
  });
333
410
  }
334
411
  };
335
412
  //#endregion
336
- //#region src/document/CloudDocumentFormsService.ts
337
- /** Content types the import POST body may carry; the server sniffs the
338
- * actual format from the bytes, so this is advisory only. */
339
- const IMPORT_CONTENT_TYPE = {
340
- fdf: "application/vnd.fdf",
341
- xfdf: "application/vnd.adobe.xfdf"
413
+ //#region \0@oxc-project+runtime@0.140.0/helpers/esm/typeof.js
414
+ function _typeof(o) {
415
+ "@babel/helpers - typeof";
416
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
417
+ return typeof o;
418
+ } : function(o) {
419
+ return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
420
+ }, _typeof(o);
421
+ }
422
+ //#endregion
423
+ //#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPrimitive.js
424
+ function toPrimitive(t, r) {
425
+ if ("object" != _typeof(t) || !t) return t;
426
+ var e = t[Symbol.toPrimitive];
427
+ if (void 0 !== e) {
428
+ var i = e.call(t, r || "default");
429
+ if ("object" != _typeof(i)) return i;
430
+ throw new TypeError("@@toPrimitive must return a primitive value.");
431
+ }
432
+ return ("string" === r ? String : Number)(t);
433
+ }
434
+ //#endregion
435
+ //#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPropertyKey.js
436
+ function toPropertyKey(t) {
437
+ var i = toPrimitive(t, "string");
438
+ return "symbol" == _typeof(i) ? i : i + "";
439
+ }
440
+ //#endregion
441
+ //#region \0@oxc-project+runtime@0.140.0/helpers/esm/defineProperty.js
442
+ function _defineProperty(e, r, t) {
443
+ return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
444
+ value: t,
445
+ enumerable: !0,
446
+ configurable: !0,
447
+ writable: !0
448
+ }) : e[r] = t, e;
449
+ }
450
+ //#endregion
451
+ //#region src/document/CloudDocumentChanges.ts
452
+ /** How many `opId`s a handle remembers having published: far more than any retry spans. */
453
+ const PUBLISHED_MEMORY = 4096;
454
+ /**
455
+ * `doc.apply` on the cloud: each change takes its place in the document's
456
+ * line of writes, and goes out with the changes called right after it, as
457
+ * one `POST …/changes` (see {@link CloudWrites.change}). The server checks
458
+ * every op against the caller inside the write, so nothing about the ops is
459
+ * checked here beyond what the request must hold.
460
+ *
461
+ * A change's events are published once its answer arrives, all sharing its
462
+ * `opId`. A change asked again gets the first answer and publishes nothing:
463
+ * this handle remembers the `opId`s it published.
464
+ */
465
+ var CloudDocumentChanges = class {
466
+ constructor(isClosed, manifest, publisher, writes) {
467
+ this.isClosed = isClosed;
468
+ this.manifest = manifest;
469
+ this.publisher = publisher;
470
+ this.writes = writes;
471
+ _defineProperty(this, "published", /* @__PURE__ */ new Set());
472
+ }
473
+ apply(change, options) {
474
+ if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, "document is closed"));
475
+ let opId;
476
+ try {
477
+ opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
478
+ } catch (err) {
479
+ return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
480
+ }
481
+ if (!(0, _embedpdf_engine_core_runtime.isUndoChange)(change) && change.ops.length === 0) return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue({
482
+ items: [],
483
+ meta: {
484
+ affectedPages: [],
485
+ cacheDelta: null,
486
+ opId,
487
+ undoable: false
488
+ }
489
+ });
490
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
491
+ const answer = await this.writes.change(signal, sentChangeOf(opId, change), {
492
+ named: (0, _embedpdf_engine_core_runtime.objectNumbersNamedBy)(change),
493
+ alone: carriesBytes(change)
494
+ });
495
+ if (answer.status === "refused") throw (0, _embedpdf_engine_core_runtime.deserializeError)(answer.error);
496
+ this.absorb(opId, change, answer.result);
497
+ return answer.result;
498
+ });
499
+ }
500
+ /**
501
+ * Patch the cached manifest, then publish the change's events (in that
502
+ * order: listeners reading the manifest see the change), unless this
503
+ * handle published them already.
504
+ */
505
+ absorb(opId, change, result) {
506
+ this.manifest.apply(result.meta, planesOf(result));
507
+ if (this.published.has(opId)) return;
508
+ this.published.add(opId);
509
+ if (this.published.size > PUBLISHED_MEMORY) this.published.delete(this.published.values().next().value);
510
+ const undoOf = (0, _embedpdf_engine_core_runtime.isUndoChange)(change) ? change.undoOf : void 0;
511
+ this.publisher.publishChange(opId, (0, _embedpdf_engine_core_runtime.changeEvents)(result), undoOf);
512
+ }
342
513
  };
343
514
  /**
515
+ * The sender of a document's requests of changes: JSON, or multipart when
516
+ * any change carries bytes, each op naming its parts by key.
517
+ */
518
+ function changeSender(http, docId, layerName) {
519
+ const path = _embedpdf_engine_core_wire.wirePaths.layerChanges(docId, layerName);
520
+ const parse = (raw) => _embedpdf_engine_core_wire.ChangeResponseSchema.parse(raw).changes;
521
+ return (changes, options) => {
522
+ const { body, parts } = changeRequestOf(changes);
523
+ const signal = new AbortController().signal;
524
+ return Object.keys(parts).length > 0 ? http.postMultipartJson(path, buildMutationForm(body, parts), parse, signal, options) : http.postJson(path, body, parse, signal, options);
525
+ };
526
+ }
527
+ /**
528
+ * A change ready to send: its bytes read, and checked against what a request
529
+ * may hold, so a malformed change is refused alone rather than refusing the
530
+ * request it would share.
531
+ */
532
+ async function sentChangeOf(opId, change) {
533
+ if (!(0, _embedpdf_engine_core_runtime.isUndoChange)(change) && change.ops.length > _embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.ops) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.PayloadTooLarge, `a change holds at most ${_embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.ops} ops`, { details: {
534
+ limit: "ops",
535
+ max: _embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.ops,
536
+ value: change.ops.length
537
+ } });
538
+ const sent = {
539
+ opId,
540
+ change: (await (0, _embedpdf_engine_core_runtime.resolveChangeResources)(change)).change
541
+ };
542
+ const { body } = changeRequestOf([sent]);
543
+ const checked = _embedpdf_engine_core_wire.ChangeRequestSchema.safeParse(JSON.parse(JSON.stringify(body)));
544
+ if (!checked.success) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `invalid change: ${checked.error.message}`, { details: { issues: checked.error.issues } });
545
+ return sent;
546
+ }
547
+ /** A request's JSON and its parts: each op's bytes become a part the op names by key. */
548
+ function changeRequestOf(changes) {
549
+ const parts = {};
550
+ const part = (bytes) => {
551
+ const key = `r${Object.keys(parts).length}`;
552
+ parts[key] = { bytes };
553
+ return key;
554
+ };
555
+ const opOf = (op) => {
556
+ switch (op.type) {
557
+ case "annotations.create":
558
+ case "annotations.update": {
559
+ if (!op.resources) return op;
560
+ const { appearance, file } = op.resources;
561
+ return {
562
+ ...op,
563
+ resources: {
564
+ ...appearance ? { appearance: part(appearance) } : {},
565
+ ...file ? { file: part(file) } : {}
566
+ }
567
+ };
568
+ }
569
+ case "forms.setSignatureAppearance": {
570
+ const { appearance, ...rest } = op;
571
+ return {
572
+ ...rest,
573
+ resources: { appearance: part(appearance.pdf.slice().buffer) }
574
+ };
575
+ }
576
+ default: return op;
577
+ }
578
+ };
579
+ return {
580
+ body: { changes: changes.map(({ opId, change }) => (0, _embedpdf_engine_core_runtime.isUndoChange)(change) ? {
581
+ opId,
582
+ undoOf: change.undoOf
583
+ } : {
584
+ opId,
585
+ ops: change.ops.map(opOf)
586
+ }) },
587
+ parts
588
+ };
589
+ }
590
+ /** Whether a change carries bytes: a drawing, an attached file, a signature's artwork. */
591
+ function carriesBytes(change) {
592
+ if ((0, _embedpdf_engine_core_runtime.isUndoChange)(change)) return false;
593
+ return change.ops.some((op) => op.type === "forms.setSignatureAppearance" || (op.type === "annotations.create" || op.type === "annotations.update") && op.resources !== void 0 && Object.values(op.resources).some((resource) => resource !== void 0));
594
+ }
595
+ /** The planes a change's writes took over from the base, each op by its family. */
596
+ function planesOf(result) {
597
+ const planes = /* @__PURE__ */ new Set();
598
+ for (const item of result.items) {
599
+ if ((0, _embedpdf_engine_core_runtime.isSkippedItem)(item)) continue;
600
+ if (item.type.startsWith("metadata.")) planes.add("metadata");
601
+ else if (item.type.startsWith("forms.")) planes.add("forms");
602
+ else planes.add("annotations");
603
+ }
604
+ return [...planes];
605
+ }
606
+ //#endregion
607
+ //#region src/shared/withObjectNumbers.ts
608
+ /**
609
+ * `path` with the object numbers a create names, in its query string: one
610
+ * (`objectNumber=42`) or a comma list (`objectNumbers=42,43`). The body
611
+ * stays the data. Names whose value is absent are left out.
612
+ */
613
+ function withObjectNumbers(path, numbers) {
614
+ const query = Object.entries(numbers).filter(([, value]) => value !== void 0 && (!Array.isArray(value) || value.length > 0)).map(([name, value]) => `${name}=${Array.isArray(value) ? value.join(",") : String(value)}`);
615
+ if (query.length === 0) return path;
616
+ return `${path}${path.includes("?") ? "&" : "?"}${query.join("&")}`;
617
+ }
618
+ //#endregion
619
+ //#region src/document/CloudDocumentFormsService.ts
620
+ /**
344
621
  * Cloud-side document forms service. Mirrors the local wiring: each call
345
622
  * produces an `AbortablePromise` that propagates `signal.abort()` down to
346
623
  * `fetch` and validates the JSON response with the wire-stable Zod schema.
347
624
  *
348
- * Forms are document-scoped, so reads use the unversioned `/form` URLs
349
- * (always `no-store` — there is no content-addressed variant). Mutation
350
- * results carry the per-page `cacheDelta` for pages whose widget
351
- * appearances changed; `absorbMutation` folds it into the cached manifest
352
- * so annotation/render reads stay coherent.
625
+ * The form is its own read family: `list()` reads the immutable `form@`
626
+ * leaf at the manifest's `formsVersion`. Mutation results carry the
627
+ * `cacheDelta` of the form's pins (`formsVersion`, and each changed widget
628
+ * page's `widgetVersion`); `absorbMutation` folds it into the cached
629
+ * manifest and owns the `forms` plane.
353
630
  */
354
631
  var CloudDocumentFormsService = class {
355
- constructor(http, docId, layerName, isClosed, manifest, publisher) {
632
+ constructor(http, docId, layerName, isClosed, manifest, publisher, writes, importLimits) {
356
633
  this.http = http;
357
634
  this.docId = docId;
358
635
  this.layerName = layerName;
359
636
  this.isClosed = isClosed;
360
637
  this.manifest = manifest;
361
638
  this.publisher = publisher;
639
+ this.writes = writes;
640
+ this.importLimits = importLimits;
362
641
  }
642
+ /**
643
+ * The form: one read of the immutable `form@formsVersion=N` leaf at the
644
+ * manifest's pin, at the base's URL while the layer inherits the `forms`
645
+ * plane, CDN-cacheable because the pin moves only when the form does. A
646
+ * stale pin (a write landed → 404) refreshes the manifest and reads again,
647
+ * once.
648
+ */
363
649
  list() {
364
650
  const rejected = this.rejectIfClosed();
365
651
  if (rejected) return rejected;
366
- return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJson(_embedpdf_engine_core_wire.wirePaths.layerForm(this.docId, this.layerName), (raw) => _embedpdf_engine_core_wire.FormSnapshotSchema.parse(raw), signal));
652
+ return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJsonWithRefresh(async (s) => {
653
+ const manifest = await this.manifest.get(s);
654
+ return planesInherited(manifest, ["forms"]) ? _embedpdf_engine_core_wire.wirePaths.docForm(this.docId, manifest.formsVersion) : _embedpdf_engine_core_wire.wirePaths.layerForm(this.docId, this.layerName, manifest.formsVersion);
655
+ }, (raw) => _embedpdf_engine_core_wire.FormSnapshotSchema.parse(raw), async (s) => {
656
+ await this.manifest.refresh(s);
657
+ }, signal));
367
658
  }
368
659
  get(ref) {
369
660
  const rejected = this.rejectIfClosed();
370
661
  if (rejected) return rejected;
371
662
  return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldByKey(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), (raw) => _embedpdf_engine_core_wire.FormFieldDTOSchema.parse(raw), signal));
372
663
  }
373
- setValue(ref, value) {
664
+ setValue(ref, value, options) {
665
+ const rejected = this.rejectIfClosed();
666
+ if (rejected) return rejected;
667
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
668
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
669
+ return this.writes.run(opId, signal, async (write) => {
670
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldValue(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), { value }, (raw) => _embedpdf_engine_core_wire.FormSetValueResultSchema.parse(raw), signal, sent));
671
+ return this.absorbMutation(opId, result, "forms.valueSet");
672
+ });
673
+ });
674
+ }
675
+ reset(fields, options) {
374
676
  const rejected = this.rejectIfClosed();
375
677
  if (rejected) return rejected;
678
+ const refs = fields === void 0 ? void 0 : Array.isArray(fields) ? fields : [fields];
376
679
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
377
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldValue(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), { value }, (raw) => _embedpdf_engine_core_wire.FormSetValueResultSchema.parse(raw), signal);
378
- return this.absorbMutation(result, "form.valueChanged");
680
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
681
+ return this.writes.run(opId, signal, async (write) => {
682
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormReset(this.docId, this.layerName), refs ? { refs } : {}, (raw) => _embedpdf_engine_core_wire.FormResetResultSchema.parse(raw), signal, sent));
683
+ this.manifest.apply(result.meta, ["forms"]);
684
+ this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.formResetFacts)(result).map((fact) => ({
685
+ type: "forms.valueSet",
686
+ ...fact
687
+ })));
688
+ return result;
689
+ });
379
690
  });
380
691
  }
381
- reset(ref) {
692
+ /**
693
+ * One versioned, CDN-cacheable read at the manifest's form and layout
694
+ * pins, with the stale-pin retry (404 → refresh the manifest → once). The
695
+ * response is the bundle without its bytes and one part per resource;
696
+ * every part is checked against its id before it is returned.
697
+ */
698
+ export(selection = {}) {
382
699
  const rejected = this.rejectIfClosed();
383
700
  if (rejected) return rejected;
384
701
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
385
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldReset(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), {}, (raw) => _embedpdf_engine_core_wire.FormSetValueResultSchema.parse(raw), signal);
386
- return this.absorbMutation(result, "form.valueChanged");
702
+ const read = async (s) => {
703
+ const manifest = await this.manifest.get(s);
704
+ const path = this.exportPathAt(manifest, selection);
705
+ if (path) return this.http.getFormData(path, s);
706
+ return this.http.postJsonFormData(_embedpdf_engine_core_wire.wirePaths.layerFormExportRequest(this.docId, this.layerName), {
707
+ formsVersion: manifest.formsVersion,
708
+ layoutVersion: manifest.layoutVersion,
709
+ selection
710
+ }, s);
711
+ };
712
+ let form;
713
+ try {
714
+ form = await read(signal);
715
+ } catch (error) {
716
+ if (!_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.NotFound)) throw error;
717
+ await this.manifest.refresh(signal);
718
+ form = await read(signal);
719
+ }
720
+ const { body, resources } = await readBundleParts("form", form);
721
+ const bundle = {
722
+ ...body,
723
+ resources
724
+ };
725
+ await (0, _embedpdf_engine_core_runtime.assertFormBundle)(bundle, NO_BUNDLE_LIMITS);
726
+ return bundle;
387
727
  });
388
728
  }
389
- applyEffects(effects) {
729
+ /**
730
+ * One request: the bundle's manifest and each resource once, as parts of
731
+ * one multipart POST under the import's `opId` as `Idempotency-Key`, so a
732
+ * retry applies once. The server holds the limits; the same numbers are
733
+ * checked here first, so a bundle past one fails before its bytes move.
734
+ */
735
+ import(bundle, options = {}) {
390
736
  const rejected = this.rejectIfClosed();
391
737
  if (rejected) return rejected;
392
738
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
393
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormEffects(this.docId, this.layerName), { effects }, (raw) => _embedpdf_engine_core_wire.FormEffectsResultSchema.parse(raw), signal);
394
- if (result.meta === null) return result;
395
- this.manifest.apply(result.meta, ["annotations"]);
396
- this.publisher.publishLocal({
397
- type: "form.effectsApplied",
398
- ...result
739
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
740
+ return this.writes.run(opId, signal, async (write) => {
741
+ const body = {
742
+ bundle: await this.checkedManifest(bundle),
743
+ options: {
744
+ ...options.pages !== void 0 ? { pages: options.pages } : {},
745
+ ...options.attribution !== void 0 ? { attribution: options.attribution } : {},
746
+ ...options.values !== void 0 ? { values: options.values } : {}
747
+ }
748
+ };
749
+ const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerFormImport(this.docId, this.layerName), bundleImportForm(body, bundle.resources), (raw) => _embedpdf_engine_core_wire.FormImportResultSchema.parse(raw), signal, sent));
750
+ this.manifest.apply(result.meta, ["forms"]);
751
+ this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.formImportFacts)(result).map((fact) => ({
752
+ type: "forms.created",
753
+ ...fact
754
+ })));
755
+ return result;
399
756
  });
400
- return result;
401
757
  });
402
758
  }
403
- exportData(format = "xfdf") {
759
+ /** One request, as {@link import} sends it. */
760
+ importValues(bundle, options = {}) {
404
761
  const rejected = this.rejectIfClosed();
405
762
  if (rejected) return rejected;
406
763
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
407
- return {
408
- format,
409
- bytes: await this.http.getBytes(_embedpdf_engine_core_wire.wirePaths.layerFormData(this.docId, this.layerName, format), signal)
410
- };
764
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
765
+ return this.writes.run(opId, signal, async (write) => {
766
+ const body = {
767
+ bundle: await this.checkedManifest(bundle),
768
+ options: options.attribution !== void 0 ? { attribution: options.attribution } : {}
769
+ };
770
+ const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerFormImportValues(this.docId, this.layerName), bundleImportForm(body, bundle.resources), (raw) => _embedpdf_engine_core_wire.FormValuesImportResultSchema.parse(raw), signal, sent));
771
+ this.manifest.apply(result.meta, ["forms"]);
772
+ this.publisher.publishWrite(opId, ...(0, _embedpdf_engine_core_runtime.formValuesImportFacts)(result).map((fact) => ({
773
+ type: "forms.valueSet",
774
+ ...fact
775
+ })));
776
+ return result;
777
+ });
411
778
  });
412
779
  }
413
- importData(data, format) {
780
+ create(draft, options = {}) {
414
781
  const rejected = this.rejectIfClosed();
415
782
  if (rejected) return rejected;
416
- const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
417
783
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
418
- const result = await this.http.postBytesJson(_embedpdf_engine_core_wire.wirePaths.layerFormData(this.docId, this.layerName, format), bytes, format ? IMPORT_CONTENT_TYPE[format] : "application/octet-stream", (raw) => _embedpdf_engine_core_wire.FormImportResultSchema.parse(raw), signal);
419
- return this.absorbMutation(result, "form.imported");
784
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
785
+ const { objectNumber, widgetObjectNumbers } = options;
786
+ return this.writes.run(opId, signal, async (write) => {
787
+ const result = await write.send((sent) => this.http.postJson(withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerFormFields(this.docId, this.layerName), {
788
+ objectNumber,
789
+ widgetObjectNumbers
790
+ }), draft, (raw) => _embedpdf_engine_core_wire.FormFieldCreateResultSchema.parse(raw), signal, sent));
791
+ return this.absorbMutation(opId, result, "forms.created");
792
+ }, [objectNumber, ...widgetObjectNumbers ?? []]);
420
793
  });
421
794
  }
422
- createField(draft) {
795
+ update(ref, patch, options) {
423
796
  const rejected = this.rejectIfClosed();
424
797
  if (rejected) return rejected;
425
798
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
426
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormFields(this.docId, this.layerName), draft, (raw) => _embedpdf_engine_core_wire.FormFieldCreateResultSchema.parse(raw), signal);
427
- return this.absorbMutation(result, "form.fieldCreated");
799
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
800
+ return this.writes.run(opId, signal, async (write) => {
801
+ const result = await write.send((sent) => this.http.patchJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldByKey(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), patch, (raw) => _embedpdf_engine_core_wire.FormFieldUpdateResultSchema.parse(raw), signal, sent));
802
+ return this.absorbMutation(opId, result, "forms.updated");
803
+ });
428
804
  });
429
805
  }
430
- updateField(ref, patch) {
806
+ setSignatureAppearance(ref, appearance, options) {
431
807
  const rejected = this.rejectIfClosed();
432
808
  if (rejected) return rejected;
433
809
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
434
- const result = await this.http.patchJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldByKey(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), patch, (raw) => _embedpdf_engine_core_wire.FormFieldUpdateResultSchema.parse(raw), signal);
435
- return this.absorbMutation(result, "form.fieldUpdated");
810
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
811
+ return this.writes.run(opId, signal, async (write) => {
812
+ const bytes = new ArrayBuffer(appearance.pdf.byteLength);
813
+ new Uint8Array(bytes).set(appearance.pdf);
814
+ const form = buildRoleMutationForm({}, { appearance: {
815
+ bytes,
816
+ mimeType: "application/pdf",
817
+ name: "appearance.pdf"
818
+ } });
819
+ const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldSignatureAppearance(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), form, (raw) => _embedpdf_engine_core_wire.FormFieldUpdateResultSchema.parse(raw), signal, sent));
820
+ return this.absorbMutation(opId, result, "forms.updated");
821
+ });
436
822
  });
437
823
  }
438
- setSignatureAppearance(ref, appearance) {
824
+ delete(ref, options) {
439
825
  const rejected = this.rejectIfClosed();
440
826
  if (rejected) return rejected;
441
827
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
442
- const bytes = new ArrayBuffer(appearance.pdf.byteLength);
443
- new Uint8Array(bytes).set(appearance.pdf);
444
- const form = buildMutationForm({
445
- resource: "r0",
446
- pageIndex: appearance.pageIndex ?? 0
447
- }, { r0: {
448
- bytes,
449
- mimeType: "application/pdf",
450
- name: "appearance.pdf"
451
- } });
452
- const result = await this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldSignatureAppearance(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), form, (raw) => _embedpdf_engine_core_wire.FormFieldUpdateResultSchema.parse(raw), signal);
453
- return this.absorbMutation(result, "form.fieldUpdated");
828
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
829
+ return this.writes.run(opId, signal, async (write) => {
830
+ const result = await write.send((sent) => this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldByKey(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), (raw) => _embedpdf_engine_core_wire.FormFieldDeleteResultSchema.parse(raw), signal, sent));
831
+ this.manifest.apply(result.meta, ["forms"]);
832
+ this.publisher.publishWrite(opId, {
833
+ type: "forms.deleted",
834
+ deleted: (0, _embedpdf_engine_core_runtime.deletedFieldOf)(result),
835
+ ...result
836
+ });
837
+ return result;
838
+ });
839
+ });
840
+ }
841
+ addWidget(ref, placement, options = {}) {
842
+ const rejected = this.rejectIfClosed();
843
+ if (rejected) return rejected;
844
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
845
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
846
+ const { objectNumber, splitObjectNumber } = options;
847
+ return this.writes.run(opId, signal, async (write) => {
848
+ const result = await write.send((sent) => this.http.postJson(withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerFormFieldWidgets(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), {
849
+ objectNumber,
850
+ splitObjectNumber
851
+ }), placement, (raw) => _embedpdf_engine_core_wire.FormWidgetLinkResultSchema.parse(raw), signal, sent));
852
+ return this.absorbMutation(opId, result, "forms.widgetAdded");
853
+ }, [objectNumber, splitObjectNumber]);
454
854
  });
455
855
  }
456
- deleteField(ref) {
856
+ removeWidget(ref, widget, options) {
457
857
  const rejected = this.rejectIfClosed();
458
858
  if (rejected) return rejected;
459
859
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
460
- const result = await this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldByKey(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), (raw) => _embedpdf_engine_core_wire.FormFieldDeleteResultSchema.parse(raw), signal);
461
- return this.absorbMutation(result, "form.fieldDeleted");
860
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
861
+ return this.writes.run(opId, signal, async (write) => {
862
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldWidgetsDetach(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), { widget }, (raw) => _embedpdf_engine_core_wire.FormWidgetLinkResultSchema.parse(raw), signal, sent));
863
+ return this.absorbMutation(opId, result, "forms.widgetRemoved");
864
+ });
462
865
  });
463
866
  }
464
- attachWidget(ref, widget, options) {
867
+ updateWidget(widget, patch, options) {
465
868
  const rejected = this.rejectIfClosed();
466
869
  if (rejected) return rejected;
467
- const onState = options?.onState;
468
870
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
469
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldWidgets(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), {
470
- widget,
471
- ...onState ? { onState } : {}
472
- }, (raw) => _embedpdf_engine_core_wire.FormWidgetLinkResultSchema.parse(raw), signal);
473
- return this.absorbMutation(result, "form.widgetAttached");
871
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
872
+ return this.writes.run(opId, signal, async (write) => {
873
+ const result = await write.send((sent) => this.http.patchJson(_embedpdf_engine_core_wire.wirePaths.layerFormWidget(this.docId, this.layerName, widget.page, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(widget)), { patch }, (raw) => _embedpdf_engine_core_wire.FormWidgetUpdateResultSchema.parse(raw), signal, sent));
874
+ return this.absorbMutation(opId, result, "forms.widgetUpdated");
875
+ });
474
876
  });
475
877
  }
476
- detachWidget(ref, widget) {
878
+ deleteWidget(widget, options) {
477
879
  const rejected = this.rejectIfClosed();
478
880
  if (rejected) return rejected;
479
881
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
480
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormFieldWidgetsDetach(this.docId, this.layerName, (0, _embedpdf_engine_core_runtime.encodeFieldRefKey)(ref)), { widget }, (raw) => _embedpdf_engine_core_wire.FormWidgetLinkResultSchema.parse(raw), signal);
481
- return this.absorbMutation(result, "form.widgetDetached");
882
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
883
+ return this.writes.run(opId, signal, async (write) => {
884
+ const result = await write.send((sent) => this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerFormWidget(this.docId, this.layerName, widget.page, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(widget)), (raw) => _embedpdf_engine_core_wire.FormWidgetDeleteResultSchema.parse(raw), signal, sent));
885
+ return this.absorbMutation(opId, result, "forms.widgetDeleted");
886
+ });
887
+ });
888
+ }
889
+ reorderCalculations(fields, position, options) {
890
+ const rejected = this.rejectIfClosed();
891
+ if (rejected) return rejected;
892
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
893
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
894
+ return this.writes.run(opId, signal, async (write) => {
895
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormCalculationsReorder(this.docId, this.layerName), {
896
+ fields,
897
+ position
898
+ }, (raw) => _embedpdf_engine_core_wire.FormCalculationsReorderResultSchema.parse(raw), signal, sent));
899
+ return this.absorbMutation(opId, result, "forms.calculationsReordered");
900
+ });
901
+ });
902
+ }
903
+ reorderWidgets(widgets, position, options) {
904
+ const rejected = this.rejectIfClosed();
905
+ if (rejected) return rejected;
906
+ const page = widgets[0]?.page;
907
+ if (!page) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "a reorder names at least one widget", { details: { field: "widgets" } }));
908
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
909
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
910
+ return this.writes.run(opId, signal, async (write) => {
911
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormWidgetsReorder(this.docId, this.layerName, page), {
912
+ widgets,
913
+ position
914
+ }, (raw) => _embedpdf_engine_core_wire.FormWidgetsReorderResultSchema.parse(raw), signal, sent));
915
+ return this.absorbMutation(opId, result, "forms.widgetsReordered");
916
+ });
482
917
  });
483
918
  }
484
919
  repair(options) {
485
920
  const rejected = this.rejectIfClosed();
486
921
  if (rejected) return rejected;
487
922
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
488
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormRepair(this.docId, this.layerName), { bakeAppearances: options?.bakeAppearances ?? false }, (raw) => _embedpdf_engine_core_wire.FormRepairResultSchema.parse(raw), signal);
489
- return this.absorbMutation(result, "form.repaired");
923
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
924
+ return this.writes.run(opId, signal, async (write) => {
925
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerFormRepair(this.docId, this.layerName), { bakeAppearances: options?.bakeAppearances ?? false }, (raw) => _embedpdf_engine_core_wire.FormRepairResultSchema.parse(raw), signal, sent));
926
+ return this.absorbMutation(opId, result, "forms.repaired");
927
+ });
490
928
  });
491
929
  }
930
+ /** The bundle without its bytes, checked against the server's limits first. */
931
+ async checkedManifest(bundle) {
932
+ const { resources, ...manifest } = bundle;
933
+ (0, _embedpdf_engine_core_runtime.assertFormBundleManifest)(bundle, new Map(Object.entries(resources).map(([id, bytes]) => [id, bytes.length])), await this.importLimits());
934
+ return manifest;
935
+ }
936
+ /**
937
+ * The cacheable export URL for `selection` at the manifest's pins — the
938
+ * base leaf while the layer inherits both planes the bundle depends on —
939
+ * or `null` when a URL can't carry it.
940
+ */
941
+ exportPathAt(manifest, selection) {
942
+ const token = {
943
+ formsVersion: manifest.formsVersion,
944
+ layoutVersion: manifest.layoutVersion,
945
+ selection
946
+ };
947
+ try {
948
+ return planesInherited(manifest, ["forms", "layout"]) ? _embedpdf_engine_core_wire.wirePaths.docFormExport(this.docId, token) : _embedpdf_engine_core_wire.wirePaths.layerFormExport(this.docId, this.layerName, token);
949
+ } catch {
950
+ return null;
951
+ }
952
+ }
492
953
  rejectIfClosed() {
493
954
  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`));
494
955
  return null;
@@ -498,9 +959,9 @@ var CloudDocumentFormsService = class {
498
959
  * event stream (in that order — listeners reading the manifest in their
499
960
  * callback must see post-mutation state). Same rails as annotations.
500
961
  */
501
- absorbMutation(result, type) {
502
- this.manifest.apply(result.meta, ["annotations"]);
503
- this.publisher.publishLocal({
962
+ absorbMutation(opId, result, type) {
963
+ this.manifest.apply(result.meta, ["forms"]);
964
+ this.publisher.publishWrite(opId, {
504
965
  type,
505
966
  ...result
506
967
  });
@@ -508,6 +969,28 @@ var CloudDocumentFormsService = class {
508
969
  }
509
970
  };
510
971
  //#endregion
972
+ //#region src/shared/awaitSignal.ts
973
+ /**
974
+ * Wait for a shared promise under one caller's signal: an abort rejects
975
+ * this caller only and leaves the shared work running for the others.
976
+ */
977
+ function awaitSignal(promise, signal) {
978
+ if (signal.aborted) return Promise.reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
979
+ return new Promise((resolve, reject) => {
980
+ const onAbort = () => {
981
+ reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
982
+ };
983
+ signal.addEventListener("abort", onAbort, { once: true });
984
+ promise.then((value) => {
985
+ signal.removeEventListener("abort", onAbort);
986
+ resolve(value);
987
+ }, (reason) => {
988
+ signal.removeEventListener("abort", onAbort);
989
+ reject(reason);
990
+ });
991
+ });
992
+ }
993
+ //#endregion
511
994
  //#region src/document/CloudDocumentPagesService.ts
512
995
  /** Detach a Uint8Array view into a standalone ArrayBuffer (the resource-map
513
996
  * shape) without disturbing a larger buffer the caller still owns. */
@@ -518,26 +1001,28 @@ function copyToExactBuffer(view) {
518
1001
  }
519
1002
  /**
520
1003
  * Cloud-side document pages service. Mirrors `LocalDocumentPagesService`
521
- * over HTTP: GET /pages for `list`, POST /pages/move for the reorder.
1004
+ * over HTTP: GET /pages for `list`, POST /pages/reorder for the reorder.
522
1005
  *
523
1006
  * Page identity rule (locked with the user, do not change):
524
- * - Pages are addressed exclusively by their indirect
525
- * `pageObjectNumber`. The wire never sends a page index for a
526
- * mutation. This keeps multi-call client logic from having to
527
- * account for index drift between requests.
528
- * - Successful `move()` returns the new `layout` (order + geometry) plus
529
- * cloud coherence pins. The server does NOT bump per-page revisions on a
530
- * page move (page reorder is intentionally outside the weak-ref staleness
531
- * model), only `docVersion` + `layoutVersion`.
1007
+ * - Pages are addressed exclusively by `PageRef` (their indirect
1008
+ * `pageObjectNumber`); mutation bodies ship `{ pages: PageRef[] }`.
1009
+ * The wire never sends a page index for a mutation. This keeps
1010
+ * multi-call client logic from having to account for index drift
1011
+ * between requests.
1012
+ * - Successful `reorder()` returns the new `layout` (order + geometry) plus
1013
+ * cloud coherence pins. A page reorder bumps only `docVersion` +
1014
+ * `layoutVersion`; every per-page pin stays warm.
532
1015
  */
533
1016
  var CloudDocumentPagesService = class {
534
- constructor(http, docId, layerName, isClosed, manifest, publisher) {
1017
+ constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
535
1018
  this.http = http;
536
1019
  this.docId = docId;
537
1020
  this.layerName = layerName;
538
1021
  this.isClosed = isClosed;
539
1022
  this.manifest = manifest;
540
1023
  this.publisher = publisher;
1024
+ this.writes = writes;
1025
+ _defineProperty(this, "layoutMemo", null);
541
1026
  }
542
1027
  /**
543
1028
  * Page-geometry list. The geometry bytes live at the content-addressed
@@ -551,142 +1036,195 @@ var CloudDocumentPagesService = class {
551
1036
  */
552
1037
  list() {
553
1038
  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 buildPath = async (s) => {
556
- const manifest = await this.manifest.get(s);
557
- return planesInherited(manifest, ["layout"]) ? _embedpdf_engine_core_wire.wirePaths.docLayout(this.docId, manifest.layoutVersion) : _embedpdf_engine_core_wire.wirePaths.layerLayout(this.docId, this.layerName, manifest.layoutVersion);
1039
+ return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.fetchLayout(signal));
1040
+ }
1041
+ /**
1042
+ * One page's layout, for renders to size their image without a round
1043
+ * trip per tile. The layout leaf never changes at a given URL, so the
1044
+ * last one read is kept and shared until the manifest points elsewhere.
1045
+ */
1046
+ async pageLayout(ref, signal) {
1047
+ const path = await this.layoutPath(signal);
1048
+ let memo = this.layoutMemo;
1049
+ if (memo?.path !== path) {
1050
+ const snapshot = this.fetchLayout(new AbortController().signal);
1051
+ const next = {
1052
+ path,
1053
+ snapshot
558
1054
  };
559
- return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.PageListSnapshotSchema.parse(raw), async (s) => {
560
- await this.manifest.refresh(s);
561
- }, signal);
562
- });
1055
+ snapshot.catch(() => {
1056
+ if (this.layoutMemo === next) this.layoutMemo = null;
1057
+ });
1058
+ this.layoutMemo = memo = next;
1059
+ }
1060
+ const page = (await awaitSignal(memo.snapshot, signal)).pages.find((p) => (0, _embedpdf_engine_core_runtime.pageRefsEqual)(p.ref, ref));
1061
+ if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${ref.objectNumber} in document ${this.docId}`);
1062
+ return page;
1063
+ }
1064
+ /**
1065
+ * Plane-scope rule: the layout leaf depends on the `layout` plane —
1066
+ * while inherited (no reorder/rotate/insert/delete ever ran), every
1067
+ * visitor's page list is one doc-level URL served from the base
1068
+ * session; the SDK open sequence creates no layer session.
1069
+ */
1070
+ async layoutPath(signal) {
1071
+ const manifest = await this.manifest.get(signal);
1072
+ return planesInherited(manifest, ["layout"]) ? _embedpdf_engine_core_wire.wirePaths.docLayout(this.docId, manifest.layoutVersion) : _embedpdf_engine_core_wire.wirePaths.layerLayout(this.docId, this.layerName, manifest.layoutVersion);
563
1073
  }
564
- move(pageObjectNumbers, destIndex) {
1074
+ fetchLayout(signal) {
1075
+ return this.http.getJsonWithRefresh((s) => this.layoutPath(s), (raw) => _embedpdf_engine_core_wire.PageListSnapshotSchema.parse(raw), async (s) => {
1076
+ await this.manifest.refresh(s);
1077
+ }, signal);
1078
+ }
1079
+ reorder(pages, position, options) {
565
1080
  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`));
566
1081
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
567
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesMove(this.docId, this.layerName), {
568
- pageObjectNumbers,
569
- destIndex
570
- }, (raw) => _embedpdf_engine_core_wire.PageMoveResultSchema.parse(raw), signal);
571
- if (result.cache) this.manifest.applyPageStructure(result.cache);
572
- this.publisher.publishLocal({
573
- type: "pages.moved",
574
- pageObjectNumbers,
575
- destIndex,
576
- ...result
1082
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1083
+ return this.writes.run(opId, signal, async (write) => {
1084
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesReorder(this.docId, this.layerName), {
1085
+ pages,
1086
+ position
1087
+ }, (raw) => _embedpdf_engine_core_wire.PageReorderResultSchema.parse(raw), signal, sent));
1088
+ this.manifest.apply(result.meta, ["layout"]);
1089
+ this.publisher.publishWrite(opId, {
1090
+ type: "pages.reordered",
1091
+ ...result
1092
+ });
1093
+ return result;
577
1094
  });
578
- return result;
579
1095
  });
580
1096
  }
581
- setName(input) {
582
- return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNames(this.docId, this.layerName), input, input.name, input.pageObjectNumber);
1097
+ setName(input, options) {
1098
+ return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNames(this.docId, this.layerName), input, input.name, input.page, options);
583
1099
  }
584
- removeName(input) {
585
- return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNamesDelete(this.docId, this.layerName), input, input.name, null);
1100
+ removeName(input, options) {
1101
+ return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNamesDelete(this.docId, this.layerName), input, input.name, null, options);
586
1102
  }
587
1103
  /**
588
- * Named pages are LAYOUT: both verbs share the page-move patch exactly —
1104
+ * Named pages are layout: both verbs share the page-reorder patch exactly —
589
1105
  * docVersion + layoutVersion advance, no per-page pin changes, so the
590
1106
  * cached manifest is patched in place and the fresh layout is published.
591
1107
  */
592
- runNameMutation(path, body, name, pageObjectNumber) {
1108
+ runNameMutation(path, body, name, page, options) {
593
1109
  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`));
594
1110
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
595
- const result = await this.http.postJson(path, body, (raw) => _embedpdf_engine_core_wire.PageNameResultSchema.parse(raw), signal);
596
- if (result.cache) this.manifest.applyPageStructure(result.cache);
597
- this.publisher.publishLocal({
598
- type: "pages.named",
599
- name,
600
- pageObjectNumber,
601
- ...result
1111
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1112
+ return this.writes.run(opId, signal, async (write) => {
1113
+ const result = await write.send((sent) => this.http.postJson(path, body, (raw) => _embedpdf_engine_core_wire.PageNameResultSchema.parse(raw), signal, sent));
1114
+ this.manifest.apply(result.meta, ["layout"]);
1115
+ this.publisher.publishWrite(opId, {
1116
+ type: "pages.named",
1117
+ name,
1118
+ page,
1119
+ ...result
1120
+ });
1121
+ return result;
602
1122
  });
603
- return result;
604
1123
  });
605
1124
  }
606
- rotate(pageObjectNumbers, rotation) {
1125
+ rotate(pages, rotation, options) {
607
1126
  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`));
608
1127
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
609
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesRotate(this.docId, this.layerName), {
610
- pageObjectNumbers,
611
- rotation
612
- }, (raw) => _embedpdf_engine_core_wire.PageRotateResultSchema.parse(raw), signal);
613
- if (result.cache) this.manifest.applyPageStructure(result.cache);
614
- this.publisher.publishLocal({
615
- type: "pages.rotated",
616
- pageObjectNumbers,
617
- rotation,
618
- ...result
1128
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1129
+ return this.writes.run(opId, signal, async (write) => {
1130
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesRotate(this.docId, this.layerName), {
1131
+ pages,
1132
+ rotation
1133
+ }, (raw) => _embedpdf_engine_core_wire.PageRotateResultSchema.parse(raw), signal, sent));
1134
+ this.manifest.apply(result.meta, ["layout"]);
1135
+ this.publisher.publishWrite(opId, {
1136
+ type: "pages.rotated",
1137
+ pages,
1138
+ rotation,
1139
+ ...result
1140
+ });
1141
+ return result;
619
1142
  });
620
- return result;
621
1143
  });
622
1144
  }
623
- delete(pageObjectNumbers) {
1145
+ delete(pages, options) {
624
1146
  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`));
625
1147
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
626
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesDelete(this.docId, this.layerName), { pageObjectNumbers }, (raw) => _embedpdf_engine_core_wire.PageDeleteResultSchema.parse(raw), signal);
627
- if (result.cache) this.manifest.applyPageDelete(result.cache, pageObjectNumbers);
628
- this.publisher.publishLocal({
629
- type: "pages.deleted",
630
- pageObjectNumbers,
631
- ...result
1148
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1149
+ return this.writes.run(opId, signal, async (write) => {
1150
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesDelete(this.docId, this.layerName), { pages }, (raw) => _embedpdf_engine_core_wire.PageDeleteResultSchema.parse(raw), signal, sent));
1151
+ this.manifest.applyPageDelete(result.meta, pages);
1152
+ this.publisher.publishWrite(opId, {
1153
+ type: "pages.deleted",
1154
+ pages,
1155
+ ...result
1156
+ });
1157
+ return result;
632
1158
  });
633
- return result;
634
1159
  });
635
1160
  }
636
- insert(bytes, destIndex) {
1161
+ insert(bytes, position = "end", options) {
637
1162
  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`));
638
1163
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
639
- const buffer = bytes instanceof ArrayBuffer ? bytes : copyToExactBuffer(bytes);
640
- const form = buildMutationForm(destIndex !== void 0 ? { destIndex } : {}, { source: {
641
- bytes: buffer,
642
- mimeType: "application/pdf",
643
- name: "source.pdf"
644
- } });
645
- 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);
646
- if (result.cache) this.manifest.applyPageInsert(result.cache);
647
- this.publisher.publishLocal({
648
- type: "pages.inserted",
649
- destIndex,
650
- ...result
1164
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1165
+ return this.writes.run(opId, signal, async (write) => {
1166
+ const buffer = bytes instanceof ArrayBuffer ? bytes : copyToExactBuffer(bytes);
1167
+ const form = buildRoleMutationForm({ position }, { source: {
1168
+ bytes: buffer,
1169
+ mimeType: "application/pdf",
1170
+ name: "source.pdf"
1171
+ } });
1172
+ const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerPagesInsert(this.docId, this.layerName), form, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal, sent));
1173
+ this.manifest.applyPageInsert(result.meta);
1174
+ this.publisher.publishWrite(opId, {
1175
+ type: "pages.inserted",
1176
+ ...result
1177
+ });
1178
+ return result;
651
1179
  });
652
- return result;
653
1180
  });
654
1181
  }
655
- insertBlank(spec, destIndex) {
1182
+ insertBlank(spec, position = "end", options = {}) {
656
1183
  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`));
657
1184
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
658
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesInsertBlank(this.docId, this.layerName), {
659
- size: spec.size,
660
- ...spec.count !== void 0 ? { count: spec.count } : {},
661
- ...destIndex !== void 0 ? { destIndex } : {}
662
- }, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal);
663
- if (result.cache) this.manifest.applyPageInsert(result.cache);
664
- this.publisher.publishLocal({
665
- type: "pages.inserted",
666
- destIndex,
667
- ...result
668
- });
669
- return result;
1185
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1186
+ const { objectNumbers } = options;
1187
+ return this.writes.run(opId, signal, async (write) => {
1188
+ const result = await write.send((sent) => this.http.postJson(withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerPagesInsertBlank(this.docId, this.layerName), { objectNumbers }), {
1189
+ size: spec.size,
1190
+ ...spec.count !== void 0 ? { count: spec.count } : {},
1191
+ position
1192
+ }, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal, sent));
1193
+ this.manifest.applyPageInsert(result.meta);
1194
+ this.publisher.publishWrite(opId, {
1195
+ type: "pages.inserted",
1196
+ ...result
1197
+ });
1198
+ return result;
1199
+ }, objectNumbers ?? []);
670
1200
  });
671
1201
  }
672
- extract(pageObjectNumbers) {
1202
+ extract(pages) {
673
1203
  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`));
674
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPagesExtract(this.docId, this.layerName), { pageObjectNumbers }, signal));
1204
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPagesExtract(this.docId, this.layerName), { pages }, signal));
675
1205
  }
676
- flatten(pageObjectNumbers, usage = "display") {
1206
+ flatten(pages, options) {
1207
+ const usage = options?.usage ?? "display";
677
1208
  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`));
678
1209
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
679
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesFlatten(this.docId, this.layerName), {
680
- pageObjectNumbers,
681
- usage
682
- }, (raw) => _embedpdf_engine_core_wire.PageFlattenResultSchema.parse(raw), signal);
683
- if (result.meta === null) return result;
684
- this.manifest.apply(result.meta, ["content", "annotations"]);
685
- this.publisher.publishLocal({
686
- type: "pages.flattened",
687
- ...result
1210
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1211
+ return this.writes.run(opId, signal, async (write) => {
1212
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesFlatten(this.docId, this.layerName), {
1213
+ pages,
1214
+ usage
1215
+ }, (raw) => _embedpdf_engine_core_wire.PageFlattenResultSchema.parse(raw), signal, sent));
1216
+ if (result.meta.cacheDelta === null) return result;
1217
+ this.manifest.apply(result.meta, [
1218
+ "content",
1219
+ "annotations",
1220
+ "forms"
1221
+ ]);
1222
+ this.publisher.publishWrite(opId, {
1223
+ type: "pages.flattened",
1224
+ ...result
1225
+ });
1226
+ return result;
688
1227
  });
689
- return result;
690
1228
  });
691
1229
  }
692
1230
  };
@@ -697,29 +1235,37 @@ var CloudDocumentPagesService = class {
697
1235
  * HTTP (POST /redactions/apply); the server enforces the capability gate
698
1236
  * (`doc.pages.modify` + `doc.annotate.modify` + `doc.redact`) and persists
699
1237
  * the rewritten layer artifact. See `DocumentRedactionService` for the
700
- * two-stage model and the layer trust boundary — an apply rewrites THIS
701
- * LAYER's bytes; the immutable base keeps the original.
1238
+ * two-stage model and the layer trust boundary — an apply rewrites this
1239
+ * layer's bytes; the immutable base keeps the original.
702
1240
  */
703
1241
  var CloudDocumentRedactionService = class {
704
- constructor(http, docId, layerName, isClosed, manifest, publisher) {
1242
+ constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
705
1243
  this.http = http;
706
1244
  this.docId = docId;
707
1245
  this.layerName = layerName;
708
1246
  this.isClosed = isClosed;
709
1247
  this.manifest = manifest;
710
1248
  this.publisher = publisher;
1249
+ this.writes = writes;
711
1250
  }
712
- apply(scope) {
1251
+ apply(scope, options) {
713
1252
  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`));
714
1253
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
715
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerRedactionsApply(this.docId, this.layerName), { scope }, (raw) => _embedpdf_engine_core_wire.RedactionApplyResultSchema.parse(raw), signal);
716
- if (result.meta === null) return result;
717
- this.manifest.apply(result.meta, ["content", "annotations"]);
718
- this.publisher.publishLocal({
719
- type: "redaction.applied",
720
- ...result
1254
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1255
+ return this.writes.run(opId, signal, async (write) => {
1256
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerRedactionsApply(this.docId, this.layerName), scope, (raw) => _embedpdf_engine_core_wire.RedactionApplyResultSchema.parse(raw), signal, sent));
1257
+ if (result.meta.cacheDelta === null) return result;
1258
+ this.manifest.apply(result.meta, [
1259
+ "content",
1260
+ "annotations",
1261
+ "forms"
1262
+ ]);
1263
+ this.publisher.publishWrite(opId, {
1264
+ type: "redaction.applied",
1265
+ ...result
1266
+ });
1267
+ return result;
721
1268
  });
722
- return result;
723
1269
  });
724
1270
  }
725
1271
  };
@@ -730,14 +1276,14 @@ var CloudDocumentRedactionService = class {
730
1276
  *
731
1277
  * Every slice URL is a canonical cache key — the search token pins the
732
1278
  * layer's content epoch (computed from the cached manifest), the
733
- * CANONICAL query (default-fold literals collapse case/diacritic
1279
+ * canonical query (default-fold literals collapse case/diacritic
734
1280
  * variants into one entry), and the resume position. Identical searches
735
1281
  * from any reader of the document hit the same CDN entry; matches are
736
1282
  * all that ever crosses the trust boundary, and the rects/full endpoint
737
1283
  * split keeps permission tiers in disjoint cache namespaces.
738
1284
  *
739
1285
  * Staleness rides the standard versioned-read signal: a first slice
740
- * answered `NotFound` means OUR manifest is stale → refresh and retry
1286
+ * answered `NotFound` means our manifest is stale → refresh and retry
741
1287
  * once; a continuation answered `NotFound` means the document changed
742
1288
  * mid-search → `InvalidArg`, same contract as the local engine's cursor.
743
1289
  */
@@ -752,9 +1298,10 @@ var CloudDocumentSearchService = class {
752
1298
  query(request) {
753
1299
  if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document not open: ${this.docId}`));
754
1300
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
755
- const path = (request.mode ?? "full") === "rects" ? _embedpdf_engine_core_wire.wirePaths.layerSearchRects : _embedpdf_engine_core_wire.wirePaths.layerSearchFull;
1301
+ const path = request.snippets ? _embedpdf_engine_core_wire.wirePaths.layerSearchFull : _embedpdf_engine_core_wire.wirePaths.layerSearchRects;
756
1302
  const get = (token) => this.http.getJson(path(this.docId, this.layerName, token), (raw) => _embedpdf_engine_core_wire.SearchSliceSchema.parse(raw), signal);
757
- const query = (0, _embedpdf_engine_core_runtime.canonicalSearchQuery)(request.query);
1303
+ const query = (0, _embedpdf_engine_core_runtime.canonicalSearchQuery)(request);
1304
+ const from = request.from?.objectNumber;
758
1305
  if (request.cursor !== void 0) {
759
1306
  let token;
760
1307
  try {
@@ -763,7 +1310,7 @@ var CloudDocumentSearchService = class {
763
1310
  throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "malformed search cursor");
764
1311
  }
765
1312
  if (queryIdentity(token.query) !== queryIdentity(query)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "search cursor belongs to a different query — restart the search");
766
- if (request.startPage !== void 0 && request.startPage !== token.startPage) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "startPage conflicts with the cursor — omit startPage when resuming");
1313
+ if (from !== void 0 && from !== token.from) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "`from` conflicts with the cursor — omit it when resuming");
767
1314
  try {
768
1315
  return await get(request.cursor);
769
1316
  } catch (err) {
@@ -774,9 +1321,9 @@ var CloudDocumentSearchService = class {
774
1321
  const mint = (epoch) => (0, _embedpdf_engine_core_wire.encodeSearchToken)({
775
1322
  epoch,
776
1323
  query,
777
- ...request.startPage !== void 0 ? { startPage: request.startPage } : {},
1324
+ ...from !== void 0 ? { from } : {},
778
1325
  skip: 0,
779
- ...request.budget !== void 0 ? { budget: request.budget } : {}
1326
+ ...request.limit !== void 0 ? { limit: request.limit } : {}
780
1327
  });
781
1328
  try {
782
1329
  return await get(mint((0, _embedpdf_engine_core_runtime.searchContentEpoch)(await this.manifest.get(signal))));
@@ -794,7 +1341,8 @@ function queryIdentity(query) {
794
1341
  query.text,
795
1342
  query.matchCase ? 1 : 0,
796
1343
  query.matchDiacritics ? 1 : 0,
797
- query.wholeWord ? 1 : 0
1344
+ query.wholeWord ? 1 : 0,
1345
+ query.ignoreWhitespace ? 1 : 0
798
1346
  ]);
799
1347
  }
800
1348
  //#endregion
@@ -841,22 +1389,39 @@ function base64UrlDecode(b64url) {
841
1389
  //#endregion
842
1390
  //#region src/document/CloudDocumentSecurityService.ts
843
1391
  var CloudDocumentSecurityService = class {
844
- constructor(http, docId, layerName, initialHead, view, initialToken = null) {
1392
+ constructor(http, docId, layerName, initialHead, view, initialToken = null, editing = null) {
845
1393
  this.http = http;
846
1394
  this.docId = docId;
847
1395
  this.layerName = layerName;
848
1396
  this.view = view;
849
- _defineProperty(this, "state", void 0);
1397
+ this.editing = editing;
1398
+ _defineProperty(this, "securityState", void 0);
850
1399
  _defineProperty(this, "access", null);
851
1400
  _defineProperty(this, "tokenScope", void 0);
852
1401
  _defineProperty(this, "tokenIdentity", void 0);
853
- this.state = (0, _embedpdf_engine_core_runtime.securityStateFromHead)(initialHead);
1402
+ _defineProperty(this, "tokenHasDocScope", void 0);
1403
+ _defineProperty(this, "protection", null);
1404
+ _defineProperty(this, "passwordRejected", false);
1405
+ this.securityState = (0, _embedpdf_engine_core_runtime.securityStateFromHead)(initialHead);
854
1406
  const claims = initialToken ? safeDecodeClaims(initialToken) : null;
855
1407
  this.tokenScope = Array.isArray(claims?.scope) ? claims.scope : [];
856
1408
  this.tokenIdentity = claims ? identityFromClaims(claims) : null;
1409
+ this.tokenHasDocScope = typeof claims?.doc_id === "string" && Array.isArray(claims?.scope);
1410
+ }
1411
+ /**
1412
+ * What the caller may read of a page picture, when the client can know it:
1413
+ * once `/access` answered, or from a document token's scope. Null without
1414
+ * either (no token, a tenant or API token): the server decides.
1415
+ */
1416
+ pageLayerRights() {
1417
+ if (!this.access && !this.tokenHasDocScope) return null;
1418
+ return {
1419
+ annotations: this.allows("doc.annotate.read"),
1420
+ formFields: this.allows("doc.forms.read")
1421
+ };
857
1422
  }
858
- get current() {
859
- return this.state;
1423
+ get state() {
1424
+ return this.securityState;
860
1425
  }
861
1426
  get currentAccess() {
862
1427
  return this.access;
@@ -864,52 +1429,60 @@ var CloudDocumentSecurityService = class {
864
1429
  /**
865
1430
  * Expanded capability set. Cloud-canonical post-/access; otherwise
866
1431
  * computed locally from the JWT scope + /head's pdf bits using the
867
- * SAME `expandRawScope` helper engine-local calls — so the value
1432
+ * same `expandRawScope` helper engine-local calls — so the value
868
1433
  * matches across engines bit-for-bit on the same inputs.
869
1434
  */
870
- get effectiveScope() {
1435
+ get scope() {
871
1436
  if (this.access) return this.access.effectiveScope;
872
- const bits = (0, _embedpdf_engine_core_runtime.decodePdfBits)(this.state.permissions.bits);
873
- return Array.from((0, _embedpdf_engine_core_runtime.expandRawScope)(this.tokenScope, bits)).sort();
1437
+ return Array.from((0, _embedpdf_engine_core_runtime.expandRawScope)(this.tokenScope, this.pdfBits(), this.protection)).sort();
874
1438
  }
875
1439
  /**
876
- * Wildcard-aware authorization check — mirrors what the server route layer
877
- * enforces with. `effectiveScope` alone can't gate UI: it enumerates concrete
878
- * grants and drops the `*` admin wildcard. So we honor a server-canonical
879
- * concrete grant when present, then fall back to the same `checkCapability`
880
- * predicate (which short-circuits `*`) against the JWT scope + /head bits.
1440
+ * The server route guards' own predicate over the same inputs: what the
1441
+ * document's signatures forbid first, then the scope (the server's copy
1442
+ * once `/access` answered, else the JWT's), wildcard included, against the
1443
+ * PDF bits. `scope` alone can't gate UI: it enumerates concrete grants and
1444
+ * drops the `*` admin wildcard.
881
1445
  */
882
1446
  allows(cap) {
883
- if (this.access?.effectiveScope.includes(cap)) return true;
884
- const bits = (0, _embedpdf_engine_core_runtime.decodePdfBits)(this.state.permissions.bits);
885
- return (0, _embedpdf_engine_core_runtime.checkCapability)(cap, this.tokenScope, bits);
1447
+ return (0, _embedpdf_engine_core_runtime.checkCapability)(cap, this.rawScope(), this.pdfBits(), this.protection);
886
1448
  }
887
1449
  /**
888
- * Per-record annotation authorization mirrors — the same
889
- * `checkCollab`/`checkSetGroup` resolvers the server's route layer
890
- * enforces with, over the same inputs: the raw scope
891
- * (server-canonical post-/access, else the JWT's own claim), the
892
- * caller's identity, and /head's PDF bits. A control gated on these
893
- * matches the server's allow/deny for the same mutation. False
894
- * before any token/access context exists — fail closed, same rule
895
- * as `allows`.
1450
+ * Cloud-internal: what the document's signatures forbid, from the latest
1451
+ * manifest (it changes only when a signature publishes a new version).
896
1452
  */
897
- allowsAnnotationCreate() {
1453
+ setProtection(protection) {
1454
+ this.protection = protection;
1455
+ }
1456
+ allowsAnnotation(action, target) {
898
1457
  const id = this.identity ?? {};
899
- return (0, _embedpdf_engine_core_runtime.checkCollab)("create", selfTarget(id), this.rawScope(), id, this.pdfBits());
1458
+ const annotationsProtected = (0, _embedpdf_engine_core_runtime.protectedCapabilities)(this.protection).has("doc.annotate.modify");
1459
+ switch (action) {
1460
+ case "create":
1461
+ if (annotationsProtected) return false;
1462
+ return (0, _embedpdf_engine_core_runtime.checkCollab)("create", selfTarget(id), this.rawScope(), id, this.pdfBits());
1463
+ case "set-group": return (0, _embedpdf_engine_core_runtime.checkSetGroup)("annotations", target.groupId, id.groupId, this.rawScope());
1464
+ default: {
1465
+ const annotation = target ?? {};
1466
+ if (annotation.subtype === "widget") return this.allows("doc.forms.modify");
1467
+ if (annotationsProtected) return false;
1468
+ return (0, _embedpdf_engine_core_runtime.checkCollab)(action, (0, _embedpdf_engine_core_runtime.collabTargetOf)(annotation), this.rawScope(), id, this.pdfBits());
1469
+ }
1470
+ }
900
1471
  }
901
- allowsAnnotationMutation(action, target) {
902
- return (0, _embedpdf_engine_core_runtime.checkCollab)(action, target, this.rawScope(), this.identity ?? {}, this.pdfBits());
1472
+ allowsField(action, target) {
1473
+ if (action === "set-group") return (0, _embedpdf_engine_core_runtime.checkSetGroup)("fields", target.groupId ?? "", this.identity?.groupId, this.rawScope());
1474
+ return (0, _embedpdf_engine_core_runtime.checkFieldAction)(action, target.groupId, this.rawScope(), this.pdfBits(), this.protection);
903
1475
  }
904
- allowsAnnotationGroupAssignment(groupId) {
905
- return (0, _embedpdf_engine_core_runtime.checkSetGroup)(groupId, this.identity?.group_id, this.rawScope(), this.pdfBits());
1476
+ /** The same inputs as `allowsField`, as lists. */
1477
+ fieldRights() {
1478
+ return (0, _embedpdf_engine_core_runtime.fieldRightsOf)(this.rawScope(), this.pdfBits(), this.protection, this.identity?.groupId);
906
1479
  }
907
1480
  /** Raw scope for the collab resolver: server-canonical post-/access, else the JWT claim. */
908
1481
  rawScope() {
909
1482
  return this.access?.scope ?? this.tokenScope;
910
1483
  }
911
1484
  pdfBits() {
912
- return (0, _embedpdf_engine_core_runtime.decodePdfBits)(this.state.permissions.bits);
1485
+ return (0, _embedpdf_engine_core_runtime.decodePdfBits)(this.securityState.permissions.bits);
913
1486
  }
914
1487
  /**
915
1488
  * Identity of the current caller, or null when anonymous.
@@ -929,21 +1502,28 @@ var CloudDocumentSecurityService = class {
929
1502
  * security state. See `passwordPromptFromState` for the rules.
930
1503
  */
931
1504
  get passwordPrompt() {
932
- return (0, _embedpdf_engine_core_runtime.passwordPromptFromState)(this.state);
1505
+ return (0, _embedpdf_engine_core_runtime.passwordPromptFromState)(this.securityState, this.passwordRejected);
933
1506
  }
934
1507
  unlock(input) {
935
1508
  if (this.view.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`));
936
1509
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
937
- return await this.postAccess(signal, {
938
- password: input.password,
939
- mode: input.mode ?? "any"
940
- });
1510
+ try {
1511
+ const result = await this.postAccess(signal, {
1512
+ password: input.password,
1513
+ mode: input.mode ?? "any"
1514
+ });
1515
+ this.passwordRejected = false;
1516
+ return result;
1517
+ } catch (error) {
1518
+ if (_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordIncorrect)) this.passwordRejected = true;
1519
+ throw error;
1520
+ }
941
1521
  });
942
1522
  }
943
1523
  /**
944
1524
  * Cloud-internal: call /v1/access with no password to establish
945
1525
  * a CDN-credentialed session. Used by `CloudEngine.open` when
946
- * /head's `access.reasons` includes 'cdn' but NOT 'password' —
1526
+ * /head's `access.reasons` includes 'cdn' but not 'password' —
947
1527
  * the server accepts an authenticated /access POST without a
948
1528
  * password and returns the signed-URL block.
949
1529
  *
@@ -962,7 +1542,9 @@ var CloudDocumentSecurityService = class {
962
1542
  * password) and `establishAccess()` (no password). Updates cached
963
1543
  * security state, caches the access block, and pushes the CDN
964
1544
  * binding into the HttpClient so subsequent fetches apply CDN
965
- * tokens via `applyCdnAccess`.
1545
+ * tokens via `applyCdnAccess`. It goes as the editing session, and a
1546
+ * caller that may create gets the session's object numbers with it
1547
+ * (`edit`).
966
1548
  *
967
1549
  * `none` CDN adapter → the access block has null overrides/policies
968
1550
  * and `applyCdnAccess` short-circuits to origin; safe to call always.
@@ -970,27 +1552,33 @@ var CloudDocumentSecurityService = class {
970
1552
  async postAccess(signal, body) {
971
1553
  const response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.access(this.docId, this.layerName), {
972
1554
  ...body.password ? { password: body.password } : {},
973
- mode: body.mode
974
- }, (raw) => _embedpdf_engine_core_wire.AccessResponseSchema.parse(raw), signal);
975
- this.state = response.security;
1555
+ mode: body.mode,
1556
+ ...this.editing ? { objectNumbers: this.editing.wanted() } : {}
1557
+ }, (raw) => _embedpdf_engine_core_wire.AccessResponseSchema.parse(raw), signal, { session: true });
1558
+ this.securityState = response.security;
1559
+ this.protection = response.protection;
976
1560
  this.access = {
977
1561
  cdn: response.cdn,
978
1562
  passwordGrant: response.passwordGrant,
979
1563
  pdfPermissions: response.pdfPermissions,
980
1564
  scope: response.scope,
981
1565
  effectiveScope: response.effectiveScope,
1566
+ protection: response.protection,
982
1567
  identity: response.identity,
983
1568
  originPasswordPolicy: response.originPasswordPolicy,
984
1569
  expiresAt: response.expiresAt,
985
- ...response.renderPolicy ? { renderPolicy: response.renderPolicy } : {}
1570
+ ...response.renderPolicy ? { renderPolicy: response.renderPolicy } : {},
1571
+ bundleLimits: response.bundleLimits,
1572
+ ...response.edit ? { edit: response.edit } : {}
986
1573
  };
1574
+ if (response.edit) this.editing?.opened(response.edit);
987
1575
  this.http.setCdnAccess({
988
1576
  cdn: response.cdn,
989
1577
  docId: this.docId,
990
1578
  layerName: this.layerName
991
1579
  });
992
1580
  return {
993
- security: this.state,
1581
+ security: this.securityState,
994
1582
  access: this.access ?? void 0
995
1583
  };
996
1584
  }
@@ -1010,25 +1598,39 @@ function safeDecodeClaims(token) {
1010
1598
  }
1011
1599
  }
1012
1600
  /**
1013
- * CREATE's CollabTarget is the caller's own identity — the same
1601
+ * Create's CollabTarget is the caller's own identity — the same
1014
1602
  * derivation as engine-local's `ScopeGuard.targetForSelfCreate`, so
1015
1603
  * `:self` trivially passes and `:group=X` matches the caller's
1016
1604
  * default group.
1017
1605
  */
1018
1606
  function selfTarget(id) {
1019
1607
  return {
1020
- ...id.user_id !== void 0 ? { userId: id.user_id } : {},
1021
- ...id.group_id !== void 0 ? { groupId: id.group_id } : {}
1608
+ ...id.userId !== void 0 ? { userId: id.userId } : {},
1609
+ ...id.groupId !== void 0 ? { groupId: id.groupId } : {}
1022
1610
  };
1023
1611
  }
1612
+ const IDENTITY_STRING_FIELDS = [
1613
+ "userId",
1614
+ "displayName",
1615
+ "email",
1616
+ "title",
1617
+ "organization",
1618
+ "organizationalUnit",
1619
+ "groupId"
1620
+ ];
1621
+ /**
1622
+ * The token's `identity` claim, read the way the server reads it: string
1623
+ * fields, empty values absent. The server rejects a
1624
+ * malformed claim; unverified here, anything else is skipped.
1625
+ */
1024
1626
  function identityFromClaims(claims) {
1627
+ const claim = claims["identity"];
1628
+ if (!claim || typeof claim !== "object" || Array.isArray(claim)) return null;
1629
+ const record = claim;
1025
1630
  const out = {};
1026
- if (typeof claims["user_id"] === "string") out.user_id = claims["user_id"];
1027
- if (typeof claims["group_id"] === "string") out.group_id = claims["group_id"];
1028
- if (typeof claims["display_name"] === "string") out.display_name = claims["display_name"];
1029
- if (Array.isArray(claims["groups"])) {
1030
- const groups = claims["groups"].filter((g) => typeof g === "string");
1031
- if (groups.length > 0) out.groups = groups;
1631
+ for (const key of IDENTITY_STRING_FIELDS) {
1632
+ const value = record[key];
1633
+ if (typeof value === "string" && value.length > 0) out[key] = value;
1032
1634
  }
1033
1635
  return Object.keys(out).length > 0 ? out : null;
1034
1636
  }
@@ -1037,11 +1639,11 @@ function identityFromClaims(claims) {
1037
1639
  /**
1038
1640
  * Cloud-side digital signatures. Two kinds of read, two kinds of URL:
1039
1641
  *
1040
- * - The LAYER view (`list`, and `analyze` of the working copy) is pinned
1642
+ * - The layer view (`list`, and `analyze` of the working copy) is pinned
1041
1643
  * by the manifest's `docVersion`, like every other layer read: a
1042
1644
  * signature is a layer state change, the immutable URL moves with it.
1043
- * - Signed BYTES (`contents`, `digest`, `revisionBytes`, and `analyze`
1044
- * of history) belong to a base VERSION and are content-addressed by
1645
+ * - Signed bytes (`contents`, `digest`, `revisionBytes`, and `analyze`
1646
+ * of history) belong to a base version and are content-addressed by
1045
1647
  * the manifest's `baseSha`: immutable forever, shared by every layer
1046
1648
  * and every caller.
1047
1649
  *
@@ -1051,13 +1653,14 @@ function identityFromClaims(claims) {
1051
1653
  * refresh it before returning.
1052
1654
  */
1053
1655
  var CloudDocumentSignaturesService = class {
1054
- constructor(http, docId, layerName, isClosed, manifest, publisher) {
1656
+ constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
1055
1657
  this.http = http;
1056
1658
  this.docId = docId;
1057
1659
  this.layerName = layerName;
1058
1660
  this.isClosed = isClosed;
1059
1661
  this.manifest = manifest;
1060
1662
  this.publisher = publisher;
1663
+ this.writes = writes;
1061
1664
  }
1062
1665
  list() {
1063
1666
  const rejected = this.closedRejection();
@@ -1069,7 +1672,7 @@ var CloudDocumentSignaturesService = class {
1069
1672
  await this.manifest.refresh(s);
1070
1673
  }, signal));
1071
1674
  }
1072
- contents(field) {
1675
+ getContents(field) {
1073
1676
  const rejected = this.closedRejection();
1074
1677
  if (rejected) return rejected;
1075
1678
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
@@ -1078,7 +1681,7 @@ var CloudDocumentSignaturesService = class {
1078
1681
  return this.http.getBytes(_embedpdf_engine_core_wire.wirePaths.docVersionSignatureContents(this.docId, manifest.baseSha, name), signal);
1079
1682
  });
1080
1683
  }
1081
- digest(field, algorithm) {
1684
+ getDigest(field, algorithm) {
1082
1685
  const rejected = this.closedRejection();
1083
1686
  if (rejected) return rejected;
1084
1687
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
@@ -1087,7 +1690,7 @@ var CloudDocumentSignaturesService = class {
1087
1690
  return this.http.getBytes(_embedpdf_engine_core_wire.wirePaths.docVersionSignatureDigest(this.docId, manifest.baseSha, name, algorithm), signal);
1088
1691
  });
1089
1692
  }
1090
- revisionBytes(revisionIndex) {
1693
+ downloadRevision(revisionIndex) {
1091
1694
  const rejected = this.closedRejection();
1092
1695
  if (rejected) return rejected;
1093
1696
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
@@ -1096,16 +1699,17 @@ var CloudDocumentSignaturesService = class {
1096
1699
  });
1097
1700
  }
1098
1701
  /**
1099
- * `until: 'working-copy'` (or unset while the layer holds edits) judges
1100
- * the layer's pending edits against the base at the layer URL; anything
1101
- * else is history of the base version and resolves at the version URL.
1702
+ * `until: 'working-copy'` judges the layer's pending edits against the
1703
+ * base at the layer URL; anything else, the default `'persisted'`
1704
+ * included, is history of the base version and resolves at the version
1705
+ * URL (the saved file, as the local engine judges by default).
1102
1706
  */
1103
1707
  analyze(input) {
1104
1708
  const rejected = this.closedRejection();
1105
1709
  if (rejected) return rejected;
1106
1710
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1107
1711
  const manifest = await this.manifest.get(signal);
1108
- if (input.until === "working-copy" || input.until === void 0 && manifest.working) return this.http.getJsonWithRefresh(async (s) => {
1712
+ if (input.until === "working-copy") return this.http.getJsonWithRefresh(async (s) => {
1109
1713
  const current = await this.manifest.get(s);
1110
1714
  return _embedpdf_engine_core_wire.wirePaths.layerSignaturesAnalysis(this.docId, this.layerName, {
1111
1715
  docVersion: current.docVersion,
@@ -1124,65 +1728,65 @@ var CloudDocumentSignaturesService = class {
1124
1728
  }), (raw) => _embedpdf_engine_core_wire.ChangeAnalysisSchema.parse(raw), signal);
1125
1729
  });
1126
1730
  }
1127
- prepare(input) {
1731
+ prepare(input, options) {
1128
1732
  const rejected = this.closedRejection();
1129
1733
  if (rejected) return rejected;
1130
1734
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1131
- const { appearance, ...rest } = input;
1132
- const form = buildMutationForm({
1133
- ...rest,
1134
- ...appearance ? { appearance: {
1135
- resource: "appearance",
1136
- ...appearance.pageIndex !== void 0 ? { pageIndex: appearance.pageIndex } : {}
1137
- } } : {}
1138
- }, appearance ? { appearance: {
1139
- bytes: appearance.pdf.slice().buffer,
1140
- mimeType: "application/pdf",
1141
- name: "appearance.pdf"
1142
- } } : {});
1143
- const prepared = await this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerSignaturesPrepare(this.docId, this.layerName), form, (raw) => (0, _embedpdf_engine_core_wire.decodePrepared)(_embedpdf_engine_core_wire.SignaturePreparedWireSchema.parse(raw)), signal);
1144
- await this.manifest.refresh(signal);
1145
- this.publisher.publishLocal({
1146
- type: "signature.prepared",
1147
- signingId: prepared.signingId,
1148
- field: input.field
1735
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1736
+ return this.writes.run(opId, signal, async (write) => {
1737
+ const { appearance, ...rest } = input;
1738
+ const form = buildRoleMutationForm(rest, { appearance: appearance && {
1739
+ bytes: appearance.pdf.slice().buffer,
1740
+ mimeType: "application/pdf",
1741
+ name: "appearance.pdf"
1742
+ } });
1743
+ const prepared = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerSignaturesPrepare(this.docId, this.layerName), form, (raw) => (0, _embedpdf_engine_core_wire.decodePrepared)(_embedpdf_engine_core_wire.SignaturePreparedWireSchema.parse(raw)), signal, sent));
1744
+ await this.manifest.refresh(signal);
1745
+ this.publisher.publishWrite(opId, {
1746
+ type: "signatures.prepared",
1747
+ field: input.field,
1748
+ ...prepared
1749
+ });
1750
+ return prepared;
1149
1751
  });
1150
- return prepared;
1151
1752
  });
1152
1753
  }
1153
- complete(input) {
1754
+ complete(input, options) {
1154
1755
  const rejected = this.closedRejection();
1155
1756
  if (rejected) return rejected;
1156
1757
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1157
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerSignatureComplete(this.docId, this.layerName, input.signingId), {
1158
- cms: (0, _embedpdf_engine_core_wire.toBase64)(input.cms),
1159
- expectedVersion: input.expectedVersion
1160
- }, (raw) => _embedpdf_engine_core_wire.SignatureCompleteResultSchema.parse(raw), signal);
1161
- await this.manifest.refresh(signal);
1162
- if (result.status === "completed") {
1163
- this.publisher.publishLocal({
1164
- type: "signature.completed",
1758
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1759
+ return this.writes.run(opId, signal, async (write) => {
1760
+ const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerSignatureComplete(this.docId, this.layerName, input.signingId), {
1761
+ cms: (0, _embedpdf_engine_core_wire.toBase64)(input.cms),
1762
+ expectedVersion: input.expectedVersion
1763
+ }, (raw) => _embedpdf_engine_core_wire.SignatureCompleteResultSchema.parse(raw), signal, sent));
1764
+ await this.manifest.refresh(signal);
1765
+ if (result.status === "completed") this.publisher.publishWrite(opId, {
1766
+ type: "signatures.completed",
1165
1767
  signingId: input.signingId,
1166
1768
  ...result
1167
- });
1168
- this.publisher.publishLocal({
1769
+ }, {
1169
1770
  type: "document.versioned",
1170
1771
  version: result.version
1171
1772
  });
1172
- }
1173
- return result;
1773
+ return result;
1774
+ });
1174
1775
  });
1175
1776
  }
1176
- abort(signingId) {
1777
+ cancel(signingId, options) {
1177
1778
  const rejected = this.closedRejection();
1178
1779
  if (rejected) return rejected;
1179
1780
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1180
- const result = await this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerSignatureAbort(this.docId, this.layerName, signingId), (raw) => _embedpdf_engine_core_wire.SignatureAbortResultSchema.parse(raw), signal);
1181
- if (result.status === "aborted") this.publisher.publishLocal({
1182
- type: "signature.aborted",
1183
- signingId
1781
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1782
+ return this.writes.run(opId, signal, async (write) => {
1783
+ const result = await write.send((sent) => this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerSignatureCancel(this.docId, this.layerName, signingId), (raw) => _embedpdf_engine_core_wire.SignatureCancelResultSchema.parse(raw), signal, sent));
1784
+ if (result.status === "cancelled") this.publisher.publishWrite(opId, {
1785
+ type: "signatures.cancelled",
1786
+ signingId
1787
+ });
1788
+ return result;
1184
1789
  });
1185
- return result;
1186
1790
  });
1187
1791
  }
1188
1792
  /** Version routes address a field by its fully qualified name; an object-number ref is resolved through the snapshot. */
@@ -1193,8 +1797,8 @@ var CloudDocumentSignaturesService = class {
1193
1797
  return _embedpdf_engine_core_wire.wirePaths.layerSignatures(this.docId, this.layerName, manifest.docVersion);
1194
1798
  }, (raw) => _embedpdf_engine_core_wire.SignatureSnapshotSchema.parse(raw), async (s) => {
1195
1799
  await this.manifest.refresh(s);
1196
- }, signal)).signatures.find((s) => s.field.kind === "objectNumber" && s.field.fieldObjectNumber === field.fieldObjectNumber);
1197
- if (!match) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no signature field #${field.fieldObjectNumber}`);
1800
+ }, signal)).signatures.find((s) => s.field.kind === "objectNumber" && s.field.objectNumber === field.objectNumber);
1801
+ if (!match) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no signature field #${field.objectNumber}`);
1198
1802
  return match.fieldName;
1199
1803
  }
1200
1804
  closedRejection() {
@@ -1204,50 +1808,330 @@ var CloudDocumentSignaturesService = class {
1204
1808
  };
1205
1809
  //#endregion
1206
1810
  //#region src/document/CloudMetadataService.ts
1811
+ const STANDARD = {
1812
+ docPath: _embedpdf_engine_core_wire.wirePaths.docMetadata,
1813
+ layerPath: _embedpdf_engine_core_wire.wirePaths.layerMetadata,
1814
+ updatePath: _embedpdf_engine_core_wire.wirePaths.layerMetadataUpdate,
1815
+ parse: (raw) => _embedpdf_engine_core_wire.DocumentMetadataSchema.parse(raw),
1816
+ parseResult: (raw) => _embedpdf_engine_core_wire.MetadataUpdateResultSchema.parse(raw)
1817
+ };
1818
+ const CUSTOM = {
1819
+ docPath: _embedpdf_engine_core_wire.wirePaths.docCustomMetadata,
1820
+ layerPath: _embedpdf_engine_core_wire.wirePaths.layerCustomMetadata,
1821
+ updatePath: _embedpdf_engine_core_wire.wirePaths.layerCustomMetadataUpdate,
1822
+ parse: (raw) => _embedpdf_engine_core_wire.CustomMetadataSchema.parse(raw),
1823
+ parseResult: (raw) => _embedpdf_engine_core_wire.CustomMetadataUpdateResultSchema.parse(raw)
1824
+ };
1825
+ function closedError(docId) {
1826
+ return new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${docId} is closed`);
1827
+ }
1828
+ /**
1829
+ * The read. The JSON lives at a content-addressed `…@metadataVersion=N` leaf
1830
+ * (not in the manifest); the manifest only publishes the `metadataVersion`
1831
+ * pointer, which both halves of the dict share. So the read pulls it from
1832
+ * the cached manifest, fetches the leaf, and on a 404 (stale pointer)
1833
+ * transparently refreshes the manifest and retries once. `metadataVersion`
1834
+ * bumps only on metadata writes, so the leaf stays cached across page and
1835
+ * annotation edits.
1836
+ */
1837
+ function readLeaf(deps, leaf) {
1838
+ if (deps.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(closedError(deps.docId));
1839
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1840
+ const buildPath = async (s) => {
1841
+ const manifest = await deps.manifest.get(s);
1842
+ return planesInherited(manifest, ["metadata"]) ? leaf.docPath(deps.docId, manifest.metadataVersion) : leaf.layerPath(deps.docId, deps.layerName, manifest.metadataVersion);
1843
+ };
1844
+ return deps.http.getJsonWithRefresh(buildPath, leaf.parse, async (s) => {
1845
+ await deps.manifest.refresh(s);
1846
+ }, signal);
1847
+ });
1848
+ }
1849
+ /**
1850
+ * The write. It only advances docVersion + metadataVersion (no per-page pin
1851
+ * changes, no layoutVersion), so the cached manifest is patched in place —
1852
+ * no refetch. `publish` gets the write's `opId`.
1853
+ */
1854
+ function writeLeaf(deps, leaf, patch, options, publish) {
1855
+ if (deps.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(closedError(deps.docId));
1856
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1857
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1858
+ return deps.writes.run(opId, signal, async (write) => {
1859
+ const result = await write.send((sent) => deps.http.postJson(leaf.updatePath(deps.docId, deps.layerName), patch, leaf.parseResult, signal, sent));
1860
+ deps.manifest.apply(result.meta, ["metadata"]);
1861
+ publish(result, opId);
1862
+ return result;
1863
+ });
1864
+ });
1865
+ }
1866
+ var CloudCustomMetadataService = class {
1867
+ constructor(deps) {
1868
+ this.deps = deps;
1869
+ }
1870
+ get() {
1871
+ return readLeaf(this.deps, CUSTOM);
1872
+ }
1873
+ update(patch, options) {
1874
+ return writeLeaf(this.deps, CUSTOM, patch, options, (result, opId) => this.deps.publisher.publishWrite(opId, {
1875
+ type: "metadata.customUpdated",
1876
+ ...result
1877
+ }));
1878
+ }
1879
+ };
1207
1880
  var CloudMetadataService = class {
1208
- constructor(http, docId, layerName, isClosed, manifest, publisher) {
1209
- this.http = http;
1210
- this.docId = docId;
1211
- this.layerName = layerName;
1212
- this.isClosed = isClosed;
1213
- this.manifest = manifest;
1214
- this.publisher = publisher;
1881
+ constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
1882
+ _defineProperty(this, "custom", void 0);
1883
+ _defineProperty(this, "deps", void 0);
1884
+ this.deps = {
1885
+ http,
1886
+ docId,
1887
+ layerName,
1888
+ isClosed,
1889
+ manifest,
1890
+ publisher,
1891
+ writes
1892
+ };
1893
+ this.custom = new CloudCustomMetadataService(this.deps);
1215
1894
  }
1216
- /**
1217
- * Document metadata read. The Info-dict JSON lives at the
1218
- * content-addressed `/metadata@metadataVersion=N` leaf (not in the
1219
- * manifest); the manifest only publishes the `metadataVersion` pointer.
1220
- * So `read()` pulls `metadataVersion` from the cached manifest, fetches
1221
- * the leaf, and on a 404 (stale pointer) transparently refreshes the
1222
- * manifest and retries once. `metadataVersion` bumps only on metadata
1223
- * writes, so this leaf stays cached across page and annotation edits.
1224
- */
1225
- read() {
1226
- 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`));
1227
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1228
- const buildPath = async (s) => {
1229
- const manifest = await this.manifest.get(s);
1230
- return planesInherited(manifest, ["metadata"]) ? _embedpdf_engine_core_wire.wirePaths.docMetadata(this.docId, manifest.metadataVersion) : _embedpdf_engine_core_wire.wirePaths.layerMetadata(this.docId, this.layerName, manifest.metadataVersion);
1231
- };
1232
- return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.DocumentMetadataSchema.parse(raw), async (s) => {
1233
- await this.manifest.refresh(s);
1234
- }, signal);
1235
- });
1895
+ get() {
1896
+ return readLeaf(this.deps, STANDARD);
1236
1897
  }
1237
- update(patch) {
1238
- 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`));
1898
+ update(patch, options) {
1899
+ return writeLeaf(this.deps, STANDARD, patch, options, (result, opId) => this.deps.publisher.publishWrite(opId, {
1900
+ type: "metadata.updated",
1901
+ ...result
1902
+ }));
1903
+ }
1904
+ };
1905
+ //#endregion
1906
+ //#region src/document/CloudObjectNumberPool.ts
1907
+ /** Below this many held numbers, a write asks for a top-up… */
1908
+ const LOW_WATER = 8;
1909
+ /** …of up to this many, the most the server tops up by. */
1910
+ const TOP_UP = 32;
1911
+ /** The most one bulk reservation asks for. */
1912
+ const MAX_RESERVATION = 1e3;
1913
+ /** An editing session's lifetime (ms) until the server states one: the protocol's 15 minutes. */
1914
+ const DEFAULT_LIFETIME_MS = 15 * 6e4;
1915
+ /**
1916
+ * The cloud engine's object numbers: what the server handed this editing
1917
+ * session (the engine's `X-Engine-Session-Id` with the token's subject).
1918
+ *
1919
+ * Numbers arrive with `/access`, with a write's response (the top-up a
1920
+ * write asks for when fewer than 8 are held), with the event stream's
1921
+ * `session` events, and from `reserve`'s bulk reservation. A number
1922
+ * `take` hands out is the caller's until a write naming it commits.
1923
+ *
1924
+ * The session lives while the event stream is connected or something
1925
+ * keeps it alive; without that it expires, and its numbers may go to
1926
+ * another session. So once its expiry has passed while the stream is down,
1927
+ * the pool hands out nothing until the server says again what it holds.
1928
+ * The stream's `session` event lists everything the session holds: any
1929
+ * number this pool held or handed out that isn't on it, and that no write
1930
+ * in flight names, is lost (`onLost`, `'reclaimed'`). A new version of the
1931
+ * document numbers objects anew: everything is lost (`'versioned'`).
1932
+ */
1933
+ var CloudObjectNumberPool = class {
1934
+ constructor(opts) {
1935
+ this.opts = opts;
1936
+ _defineProperty(this, "now", void 0);
1937
+ _defineProperty(this, "available", []);
1938
+ _defineProperty(this, "taken", /* @__PURE__ */ new Set());
1939
+ _defineProperty(this, "writing", /* @__PURE__ */ new Map());
1940
+ _defineProperty(this, "expiresAt", 0);
1941
+ _defineProperty(this, "halfLifetimeMs", DEFAULT_LIFETIME_MS / 2);
1942
+ _defineProperty(this, "streamConnected", false);
1943
+ _defineProperty(this, "listeners", /* @__PURE__ */ new Set());
1944
+ _defineProperty(this, "reserving", Promise.resolve());
1945
+ _defineProperty(this, "version", null);
1946
+ this.now = opts.now ?? Date.now;
1947
+ }
1948
+ get held() {
1949
+ return this.usable() ? this.available.length : 0;
1950
+ }
1951
+ take() {
1952
+ if (!this.usable()) return null;
1953
+ const objectNumber = this.available.shift();
1954
+ if (objectNumber === void 0) return null;
1955
+ this.taken.add(objectNumber);
1956
+ return objectNumber;
1957
+ }
1958
+ reserve(count) {
1239
1959
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1240
- const result = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerMetadataUpdate(this.docId, this.layerName), patch, (raw) => _embedpdf_engine_core_wire.MetadataUpdateResultSchema.parse(raw), signal);
1241
- if (result.cache) this.manifest.applyMetadata(result.cache);
1242
- this.publisher.publishLocal({
1243
- type: "metadata.updated",
1244
- ...result
1960
+ const turn = this.reserving.then(async () => {
1961
+ while (this.held < count) {
1962
+ const wanted = Math.min(count - this.held, MAX_RESERVATION);
1963
+ const reserved = await this.opts.reserve(wanted, signal);
1964
+ this.receive(reserved.objectNumbers, reserved.expiresIn);
1965
+ }
1245
1966
  });
1246
- return result;
1967
+ this.reserving = turn.catch(() => void 0);
1968
+ await turn;
1247
1969
  });
1248
1970
  }
1971
+ onLost(listener) {
1972
+ this.listeners.add(listener);
1973
+ return () => {
1974
+ this.listeners.delete(listener);
1975
+ };
1976
+ }
1977
+ /** How many numbers a write should ask the server for: 0 while enough are held. */
1978
+ topUp() {
1979
+ const held = this.held;
1980
+ return held < LOW_WATER ? TOP_UP - held : 0;
1981
+ }
1982
+ /** How many numbers `/access` should ask for: enough to make up the first block. */
1983
+ wantedAtAccess() {
1984
+ return Math.max(0, LOW_WATER - this.held);
1985
+ }
1986
+ /**
1987
+ * `/access` answered for the editing session. A session the server made
1988
+ * anew holds nothing of what this pool had.
1989
+ */
1990
+ opened(edit) {
1991
+ if (edit.session === "new") this.lose(this.unspent(), "reclaimed");
1992
+ this.receive(edit.objectNumbers, edit.expiresIn);
1993
+ }
1994
+ /**
1995
+ * Numbers handed to the session: by `/access`, a write's top-up or the
1996
+ * bulk reservation; `expiresIn` (seconds) when the answer told it. Numbers
1997
+ * held from before an expiry the server can't vouch for any more are
1998
+ * dropped: only a `session` event says which survived.
1999
+ */
2000
+ receive(ranges, expiresIn) {
2001
+ if (expiresIn !== void 0) {
2002
+ if (!this.usable()) this.lose(this.available.splice(0), "reclaimed");
2003
+ this.expiresIn(expiresIn);
2004
+ }
2005
+ this.add((0, _embedpdf_engine_core_runtime.objectNumbersIn)(ranges));
2006
+ }
2007
+ /**
2008
+ * The event stream's `session` event: everything the session holds now.
2009
+ * What this pool held or handed out and the list doesn't name, unless a
2010
+ * write in flight names it, is lost.
2011
+ */
2012
+ sync(status) {
2013
+ this.expiresIn(status.expiresIn);
2014
+ const held = new Set((0, _embedpdf_engine_core_runtime.objectNumbersIn)(status.held));
2015
+ const lost = this.unspent().filter((n) => !held.has(n));
2016
+ for (const n of lost) this.taken.delete(n);
2017
+ this.available = [...held].filter((n) => !this.taken.has(n) && !this.writing.has(n)).sort((a, b) => a - b);
2018
+ this.emit(lost, "reclaimed");
2019
+ }
2020
+ /**
2021
+ * Version `sha256` was published: it numbers objects anew, so every number
2022
+ * held or handed out is gone. Told once per version (the stream and this
2023
+ * engine's own completion both tell it, in either order).
2024
+ */
2025
+ versioned(sha256) {
2026
+ if (sha256 === this.version) return;
2027
+ this.version = sha256;
2028
+ this.lose(this.unspent(), "versioned");
2029
+ }
2030
+ /** The event stream connected or dropped: while it's up, the server keeps the session alive. */
2031
+ connected(connected) {
2032
+ this.streamConnected = connected;
2033
+ }
2034
+ /** A write that names `numbers` was called. */
2035
+ beginWrite(numbers) {
2036
+ for (const n of numbers) this.writing.set(n, (this.writing.get(n) ?? 0) + 1);
2037
+ }
2038
+ /**
2039
+ * The write that named `numbers` settled. A committed write also kept the
2040
+ * session alive: at least half its lifetime is left.
2041
+ */
2042
+ endWrite(numbers, outcome) {
2043
+ for (const n of numbers) {
2044
+ const left = (this.writing.get(n) ?? 1) - 1;
2045
+ if (left > 0) this.writing.set(n, left);
2046
+ else this.writing.delete(n);
2047
+ if (outcome !== "failed") this.taken.delete(n);
2048
+ }
2049
+ if (outcome === "committed") this.expiresAt = Math.max(this.expiresAt, this.now() + this.halfLifetimeMs);
2050
+ }
2051
+ usable() {
2052
+ return this.streamConnected || this.now() < this.expiresAt;
2053
+ }
2054
+ expiresIn(seconds) {
2055
+ const ms = seconds * 1e3;
2056
+ this.expiresAt = this.now() + ms;
2057
+ this.halfLifetimeMs = Math.max(this.halfLifetimeMs, ms / 2);
2058
+ }
2059
+ add(numbers) {
2060
+ const known = /* @__PURE__ */ new Set([...this.available, ...this.taken]);
2061
+ const fresh = numbers.filter((n) => !known.has(n) && !this.writing.has(n));
2062
+ if (fresh.length === 0) return;
2063
+ this.available = [...this.available, ...fresh].sort((a, b) => a - b);
2064
+ }
2065
+ /** Held or handed out, and named by no write in flight. */
2066
+ unspent() {
2067
+ return [...this.available, ...this.taken].filter((n) => !this.writing.has(n));
2068
+ }
2069
+ lose(numbers, reason) {
2070
+ const gone = new Set(numbers);
2071
+ this.available = this.available.filter((n) => !gone.has(n));
2072
+ for (const n of gone) this.taken.delete(n);
2073
+ this.emit([...gone], reason);
2074
+ }
2075
+ emit(numbers, reason) {
2076
+ if (numbers.length === 0) return;
2077
+ const lost = {
2078
+ numbers: [...numbers].sort((a, b) => a - b),
2079
+ reason
2080
+ };
2081
+ for (const listener of [...this.listeners]) listener(lost);
2082
+ }
1249
2083
  };
1250
2084
  //#endregion
2085
+ //#region src/document/appearanceForm.ts
2086
+ /**
2087
+ * Parse the appearance `multipart/form-data` response into the same
2088
+ * `AnnotationAppearanceImagesResult` shape the local engine produces. The
2089
+ * `body` part is validated against the wire schema; each image (the part
2090
+ * `resource:<key>` its entry names) is wrapped in a `PageImageHandle`
2091
+ * backed by the in-memory blob we already downloaded.
2092
+ */
2093
+ async function parseAppearanceForm(form) {
2094
+ const raw = form.get("body");
2095
+ if (typeof raw !== "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, "appearance response missing its body part");
2096
+ let parsedJson;
2097
+ try {
2098
+ parsedJson = JSON.parse(raw);
2099
+ } catch (err) {
2100
+ throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `appearance body is not valid JSON: ${err?.message ?? err}`);
2101
+ }
2102
+ const body = _embedpdf_engine_core_wire.AnnotationAppearanceBatchSchema.parse(parsedJson);
2103
+ const appearances = await Promise.all(body.appearances.map(async (entry) => {
2104
+ const name = `resource:${entry.resources.image}`;
2105
+ const partValue = form.get(name);
2106
+ if (partValue === null || typeof partValue === "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `appearance response missing image part "${name}"`);
2107
+ const blob = partValue;
2108
+ const bytes = new Uint8Array(await blob.arrayBuffer());
2109
+ const image = (0, _embedpdf_engine_core_runtime.createPageImageHandle)({
2110
+ width: entry.width,
2111
+ height: entry.height,
2112
+ format: entry.format,
2113
+ contentType: entry.contentType,
2114
+ source: {
2115
+ kind: "bytes",
2116
+ bytes
2117
+ }
2118
+ }, { async blob() {
2119
+ return blob;
2120
+ } });
2121
+ return {
2122
+ ref: entry.ref,
2123
+ mode: entry.mode,
2124
+ state: entry.state,
2125
+ rect: entry.rect,
2126
+ image
2127
+ };
2128
+ }));
2129
+ return {
2130
+ page: body.page,
2131
+ appearances
2132
+ };
2133
+ }
2134
+ //#endregion
1251
2135
  //#region src/document/CloudPageAnnotationsService.ts
1252
2136
  /**
1253
2137
  * Cloud-side page annotation service. Mirrors the local wiring: each
@@ -1259,45 +2143,46 @@ var CloudMetadataService = class {
1259
2143
  * manifest. Mutations use unversioned layer URLs and are never cached.
1260
2144
  */
1261
2145
  var CloudPageAnnotationsService = class {
1262
- constructor(http, docId, layerName, pageObjectNumber, isClosed, manifest, publisher) {
2146
+ constructor(http, docId, layerName, pageRef, isClosed, manifest, publisher, writes) {
1263
2147
  this.http = http;
1264
2148
  this.docId = docId;
1265
2149
  this.layerName = layerName;
1266
- this.pageObjectNumber = pageObjectNumber;
2150
+ this.pageRef = pageRef;
1267
2151
  this.isClosed = isClosed;
1268
2152
  this.manifest = manifest;
1269
2153
  this.publisher = publisher;
2154
+ this.writes = writes;
1270
2155
  }
1271
2156
  list() {
1272
2157
  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`));
1273
2158
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1274
2159
  const buildPath = async (s) => {
1275
2160
  const manifest = await this.manifest.get(s);
1276
- const page = manifest.pages.find((p) => p.state.pageObjectNumber === this.pageObjectNumber);
1277
- if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${this.pageObjectNumber} in document ${this.docId}`);
1278
- return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotations(this.docId, this.pageObjectNumber, page.cache.annotationVersion) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotations(this.docId, this.layerName, this.pageObjectNumber, page.cache.annotationVersion);
2161
+ const pageObjectNumber = this.pageRef.objectNumber;
2162
+ const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
2163
+ if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
2164
+ return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotations(this.docId, this.pageRef, page.cache.annotationVersion) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotations(this.docId, this.layerName, this.pageRef, page.cache.annotationVersion);
1279
2165
  };
1280
- return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.AnnotationListPageSnapshotSchema.parse(raw), async (s) => {
2166
+ return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.AnnotationListSchema.parse(raw), async (s) => {
1281
2167
  await this.manifest.refresh(s);
1282
2168
  }, signal);
1283
2169
  });
1284
2170
  }
1285
- renderAppearances(_options) {
1286
- return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "annotations.renderAppearances() raw rasters are not available in the cloud engine; use renderAppearanceImages()"));
1287
- }
1288
- renderAppearanceImages(options = {}) {
2171
+ renderAppearances(options = {}) {
1289
2172
  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`));
1290
2173
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
2174
+ (0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
1291
2175
  const format = options.format ?? "webp";
1292
2176
  const buildPath = async (s) => {
1293
2177
  const manifest = await this.manifest.get(s);
1294
- const page = manifest.pages.find((p) => p.state.pageObjectNumber === this.pageObjectNumber);
1295
- if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${this.pageObjectNumber} in document ${this.docId}`);
2178
+ const pageObjectNumber = this.pageRef.objectNumber;
2179
+ const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
2180
+ if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
1296
2181
  const wireToken = (0, _embedpdf_engine_core_wire.annotationAppearancesImageOptionsToWire)({
1297
2182
  ...options,
1298
2183
  format
1299
2184
  }, { annotationVersion: page.cache.annotationVersion });
1300
- return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotationAppearances(this.docId, this.pageObjectNumber, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationAppearances(this.docId, this.layerName, this.pageObjectNumber, wireToken);
2185
+ return planesInherited(manifest, ["annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageAnnotationAppearances(this.docId, this.pageRef, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationAppearances(this.docId, this.layerName, this.pageRef, wireToken);
1301
2186
  };
1302
2187
  return parseAppearanceForm(await this.http.getFormDataWithRefresh(buildPath, async (s) => {
1303
2188
  await this.manifest.refresh(s);
@@ -1305,121 +2190,140 @@ var CloudPageAnnotationsService = class {
1305
2190
  });
1306
2191
  }
1307
2192
  /**
1308
- * Decode and return the file embedded in a FileAttachment annotation.
1309
- * A read over the immutable `attachment-files` leaf, pinned by the
1310
- * manifest's `attachmentsVersion` (annotation-level files re-key on the
1311
- * same pin as document-level ones), with the same stale-404 refresh
1312
- * retry as {@link list}. Weak `index` refs cannot be spliced into a GET
1313
- * URL (no body to carry the revision), so bytes reads require a stable
1314
- * id — the same `:annotKey` routing update()/delete() use.
2193
+ * One of the annotation's resources. `file` is a read over the immutable
2194
+ * `attachment-files` leaf, pinned by the manifest's `attachmentsVersion`
2195
+ * (annotation-level files re-key on the same pin as document-level ones),
2196
+ * with the same stale-404 refresh retry as {@link list}. `appearance` is a
2197
+ * derived read (no-store), like {@link exportAppearance}. Both address the
2198
+ * annotation by the same `:annotKey` update()/delete() use.
1315
2199
  */
1316
- downloadFile(ref) {
2200
+ downloadResource(ref, role) {
1317
2201
  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`));
1318
- if (ref.pageObjectNumber !== this.pageObjectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `ref.pageObjectNumber ${ref.pageObjectNumber} != page ${this.pageObjectNumber}`));
1319
- if (ref.kind === "index") return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "downloadFile requires a stable ref (objectNumber or nm); index refs cannot address the content-addressed file leaf"));
1320
- const annotKey = (0, _embedpdf_engine_core_runtime.encodeStableIdKey)(refToStableId(ref));
2202
+ if (ref.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `ref.page ${ref.page.objectNumber} != page ${this.pageRef.objectNumber}`));
2203
+ const annotKey = (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(ref);
2204
+ if (role === "appearance") return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.getBytes(_embedpdf_engine_core_wire.wirePaths.layerAnnotationAppearanceResource(this.docId, this.layerName, this.pageRef, annotKey), signal));
1321
2205
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1322
2206
  const buildPath = async (s) => {
1323
2207
  const manifest = await this.manifest.get(s);
1324
- return planesInherited(manifest, ["annotations", "attachments"]) ? _embedpdf_engine_core_wire.wirePaths.docAnnotationFile(this.docId, this.pageObjectNumber, annotKey, manifest.attachmentsVersion) : _embedpdf_engine_core_wire.wirePaths.layerAnnotationFile(this.docId, this.layerName, this.pageObjectNumber, annotKey, manifest.attachmentsVersion);
2208
+ return planesInherited(manifest, ["annotations", "attachments"]) ? _embedpdf_engine_core_wire.wirePaths.docAnnotationFile(this.docId, this.pageRef, annotKey, manifest.attachmentsVersion) : _embedpdf_engine_core_wire.wirePaths.layerAnnotationFile(this.docId, this.layerName, this.pageRef, annotKey, manifest.attachmentsVersion);
1325
2209
  };
1326
- return parseAttachmentContent(await this.http.getFileWithRefresh(buildPath, async (s) => {
2210
+ return (await this.http.getFileWithRefresh(buildPath, async (s) => {
1327
2211
  await this.manifest.refresh(s);
1328
- }, signal));
2212
+ }, signal)).bytes;
1329
2213
  });
1330
2214
  }
1331
- create(draft) {
2215
+ create(draft, options = {}) {
1332
2216
  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`));
1333
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1334
- const { wire, resources } = await (0, _embedpdf_engine_core_runtime.normalizeAnnotationDraft)(draft);
1335
- const path = _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsCreate(this.docId, this.layerName, this.pageObjectNumber);
2217
+ let opId;
2218
+ try {
2219
+ opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
2220
+ } catch (err) {
2221
+ return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
2222
+ }
2223
+ const { resources, objectNumber } = options;
2224
+ const data = (0, _embedpdf_engine_core_runtime.withFileFromResource)(draft, resources);
2225
+ const path = withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsCreate(this.docId, this.layerName, this.pageRef), { objectNumber });
2226
+ return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
2227
+ const wireResources = await (0, _embedpdf_engine_core_runtime.resolveAnnotationResources)(resources);
1336
2228
  const parse = (raw) => _embedpdf_engine_core_wire.AnnotationCreateResultSchema.parse(raw);
1337
- const result = hasResources(resources) ? await this.http.postMultipartJson(path, buildMutationForm(wire, resources), parse, signal) : await this.http.postJson(path, wire, parse, signal);
1338
- return this.absorbMutation(result, "annotation.created");
1339
- });
2229
+ const result = await write.send((sent) => (0, _embedpdf_engine_core_runtime.hasAnnotationResources)(wireResources) ? this.http.postMultipartJson(path, buildAnnotationMutationForm(data, wireResources), parse, signal, sent) : this.http.postJson(path, data, parse, signal, sent));
2230
+ return this.absorbMutation(opId, result, "annotations.created");
2231
+ }, [objectNumber]));
1340
2232
  }
1341
- update(ref, patch) {
2233
+ update(ref, patch, options = {}) {
1342
2234
  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`));
1343
- if (ref.pageObjectNumber !== this.pageObjectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `ref.pageObjectNumber ${ref.pageObjectNumber} != page ${this.pageObjectNumber}`));
1344
- if (ref.kind === "index") {
1345
- const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, ref.pageObjectNumber, "index");
1346
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1347
- const { wire, resources } = await (0, _embedpdf_engine_core_runtime.normalizeAnnotationPatch)(patch);
1348
- const result = await this.patchMutation(path, {
1349
- ref,
1350
- patch: wire
1351
- }, resources, signal);
1352
- return this.absorbMutation(result, "annotation.updated");
1353
- });
2235
+ let opId;
2236
+ try {
2237
+ opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
2238
+ } catch (err) {
2239
+ return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
1354
2240
  }
1355
- const stableKey = (0, _embedpdf_engine_core_runtime.encodeStableIdKey)(refToStableId(ref));
1356
- const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, ref.pageObjectNumber, stableKey);
1357
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1358
- const { wire, resources } = await (0, _embedpdf_engine_core_runtime.normalizeAnnotationPatch)(patch);
1359
- const result = await this.patchMutation(path, { patch: wire }, resources, signal);
1360
- return this.absorbMutation(result, "annotation.updated");
1361
- });
2241
+ const { resources } = options;
2242
+ if (ref.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `ref.page ${ref.page.objectNumber} != page ${this.pageRef.objectNumber}`));
2243
+ const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, this.pageRef, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(ref));
2244
+ return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
2245
+ const wireResources = await (0, _embedpdf_engine_core_runtime.resolveAnnotationResources)(resources);
2246
+ const result = await this.patchMutation(write, path, { patch }, wireResources, signal);
2247
+ return this.absorbMutation(opId, result, "annotations.updated");
2248
+ }));
1362
2249
  }
1363
- /** PATCH as plain JSON, or as multipart when the patch carried binaries. */
1364
- patchMutation(path, body, resources, signal) {
2250
+ /** PATCH as plain JSON, or as multipart when resources came with the patch. */
2251
+ patchMutation(write, path, body, resources, signal) {
1365
2252
  const parse = (raw) => _embedpdf_engine_core_wire.AnnotationUpdateResultSchema.parse(raw);
1366
- if (hasResources(resources)) return this.http.patchMultipartJson(path, buildMutationForm(body, resources), parse, signal);
1367
- return this.http.patchJson(path, body, parse, signal);
2253
+ return write.send((sent) => (0, _embedpdf_engine_core_runtime.hasAnnotationResources)(resources) ? this.http.patchMultipartJson(path, buildAnnotationMutationForm(body, resources), parse, signal, sent) : this.http.patchJson(path, body, parse, signal, sent));
1368
2254
  }
1369
- delete(ref) {
2255
+ delete(ref, options) {
1370
2256
  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`));
1371
- if (ref.pageObjectNumber !== this.pageObjectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `ref.pageObjectNumber ${ref.pageObjectNumber} != page ${this.pageObjectNumber}`));
1372
- if (ref.kind === "index") {
1373
- const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, ref.pageObjectNumber, "index");
1374
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1375
- const result = await this.http.patchJson(path, {
1376
- ref,
1377
- op: "delete"
1378
- }, (raw) => _embedpdf_engine_core_wire.AnnotationDeleteResultSchema.parse(raw), signal);
1379
- return this.absorbMutation(result, "annotation.deleted");
1380
- });
2257
+ let opId;
2258
+ try {
2259
+ opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
2260
+ } catch (err) {
2261
+ return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
1381
2262
  }
1382
- const stableKey = (0, _embedpdf_engine_core_runtime.encodeStableIdKey)(refToStableId(ref));
1383
- const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, ref.pageObjectNumber, stableKey);
1384
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1385
- const result = await this.http.deleteJson(path, (raw) => _embedpdf_engine_core_wire.AnnotationDeleteResultSchema.parse(raw), signal);
1386
- return this.absorbMutation(result, "annotation.deleted");
1387
- });
2263
+ if (ref.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `ref.page ${ref.page.objectNumber} != page ${this.pageRef.objectNumber}`));
2264
+ const path = _embedpdf_engine_core_wire.wirePaths.layerAnnotationByKey(this.docId, this.layerName, this.pageRef, (0, _embedpdf_engine_core_runtime.encodeAnnotKey)(ref));
2265
+ return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
2266
+ const result = await write.send((sent) => this.http.deleteJson(path, (raw) => _embedpdf_engine_core_wire.AnnotationDeleteResultSchema.parse(raw), signal, sent));
2267
+ return this.absorbDelete(opId, result);
2268
+ }));
1388
2269
  }
1389
- move(refs, toIndex) {
2270
+ reorder(refs, position, options) {
1390
2271
  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`));
1391
- for (const r of refs) if (r.pageObjectNumber !== this.pageObjectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `move ref points at page ${r.pageObjectNumber}; service is bound to page ${this.pageObjectNumber}`));
1392
- const path = _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsMove(this.docId, this.layerName, this.pageObjectNumber);
1393
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1394
- const result = await this.http.postJson(path, {
2272
+ let opId;
2273
+ try {
2274
+ opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
2275
+ } catch (err) {
2276
+ return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
2277
+ }
2278
+ for (const r of refs) if (r.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `reorder ref points at page ${r.page.objectNumber}; service is bound to page ${this.pageRef.objectNumber}`));
2279
+ const path = _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsReorder(this.docId, this.layerName, this.pageRef);
2280
+ return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
2281
+ const result = await write.send((sent) => this.http.postJson(path, {
1395
2282
  refs,
1396
- toIndex
1397
- }, (raw) => _embedpdf_engine_core_wire.AnnotationMoveResultSchema.parse(raw), signal);
1398
- return this.absorbMutation(result, "annotation.moved");
1399
- });
2283
+ position
2284
+ }, (raw) => _embedpdf_engine_core_wire.AnnotationReorderResultSchema.parse(raw), signal, sent));
2285
+ this.manifest.apply(result.meta, ["annotations"]);
2286
+ this.publisher.publishWrite(opId, {
2287
+ type: "annotations.reordered",
2288
+ page: this.pageRef,
2289
+ ...result
2290
+ });
2291
+ return result;
2292
+ }));
1400
2293
  }
1401
- flatten(refs, usage = "display") {
2294
+ flatten(refs, options) {
2295
+ const usage = options?.usage ?? "display";
1402
2296
  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`));
1403
- for (const r of refs) if (r.pageObjectNumber !== this.pageObjectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `flatten ref points at page ${r.pageObjectNumber}; service is bound to page ${this.pageObjectNumber}`));
1404
- const path = _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsFlatten(this.docId, this.layerName, this.pageObjectNumber);
1405
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1406
- const result = await this.http.postJson(path, {
2297
+ let opId;
2298
+ try {
2299
+ opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
2300
+ } catch (err) {
2301
+ return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(err);
2302
+ }
2303
+ for (const r of refs) if (r.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `flatten ref points at page ${r.page.objectNumber}; service is bound to page ${this.pageRef.objectNumber}`));
2304
+ const path = _embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsFlatten(this.docId, this.layerName, this.pageRef);
2305
+ return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.writes.run(opId, signal, async (write) => {
2306
+ const result = await write.send((sent) => this.http.postJson(path, {
1407
2307
  refs,
1408
2308
  usage
1409
- }, (raw) => _embedpdf_engine_core_wire.AnnotationFlattenResultSchema.parse(raw), signal);
1410
- if (result.meta === null) return result;
1411
- this.manifest.apply(result.meta, ["content", "annotations"]);
1412
- this.publisher.publishLocal({
2309
+ }, (raw) => _embedpdf_engine_core_wire.AnnotationFlattenResultSchema.parse(raw), signal, sent));
2310
+ if (result.meta.cacheDelta === null) return result;
2311
+ this.manifest.apply(result.meta, [
2312
+ "content",
2313
+ "annotations",
2314
+ "forms"
2315
+ ]);
2316
+ this.publisher.publishWrite(opId, {
1413
2317
  type: "annotations.flattened",
1414
2318
  ...result
1415
2319
  });
1416
2320
  return result;
1417
- });
2321
+ }));
1418
2322
  }
1419
2323
  exportAppearance(refs) {
1420
2324
  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`));
1421
- for (const r of refs) if (r.pageObjectNumber !== this.pageObjectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `exportAppearance ref points at page ${r.pageObjectNumber}; service is bound to page ${this.pageObjectNumber}`));
1422
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsAppearance(this.docId, this.layerName, this.pageObjectNumber), { refs }, signal));
2325
+ for (const r of refs) if (r.page.objectNumber !== this.pageRef.objectNumber) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `exportAppearance ref points at page ${r.page.objectNumber}; service is bound to page ${this.pageRef.objectNumber}`));
2326
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPageAnnotationsAppearance(this.docId, this.layerName, this.pageRef), { refs }, signal));
1423
2327
  }
1424
2328
  /**
1425
2329
  * Patch the cached manifest, then publish the mutation to the document's
@@ -1428,148 +2332,130 @@ var CloudPageAnnotationsService = class {
1428
2332
  * `type` with the matching result by construction; the cast localizes that
1429
2333
  * pairing here instead of widening every site.
1430
2334
  */
1431
- absorbMutation(result, type) {
2335
+ /** A delete's event names what went, for listeners that didn't delete it. */
2336
+ absorbDelete(opId, result) {
2337
+ this.manifest.apply(result.meta, ["annotations"]);
2338
+ this.publisher.publishWrite(opId, {
2339
+ type: "annotations.deleted",
2340
+ page: this.pageRef,
2341
+ deleted: (0, _embedpdf_engine_core_runtime.deletedAnnotationsOf)(result),
2342
+ ...result
2343
+ });
2344
+ return result;
2345
+ }
2346
+ absorbMutation(opId, result, type) {
1432
2347
  this.manifest.apply(result.meta, ["annotations"]);
1433
- this.publisher.publishLocal({
2348
+ this.publisher.publishWrite(opId, {
1434
2349
  type,
1435
- pageObjectNumber: this.pageObjectNumber,
2350
+ page: this.pageRef,
1436
2351
  ...result
1437
2352
  });
1438
2353
  return result;
1439
2354
  }
1440
2355
  };
2356
+ //#endregion
2357
+ //#region src/document/CloudPageFormsService.ts
1441
2358
  /**
1442
- * Parse the appearance `multipart/form-data` response into the same
1443
- * `AnnotationAppearanceImagesResult` shape the local engine produces. The
1444
- * `manifest` part is validated against the wire schema; each image part is
1445
- * wrapped in a `PageImageHandle` backed by the in-memory blob we already
1446
- * downloaded.
1447
- */
1448
- async function parseAppearanceForm(form) {
1449
- const manifestRaw = form.get("manifest");
1450
- if (typeof manifestRaw !== "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, "appearance response missing JSON manifest part");
1451
- let parsedJson;
1452
- try {
1453
- parsedJson = JSON.parse(manifestRaw);
1454
- } catch (err) {
1455
- throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `appearance manifest is not valid JSON: ${err?.message ?? err}`);
1456
- }
1457
- const manifest = _embedpdf_engine_core_wire.AnnotationAppearanceManifestSchema.parse(parsedJson);
1458
- const appearances = await Promise.all(manifest.appearances.map(async (entry) => {
1459
- const partValue = form.get(entry.part);
1460
- if (partValue === null || typeof partValue === "string") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `appearance response missing image part "${entry.part}"`);
1461
- const blob = partValue;
1462
- const bytes = new Uint8Array(await blob.arrayBuffer());
1463
- const image = (0, _embedpdf_engine_core_runtime.createPageImageHandle)({
1464
- width: entry.width,
1465
- height: entry.height,
1466
- format: entry.format,
1467
- contentType: entry.contentType,
1468
- source: {
1469
- kind: "bytes",
1470
- bytes
1471
- }
1472
- }, { async blob() {
1473
- return blob;
1474
- } });
1475
- return {
1476
- ref: entry.ref,
1477
- mode: entry.mode,
1478
- rect: entry.rect,
1479
- image
1480
- };
1481
- }));
1482
- return {
1483
- pageState: manifest.pageState,
1484
- appearances
1485
- };
1486
- }
1487
- /**
1488
- * Local helper: project a non-index `AnnotationRef` into the matching
1489
- * `AnnotationStableId` shape so we can route by stable key. The compiler
1490
- * narrows on `ref.kind` here so we can't accidentally pass an index ref.
2359
+ * Cloud page form service: the page's widget images, one read of the
2360
+ * immutable `form/pages/{p}/appearances@` leaf at the page's `widgetVersion`,
2361
+ * at the base's URL while the layer inherits the `forms` plane.
1491
2362
  */
1492
- function refToStableId(ref) {
1493
- if (ref.kind === "objectNumber") return {
1494
- kind: "objectNumber",
1495
- value: ref.annotObjectNumber
1496
- };
1497
- return {
1498
- kind: "nm",
1499
- value: ref.nm
1500
- };
1501
- }
1502
- //#endregion
1503
- //#region src/document/CloudPageGeometryService.ts
1504
- var CloudPageGeometryService = class {
1505
- constructor(http, docId, layerName, pageObjectNumber, isClosed, manifest) {
2363
+ var CloudPageFormsService = class {
2364
+ constructor(http, docId, layerName, pageRef, isClosed, manifest) {
1506
2365
  this.http = http;
1507
2366
  this.docId = docId;
1508
2367
  this.layerName = layerName;
1509
- this.pageObjectNumber = pageObjectNumber;
2368
+ this.pageRef = pageRef;
1510
2369
  this.isClosed = isClosed;
1511
2370
  this.manifest = manifest;
1512
2371
  }
1513
- read() {
2372
+ renderAppearances(options = {}) {
1514
2373
  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`));
1515
2374
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
2375
+ (0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
2376
+ const format = options.format ?? "webp";
1516
2377
  const buildPath = async (s) => {
1517
2378
  const manifest = await this.manifest.get(s);
1518
- const page = manifest.pages.find((p) => p.state.pageObjectNumber === this.pageObjectNumber);
1519
- if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${this.pageObjectNumber} in document ${this.docId}`);
1520
- return planesInherited(manifest, ["content"]) ? _embedpdf_engine_core_wire.wirePaths.docPageGeometry(this.docId, this.pageObjectNumber, page.cache.contentVersion) : _embedpdf_engine_core_wire.wirePaths.layerPageGeometry(this.docId, this.layerName, this.pageObjectNumber, page.cache.contentVersion);
2379
+ const pageObjectNumber = this.pageRef.objectNumber;
2380
+ const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
2381
+ if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
2382
+ const token = (0, _embedpdf_engine_core_wire.widgetAppearancesImageOptionsToWire)({
2383
+ ...options,
2384
+ format
2385
+ }, { widgetVersion: page.cache.widgetVersion });
2386
+ return planesInherited(manifest, ["forms"]) ? _embedpdf_engine_core_wire.wirePaths.docPageFormAppearances(this.docId, this.pageRef, token) : _embedpdf_engine_core_wire.wirePaths.layerPageFormAppearances(this.docId, this.layerName, this.pageRef, token);
1521
2387
  };
1522
- return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.PageGeometrySnapshotSchema.parse(raw), async (s) => {
2388
+ return parseAppearanceForm(await this.http.getFormDataWithRefresh(buildPath, async (s) => {
1523
2389
  await this.manifest.refresh(s);
1524
- }, signal);
2390
+ }, signal));
1525
2391
  });
1526
2392
  }
1527
2393
  };
1528
2394
  //#endregion
1529
2395
  //#region src/document/CloudPageRenderService.ts
1530
2396
  var CloudPageRenderService = class {
1531
- constructor(http, docId, layerName, pageObjectNumber, isClosed, manifest) {
2397
+ constructor(http, docId, layerName, pageRef, isClosed, manifest, layout, layerRights) {
1532
2398
  this.http = http;
1533
2399
  this.docId = docId;
1534
2400
  this.layerName = layerName;
1535
- this.pageObjectNumber = pageObjectNumber;
2401
+ this.pageRef = pageRef;
1536
2402
  this.isClosed = isClosed;
1537
2403
  this.manifest = manifest;
2404
+ this.layout = layout;
2405
+ this.layerRights = layerRights;
1538
2406
  }
1539
2407
  image(options = {}) {
1540
2408
  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`));
1541
2409
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
2410
+ (0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
1542
2411
  const format = normalizeFormat(options.format);
1543
- const includeAnnotations = options.includeAnnotations ?? true;
2412
+ const family = _embedpdf_engine_core_wire.PAGE_RENDER_FAMILIES[(0, _embedpdf_engine_core_wire.pageRenderFamilyOf)((0, _embedpdf_engine_core_runtime.resolvePageLayers)(options, this.layerRights() ?? {
2413
+ annotations: true,
2414
+ formFields: true
2415
+ }))];
1544
2416
  const buildPath = async (s) => {
1545
2417
  const manifest = await this.manifest.get(s);
1546
- const page = manifest.pages.find((p) => p.state.pageObjectNumber === this.pageObjectNumber);
1547
- if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${this.pageObjectNumber} in document ${this.docId}`);
2418
+ const pageObjectNumber = this.pageRef.objectNumber;
2419
+ const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
2420
+ if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
2421
+ const pins = { contentVersion: page.cache.contentVersion };
2422
+ for (const pin of family.pins) pins[pin] = page.cache[pin];
1548
2423
  const wireToken = (0, _embedpdf_engine_core_wire.renderImageOptionsToWire)({
1549
2424
  ...options,
1550
2425
  format
1551
- }, {
1552
- contentVersion: page.cache.contentVersion,
1553
- ...includeAnnotations ? { annotationVersion: page.cache.annotationVersion } : {}
1554
- });
1555
- if (includeAnnotations) return planesInherited(manifest, ["content", "annotations"]) ? _embedpdf_engine_core_wire.wirePaths.docPageRenderAnnotated(this.docId, this.pageObjectNumber, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageRenderAnnotated(this.docId, this.layerName, this.pageObjectNumber, wireToken);
1556
- return planesInherited(manifest, ["content"]) ? _embedpdf_engine_core_wire.wirePaths.docPageRender(this.docId, this.pageObjectNumber, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageRender(this.docId, this.layerName, this.pageObjectNumber, wireToken);
2426
+ }, pins);
2427
+ return planesInherited(manifest, family.planes) ? _embedpdf_engine_core_wire.wirePaths.docPageRender(this.docId, family.family, this.pageRef, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageRender(this.docId, this.layerName, family.family, this.pageRef, wireToken);
2428
+ };
2429
+ const [requestPath, transform] = await Promise.all([buildPath(signal), this.transformOf(options, signal)]);
2430
+ return {
2431
+ ...createCloudPageImageHandle({
2432
+ width: transform.width,
2433
+ height: transform.height,
2434
+ format,
2435
+ contentType: `image/${format}`,
2436
+ source: {
2437
+ kind: "url",
2438
+ url: this.http.absoluteUrl(requestPath)
2439
+ }
2440
+ }, this.http, buildPath, async (s) => {
2441
+ await this.manifest.refresh(s);
2442
+ }),
2443
+ transform
1557
2444
  };
1558
- const requestPath = await buildPath(signal);
1559
- return createCloudPageImageHandle({
1560
- format,
1561
- contentType: `image/${format}`,
1562
- source: {
1563
- kind: "url",
1564
- url: this.http.absoluteUrl(requestPath)
1565
- }
1566
- }, this.http, buildPath, async (s) => {
1567
- await this.manifest.refresh(s);
1568
- });
1569
2445
  });
1570
2446
  }
1571
- raw(_options) {
1572
- return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "render.raw() is not available in the cloud engine; use render.image()"));
2447
+ /**
2448
+ * The image's transform, computed like the server's render (the same
2449
+ * `renderSize` and matrix), so the handle knows its size and its pixels
2450
+ * before any are fetched. Only a full page needs the page's layout.
2451
+ */
2452
+ async transformOf(options, signal) {
2453
+ const target = options.target;
2454
+ return (0, _embedpdf_engine_core_runtime.renderAreaTransform)(target?.kind === "rect" ? (0, _embedpdf_engine_core_runtime.renderTargetArea)(target.rect) : {
2455
+ x: 0,
2456
+ y: 0,
2457
+ ...(await this.layout(signal)).size
2458
+ }, options);
1573
2459
  }
1574
2460
  };
1575
2461
  function createCloudPageImageHandle(result, http, buildPath, onStaleVersion) {
@@ -1583,115 +2469,379 @@ function normalizeFormat(format) {
1583
2469
  //#endregion
1584
2470
  //#region src/document/CloudPageTextService.ts
1585
2471
  /**
1586
- * Cloud-side per-page text service. `read()` fetches the
1587
- * content-addressed URL `/v1/docs/:id/text/pages/:pon/data@contentVersion=N`, where
1588
- * `:P` is the page's current `contentVersion` from the cached
1589
- * manifest. On a 404 (stale version) the SDK transparently refreshes
1590
- * `/head` + `/manifest@docVersion=N`, rebuilds the URL with the fresh `cN`,
1591
- * and retries exactly once.
2472
+ * Cloud-side per-page text service. `get()` fetches the content-addressed
2473
+ * URL `/v1/docs/:id/text/pages/:pon/data@contentVersion=N` and `layout()`
2474
+ * its geometry twin, where `N` is the page's current `contentVersion` from
2475
+ * the cached manifest. On a 404 (stale version) the SDK transparently
2476
+ * refreshes `/head` + `/manifest@docVersion=N`, rebuilds the URL with the
2477
+ * fresh version, and retries exactly once.
1592
2478
  */
1593
2479
  var CloudPageTextService = class {
1594
- constructor(http, docId, layerName, pageObjectNumber, isClosed, manifest) {
2480
+ constructor(http, docId, layerName, pageRef, isClosed, manifest) {
2481
+ this.http = http;
2482
+ this.docId = docId;
2483
+ this.layerName = layerName;
2484
+ this.pageRef = pageRef;
2485
+ this.isClosed = isClosed;
2486
+ this.manifest = manifest;
2487
+ }
2488
+ get() {
2489
+ return this.readLeaf("text", (raw) => _embedpdf_engine_core_wire.PageTextSnapshotSchema.parse(raw));
2490
+ }
2491
+ slice(range) {
2492
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => (0, _embedpdf_engine_core_runtime.sliceText)(await this.get().abortWith(signal), range));
2493
+ }
2494
+ layout() {
2495
+ return this.readLeaf("geometry", (raw) => (0, _embedpdf_engine_core_runtime.createTextLayout)(_embedpdf_engine_core_wire.PageGeometrySnapshotSchema.parse(raw)));
2496
+ }
2497
+ /** One of the page's content leaves at the manifest's pin, with the stale-pin retry. */
2498
+ readLeaf(leaf, parse) {
2499
+ 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`));
2500
+ return _embedpdf_engine_core_runtime.AbortablePromise.run((signal) => this.http.getJsonWithRefresh(async (s) => this.leafPath(leaf, await this.manifest.get(s)), parse, async (s) => {
2501
+ await this.manifest.refresh(s);
2502
+ }, signal));
2503
+ }
2504
+ leafPath(leaf, manifest) {
2505
+ const pageObjectNumber = this.pageRef.objectNumber;
2506
+ const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
2507
+ if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
2508
+ const version = page.cache.contentVersion;
2509
+ if (planesInherited(manifest, ["content"])) return leaf === "text" ? _embedpdf_engine_core_wire.wirePaths.docPageText(this.docId, this.pageRef, version) : _embedpdf_engine_core_wire.wirePaths.docPageGeometry(this.docId, this.pageRef, version);
2510
+ return leaf === "text" ? _embedpdf_engine_core_wire.wirePaths.layerPageText(this.docId, this.layerName, this.pageRef, version) : _embedpdf_engine_core_wire.wirePaths.layerPageGeometry(this.docId, this.layerName, this.pageRef, version);
2511
+ }
2512
+ };
2513
+ //#endregion
2514
+ //#region src/document/CloudPageMeasureService.ts
2515
+ var CloudPageMeasureService = class {
2516
+ constructor(http, docId, layerName, pageRef, isClosed, manifest, publisher, writes) {
1595
2517
  this.http = http;
1596
2518
  this.docId = docId;
1597
2519
  this.layerName = layerName;
1598
- this.pageObjectNumber = pageObjectNumber;
2520
+ this.pageRef = pageRef;
1599
2521
  this.isClosed = isClosed;
1600
2522
  this.manifest = manifest;
2523
+ this.publisher = publisher;
2524
+ this.writes = writes;
1601
2525
  }
1602
- read() {
1603
- 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`));
2526
+ listViewports() {
1604
2527
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1605
- const buildPath = async (s) => {
1606
- const manifest = await this.manifest.get(s);
1607
- const page = manifest.pages.find((p) => p.state.pageObjectNumber === this.pageObjectNumber);
1608
- if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${this.pageObjectNumber} in document ${this.docId}`);
1609
- return planesInherited(manifest, ["content"]) ? _embedpdf_engine_core_wire.wirePaths.docPageText(this.docId, this.pageObjectNumber, page.cache.contentVersion) : _embedpdf_engine_core_wire.wirePaths.layerPageText(this.docId, this.layerName, this.pageObjectNumber, page.cache.contentVersion);
1610
- };
1611
- return this.http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.PageTextSnapshotSchema.parse(raw), async (s) => {
1612
- await this.manifest.refresh(s);
1613
- }, signal);
2528
+ this.check();
2529
+ return this.http.getJson(_embedpdf_engine_core_wire.wirePaths.layerPageViewports(this.docId, this.layerName, this.pageRef), (raw) => _embedpdf_engine_core_wire.PageMeasurementViewportListSchema.parse(raw), signal);
2530
+ });
2531
+ }
2532
+ setScale(measure, options) {
2533
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
2534
+ const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
2535
+ this.check();
2536
+ return this.writes.run(opId, signal, async (write) => {
2537
+ const result = await write.send((sent) => this.http.putJson(_embedpdf_engine_core_wire.wirePaths.layerPageScale(this.docId, this.layerName, this.pageRef), { measure }, (raw) => _embedpdf_engine_core_wire.PageScaleResultSchema.parse(raw), signal, sent));
2538
+ this.manifest.apply(result.meta, []);
2539
+ this.publisher.publishWrite(opId, {
2540
+ type: "pages.scaleSet",
2541
+ ...result
2542
+ });
2543
+ return result;
2544
+ });
1614
2545
  });
1615
2546
  }
2547
+ check() {
2548
+ if (this.isClosed()) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `Document not open: ${this.docId}`);
2549
+ }
1616
2550
  };
1617
2551
  //#endregion
1618
2552
  //#region src/document/CloudPageHandle.ts
2553
+ /**
2554
+ * Cloud page handle, keyed on `ref` — the page's durable address (its
2555
+ * object number). Every service below addresses the wire by it
2556
+ * (`/pages/obj:N/…`), keys the manifest by its number, and publishes
2557
+ * that number in events.
2558
+ */
1619
2559
  var CloudPageHandle = class {
1620
- constructor(pageObjectNumber, pageIndex, http, docId, layerName, isClosed, manifest, publisher) {
1621
- this.pageObjectNumber = pageObjectNumber;
1622
- this.pageIndex = pageIndex;
2560
+ constructor(ref, http, docId, layerName, isClosed, manifest, publisher, writes, layout, layerRights) {
2561
+ this.ref = ref;
1623
2562
  _defineProperty(this, "annotations", void 0);
2563
+ _defineProperty(this, "forms", void 0);
1624
2564
  _defineProperty(this, "text", void 0);
1625
- _defineProperty(this, "geometry", void 0);
1626
2565
  _defineProperty(this, "render", void 0);
1627
- this.annotations = new CloudPageAnnotationsService(http, docId, layerName, pageObjectNumber, isClosed, manifest, publisher);
1628
- this.text = new CloudPageTextService(http, docId, layerName, pageObjectNumber, isClosed, manifest);
1629
- this.geometry = new CloudPageGeometryService(http, docId, layerName, pageObjectNumber, isClosed, manifest);
1630
- this.render = new CloudPageRenderService(http, docId, layerName, pageObjectNumber, isClosed, manifest);
2566
+ _defineProperty(this, "measure", void 0);
2567
+ this.measure = new CloudPageMeasureService(http, docId, layerName, ref, isClosed, manifest, publisher, writes);
2568
+ this.annotations = new CloudPageAnnotationsService(http, docId, layerName, ref, isClosed, manifest, publisher, writes);
2569
+ this.forms = new CloudPageFormsService(http, docId, layerName, ref, isClosed, manifest);
2570
+ this.text = new CloudPageTextService(http, docId, layerName, ref, isClosed, manifest);
2571
+ this.render = new CloudPageRenderService(http, docId, layerName, ref, isClosed, manifest, layout, layerRights);
2572
+ }
2573
+ };
2574
+ //#endregion
2575
+ //#region src/document/CloudWrites.ts
2576
+ /**
2577
+ * A document's writes, sent one at a time in the order they were called,
2578
+ * so the server applies them in that order: a create, then an update of
2579
+ * what it made, never the other way round. Reads don't wait.
2580
+ *
2581
+ * A write takes its place when it is called: `run` must be the first thing
2582
+ * a write does, before anything it awaits. What it does before `send`
2583
+ * (reading a file to upload, say) happens meanwhile; its turn ends when it
2584
+ * settles, after the event it publishes.
2585
+ *
2586
+ * Changes (`doc.apply`) called one after another share a place: while the
2587
+ * write before them is in flight they wait together, and go out as one
2588
+ * request when their turn comes, each answered on its own.
2589
+ */
2590
+ var CloudWrites = class {
2591
+ constructor(pool, sendChanges = () => {
2592
+ throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "this line sends no changes");
2593
+ }) {
2594
+ this.pool = pool;
2595
+ this.sendChanges = sendChanges;
2596
+ _defineProperty(this, "last", Promise.resolve());
2597
+ _defineProperty(this, "open", null);
2598
+ }
2599
+ run(opId, signal, body, named = []) {
2600
+ this.open = null;
2601
+ const before = this.last;
2602
+ const numbers = named.filter((n) => n !== void 0);
2603
+ this.pool.beginWrite(numbers);
2604
+ const settled = body({ send: async (request) => {
2605
+ await untilTurn(before, signal);
2606
+ const reserve = this.pool.topUp();
2607
+ return request({ write: {
2608
+ opId,
2609
+ ...reserve > 0 ? { reserveObjectNumbers: reserve } : {},
2610
+ onObjectNumbers: (ranges) => this.pool.receive(ranges)
2611
+ } });
2612
+ } }).then((result) => {
2613
+ this.pool.endWrite(numbers, "committed");
2614
+ return result;
2615
+ }, (error) => {
2616
+ this.pool.endWrite(numbers, outcomeOf(error));
2617
+ throw error;
2618
+ });
2619
+ this.last = before.then(() => settled.then(noop, noop));
2620
+ return settled;
2621
+ }
2622
+ /**
2623
+ * Take a change's place in the line: it joins the changes waiting for
2624
+ * their turn, and goes out with them. `ready` is what the request sends
2625
+ * for it, which it may still be preparing (reading bytes). A change aborted
2626
+ * before its request goes out is left out of it; once out, it is answered.
2627
+ */
2628
+ change(signal, ready, { named = [], alone = false } = {}) {
2629
+ this.pool.beginWrite(named);
2630
+ ready.catch(noop);
2631
+ return new Promise((resolve, reject) => {
2632
+ const lined = {
2633
+ ready,
2634
+ signal,
2635
+ numbers: named,
2636
+ resolve,
2637
+ reject
2638
+ };
2639
+ (!alone && this.open !== null && this.open.changes.length < _embedpdf_engine_core_wire.CHANGE_REQUEST_LIMITS.changes ? this.open : this.queue()).changes.push(lined);
2640
+ if (alone) this.open = null;
2641
+ }).then((settled) => {
2642
+ this.pool.endWrite(named, settledOutcomeOf(settled));
2643
+ return settled;
2644
+ }, (error) => {
2645
+ this.pool.endWrite(named, outcomeOf(error));
2646
+ throw error;
2647
+ });
2648
+ }
2649
+ /** A new batch at the end of the line, open for the changes that follow. */
2650
+ queue() {
2651
+ const before = this.last;
2652
+ const batch = { changes: [] };
2653
+ this.open = batch;
2654
+ const sent = before.then(() => this.send(batch));
2655
+ this.last = sent.then(noop, noop);
2656
+ return batch;
2657
+ }
2658
+ /** A batch's turn: nothing joins it any more, and what's ready goes out. */
2659
+ async send(batch) {
2660
+ if (this.open === batch) this.open = null;
2661
+ const prepared = await Promise.allSettled(batch.changes.map((lined) => lined.ready));
2662
+ const going = [];
2663
+ batch.changes.forEach((lined, index) => {
2664
+ const ready = prepared[index];
2665
+ if (lined.signal.aborted) lined.reject(new _embedpdf_engine_core_runtime.AbortError(lined.signal.reason ?? "aborted"));
2666
+ else if (ready.status === "rejected") lined.reject(ready.reason);
2667
+ else going.push({
2668
+ lined,
2669
+ sent: ready.value
2670
+ });
2671
+ });
2672
+ if (going.length === 0) return;
2673
+ const reserve = this.pool.topUp();
2674
+ try {
2675
+ const answers = await this.sendChanges(going.map(({ sent }) => sent), { write: {
2676
+ ...reserve > 0 ? { reserveObjectNumbers: reserve } : {},
2677
+ onObjectNumbers: (ranges) => this.pool.receive(ranges)
2678
+ } });
2679
+ if (answers.length !== going.length) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `${going.length} changes sent, ${answers.length} answered`);
2680
+ going.forEach(({ lined }, index) => lined.resolve(answers[index]));
2681
+ } catch (error) {
2682
+ for (const { lined } of going) lined.reject(error);
2683
+ }
1631
2684
  }
1632
2685
  };
2686
+ function noop() {}
2687
+ /** What a write that failed with `error` did with the numbers it named. */
2688
+ function outcomeOf(error) {
2689
+ return _embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.ObjectNumberUnavailable) ? "refused" : "failed";
2690
+ }
2691
+ /** What an answered change did with the numbers it named, as a single write's outcome. */
2692
+ function settledOutcomeOf(answer) {
2693
+ if (answer.status === "applied") return "committed";
2694
+ return answer.error.code === _embedpdf_engine_core_runtime.EngineErrorCode.ObjectNumberUnavailable ? "refused" : "failed";
2695
+ }
2696
+ /** `before`, or an `AbortError` once `signal` aborts. */
2697
+ function untilTurn(before, signal) {
2698
+ if (signal.aborted) return Promise.reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason ?? "aborted"));
2699
+ return new Promise((resolve, reject) => {
2700
+ const onAbort = () => reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason ?? "aborted"));
2701
+ signal.addEventListener("abort", onAbort, { once: true });
2702
+ before.then(() => {
2703
+ signal.removeEventListener("abort", onAbort);
2704
+ resolve();
2705
+ });
2706
+ });
2707
+ }
1633
2708
  //#endregion
1634
- //#region src/realtime/auditRowToEvent.ts
2709
+ //#region src/realtime/auditRowToEvents.ts
1635
2710
  /**
1636
- * Translate a remote audit row into a `DocumentEvent` — pure, so the
1637
- * exactly-once and verbatim-payload invariants are unit-testable without a
1638
- * server. Returns `null` for kinds this engine version doesn't know
1639
- * (a NEWER server's events degrade to "ignored", never to a crash).
2711
+ * Translate a remote audit row into the `DocumentEvent`s it records — pure,
2712
+ * so the exactly-once and verbatim-payload invariants are unit-testable
2713
+ * without a server. Most rows are one fact; a change is the events of its
2714
+ * items, a form reset is one `forms.valueSet` per field it changed, an
2715
+ * import is one `annotations.created` per annotation, and a completed
2716
+ * signing is the signature and the new version. An undo's events name the
2717
+ * change it undid (`origin.undoOf`). Every event of a row shares `origin.tx`,
2718
+ * its id the request's `Idempotency-Key`. Returns none for an own echo and for kinds
2719
+ * this engine version doesn't know (a newer server's events degrade to
2720
+ * "ignored", never to a crash).
1640
2721
  *
1641
2722
  * Context-field fidelity differs by op, by design of the audit row:
1642
2723
  * - rotate/delete: `affectedPages` is exactly the op's page set; rotation
1643
2724
  * is recovered from the layout (it's absolute — every affected page
1644
2725
  * carries the value).
1645
- * - move: the originator knows which block it moved; the audit row only
1646
- * records the resulting order, so `pageObjectNumbers` is the full new
1647
- * order and `destIndex` is absent (remote consumers use `layout`).
2726
+ * - reorder: the payload is the result, as the originator got it: the
2727
+ * pages that moved and the new `layout`.
2728
+ *
2729
+ * The audit row keys pages by object number (its storage identity); the
2730
+ * event carries them as `PageRef` addresses, like every other event.
1648
2731
  */
1649
- function auditRowToEvent(row, mySessionId) {
1650
- if (row.originSessionId === mySessionId) return null;
2732
+ function auditRowToEvents(row, mySessionId) {
2733
+ if (row.originSessionId === mySessionId) return [];
2734
+ if (row.withheld) return [];
1651
2735
  const origin = {
1652
2736
  kind: "remote",
1653
2737
  sessionId: row.originSessionId ?? `unknown:${row.sub}`,
1654
2738
  sub: row.sub,
1655
2739
  ts: row.ts,
1656
- serverId: row.id
2740
+ serverId: row.id,
2741
+ ...row.undoOf ? { undoOf: row.undoOf } : {}
1657
2742
  };
2743
+ const id = row.idempotencyKey ?? `audit:${row.id}`;
2744
+ const events = factsOf(row, origin);
2745
+ return events.map((event, index) => ({
2746
+ ...event,
2747
+ origin: {
2748
+ ...origin,
2749
+ tx: {
2750
+ id,
2751
+ index,
2752
+ count: events.length
2753
+ }
2754
+ }
2755
+ }));
2756
+ }
2757
+ /** The events a row records, in order, or none for a kind this version doesn't know. */
2758
+ function factsOf(row, origin) {
2759
+ if (row.kind === "change") return (0, _embedpdf_engine_core_runtime.changeEvents)(row.payload).map((event) => ({
2760
+ ...event,
2761
+ origin
2762
+ }));
2763
+ if (row.kind === "annot.import") return (0, _embedpdf_engine_core_runtime.annotationImportFacts)(row.payload).map((fact) => ({
2764
+ type: "annotations.created",
2765
+ origin,
2766
+ ...fact
2767
+ }));
2768
+ if (row.kind === "form.import") return (0, _embedpdf_engine_core_runtime.formImportFacts)(row.payload).map((fact) => ({
2769
+ type: "forms.created",
2770
+ origin,
2771
+ ...fact
2772
+ }));
2773
+ if (row.kind === "form.importValues") return (0, _embedpdf_engine_core_runtime.formValuesImportFacts)(row.payload).map((fact) => ({
2774
+ type: "forms.valueSet",
2775
+ origin,
2776
+ ...fact
2777
+ }));
2778
+ if (row.kind === "form.reset") return (0, _embedpdf_engine_core_runtime.formResetFacts)(row.payload).map((fact) => ({
2779
+ type: "forms.valueSet",
2780
+ origin,
2781
+ ...fact
2782
+ }));
2783
+ if (row.kind === "signature.complete") {
2784
+ const { signingId, ...result } = row.payload;
2785
+ return [{
2786
+ type: "signatures.completed",
2787
+ origin,
2788
+ signingId,
2789
+ ...result
2790
+ }, {
2791
+ type: "document.versioned",
2792
+ origin,
2793
+ version: result.version
2794
+ }];
2795
+ }
2796
+ const event = eventOf(row, origin);
2797
+ return event ? [event] : [];
2798
+ }
2799
+ /** The one event a single-fact row records, or `null` for a kind this version doesn't know. */
2800
+ function eventOf(row, origin) {
2801
+ const rowPage = () => (0, _embedpdf_engine_core_runtime.toPageRef)(row.pageObjectNumber ?? row.affectedPages[0] ?? 0);
2802
+ const affectedPages = () => row.affectedPages.map((pageObjectNumber) => (0, _embedpdf_engine_core_runtime.toPageRef)(pageObjectNumber));
1658
2803
  switch (row.kind) {
2804
+ case "measure.setScale": return {
2805
+ type: "pages.scaleSet",
2806
+ origin,
2807
+ ...row.payload
2808
+ };
1659
2809
  case "annot.create": return {
1660
- type: "annotation.created",
1661
- pageObjectNumber: row.pageObjectNumber ?? row.affectedPages[0] ?? 0,
2810
+ type: "annotations.created",
2811
+ page: rowPage(),
1662
2812
  origin,
1663
2813
  ...row.payload
1664
2814
  };
1665
2815
  case "annot.update": return {
1666
- type: "annotation.updated",
1667
- pageObjectNumber: row.pageObjectNumber ?? row.affectedPages[0] ?? 0,
2816
+ type: "annotations.updated",
2817
+ page: rowPage(),
1668
2818
  origin,
1669
2819
  ...row.payload
1670
2820
  };
1671
2821
  case "annot.delete": return {
1672
- type: "annotation.deleted",
1673
- pageObjectNumber: row.pageObjectNumber ?? row.affectedPages[0] ?? 0,
2822
+ type: "annotations.deleted",
2823
+ page: rowPage(),
1674
2824
  origin,
2825
+ deleted: (0, _embedpdf_engine_core_runtime.deletedAnnotationsOf)(row.payload),
1675
2826
  ...row.payload
1676
2827
  };
1677
- case "annot.move": return {
1678
- type: "annotation.moved",
1679
- pageObjectNumber: row.pageObjectNumber ?? row.affectedPages[0] ?? 0,
2828
+ case "annot.reorder": return {
2829
+ type: "annotations.reordered",
2830
+ page: rowPage(),
1680
2831
  origin,
1681
2832
  ...row.payload
1682
2833
  };
1683
- case "pages.move": return {
1684
- type: "pages.moved",
1685
- pageObjectNumbers: row.affectedPages,
2834
+ case "pages.reorder": return {
2835
+ type: "pages.reordered",
1686
2836
  origin,
1687
2837
  ...row.payload
1688
2838
  };
1689
2839
  case "pages.rotate": {
1690
2840
  const payload = row.payload;
1691
- const rotation = payload.layout.pages.find((page) => page.pageObjectNumber === row.affectedPages[0])?.rotation ?? 0;
2841
+ const rotation = payload.layout.pages.find((page) => page.ref.objectNumber === row.affectedPages[0])?.rotation ?? 0;
1692
2842
  return {
1693
2843
  type: "pages.rotated",
1694
- pageObjectNumbers: row.affectedPages,
2844
+ pages: affectedPages(),
1695
2845
  rotation,
1696
2846
  origin,
1697
2847
  ...payload
@@ -1699,7 +2849,7 @@ function auditRowToEvent(row, mySessionId) {
1699
2849
  }
1700
2850
  case "pages.delete": return {
1701
2851
  type: "pages.deleted",
1702
- pageObjectNumbers: row.affectedPages,
2852
+ pages: affectedPages(),
1703
2853
  origin,
1704
2854
  ...row.payload
1705
2855
  };
@@ -1719,63 +2869,96 @@ function auditRowToEvent(row, mySessionId) {
1719
2869
  origin,
1720
2870
  ...row.payload
1721
2871
  };
1722
- case "attachment.create": return {
1723
- type: "attachment.created",
2872
+ case "metadata.updateCustom": return {
2873
+ type: "metadata.customUpdated",
1724
2874
  origin,
1725
2875
  ...row.payload
1726
2876
  };
1727
- case "attachment.delete": return {
1728
- type: "attachment.deleted",
2877
+ case "attachment.create": return {
2878
+ type: "attachments.created",
1729
2879
  origin,
1730
2880
  ...row.payload
1731
2881
  };
1732
- case "form.setValue":
1733
- case "form.reset": return {
1734
- type: "form.valueChanged",
2882
+ case "attachment.delete": return {
2883
+ type: "attachments.deleted",
1735
2884
  origin,
2885
+ deleted: (0, _embedpdf_engine_core_runtime.deletedAttachmentOf)(row.payload),
1736
2886
  ...row.payload
1737
2887
  };
1738
- case "form.import": return {
1739
- type: "form.imported",
2888
+ case "form.setValue": return {
2889
+ type: "forms.valueSet",
1740
2890
  origin,
1741
2891
  ...row.payload
1742
2892
  };
1743
2893
  case "form.repair": return {
1744
- type: "form.repaired",
2894
+ type: "forms.repaired",
1745
2895
  origin,
1746
2896
  ...row.payload
1747
2897
  };
1748
2898
  case "form.createField": return {
1749
- type: "form.fieldCreated",
2899
+ type: "forms.created",
1750
2900
  origin,
1751
2901
  ...row.payload
1752
2902
  };
1753
2903
  case "form.updateField":
1754
2904
  case "form.setSignatureAppearance": return {
1755
- type: "form.fieldUpdated",
2905
+ type: "forms.updated",
1756
2906
  origin,
1757
2907
  ...row.payload
1758
2908
  };
1759
- case "form.deleteField": return {
1760
- type: "form.fieldDeleted",
2909
+ case "form.deleteField": {
2910
+ const result = row.payload;
2911
+ return {
2912
+ type: "forms.deleted",
2913
+ origin,
2914
+ deleted: (0, _embedpdf_engine_core_runtime.deletedFieldOf)(result),
2915
+ ...result
2916
+ };
2917
+ }
2918
+ case "form.addWidget": return {
2919
+ type: "forms.widgetAdded",
1761
2920
  origin,
1762
2921
  ...row.payload
1763
2922
  };
1764
- case "form.attachWidget": return {
1765
- type: "form.widgetAttached",
2923
+ case "form.detachWidget": return {
2924
+ type: "forms.widgetRemoved",
1766
2925
  origin,
1767
2926
  ...row.payload
1768
2927
  };
1769
- case "form.detachWidget": return {
1770
- type: "form.widgetDetached",
2928
+ case "form.updateWidget": return {
2929
+ type: "forms.widgetUpdated",
2930
+ origin,
2931
+ ...row.payload
2932
+ };
2933
+ case "form.deleteWidget": return {
2934
+ type: "forms.widgetDeleted",
2935
+ origin,
2936
+ ...row.payload
2937
+ };
2938
+ case "form.reorderWidgets": return {
2939
+ type: "forms.widgetsReordered",
1771
2940
  origin,
1772
2941
  ...row.payload
1773
2942
  };
1774
- case "form.applyEffects": return {
1775
- type: "form.effectsApplied",
2943
+ case "form.reorderCalculations": return {
2944
+ type: "forms.calculationsReordered",
1776
2945
  origin,
1777
2946
  ...row.payload
1778
2947
  };
2948
+ case "signature.prepare": {
2949
+ const { field, ...wire } = row.payload;
2950
+ return {
2951
+ type: "signatures.prepared",
2952
+ origin,
2953
+ field,
2954
+ ...(0, _embedpdf_engine_core_wire.decodePrepared)(wire)
2955
+ };
2956
+ }
2957
+ case "signature.cancel": return {
2958
+ type: "signatures.cancelled",
2959
+ origin,
2960
+ signingId: row.payload.signingId
2961
+ };
1779
2962
  default: return null;
1780
2963
  }
1781
2964
  }
@@ -1831,7 +3014,12 @@ var SseClient = class {
1831
3014
  return;
1832
3015
  }
1833
3016
  if (!res.ok || !res.body) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Network, `events stream HTTP ${res.status}`);
1834
- await this.consume(res.body);
3017
+ this.opts.onConnected?.(true);
3018
+ try {
3019
+ await this.consume(res.body);
3020
+ } finally {
3021
+ this.opts.onConnected?.(false);
3022
+ }
1835
3023
  if (Date.now() - startedAt >= 3e4) this.retryDelayMs = 500;
1836
3024
  } catch {}
1837
3025
  if (this.closed) return;
@@ -1868,7 +3056,11 @@ var SseClient = class {
1868
3056
  if (event === "mutation") try {
1869
3057
  this.opts.onRow(JSON.parse(data.join("\n")));
1870
3058
  } catch {}
1871
- else if (event === "full-refresh") this.opts.onFullRefresh();
3059
+ else if (event === "session") {
3060
+ const parsed = safeJson(data.join("\n"));
3061
+ const status = _embedpdf_engine_core_wire.EditSessionStatusSchema.safeParse(parsed);
3062
+ if (status.success) this.opts.onSession?.(status.data);
3063
+ } else if (event === "full-refresh") this.opts.onFullRefresh();
1872
3064
  else if (event === "auth-revoked") {
1873
3065
  this.closed = true;
1874
3066
  this.abort?.abort();
@@ -1876,17 +3068,21 @@ var SseClient = class {
1876
3068
  }
1877
3069
  }
1878
3070
  };
3071
+ function safeJson(text) {
3072
+ try {
3073
+ return JSON.parse(text);
3074
+ } catch {
3075
+ return null;
3076
+ }
3077
+ }
1879
3078
  //#endregion
1880
3079
  //#region src/document/CloudDocumentHandle.ts
1881
3080
  var CloudDocumentHandle = class {
1882
- constructor(http, id, layerName = _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME, initialHead, initialToken = null, sessionId = `cloud:anon:${id}`) {
3081
+ constructor(http, id, layerName = _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME, initialHead, initialToken = null, onClose = () => {}) {
1883
3082
  this.http = http;
1884
3083
  this.layerName = layerName;
3084
+ this.onClose = onClose;
1885
3085
  _defineProperty(this, "id", void 0);
1886
- _defineProperty(this, "capabilities", {
1887
- weakAnnotationEditSessions: "required",
1888
- pageEditSessions: "unsupported"
1889
- });
1890
3086
  _defineProperty(this, "metadata", void 0);
1891
3087
  _defineProperty(this, "annotations", void 0);
1892
3088
  _defineProperty(this, "actions", void 0);
@@ -1899,6 +3095,10 @@ var CloudDocumentHandle = class {
1899
3095
  _defineProperty(this, "security", void 0);
1900
3096
  _defineProperty(this, "render", void 0);
1901
3097
  _defineProperty(this, "events", void 0);
3098
+ _defineProperty(this, "objectNumbers", void 0);
3099
+ _defineProperty(this, "pool", void 0);
3100
+ _defineProperty(this, "writes", void 0);
3101
+ _defineProperty(this, "changes", void 0);
1902
3102
  _defineProperty(this, "publisher", void 0);
1903
3103
  _defineProperty(this, "hub", void 0);
1904
3104
  _defineProperty(this, "sessionId", void 0);
@@ -1910,11 +3110,20 @@ var CloudDocumentHandle = class {
1910
3110
  _defineProperty(this, "manifestFloorVersion", 0);
1911
3111
  _defineProperty(this, "pendingInitialHead", void 0);
1912
3112
  _defineProperty(this, "manifestAccessor", void 0);
3113
+ _defineProperty(this, "cloudSecurity", void 0);
1913
3114
  this.id = id;
1914
3115
  this.pendingInitialHead = initialHead ?? null;
1915
- const security = new CloudDocumentSecurityService(http, id, layerName, initialHead ?? fallbackUnknownHead(id), { isClosed: () => this.closed }, initialToken);
3116
+ const pool = new CloudObjectNumberPool({ reserve: (count, signal) => http.postJson(_embedpdf_engine_core_wire.wirePaths.objectNumbers(id, layerName), { count }, (raw) => _embedpdf_engine_core_wire.ObjectNumberReservationResponseSchema.parse(raw), signal, { session: true }) });
3117
+ this.pool = pool;
3118
+ this.objectNumbers = pool;
3119
+ this.writes = new CloudWrites(pool, changeSender(http, id, layerName));
3120
+ const security = new CloudDocumentSecurityService(http, id, layerName, initialHead ?? fallbackUnknownHead(id), { isClosed: () => this.closed }, initialToken, {
3121
+ wanted: () => pool.wantedAtAccess(),
3122
+ opened: (edit) => pool.opened(edit)
3123
+ });
1916
3124
  this.security = security;
1917
- this.render = { policy: async () => {
3125
+ this.cloudSecurity = security;
3126
+ this.render = { getPolicy: () => _embedpdf_engine_core_runtime.AbortablePromise.run(async () => {
1918
3127
  const advertised = (security.currentAccess ?? (await security.establishAccess()).access ?? null)?.renderPolicy;
1919
3128
  if (!advertised) return _embedpdf_engine_core_runtime.CONTINUOUS_RENDER_POLICY;
1920
3129
  return {
@@ -1927,52 +3136,71 @@ var CloudDocumentHandle = class {
1927
3136
  background: advertised.background,
1928
3137
  enforced: advertised.enforced
1929
3138
  };
1930
- } };
1931
- const hub = new _embedpdf_engine_services.EventHub();
3139
+ }) };
3140
+ const hub = new _embedpdf_engine_core_runtime.EventHub();
1932
3141
  this.hub = hub;
3142
+ const sessionId = http.sessionId ?? `cloud:anon:${id}`;
1933
3143
  this.sessionId = sessionId;
3144
+ const subscribe = (listener) => {
3145
+ const unsubscribe = hub.subscribe(listener);
3146
+ this.retainRemoteStream();
3147
+ let released = false;
3148
+ return () => {
3149
+ unsubscribe();
3150
+ if (!released) {
3151
+ released = true;
3152
+ this.releaseRemoteStream();
3153
+ }
3154
+ };
3155
+ };
1934
3156
  this.events = {
1935
- subscribe: (listener) => {
1936
- const unsubscribe = hub.subscribe(listener);
1937
- this.retainRemoteStream();
1938
- let released = false;
1939
- return () => {
1940
- unsubscribe();
1941
- if (!released) {
1942
- released = true;
1943
- this.releaseRemoteStream();
1944
- }
1945
- };
1946
- },
3157
+ subscribe,
3158
+ on: (type, listener) => (0, _embedpdf_engine_core_runtime.subscribeToType)(subscribe, type, listener),
1947
3159
  lastServerId: () => hub.lastServerId()
1948
3160
  };
1949
- this.publisher = new _embedpdf_engine_services.SessionEventPublisher(hub, sessionId);
3161
+ this.publisher = new _embedpdf_engine_core_runtime.SessionEventPublisher(hub, sessionId);
3162
+ hub.subscribe((event) => {
3163
+ if (event.type === "document.versioned") pool.versioned(event.version.sha256);
3164
+ });
1950
3165
  this.manifestAccessor = {
1951
3166
  get: (signal) => this.getManifest(signal),
1952
3167
  refresh: (signal) => this.refreshManifest(signal),
1953
3168
  apply: (meta, owns) => this.absorbMutation(meta, owns),
1954
- applyPageStructure: (cache) => this.absorbPageStructure(cache),
1955
- applyPageDelete: (cache, deletedPages) => this.absorbPageDelete(cache, deletedPages),
1956
- applyPageInsert: (cache) => this.absorbPageInsert(cache),
1957
- applyMetadata: (cache) => this.absorbMetadata(cache),
1958
- applyAttachments: (cache) => this.absorbAttachments(cache)
3169
+ applyPageDelete: (meta, deletedPages) => this.absorbPageDelete(meta, deletedPages),
3170
+ applyPageInsert: (meta) => this.absorbPageInsert(meta)
1959
3171
  };
1960
- this.metadata = new CloudMetadataService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
1961
- this.annotations = new CloudDocumentAnnotationsService(http, id, layerName, () => this.closed, this.manifestAccessor);
3172
+ this.changes = new CloudDocumentChanges(() => this.closed, this.manifestAccessor, this.publisher, this.writes);
3173
+ this.metadata = new CloudMetadataService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3174
+ const importLimits = async () => (security.currentAccess ?? (await security.establishAccess()).access)?.bundleLimits ?? _embedpdf_engine_core_runtime.DEFAULT_BUNDLE_LIMITS;
3175
+ this.annotations = new CloudDocumentAnnotationsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, importLimits);
1962
3176
  this.actions = new CloudDocumentActionsService(http, id, layerName, () => this.closed, this.manifestAccessor);
1963
- this.attachments = new CloudDocumentAttachmentsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
1964
- this.signatures = new CloudDocumentSignaturesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
1965
- this.forms = new CloudDocumentFormsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
3177
+ this.attachments = new CloudDocumentAttachmentsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3178
+ this.signatures = new CloudDocumentSignaturesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3179
+ this.forms = new CloudDocumentFormsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, importLimits);
1966
3180
  this.search = new CloudDocumentSearchService(http, id, layerName, () => this.closed, this.manifestAccessor);
1967
- this.pages = new CloudDocumentPagesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
1968
- this.redaction = new CloudDocumentRedactionService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher);
3181
+ this.pages = new CloudDocumentPagesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3182
+ this.redaction = new CloudDocumentRedactionService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3183
+ }
3184
+ page(ref) {
3185
+ return new CloudPageHandle(ref, this.http, this.id, this.layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, (signal) => this.pages.pageLayout(ref, signal), () => this.cloudSecurity.pageLayerRights());
1969
3186
  }
1970
- page(pageObjectNumber) {
1971
- return new CloudPageHandle(pageObjectNumber, -1, this.http, this.id, this.layerName, () => this.closed, this.manifestAccessor, this.publisher);
3187
+ /**
3188
+ * This handle: the cloud engine sends every call as it's made, in
3189
+ * parallel, and the server runs them in arrival order, so a call's facts
3190
+ * change nothing here.
3191
+ */
3192
+ with(_facts) {
3193
+ return this;
1972
3194
  }
1973
- download(opts = {}) {
3195
+ /** One change as one transaction, or the undo of one (see `DocumentHandle.apply`). */
3196
+ apply(change, options) {
3197
+ return this.changes.apply(change, options);
3198
+ }
3199
+ /** Ignored: the server learns what's needed from what is asked of it. */
3200
+ setWorkingSet(_view, _pages) {}
3201
+ download(options = {}) {
1974
3202
  if (this.closed) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.id} is closed`));
1975
- const mode = opts.mode ?? _embedpdf_engine_core_runtime.DEFAULT_PDF_SAVE_MODE;
3203
+ const mode = options.mode ?? _embedpdf_engine_core_runtime.DEFAULT_PDF_SAVE_MODE;
1976
3204
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1977
3205
  const buildPath = async () => {
1978
3206
  const manifest = await this.getManifest(signal);
@@ -1991,6 +3219,20 @@ var CloudDocumentHandle = class {
1991
3219
  });
1992
3220
  }
1993
3221
  /**
3222
+ * Learn what the document's signatures forbid before the caller's first
3223
+ * question: the manifest carries it, so `open()` fetches it up front and
3224
+ * `security.allows()` answers as the server would from the start. A locked
3225
+ * document, which the server won't describe yet, learns it from `unlock()`.
3226
+ */
3227
+ async learnProtection(signal) {
3228
+ try {
3229
+ await this.getManifest(signal);
3230
+ } catch (error) {
3231
+ if (_embedpdf_engine_core_runtime.EngineError.is(error, _embedpdf_engine_core_runtime.EngineErrorCode.DocPasswordRequired)) return;
3232
+ throw error;
3233
+ }
3234
+ }
3235
+ /**
1994
3236
  * Return the cached manifest, fetching cold-cache once if needed.
1995
3237
  * Concurrent callers share a single inflight request (singleflight)
1996
3238
  * so an N-page handle that opens N services in parallel still
@@ -2012,9 +3254,9 @@ var CloudDocumentHandle = class {
2012
3254
  return awaitSignal(this.startManifestFetch({ allowInitialHead: false }), signal);
2013
3255
  }
2014
3256
  /**
2015
- * Monotone plane-scope flip: mark the planes a mutation OWNS as layer-scoped in
3257
+ * Monotone plane-scope flip: mark the planes a mutation owns as layer-scoped in
2016
3258
  * the cached manifest. Scopes only ever move base → layer (no revert op
2017
- * exists), so flipping is safe under ANY event ordering — a duplicate or
3259
+ * exists), so flipping is safe under any event ordering — a duplicate or
2018
3260
  * out-of-order event still proves the plane diverged at some point — and
2019
3261
  * the manifest fetch stays the authoritative source (the 404 → refresh
2020
3262
  * rail heals any miss, e.g. a future mutation kind this client doesn't
@@ -2045,17 +3287,10 @@ var CloudDocumentHandle = class {
2045
3287
  return;
2046
3288
  }
2047
3289
  }
2048
- const byPageObjectNumber = new Map(this.manifestCache.pages.map((page) => [page.state.pageObjectNumber, page]));
2049
- for (const pageState of meta.affectedPages) {
2050
- const existing = byPageObjectNumber.get(pageState.pageObjectNumber);
2051
- if (existing) byPageObjectNumber.set(pageState.pageObjectNumber, {
2052
- ...existing,
2053
- state: pageState
2054
- });
2055
- }
3290
+ const byPageObjectNumber = new Map(this.manifestCache.pages.map((page) => [page.page.objectNumber, page]));
2056
3291
  if (delta) for (const page of delta.pages) {
2057
- const existing = byPageObjectNumber.get(page.pageObjectNumber);
2058
- if (existing) byPageObjectNumber.set(page.pageObjectNumber, {
3292
+ const existing = byPageObjectNumber.get(page.page.objectNumber);
3293
+ if (existing) byPageObjectNumber.set(page.page.objectNumber, {
2059
3294
  ...existing,
2060
3295
  cache: page.cache
2061
3296
  });
@@ -2064,140 +3299,68 @@ var CloudDocumentHandle = class {
2064
3299
  ...this.manifestCache,
2065
3300
  docVersion: delta?.docVersion ?? this.manifestCache.docVersion,
2066
3301
  ...delta?.annotationsVersion !== void 0 ? { annotationsVersion: delta.annotationsVersion } : {},
3302
+ ...delta?.formsVersion !== void 0 ? { formsVersion: delta.formsVersion } : {},
3303
+ ...delta?.layoutVersion !== void 0 ? { layoutVersion: delta.layoutVersion } : {},
3304
+ ...delta?.metadataVersion !== void 0 ? { metadataVersion: delta.metadataVersion } : {},
3305
+ ...delta?.attachmentsVersion !== void 0 ? { attachmentsVersion: delta.attachmentsVersion } : {},
2067
3306
  ...delta?.layerVersion !== void 0 ? { layerVersion: delta.layerVersion } : {},
2068
3307
  ...delta?.working !== void 0 ? { working: delta.working } : {},
2069
- pages: Array.from(byPageObjectNumber.values()).sort((a, b) => a.state.pageObjectNumber - b.state.pageObjectNumber)
2070
- };
2071
- }
2072
- /**
2073
- * Patch the cached manifest after a set-preserving page-structure op (move,
2074
- * rotate). Both are purely structural: they advance `docVersion` (so leaf
2075
- * URLs re-resolve) and `layoutVersion` (so the /layout leaf re-fetches),
2076
- * but leave every per-page pin untouched — a rotate renders the SAME
2077
- * normalized bitmaps, a move the same pages. We raise the floor
2078
- * unconditionally and, when our cache is exactly one version behind,
2079
- * advance it in place; otherwise we drop it and refetch lazily.
2080
- */
2081
- absorbPageStructure(cache) {
2082
- this.flipScopes(["layout"]);
2083
- this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
2084
- this.inflightManifest = null;
2085
- if (!this.manifestCache) return;
2086
- if (cache.docVersion <= this.manifestCache.docVersion) return;
2087
- if (cache.previousDocVersion !== this.manifestCache.docVersion) {
2088
- this.manifestCache = null;
2089
- return;
2090
- }
2091
- this.manifestCache = {
2092
- ...this.manifestCache,
2093
- docVersion: cache.docVersion,
2094
- layoutVersion: cache.layoutVersion
3308
+ pages: Array.from(byPageObjectNumber.values()).sort((a, b) => a.page.objectNumber - b.page.objectNumber)
2095
3309
  };
2096
3310
  }
2097
3311
  /**
2098
- * Patch the cached manifest after a page delete: the shared structural
2099
- * advance plus dropping the deleted pages' manifest rows, so no leaf URL
2100
- * for a retired PON can be built from the cache (a stale request would
2101
- * 404 anyway — this keeps the failure local and instant).
3312
+ * Patch the cached manifest after a page delete: the shared advance plus
3313
+ * dropping the deleted pages' manifest rows, so no leaf URL for a retired
3314
+ * page object number can be built from the cache (a stale request would
3315
+ * 404 anyway — this keeps the failure local and instant). Delete changes
3316
+ * the page set: a view that removed content must never resolve base
3317
+ * artifacts again, so content, annotations and forms flip with layout.
2102
3318
  */
2103
- absorbPageDelete(cache, deletedPages) {
2104
- this.flipScopes([
3319
+ absorbPageDelete(meta, deletedPages) {
3320
+ this.absorbMutation(meta, [
2105
3321
  "layout",
2106
3322
  "content",
2107
- "annotations"
3323
+ "annotations",
3324
+ "forms"
2108
3325
  ]);
2109
- this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
2110
- this.inflightManifest = null;
2111
3326
  if (!this.manifestCache) return;
2112
- if (cache.docVersion <= this.manifestCache.docVersion) return;
2113
- if (cache.previousDocVersion !== this.manifestCache.docVersion) {
2114
- this.manifestCache = null;
2115
- return;
2116
- }
2117
- const deleted = new Set(deletedPages);
3327
+ const deleted = new Set(deletedPages.map((page) => page.objectNumber));
2118
3328
  this.manifestCache = {
2119
3329
  ...this.manifestCache,
2120
- docVersion: cache.docVersion,
2121
- layoutVersion: cache.layoutVersion,
2122
- pages: this.manifestCache.pages.filter((page) => !deleted.has(page.state.pageObjectNumber))
3330
+ pages: this.manifestCache.pages.filter((page) => !deleted.has(page.page.objectNumber))
2123
3331
  };
2124
3332
  }
2125
3333
  /**
2126
3334
  * Patch bookkeeping after a page insert — the mirror of
2127
3335
  * {@link absorbPageDelete}, with one asymmetry: delete can patch the
2128
- * cached manifest losslessly (it only REMOVES rows), but an insert needs
2129
- * manifest rows for the fresh PONs and the result doesn't carry them. So
2130
- * this absorb flips the planes (insert changes the page SET, so like
2131
- * delete it owns content + annotations alongside layout), raises the
2132
- * version floor, and DROPS the cache — the next read refetches a manifest
3336
+ * cached manifest losslessly (it only removes rows), but an insert needs
3337
+ * manifest rows for the fresh page object numbers and the result doesn't carry them. So
3338
+ * this absorb flips the planes (insert changes the page set, so like
3339
+ * delete it owns content, annotations and forms alongside layout), raises the
3340
+ * version floor, and drops the cache — the next read refetches a manifest
2133
3341
  * that includes the new pages' rows. The UI never waits on that refetch:
2134
3342
  * the mutation result / event already carries the full new layout.
2135
3343
  */
2136
- absorbPageInsert(cache) {
3344
+ absorbPageInsert(meta) {
2137
3345
  this.flipScopes([
2138
3346
  "layout",
2139
3347
  "content",
2140
- "annotations"
3348
+ "annotations",
3349
+ "forms"
2141
3350
  ]);
2142
- this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
3351
+ if (meta.cacheDelta) this.manifestFloorVersion = Math.max(this.manifestFloorVersion, meta.cacheDelta.docVersion);
2143
3352
  this.inflightManifest = null;
2144
3353
  this.manifestCache = null;
2145
3354
  }
2146
- /**
2147
- * Patch the cached manifest after a metadata write. Symmetric with
2148
- * {@link absorbPageMove}: a metadata edit advances `docVersion` (so leaf
2149
- * URLs re-resolve) and `metadataVersion` (so the /metadata leaf re-fetches),
2150
- * but leaves `layoutVersion` and every per-page pin untouched. Raise the
2151
- * floor unconditionally and, when our cache is exactly one version behind,
2152
- * advance it in place; otherwise drop it and refetch lazily.
2153
- */
2154
- absorbMetadata(cache) {
2155
- this.flipScopes(["metadata"]);
2156
- this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
2157
- this.inflightManifest = null;
2158
- if (!this.manifestCache) return;
2159
- if (cache.docVersion <= this.manifestCache.docVersion) return;
2160
- if (cache.previousDocVersion !== this.manifestCache.docVersion) {
2161
- this.manifestCache = null;
2162
- return;
2163
- }
2164
- this.manifestCache = {
2165
- ...this.manifestCache,
2166
- docVersion: cache.docVersion,
2167
- metadataVersion: cache.metadataVersion
2168
- };
2169
- }
2170
- /**
2171
- * Patch the cached manifest after an attachment create/delete. Symmetric
2172
- * with {@link absorbMetadata}: an attachment write advances `docVersion`
2173
- * (so leaf URLs re-resolve) and `attachmentsVersion` (so the /attachments
2174
- * listing and /attachment-files leaves re-fetch), but leaves
2175
- * `layoutVersion` and every per-page pin untouched. Raise the floor
2176
- * unconditionally and, when our cache is exactly one version behind,
2177
- * advance it in place; otherwise drop it and refetch lazily.
2178
- */
2179
- absorbAttachments(cache) {
2180
- this.flipScopes(["attachments"]);
2181
- this.manifestFloorVersion = Math.max(this.manifestFloorVersion, cache.docVersion);
2182
- this.inflightManifest = null;
2183
- if (!this.manifestCache) return;
2184
- if (cache.docVersion <= this.manifestCache.docVersion) return;
2185
- if (cache.previousDocVersion !== this.manifestCache.docVersion) {
2186
- this.manifestCache = null;
2187
- return;
2188
- }
2189
- this.manifestCache = {
2190
- ...this.manifestCache,
2191
- docVersion: cache.docVersion,
2192
- attachmentsVersion: cache.attachmentsVersion
2193
- };
2194
- }
2195
3355
  startManifestFetch(opts) {
2196
3356
  const ctrl = new AbortController();
2197
3357
  const promise = this.fetchManifest(ctrl.signal, opts);
2198
3358
  this.inflightManifest = promise;
2199
3359
  promise.then((manifest) => {
2200
- if (manifest.docVersion >= this.manifestFloorVersion && (!this.manifestCache || manifest.docVersion >= this.manifestCache.docVersion)) this.manifestCache = manifest;
3360
+ if (manifest.docVersion >= this.manifestFloorVersion && (!this.manifestCache || manifest.docVersion >= this.manifestCache.docVersion)) {
3361
+ this.manifestCache = manifest;
3362
+ this.cloudSecurity.setProtection(manifest.protection);
3363
+ }
2201
3364
  }).catch(() => void 0).finally(() => {
2202
3365
  if (this.inflightManifest === promise) this.inflightManifest = null;
2203
3366
  });
@@ -2229,6 +3392,7 @@ var CloudDocumentHandle = class {
2229
3392
  this.sseClient = null;
2230
3393
  this.manifestCache = null;
2231
3394
  this.inflightManifest = null;
3395
+ this.onClose();
2232
3396
  return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue(void 0);
2233
3397
  }
2234
3398
  retainRemoteStream() {
@@ -2243,10 +3407,19 @@ var CloudDocumentHandle = class {
2243
3407
  ...this.manifestCache,
2244
3408
  auditHead: row.id
2245
3409
  };
2246
- const event = auditRowToEvent(row, this.sessionId);
2247
- if (!event) return;
2248
- this.absorbRemoteEvent(event);
2249
- this.hub.publish(event);
3410
+ if (row.withheld) {
3411
+ const meta = row.payload?.meta;
3412
+ if (meta && row.originSessionId !== this.sessionId) this.absorbMutation(meta, ["annotations", "forms"]);
3413
+ return;
3414
+ }
3415
+ if (row.kind === "signature.complete") {
3416
+ const sha = row.payload.version?.sha256;
3417
+ if (typeof sha === "string") this.pool.versioned(sha);
3418
+ }
3419
+ for (const event of auditRowToEvents(row, this.sessionId)) {
3420
+ this.absorbRemoteEvent(event);
3421
+ this.hub.publish(event);
3422
+ }
2250
3423
  },
2251
3424
  onFullRefresh: () => {
2252
3425
  this.manifestCache = null;
@@ -2259,7 +3432,9 @@ var CloudDocumentHandle = class {
2259
3432
  },
2260
3433
  onAuthLost: () => {
2261
3434
  this.sseClient = null;
2262
- }
3435
+ },
3436
+ onSession: (status) => this.pool.sync(status),
3437
+ onConnected: (connected) => this.pool.connected(connected)
2263
3438
  });
2264
3439
  this.sseClient.open();
2265
3440
  }
@@ -2270,52 +3445,73 @@ var CloudDocumentHandle = class {
2270
3445
  this.sseClient = null;
2271
3446
  }
2272
3447
  }
2273
- /** Patch the cached manifest from a REMOTE event's coherence pins — the
3448
+ /** Patch the cached manifest from a remote event's coherence pins — the
2274
3449
  * same absorb rails local mutations use, so reads stay warm no matter
2275
3450
  * whose hand caused the change. */
2276
3451
  absorbRemoteEvent(event) {
2277
3452
  switch (event.type) {
2278
- case "annotation.created":
2279
- case "annotation.updated":
2280
- case "annotation.deleted":
2281
- case "annotation.moved":
3453
+ case "pages.scaleSet":
3454
+ this.absorbMutation(event.meta, []);
3455
+ return;
3456
+ case "annotations.created":
3457
+ case "annotations.updated":
3458
+ case "annotations.deleted":
3459
+ case "annotations.reordered":
2282
3460
  this.absorbMutation(event.meta, ["annotations"]);
2283
3461
  return;
2284
- case "pages.moved":
3462
+ case "pages.reordered":
2285
3463
  case "pages.rotated":
2286
- if (event.cache) this.absorbPageStructure(event.cache);
3464
+ case "pages.named":
3465
+ this.absorbMutation(event.meta, ["layout"]);
2287
3466
  return;
2288
3467
  case "pages.deleted":
2289
- if (event.cache) this.absorbPageDelete(event.cache, event.pageObjectNumbers);
3468
+ this.absorbPageDelete(event.meta, event.pages);
2290
3469
  return;
2291
3470
  case "pages.inserted":
2292
- if (event.cache) this.absorbPageInsert(event.cache);
3471
+ this.absorbPageInsert(event.meta);
2293
3472
  return;
2294
3473
  case "metadata.updated":
2295
- if (event.cache) this.absorbMetadata(event.cache);
3474
+ case "metadata.customUpdated":
3475
+ this.absorbMutation(event.meta, ["metadata"]);
2296
3476
  return;
2297
- case "attachment.created":
2298
- case "attachment.deleted":
2299
- if (event.cache) this.absorbAttachments(event.cache);
3477
+ case "attachments.created":
3478
+ case "attachments.deleted":
3479
+ this.absorbMutation(event.meta, ["attachments"]);
2300
3480
  return;
2301
- case "form.valueChanged":
2302
- case "form.imported":
2303
- case "form.repaired":
2304
- case "form.fieldCreated":
2305
- case "form.fieldUpdated":
2306
- case "form.fieldDeleted":
2307
- case "form.widgetAttached":
2308
- case "form.widgetDetached":
2309
- this.absorbMutation(event.meta, ["annotations"]);
2310
- return;
2311
- case "form.effectsApplied":
2312
- if (event.meta) this.absorbMutation(event.meta, ["annotations"]);
3481
+ case "forms.valueSet":
3482
+ case "forms.repaired":
3483
+ case "forms.created":
3484
+ case "forms.updated":
3485
+ case "forms.deleted":
3486
+ case "forms.restored":
3487
+ case "forms.widgetAdded":
3488
+ case "forms.widgetRemoved":
3489
+ case "forms.widgetUpdated":
3490
+ case "forms.widgetDeleted":
3491
+ case "forms.widgetRestored":
3492
+ case "forms.widgetsReordered":
3493
+ case "forms.calculationsReordered":
3494
+ this.absorbMutation(event.meta, ["forms"]);
2313
3495
  return;
2314
3496
  case "pages.flattened":
2315
- if (event.meta) this.absorbMutation(event.meta, ["content", "annotations"]);
3497
+ case "annotations.flattened":
3498
+ this.absorbMutation(event.meta, [
3499
+ "content",
3500
+ "annotations",
3501
+ "forms"
3502
+ ]);
3503
+ return;
3504
+ case "signatures.prepared":
3505
+ case "document.versioned":
3506
+ this.manifestCache = null;
3507
+ this.inflightManifest = null;
2316
3508
  return;
2317
3509
  case "redaction.applied":
2318
- if (event.meta) this.absorbMutation(event.meta, ["content", "annotations"]);
3510
+ this.absorbMutation(event.meta, [
3511
+ "content",
3512
+ "annotations",
3513
+ "forms"
3514
+ ]);
2319
3515
  return;
2320
3516
  }
2321
3517
  }
@@ -2346,22 +3542,6 @@ function fallbackUnknownHead(id) {
2346
3542
  }
2347
3543
  };
2348
3544
  }
2349
- function awaitSignal(promise, signal) {
2350
- if (signal.aborted) return Promise.reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
2351
- return new Promise((resolve, reject) => {
2352
- const onAbort = () => {
2353
- reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason));
2354
- };
2355
- signal.addEventListener("abort", onAbort, { once: true });
2356
- promise.then((value) => {
2357
- signal.removeEventListener("abort", onAbort);
2358
- resolve(value);
2359
- }, (reason) => {
2360
- signal.removeEventListener("abort", onAbort);
2361
- reject(reason);
2362
- });
2363
- });
2364
- }
2365
3545
  //#endregion
2366
3546
  //#region src/share.ts
2367
3547
  /**
@@ -2445,8 +3625,11 @@ function shareSessionSource(baseUrl, shareToken, opts = {}) {
2445
3625
  }
2446
3626
  //#endregion
2447
3627
  //#region src/transport/HttpClient.ts
3628
+ /** CloudPDF SaaS: where an engine connects when it names no server of its own. */
3629
+ const CLOUDPDF_SAAS_URL = "https://engine.cloudpdf.com";
2448
3630
  var HttpClient = class HttpClient {
2449
- constructor(opts) {
3631
+ constructor(options) {
3632
+ this.options = options;
2450
3633
  _defineProperty(this, "base", void 0);
2451
3634
  _defineProperty(this, "tokenFn", void 0);
2452
3635
  _defineProperty(this, "fetchFn", void 0);
@@ -2454,13 +3637,13 @@ var HttpClient = class HttpClient {
2454
3637
  _defineProperty(this, "onRetry", void 0);
2455
3638
  _defineProperty(this, "sessionId", void 0);
2456
3639
  _defineProperty(this, "cdnBinding", null);
2457
- this.base = opts.baseUrl.replace(/\/$/, "");
2458
- const token = opts.token;
3640
+ this.base = (options.baseUrl ?? "https://engine.cloudpdf.com").replace(/\/$/, "");
3641
+ const token = options.token;
2459
3642
  this.tokenFn = token === void 0 ? null : typeof token === "function" ? token : () => token;
2460
- this.sessionId = opts.sessionId ?? null;
2461
- this.fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
2462
- this.docAffinityHeader = opts.docAffinityHeader ?? true;
2463
- this.onRetry = opts.onRetry;
3643
+ this.sessionId = options.sessionId ?? null;
3644
+ this.fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
3645
+ this.docAffinityHeader = options.docAffinityHeader ?? true;
3646
+ this.onRetry = options.onRetry;
2464
3647
  }
2465
3648
  /** Normalized server base URL (no trailing slash). */
2466
3649
  get baseUrl() {
@@ -2476,18 +3659,17 @@ var HttpClient = class HttpClient {
2476
3659
  return this.fetchFn;
2477
3660
  }
2478
3661
  /**
2479
- * Return a clone of this client bound to a different bearer. Used
2480
- * by `CloudEngine.open` to mint a per-handle client carrying the
2481
- * per-open token, so each opened document's RPCs go out under
2482
- * the right authorization without disturbing the engine-level
2483
- * token.
3662
+ * A client for one open document: this client's settings, the document's
3663
+ * own editing session, its own CDN binding, and `token` as its bearer
3664
+ * when the open names one (else this client's). Two open documents never
3665
+ * share object numbers, event echoes or a CDN binding, even two opens of
3666
+ * the same document on one engine.
2484
3667
  */
2485
- withToken(token) {
3668
+ forDocument({ sessionId, token }) {
2486
3669
  return new HttpClient({
2487
- baseUrl: this.baseUrl,
2488
- token,
2489
- ...this.sessionId ? { sessionId: this.sessionId } : {},
2490
- fetch: this.fetchFn
3670
+ ...this.options,
3671
+ ...token !== void 0 ? { token } : {},
3672
+ sessionId
2491
3673
  });
2492
3674
  }
2493
3675
  /**
@@ -2502,20 +3684,20 @@ var HttpClient = class HttpClient {
2502
3684
  ...headers
2503
3685
  }),
2504
3686
  signal
2505
- });
3687
+ }, { session: true });
2506
3688
  }
2507
3689
  /**
2508
3690
  * Install (or clear) the per-session CDN binding. Pass `null` to
2509
3691
  * route everything to origin again — useful on session close or
2510
3692
  * when an adapter swap happens mid-session (rare).
2511
3693
  *
2512
- * The HttpClient stays dumb about WHEN to do this; the cloud
3694
+ * The HttpClient stays dumb about when to do this; the cloud
2513
3695
  * document handle pushes the binding in after /access succeeds.
2514
3696
  *
2515
3697
  * Side effect: in the browser, signedCookies are written to
2516
3698
  * `document.cookie` once per setCdnAccess call so the CDN edge
2517
- * receives them on subsequent fetches. Node side ignores cookies
2518
- * for now (cookie jar wiring deferred until needed for tests).
3699
+ * receives them on subsequent fetches. Outside the browser signed
3700
+ * cookies are ignored: this client keeps no cookie jar.
2519
3701
  */
2520
3702
  setCdnAccess(binding) {
2521
3703
  this.cdnBinding = binding;
@@ -2670,27 +3852,44 @@ var HttpClient = class HttpClient {
2670
3852
  return await this.getFile(retryPath, signal);
2671
3853
  }
2672
3854
  }
2673
- async postMultipartJson(path, body, parser, signal) {
3855
+ async postMultipartJson(path, body, parser, signal, options = {}) {
2674
3856
  const res = await this.request(path, {
2675
3857
  method: "POST",
2676
3858
  body,
2677
3859
  signal
2678
- });
3860
+ }, options);
2679
3861
  return await this.parseJsonResponse(res, parser);
2680
3862
  }
2681
3863
  /** PATCH counterpart of {@link postMultipartJson} (binary-carrying mutations). */
2682
- async patchMultipartJson(path, body, parser, signal) {
3864
+ async patchMultipartJson(path, body, parser, signal, options = {}) {
2683
3865
  const res = await this.request(path, {
2684
3866
  method: "PATCH",
2685
3867
  body,
2686
3868
  signal
2687
- });
3869
+ }, options);
2688
3870
  return await this.parseJsonResponse(res, parser);
2689
3871
  }
2690
- async postJson(path, body, parser, signal) {
2691
- const res = await this.requestJson(path, "POST", body, signal);
3872
+ async postJson(path, body, parser, signal, options = {}) {
3873
+ const res = await this.requestJson(path, "POST", body, signal, options);
2692
3874
  return await this.parseJsonResponse(res, parser);
2693
3875
  }
3876
+ async putJson(path, body, parser, signal, options = {}) {
3877
+ return this.parseJsonResponse(await this.requestJson(path, "PUT", body, signal, options), parser);
3878
+ }
3879
+ /** POST a JSON body and parse a `multipart/form-data` response (a large annotation export). */
3880
+ async postJsonFormData(path, body, signal) {
3881
+ const res = await this.request(path, {
3882
+ method: "POST",
3883
+ body: JSON.stringify(body),
3884
+ headers: new Headers({
3885
+ "Content-Type": "application/json",
3886
+ Accept: "multipart/form-data"
3887
+ }),
3888
+ signal
3889
+ });
3890
+ if (!res.ok) await this.throwFromBody(res);
3891
+ return await res.formData();
3892
+ }
2694
3893
  /** POST a JSON body and return the raw binary response (pages.extract). */
2695
3894
  async postJsonBytes(path, body, signal) {
2696
3895
  const res = await this.requestJson(path, "POST", body, signal);
@@ -2698,7 +3897,7 @@ var HttpClient = class HttpClient {
2698
3897
  return new Uint8Array(await res.arrayBuffer());
2699
3898
  }
2700
3899
  /** POST a raw binary body (FDF/XFDF import) and parse the JSON response. */
2701
- async postBytesJson(path, bytes, contentType, parser, signal) {
3900
+ async postBytesJson(path, bytes, contentType, parser, signal, options = {}) {
2702
3901
  const headers = new Headers({ "Content-Type": contentType });
2703
3902
  const body = new Uint8Array(bytes).buffer;
2704
3903
  const res = await this.request(path, {
@@ -2706,61 +3905,73 @@ var HttpClient = class HttpClient {
2706
3905
  body,
2707
3906
  headers,
2708
3907
  signal
2709
- });
3908
+ }, options);
2710
3909
  return await this.parseJsonResponse(res, parser);
2711
3910
  }
2712
- async patchJson(path, body, parser, signal) {
2713
- const res = await this.requestJson(path, "PATCH", body, signal);
3911
+ async patchJson(path, body, parser, signal, options = {}) {
3912
+ const res = await this.requestJson(path, "PATCH", body, signal, options);
2714
3913
  return await this.parseJsonResponse(res, parser);
2715
3914
  }
2716
- async deleteJson(path, parser, signal) {
3915
+ async deleteJson(path, parser, signal, options = {}) {
2717
3916
  const res = await this.request(path, {
2718
3917
  method: "DELETE",
2719
3918
  signal
2720
- });
3919
+ }, options);
2721
3920
  return await this.parseJsonResponse(res, parser);
2722
3921
  }
2723
- async deleteEmpty(path, signal) {
2724
- const res = await this.request(path, {
2725
- method: "DELETE",
2726
- signal
2727
- });
2728
- if (res.status === 204) return;
2729
- if (!res.ok) await this.throwFromBody(res);
2730
- }
2731
- async postEmpty(path, body, signal) {
2732
- const res = await this.requestJson(path, "POST", body, signal);
2733
- if (res.status === 204) return;
2734
- if (!res.ok) await this.throwFromBody(res);
2735
- }
2736
- requestJson(path, method, body, signal) {
3922
+ requestJson(path, method, body, signal, options = {}) {
2737
3923
  const headers = new Headers({ "Content-Type": "application/json" });
2738
3924
  return this.request(path, {
2739
3925
  method,
2740
3926
  body: JSON.stringify(body ?? {}),
2741
3927
  headers,
2742
3928
  signal
2743
- });
3929
+ }, options);
2744
3930
  }
2745
- async request(path, init) {
2746
- const { url, extraHeader, routedToCdn } = this.resolveOutgoing(path);
3931
+ async request(path, init, options = {}) {
3932
+ const { url, extraHeader, routedToCdn } = this.resolveOutgoing(path, init.method ?? "GET");
2747
3933
  const headers = new Headers(init.headers ?? {});
2748
3934
  if (this.tokenFn) {
2749
3935
  const token = await this.tokenFn();
2750
3936
  headers.set("Authorization", `Bearer ${token}`);
2751
3937
  }
2752
3938
  if (!headers.has("Accept")) headers.set("Accept", "application/json");
2753
- if (this.sessionId) headers.set("X-Engine-Session-Id", this.sessionId);
3939
+ const { write } = options;
3940
+ if (this.sessionId && (write || options.session)) headers.set("X-Engine-Session-Id", this.sessionId);
3941
+ if (write) {
3942
+ if (write.opId !== void 0) headers.set("Idempotency-Key", write.opId);
3943
+ if (write.reserveObjectNumbers && write.reserveObjectNumbers > 0) headers.set("EmbedPDF-Reserve-Object-Numbers", String(write.reserveObjectNumbers));
3944
+ }
2754
3945
  if (extraHeader) headers.set(extraHeader.name, extraHeader.value);
2755
3946
  if (this.docAffinityHeader && !routedToCdn) {
2756
3947
  const doc = /^\/v1\/docs\/([^/]+)\//.exec(path);
2757
3948
  if (doc) headers.set("X-CloudPDF-Doc", decodeURIComponent(doc[1]));
2758
3949
  }
2759
- for (let attempt = 0;; attempt++) {
2760
- const res = await this.execute(url, {
2761
- ...init,
2762
- headers
2763
- });
3950
+ for (let attempt = 0, networkFailures = 0;; attempt++) {
3951
+ let res;
3952
+ try {
3953
+ res = await this.execute(url, {
3954
+ ...init,
3955
+ headers
3956
+ });
3957
+ } catch (err) {
3958
+ const wait = HttpClient.NETWORK_RETRY_MS[networkFailures];
3959
+ if (!write || wait === void 0 || !_embedpdf_engine_core_runtime.EngineError.is(err, _embedpdf_engine_core_runtime.EngineErrorCode.Network) || isStreamBody(init.body ?? null)) throw err;
3960
+ networkFailures++;
3961
+ const waitMs = wait * (.9 + .2 * Math.random());
3962
+ this.onRetry?.({
3963
+ path,
3964
+ code: _embedpdf_engine_core_runtime.EngineErrorCode.Network,
3965
+ attempt,
3966
+ waitMs
3967
+ });
3968
+ await sleepRacingAbort(waitMs, init.signal ?? null);
3969
+ continue;
3970
+ }
3971
+ if (res.ok && write?.onObjectNumbers) {
3972
+ const ranges = (0, _embedpdf_engine_core_runtime.parseObjectNumberRanges)(res.headers.get("EmbedPDF-Object-Numbers"));
3973
+ if (ranges.length > 0) write.onObjectNumbers(ranges);
3974
+ }
2764
3975
  if (res.status !== 503 || attempt >= HttpClient.MAX_RETRIES || isStreamBody(init.body ?? null)) return res;
2765
3976
  const code = await peekErrorCode(res);
2766
3977
  if (code === null || !RETRYABLE_503.has(code)) return res;
@@ -2795,13 +4006,15 @@ var HttpClient = class HttpClient {
2795
4006
  }
2796
4007
  /**
2797
4008
  * Decide where this request actually goes. With no CDN binding (or
2798
- * binding's `cdn` is null) every request stays on origin. Otherwise
4009
+ * binding's `cdn` is null) every request stays on origin, and so does
4010
+ * every request that isn't a read (`GET`, `HEAD`): a write must reach the
4011
+ * server itself, whatever read its path looks like. Otherwise
2799
4012
  * `applyCdnAccess` decides per-request based on the resource the
2800
4013
  * path resolves to (and whether the caller's scope granted CDN
2801
4014
  * access to it).
2802
4015
  */
2803
- resolveOutgoing(path) {
2804
- if (!this.cdnBinding || !this.cdnBinding.cdn) return {
4016
+ resolveOutgoing(path, method) {
4017
+ if (!(method === "GET" || method === "HEAD") || !this.cdnBinding || !this.cdnBinding.cdn) return {
2805
4018
  url: `${this.baseUrl}${path}`,
2806
4019
  extraHeader: null,
2807
4020
  routedToCdn: false
@@ -2839,11 +4052,17 @@ var HttpClient = class HttpClient {
2839
4052
  _defineProperty(HttpClient, "MAX_RETRIES", 2);
2840
4053
  _defineProperty(HttpClient, "RETRY_AFTER_CAP_MS", 5e3);
2841
4054
  _defineProperty(HttpClient, "RETRY_AFTER_DEFAULT_MS", 1e3);
4055
+ _defineProperty(HttpClient, "NETWORK_RETRY_MS", [
4056
+ 250,
4057
+ 1e3,
4058
+ 3e3
4059
+ ]);
2842
4060
  function mapStatusToCode(status) {
2843
4061
  if (status === 401) return _embedpdf_engine_core_runtime.EngineErrorCode.Unauthenticated;
2844
4062
  if (status === 403) return _embedpdf_engine_core_runtime.EngineErrorCode.Forbidden;
2845
- if (status === 409) return _embedpdf_engine_core_runtime.EngineErrorCode.WeakAnnotationSessionConflict;
4063
+ if (status === 409) return _embedpdf_engine_core_runtime.EngineErrorCode.LayerVersionConflict;
2846
4064
  if (status === 404) return _embedpdf_engine_core_runtime.EngineErrorCode.NotFound;
4065
+ if (status === 413) return _embedpdf_engine_core_runtime.EngineErrorCode.PayloadTooLarge;
2847
4066
  if (status === 422) return _embedpdf_engine_core_runtime.EngineErrorCode.DocOpenFailed;
2848
4067
  if (status === 499) return _embedpdf_engine_core_runtime.EngineErrorCode.Aborted;
2849
4068
  if (status === 400) return _embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg;
@@ -2892,22 +4111,19 @@ function sleepRacingAbort(ms, signal) {
2892
4111
  */
2893
4112
  var CloudEngine = class CloudEngine {
2894
4113
  static fromOptions(opts) {
2895
- const sessionId = `cloud:${(0, _embedpdf_engine_services.generateUuid)()}`;
2896
- return new CloudEngine(new HttpClient({
2897
- ...opts,
2898
- sessionId
2899
- }), sessionId);
4114
+ return new CloudEngine(new HttpClient(opts));
2900
4115
  }
2901
- constructor(http, sessionId) {
4116
+ constructor(http) {
2902
4117
  this.http = http;
2903
- this.sessionId = sessionId;
4118
+ _defineProperty(this, "canOpenFiles", false);
2904
4119
  _defineProperty(this, "destroyed", false);
4120
+ _defineProperty(this, "handles", /* @__PURE__ */ new Set());
2905
4121
  }
2906
4122
  open(input, options) {
2907
4123
  if (this.destroyed) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, "engine destroyed"));
2908
4124
  options?.scope;
2909
4125
  options?.identity;
2910
- const effectivePassword = options && "password" in options ? options.password ?? null : input.password ?? null;
4126
+ const effectivePassword = options?.password ?? null;
2911
4127
  if (input.kind === "share") {
2912
4128
  const raw = shareSessionSource(this.http.baseUrl, input.shareToken, {
2913
4129
  ...input.sharePassword !== void 0 ? { password: input.sharePassword } : {},
@@ -2922,28 +4138,39 @@ var CloudEngine = class CloudEngine {
2922
4138
  };
2923
4139
  return this.open({
2924
4140
  kind: "token",
2925
- token,
2926
- password: input.password ?? null
4141
+ token
2927
4142
  }, options);
2928
4143
  }
2929
4144
  if (input.kind === "token") {
2930
4145
  const tokenSource = input.token;
2931
4146
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
2932
- const docHttp = this.http.withToken(tokenSource);
4147
+ const docHttp = this.http.forDocument({
4148
+ sessionId: newSessionId(),
4149
+ token: tokenSource
4150
+ });
2933
4151
  const token = await docHttp.currentToken();
2934
4152
  const claims = decodeUnverifiedClaims(token);
2935
4153
  const docId = claims.doc_id;
2936
4154
  if (typeof docId !== "string" || docId.length === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "cloud engine: token has no doc_id claim — mint a doc-scoped JWT");
2937
4155
  const layerName = typeof claims.layer_name === "string" && claims.layer_name.length > 0 ? claims.layer_name : _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME;
2938
4156
  const head = await docHttp.getJson(_embedpdf_engine_core_wire.wirePaths.layerHead(docId, layerName), (raw) => _embedpdf_engine_core_wire.DocumentHeadSchema.parse(raw), signal);
2939
- const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, token, this.sessionId);
2940
- await maybeAutoEstablishAccess(handle, head, signal, effectivePassword);
4157
+ const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, token, () => this.handles.delete(handle));
4158
+ this.handles.add(handle);
4159
+ try {
4160
+ await establishOnOpen(handle, head, signal, effectivePassword, false);
4161
+ } catch (error) {
4162
+ await handle.close();
4163
+ throw error;
4164
+ }
2941
4165
  return handle;
2942
4166
  });
2943
4167
  }
2944
4168
  if (input.kind === "id") {
2945
4169
  const id = input.id;
2946
- const docHttp = input.token ? this.http.withToken(input.token) : this.http;
4170
+ const docHttp = this.http.forDocument({
4171
+ sessionId: newSessionId(),
4172
+ ...input.token ? { token: input.token } : {}
4173
+ });
2947
4174
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
2948
4175
  let layerName = input.layerName ?? _embedpdf_engine_core_wire.DEFAULT_LAYER_NAME;
2949
4176
  let resolvedToken = null;
@@ -2957,46 +4184,97 @@ var CloudEngine = class CloudEngine {
2957
4184
  if (typeof claims.layer_name === "string" && claims.layer_name.length > 0) layerName = claims.layer_name;
2958
4185
  }
2959
4186
  const head = await docHttp.getJson(_embedpdf_engine_core_wire.wirePaths.layerHead(id, layerName), (raw) => _embedpdf_engine_core_wire.DocumentHeadSchema.parse(raw), signal);
2960
- const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, resolvedToken, this.sessionId);
2961
- await maybeAutoEstablishAccess(handle, head, signal, effectivePassword);
4187
+ const handle = new CloudDocumentHandle(docHttp, head.id, layerName, head, resolvedToken, () => this.handles.delete(handle));
4188
+ this.handles.add(handle);
4189
+ const tenant = resolvedToken !== null && typeof decodeUnverifiedClaims(resolvedToken).doc_id !== "string";
4190
+ try {
4191
+ await establishOnOpen(handle, head, signal, effectivePassword, tenant);
4192
+ } catch (error) {
4193
+ await handle.close();
4194
+ throw error;
4195
+ }
2962
4196
  return handle;
2963
4197
  });
2964
4198
  }
2965
4199
  return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `cloud engine supports OpenInput.kind === 'token', 'id', or 'share' (got '${input.kind}')`));
2966
4200
  }
4201
+ /**
4202
+ * Close every document this engine opened (their event streams stop), then
4203
+ * refuse new opens. As on the local engine, a handle is unusable after.
4204
+ */
2967
4205
  destroy() {
2968
4206
  if (this.destroyed) return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue(void 0);
2969
4207
  this.destroyed = true;
2970
- return _embedpdf_engine_core_runtime.AbortablePromise.resolveValue(void 0);
4208
+ const open = [...this.handles];
4209
+ this.handles.clear();
4210
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async () => {
4211
+ await Promise.all(open.map((handle) => handle.close()));
4212
+ });
2971
4213
  }
2972
4214
  };
2973
4215
  /**
4216
+ * A session id for a document being opened. Every open is its own session,
4217
+ * even a second open of the same document on this engine. The id stamps the
4218
+ * document's own events' origins and travels as `X-Engine-Session-Id`: the
4219
+ * server keeps the session's object numbers under it and marks its audit
4220
+ * rows with it, so the document's stream drops its own echoes and hears
4221
+ * every other open as remote.
4222
+ */
4223
+ function newSessionId() {
4224
+ return `cloud:${(0, _embedpdf_engine_core_runtime.generateUuid)()}`;
4225
+ }
4226
+ /**
4227
+ * What `open()` learns before handing the document over: access when the
4228
+ * password or a CDN needs it (see {@link maybeAutoEstablishAccess}), then
4229
+ * what the document's signatures forbid (the manifest). A caller that may
4230
+ * create objects calls `/access` anyway, beside the manifest fetch, for its
4231
+ * editing session's first object numbers; on a document locked by a
4232
+ * password, `unlock()` brings them. A failed `/access` doesn't fail the
4233
+ * open: creates then go without numbers until the pool gets some.
4234
+ */
4235
+ async function establishOnOpen(handle, head, signal, password, tenant) {
4236
+ const accessed = await maybeAutoEstablishAccess(handle, head, signal, password);
4237
+ const security = handle.security;
4238
+ const locked = !accessed && head.access.reasons.includes("password");
4239
+ await Promise.all([!accessed && !locked && (tenant || mayCreateObjects(security)) ? settleLinked(security.establishAccess(), signal) : void 0, handle.learnProtection(signal)]);
4240
+ }
4241
+ /**
4242
+ * Whether the caller may make new objects (annotations as itself, pages,
4243
+ * form fields): the server's own test for handing out object numbers, from
4244
+ * what the client knows before `/access`.
4245
+ */
4246
+ function mayCreateObjects(security) {
4247
+ return security.allowsAnnotation("create") || security.allows("doc.pages.assemble") || security.allows("doc.forms.modify");
4248
+ }
4249
+ /**
2974
4250
  * Post-/head access establishment, in two layers:
2975
4251
  *
2976
- * 1. **Supplied password** — a password given at open is always TRIED
4252
+ * 1. **Supplied password** — a password given at open is always tried
2977
4253
  * (PDFium parity: the local engine feeds it to the loader no matter
2978
4254
  * what). Two triggers, because `/head` only carries the `password`
2979
- * reason when a password is needed to READ:
4255
+ * reason when a password is needed to read:
2980
4256
  * - `reasons` has `'password'` → required-to-read case
2981
4257
  * - `head.permissions.canUpgradeToOwner` → permission-only encrypted
2982
4258
  * doc; the password can still upgrade to owner. Skipping this arm
2983
- * would silently drop supplied OWNER passwords — and diverge from
4259
+ * would silently drop supplied owner passwords — and diverge from
2984
4260
  * the local engine on identical input.
2985
4261
  * Outcomes follow "a password failure may only block what the password
2986
4262
  * was needed for": a rejection in the required case leaves the handle
2987
- * locked (the caller's prompt takes over, showing "incorrect"); in the
4263
+ * locked (the prompt says `incorrect`, as on the local engine); in the
2988
4264
  * upgrade case the document stays readable and the failure is
2989
4265
  * non-fatal. The one /access POST also installs the CDN binding, so a
2990
4266
  * successful unlock covers the `password + cdn` combined case.
2991
4267
  *
2992
4268
  * 2. **CDN-only** — unchanged: `/access` without a password, transparently.
2993
- * A required password with NO supplied password never auto-calls; the
4269
+ * A required password with no supplied password never auto-calls; the
2994
4270
  * dev prompts and `unlock()` runs the same POST.
2995
4271
  *
2996
4272
  * Non-password /access failures never break `open()` (the first real
2997
4273
  * request still tries origin, where the JWT check produces a regular
2998
4274
  * Forbidden) — but they are also never mistaken for a wrong password:
2999
4275
  * only a DocPasswordRequired/Incorrect rejection means "wrong password".
4276
+ *
4277
+ * Returns whether `/access` answered.
3000
4278
  */
3001
4279
  async function maybeAutoEstablishAccess(handle, head, signal, password) {
3002
4280
  const reasons = new Set(head.access.reasons);
@@ -3005,13 +4283,13 @@ async function maybeAutoEstablishAccess(handle, head, signal, password) {
3005
4283
  if (await settleLinked(security.unlock({
3006
4284
  password,
3007
4285
  mode: "any"
3008
- }), signal)) return;
3009
- if (reasons.has("password")) return;
4286
+ }), signal)) return true;
4287
+ if (reasons.has("password")) return false;
3010
4288
  }
3011
- if (!head.access.required) return;
3012
- if (reasons.has("password")) return;
3013
- if (!reasons.has("cdn")) return;
3014
- await settleLinked(security.establishAccess(), signal);
4289
+ if (!head.access.required) return false;
4290
+ if (reasons.has("password")) return false;
4291
+ if (!reasons.has("cdn")) return false;
4292
+ return settleLinked(security.establishAccess(), signal);
3015
4293
  }
3016
4294
  /** Await an /access attempt with open()-abort linkage. Returns whether it
3017
4295
  * succeeded; failures are contained (see maybeAutoEstablishAccess). */
@@ -3030,9 +4308,6 @@ async function settleLinked(pending, signal) {
3030
4308
  }
3031
4309
  //#endregion
3032
4310
  //#region src/index.ts
3033
- function createCloudEngine(opts) {
3034
- return CloudEngine.fromOptions(opts);
3035
- }
3036
4311
  /**
3037
4312
  * Create a cloud {@link CloudEngine} — the drop-in counterpart of
3038
4313
  * `localEngine()`, so swapping local for cloud is a one-import change.
@@ -3044,59 +4319,46 @@ function createCloudEngine(opts) {
3044
4319
  * to let a `<Viewer>` own the lifetime instead.
3045
4320
  *
3046
4321
  * Note the deliberate asymmetry with `localEngine()`: there is no `fonts`
3047
- * option. Fallback fonts are a SERVER policy on the cloud (`Engine.fonts` is
4322
+ * option. Fallback fonts are a server policy on the cloud (`Engine.fonts` is
3048
4323
  * `undefined` cloud-side), so they cannot be configured from the client. This
3049
4324
  * is the local-vs-cloud split, surfaced in the API.
3050
4325
  *
4326
+ * Without a `baseUrl` it connects to CloudPDF SaaS; your own server names itself:
4327
+ *
3051
4328
  * ```ts
3052
- * const engine = cloudEngine({ baseUrl: 'https://pdf.example.com', token });
4329
+ * const engine = cloudEngine(); // CloudPDF SaaS
4330
+ * const own = cloudEngine({ baseUrl: 'https://pdf.example.com' }); // your @cloudpdf/server
3053
4331
  * <Viewer engine={engine} plugins={[stagePlugin(), renderPlugin()]} />
3054
4332
  * ```
3055
4333
  */
3056
- function cloudEngine(opts) {
4334
+ function cloudEngine(opts = {}) {
3057
4335
  return CloudEngine.fromOptions(opts);
3058
4336
  }
3059
4337
  //#endregion
3060
- Object.defineProperty(exports, "AbortError", {
3061
- enumerable: true,
3062
- get: function() {
3063
- return _embedpdf_engine_core_runtime.AbortError;
3064
- }
3065
- });
3066
- Object.defineProperty(exports, "AbortablePromise", {
3067
- enumerable: true,
3068
- get: function() {
3069
- return _embedpdf_engine_core_runtime.AbortablePromise;
3070
- }
3071
- });
4338
+ exports.CLOUDPDF_SAAS_URL = CLOUDPDF_SAAS_URL;
3072
4339
  exports.CloudDocumentAnnotationsService = CloudDocumentAnnotationsService;
3073
4340
  exports.CloudDocumentHandle = CloudDocumentHandle;
3074
4341
  exports.CloudDocumentPagesService = CloudDocumentPagesService;
3075
4342
  exports.CloudEngine = CloudEngine;
3076
4343
  exports.CloudMetadataService = CloudMetadataService;
3077
4344
  exports.CloudPageAnnotationsService = CloudPageAnnotationsService;
3078
- exports.CloudPageGeometryService = CloudPageGeometryService;
3079
4345
  exports.CloudPageHandle = CloudPageHandle;
3080
4346
  exports.CloudPageRenderService = CloudPageRenderService;
3081
- Object.defineProperty(exports, "EngineError", {
3082
- enumerable: true,
3083
- get: function() {
3084
- return _embedpdf_engine_core_runtime.EngineError;
3085
- }
3086
- });
3087
- Object.defineProperty(exports, "EngineErrorCode", {
3088
- enumerable: true,
3089
- get: function() {
3090
- return _embedpdf_engine_core_runtime.EngineErrorCode;
3091
- }
3092
- });
3093
4347
  exports.HttpClient = HttpClient;
3094
4348
  exports.ShareExchangeError = ShareExchangeError;
3095
4349
  exports.cloudEngine = cloudEngine;
3096
- exports.createCloudEngine = createCloudEngine;
3097
4350
  exports.decodeUnverifiedClaims = decodeUnverifiedClaims;
3098
4351
  exports.engineErrorFromShareExchange = engineErrorFromShareExchange;
3099
4352
  exports.exchangeShareToken = exchangeShareToken;
3100
4353
  exports.shareSessionSource = shareSessionSource;
4354
+ var _embedpdf_engine_core_public = require("@embedpdf/engine-core/public");
4355
+ Object.keys(_embedpdf_engine_core_public).forEach(function(k) {
4356
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
4357
+ enumerable: true,
4358
+ get: function() {
4359
+ return _embedpdf_engine_core_public[k];
4360
+ }
4361
+ });
4362
+ });
3101
4363
 
3102
4364
  //# sourceMappingURL=index.cjs.map