@embedpdf/core 3.0.0-next.14 → 3.0.0-next.16

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.
@@ -0,0 +1,124 @@
1
+ import { Ct as NoSettings, Dt as SettingsDeclaration, Ut as HostClock, bt as Unsubscribe, it as PluginContext, r as CapabilityToken } from "./types-CfnZ7go6.js";
2
+ import { Change, DocumentEvent, DocumentHandle, Engine, LOCAL_ENGINE_BRAND, PageLayout, PageRef, PdfRect } from "@embedpdf/engine-core/runtime";
3
+ //#region src/testing.d.ts
4
+ interface TestPage {
5
+ readonly ref: PageRef;
6
+ /** Page size in points (default 612 × 792, or the crop's extent); the crop box is `[0, 0, w, h]` unless given. */
7
+ readonly size?: {
8
+ readonly width: number;
9
+ readonly height: number;
10
+ };
11
+ readonly crop?: PdfRect;
12
+ readonly rotation?: PageLayout['rotation'];
13
+ readonly userUnit?: number;
14
+ readonly label?: string | null;
15
+ }
16
+ interface TestContextOptions<S, T extends object = NoSettings> {
17
+ /** The plugin id (`ctx.id`, error prefixes). Default `'test'`. */
18
+ readonly id?: string;
19
+ /** The instance's initial state; omit for a stateless plugin. */
20
+ readonly state?: S;
21
+ /**
22
+ * The plugin's settings, as its definition declares them; `ctx.settings()` reads them.
23
+ * Without them the context has none, and `ctx.settings()` throws.
24
+ */
25
+ readonly settings?: SettingsDeclaration<T>;
26
+ /** The document's pages; `document()` and `geometry` derive from them. */
27
+ readonly pages?: readonly TestPage[];
28
+ readonly documentId?: string;
29
+ /** Capabilities `get`/`tryGet` resolve, by token. `DocumentsToken` defaults to a
30
+ * registry holding this one document. */
31
+ readonly capabilities?: ReadonlyArray<readonly [CapabilityToken<unknown>, unknown]>;
32
+ /** The document handle members the test exercises (`security`, `page`, `events`, …),
33
+ * or a real engine document; `null` for a workspace plugin with no document. */
34
+ readonly doc?: Partial<DocumentHandle> | null;
35
+ readonly engine?: Partial<Engine>;
36
+ /** The host's time (`ctx.clock`). Default: `timerClock`, the environment's timers and no frames. */
37
+ readonly clock?: HostClock;
38
+ }
39
+ interface TestContext<S, T extends object = NoSettings> extends PluginContext<S, T> {
40
+ /** Every capability `get` can resolve — add or replace during a test. */
41
+ readonly capabilities: Map<CapabilityToken<unknown>, unknown>;
42
+ /** Observe the change stream: state updates and `notify` wake the listener. */
43
+ subscribe(listener: () => void): Unsubscribe;
44
+ /**
45
+ * What the kernel does after `create`: run the instance's `connect`, then
46
+ * start its mirrors' first loads. Returns the instance's api.
47
+ */
48
+ connect<C>(instance: {
49
+ api: C;
50
+ connect?: () => void;
51
+ }): C;
52
+ /** Deliver a confirmed document event, as the engine would (the default `doc.events`). */
53
+ emitDocumentEvent(event: DocumentEvent): void;
54
+ /** What `documents.download()` does first: run every `onSettle` flush and wait for it (settle.ts). */
55
+ settle(signal?: AbortSignal): Promise<void>;
56
+ /**
57
+ * What `documents.download()` does after settling: `read` the file inside every
58
+ * `aroundDownload` wrap, the first registered outermost.
59
+ */
60
+ download(read: () => Promise<Uint8Array>): Promise<Uint8Array>;
61
+ /**
62
+ * What a print does around the print dialog: `print` inside every `aroundPrint` wrap, the
63
+ * first registered outermost.
64
+ */
65
+ print(print: () => Promise<void>): Promise<void>;
66
+ /** Run the registered cleanups and abort the lifetime (the plugin's unmount). */
67
+ dispose(): Promise<void>;
68
+ }
69
+ declare function createTestContext<S = void, T extends object = NoSettings>(options?: TestContextOptions<S, T>): TestContext<S, T>;
70
+ /** A clock a test steps by hand (see {@link manualClock}). */
71
+ interface ManualClock {
72
+ /** The clock to hand a test context or a kernel. */
73
+ readonly clock: HostClock;
74
+ /** The current time in milliseconds. It starts at 0. */
75
+ readonly now: number;
76
+ /** How many timers and frame callbacks are waiting. */
77
+ readonly pending: {
78
+ readonly timers: number;
79
+ readonly frames: number;
80
+ };
81
+ /** Move time forward by `ms`, running the timers that fall due, earliest first. */
82
+ advance(ms: number): void;
83
+ /**
84
+ * Show a frame at `timeMs` (default: now): move time there, running the
85
+ * timers due by then, and run the callbacks waiting for a frame. What they
86
+ * schedule waits for the next frame.
87
+ */
88
+ frame(timeMs?: number): void;
89
+ }
90
+ /**
91
+ * Time that moves only when the test moves it. `frames: false` gives a host
92
+ * that never paints, as Node is.
93
+ */
94
+ declare function manualClock(options?: {
95
+ frames?: boolean;
96
+ }): ManualClock;
97
+ /**
98
+ * An engine whose documents hold every change until the test lets it go, so
99
+ * a test sees what the reads show while a change is on its way: wrap a real
100
+ * engine with `gate(engine)`, boot the kernel on `gate.engine`, set
101
+ * `holding`, then `release()` or `refuse()` the oldest held change.
102
+ */
103
+ /** A change a gate holds, with the promise its sender awaits. */
104
+ interface GatedChange {
105
+ readonly change: Change;
106
+ readonly options: unknown;
107
+ resolve(value: unknown): void;
108
+ reject(error: unknown): void;
109
+ }
110
+ interface Gate {
111
+ readonly engine: Engine;
112
+ /** The changes held so far, oldest first. */
113
+ readonly held: GatedChange[];
114
+ /** Whether changes are held; when not, they go straight to the engine. */
115
+ holding: boolean;
116
+ /** Send the oldest held change to the engine, and wait for its answer to land. */
117
+ release(): Promise<void>;
118
+ /** Refuse the oldest held change, as the engine would. */
119
+ refuse(error: unknown): void;
120
+ }
121
+ declare function gate(engine: Engine): Gate;
122
+ //#endregion
123
+ export { Gate, GatedChange, LOCAL_ENGINE_BRAND, ManualClock, TestContext, TestContextOptions, TestPage, createTestContext, gate, manualClock };
124
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1,445 @@
1
+ import { C as createDocumentChanges, M as createBurstGate, N as DocumentsToken, S as timerClock, T as PluginError, b as createStore, c as fieldPermissionDenied, d as findPage, f as pageOf, g as createMirror, h as createPageMirror, l as permissionDenied, m as createSerialQueue, o as runWrapped, p as cancellable, r as settle, t as createSettingsStore, u as sessionAllows, v as createLatestLane, w as createEventHook, x as instanceClock } from "./settings-CHdHuZ3k.js";
2
+ import { LOCAL_ENGINE_BRAND, pageRefsEqual, subscribeToType } from "@embedpdf/engine-core/runtime";
3
+ //#region src/testing.ts
4
+ const layoutOf = (page, index) => {
5
+ const size = page.size ?? (page.crop ? {
6
+ width: page.crop.right - page.crop.left,
7
+ height: page.crop.top - page.crop.bottom
8
+ } : {
9
+ width: 612,
10
+ height: 792
11
+ });
12
+ const crop = page.crop ?? {
13
+ left: 0,
14
+ bottom: 0,
15
+ right: size.width,
16
+ top: size.height
17
+ };
18
+ const visible = {
19
+ x: 0,
20
+ y: 0,
21
+ width: crop.right - crop.left,
22
+ height: crop.top - crop.bottom
23
+ };
24
+ return {
25
+ index,
26
+ ref: page.ref,
27
+ label: page.label ?? null,
28
+ size,
29
+ rotation: page.rotation ?? 0,
30
+ userUnit: page.userUnit ?? 1,
31
+ tabOrder: null,
32
+ boxes: {
33
+ media: { ...visible },
34
+ crop: { ...visible },
35
+ bleed: { ...visible },
36
+ trim: { ...visible },
37
+ art: { ...visible }
38
+ },
39
+ pdfCropBox: crop
40
+ };
41
+ };
42
+ /** A read-only document registry holding the test's one document. */
43
+ function testDocuments(meta) {
44
+ const info = {
45
+ id: meta.id,
46
+ status: "ready",
47
+ pageCount: meta.pageCount,
48
+ hasUnsavedChanges: false
49
+ };
50
+ const known = (id) => id === void 0 || id === meta.id;
51
+ const pagesOf = (id) => known(id) ? meta.pages : [];
52
+ const unsupported = (verb) => () => {
53
+ throw new PluginError("unsupported", "documents", `${verb} is not available in a test context`);
54
+ };
55
+ const never = () => () => {};
56
+ return {
57
+ getActiveId: () => meta.id,
58
+ getActive: () => info,
59
+ list: () => [info],
60
+ get: (id) => known(id) ? info : null,
61
+ has: (id) => known(id),
62
+ getCount: () => 1,
63
+ getOrder: () => [meta.id],
64
+ listPages: pagesOf,
65
+ getPage: (page, id) => findPage(pagesOf(id), page),
66
+ getPageIndex: (ref, id) => pagesOf(id).findIndex((page) => pageRefsEqual(page.ref, ref)),
67
+ getRevision: (id) => known(id) ? meta.revision : -1,
68
+ canDownload: () => true,
69
+ canPrint: () => false,
70
+ getPrintQuality: () => null,
71
+ canOpenFiles: () => false,
72
+ open: unsupported("open"),
73
+ retry: unsupported("retry"),
74
+ rename: unsupported("rename"),
75
+ openAll: unsupported("openAll"),
76
+ unlock: unsupported("unlock"),
77
+ close: unsupported("close"),
78
+ closeAll: unsupported("closeAll"),
79
+ setActive: unsupported("setActive"),
80
+ setOrder: unsupported("setOrder"),
81
+ move: unsupported("move"),
82
+ swap: unsupported("swap"),
83
+ download: unsupported("download"),
84
+ downloadLayer: unsupported("downloadLayer"),
85
+ preparePrint: unsupported("preparePrint"),
86
+ print: unsupported("print"),
87
+ onOpened: never,
88
+ onOpenFailed: never,
89
+ onLocked: never,
90
+ onClosed: never,
91
+ onActiveChanged: never,
92
+ onPagesChanged: never,
93
+ onUnsavedChangesChanged: never
94
+ };
95
+ }
96
+ function createTestContext(options = {}) {
97
+ const id = options.id ?? "test";
98
+ const documentId = options.documentId ?? "doc";
99
+ const report = (error) => console.error(`[${id}]`, error);
100
+ const store = createStore(report);
101
+ const instanceId = `${documentId}:${id}`;
102
+ const lease = store.lease(id, options.state, instanceId);
103
+ const cleanups = [];
104
+ const lifetime = new AbortController();
105
+ const settleFlushes = /* @__PURE__ */ new Set();
106
+ const downloadWraps = [];
107
+ const printWraps = [];
108
+ const pages = (options.pages ?? []).map(layoutOf);
109
+ const capabilities = new Map(options.capabilities ?? []);
110
+ const documentEvents = createEventHook(report);
111
+ const isRealHandle = options.doc != null && Object.getPrototypeOf(options.doc) !== Object.prototype;
112
+ const doc = options.doc === null ? null : isRealHandle ? options.doc : fakeDocument({
113
+ id: documentId,
114
+ events: {
115
+ subscribe: documentEvents.on,
116
+ on: (type, listener) => subscribeToType(documentEvents.on, type, listener),
117
+ lastServerId: () => null
118
+ },
119
+ security: {
120
+ allows: () => true,
121
+ allowsAnnotation: () => true,
122
+ allowsField: () => true
123
+ },
124
+ ...options.doc
125
+ });
126
+ const requireDoc = () => {
127
+ if (!doc) throw new PluginError("not-ready", id, "the test context has no document");
128
+ return doc;
129
+ };
130
+ const changes = createDocumentChanges({
131
+ handle: () => doc,
132
+ notify: store.notify,
133
+ report
134
+ });
135
+ settleFlushes.add(() => changes.settle());
136
+ cleanups.push(() => changes.close());
137
+ const bursts = createBurstGate(store);
138
+ const mirrors = [];
139
+ const mirrorEnvironment = () => ({
140
+ doc: requireDoc(),
141
+ changes,
142
+ notify: store.notify,
143
+ lifetime: lifetime.signal,
144
+ cell(name, initial) {
145
+ const cell = store.lease(`${id}/${name}`, initial, instanceId);
146
+ lifetime.signal.addEventListener("abort", () => cell.revoke(), { once: true });
147
+ return cell;
148
+ },
149
+ onDocumentEvent(listener) {
150
+ cleanups.push(requireDoc().events.subscribe(listener));
151
+ },
152
+ report
153
+ });
154
+ const meta = {
155
+ id: documentId,
156
+ instanceId,
157
+ pageCount: pages.length,
158
+ pages,
159
+ revision: 1,
160
+ renderPolicy: { kind: "continuous" },
161
+ hasUnsavedChanges: false
162
+ };
163
+ if (!capabilities.has(DocumentsToken)) capabilities.set(DocumentsToken, testDocuments(meta));
164
+ const notFound = (ref) => new PluginError("not-found", id, `page ${ref.objectNumber} is not in this document`);
165
+ const resolve = (token) => capabilities.has(token) ? capabilities.get(token) : null;
166
+ const require = (token) => {
167
+ const capability = resolve(token);
168
+ if (capability === null) throw new PluginError("not-found", id, `no capability for ${token.name} in the test context`);
169
+ return capability;
170
+ };
171
+ const queues = /* @__PURE__ */ new Map();
172
+ const lanes = /* @__PURE__ */ new Map();
173
+ const settingsStore = options.settings ? createSettingsStore(options.settings, store.notify, report) : null;
174
+ if (settingsStore) cleanups.push(() => settingsStore.dispose());
175
+ const settings = settingsStore?.forInstance((teardown) => cleanups.push(teardown)) ?? null;
176
+ const context = {
177
+ id,
178
+ instanceId: meta.instanceId,
179
+ engine: options.engine ?? {},
180
+ documentId,
181
+ get doc() {
182
+ return doc;
183
+ },
184
+ documentHandle: () => doc,
185
+ subscribe: store.subscribe,
186
+ state: {
187
+ get: () => lease.read(),
188
+ update: (transition, ...args) => {
189
+ lease.write(transition(lease.read(), ...args));
190
+ },
191
+ onChange: lease.onChange
192
+ },
193
+ settings: () => {
194
+ if (!settings) throw new Error(`[${id}] the test context declares no settings`);
195
+ return settings;
196
+ },
197
+ notify: store.notify,
198
+ watch: (select, handler, isEqual = Object.is) => {
199
+ let previous = select();
200
+ const off = store.subscribe(() => {
201
+ const next = select();
202
+ if (isEqual(previous, next)) return;
203
+ const prior = previous;
204
+ previous = next;
205
+ handler(next, prior);
206
+ });
207
+ cleanups.push(off);
208
+ return off;
209
+ },
210
+ mirror: (spec) => {
211
+ const controller = createMirror(spec, mirrorEnvironment());
212
+ mirrors.push(controller);
213
+ return controller.mirror;
214
+ },
215
+ pageMirror: (spec) => createPageMirror(spec, mirrorEnvironment()),
216
+ changes: changes.forPlugin(id),
217
+ document: () => meta,
218
+ get: require,
219
+ tryGet: resolve,
220
+ forDocument: (token) => require(token),
221
+ tryForDocument: (token) => resolve(token),
222
+ cleanup: (fn) => {
223
+ cleanups.push(fn);
224
+ },
225
+ clock: instanceClock(options.clock ?? timerClock, lifetime.signal),
226
+ onSettle: (flush) => {
227
+ settleFlushes.add(flush);
228
+ cleanups.push(() => {
229
+ settleFlushes.delete(flush);
230
+ });
231
+ },
232
+ aroundDownload: (wrap) => {
233
+ downloadWraps.push(wrap);
234
+ cleanups.push(() => {
235
+ const index = downloadWraps.indexOf(wrap);
236
+ if (index >= 0) downloadWraps.splice(index, 1);
237
+ });
238
+ },
239
+ aroundPrint: (wrap) => {
240
+ printWraps.push(wrap);
241
+ cleanups.push(() => {
242
+ const index = printWraps.indexOf(wrap);
243
+ if (index >= 0) printWraps.splice(index, 1);
244
+ });
245
+ },
246
+ events: { source: () => {
247
+ const hook = createEventHook((error) => console.error(`[${id}] listener failed:`, error));
248
+ cleanups.push(() => hook.dispose());
249
+ return hook;
250
+ } },
251
+ listen: (source, listener) => {
252
+ const off = typeof source === "function" ? source(listener) : source.subscribe(listener);
253
+ cleanups.push(off);
254
+ },
255
+ waitFor: (predicate, waitOptions) => new Promise((resolveWait, reject) => {
256
+ if (predicate()) return resolveWait();
257
+ const off = context.subscribe(() => {
258
+ if (!predicate()) return;
259
+ off();
260
+ resolveWait();
261
+ });
262
+ waitOptions?.signal?.addEventListener("abort", () => {
263
+ off();
264
+ reject(new PluginError("operation-cancelled", id, "waitFor cancelled"));
265
+ });
266
+ }),
267
+ serialQueue: (key = "default") => {
268
+ let queue = queues.get(key);
269
+ if (!queue) {
270
+ queue = createSerialQueue(id);
271
+ queues.set(key, queue);
272
+ }
273
+ return queue;
274
+ },
275
+ latest: (key) => {
276
+ let lane = lanes.get(key);
277
+ if (!lane) {
278
+ lane = createLatestLane(lifetime.signal, id, key);
279
+ lanes.set(key, lane);
280
+ }
281
+ return lane;
282
+ },
283
+ acquire: async (get, dispose) => {
284
+ const resource = await get(lifetime.signal);
285
+ if (lifetime.signal.aborted) {
286
+ await dispose(resource);
287
+ throw new PluginError("instance-closed", id, "acquired after close");
288
+ }
289
+ cleanups.push(() => dispose(resource));
290
+ return resource;
291
+ },
292
+ assertPageRef: (ref) => {
293
+ if (!pages.some((pageInfo) => pageRefsEqual(pageInfo.ref, ref))) throw notFound(ref);
294
+ },
295
+ getPage: (page) => findPage(pages, page),
296
+ pageOf: (page) => pageOf(pages, page, id),
297
+ allows: (permission) => sessionAllows(requireDoc().security, permission),
298
+ assertAllowed: (permission, operation) => {
299
+ if (!sessionAllows(requireDoc().security, permission)) throw permissionDenied(id, permission, operation);
300
+ },
301
+ allowsField: (action, field) => requireDoc().security.allowsField(action, { groupId: field.groupId }),
302
+ assertAllowedField: (action, field, operation) => {
303
+ if (!requireDoc().security.allowsField(action, { groupId: field.groupId })) throw fieldPermissionDenied(id, action, field, operation);
304
+ },
305
+ cancellable: (signal, task) => cancellable(id, signal, task),
306
+ capabilities,
307
+ connect: (instance) => {
308
+ instance.connect?.();
309
+ for (const mirror of mirrors.splice(0)) mirror.start();
310
+ return instance.api;
311
+ },
312
+ emitDocumentEvent: (event) => {
313
+ bursts.first(event);
314
+ documentEvents.emit(event);
315
+ bursts.last(event);
316
+ },
317
+ settle: (signal) => settle(settleFlushes, [signal, lifetime.signal], report),
318
+ download: (read) => runWrapped(downloadWraps, read),
319
+ print: (print) => runWrapped(printWraps, print),
320
+ dispose: async () => {
321
+ lifetime.abort();
322
+ for (const fn of cleanups.splice(0).reverse()) await fn();
323
+ }
324
+ };
325
+ return context;
326
+ }
327
+ /**
328
+ * Time that moves only when the test moves it. `frames: false` gives a host
329
+ * that never paints, as Node is.
330
+ */
331
+ function manualClock(options = {}) {
332
+ let now = 0;
333
+ let order = 0;
334
+ const timers = /* @__PURE__ */ new Map();
335
+ const frames = /* @__PURE__ */ new Map();
336
+ const nextDue = (until) => {
337
+ let found;
338
+ for (const [id, timer] of timers) {
339
+ if (timer.due > until) continue;
340
+ if (found === void 0 || timer.due < timers.get(found).due) found = id;
341
+ }
342
+ return found;
343
+ };
344
+ const advanceTo = (target) => {
345
+ if (target < now) throw new Error(`manualClock: time does not go back (${target} < ${now})`);
346
+ for (let id = nextDue(target); id !== void 0; id = nextDue(target)) {
347
+ const timer = timers.get(id);
348
+ timers.delete(id);
349
+ now = timer.due;
350
+ timer.run();
351
+ }
352
+ now = target;
353
+ };
354
+ return {
355
+ clock: {
356
+ now: () => now,
357
+ after(ms, run) {
358
+ const id = ++order;
359
+ timers.set(id, {
360
+ due: now + Math.max(0, ms),
361
+ run
362
+ });
363
+ return () => void timers.delete(id);
364
+ },
365
+ ...options.frames === false ? {} : { nextFrame(run) {
366
+ const id = ++order;
367
+ frames.set(id, run);
368
+ return () => void frames.delete(id);
369
+ } }
370
+ },
371
+ get now() {
372
+ return now;
373
+ },
374
+ get pending() {
375
+ return {
376
+ timers: timers.size,
377
+ frames: frames.size
378
+ };
379
+ },
380
+ advance: (ms) => advanceTo(now + ms),
381
+ frame(timeMs = now) {
382
+ advanceTo(timeMs);
383
+ const due = [...frames.values()];
384
+ frames.clear();
385
+ for (const run of due) run(now);
386
+ }
387
+ };
388
+ }
389
+ /**
390
+ * A faked document's members over the defaults every document has: calls'
391
+ * facts and working sets change no result, so `with` gives the same fake and
392
+ * a working set goes nowhere, unless the test fakes them.
393
+ */
394
+ function fakeDocument(members) {
395
+ const fake = {
396
+ setWorkingSet: () => {},
397
+ ...members
398
+ };
399
+ fake.with ?? (fake.with = () => fake);
400
+ return fake;
401
+ }
402
+ function gate(engine) {
403
+ const held = [];
404
+ let inner = null;
405
+ const state = {
406
+ engine: null,
407
+ held,
408
+ holding: false,
409
+ async release() {
410
+ const next = held.shift();
411
+ if (!next || !inner) throw new Error("nothing is held");
412
+ await inner.apply(next.change, next.options).then(next.resolve, next.reject);
413
+ },
414
+ refuse(error) {
415
+ const next = held.shift();
416
+ if (!next) throw new Error("nothing is held");
417
+ next.reject(error);
418
+ }
419
+ };
420
+ const wrap = (doc) => new Proxy(doc, { get(target, prop) {
421
+ if (prop === "apply") return (change, options) => state.holding ? new Promise((resolve, reject) => held.push({
422
+ change,
423
+ options,
424
+ resolve,
425
+ reject
426
+ })) : target.apply(change, options);
427
+ const value = Reflect.get(target, prop, target);
428
+ if (prop === "with" && typeof value === "function") return (...args) => wrap(value.apply(target, args));
429
+ return typeof value === "function" ? value.bind(target) : value;
430
+ } });
431
+ state.engine = new Proxy(engine, { get(target, prop) {
432
+ if (prop === "open") return async (...args) => {
433
+ const doc = await target.open(...args);
434
+ inner = doc;
435
+ return wrap(doc);
436
+ };
437
+ const value = Reflect.get(target, prop, target);
438
+ return typeof value === "function" ? value.bind(target) : value;
439
+ } });
440
+ return state;
441
+ }
442
+ //#endregion
443
+ export { LOCAL_ENGINE_BRAND, createTestContext, gate, manualClock };
444
+
445
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["/**\n * @embedpdf/core/testing — a real plugin context for plugin unit tests, so a\n * suite never re-invents the kernel: the kernel's own store, state cell,\n * mirrors and event hooks, page geometry from a page list, capabilities from\n * a token map, cleanups you can run, and a document handle you shape per\n * test. Everything a controller reaches for behaves as in the kernel, minus\n * the engine.\n *\n * const ctx = createTestContext({\n * id: 'measurement',\n * state: initialMeasurementState(),\n * pages: [{ ref: toPageRef(1), size: { width: 600, height: 800 } }],\n * capabilities: [[AnnotationToken, fakeAnnotation]],\n * doc: { security: { allows: () => true } },\n * });\n * const api = ctx.connect(createMeasurementController(ctx));\n * ctx.emitDocumentEvent(event); // folds into the plugin's mirrors\n *\n * Time is the host's timers unless a test passes a clock: `manualClock()`\n * steps timers and frames by hand.\n *\n * const time = manualClock();\n * const ctx = createTestContext({ clock: time.clock });\n * time.advance(300); // timers due by then run\n * time.frame(16); // and the callbacks waiting for a frame\n */\nimport type {\n Change,\n DocumentEvent,\n DocumentHandle,\n Engine,\n PageLayout,\n PageRef,\n PdfRect,\n} from '@embedpdf/engine-core/runtime';\nimport { pageRefsEqual, subscribeToType } from '@embedpdf/engine-core/runtime';\n\n/** Brand a fake engine or handle as local (`[LOCAL_ENGINE_BRAND]: true`) so\n * `isLocalEngine` and `isLocalDocument` accept it. */\nexport { LOCAL_ENGINE_BRAND } from '@embedpdf/engine-core/runtime';\n\nimport { createBurstGate } from './bursts';\nimport { createDocumentChanges } from './changes';\nimport { instanceClock, timerClock, type Cancel, type HostClock } from './clock';\nimport { PluginError } from './errors';\nimport { createEventHook } from './event-hook';\nimport { createLatestLane, type LatestLane } from './lanes';\nimport { createMirror, type MirrorController, type MirrorEnvironment } from './mirror';\nimport { createPageMirror } from './page-mirror';\nimport { createSerialQueue, type SerialQueue } from './serial-queue';\nimport { cancellable } from './cancellable';\nimport { findPage, pageOf } from './page-of';\nimport { runWrapped, type DownloadWrap, type PrintWrap } from './context';\nimport { fieldPermissionDenied, permissionDenied, sessionAllows } from './permissions';\nimport { createSettingsStore, type NoSettings, type SettingsDeclaration } from './settings';\nimport { settle, type SettleFlush } from './settle';\nimport { createStore } from './store';\nimport {\n DocumentsToken,\n type CapabilityToken,\n type DocumentInfo,\n type DocumentMeta,\n type DocumentsCapability,\n type OperationOptions,\n type PluginContext,\n type Unsubscribe,\n} from './types';\n\nexport interface TestPage {\n readonly ref: PageRef;\n /** Page size in points (default 612 × 792, or the crop's extent); the crop box is `[0, 0, w, h]` unless given. */\n readonly size?: { readonly width: number; readonly height: number };\n readonly crop?: PdfRect;\n readonly rotation?: PageLayout['rotation'];\n readonly userUnit?: number;\n readonly label?: string | null;\n}\n\nexport interface TestContextOptions<S, T extends object = NoSettings> {\n /** The plugin id (`ctx.id`, error prefixes). Default `'test'`. */\n readonly id?: string;\n /** The instance's initial state; omit for a stateless plugin. */\n readonly state?: S;\n /**\n * The plugin's settings, as its definition declares them; `ctx.settings()` reads them.\n * Without them the context has none, and `ctx.settings()` throws.\n */\n readonly settings?: SettingsDeclaration<T>;\n /** The document's pages; `document()` and `geometry` derive from them. */\n readonly pages?: readonly TestPage[];\n readonly documentId?: string;\n /** Capabilities `get`/`tryGet` resolve, by token. `DocumentsToken` defaults to a\n * registry holding this one document. */\n readonly capabilities?: ReadonlyArray<readonly [CapabilityToken<unknown>, unknown]>;\n /** The document handle members the test exercises (`security`, `page`, `events`, …),\n * or a real engine document; `null` for a workspace plugin with no document. */\n readonly doc?: Partial<DocumentHandle> | null;\n readonly engine?: Partial<Engine>;\n /** The host's time (`ctx.clock`). Default: `timerClock`, the environment's timers and no frames. */\n readonly clock?: HostClock;\n}\n\nexport interface TestContext<S, T extends object = NoSettings> extends PluginContext<S, T> {\n /** Every capability `get` can resolve — add or replace during a test. */\n readonly capabilities: Map<CapabilityToken<unknown>, unknown>;\n /** Observe the change stream: state updates and `notify` wake the listener. */\n subscribe(listener: () => void): Unsubscribe;\n /**\n * What the kernel does after `create`: run the instance's `connect`, then\n * start its mirrors' first loads. Returns the instance's api.\n */\n connect<C>(instance: { api: C; connect?: () => void }): C;\n /** Deliver a confirmed document event, as the engine would (the default `doc.events`). */\n emitDocumentEvent(event: DocumentEvent): void;\n /** What `documents.download()` does first: run every `onSettle` flush and wait for it (settle.ts). */\n settle(signal?: AbortSignal): Promise<void>;\n /**\n * What `documents.download()` does after settling: `read` the file inside every\n * `aroundDownload` wrap, the first registered outermost.\n */\n download(read: () => Promise<Uint8Array>): Promise<Uint8Array>;\n /**\n * What a print does around the print dialog: `print` inside every `aroundPrint` wrap, the\n * first registered outermost.\n */\n print(print: () => Promise<void>): Promise<void>;\n /** Run the registered cleanups and abort the lifetime (the plugin's unmount). */\n dispose(): Promise<void>;\n}\n\nconst layoutOf = (page: TestPage, index: number): PageLayout => {\n // Size follows the crop when only the crop is given, and vice versa.\n const size =\n page.size ??\n (page.crop\n ? { width: page.crop.right - page.crop.left, height: page.crop.top - page.crop.bottom }\n : { width: 612, height: 792 });\n const crop = page.crop ?? { left: 0, bottom: 0, right: size.width, top: size.height };\n // Every box is the crop box, measured from its own top-left corner.\n const visible = { x: 0, y: 0, width: crop.right - crop.left, height: crop.top - crop.bottom };\n return {\n index,\n ref: page.ref,\n label: page.label ?? null,\n size,\n rotation: page.rotation ?? 0,\n userUnit: page.userUnit ?? 1,\n tabOrder: null,\n boxes: {\n media: { ...visible },\n crop: { ...visible },\n bleed: { ...visible },\n trim: { ...visible },\n art: { ...visible },\n },\n pdfCropBox: crop,\n };\n};\n\n/** A read-only document registry holding the test's one document. */\nfunction testDocuments(meta: DocumentMeta): DocumentsCapability {\n const info: DocumentInfo = {\n id: meta.id,\n status: 'ready',\n pageCount: meta.pageCount,\n hasUnsavedChanges: false,\n };\n const known = (id?: string) => id === undefined || id === meta.id;\n const pagesOf = (id?: string) => (known(id) ? meta.pages : []);\n const unsupported = (verb: string) => () => {\n throw new PluginError('unsupported', 'documents', `${verb} is not available in a test context`);\n };\n const never = () => () => {};\n return {\n getActiveId: () => meta.id,\n getActive: () => info,\n list: () => [info],\n get: (id) => (known(id) ? info : null),\n has: (id) => known(id),\n getCount: () => 1,\n getOrder: () => [meta.id],\n listPages: pagesOf,\n getPage: (page, id) => findPage(pagesOf(id), page),\n getPageIndex: (ref, id) => pagesOf(id).findIndex((page) => pageRefsEqual(page.ref, ref)),\n getRevision: (id) => (known(id) ? meta.revision : -1),\n canDownload: () => true,\n // A test context has no printer.\n canPrint: () => false,\n getPrintQuality: () => null,\n // A test context opens nothing.\n canOpenFiles: () => false,\n open: unsupported('open'),\n retry: unsupported('retry'),\n rename: unsupported('rename'),\n openAll: unsupported('openAll'),\n unlock: unsupported('unlock'),\n close: unsupported('close'),\n closeAll: unsupported('closeAll'),\n setActive: unsupported('setActive'),\n setOrder: unsupported('setOrder'),\n move: unsupported('move'),\n swap: unsupported('swap'),\n download: unsupported('download'),\n downloadLayer: unsupported('downloadLayer'),\n preparePrint: unsupported('preparePrint'),\n print: unsupported('print'),\n onOpened: never,\n onOpenFailed: never,\n onLocked: never,\n onClosed: never,\n onActiveChanged: never,\n onPagesChanged: never,\n onUnsavedChangesChanged: never,\n } satisfies DocumentsCapability;\n}\n\nexport function createTestContext<S = void, T extends object = NoSettings>(\n options: TestContextOptions<S, T> = {},\n): TestContext<S, T> {\n const id = options.id ?? 'test';\n const documentId = options.documentId ?? 'doc';\n const report = (error: unknown) => console.error(`[${id}]`, error);\n const store = createStore(report);\n const instanceId = `${documentId}:${id}`;\n const lease = store.lease<S>(id, options.state as S, instanceId);\n const cleanups: Array<() => void | Promise<void>> = [];\n const lifetime = new AbortController();\n const settleFlushes = new Set<SettleFlush>();\n const downloadWraps: DownloadWrap[] = [];\n const printWraps: PrintWrap[] = [];\n const pages = (options.pages ?? []).map(layoutOf);\n const capabilities = new Map<CapabilityToken<unknown>, unknown>(options.capabilities ?? []);\n const documentEvents = createEventHook<DocumentEvent>(report);\n // A plain object lists the members a test fakes, over working defaults; a\n // real engine document (a class instance) is used as it is.\n const isRealHandle =\n options.doc != null && Object.getPrototypeOf(options.doc) !== Object.prototype;\n const doc =\n options.doc === null\n ? null\n : isRealHandle\n ? (options.doc as DocumentHandle)\n : fakeDocument({\n id: documentId,\n events: {\n subscribe: documentEvents.on,\n on: (type: DocumentEvent['type'], listener: (event: DocumentEvent) => void) =>\n subscribeToType(documentEvents.on, type, listener),\n lastServerId: () => null,\n },\n security: { allows: () => true, allowsAnnotation: () => true, allowsField: () => true },\n ...options.doc,\n });\n const requireDoc = (): DocumentHandle => {\n if (!doc) throw new PluginError('not-ready', id, 'the test context has no document');\n return doc;\n };\n // The document's change queue, as the kernel keeps one: `doc.apply` sends its changes.\n const changes = createDocumentChanges({ handle: () => doc, notify: store.notify, report });\n settleFlushes.add(() => changes.settle());\n cleanups.push(() => changes.close());\n const bursts = createBurstGate(store);\n const mirrors: MirrorController<unknown>[] = [];\n const mirrorEnvironment = (): MirrorEnvironment => ({\n doc: requireDoc(),\n changes,\n notify: store.notify,\n lifetime: lifetime.signal,\n cell(name, initial) {\n const cell = store.lease(`${id}/${name}`, initial, instanceId);\n lifetime.signal.addEventListener('abort', () => cell.revoke(), { once: true });\n return cell;\n },\n onDocumentEvent(listener) {\n cleanups.push(requireDoc().events.subscribe(listener));\n },\n report,\n });\n const meta: DocumentMeta = {\n id: documentId,\n instanceId,\n pageCount: pages.length,\n pages,\n revision: 1,\n renderPolicy: { kind: 'continuous' },\n hasUnsavedChanges: false,\n };\n if (!capabilities.has(DocumentsToken as CapabilityToken<unknown>)) {\n capabilities.set(DocumentsToken as CapabilityToken<unknown>, testDocuments(meta));\n }\n const notFound = (ref: PageRef) =>\n new PluginError('not-found', id, `page ${ref.objectNumber} is not in this document`);\n const resolve = <T>(token: CapabilityToken<T>): T | null =>\n capabilities.has(token as CapabilityToken<unknown>)\n ? (capabilities.get(token as CapabilityToken<unknown>) as T)\n : null;\n const require = <T>(token: CapabilityToken<T>): T => {\n const capability = resolve(token);\n if (capability === null) {\n throw new PluginError('not-found', id, `no capability for ${token.name} in the test context`);\n }\n return capability;\n };\n const queues = new Map<string, SerialQueue>();\n const lanes = new Map<string, LatestLane>();\n // The context is the plugin's only instance, so it holds the registration's settings itself.\n const settingsStore = options.settings\n ? createSettingsStore(options.settings, store.notify, report)\n : null;\n if (settingsStore) cleanups.push(() => settingsStore.dispose());\n const settings = settingsStore?.forInstance((teardown) => cleanups.push(teardown)) ?? null;\n\n const context: TestContext<S, T> = {\n id,\n instanceId: meta.instanceId,\n engine: (options.engine ?? {}) as Engine,\n documentId,\n get doc() {\n return doc as DocumentHandle;\n },\n documentHandle: () => doc,\n subscribe: store.subscribe,\n state: {\n get: () => lease.read(),\n update: (transition, ...args) => {\n lease.write(transition(lease.read(), ...args));\n },\n onChange: lease.onChange,\n },\n settings: () => {\n if (!settings) throw new Error(`[${id}] the test context declares no settings`);\n return settings;\n },\n notify: store.notify,\n watch: (select, handler, isEqual = Object.is) => {\n let previous = select();\n const off = store.subscribe(() => {\n const next = select();\n if (isEqual(previous, next)) return;\n const prior = previous;\n previous = next;\n handler(next, prior);\n });\n cleanups.push(off);\n return off;\n },\n mirror: (spec) => {\n const controller = createMirror(spec, mirrorEnvironment());\n mirrors.push(controller as MirrorController<unknown>);\n return controller.mirror;\n },\n pageMirror: (spec) => createPageMirror(spec, mirrorEnvironment()),\n changes: changes.forPlugin(id),\n document: () => meta,\n get: require,\n tryGet: resolve,\n forDocument: (token) => require(token),\n tryForDocument: (token) => resolve(token),\n cleanup: (fn) => {\n cleanups.push(fn);\n },\n clock: instanceClock(options.clock ?? timerClock, lifetime.signal),\n onSettle: (flush) => {\n settleFlushes.add(flush);\n cleanups.push(() => {\n settleFlushes.delete(flush);\n });\n },\n aroundDownload: (wrap) => {\n downloadWraps.push(wrap);\n cleanups.push(() => {\n const index = downloadWraps.indexOf(wrap);\n if (index >= 0) downloadWraps.splice(index, 1);\n });\n },\n aroundPrint: (wrap) => {\n printWraps.push(wrap);\n cleanups.push(() => {\n const index = printWraps.indexOf(wrap);\n if (index >= 0) printWraps.splice(index, 1);\n });\n },\n events: {\n source: <T>() => {\n const hook = createEventHook<T>((error) =>\n console.error(`[${id}] listener failed:`, error),\n );\n cleanups.push(() => hook.dispose());\n return hook;\n },\n },\n listen: (source, listener) => {\n const off = typeof source === 'function' ? source(listener) : source.subscribe(listener);\n cleanups.push(off);\n },\n waitFor: (predicate, waitOptions?: OperationOptions) =>\n new Promise<void>((resolveWait, reject) => {\n if (predicate()) return resolveWait();\n const off = context.subscribe(() => {\n if (!predicate()) return;\n off();\n resolveWait();\n });\n waitOptions?.signal?.addEventListener('abort', () => {\n off();\n reject(new PluginError('operation-cancelled', id, 'waitFor cancelled'));\n });\n }),\n serialQueue: (key = 'default') => {\n let queue = queues.get(key);\n if (!queue) {\n queue = createSerialQueue(id);\n queues.set(key, queue);\n }\n return queue;\n },\n latest: (key) => {\n let lane = lanes.get(key);\n if (!lane) {\n lane = createLatestLane(lifetime.signal, id, key);\n lanes.set(key, lane);\n }\n return lane;\n },\n acquire: async (get, dispose) => {\n const resource = await get(lifetime.signal);\n if (lifetime.signal.aborted) {\n await dispose(resource);\n throw new PluginError('instance-closed', id, 'acquired after close');\n }\n cleanups.push(() => dispose(resource));\n return resource;\n },\n assertPageRef: (ref) => {\n if (!pages.some((pageInfo) => pageRefsEqual(pageInfo.ref, ref))) throw notFound(ref);\n },\n getPage: (page) => findPage(pages, page),\n pageOf: (page) => pageOf(pages, page, id),\n allows: (permission) => sessionAllows(requireDoc().security, permission),\n assertAllowed: (permission, operation) => {\n if (!sessionAllows(requireDoc().security, permission)) {\n throw permissionDenied(id, permission, operation);\n }\n },\n allowsField: (action, field) =>\n requireDoc().security.allowsField(action, { groupId: field.groupId }),\n assertAllowedField: (action, field, operation) => {\n if (!requireDoc().security.allowsField(action, { groupId: field.groupId })) {\n throw fieldPermissionDenied(id, action, field, operation);\n }\n },\n cancellable: (signal, task) => cancellable(id, signal, task),\n capabilities,\n connect: (instance) => {\n instance.connect?.();\n for (const mirror of mirrors.splice(0)) mirror.start();\n return instance.api;\n },\n emitDocumentEvent: (event) => {\n bursts.first(event);\n documentEvents.emit(event);\n bursts.last(event);\n },\n settle: (signal) => settle(settleFlushes, [signal, lifetime.signal], report),\n download: (read) => runWrapped(downloadWraps, read),\n print: (print) => runWrapped(printWraps, print),\n dispose: async () => {\n lifetime.abort();\n for (const fn of cleanups.splice(0).reverse()) await fn();\n },\n };\n return context;\n}\n\n/** A clock a test steps by hand (see {@link manualClock}). */\nexport interface ManualClock {\n /** The clock to hand a test context or a kernel. */\n readonly clock: HostClock;\n /** The current time in milliseconds. It starts at 0. */\n readonly now: number;\n /** How many timers and frame callbacks are waiting. */\n readonly pending: { readonly timers: number; readonly frames: number };\n /** Move time forward by `ms`, running the timers that fall due, earliest first. */\n advance(ms: number): void;\n /**\n * Show a frame at `timeMs` (default: now): move time there, running the\n * timers due by then, and run the callbacks waiting for a frame. What they\n * schedule waits for the next frame.\n */\n frame(timeMs?: number): void;\n}\n\n/**\n * Time that moves only when the test moves it. `frames: false` gives a host\n * that never paints, as Node is.\n */\nexport function manualClock(options: { frames?: boolean } = {}): ManualClock {\n let now = 0;\n let order = 0;\n const timers = new Map<number, { due: number; run: () => void }>();\n const frames = new Map<number, (timeMs: number) => void>();\n\n const nextDue = (until: number): number | undefined => {\n let found: number | undefined;\n for (const [id, timer] of timers) {\n if (timer.due > until) continue;\n if (found === undefined || timer.due < timers.get(found)!.due) found = id;\n }\n return found;\n };\n const advanceTo = (target: number): void => {\n if (target < now) throw new Error(`manualClock: time does not go back (${target} < ${now})`);\n for (let id = nextDue(target); id !== undefined; id = nextDue(target)) {\n const timer = timers.get(id)!;\n timers.delete(id);\n now = timer.due;\n timer.run();\n }\n now = target;\n };\n\n const clock: HostClock = {\n now: () => now,\n after(ms, run): Cancel {\n const id = ++order;\n timers.set(id, { due: now + Math.max(0, ms), run });\n return () => void timers.delete(id);\n },\n ...(options.frames === false\n ? {}\n : {\n nextFrame(run: (timeMs: number) => void): Cancel {\n const id = ++order;\n frames.set(id, run);\n return () => void frames.delete(id);\n },\n }),\n };\n return {\n clock,\n get now() {\n return now;\n },\n get pending() {\n return { timers: timers.size, frames: frames.size };\n },\n advance: (ms) => advanceTo(now + ms),\n frame(timeMs = now) {\n advanceTo(timeMs);\n const due = [...frames.values()];\n frames.clear();\n for (const run of due) run(now);\n },\n };\n}\n\n/**\n * A faked document's members over the defaults every document has: calls'\n * facts and working sets change no result, so `with` gives the same fake and\n * a working set goes nowhere, unless the test fakes them.\n */\nfunction fakeDocument(members: Record<string, unknown>): DocumentHandle {\n const fake: Record<string, unknown> = { setWorkingSet: () => {}, ...members };\n fake.with ??= () => fake;\n return fake as unknown as DocumentHandle;\n}\n\n/* ── gate: an engine that holds changes ─────────────────────────────────── */\n\n/**\n * An engine whose documents hold every change until the test lets it go, so\n * a test sees what the reads show while a change is on its way: wrap a real\n * engine with `gate(engine)`, boot the kernel on `gate.engine`, set\n * `holding`, then `release()` or `refuse()` the oldest held change.\n */\n/** A change a gate holds, with the promise its sender awaits. */\nexport interface GatedChange {\n readonly change: Change;\n readonly options: unknown;\n resolve(value: unknown): void;\n reject(error: unknown): void;\n}\n\nexport interface Gate {\n readonly engine: Engine;\n /** The changes held so far, oldest first. */\n readonly held: GatedChange[];\n /** Whether changes are held; when not, they go straight to the engine. */\n holding: boolean;\n /** Send the oldest held change to the engine, and wait for its answer to land. */\n release(): Promise<void>;\n /** Refuse the oldest held change, as the engine would. */\n refuse(error: unknown): void;\n}\n\nexport function gate(engine: Engine): Gate {\n const held: GatedChange[] = [];\n let inner: DocumentHandle | null = null;\n const state: Gate = {\n engine: null as unknown as Engine,\n held,\n holding: false,\n async release() {\n const next = held.shift();\n if (!next || !inner) throw new Error('nothing is held');\n await inner.apply(next.change, next.options as never).then(next.resolve, next.reject);\n },\n refuse(error) {\n const next = held.shift();\n if (!next) throw new Error('nothing is held');\n next.reject(error);\n },\n };\n const wrap = (doc: DocumentHandle): DocumentHandle =>\n new Proxy(doc, {\n get(target, prop) {\n if (prop === 'apply') {\n return (change: Change, options: unknown) =>\n state.holding\n ? new Promise((resolve, reject) => held.push({ change, options, resolve, reject }))\n : target.apply(change, options as never);\n }\n const value = Reflect.get(target, prop, target) as unknown;\n if (prop === 'with' && typeof value === 'function') {\n return (...args: unknown[]) => wrap(value.apply(target, args) as DocumentHandle);\n }\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n (state as { engine: Engine }).engine = new Proxy(engine, {\n get(target, prop) {\n if (prop === 'open') {\n return async (...args: Parameters<Engine['open']>) => {\n const doc = await target.open(...args);\n inner = doc;\n return wrap(doc);\n };\n }\n const value = Reflect.get(target, prop, target) as unknown;\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n return state;\n}\n"],"mappings":";;;AAkIA,MAAM,YAAY,MAAgB,UAA8B;CAE9D,MAAM,OACJ,KAAK,SACJ,KAAK,OACF;EAAE,OAAO,KAAK,KAAK,QAAQ,KAAK,KAAK;EAAM,QAAQ,KAAK,KAAK,MAAM,KAAK,KAAK;CAAO,IACpF;EAAE,OAAO;EAAK,QAAQ;CAAI;CAChC,MAAM,OAAO,KAAK,QAAQ;EAAE,MAAM;EAAG,QAAQ;EAAG,OAAO,KAAK;EAAO,KAAK,KAAK;CAAO;CAEpF,MAAM,UAAU;EAAE,GAAG;EAAG,GAAG;EAAG,OAAO,KAAK,QAAQ,KAAK;EAAM,QAAQ,KAAK,MAAM,KAAK;CAAO;CAC5F,OAAO;EACL;EACA,KAAK,KAAK;EACV,OAAO,KAAK,SAAS;EACrB;EACA,UAAU,KAAK,YAAY;EAC3B,UAAU,KAAK,YAAY;EAC3B,UAAU;EACV,OAAO;GACL,OAAO,EAAE,GAAG,QAAQ;GACpB,MAAM,EAAE,GAAG,QAAQ;GACnB,OAAO,EAAE,GAAG,QAAQ;GACpB,MAAM,EAAE,GAAG,QAAQ;GACnB,KAAK,EAAE,GAAG,QAAQ;EACpB;EACA,YAAY;CACd;AACF;;AAGA,SAAS,cAAc,MAAyC;CAC9D,MAAM,OAAqB;EACzB,IAAI,KAAK;EACT,QAAQ;EACR,WAAW,KAAK;EAChB,mBAAmB;CACrB;CACA,MAAM,SAAS,OAAgB,OAAO,KAAA,KAAa,OAAO,KAAK;CAC/D,MAAM,WAAW,OAAiB,MAAM,EAAE,IAAI,KAAK,QAAQ,CAAC;CAC5D,MAAM,eAAe,eAAuB;EAC1C,MAAM,IAAI,YAAY,eAAe,aAAa,GAAG,KAAK,oCAAoC;CAChG;CACA,MAAM,oBAAoB,CAAC;CAC3B,OAAO;EACL,mBAAmB,KAAK;EACxB,iBAAiB;EACjB,YAAY,CAAC,IAAI;EACjB,MAAM,OAAQ,MAAM,EAAE,IAAI,OAAO;EACjC,MAAM,OAAO,MAAM,EAAE;EACrB,gBAAgB;EAChB,gBAAgB,CAAC,KAAK,EAAE;EACxB,WAAW;EACX,UAAU,MAAM,OAAO,SAAS,QAAQ,EAAE,GAAG,IAAI;EACjD,eAAe,KAAK,OAAO,QAAQ,EAAE,CAAC,CAAC,WAAW,SAAS,cAAc,KAAK,KAAK,GAAG,CAAC;EACvF,cAAc,OAAQ,MAAM,EAAE,IAAI,KAAK,WAAW;EAClD,mBAAmB;EAEnB,gBAAgB;EAChB,uBAAuB;EAEvB,oBAAoB;EACpB,MAAM,YAAY,MAAM;EACxB,OAAO,YAAY,OAAO;EAC1B,QAAQ,YAAY,QAAQ;EAC5B,SAAS,YAAY,SAAS;EAC9B,QAAQ,YAAY,QAAQ;EAC5B,OAAO,YAAY,OAAO;EAC1B,UAAU,YAAY,UAAU;EAChC,WAAW,YAAY,WAAW;EAClC,UAAU,YAAY,UAAU;EAChC,MAAM,YAAY,MAAM;EACxB,MAAM,YAAY,MAAM;EACxB,UAAU,YAAY,UAAU;EAChC,eAAe,YAAY,eAAe;EAC1C,cAAc,YAAY,cAAc;EACxC,OAAO,YAAY,OAAO;EAC1B,UAAU;EACV,cAAc;EACd,UAAU;EACV,UAAU;EACV,iBAAiB;EACjB,gBAAgB;EAChB,yBAAyB;CAC3B;AACF;AAEA,SAAgB,kBACd,UAAoC,CAAC,GAClB;CACnB,MAAM,KAAK,QAAQ,MAAM;CACzB,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,UAAU,UAAmB,QAAQ,MAAM,IAAI,GAAG,IAAI,KAAK;CACjE,MAAM,QAAQ,YAAY,MAAM;CAChC,MAAM,aAAa,GAAG,WAAW,GAAG;CACpC,MAAM,QAAQ,MAAM,MAAS,IAAI,QAAQ,OAAY,UAAU;CAC/D,MAAM,WAA8C,CAAC;CACrD,MAAM,WAAW,IAAI,gBAAgB;CACrC,MAAM,gCAAgB,IAAI,IAAiB;CAC3C,MAAM,gBAAgC,CAAC;CACvC,MAAM,aAA0B,CAAC;CACjC,MAAM,SAAS,QAAQ,SAAS,CAAC,EAAA,CAAG,IAAI,QAAQ;CAChD,MAAM,eAAe,IAAI,IAAuC,QAAQ,gBAAgB,CAAC,CAAC;CAC1F,MAAM,iBAAiB,gBAA+B,MAAM;CAG5D,MAAM,eACJ,QAAQ,OAAO,QAAQ,OAAO,eAAe,QAAQ,GAAG,MAAM,OAAO;CACvE,MAAM,MACJ,QAAQ,QAAQ,OACZ,OACA,eACG,QAAQ,MACT,aAAa;EACX,IAAI;EACJ,QAAQ;GACN,WAAW,eAAe;GAC1B,KAAK,MAA6B,aAChC,gBAAgB,eAAe,IAAI,MAAM,QAAQ;GACnD,oBAAoB;EACtB;EACA,UAAU;GAAE,cAAc;GAAM,wBAAwB;GAAM,mBAAmB;EAAK;EACtF,GAAG,QAAQ;CACb,CAAC;CACT,MAAM,mBAAmC;EACvC,IAAI,CAAC,KAAK,MAAM,IAAI,YAAY,aAAa,IAAI,kCAAkC;EACnF,OAAO;CACT;CAEA,MAAM,UAAU,sBAAsB;EAAE,cAAc;EAAK,QAAQ,MAAM;EAAQ;CAAO,CAAC;CACzF,cAAc,UAAU,QAAQ,OAAO,CAAC;CACxC,SAAS,WAAW,QAAQ,MAAM,CAAC;CACnC,MAAM,SAAS,gBAAgB,KAAK;CACpC,MAAM,UAAuC,CAAC;CAC9C,MAAM,2BAA8C;EAClD,KAAK,WAAW;EAChB;EACA,QAAQ,MAAM;EACd,UAAU,SAAS;EACnB,KAAK,MAAM,SAAS;GAClB,MAAM,OAAO,MAAM,MAAM,GAAG,GAAG,GAAG,QAAQ,SAAS,UAAU;GAC7D,SAAS,OAAO,iBAAiB,eAAe,KAAK,OAAO,GAAG,EAAE,MAAM,KAAK,CAAC;GAC7E,OAAO;EACT;EACA,gBAAgB,UAAU;GACxB,SAAS,KAAK,WAAW,CAAC,CAAC,OAAO,UAAU,QAAQ,CAAC;EACvD;EACA;CACF;CACA,MAAM,OAAqB;EACzB,IAAI;EACJ;EACA,WAAW,MAAM;EACjB;EACA,UAAU;EACV,cAAc,EAAE,MAAM,aAAa;EACnC,mBAAmB;CACrB;CACA,IAAI,CAAC,aAAa,IAAI,cAA0C,GAC9D,aAAa,IAAI,gBAA4C,cAAc,IAAI,CAAC;CAElF,MAAM,YAAY,QAChB,IAAI,YAAY,aAAa,IAAI,QAAQ,IAAI,aAAa,yBAAyB;CACrF,MAAM,WAAc,UAClB,aAAa,IAAI,KAAiC,IAC7C,aAAa,IAAI,KAAiC,IACnD;CACN,MAAM,WAAc,UAAiC;EACnD,MAAM,aAAa,QAAQ,KAAK;EAChC,IAAI,eAAe,MACjB,MAAM,IAAI,YAAY,aAAa,IAAI,qBAAqB,MAAM,KAAK,qBAAqB;EAE9F,OAAO;CACT;CACA,MAAM,yBAAS,IAAI,IAAyB;CAC5C,MAAM,wBAAQ,IAAI,IAAwB;CAE1C,MAAM,gBAAgB,QAAQ,WAC1B,oBAAoB,QAAQ,UAAU,MAAM,QAAQ,MAAM,IAC1D;CACJ,IAAI,eAAe,SAAS,WAAW,cAAc,QAAQ,CAAC;CAC9D,MAAM,WAAW,eAAe,aAAa,aAAa,SAAS,KAAK,QAAQ,CAAC,KAAK;CAEtF,MAAM,UAA6B;EACjC;EACA,YAAY,KAAK;EACjB,QAAS,QAAQ,UAAU,CAAC;EAC5B;EACA,IAAI,MAAM;GACR,OAAO;EACT;EACA,sBAAsB;EACtB,WAAW,MAAM;EACjB,OAAO;GACL,WAAW,MAAM,KAAK;GACtB,SAAS,YAAY,GAAG,SAAS;IAC/B,MAAM,MAAM,WAAW,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC;GAC/C;GACA,UAAU,MAAM;EAClB;EACA,gBAAgB;GACd,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,IAAI,GAAG,wCAAwC;GAC9E,OAAO;EACT;EACA,QAAQ,MAAM;EACd,QAAQ,QAAQ,SAAS,UAAU,OAAO,OAAO;GAC/C,IAAI,WAAW,OAAO;GACtB,MAAM,MAAM,MAAM,gBAAgB;IAChC,MAAM,OAAO,OAAO;IACpB,IAAI,QAAQ,UAAU,IAAI,GAAG;IAC7B,MAAM,QAAQ;IACd,WAAW;IACX,QAAQ,MAAM,KAAK;GACrB,CAAC;GACD,SAAS,KAAK,GAAG;GACjB,OAAO;EACT;EACA,SAAS,SAAS;GAChB,MAAM,aAAa,aAAa,MAAM,kBAAkB,CAAC;GACzD,QAAQ,KAAK,UAAuC;GACpD,OAAO,WAAW;EACpB;EACA,aAAa,SAAS,iBAAiB,MAAM,kBAAkB,CAAC;EAChE,SAAS,QAAQ,UAAU,EAAE;EAC7B,gBAAgB;EAChB,KAAK;EACL,QAAQ;EACR,cAAc,UAAU,QAAQ,KAAK;EACrC,iBAAiB,UAAU,QAAQ,KAAK;EACxC,UAAU,OAAO;GACf,SAAS,KAAK,EAAE;EAClB;EACA,OAAO,cAAc,QAAQ,SAAS,YAAY,SAAS,MAAM;EACjE,WAAW,UAAU;GACnB,cAAc,IAAI,KAAK;GACvB,SAAS,WAAW;IAClB,cAAc,OAAO,KAAK;GAC5B,CAAC;EACH;EACA,iBAAiB,SAAS;GACxB,cAAc,KAAK,IAAI;GACvB,SAAS,WAAW;IAClB,MAAM,QAAQ,cAAc,QAAQ,IAAI;IACxC,IAAI,SAAS,GAAG,cAAc,OAAO,OAAO,CAAC;GAC/C,CAAC;EACH;EACA,cAAc,SAAS;GACrB,WAAW,KAAK,IAAI;GACpB,SAAS,WAAW;IAClB,MAAM,QAAQ,WAAW,QAAQ,IAAI;IACrC,IAAI,SAAS,GAAG,WAAW,OAAO,OAAO,CAAC;GAC5C,CAAC;EACH;EACA,QAAQ,EACN,cAAiB;GACf,MAAM,OAAO,iBAAoB,UAC/B,QAAQ,MAAM,IAAI,GAAG,qBAAqB,KAAK,CACjD;GACA,SAAS,WAAW,KAAK,QAAQ,CAAC;GAClC,OAAO;EACT,EACF;EACA,SAAS,QAAQ,aAAa;GAC5B,MAAM,MAAM,OAAO,WAAW,aAAa,OAAO,QAAQ,IAAI,OAAO,UAAU,QAAQ;GACvF,SAAS,KAAK,GAAG;EACnB;EACA,UAAU,WAAW,gBACnB,IAAI,SAAe,aAAa,WAAW;GACzC,IAAI,UAAU,GAAG,OAAO,YAAY;GACpC,MAAM,MAAM,QAAQ,gBAAgB;IAClC,IAAI,CAAC,UAAU,GAAG;IAClB,IAAI;IACJ,YAAY;GACd,CAAC;GACD,aAAa,QAAQ,iBAAiB,eAAe;IACnD,IAAI;IACJ,OAAO,IAAI,YAAY,uBAAuB,IAAI,mBAAmB,CAAC;GACxE,CAAC;EACH,CAAC;EACH,cAAc,MAAM,cAAc;GAChC,IAAI,QAAQ,OAAO,IAAI,GAAG;GAC1B,IAAI,CAAC,OAAO;IACV,QAAQ,kBAAkB,EAAE;IAC5B,OAAO,IAAI,KAAK,KAAK;GACvB;GACA,OAAO;EACT;EACA,SAAS,QAAQ;GACf,IAAI,OAAO,MAAM,IAAI,GAAG;GACxB,IAAI,CAAC,MAAM;IACT,OAAO,iBAAiB,SAAS,QAAQ,IAAI,GAAG;IAChD,MAAM,IAAI,KAAK,IAAI;GACrB;GACA,OAAO;EACT;EACA,SAAS,OAAO,KAAK,YAAY;GAC/B,MAAM,WAAW,MAAM,IAAI,SAAS,MAAM;GAC1C,IAAI,SAAS,OAAO,SAAS;IAC3B,MAAM,QAAQ,QAAQ;IACtB,MAAM,IAAI,YAAY,mBAAmB,IAAI,sBAAsB;GACrE;GACA,SAAS,WAAW,QAAQ,QAAQ,CAAC;GACrC,OAAO;EACT;EACA,gBAAgB,QAAQ;GACtB,IAAI,CAAC,MAAM,MAAM,aAAa,cAAc,SAAS,KAAK,GAAG,CAAC,GAAG,MAAM,SAAS,GAAG;EACrF;EACA,UAAU,SAAS,SAAS,OAAO,IAAI;EACvC,SAAS,SAAS,OAAO,OAAO,MAAM,EAAE;EACxC,SAAS,eAAe,cAAc,WAAW,CAAC,CAAC,UAAU,UAAU;EACvE,gBAAgB,YAAY,cAAc;GACxC,IAAI,CAAC,cAAc,WAAW,CAAC,CAAC,UAAU,UAAU,GAClD,MAAM,iBAAiB,IAAI,YAAY,SAAS;EAEpD;EACA,cAAc,QAAQ,UACpB,WAAW,CAAC,CAAC,SAAS,YAAY,QAAQ,EAAE,SAAS,MAAM,QAAQ,CAAC;EACtE,qBAAqB,QAAQ,OAAO,cAAc;GAChD,IAAI,CAAC,WAAW,CAAC,CAAC,SAAS,YAAY,QAAQ,EAAE,SAAS,MAAM,QAAQ,CAAC,GACvE,MAAM,sBAAsB,IAAI,QAAQ,OAAO,SAAS;EAE5D;EACA,cAAc,QAAQ,SAAS,YAAY,IAAI,QAAQ,IAAI;EAC3D;EACA,UAAU,aAAa;GACrB,SAAS,UAAU;GACnB,KAAK,MAAM,UAAU,QAAQ,OAAO,CAAC,GAAG,OAAO,MAAM;GACrD,OAAO,SAAS;EAClB;EACA,oBAAoB,UAAU;GAC5B,OAAO,MAAM,KAAK;GAClB,eAAe,KAAK,KAAK;GACzB,OAAO,KAAK,KAAK;EACnB;EACA,SAAS,WAAW,OAAO,eAAe,CAAC,QAAQ,SAAS,MAAM,GAAG,MAAM;EAC3E,WAAW,SAAS,WAAW,eAAe,IAAI;EAClD,QAAQ,UAAU,WAAW,YAAY,KAAK;EAC9C,SAAS,YAAY;GACnB,SAAS,MAAM;GACf,KAAK,MAAM,MAAM,SAAS,OAAO,CAAC,CAAC,CAAC,QAAQ,GAAG,MAAM,GAAG;EAC1D;CACF;CACA,OAAO;AACT;;;;;AAwBA,SAAgB,YAAY,UAAgC,CAAC,GAAgB;CAC3E,IAAI,MAAM;CACV,IAAI,QAAQ;CACZ,MAAM,yBAAS,IAAI,IAA8C;CACjE,MAAM,yBAAS,IAAI,IAAsC;CAEzD,MAAM,WAAW,UAAsC;EACrD,IAAI;EACJ,KAAK,MAAM,CAAC,IAAI,UAAU,QAAQ;GAChC,IAAI,MAAM,MAAM,OAAO;GACvB,IAAI,UAAU,KAAA,KAAa,MAAM,MAAM,OAAO,IAAI,KAAK,CAAC,CAAE,KAAK,QAAQ;EACzE;EACA,OAAO;CACT;CACA,MAAM,aAAa,WAAyB;EAC1C,IAAI,SAAS,KAAK,MAAM,IAAI,MAAM,uCAAuC,OAAO,KAAK,IAAI,EAAE;EAC3F,KAAK,IAAI,KAAK,QAAQ,MAAM,GAAG,OAAO,KAAA,GAAW,KAAK,QAAQ,MAAM,GAAG;GACrE,MAAM,QAAQ,OAAO,IAAI,EAAE;GAC3B,OAAO,OAAO,EAAE;GAChB,MAAM,MAAM;GACZ,MAAM,IAAI;EACZ;EACA,MAAM;CACR;CAmBA,OAAO;EACL,OAAA;GAjBA,WAAW;GACX,MAAM,IAAI,KAAa;IACrB,MAAM,KAAK,EAAE;IACb,OAAO,IAAI,IAAI;KAAE,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE;KAAG;IAAI,CAAC;IAClD,aAAa,KAAK,OAAO,OAAO,EAAE;GACpC;GACA,GAAI,QAAQ,WAAW,QACnB,CAAC,IACD,EACE,UAAU,KAAuC;IAC/C,MAAM,KAAK,EAAE;IACb,OAAO,IAAI,IAAI,GAAG;IAClB,aAAa,KAAK,OAAO,OAAO,EAAE;GACpC,EACF;EAGA;EACJ,IAAI,MAAM;GACR,OAAO;EACT;EACA,IAAI,UAAU;GACZ,OAAO;IAAE,QAAQ,OAAO;IAAM,QAAQ,OAAO;GAAK;EACpD;EACA,UAAU,OAAO,UAAU,MAAM,EAAE;EACnC,MAAM,SAAS,KAAK;GAClB,UAAU,MAAM;GAChB,MAAM,MAAM,CAAC,GAAG,OAAO,OAAO,CAAC;GAC/B,OAAO,MAAM;GACb,KAAK,MAAM,OAAO,KAAK,IAAI,GAAG;EAChC;CACF;AACF;;;;;;AAOA,SAAS,aAAa,SAAkD;CACtE,MAAM,OAAgC;EAAE,qBAAqB,CAAC;EAAG,GAAG;CAAQ;CAC5E,KAAK,SAAL,KAAK,aAAe;CACpB,OAAO;AACT;AA8BA,SAAgB,KAAK,QAAsB;CACzC,MAAM,OAAsB,CAAC;CAC7B,IAAI,QAA+B;CACnC,MAAM,QAAc;EAClB,QAAQ;EACR;EACA,SAAS;EACT,MAAM,UAAU;GACd,MAAM,OAAO,KAAK,MAAM;GACxB,IAAI,CAAC,QAAQ,CAAC,OAAO,MAAM,IAAI,MAAM,iBAAiB;GACtD,MAAM,MAAM,MAAM,KAAK,QAAQ,KAAK,OAAgB,CAAC,CAAC,KAAK,KAAK,SAAS,KAAK,MAAM;EACtF;EACA,OAAO,OAAO;GACZ,MAAM,OAAO,KAAK,MAAM;GACxB,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,iBAAiB;GAC5C,KAAK,OAAO,KAAK;EACnB;CACF;CACA,MAAM,QAAQ,QACZ,IAAI,MAAM,KAAK,EACb,IAAI,QAAQ,MAAM;EAChB,IAAI,SAAS,SACX,QAAQ,QAAgB,YACtB,MAAM,UACF,IAAI,SAAS,SAAS,WAAW,KAAK,KAAK;GAAE;GAAQ;GAAS;GAAS;EAAO,CAAC,CAAC,IAChF,OAAO,MAAM,QAAQ,OAAgB;EAE7C,MAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,MAAM;EAC9C,IAAI,SAAS,UAAU,OAAO,UAAU,YACtC,QAAQ,GAAG,SAAoB,KAAK,MAAM,MAAM,QAAQ,IAAI,CAAmB;EAEjF,OAAO,OAAO,UAAU,aAAa,MAAM,KAAK,MAAM,IAAI;CAC5D,EACF,CAAC;CACH,MAA8B,SAAS,IAAI,MAAM,QAAQ,EACvD,IAAI,QAAQ,MAAM;EAChB,IAAI,SAAS,QACX,OAAO,OAAO,GAAG,SAAqC;GACpD,MAAM,MAAM,MAAM,OAAO,KAAK,GAAG,IAAI;GACrC,QAAQ;GACR,OAAO,KAAK,GAAG;EACjB;EAEF,MAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,MAAM;EAC9C,OAAO,OAAO,UAAU,aAAa,MAAM,KAAK,MAAM,IAAI;CAC5D,EACF,CAAC;CACD,OAAO;AACT"}