@embedpdf/engine-core 3.0.0-next.0

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.
Files changed (55) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +24 -0
  3. package/dist/AbortError-ByBflvEl.cjs +38 -0
  4. package/dist/AbortError-ByBflvEl.cjs.map +1 -0
  5. package/dist/AbortError-CjSasIlL.js +27 -0
  6. package/dist/AbortError-CjSasIlL.js.map +1 -0
  7. package/dist/AnnotationListSnapshot-W0RCsyDE.d.cts +5043 -0
  8. package/dist/AnnotationListSnapshot-W0RCsyDE.d.ts +5043 -0
  9. package/dist/DocumentSecurityService-B-tULKNY.d.ts +332 -0
  10. package/dist/DocumentSecurityService-Df79fgpe.d.cts +332 -0
  11. package/dist/Engine-B7Oe7f5i.d.ts +1013 -0
  12. package/dist/Engine-D9loFZc1.d.cts +1013 -0
  13. package/dist/builders-BvikWRuO.cjs +1684 -0
  14. package/dist/builders-BvikWRuO.cjs.map +1 -0
  15. package/dist/builders-C548U47M.js +1367 -0
  16. package/dist/builders-C548U47M.js.map +1 -0
  17. package/dist/conformance.cjs +5414 -0
  18. package/dist/conformance.cjs.map +1 -0
  19. package/dist/conformance.d.cts +530 -0
  20. package/dist/conformance.d.ts +530 -0
  21. package/dist/conformance.js +5394 -0
  22. package/dist/conformance.js.map +1 -0
  23. package/dist/fold-CB4fAOOy.js +175 -0
  24. package/dist/fold-CB4fAOOy.js.map +1 -0
  25. package/dist/fold-IaMph3CO.cjs +216 -0
  26. package/dist/fold-IaMph3CO.cjs.map +1 -0
  27. package/dist/index.cjs +82 -0
  28. package/dist/index.d.cts +2 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +5 -0
  31. package/dist/resolver-DAZWVoJL.cjs +434 -0
  32. package/dist/resolver-DAZWVoJL.cjs.map +1 -0
  33. package/dist/resolver-DiPMTfcy.js +363 -0
  34. package/dist/resolver-DiPMTfcy.js.map +1 -0
  35. package/dist/runtime.cjs +453 -0
  36. package/dist/runtime.cjs.map +1 -0
  37. package/dist/runtime.d.cts +1002 -0
  38. package/dist/runtime.d.ts +1002 -0
  39. package/dist/runtime.js +361 -0
  40. package/dist/runtime.js.map +1 -0
  41. package/dist/schemas-9OpSSeM6.js +2493 -0
  42. package/dist/schemas-9OpSSeM6.js.map +1 -0
  43. package/dist/schemas-B_1VyFsm.cjs +3608 -0
  44. package/dist/schemas-B_1VyFsm.cjs.map +1 -0
  45. package/dist/subtype-BvAzLCGC.js +196 -0
  46. package/dist/subtype-BvAzLCGC.js.map +1 -0
  47. package/dist/subtype-qejjVr6h.cjs +231 -0
  48. package/dist/subtype-qejjVr6h.cjs.map +1 -0
  49. package/dist/wire.cjs +1516 -0
  50. package/dist/wire.cjs.map +1 -0
  51. package/dist/wire.d.cts +1546 -0
  52. package/dist/wire.d.ts +1546 -0
  53. package/dist/wire.js +1280 -0
  54. package/dist/wire.js.map +1 -0
  55. package/package.json +74 -0
package/dist/wire.js ADDED
@@ -0,0 +1,1280 @@
1
+ import { n as checkCapability, t as checkAnyCapability } from "./resolver-DiPMTfcy.js";
2
+ import { $ as PageStateSchema, $n as FreeTextIntentSchema, $t as SquigglyDraftSchema, A as FormSetValueResultSchema, An as FileAttachmentPatchSchema, At as AnnotationDTOSchema, B as PageDeleteResultSchema, Bn as CaretDraftSchema, Bt as UnderlinePatchSchema, C as FormEffectSchema, Cn as HighlightDraftSchema, Cr as PdfRotationSchema, Ct as FormKindSchema, D as FormFieldUpdateResultSchema, Dn as FreeTextPatchSchema, Dt as ToggleFieldWidgetSchema, E as FormFieldDeleteResultSchema, En as FreeTextDraftSchema, Et as FormWidgetRefSchema, F as MetadataUpdateResultSchema, Fn as EmbeddedFileRefSchema, Ft as UnsupportedDTOSchema, G as PageListSnapshotSchema, Gn as AnnotationFlagsPartialSchema, Gt as StrikeoutDTOSchema, H as PageFlattenResultSchema, Hn as AnnotationBaseShape, Ht as TextDTOSchema, I as MutationMetaSchema, In as CircleDTOSchema, It as UnsupportedDraftSchema, J as PageNetworkRenderFormatSchema, Jn as AnnotationRefSchema, Jt as ResourceRefSchema, K as PageMoveInputSchema, Kn as AnnotationFlagsSchema, Kt as StrikeoutDraftSchema, L as OpenDocumentResponseSchema, Ln as CircleDraftSchema, Lt as UnsupportedPatchSchema, M as LayerScopesSchema, Mn as WireAttachmentFileSchema, Mt as AnnotationPatchSchema, N as ManifestPageSchema, Nn as AttachmentFileInfoSchema, Nt as KIND_BY_SUBTYPE, O as FormImportResultSchema, On as FileAttachmentDTOSchema, Ot as WidgetPlacementSchema, P as MetadataPatchSchema, Pn as EmbeddedFileItemSchema, Pt as WidgetAppearanceSchema, Q as PageRotateResultSchema, Qn as ColorSchema, Qt as SquigglyDTOSchema, R as PageBoxesSchema, Rn as CirclePatchSchema, Rt as UnderlineDTOSchema, S as EngineErrorPayloadSchema, Sn as HighlightDTOSchema, Sr as PdfRectSchema, St as FormFieldValueSchema, T as FormFieldCreateResultSchema, Tn as FreeTextDTOSchema, Tt as FormValueEntrySchema, U as PageGeometrySnapshotSchema, Un as AnnotationBorderStyleSchema, Ut as TextDraftSchema, V as PageFlattenInputSchema, Vn as CaretPatchSchema, Vt as NoteIconSchema, W as PageLayoutSchema, Wn as AnnotationDraftBaseShape, Wt as TextPatchSchema, X as PageRenderQuerySchema, Xn as BlendModeSchema, Xt as StampWireDraftSchema, Y as PageRenderAnnotatedQuerySchema, Yn as AnnotationStableIdSchema, Yt as StampDTOSchema, Z as PageRotateInputSchema, Zn as CaretIntentSchema, Zt as StampWirePatchSchema, _ as CachePinsSchema, _n as LineDraftSchema, _r as CalloutLineSchema, _t as FormFieldFlagsSchema, a as AnnotationCreateResultSchema, an as PolylineDraftSchema, ar as RectSchema, at as RefetchReasonSchema, b as DocumentMetadataSchema, bn as InkDraftSchema, br as PdfPointSchema, bt as FormFieldPatchSchema, c as AnnotationListPageSnapshotSchema, cn as PolygonDraftSchema, cr as StrikeoutIntentSchema, ct as SearchQuerySchema, d as AnnotationUpdateResultSchema, dn as LinkDraftSchema, dr as PdfActionNodeSchema, dt as SearchSnippetSchema, en as SquigglyPatchSchema, er as InkIntentSchema, et as PageStructureCacheSchema, f as AppearanceOutcomeSchema, fn as LinkPatchSchema, fr as PdfActionTreeSchema, ft as WeakAnnotationSessionPagesRequestSchema, g as CacheDeltaSchema, gn as LineDTOSchema, gr as PdfPageActionsSchema, gt as FormFieldDraftSchema, h as AttachmentsCacheSchema, hn as PdfLinkTargetWritableSchema, hr as PdfFieldActionsSchema, ht as FormFieldDTOSchema, i as AnnotationAppearancesQuerySchema, in as PolylineDTOSchema, ir as PointSchema, it as RedactionApplyScopeSchema, j as FormWidgetLinkResultSchema, jn as FileAttachmentWireDraftSchema, jt as AnnotationDraftSchema, k as FormRepairResultSchema, kn as FileAttachmentIconSchema, kt as ANNOTATION_KINDS, l as AnnotationListSnapshotAllPagesSchema, ln as PolygonPatchSchema, lr as TextAlignmentSchema, lt as SearchRequestSchema, m as AttachmentDeleteResultSchema, mn as PdfLinkTargetSchema, mr as PdfAnnotationActionsSchema, mt as FormDataFormatSchema, n as AccessResponseSchema, nn as SquareDraftSchema, nr as LineEndingsSchema, nt as PdfSaveModeSchema, o as AnnotationDeleteResultSchema, on as PolylinePatchSchema, or as RevisionTokenSchema, ot as SearchMatchSchema, p as AttachmentCreateResultSchema, pn as PdfDestinationSchema, pr as PdfActionTypeSchema, pt as WeakAnnotationSessionResponseSchema, q as PageMoveResultSchema, qn as AnnotationPatchBaseShape, qt as StrikeoutPatchSchema, r as AnnotationAppearanceManifestSchema, rn as SquarePatchSchema, rr as PdfRectDifferencesSchema, rt as RedactionApplyResultSchema, s as AnnotationListMutationMetaSchema, sn as PolygonDTOSchema, sr as StandardFontSchema, st as SearchModeSchema, t as AccessRequestSchema, tn as SquareDTOSchema, tr as LineEndingSchema, tt as PageTextSnapshotSchema, u as AnnotationMoveResultSchema, un as LinkDTOSchema, ur as DocumentActionsSnapshotSchema, ut as SearchSliceSchema, v as DocumentHeadSchema, vn as LinePatchSchema, vr as InkListSchema, vt as FormFieldOptionInputSchema, w as FormEffectsResultSchema, wn as HighlightPatchSchema, wr as PdfSizeSchema, wt as FormSnapshotSchema, x as DocumentSecurityStateSchema, xn as InkPatchSchema, xr as PdfQuadSchema, xt as FormFieldRefSchema, y as DocumentManifestSchema, yn as InkDTOSchema, yr as LinePointsSchema, yt as FormFieldOptionSchema, z as PageDeleteInputSchema, zn as CaretDTOSchema, zt as UnderlineDraftSchema } from "./schemas-9OpSSeM6.js";
3
+ //#region src/wire/token.ts
4
+ const DEFAULT_MAX_TOKEN_LENGTH = 256;
5
+ const FIELD_NAME_RE = /^[A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z][A-Za-z0-9]*)*$/;
6
+ const VALUE_RE = /^[A-Za-z0-9.-]+$/;
7
+ function encodeToken(schema, input) {
8
+ assertSchema(schema);
9
+ const allowed = new Set(schema.fields);
10
+ const pairs = [];
11
+ for (const [key, value] of Object.entries(input)) {
12
+ if (value === void 0) continue;
13
+ assertKnownField(allowed, key);
14
+ const encoded = encodeScalar(key, value);
15
+ assertSafeValue(key, encoded);
16
+ pairs.push([key, encoded]);
17
+ }
18
+ pairs.sort(([a], [b]) => a.localeCompare(b));
19
+ const token = pairs.map(([key, value]) => `${key}=${value}`).join(",");
20
+ if (token.length === 0) throw new Error("token must not be empty");
21
+ assertTokenLength(schema, token);
22
+ return token;
23
+ }
24
+ function decodeToken(schema, raw) {
25
+ assertSchema(schema);
26
+ const token = raw.startsWith("@") ? raw.slice(1) : raw;
27
+ assertTokenLength(schema, token);
28
+ if (token.length === 0) throw new Error("token must not be empty");
29
+ const allowed = new Set(schema.fields);
30
+ const output = {};
31
+ let previousKey;
32
+ for (const pair of token.split(",")) {
33
+ if (pair.length === 0) throw new Error("token contains an empty field");
34
+ const separator = pair.indexOf("=");
35
+ if (separator <= 0 || separator === pair.length - 1) throw new Error(`token field "${pair}" must be key=value`);
36
+ const key = pair.slice(0, separator);
37
+ const value = pair.slice(separator + 1);
38
+ assertKnownField(allowed, key);
39
+ assertSafeValue(key, value);
40
+ if (output[key] !== void 0) throw new Error(`token field "${key}" appears more than once`);
41
+ if (previousKey !== void 0 && key.localeCompare(previousKey) <= 0) throw new Error("token fields must be in alphabetical order");
42
+ previousKey = key;
43
+ output[key] = value;
44
+ }
45
+ return output;
46
+ }
47
+ function assertSchema(schema) {
48
+ for (const field of schema.fields) if (!FIELD_NAME_RE.test(field)) throw new Error(`token field name "${field}" must be camelCase segments joined by "."`);
49
+ }
50
+ function assertKnownField(allowed, key) {
51
+ if (!allowed.has(key)) throw new Error(`unknown token field "${key}"`);
52
+ }
53
+ function encodeScalar(key, value) {
54
+ if (typeof value === "boolean") return value ? "true" : "false";
55
+ if (typeof value === "number") {
56
+ if (!Number.isFinite(value)) throw new Error(`token field "${key}" must be finite`);
57
+ return Number.isInteger(value) ? String(value) : trimDecimal(value);
58
+ }
59
+ return value;
60
+ }
61
+ function trimDecimal(value) {
62
+ return value.toFixed(6).replace(/0+$/, "").replace(/\.$/, "");
63
+ }
64
+ function assertSafeValue(key, value) {
65
+ if (!VALUE_RE.test(value)) throw new Error(`token field "${key}" value contains a reserved character`);
66
+ }
67
+ function assertTokenLength(schema, token) {
68
+ const maxLength = schema.maxLength ?? DEFAULT_MAX_TOKEN_LENGTH;
69
+ if (token.length > maxLength) throw new Error(`token exceeds ${maxLength} characters`);
70
+ }
71
+ //#endregion
72
+ //#region src/wire/tokenSchemas.ts
73
+ const DocTokenSchema = { fields: ["docVersion"] };
74
+ const ContentTokenSchema = { fields: ["contentVersion"] };
75
+ const LayoutTokenSchema = { fields: ["layoutVersion"] };
76
+ const MetadataTokenSchema = { fields: ["metadataVersion"] };
77
+ const AnnotationTokenSchema = { fields: ["annotationVersion"] };
78
+ const ActionsTokenSchema = { fields: ["actionsVersion"] };
79
+ const AttachmentsTokenSchema = { fields: ["attachmentsVersion"] };
80
+ const DownloadTokenSchema = {
81
+ fields: ["docVersion", "mode"],
82
+ maxLength: 128
83
+ };
84
+ /**
85
+ * Allowed flat keys for the render token, expressed as dotted paths that
86
+ * mirror the SDK `PageImageOptions` shape 1:1. The token codec is fully
87
+ * generic over this list — adding a new render option means adding its
88
+ * dotted path here and a matching branch in `PageImageOptionsWireSchema`.
89
+ * No encoder/decoder code changes.
90
+ *
91
+ * `includeAnnotations` is deliberately NOT a token field: annotatedness
92
+ * changes the artifact's plane-dependency
93
+ * set, so it is expressed by the path FAMILY (`…/render/pages/` vs
94
+ * `…/render/annotated/pages/`), never inside the token. `annotationVersion`
95
+ * belongs to the annotated family's tokens only — each family's query
96
+ * schema enforces its own pin grammar.
97
+ */
98
+ const RenderTokenSchema = {
99
+ fields: [
100
+ "annotationVersion",
101
+ "background",
102
+ "contentVersion",
103
+ "format",
104
+ "quality",
105
+ "rotation",
106
+ "target.kind",
107
+ "target.rect.bottom",
108
+ "target.rect.left",
109
+ "target.rect.right",
110
+ "target.rect.top",
111
+ "viewport.kind",
112
+ "viewport.scale",
113
+ "viewport.width"
114
+ ],
115
+ maxLength: 512
116
+ };
117
+ /**
118
+ * Token for the versioned search endpoints (`/search/{rects,full}/data@…`).
119
+ * One token carries the whole cache key: the content epoch, the query
120
+ * (text/pattern rides base64-variant-encoded in `q` — the token value
121
+ * charset excludes free text), and the resume position. Canonical by
122
+ * construction: the codec sorts fields and the encoder omits every
123
+ * default, so equal searches produce byte-equal tokens — the property CDN
124
+ * cache hits live on. Mode is NOT a field; it is the endpoint (separate
125
+ * permission tiers must never share cache entries).
126
+ */
127
+ const SearchTokenSchema = {
128
+ fields: [
129
+ "epoch",
130
+ "matchCase",
131
+ "matchDiacritics",
132
+ "maxMatches",
133
+ "maxPages",
134
+ "q",
135
+ "regex",
136
+ "skip",
137
+ "startPage",
138
+ "wholeWord"
139
+ ],
140
+ maxLength: 2048
141
+ };
142
+ /**
143
+ * Token for the batch annotation-appearance render endpoint. Narrower than
144
+ * the page render token: appearance bitmaps are sized per annotation `/Rect`
145
+ * so there is no target/viewport — only a uniform `scale` and page
146
+ * `rotation`. Keyed by `annotationVersion` only (appearances do not depend on
147
+ * page base content). The cloud endpoint renders the Normal appearance only,
148
+ * so `modes` is intentionally absent here (it is a worker/local-only option).
149
+ */
150
+ const AnnotationAppearancesRenderTokenSchema = {
151
+ fields: [
152
+ "annotationVersion",
153
+ "format",
154
+ "quality",
155
+ "rotation",
156
+ "scale"
157
+ ],
158
+ maxLength: 256
159
+ };
160
+ //#endregion
161
+ //#region src/wire/tokens.ts
162
+ const encodeDocToken = (docVersion) => encodeToken(DocTokenSchema, { docVersion });
163
+ const decodeDocToken = (raw) => decodePositiveInteger(decodeToken(DocTokenSchema, raw).docVersion, "docVersion");
164
+ const encodeContentToken = (contentVersion) => encodeToken(ContentTokenSchema, { contentVersion });
165
+ const decodeContentToken = (raw) => decodePositiveInteger(decodeToken(ContentTokenSchema, raw).contentVersion, "contentVersion");
166
+ const encodeLayoutToken = (layoutVersion) => encodeToken(LayoutTokenSchema, { layoutVersion });
167
+ const decodeLayoutToken = (raw) => decodePositiveInteger(decodeToken(LayoutTokenSchema, raw).layoutVersion, "layoutVersion");
168
+ const encodeMetadataToken = (metadataVersion) => encodeToken(MetadataTokenSchema, { metadataVersion });
169
+ const decodeMetadataToken = (raw) => decodePositiveInteger(decodeToken(MetadataTokenSchema, raw).metadataVersion, "metadataVersion");
170
+ const encodeAnnotationToken = (annotationVersion) => encodeToken(AnnotationTokenSchema, { annotationVersion });
171
+ const decodeAnnotationToken = (raw) => decodePositiveInteger(decodeToken(AnnotationTokenSchema, raw).annotationVersion, "annotationVersion");
172
+ const encodeActionsToken = (actionsVersion) => encodeToken(ActionsTokenSchema, { actionsVersion });
173
+ const decodeActionsToken = (raw) => decodePositiveInteger(decodeToken(ActionsTokenSchema, raw).actionsVersion, "actionsVersion");
174
+ const encodeAttachmentsToken = (attachmentsVersion) => encodeToken(AttachmentsTokenSchema, { attachmentsVersion });
175
+ const decodeAttachmentsToken = (raw) => decodePositiveInteger(decodeToken(AttachmentsTokenSchema, raw).attachmentsVersion, "attachmentsVersion");
176
+ const encodeDownloadToken = (input) => encodeToken(DownloadTokenSchema, {
177
+ docVersion: input.docVersion,
178
+ mode: input.mode
179
+ });
180
+ const decodeDownloadToken = (raw) => {
181
+ const decoded = decodeToken(DownloadTokenSchema, raw);
182
+ return {
183
+ docVersion: decodePositiveInteger(decoded.docVersion, "docVersion"),
184
+ mode: decodePdfSaveMode(decoded.mode)
185
+ };
186
+ };
187
+ /**
188
+ * Encode a render token from a flat wire-shape input. The input is the
189
+ * output of `flatten(...)` over an SDK `PageImageOptions`-shaped object plus
190
+ * cache versions. Semantic invariants (viewport-kind XOR fields, per-family
191
+ * pin grammar — annotatedness itself is PATH-expressed, never a token key —
192
+ * target rect coherence) live in the per-family render query schemas —
193
+ * running them here would duplicate the spec.
194
+ */
195
+ const encodeRenderToken = (input) => encodeToken(RenderTokenSchema, input);
196
+ /**
197
+ * Decode a render token to a flat `Record<string, string>` of allowed
198
+ * fields. Use `unflatten(...)` + `PageImageOptionsWireSchema.parse(...)` to
199
+ * recover the nested SDK shape with full coercion + validation.
200
+ */
201
+ const decodeRenderToken = (raw) => decodeToken(RenderTokenSchema, raw);
202
+ /**
203
+ * Encode an annotation-appearance render token from a flat wire-shape input
204
+ * (the `flatten(...)` of an `AnnotationAppearanceImageOptions`-shaped object
205
+ * plus cache versions). Semantic validation lives in
206
+ * `AnnotationAppearancesQuerySchema`.
207
+ */
208
+ const encodeAnnotationAppearancesRenderToken = (input) => encodeToken(AnnotationAppearancesRenderTokenSchema, input);
209
+ /** Decode an annotation-appearance render token to a flat field map. */
210
+ const decodeAnnotationAppearancesRenderToken = (raw) => decodeToken(AnnotationAppearancesRenderTokenSchema, raw);
211
+ const encodeSearchToken = (input) => {
212
+ const q = input.query;
213
+ return encodeToken(SearchTokenSchema, {
214
+ epoch: input.epoch,
215
+ q: encodeTokenText(q.text),
216
+ regex: q.regex ? true : void 0,
217
+ matchCase: q.matchCase ? true : void 0,
218
+ matchDiacritics: q.matchDiacritics ? true : void 0,
219
+ wholeWord: q.wholeWord ? true : void 0,
220
+ startPage: input.startPage,
221
+ skip: input.skip > 0 ? input.skip : void 0,
222
+ maxPages: input.budget?.maxPages,
223
+ maxMatches: input.budget?.maxMatches
224
+ });
225
+ };
226
+ const decodeSearchToken = (raw) => {
227
+ const t = decodeToken(SearchTokenSchema, raw);
228
+ if (t.epoch === void 0) throw new Error("search token is missing \"epoch\"");
229
+ if (t.q === void 0) throw new Error("search token is missing \"q\"");
230
+ const query = {
231
+ text: decodeTokenText(t.q),
232
+ ...t.regex === "true" ? { regex: true } : {},
233
+ ...t.matchCase === "true" ? { matchCase: true } : {},
234
+ ...t.matchDiacritics === "true" ? { matchDiacritics: true } : {},
235
+ ...t.wholeWord === "true" ? { wholeWord: true } : {}
236
+ };
237
+ const maxPages = t.maxPages === void 0 ? void 0 : decodePositiveInteger(t.maxPages, "maxPages");
238
+ const maxMatches = t.maxMatches === void 0 ? void 0 : decodePositiveInteger(t.maxMatches, "maxMatches");
239
+ return {
240
+ epoch: t.epoch,
241
+ query,
242
+ ...t.startPage === void 0 ? {} : { startPage: decodePositiveInteger(t.startPage, "startPage") },
243
+ skip: t.skip === void 0 ? 0 : decodePositiveInteger(t.skip, "skip"),
244
+ ...maxPages !== void 0 || maxMatches !== void 0 ? { budget: {
245
+ ...maxPages !== void 0 ? { maxPages } : {},
246
+ ...maxMatches !== void 0 ? { maxMatches } : {}
247
+ } } : {}
248
+ };
249
+ };
250
+ const TOKEN_TEXT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.";
251
+ function encodeTokenText(text) {
252
+ const bytes = new TextEncoder().encode(text);
253
+ let out = "";
254
+ for (let i = 0; i < bytes.length; i += 3) {
255
+ const b0 = bytes[i];
256
+ const b1 = bytes[i + 1];
257
+ const b2 = bytes[i + 2];
258
+ out += TOKEN_TEXT_ALPHABET[b0 >> 2];
259
+ out += TOKEN_TEXT_ALPHABET[(b0 & 3) << 4 | (b1 ?? 0) >> 4];
260
+ if (b1 === void 0) break;
261
+ out += TOKEN_TEXT_ALPHABET[(b1 & 15) << 2 | (b2 ?? 0) >> 6];
262
+ if (b2 === void 0) break;
263
+ out += TOKEN_TEXT_ALPHABET[b2 & 63];
264
+ }
265
+ return out;
266
+ }
267
+ function decodeTokenText(encoded) {
268
+ if (encoded.length % 4 === 1) throw new Error("malformed token text");
269
+ const bytes = [];
270
+ let buffer = 0;
271
+ let bits = 0;
272
+ for (const ch of encoded) {
273
+ const value = TOKEN_TEXT_ALPHABET.indexOf(ch);
274
+ if (value < 0) throw new Error("malformed token text");
275
+ buffer = buffer << 6 | value;
276
+ bits += 6;
277
+ if (bits >= 8) {
278
+ bits -= 8;
279
+ bytes.push(buffer >> bits & 255);
280
+ }
281
+ }
282
+ return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(bytes));
283
+ }
284
+ function decodePositiveInteger(raw, field) {
285
+ if (raw === void 0 || !/^\d+$/.test(raw)) throw new Error(`token field "${field}" must be a positive integer`);
286
+ const value = Number(raw);
287
+ if (!Number.isInteger(value) || value <= 0) throw new Error(`token field "${field}" must be a positive integer`);
288
+ return value;
289
+ }
290
+ function decodePdfSaveMode(raw) {
291
+ if (raw === "incremental" || raw === "rewrite") return raw;
292
+ throw new Error(`token field "mode" must be "incremental" or "rewrite"`);
293
+ }
294
+ //#endregion
295
+ //#region src/wire/paths.ts
296
+ /**
297
+ * Single source of truth for cloud HTTP paths. Both @cloudpdf/engine and
298
+ * @cloudpdf/server import these so they cannot drift.
299
+ *
300
+ * **URL layout convention (paths v2)**
301
+ *
302
+ * Each resource type lives at its own distinct path prefix. This
303
+ * lets prefix-matching CDNs (Bunny, Cloud CDN, Azure FD) enforce
304
+ * per-resource scope at the edge — a Bunny token signed at
305
+ * `/v1/docs/{id}/render/pages/` can only authorize render bytes,
306
+ * never text or annotations.
307
+ *
308
+ * Shape:
309
+ * /v1/docs/{id} — doc root
310
+ * /v1/docs/{id}/manifest@{ver} — doc-level read
311
+ * /v1/docs/{id}/render/pages/{N}/data@{ver} — render is its own prefix
312
+ * /v1/docs/{id}/text/pages/{N}/data@{ver} — text is its own prefix
313
+ * /v1/docs/{id}/geometry/pages/{N}/data@{ver} — geometry is its own prefix
314
+ * /v1/docs/{id}/layers/{L}/manifest@{ver}
315
+ * /v1/docs/{id}/layers/{L}/metadata@{ver}
316
+ * /v1/docs/{id}/layers/{L}/render/pages/{N}/data@{ver}
317
+ * /v1/docs/{id}/layers/{L}/text/pages/{N}/data@{ver}
318
+ * /v1/docs/{id}/layers/{L}/geometry/pages/{N}/data@{ver}
319
+ * /v1/docs/{id}/layers/{L}/annotations/pages/{N}/items@{ver} — collection (read)
320
+ * /v1/docs/{id}/layers/{L}/annotations/pages/{N}/items — collection (create)
321
+ * /v1/docs/{id}/layers/{L}/annotations/pages/{N}/items/{key} — member
322
+ * /v1/docs/{id}/layers/{L}/annotations/pages/{N}/items/move — batch reorder
323
+ * /v1/docs/{id}/layers/{L}/pages/move — batch page reorder
324
+ * /v1/docs/{id}/layers/{L}/pages/rotate — batch absolute rotation
325
+ * /v1/docs/{id}/layers/{L}/pages/delete — batch page delete
326
+ * /v1/docs/{id}/layers/{L}/form — reconciled snapshot (read)
327
+ * /v1/docs/{id}/layers/{L}/form/fields — field collection (create)
328
+ * /v1/docs/{id}/layers/{L}/form/fields/{key} — field member (read/patch/delete)
329
+ * /v1/docs/{id}/layers/{L}/form/fields/{key}/value — value write (fill)
330
+ * /v1/docs/{id}/layers/{L}/form/fields/{key}/reset — reset to /DV (fill)
331
+ * /v1/docs/{id}/layers/{L}/form/fields/{key}/widgets — adopt a widget (attach)
332
+ * /v1/docs/{id}/layers/{L}/form/fields/{key}/widgets/detach — release a widget
333
+ * /v1/docs/{id}/layers/{L}/form/data — FDF/XFDF export (GET) / import (POST)
334
+ * /v1/docs/{id}/layers/{L}/form/repair — durable reconciliation
335
+ * /v1/docs/{id}/layers/{L}/download@{ver}
336
+ *
337
+ * `items` appears on both the read collection (`items@{ver}`) and
338
+ * the mutation surface (`items` POST, `items/{key}` PATCH/DELETE)
339
+ * for symmetry — `items@version` is the page's versioned annotation
340
+ * collection; `items/{key}` is one annotation inside it.
341
+ */
342
+ const DEFAULT_LAYER_NAME = "default";
343
+ const wirePaths = {
344
+ /** POST: grant document access/caching credentials for the current bearer. */
345
+ access: "/v1/access",
346
+ /**
347
+ * GET: open the document referenced by the doc-scoped JWT and
348
+ * return its `DocumentHead`. The server materialises the base
349
+ * PDF into its file cache and binds it to a worker the first
350
+ * time this is hit.
351
+ */
352
+ docHead: (docId) => `/v1/docs/${encodeURIComponent(docId)}/head`,
353
+ /**
354
+ * GET: layer-scoped head. The cloud SDK always uses a layer namespace;
355
+ * tokens without `layer_name` bind to `DEFAULT_LAYER_NAME`.
356
+ */
357
+ layerHead: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/head`,
358
+ /**
359
+ * GET: full document manifest at a specific `docVersion`. Content-
360
+ * addressed: the URL bytes are immutable for the lifetime of the
361
+ * version, so CDNs may cache `public, max-age=31536000, immutable`.
362
+ * A request whose `docVersion` mismatches the current version
363
+ * returns 404 — the SDK refetches `/head` to learn the new
364
+ * version, then re-requests the manifest at the new URL.
365
+ */
366
+ docManifest: (docId, docVersion) => `/v1/docs/${encodeURIComponent(docId)}/manifest@${encodeDocToken(docVersion)}`,
367
+ /**
368
+ * GET: full layer manifest at a specific layer document version.
369
+ * Never-mutated layers may fall through to the immutable base view;
370
+ * once a layer row exists, `layers.doc_version` and `layer_pages`
371
+ * drive the response.
372
+ */
373
+ layerManifest: (docId, layerName, docVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/manifest@${encodeDocToken(docVersion)}`,
374
+ /**
375
+ * GET: page-geometry list for the whole layer at a specific
376
+ * `layoutVersion`. Content-addressed; CDN may cache forever. The
377
+ * `layoutVersion` lives in the manifest (doc-level pointer) and bumps
378
+ * only on structural page ops. Stale-version requests 404 and the SDK's
379
+ * transparent retry walks `/head` -> `/manifest@docVersion=N` to learn
380
+ * the new `layoutVersion`.
381
+ */
382
+ layerLayout: (docId, layerName, layoutVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/layout@${encodeLayoutToken(layoutVersion)}`,
383
+ /**
384
+ * Immutable BASE page-geometry list (plane-scope model): the shared-URL
385
+ * variant a layout-inheriting layer resolves at — every visitor's page
386
+ * list is ONE CDN object served from the base worker session.
387
+ */
388
+ docLayout: (docId, layoutVersion) => `/v1/docs/${encodeURIComponent(docId)}/layout@${encodeLayoutToken(layoutVersion)}`,
389
+ layerLayoutCurrent: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/layout`,
390
+ /**
391
+ * GET: full document metadata for the layer at a specific
392
+ * `metadataVersion`. Content-addressed; CDN may cache forever. The
393
+ * `metadataVersion` lives in the manifest (doc-level pointer) and bumps
394
+ * only on metadata writes. Stale-version requests 404 and the SDK's
395
+ * transparent retry walks `/head` -> `/manifest@docVersion=N` to learn
396
+ * the new `metadataVersion`.
397
+ */
398
+ layerMetadata: (docId, layerName, metadataVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/metadata@${encodeMetadataToken(metadataVersion)}`,
399
+ layerMetadataCurrent: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/metadata`,
400
+ /** Immutable BASE metadata (plane-scope model): the shared-URL variant a
401
+ * metadata-inheriting layer resolves at. */
402
+ docMetadata: (docId, metadataVersion) => `/v1/docs/${encodeURIComponent(docId)}/metadata@${encodeMetadataToken(metadataVersion)}`,
403
+ /** Immutable catalog-owned actions, independently pinned in the manifest. */
404
+ layerActions: (docId, layerName, actionsVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/actions@${encodeActionsToken(actionsVersion)}`,
405
+ /** Immutable BASE catalog actions (plane-scope model): the shared-URL
406
+ * variant an actions-inheriting layer resolves at. */
407
+ docActions: (docId, actionsVersion) => `/v1/docs/${encodeURIComponent(docId)}/actions@${encodeActionsToken(actionsVersion)}`,
408
+ /** POST: rewrite the document Info dict for the layer (metadata edit). */
409
+ layerMetadataUpdate: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/metadata`,
410
+ /**
411
+ * Immutable BASE /EmbeddedFiles listing: the shared-URL variant an
412
+ * attachments-undiverged layer resolves at — every visitor's sidebar list
413
+ * is ONE CDN object served from the base worker session.
414
+ */
415
+ docAttachments: (docId, attachmentsVersion) => `/v1/docs/${encodeURIComponent(docId)}/attachments@${encodeAttachmentsToken(attachmentsVersion)}`,
416
+ /** Immutable BASE decoded bytes of one embedded file (twin of
417
+ * `layerAttachmentFile` — same capability tier split). */
418
+ docAttachmentFile: (docId, key, attachmentsVersion) => `/v1/docs/${encodeURIComponent(docId)}/attachment-files/${encodeTokenText(key)}/data@${encodeAttachmentsToken(attachmentsVersion)}`,
419
+ /** Immutable /EmbeddedFiles listing, pinned by `attachmentsVersion`. */
420
+ layerAttachments: (docId, layerName, attachmentsVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/attachments@${encodeAttachmentsToken(attachmentsVersion)}`,
421
+ /** POST: create a document-level embedded file (multipart mutation envelope). */
422
+ layerAttachmentsCollection: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/attachments`,
423
+ /** DELETE: remove a document-level embedded file by name-tree key. */
424
+ layerAttachmentItem: (docId, layerName, key) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/attachments/${encodeTokenText(key)}`,
425
+ /**
426
+ * Immutable decoded bytes of one document-level embedded file. Lives
427
+ * under its own `attachment-files` prefix — a stronger capability tier
428
+ * than the metadata listing, so a CDN credential for one can never
429
+ * authorize the other (the search-rects/search-full rule).
430
+ */
431
+ layerAttachmentFile: (docId, layerName, key, attachmentsVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/attachment-files/${encodeTokenText(key)}/data@${encodeAttachmentsToken(attachmentsVersion)}`,
432
+ /** Immutable decoded bytes of a FileAttachment annotation's embedded file. */
433
+ layerAnnotationFile: (docId, layerName, pageObjectNumber, annotKey, attachmentsVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/attachment-files/pages/${pageObjectNumber}/items/${encodeURIComponent(annotKey)}/data@${encodeAttachmentsToken(attachmentsVersion)}`,
434
+ /**
435
+ * Immutable BASE bytes of a FileAttachment annotation's embedded file
436
+ * (plane-scope model). Depends on the `annotations` plane (the annotation
437
+ * exists in this view) AND the `attachments` plane (the pin); the origin
438
+ * guard requires both inherited.
439
+ */
440
+ docAnnotationFile: (docId, pageObjectNumber, annotKey, attachmentsVersion) => `/v1/docs/${encodeURIComponent(docId)}/attachment-files/pages/${pageObjectNumber}/items/${encodeURIComponent(annotKey)}/data@${encodeAttachmentsToken(attachmentsVersion)}`,
441
+ /**
442
+ * GET: full plain-text extraction for a single page at a specific
443
+ * `contentVersion`. Content-addressed; CDN may cache forever.
444
+ * Stale-version requests return 404 and the SDK's transparent
445
+ * retry walks `/head` → `/manifest@docVersion=N` to learn the new
446
+ * `contentVersion`.
447
+ */
448
+ docPageText: (docId, pageObjectNumber, contentVersion) => `/v1/docs/${encodeURIComponent(docId)}/text/pages/${pageObjectNumber}/data@${encodeContentToken(contentVersion)}`,
449
+ layerPageText: (docId, layerName, pageObjectNumber, contentVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/text/pages/${pageObjectNumber}/data@${encodeContentToken(contentVersion)}`,
450
+ layerPageTextCurrent: (docId, layerName, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/text/pages/${pageObjectNumber}/data`,
451
+ docPageGeometry: (docId, pageObjectNumber, contentVersion) => `/v1/docs/${encodeURIComponent(docId)}/geometry/pages/${pageObjectNumber}/data@${encodeContentToken(contentVersion)}`,
452
+ docPageGeometryCurrent: (docId, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/geometry/pages/${pageObjectNumber}/data`,
453
+ docPageRender: (docId, pageObjectNumber, token) => `/v1/docs/${encodeURIComponent(docId)}/render/pages/${pageObjectNumber}/data@${encodeRenderToken(token)}`,
454
+ docPageRenderCurrent: (docId, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/render/pages/${pageObjectNumber}/data`,
455
+ /**
456
+ * Immutable BASE annotated render (plane-scope model). Its OWN path family,
457
+ * not a token flag under `/render/pages/`: an annotated render depends on
458
+ * `content + annotations`, an annotation-free one on `content` alone, and
459
+ * edge grants are prefix-scoped — the prefix law says a path's prefix must
460
+ * identify its full plane-dependency set. Annotatedness is therefore
461
+ * PATH-ONLY (the token/path law): the wire token has no
462
+ * `includeAnnotations` key at all; the annotated family's token carries
463
+ * `annotationVersion`, the free family's cannot.
464
+ */
465
+ docPageRenderAnnotated: (docId, pageObjectNumber, token) => `/v1/docs/${encodeURIComponent(docId)}/render/annotated/pages/${pageObjectNumber}/data@${encodeRenderToken(token)}`,
466
+ layerPageGeometry: (docId, layerName, pageObjectNumber, contentVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/geometry/pages/${pageObjectNumber}/data@${encodeContentToken(contentVersion)}`,
467
+ layerPageGeometryCurrent: (docId, layerName, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/geometry/pages/${pageObjectNumber}/data`,
468
+ layerPageRender: (docId, layerName, pageObjectNumber, token) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/render/pages/${pageObjectNumber}/data@${encodeRenderToken(token)}`,
469
+ layerPageRenderCurrent: (docId, layerName, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/render/pages/${pageObjectNumber}/data`,
470
+ /** Layer twin of `docPageRenderAnnotated` — the grammar is uniform:
471
+ * annotatedness is path-only at BOTH tiers. */
472
+ layerPageRenderAnnotated: (docId, layerName, pageObjectNumber, token) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/render/annotated/pages/${pageObjectNumber}/data@${encodeRenderToken(token)}`,
473
+ layerPageRenderAnnotatedCurrent: (docId, layerName, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/render/annotated/pages/${pageObjectNumber}/data`,
474
+ /**
475
+ * Immutable BASE annotation list for a single page (plane-scope model):
476
+ * the shared-URL variant an annotations-inheriting layer resolves at — a
477
+ * base's own annotations (weak-identity ones included) are simply visible
478
+ * through every pristine layer, so 1,000 visitors' sidebars are ONE CDN
479
+ * object served from the base worker session.
480
+ */
481
+ docPageAnnotations: (docId, pageObjectNumber, annotationVersion) => `/v1/docs/${encodeURIComponent(docId)}/annotations/pages/${pageObjectNumber}/items@${encodeAnnotationToken(annotationVersion)}`,
482
+ /** Immutable BASE appearance batch (twin of
483
+ * `layerPageAnnotationAppearances` — same `annotations` plane gate). */
484
+ docPageAnnotationAppearances: (docId, pageObjectNumber, token) => `/v1/docs/${encodeURIComponent(docId)}/annotations/pages/${pageObjectNumber}/appearances@${encodeAnnotationAppearancesRenderToken(token)}`,
485
+ /**
486
+ * GET: full annotation list for a single page at a specific
487
+ * `annotationVersion`. Same cache-control rules and 404-retry
488
+ * semantics as `docPageText`. The `items` suffix is the
489
+ * collection name — see file-level docstring.
490
+ */
491
+ layerPageAnnotations: (docId, layerName, pageObjectNumber, annotationVersion) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/annotations/pages/${pageObjectNumber}/items@${encodeAnnotationToken(annotationVersion)}`,
492
+ layerPageAnnotationsCurrent: (docId, layerName, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/annotations/pages/${pageObjectNumber}/items`,
493
+ /**
494
+ * GET: batch-rendered annotation appearance bitmaps for a single page as a
495
+ * `multipart/form-data` body. Sibling collection of `items` under the same
496
+ * `annotations/pages/{N}/` resource, so it shares the `annotations-read`
497
+ * gate (`doc.annotate.read`) and CDN coverage — reading an annotation lets
498
+ * you see its rendered appearance, the same boundary Adobe uses.
499
+ *
500
+ * Content-addressed via the appearance render token (`annotationVersion`
501
+ * plus render options like scale/format); CDN may cache forever. Appearance
502
+ * pixels depend only on the annotation `/AP` stream, so `contentVersion` is
503
+ * deliberately NOT part of the key.
504
+ */
505
+ layerPageAnnotationAppearances: (docId, layerName, pageObjectNumber, token) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/annotations/pages/${pageObjectNumber}/appearances@${encodeAnnotationAppearancesRenderToken(token)}`,
506
+ layerPageAnnotationAppearancesCurrent: (docId, layerName, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/annotations/pages/${pageObjectNumber}/appearances`,
507
+ layerPageAnnotationsCreate: (docId, layerName, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/annotations/pages/${pageObjectNumber}/items`,
508
+ layerAnnotationByKey: (docId, layerName, pageObjectNumber, key) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/annotations/pages/${pageObjectNumber}/items/${encodeURIComponent(key)}`,
509
+ layerPageAnnotationsMove: (docId, layerName, pageObjectNumber) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/annotations/pages/${pageObjectNumber}/items/move`,
510
+ /**
511
+ * GET: the reconciled form snapshot (field tree + widget joins) for the
512
+ * layer's CURRENT state. Forms are document-scoped (one AcroForm per
513
+ * document), so there is no per-page collection and — unlike annotations —
514
+ * no content-addressed `@version` variant: the snapshot is always served
515
+ * `no-store`. Mutation results carry the per-page `cacheDelta` that keeps
516
+ * annotation/render caches coherent when widget appearances change.
517
+ */
518
+ layerForm: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form`,
519
+ /** POST: create a field (optionally with styled widget placements). */
520
+ layerFormFields: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form/fields`,
521
+ /**
522
+ * Field member: GET (single field) / PATCH (updateField) / DELETE
523
+ * (deleteField + widget cascade). `fieldKey` is an encoded `FormFieldRef`
524
+ * (`encodeFieldRefKey`): `obj:12` or `fqn:billing.name`.
525
+ */
526
+ layerFormFieldByKey: (docId, layerName, fieldKey) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form/fields/${encodeURIComponent(fieldKey)}`,
527
+ /** POST: replace the field's value (`{ value: FormFieldValue }`). */
528
+ layerFormFieldValue: (docId, layerName, fieldKey) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form/fields/${encodeURIComponent(fieldKey)}/value`,
529
+ /** POST: reset the field to /DV (or clear). Empty body. */
530
+ layerFormFieldReset: (docId, layerName, fieldKey) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form/fields/${encodeURIComponent(fieldKey)}/reset`,
531
+ /** POST: adopt an inert widget annotation (`{ widget, onState? }`). */
532
+ layerFormFieldWidgets: (docId, layerName, fieldKey) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form/fields/${encodeURIComponent(fieldKey)}/widgets`,
533
+ /**
534
+ * POST: release a widget back to the inert annotation plane
535
+ * (`{ widget }`). An action POST rather than a member DELETE because a
536
+ * widget ref is a (page, annotation) pair — carrying it in the body keeps
537
+ * one codec instead of inventing a second composite key syntax.
538
+ */
539
+ layerFormFieldWidgetsDetach: (docId, layerName, fieldKey) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form/fields/${encodeURIComponent(fieldKey)}/widgets/detach`,
540
+ /**
541
+ * GET: serialized form data (`?format=fdf|xfdf`, default `xfdf`).
542
+ * POST: import an FDF/XFDF payload (raw bytes body; format sniffed
543
+ * server-side unless `?format=` pins it).
544
+ */
545
+ layerFormData: (docId, layerName, format) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form/data${format ? `?format=${format}` : ""}`,
546
+ /** POST: durable reconciliation (`{ bakeAppearances? }`). */
547
+ layerFormRepair: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form/repair`,
548
+ layerFormEffects: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/form/effects`,
549
+ /**
550
+ * GET: one budgeted search slice, versioned form. The token
551
+ * (`encodeSearchToken`) IS the cache key: content epoch + query +
552
+ * position. Immutable; CDN may cache forever. Mode is the path — rects
553
+ * and full are separate resources so permission tiers never share
554
+ * cache entries (`'rects'` needs `doc.text.search`; `'full'` also
555
+ * needs `doc.text.copy`).
556
+ */
557
+ layerSearchRects: (docId, layerName, token) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/search/rects/data@${token}`,
558
+ layerSearchFull: (docId, layerName, token) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/search/full/data@${token}`,
559
+ /**
560
+ * GET: unversioned form — same fields as flat query params (`q` as
561
+ * plain text), served from the CURRENT content, always `no-store`.
562
+ * The debug/simple-client variant; the SDK uses the versioned form.
563
+ */
564
+ layerSearchRectsCurrent: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/search/rects/data`,
565
+ layerSearchFullCurrent: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/search/full/data`,
566
+ layerPagesMove: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/pages/move`,
567
+ layerPagesRotate: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/pages/rotate`,
568
+ layerPagesDelete: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/pages/delete`,
569
+ layerPagesFlatten: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/pages/flatten`,
570
+ layerRedactionsApply: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/redactions/apply`,
571
+ layerEvents: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/events`,
572
+ layerDownload: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/download`,
573
+ layerDownloadVersioned: (docId, layerName, token) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/download@${encodeDownloadToken(token)}`,
574
+ /**
575
+ * Weak-annotation-sessions: pluralized in v2 so the collection
576
+ * lives at `/weak-annotation-sessions` and members at
577
+ * `/weak-annotation-sessions/{sessionId}` — REST-conventional.
578
+ */
579
+ layerWeakAnnotationSession: (docId, layerName) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/weak-annotation-sessions`,
580
+ layerWeakAnnotationSessionHeartbeat: (docId, layerName, sessionId) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/weak-annotation-sessions/${encodeURIComponent(sessionId)}/heartbeat`,
581
+ layerWeakAnnotationSessionPages: (docId, layerName, sessionId) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/weak-annotation-sessions/${encodeURIComponent(sessionId)}/pages`,
582
+ layerWeakAnnotationSessionRelease: (docId, layerName, sessionId) => `/v1/docs/${encodeURIComponent(docId)}/layers/${encodeURIComponent(layerName)}/weak-annotation-sessions/${encodeURIComponent(sessionId)}`,
583
+ /**
584
+ * POST: pre-warm the doc cache + worker open before any user
585
+ * request lands. Body is `{ docId }`. Doc-scoped token required.
586
+ */
587
+ docWarm: "/v1/warm"
588
+ };
589
+ //#endregion
590
+ //#region src/wire/resources.ts
591
+ /**
592
+ * URL layout (paths v2): each resource type lives at a distinct path
593
+ * prefix so prefix-matching CDNs (Bunny, Cloud CDN, Azure FD) can
594
+ * enforce per-resource scope at the edge. See wire/paths.ts for the
595
+ * full shape and rationale.
596
+ */
597
+ const DOC_RESOURCES = {
598
+ head: {
599
+ id: "head",
600
+ pathPattern: "/v1/docs/{docId}/head",
601
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/head`,
602
+ pathPrefix: "/v1/docs/{docId}/head",
603
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/head`,
604
+ requirement: {
605
+ kind: "single",
606
+ capability: "doc.open"
607
+ },
608
+ routeKind: "origin",
609
+ cdnCacheable: false
610
+ },
611
+ manifest: {
612
+ id: "manifest",
613
+ pathPattern: "/v1/docs/{docId}/manifest@*",
614
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/manifest@*`,
615
+ pathPrefix: "/v1/docs/{docId}/manifest@",
616
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/manifest@`,
617
+ requirement: {
618
+ kind: "single",
619
+ capability: "doc.open"
620
+ },
621
+ routeKind: "versioned-read",
622
+ cdnCacheable: true
623
+ },
624
+ "layer-manifest": {
625
+ id: "layer-manifest",
626
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/manifest@*",
627
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/manifest@*`,
628
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/manifest@",
629
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/manifest@`,
630
+ requirement: {
631
+ kind: "single",
632
+ capability: "doc.open"
633
+ },
634
+ routeKind: "versioned-read",
635
+ cdnCacheable: true
636
+ },
637
+ "page-render": {
638
+ id: "page-render",
639
+ pathPattern: "/v1/docs/{docId}/render/pages/*/data@*",
640
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/render/pages/*/data@*`,
641
+ pathPrefix: "/v1/docs/{docId}/render/pages/",
642
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/render/pages/`,
643
+ requirement: {
644
+ kind: "single",
645
+ capability: "doc.render"
646
+ },
647
+ routeKind: "versioned-read",
648
+ cdnCacheable: true
649
+ },
650
+ "page-render-annotated": {
651
+ id: "page-render-annotated",
652
+ pathPattern: "/v1/docs/{docId}/render/annotated/pages/*/data@*",
653
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/render/annotated/pages/*/data@*`,
654
+ pathPrefix: "/v1/docs/{docId}/render/annotated/pages/",
655
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/render/annotated/pages/`,
656
+ requirement: {
657
+ kind: "single",
658
+ capability: "doc.render"
659
+ },
660
+ routeKind: "versioned-read",
661
+ cdnCacheable: true
662
+ },
663
+ "page-annotations": {
664
+ id: "page-annotations",
665
+ pathPattern: "/v1/docs/{docId}/annotations/pages/*/items@*",
666
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/annotations/pages/*/items@*`,
667
+ pathPrefix: "/v1/docs/{docId}/annotations/pages/",
668
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/annotations/pages/`,
669
+ requirement: {
670
+ kind: "single",
671
+ capability: "doc.annotate.read"
672
+ },
673
+ routeKind: "versioned-read",
674
+ cdnCacheable: true
675
+ },
676
+ layout: {
677
+ id: "layout",
678
+ pathPattern: "/v1/docs/{docId}/layout@*",
679
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/layout@*`,
680
+ pathPrefix: "/v1/docs/{docId}/layout@",
681
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/layout@`,
682
+ requirement: {
683
+ kind: "single",
684
+ capability: "doc.open"
685
+ },
686
+ routeKind: "versioned-read",
687
+ cdnCacheable: true
688
+ },
689
+ metadata: {
690
+ id: "metadata",
691
+ pathPattern: "/v1/docs/{docId}/metadata@*",
692
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/metadata@*`,
693
+ pathPrefix: "/v1/docs/{docId}/metadata@",
694
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/metadata@`,
695
+ requirement: {
696
+ kind: "single",
697
+ capability: "doc.open"
698
+ },
699
+ routeKind: "versioned-read",
700
+ cdnCacheable: true
701
+ },
702
+ actions: {
703
+ id: "actions",
704
+ pathPattern: "/v1/docs/{docId}/actions@*",
705
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/actions@*`,
706
+ pathPrefix: "/v1/docs/{docId}/actions@",
707
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/actions@`,
708
+ requirement: {
709
+ kind: "single",
710
+ capability: "doc.open"
711
+ },
712
+ routeKind: "versioned-read",
713
+ cdnCacheable: true
714
+ },
715
+ "layer-layout": {
716
+ id: "layer-layout",
717
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/layout@*",
718
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/layout@*`,
719
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/layout@",
720
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/layout@`,
721
+ requirement: {
722
+ kind: "single",
723
+ capability: "doc.open"
724
+ },
725
+ routeKind: "versioned-read",
726
+ cdnCacheable: true
727
+ },
728
+ "layer-metadata": {
729
+ id: "layer-metadata",
730
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/metadata@*",
731
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/metadata@*`,
732
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/metadata@",
733
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/metadata@`,
734
+ requirement: {
735
+ kind: "single",
736
+ capability: "doc.open"
737
+ },
738
+ routeKind: "versioned-read",
739
+ cdnCacheable: true
740
+ },
741
+ "layer-actions": {
742
+ id: "layer-actions",
743
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/actions@*",
744
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/actions@*`,
745
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/actions@",
746
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/actions@`,
747
+ requirement: {
748
+ kind: "single",
749
+ capability: "doc.open"
750
+ },
751
+ routeKind: "versioned-read",
752
+ cdnCacheable: true
753
+ },
754
+ attachments: {
755
+ id: "attachments",
756
+ pathPattern: "/v1/docs/{docId}/attachments@*",
757
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/attachments@*`,
758
+ pathPrefix: "/v1/docs/{docId}/attachments@",
759
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/attachments@`,
760
+ requirement: {
761
+ kind: "single",
762
+ capability: "doc.open"
763
+ },
764
+ routeKind: "versioned-read",
765
+ cdnCacheable: true
766
+ },
767
+ "attachment-files": {
768
+ id: "attachment-files",
769
+ pathPattern: "/v1/docs/{docId}/attachment-files/*",
770
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/attachment-files/*`,
771
+ pathPrefix: "/v1/docs/{docId}/attachment-files/",
772
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/attachment-files/`,
773
+ requirement: {
774
+ kind: "single",
775
+ capability: "doc.download"
776
+ },
777
+ routeKind: "versioned-read",
778
+ cdnCacheable: true
779
+ },
780
+ "layer-attachments": {
781
+ id: "layer-attachments",
782
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/attachments@*",
783
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/attachments@*`,
784
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/attachments@",
785
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/attachments@`,
786
+ requirement: {
787
+ kind: "single",
788
+ capability: "doc.open"
789
+ },
790
+ routeKind: "versioned-read",
791
+ cdnCacheable: true
792
+ },
793
+ "layer-attachment-files": {
794
+ id: "layer-attachment-files",
795
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/attachment-files/*",
796
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/attachment-files/*`,
797
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/attachment-files/",
798
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/attachment-files/`,
799
+ requirement: {
800
+ kind: "single",
801
+ capability: "doc.download"
802
+ },
803
+ routeKind: "versioned-read",
804
+ cdnCacheable: true
805
+ },
806
+ "layer-page-render": {
807
+ id: "layer-page-render",
808
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/render/pages/*/data@*",
809
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/render/pages/*/data@*`,
810
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/render/pages/",
811
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/render/pages/`,
812
+ requirement: {
813
+ kind: "single",
814
+ capability: "doc.render"
815
+ },
816
+ routeKind: "versioned-read",
817
+ cdnCacheable: true
818
+ },
819
+ "layer-page-render-annotated": {
820
+ id: "layer-page-render-annotated",
821
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/render/annotated/pages/*/data@*",
822
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/render/annotated/pages/*/data@*`,
823
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/render/annotated/pages/",
824
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/render/annotated/pages/`,
825
+ requirement: {
826
+ kind: "single",
827
+ capability: "doc.render"
828
+ },
829
+ routeKind: "versioned-read",
830
+ cdnCacheable: true
831
+ },
832
+ "page-text": {
833
+ id: "page-text",
834
+ pathPattern: "/v1/docs/{docId}/text/pages/*/data@*",
835
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/text/pages/*/data@*`,
836
+ pathPrefix: "/v1/docs/{docId}/text/pages/",
837
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/text/pages/`,
838
+ requirement: {
839
+ kind: "single",
840
+ capability: "doc.text.copy"
841
+ },
842
+ routeKind: "versioned-read",
843
+ cdnCacheable: true
844
+ },
845
+ "layer-page-text": {
846
+ id: "layer-page-text",
847
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/text/pages/*/data@*",
848
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/text/pages/*/data@*`,
849
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/text/pages/",
850
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/text/pages/`,
851
+ requirement: {
852
+ kind: "single",
853
+ capability: "doc.text.copy"
854
+ },
855
+ routeKind: "versioned-read",
856
+ cdnCacheable: true
857
+ },
858
+ "page-geometry": {
859
+ id: "page-geometry",
860
+ pathPattern: "/v1/docs/{docId}/geometry/pages/*/data@*",
861
+ resolvePathPattern: (docId) => `/v1/docs/${docId}/geometry/pages/*/data@*`,
862
+ pathPrefix: "/v1/docs/{docId}/geometry/pages/",
863
+ resolvePathPrefix: (docId) => `/v1/docs/${docId}/geometry/pages/`,
864
+ requirement: {
865
+ kind: "single",
866
+ capability: "doc.text.select"
867
+ },
868
+ routeKind: "versioned-read",
869
+ cdnCacheable: true
870
+ },
871
+ "layer-page-geometry": {
872
+ id: "layer-page-geometry",
873
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/geometry/pages/*/data@*",
874
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/geometry/pages/*/data@*`,
875
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/geometry/pages/",
876
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/geometry/pages/`,
877
+ requirement: {
878
+ kind: "single",
879
+ capability: "doc.text.select"
880
+ },
881
+ routeKind: "versioned-read",
882
+ cdnCacheable: true
883
+ },
884
+ "layer-search-rects": {
885
+ id: "layer-search-rects",
886
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/search/rects/data@*",
887
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/search/rects/data@*`,
888
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/search/rects/data@",
889
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/search/rects/data@`,
890
+ requirement: {
891
+ kind: "single",
892
+ capability: "doc.text.search"
893
+ },
894
+ routeKind: "versioned-read",
895
+ cdnCacheable: true
896
+ },
897
+ "layer-search-full": {
898
+ id: "layer-search-full",
899
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/search/full/data@*",
900
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/search/full/data@*`,
901
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/search/full/data@",
902
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/search/full/data@`,
903
+ requirement: {
904
+ kind: "all",
905
+ capabilities: ["doc.text.search", "doc.text.copy"]
906
+ },
907
+ routeKind: "versioned-read",
908
+ cdnCacheable: true
909
+ },
910
+ "annotations-read": {
911
+ id: "annotations-read",
912
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/annotations/pages/*/items@*",
913
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/annotations/pages/*/items@*`,
914
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/annotations/pages/",
915
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/annotations/pages/`,
916
+ requirement: {
917
+ kind: "single",
918
+ capability: "doc.annotate.read"
919
+ },
920
+ routeKind: "versioned-read",
921
+ cdnCacheable: true
922
+ },
923
+ "download-current": {
924
+ id: "download-current",
925
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/download",
926
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/download`,
927
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/download",
928
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/download`,
929
+ requirement: {
930
+ kind: "single",
931
+ capability: "doc.download"
932
+ },
933
+ routeKind: "origin",
934
+ cdnCacheable: false
935
+ },
936
+ "download-versioned": {
937
+ id: "download-versioned",
938
+ pathPattern: "/v1/docs/{docId}/layers/{layerName}/download@*",
939
+ resolvePathPattern: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/download@*`,
940
+ pathPrefix: "/v1/docs/{docId}/layers/{layerName}/download@",
941
+ resolvePathPrefix: (docId, layerName = "default") => `/v1/docs/${docId}/layers/${layerName}/download@`,
942
+ requirement: {
943
+ kind: "single",
944
+ capability: "doc.download"
945
+ },
946
+ routeKind: "versioned-read",
947
+ cdnCacheable: true
948
+ }
949
+ };
950
+ /**
951
+ * True iff the scope grants access to the named resource.
952
+ *
953
+ * Looks up the resource descriptor, then dispatches to
954
+ * `checkCapability` or `checkAnyCapability` based on the requirement
955
+ * shape. Used by both server route guards and the CDN signer.
956
+ */
957
+ function checkResourceAccess(resourceId, rawScope, pdfBits) {
958
+ const r = DOC_RESOURCES[resourceId];
959
+ switch (r.requirement.kind) {
960
+ case "single": return checkCapability(r.requirement.capability, rawScope, pdfBits);
961
+ case "any": return checkAnyCapability(r.requirement.capabilities, rawScope, pdfBits);
962
+ case "all": return r.requirement.capabilities.every((cap) => checkCapability(cap, rawScope, pdfBits));
963
+ }
964
+ }
965
+ //#endregion
966
+ //#region src/wire/cdn/coverage.ts
967
+ /**
968
+ * Enumerate the CDN-cacheable resources the scope can access. Returns
969
+ * one {@link CdnCoverageEntry} per granted cacheable resource, carrying
970
+ * both the pattern and the prefix so each adapter can pick the
971
+ * projection it needs.
972
+ *
973
+ * Resources that are not cacheable (head, download-current) are
974
+ * filtered out automatically — they're still gated by their
975
+ * `requirement` at the origin route, but the CDN never gets a
976
+ * credential for them.
977
+ *
978
+ * The URL restructure (paths v2) guarantees each cacheable resource
979
+ * type has a distinct prefix, so prefix-matching adapters get
980
+ * per-resource scope enforcement at the edge — a Bunny token signed
981
+ * at `/v1/docs/{id}/render/pages/` can only authorize render bytes,
982
+ * never text or annotations.
983
+ */
984
+ /**
985
+ * Plane map: the planes each DOC-LEVEL shared resource depends on. A
986
+ * layer token's edge credential covers a resource's prefix iff EVERY listed
987
+ * plane is inherited (`'base'`) in the caller's scopes — the same condition
988
+ * the origin guard enforces (origin is the truth; this grant is the
989
+ * TTL-bounded optimization). Resources absent from this map (head, manifest,
990
+ * every layer-scoped resource) are not plane-gated.
991
+ *
992
+ * `attachment-files` deliberately lists only `attachments` even though its
993
+ * prefix also serves FileAttachment-annotation bytes (an `annotations`-plane
994
+ * read): the origin guard on that route additionally requires `annotations`
995
+ * inherited, and withholding the whole prefix on annotation divergence would
996
+ * break plain attachment-file sharing for the most common divergence. The
997
+ * residual is a warm-edge window bounded by the grant TTL — the same class
998
+ * as every edge grant.
999
+ */
1000
+ const RESOURCE_PLANES = {
1001
+ "page-render": ["content"],
1002
+ "page-text": ["content"],
1003
+ "page-geometry": ["content"],
1004
+ "page-render-annotated": ["content", "annotations"],
1005
+ "page-annotations": ["annotations"],
1006
+ layout: ["layout"],
1007
+ metadata: ["metadata"],
1008
+ actions: ["actions"],
1009
+ attachments: ["attachments"],
1010
+ "attachment-files": ["attachments"]
1011
+ };
1012
+ function cdnCoverageForScope(rawScope, pdfBits, context) {
1013
+ const out = [];
1014
+ const scopes = context.scopes;
1015
+ for (const id of Object.keys(DOC_RESOURCES)) {
1016
+ const r = DOC_RESOURCES[id];
1017
+ if (!r.cdnCacheable) continue;
1018
+ const planes = RESOURCE_PLANES[id];
1019
+ if (planes && scopes && planes.some((plane) => scopes[plane] === "layer")) continue;
1020
+ if (!checkResourceAccess(id, rawScope, pdfBits)) continue;
1021
+ out.push({
1022
+ resourceId: id,
1023
+ pathPattern: r.resolvePathPattern(context.docId, context.layerName),
1024
+ pathPrefix: r.resolvePathPrefix(context.docId, context.layerName)
1025
+ });
1026
+ }
1027
+ return out;
1028
+ }
1029
+ //#endregion
1030
+ //#region src/wire/cdn/applyCdnAccess.ts
1031
+ /**
1032
+ * Pure function that turns a request path + a `CdnAccessInfo` block
1033
+ * into the final URL the SDK should hit, plus any header/cookie side
1034
+ * effects to attach. Used by both:
1035
+ * - the @cloudpdf/engine HttpClient (to actually route requests through
1036
+ * the CDN edge when an /access response says it can)
1037
+ * - the smoke/diagnostic inspector (to PREVIEW what the SDK would do
1038
+ * for a given path, with no network)
1039
+ *
1040
+ * Keeping this in engine-core means cloud-side request routing and
1041
+ * server-side test fixtures use the SAME algorithm. If a regression
1042
+ * sneaks in, both ends break together.
1043
+ *
1044
+ * Selection rules
1045
+ * ───────────────
1046
+ * 1. Resolve the request path to a `DocResourceId` via DOC_RESOURCES
1047
+ * (longest-prefix-match on the resolved `pathPrefix`). Routes
1048
+ * that don't map to a known resource (admin, /access itself) get
1049
+ * `routedToCdn: false` and stay on origin.
1050
+ * 2. If `baseUrlOverrides[resourceId]` exists, swap the request's
1051
+ * origin to that CDN origin. If absent, the request stays on
1052
+ * origin — that's how scope narrowing works at the edge: the
1053
+ * server only emits overrides for resources the caller's scope
1054
+ * grants.
1055
+ * 3. Find the LONGEST-matching `signedPathPolicies` entry (its
1056
+ * `pathPrefix` is a prefix of the request path). Append its
1057
+ * `queryParams`. No match falls back to `signedQueryParams` if
1058
+ * the adapter emitted a global token.
1059
+ * 4. `signedCookies` and `authHeader` are surfaced verbatim — the
1060
+ * HttpClient sets cookies once per session and applies the
1061
+ * header to every CDN request.
1062
+ */
1063
+ function applyCdnAccess(input) {
1064
+ const { path, originUrl, docId, layerName, cdn } = input;
1065
+ const resourceId = resolveResourceIdForPath(path, docId, layerName ?? "default");
1066
+ if (!cdn || cdn.adapter === "none") return {
1067
+ url: `${trimRight(originUrl)}${path}`,
1068
+ resourceId,
1069
+ routedToCdn: false,
1070
+ matchedPolicy: null,
1071
+ authHeader: null,
1072
+ cookies: null,
1073
+ fallbackReason: cdn ? "CDN signer is `none` — every request stays on origin." : "No CdnAccessInfo on this session — request stays on origin."
1074
+ };
1075
+ if (!resourceId) return {
1076
+ url: `${trimRight(originUrl)}${path}`,
1077
+ resourceId: null,
1078
+ routedToCdn: false,
1079
+ matchedPolicy: null,
1080
+ authHeader: null,
1081
+ cookies: null,
1082
+ fallbackReason: "Request path does not match any known DOC_RESOURCES route."
1083
+ };
1084
+ const cdnOrigin = cdn.baseUrlOverrides?.[resourceId];
1085
+ if (!cdnOrigin) return {
1086
+ url: `${trimRight(originUrl)}${path}`,
1087
+ resourceId,
1088
+ routedToCdn: false,
1089
+ matchedPolicy: null,
1090
+ authHeader: null,
1091
+ cookies: null,
1092
+ fallbackReason: `No baseUrlOverrides[${resourceId}] — scope does not grant this resource through the CDN.`
1093
+ };
1094
+ const matchedPolicy = (cdn.signedPathPolicies ?? []).filter((p) => path.startsWith(p.pathPrefix)).sort((a, b) => b.pathPrefix.length - a.pathPrefix.length)[0];
1095
+ const url = new URL(`${trimRight(cdnOrigin)}${path}`);
1096
+ if (matchedPolicy) for (const [k, v] of Object.entries(matchedPolicy.queryParams)) url.searchParams.set(k, v);
1097
+ else if (cdn.signedQueryParams) for (const [k, v] of Object.entries(cdn.signedQueryParams)) url.searchParams.set(k, v);
1098
+ return {
1099
+ url: url.toString(),
1100
+ resourceId,
1101
+ routedToCdn: true,
1102
+ matchedPolicy: matchedPolicy ? {
1103
+ pathPrefix: matchedPolicy.pathPrefix,
1104
+ queryParams: matchedPolicy.queryParams
1105
+ } : null,
1106
+ authHeader: cdn.authHeader,
1107
+ cookies: cdn.signedCookies,
1108
+ fallbackReason: ""
1109
+ };
1110
+ }
1111
+ /**
1112
+ * Walk DOC_RESOURCES and pick the entry whose resolved `pathPrefix`
1113
+ * is a (longest) prefix of `path`. Returns null when no resource
1114
+ * matches — admin routes, /access, /warm fall into this bucket.
1115
+ *
1116
+ * Each cacheable resource has a DISTINCT prefix (paths v2 invariant),
1117
+ * so longest-match is deterministic.
1118
+ */
1119
+ function resolveResourceIdForPath(path, docId, layerName) {
1120
+ let best = null;
1121
+ for (const id of Object.keys(DOC_RESOURCES)) {
1122
+ const prefix = DOC_RESOURCES[id].resolvePathPrefix(docId, layerName);
1123
+ if (!path.startsWith(prefix)) continue;
1124
+ if (!best || prefix.length > best.prefixLength) best = {
1125
+ id,
1126
+ prefixLength: prefix.length
1127
+ };
1128
+ }
1129
+ return best?.id ?? null;
1130
+ }
1131
+ function trimRight(url) {
1132
+ return url.endsWith("/") ? url.slice(0, -1) : url;
1133
+ }
1134
+ //#endregion
1135
+ //#region src/wire/flatten.ts
1136
+ const PATH_SEPARATOR = ".";
1137
+ function flatten(value, prefix = "") {
1138
+ const out = {};
1139
+ flattenInto(value, prefix, out);
1140
+ return out;
1141
+ }
1142
+ function flattenInto(value, prefix, out) {
1143
+ if (value === void 0 || value === null) return;
1144
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1145
+ if (!prefix) throw new Error("flatten: root value must be an object, not a scalar");
1146
+ if (typeof value === "number" && !Number.isFinite(value)) throw new Error(`flatten: non-finite number at "${prefix}"`);
1147
+ out[prefix] = value;
1148
+ return;
1149
+ }
1150
+ if (Array.isArray(value)) throw new Error(`flatten: arrays are not supported (at "${prefix || "<root>"}")`);
1151
+ if (typeof value !== "object") throw new Error(`flatten: unsupported value type at "${prefix || "<root>"}"`);
1152
+ for (const [k, v] of Object.entries(value)) {
1153
+ if (k.includes(PATH_SEPARATOR)) throw new Error(`flatten: object key "${k}" must not contain "${PATH_SEPARATOR}"`);
1154
+ flattenInto(v, prefix ? `${prefix}${PATH_SEPARATOR}${k}` : k, out);
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Inverse of `flatten`. Takes a flat string-or-scalar map and reconstructs a
1159
+ * nested object using `.` as the path separator. Values are passed through
1160
+ * unchanged — downstream schema validation (e.g. Zod with `.coerce`) is
1161
+ * responsible for type coercion from `string` to numbers/booleans when the
1162
+ * input came from a token or query string.
1163
+ *
1164
+ * Throws on conflicting paths (e.g. having both `viewport` and
1165
+ * `viewport.kind` in the same input).
1166
+ */
1167
+ function unflatten(flat) {
1168
+ const root = {};
1169
+ for (const [key, value] of Object.entries(flat)) {
1170
+ if (value === void 0) continue;
1171
+ if (key.length === 0) throw new Error("unflatten: empty key");
1172
+ if (key.startsWith(PATH_SEPARATOR) || key.endsWith(PATH_SEPARATOR)) throw new Error(`unflatten: key "${key}" must not start or end with "${PATH_SEPARATOR}"`);
1173
+ if (key.includes(`${PATH_SEPARATOR}${PATH_SEPARATOR}`)) throw new Error(`unflatten: key "${key}" must not contain empty segments`);
1174
+ const path = key.split(PATH_SEPARATOR);
1175
+ let cur = root;
1176
+ for (let i = 0; i < path.length - 1; i++) {
1177
+ const seg = path[i];
1178
+ const existing = cur[seg];
1179
+ if (existing === void 0) {
1180
+ const next = {};
1181
+ cur[seg] = next;
1182
+ cur = next;
1183
+ continue;
1184
+ }
1185
+ if (existing === null || typeof existing !== "object" || Array.isArray(existing)) throw new Error(`unflatten: conflicting paths at "${path.slice(0, i + 1).join(PATH_SEPARATOR)}"`);
1186
+ cur = existing;
1187
+ }
1188
+ const leaf = path[path.length - 1];
1189
+ if (cur[leaf] !== void 0) throw new Error(`unflatten: duplicate key "${key}"`);
1190
+ cur[leaf] = value;
1191
+ }
1192
+ return root;
1193
+ }
1194
+ //#endregion
1195
+ //#region src/wire/renderOptionsCodec.ts
1196
+ /**
1197
+ * Project image render options plus cache versions into the flat wire shape
1198
+ * the render token encoder consumes. The output is a generic dotted-key map
1199
+ * (`viewport.kind`, `target.rect.left`, …) — the schema and codec never need
1200
+ * to know about specific option fields. Adding a new render option means
1201
+ * extending `PageImageOptions`, the render query schemas, and
1202
+ * `RenderTokenSchema.fields`; this function does not change.
1203
+ *
1204
+ * Token/path rule: tokens carry VERSION PINS and RENDER
1205
+ * PARAMETERS; anything that changes the artifact's plane-dependency set is
1206
+ * PATH-expressed. Annotatedness changes the planes (`content` vs
1207
+ * `content + annotations`), so the wire map never carries
1208
+ * `includeAnnotations` — the caller picks the path FAMILY
1209
+ * (`…/render/pages/` vs `…/render/annotated/pages/`) and passes
1210
+ * `annotationVersion` iff it chose the annotated one. Contradictory states
1211
+ * are unrepresentable; each family's query schema enforces its own pin
1212
+ * grammar structurally.
1213
+ *
1214
+ * Semantic validation (viewport-kind invariants, per-family pin presence,
1215
+ * rect coherence) lives in `PageRenderQuerySchema` /
1216
+ * `PageRenderAnnotatedQuerySchema` and runs when the resulting URL is
1217
+ * decoded server-side. Round-tripping (flatten → encode → decode →
1218
+ * unflatten → schema parse) recovers the original SDK options.
1219
+ */
1220
+ function renderImageOptionsToWire(options, versions) {
1221
+ const { includeAnnotations: _pathExpressed, ...wireOptions } = options;
1222
+ return flatten({
1223
+ ...wireOptions,
1224
+ contentVersion: versions.contentVersion,
1225
+ ...versions.annotationVersion !== void 0 ? { annotationVersion: versions.annotationVersion } : {}
1226
+ });
1227
+ }
1228
+ /**
1229
+ * Convenience: build the full encoded render token in one call. Equivalent
1230
+ * to `encodeRenderToken(renderImageOptionsToWire(options, versions))`.
1231
+ */
1232
+ function renderImageOptionsToToken(options, versions) {
1233
+ return encodeRenderToken(renderImageOptionsToWire(options, versions));
1234
+ }
1235
+ /**
1236
+ * Re-attach `includeAnnotations` onto the worker-side `PageRenderOptions`
1237
+ * shape. Pure shape transform; consumed by the server route after
1238
+ * `PageRenderQuerySchema` has produced the SDK-shaped options.
1239
+ */
1240
+ function pageRenderOptionsFromImageOptions(options, includeAnnotations) {
1241
+ return {
1242
+ ...options.target ? { target: options.target } : {},
1243
+ ...options.viewport ? { viewport: options.viewport } : {},
1244
+ ...options.rotation !== void 0 ? { rotation: options.rotation } : {},
1245
+ ...options.background !== void 0 ? { background: options.background } : {},
1246
+ includeAnnotations
1247
+ };
1248
+ }
1249
+ /**
1250
+ * Project annotation-appearance image options plus the annotation version
1251
+ * into the flat wire shape the appearance render token encoder consumes.
1252
+ * Mirrors {@link renderImageOptionsToWire}.
1253
+ */
1254
+ function annotationAppearancesImageOptionsToWire(options, versions) {
1255
+ return flatten({
1256
+ ...options,
1257
+ annotationVersion: versions.annotationVersion
1258
+ });
1259
+ }
1260
+ /** Convenience: build the full encoded appearance render token in one call. */
1261
+ function annotationAppearancesImageOptionsToToken(options, versions) {
1262
+ return encodeAnnotationAppearancesRenderToken(annotationAppearancesImageOptionsToWire(options, versions));
1263
+ }
1264
+ /**
1265
+ * Strip image-encoding fields, leaving the worker-side
1266
+ * `AnnotationAppearanceRenderOptions`. Pure shape transform consumed by the
1267
+ * server route after `AnnotationAppearancesQuerySchema` produces the
1268
+ * SDK-shaped options.
1269
+ */
1270
+ function annotationRenderOptionsFromImageOptions(options) {
1271
+ return {
1272
+ ...options.scale !== void 0 ? { scale: options.scale } : {},
1273
+ ...options.rotation !== void 0 ? { rotation: options.rotation } : {},
1274
+ ...options.modes ? { modes: options.modes } : {}
1275
+ };
1276
+ }
1277
+ //#endregion
1278
+ export { ANNOTATION_KINDS, AccessRequestSchema, AccessResponseSchema, ActionsTokenSchema, AnnotationAppearanceManifestSchema, AnnotationAppearancesQuerySchema, AnnotationAppearancesRenderTokenSchema, AnnotationBaseShape, AnnotationBorderStyleSchema, AnnotationCreateResultSchema, AnnotationDTOSchema, AnnotationDeleteResultSchema, AnnotationDraftBaseShape, AnnotationDraftSchema, AnnotationFlagsPartialSchema, AnnotationFlagsSchema, AnnotationListMutationMetaSchema, AnnotationListPageSnapshotSchema, AnnotationListSnapshotAllPagesSchema, AnnotationMoveResultSchema, AnnotationPatchBaseShape, AnnotationPatchSchema, AnnotationRefSchema, AnnotationStableIdSchema, AnnotationTokenSchema, AnnotationUpdateResultSchema, AppearanceOutcomeSchema, AttachmentCreateResultSchema, AttachmentDeleteResultSchema, AttachmentFileInfoSchema, AttachmentsCacheSchema, BlendModeSchema, CacheDeltaSchema, CachePinsSchema, CalloutLineSchema, CaretDTOSchema, CaretDraftSchema, CaretIntentSchema, CaretPatchSchema, CircleDTOSchema, CircleDraftSchema, CirclePatchSchema, ColorSchema, ContentTokenSchema, DEFAULT_LAYER_NAME, DOC_RESOURCES, DocTokenSchema, DocumentActionsSnapshotSchema, DocumentHeadSchema, DocumentManifestSchema, DocumentMetadataSchema, DocumentSecurityStateSchema, EmbeddedFileItemSchema, EmbeddedFileRefSchema, EngineErrorPayloadSchema, FileAttachmentDTOSchema, FileAttachmentIconSchema, FileAttachmentPatchSchema, FileAttachmentWireDraftSchema, FormDataFormatSchema, FormEffectSchema, FormEffectsResultSchema, FormFieldCreateResultSchema, FormFieldDTOSchema, FormFieldDeleteResultSchema, FormFieldDraftSchema, FormFieldFlagsSchema, FormFieldOptionInputSchema, FormFieldOptionSchema, FormFieldPatchSchema, FormFieldRefSchema, FormFieldUpdateResultSchema, FormFieldValueSchema, FormImportResultSchema, FormKindSchema, FormRepairResultSchema, FormSetValueResultSchema, FormSnapshotSchema, FormValueEntrySchema, FormWidgetLinkResultSchema, FormWidgetRefSchema, FreeTextDTOSchema, FreeTextDraftSchema, FreeTextIntentSchema, FreeTextPatchSchema, HighlightDTOSchema, HighlightDraftSchema, HighlightPatchSchema, InkDTOSchema, InkDraftSchema, InkIntentSchema, InkListSchema, InkPatchSchema, KIND_BY_SUBTYPE, LayerScopesSchema, LayoutTokenSchema, LineDTOSchema, LineDraftSchema, LineEndingSchema, LineEndingsSchema, LinePatchSchema, LinePointsSchema, LinkDTOSchema, LinkDraftSchema, LinkPatchSchema, ManifestPageSchema, MetadataPatchSchema, MetadataTokenSchema, MetadataUpdateResultSchema, MutationMetaSchema, NoteIconSchema, OpenDocumentResponseSchema, PageBoxesSchema, PageDeleteInputSchema, PageDeleteResultSchema, PageFlattenInputSchema, PageFlattenResultSchema, PageGeometrySnapshotSchema, PageLayoutSchema, PageListSnapshotSchema, PageMoveInputSchema, PageMoveResultSchema, PageNetworkRenderFormatSchema, PageRenderAnnotatedQuerySchema, PageRenderQuerySchema, PageRotateInputSchema, PageRotateResultSchema, PageStateSchema, PageStructureCacheSchema, PageTextSnapshotSchema, PdfActionNodeSchema, PdfActionTreeSchema, PdfActionTypeSchema, PdfAnnotationActionsSchema, PdfDestinationSchema, PdfFieldActionsSchema, PdfLinkTargetSchema, PdfLinkTargetWritableSchema, PdfPageActionsSchema, PdfPointSchema, PdfQuadSchema, PdfRectDifferencesSchema, PdfRectSchema, PdfRotationSchema, PdfSaveModeSchema, PdfSizeSchema, PointSchema, PolygonDTOSchema, PolygonDraftSchema, PolygonPatchSchema, PolylineDTOSchema, PolylineDraftSchema, PolylinePatchSchema, RectSchema, RedactionApplyResultSchema, RedactionApplyScopeSchema, RefetchReasonSchema, RenderTokenSchema, ResourceRefSchema, RevisionTokenSchema, SearchMatchSchema, SearchModeSchema, SearchQuerySchema, SearchRequestSchema, SearchSliceSchema, SearchSnippetSchema, SearchTokenSchema, SquareDTOSchema, SquareDraftSchema, SquarePatchSchema, SquigglyDTOSchema, SquigglyDraftSchema, SquigglyPatchSchema, StampDTOSchema, StampWireDraftSchema, StampWirePatchSchema, StandardFontSchema, StrikeoutDTOSchema, StrikeoutDraftSchema, StrikeoutIntentSchema, StrikeoutPatchSchema, TextAlignmentSchema, TextDTOSchema, TextDraftSchema, TextPatchSchema, ToggleFieldWidgetSchema, UnderlineDTOSchema, UnderlineDraftSchema, UnderlinePatchSchema, UnsupportedDTOSchema, UnsupportedDraftSchema, UnsupportedPatchSchema, WeakAnnotationSessionPagesRequestSchema, WeakAnnotationSessionResponseSchema, WidgetAppearanceSchema, WidgetPlacementSchema, WireAttachmentFileSchema, annotationAppearancesImageOptionsToToken, annotationAppearancesImageOptionsToWire, annotationRenderOptionsFromImageOptions, applyCdnAccess, cdnCoverageForScope, checkResourceAccess, decodeActionsToken, decodeAnnotationAppearancesRenderToken, decodeAnnotationToken, decodeAttachmentsToken, decodeContentToken, decodeDocToken, decodeDownloadToken, decodeLayoutToken, decodeMetadataToken, decodeRenderToken, decodeSearchToken, decodeToken, decodeTokenText, encodeActionsToken, encodeAnnotationAppearancesRenderToken, encodeAnnotationToken, encodeAttachmentsToken, encodeContentToken, encodeDocToken, encodeDownloadToken, encodeLayoutToken, encodeMetadataToken, encodeRenderToken, encodeSearchToken, encodeToken, encodeTokenText, flatten, pageRenderOptionsFromImageOptions, renderImageOptionsToToken, renderImageOptionsToWire, resolveResourceIdForPath, unflatten, wirePaths };
1279
+
1280
+ //# sourceMappingURL=wire.js.map