@embedpdf/core 1.0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1308 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BasePlugin: () => BasePlugin,
24
+ CapabilityConflictError: () => CapabilityConflictError,
25
+ CapabilityNotFoundError: () => CapabilityNotFoundError,
26
+ CircularDependencyError: () => CircularDependencyError,
27
+ DependencyResolver: () => DependencyResolver,
28
+ EventControl: () => EventControl,
29
+ LOAD_DOCUMENT: () => LOAD_DOCUMENT,
30
+ PluginConfigurationError: () => PluginConfigurationError,
31
+ PluginInitializationError: () => PluginInitializationError,
32
+ PluginNotFoundError: () => PluginNotFoundError,
33
+ PluginRegistrationError: () => PluginRegistrationError,
34
+ PluginRegistry: () => PluginRegistry,
35
+ SET_DOCUMENT: () => SET_DOCUMENT,
36
+ SET_DOCUMENT_ERROR: () => SET_DOCUMENT_ERROR,
37
+ SET_PAGES: () => SET_PAGES,
38
+ SET_ROTATION: () => SET_ROTATION,
39
+ SET_SCALE: () => SET_SCALE,
40
+ arePropsEqual: () => arePropsEqual,
41
+ clamp: () => clamp,
42
+ createBehaviorEmitter: () => createBehaviorEmitter,
43
+ createEmitter: () => createEmitter,
44
+ createPluginRegistration: () => createPluginRegistration,
45
+ getPagesWithRotatedSize: () => getPagesWithRotatedSize,
46
+ initialCoreState: () => initialCoreState,
47
+ loadDocument: () => loadDocument,
48
+ setDocument: () => setDocument,
49
+ setDocumentError: () => setDocumentError,
50
+ setPages: () => setPages,
51
+ setRotation: () => setRotation,
52
+ setScale: () => setScale
53
+ });
54
+ module.exports = __toCommonJS(index_exports);
55
+
56
+ // src/lib/utils/dependency-resolver.ts
57
+ var DependencyResolver = class {
58
+ constructor() {
59
+ this.dependencyGraph = /* @__PURE__ */ new Map();
60
+ }
61
+ addNode(id, dependencies = []) {
62
+ this.dependencyGraph.set(id, new Set(dependencies));
63
+ }
64
+ hasCircularDependencies() {
65
+ const visited = /* @__PURE__ */ new Set();
66
+ const recursionStack = /* @__PURE__ */ new Set();
67
+ const dfs = (id) => {
68
+ visited.add(id);
69
+ recursionStack.add(id);
70
+ const dependencies = this.dependencyGraph.get(id) || /* @__PURE__ */ new Set();
71
+ for (const dep of dependencies) {
72
+ if (!visited.has(dep)) {
73
+ if (dfs(dep)) return true;
74
+ } else if (recursionStack.has(dep)) {
75
+ return true;
76
+ }
77
+ }
78
+ recursionStack.delete(id);
79
+ return false;
80
+ };
81
+ for (const id of this.dependencyGraph.keys()) {
82
+ if (!visited.has(id)) {
83
+ if (dfs(id)) return true;
84
+ }
85
+ }
86
+ return false;
87
+ }
88
+ resolveLoadOrder() {
89
+ if (this.hasCircularDependencies()) {
90
+ throw new Error("Circular dependencies detected");
91
+ }
92
+ const result = [];
93
+ const visited = /* @__PURE__ */ new Set();
94
+ const temp = /* @__PURE__ */ new Set();
95
+ const visit = (id) => {
96
+ if (temp.has(id)) throw new Error("Circular dependency");
97
+ if (visited.has(id)) return;
98
+ temp.add(id);
99
+ const dependencies = this.dependencyGraph.get(id) || /* @__PURE__ */ new Set();
100
+ for (const dep of dependencies) {
101
+ visit(dep);
102
+ }
103
+ temp.delete(id);
104
+ visited.add(id);
105
+ result.push(id);
106
+ };
107
+ for (const id of this.dependencyGraph.keys()) {
108
+ if (!visited.has(id)) {
109
+ visit(id);
110
+ }
111
+ }
112
+ return result;
113
+ }
114
+ };
115
+
116
+ // src/lib/types/errors.ts
117
+ var PluginRegistrationError = class extends Error {
118
+ constructor(message) {
119
+ super(message);
120
+ this.name = "PluginRegistrationError";
121
+ }
122
+ };
123
+ var PluginNotFoundError = class extends Error {
124
+ constructor(message) {
125
+ super(message);
126
+ this.name = "PluginNotFoundError";
127
+ }
128
+ };
129
+ var CircularDependencyError = class extends Error {
130
+ constructor(message) {
131
+ super(message);
132
+ this.name = "CircularDependencyError";
133
+ }
134
+ };
135
+ var CapabilityNotFoundError = class extends Error {
136
+ constructor(message) {
137
+ super(message);
138
+ this.name = "CapabilityNotFoundError";
139
+ }
140
+ };
141
+ var CapabilityConflictError = class extends Error {
142
+ constructor(message) {
143
+ super(message);
144
+ this.name = "CapabilityConflictError";
145
+ }
146
+ };
147
+ var PluginInitializationError = class extends Error {
148
+ constructor(message) {
149
+ super(message);
150
+ this.name = "PluginInitializationError";
151
+ }
152
+ };
153
+ var PluginConfigurationError = class extends Error {
154
+ constructor(message) {
155
+ super(message);
156
+ this.name = "PluginConfigurationError";
157
+ }
158
+ };
159
+
160
+ // src/lib/store/plugin-store.ts
161
+ var PluginStore = class {
162
+ /**
163
+ * Initializes the PluginStore with the main store and plugin ID.
164
+ * @param store The main store instance.
165
+ * @param pluginId The unique identifier for the plugin.
166
+ */
167
+ constructor(store, pluginId) {
168
+ this.store = store;
169
+ this.pluginId = pluginId;
170
+ }
171
+ /**
172
+ * Gets the current state of the plugin.
173
+ * @returns The plugin's state.
174
+ */
175
+ getState() {
176
+ return this.store.getState().plugins[this.pluginId];
177
+ }
178
+ /**
179
+ * Dispatches an action for the plugin and returns the *new* global state.
180
+ * If you only need the plugin’s updated state, call `getState()` afterward.
181
+ * @param action The action to dispatch.
182
+ * @returns The updated global store state (after plugin reducer).
183
+ */
184
+ dispatch(action) {
185
+ return this.store.dispatchToPlugin(this.pluginId, action);
186
+ }
187
+ /**
188
+ * Subscribes to state changes only for this specific plugin.
189
+ * You now receive (action, newPluginState, oldPluginState) in the callback.
190
+ *
191
+ * @param listener The callback to invoke when plugin state changes.
192
+ * @returns A function to unsubscribe the listener.
193
+ */
194
+ subscribeToState(listener) {
195
+ return this.store.subscribeToPlugin(this.pluginId, (action, newPluginState, oldPluginState) => {
196
+ listener(
197
+ action,
198
+ newPluginState,
199
+ oldPluginState
200
+ );
201
+ });
202
+ }
203
+ /**
204
+ * Subscribes to a specific action type for the plugin.
205
+ * This still uses the main store's `onAction`, so you get the *global*
206
+ * old/new store states there. If you specifically want old/new plugin state,
207
+ * use `subscribeToState` instead.
208
+ *
209
+ * @param type The action type to listen for.
210
+ * @param handler The callback to invoke when the action occurs.
211
+ * @returns A function to unsubscribe the handler.
212
+ */
213
+ onAction(type, handler) {
214
+ return this.store.onAction(type, (action, state, oldState) => {
215
+ handler(
216
+ action,
217
+ state.plugins[this.pluginId],
218
+ oldState.plugins[this.pluginId]
219
+ );
220
+ });
221
+ }
222
+ };
223
+
224
+ // src/lib/store/store.ts
225
+ var Store = class {
226
+ /**
227
+ * Initializes the store with the provided core state.
228
+ * @param reducer The core reducer function
229
+ * @param initialCoreState The initial core state
230
+ */
231
+ constructor(reducer, initialCoreState2) {
232
+ this.initialCoreState = initialCoreState2;
233
+ this.pluginReducers = {};
234
+ this.listeners = [];
235
+ this.pluginListeners = {};
236
+ this.state = { core: initialCoreState2, plugins: {} };
237
+ this.coreReducer = reducer;
238
+ }
239
+ /**
240
+ * Adds a reducer for a plugin-specific state.
241
+ * @param pluginId The unique identifier for the plugin.
242
+ * @param reducer The reducer function for the plugin state.
243
+ * @param initialState The initial state for the plugin.
244
+ */
245
+ addPluginReducer(pluginId, reducer, initialState) {
246
+ this.state.plugins[pluginId] = initialState;
247
+ this.pluginReducers[pluginId] = reducer;
248
+ }
249
+ /**
250
+ * Dispatches an action *only* to the core reducer.
251
+ * Notifies the global store listeners with (action, newState, oldState).
252
+ *
253
+ * @param action The action to dispatch, typed as CoreAction
254
+ * @returns The updated *global* store state
255
+ */
256
+ dispatchToCore(action) {
257
+ if (!this.coreReducer) {
258
+ return this.getState();
259
+ }
260
+ const oldState = this.getState();
261
+ this.state.core = this.coreReducer(this.state.core, action);
262
+ const newState = this.getState();
263
+ this.listeners.forEach((listener) => listener(action, newState, oldState));
264
+ return newState;
265
+ }
266
+ /**
267
+ * Dispatches an action *only* to a specific plugin.
268
+ * Optionally notifies global store listeners if `notifyGlobal` is true.
269
+ * Always notifies plugin-specific listeners with (action, newPluginState, oldPluginState).
270
+ *
271
+ * @param pluginId The plugin identifier
272
+ * @param action The plugin action to dispatch
273
+ * @param notifyGlobal Whether to also notify global store listeners
274
+ * @returns The updated *global* store state
275
+ */
276
+ dispatchToPlugin(pluginId, action, notifyGlobal = true) {
277
+ const oldGlobalState = this.getState();
278
+ const reducer = this.pluginReducers[pluginId];
279
+ if (!reducer) {
280
+ return oldGlobalState;
281
+ }
282
+ const oldPluginState = oldGlobalState.plugins[pluginId];
283
+ const newPluginState = reducer(oldPluginState, action);
284
+ this.state.plugins[pluginId] = newPluginState;
285
+ const newGlobalState = this.getState();
286
+ if (notifyGlobal) {
287
+ this.listeners.forEach((listener) => listener(action, newGlobalState, oldGlobalState));
288
+ }
289
+ if (this.pluginListeners[pluginId]) {
290
+ this.pluginListeners[pluginId].forEach((listener) => {
291
+ listener(action, newPluginState, oldPluginState);
292
+ });
293
+ }
294
+ return newPluginState;
295
+ }
296
+ /**
297
+ * Dispatches an action to update the state using:
298
+ * - the core reducer (if it's a CoreAction)
299
+ * - *all* plugin reducers (regardless of action type), with no global notify for each plugin
300
+ *
301
+ * Returns the new *global* store state after all reducers have processed the action.
302
+ *
303
+ * @param action The action to dispatch (can be CoreAction or any Action).
304
+ */
305
+ dispatch(action) {
306
+ const oldState = this.getState();
307
+ if (this.isCoreAction(action)) {
308
+ this.state.core = this.coreReducer(this.state.core, action);
309
+ }
310
+ for (const pluginId in this.pluginReducers) {
311
+ const reducer = this.pluginReducers[pluginId];
312
+ const oldPluginState = oldState.plugins[pluginId];
313
+ if (reducer) {
314
+ this.state.plugins[pluginId] = reducer(oldPluginState, action);
315
+ }
316
+ }
317
+ const newState = this.getState();
318
+ this.listeners.forEach((listener) => listener(action, newState, oldState));
319
+ return newState;
320
+ }
321
+ /**
322
+ * Returns a shallow copy of the current state.
323
+ * @returns The current store state.
324
+ */
325
+ getState() {
326
+ return {
327
+ core: { ...this.state.core },
328
+ plugins: { ...this.state.plugins }
329
+ };
330
+ }
331
+ /**
332
+ * Subscribes a listener to *global* state changes.
333
+ * The callback signature is now (action, newState, oldState).
334
+ *
335
+ * @param listener The callback to invoke on state changes
336
+ * @returns A function to unsubscribe the listener
337
+ */
338
+ subscribe(listener) {
339
+ this.listeners.push(listener);
340
+ return () => {
341
+ this.listeners = this.listeners.filter((l) => l !== listener);
342
+ };
343
+ }
344
+ /**
345
+ * Subscribes a listener to *plugin-specific* state changes.
346
+ * The callback signature is now (action, newPluginState, oldPluginState).
347
+ *
348
+ * @param pluginId The unique identifier for the plugin.
349
+ * @param listener The callback to invoke on plugin state changes.
350
+ * @returns A function to unsubscribe the listener.
351
+ */
352
+ subscribeToPlugin(pluginId, listener) {
353
+ if (!(pluginId in this.state.plugins)) {
354
+ throw new Error(
355
+ `Plugin state not found for plugin "${pluginId}". Did you forget to call addPluginReducer?`
356
+ );
357
+ }
358
+ if (!this.pluginListeners[pluginId]) {
359
+ this.pluginListeners[pluginId] = [];
360
+ }
361
+ this.pluginListeners[pluginId].push(listener);
362
+ return () => {
363
+ this.pluginListeners[pluginId] = this.pluginListeners[pluginId].filter((l) => l !== listener);
364
+ if (this.pluginListeners[pluginId].length === 0) {
365
+ delete this.pluginListeners[pluginId];
366
+ }
367
+ };
368
+ }
369
+ /**
370
+ * Subscribes to a specific action type (only from the core's action union).
371
+ * The callback signature is (action, newState, oldState).
372
+ *
373
+ * @param type The action type to listen for.
374
+ * @param handler The callback to invoke when the action occurs.
375
+ * @returns A function to unsubscribe the handler.
376
+ */
377
+ onAction(type, handler) {
378
+ return this.subscribe((action, newState, oldState) => {
379
+ if (action.type === type) {
380
+ handler(action, newState, oldState);
381
+ }
382
+ });
383
+ }
384
+ /**
385
+ * Gets a PluginStore handle for a specific plugin.
386
+ * @param pluginId The unique identifier for the plugin.
387
+ * @returns A PluginStore instance for the plugin.
388
+ */
389
+ getPluginStore(pluginId) {
390
+ if (!(pluginId in this.state.plugins)) {
391
+ throw new Error(
392
+ `Plugin state not found for plugin "${pluginId}". Did you forget to call addPluginReducer?`
393
+ );
394
+ }
395
+ return new PluginStore(this, pluginId);
396
+ }
397
+ /**
398
+ * Helper method to check if an action is a CoreAction.
399
+ * Adjust if you have a more refined way to differentiate CoreAction vs. any other Action.
400
+ */
401
+ isCoreAction(action) {
402
+ return action.type !== void 0;
403
+ }
404
+ /**
405
+ * Destroy the store: drop every listener and plugin reducer
406
+ */
407
+ destroy() {
408
+ this.listeners.length = 0;
409
+ for (const id in this.pluginListeners) {
410
+ this.pluginListeners[id]?.splice?.(0);
411
+ }
412
+ this.pluginListeners = {};
413
+ this.pluginReducers = {};
414
+ this.state.plugins = {};
415
+ this.state.core = { ...this.initialCoreState };
416
+ }
417
+ };
418
+
419
+ // src/lib/store/initial-state.ts
420
+ var import_models = require("@embedpdf/models");
421
+ var initialCoreState = (config) => ({
422
+ scale: config?.scale ?? 1,
423
+ rotation: config?.rotation ?? import_models.Rotation.Degree0,
424
+ document: null,
425
+ pages: [],
426
+ loading: false,
427
+ error: null
428
+ });
429
+
430
+ // src/lib/store/actions.ts
431
+ var LOAD_DOCUMENT = "LOAD_DOCUMENT";
432
+ var SET_DOCUMENT = "SET_DOCUMENT";
433
+ var SET_DOCUMENT_ERROR = "SET_DOCUMENT_ERROR";
434
+ var SET_SCALE = "SET_SCALE";
435
+ var SET_ROTATION = "SET_ROTATION";
436
+ var SET_PAGES = "SET_PAGES";
437
+ var loadDocument = () => ({ type: LOAD_DOCUMENT });
438
+ var setDocument = (document) => ({
439
+ type: SET_DOCUMENT,
440
+ payload: document
441
+ });
442
+ var setDocumentError = (error) => ({
443
+ type: SET_DOCUMENT_ERROR,
444
+ payload: error
445
+ });
446
+ var setScale = (scale) => ({ type: SET_SCALE, payload: scale });
447
+ var setRotation = (rotation) => ({
448
+ type: SET_ROTATION,
449
+ payload: rotation
450
+ });
451
+ var setPages = (pages) => ({
452
+ type: SET_PAGES,
453
+ payload: pages
454
+ });
455
+
456
+ // src/lib/store/selectors.ts
457
+ var import_models2 = require("@embedpdf/models");
458
+ var getPagesWithRotatedSize = (state) => {
459
+ return state.pages.map(
460
+ (page) => page.map((p) => ({
461
+ ...p,
462
+ rotatedSize: (0, import_models2.transformSize)(p.size, state.rotation, 1)
463
+ }))
464
+ );
465
+ };
466
+
467
+ // src/lib/store/reducer.ts
468
+ var coreReducer = (state, action) => {
469
+ switch (action.type) {
470
+ case LOAD_DOCUMENT:
471
+ return {
472
+ ...state,
473
+ loading: true,
474
+ error: null
475
+ };
476
+ case SET_DOCUMENT:
477
+ return {
478
+ ...state,
479
+ document: action.payload,
480
+ pages: action.payload.pages.map((page) => [page]),
481
+ loading: false,
482
+ error: null
483
+ };
484
+ case SET_ROTATION:
485
+ return {
486
+ ...state,
487
+ rotation: action.payload
488
+ };
489
+ case SET_PAGES:
490
+ return {
491
+ ...state,
492
+ pages: action.payload
493
+ };
494
+ case SET_DOCUMENT_ERROR:
495
+ return {
496
+ ...state,
497
+ loading: false,
498
+ error: action.payload
499
+ };
500
+ case SET_SCALE:
501
+ return {
502
+ ...state,
503
+ scale: action.payload
504
+ };
505
+ default:
506
+ return state;
507
+ }
508
+ };
509
+
510
+ // src/lib/registry/plugin-registry.ts
511
+ var PluginRegistry = class {
512
+ constructor(engine, config) {
513
+ this.plugins = /* @__PURE__ */ new Map();
514
+ this.manifests = /* @__PURE__ */ new Map();
515
+ this.capabilities = /* @__PURE__ */ new Map();
516
+ // capability -> pluginId
517
+ this.status = /* @__PURE__ */ new Map();
518
+ this.configurations = /* @__PURE__ */ new Map();
519
+ this.engineInitialized = false;
520
+ this.initPromise = null;
521
+ this.pendingRegistrations = [];
522
+ this.processingRegistrations = [];
523
+ this.initialized = false;
524
+ this.isInitializing = false;
525
+ this.pluginsReadyPromise = null;
526
+ this.destroyed = false;
527
+ this.resolver = new DependencyResolver();
528
+ this.engine = engine;
529
+ this.initialCoreState = initialCoreState(config);
530
+ this.store = new Store(coreReducer, this.initialCoreState);
531
+ }
532
+ /**
533
+ * Ensure engine is initialized before proceeding
534
+ */
535
+ async ensureEngineInitialized() {
536
+ if (this.engineInitialized) {
537
+ return;
538
+ }
539
+ if (this.engine.initialize) {
540
+ const task = this.engine.initialize();
541
+ await task.toPromise();
542
+ this.engineInitialized = true;
543
+ } else {
544
+ this.engineInitialized = true;
545
+ }
546
+ }
547
+ /**
548
+ * Register a plugin without initializing it
549
+ */
550
+ registerPlugin(pluginPackage, config) {
551
+ if (this.initialized && !this.isInitializing) {
552
+ throw new PluginRegistrationError("Cannot register plugins after initialization");
553
+ }
554
+ this.validateManifest(pluginPackage.manifest);
555
+ this.store.addPluginReducer(
556
+ pluginPackage.manifest.id,
557
+ // We need one type assertion here since we can't fully reconcile TAction with Action
558
+ // due to TypeScript's type system limitations with generic variance
559
+ pluginPackage.reducer,
560
+ "function" === typeof pluginPackage.initialState ? pluginPackage.initialState(
561
+ this.initialCoreState,
562
+ {
563
+ ...pluginPackage.manifest.defaultConfig,
564
+ ...config
565
+ }
566
+ ) : pluginPackage.initialState
567
+ );
568
+ this.pendingRegistrations.push({
569
+ package: pluginPackage,
570
+ config
571
+ });
572
+ }
573
+ /**
574
+ * Get the central store instance
575
+ */
576
+ getStore() {
577
+ return this.store;
578
+ }
579
+ /**
580
+ * Get the engine instance
581
+ */
582
+ getEngine() {
583
+ return this.engine;
584
+ }
585
+ /**
586
+ * Get a promise that resolves when all plugins are ready
587
+ */
588
+ pluginsReady() {
589
+ if (this.pluginsReadyPromise) {
590
+ return this.pluginsReadyPromise;
591
+ }
592
+ this.pluginsReadyPromise = (async () => {
593
+ if (!this.initialized) {
594
+ await this.initialize();
595
+ }
596
+ const readyPromises = Array.from(this.plugins.values()).map(
597
+ (p) => typeof p.ready === "function" ? p.ready() : Promise.resolve()
598
+ );
599
+ await Promise.all(readyPromises);
600
+ })();
601
+ return this.pluginsReadyPromise;
602
+ }
603
+ /**
604
+ * INITIALISE THE REGISTRY – runs once no-matter-how-many calls *
605
+ */
606
+ async initialize() {
607
+ if (this.destroyed) {
608
+ throw new PluginRegistrationError("Registry has been destroyed");
609
+ }
610
+ if (this.initPromise) {
611
+ return this.initPromise;
612
+ }
613
+ this.initPromise = (async () => {
614
+ if (this.initialized) {
615
+ throw new PluginRegistrationError("Registry is already initialized");
616
+ }
617
+ this.isInitializing = true;
618
+ try {
619
+ await this.ensureEngineInitialized();
620
+ if (this.destroyed) {
621
+ return;
622
+ }
623
+ while (this.pendingRegistrations.length > 0) {
624
+ if (this.destroyed) {
625
+ return;
626
+ }
627
+ this.processingRegistrations = [...this.pendingRegistrations];
628
+ this.pendingRegistrations = [];
629
+ for (const reg of this.processingRegistrations) {
630
+ const dependsOn = /* @__PURE__ */ new Set();
631
+ const allDeps = [...reg.package.manifest.requires, ...reg.package.manifest.optional];
632
+ for (const cap of allDeps) {
633
+ const provider = this.processingRegistrations.find(
634
+ (r) => r.package.manifest.provides.includes(cap)
635
+ );
636
+ if (provider) dependsOn.add(provider.package.manifest.id);
637
+ }
638
+ this.resolver.addNode(reg.package.manifest.id, [...dependsOn]);
639
+ }
640
+ const loadOrder = this.resolver.resolveLoadOrder();
641
+ for (const id of loadOrder) {
642
+ const reg = this.processingRegistrations.find((r) => r.package.manifest.id === id);
643
+ await this.initializePlugin(reg.package.manifest, reg.package.create, reg.config);
644
+ }
645
+ this.processingRegistrations = [];
646
+ this.resolver = new DependencyResolver();
647
+ }
648
+ for (const plugin of this.plugins.values()) {
649
+ await plugin.postInitialize?.().catch((e) => {
650
+ console.error(`Error in postInitialize for plugin ${plugin.id}`, e);
651
+ this.status.set(plugin.id, "error");
652
+ });
653
+ }
654
+ this.initialized = true;
655
+ } catch (err) {
656
+ if (err instanceof Error) {
657
+ throw new CircularDependencyError(
658
+ `Failed to resolve plugin dependencies: ${err.message}`
659
+ );
660
+ }
661
+ throw err;
662
+ } finally {
663
+ this.isInitializing = false;
664
+ }
665
+ })();
666
+ return this.initPromise;
667
+ }
668
+ /**
669
+ * Initialize a single plugin with all necessary checks
670
+ */
671
+ async initializePlugin(manifest, packageCreator, config) {
672
+ const finalConfig = {
673
+ ...manifest.defaultConfig,
674
+ ...config
675
+ };
676
+ this.validateConfig(manifest.id, finalConfig, manifest.defaultConfig);
677
+ const plugin = packageCreator(this, this.engine, finalConfig);
678
+ this.validatePlugin(plugin);
679
+ for (const capability of manifest.requires) {
680
+ if (!this.capabilities.has(capability)) {
681
+ throw new PluginRegistrationError(
682
+ `Missing required capability: ${capability} for plugin ${manifest.id}`
683
+ );
684
+ }
685
+ }
686
+ for (const capability of manifest.optional) {
687
+ if (this.capabilities.has(capability)) {
688
+ console.debug(`Optional capability ${capability} is available for plugin ${manifest.id}`);
689
+ }
690
+ }
691
+ console.log("initializePlugin", manifest.id, manifest.provides);
692
+ for (const capability of manifest.provides) {
693
+ if (this.capabilities.has(capability)) {
694
+ throw new PluginRegistrationError(
695
+ `Capability ${capability} is already provided by plugin ${this.capabilities.get(capability)}`
696
+ );
697
+ }
698
+ this.capabilities.set(capability, manifest.id);
699
+ }
700
+ this.plugins.set(manifest.id, plugin);
701
+ this.manifests.set(manifest.id, manifest);
702
+ this.status.set(manifest.id, "registered");
703
+ this.configurations.set(manifest.id, finalConfig);
704
+ try {
705
+ if (plugin.initialize) {
706
+ await plugin.initialize(finalConfig);
707
+ }
708
+ this.status.set(manifest.id, "active");
709
+ } catch (error) {
710
+ this.plugins.delete(manifest.id);
711
+ this.manifests.delete(manifest.id);
712
+ console.log("initializePlugin failed", manifest.id, manifest.provides);
713
+ manifest.provides.forEach((cap) => this.capabilities.delete(cap));
714
+ throw error;
715
+ }
716
+ }
717
+ getPluginConfig(pluginId) {
718
+ const config = this.configurations.get(pluginId);
719
+ if (!config) {
720
+ throw new PluginNotFoundError(`Configuration for plugin ${pluginId} not found`);
721
+ }
722
+ return config;
723
+ }
724
+ validateConfig(pluginId, config, defaultConfig) {
725
+ const requiredKeys = Object.keys(defaultConfig);
726
+ const missingKeys = requiredKeys.filter((key) => !config.hasOwnProperty(key));
727
+ if (missingKeys.length > 0) {
728
+ throw new PluginConfigurationError(
729
+ `Missing required configuration keys for plugin ${pluginId}: ${missingKeys.join(", ")}`
730
+ );
731
+ }
732
+ }
733
+ async updatePluginConfig(pluginId, config) {
734
+ const plugin = this.getPlugin(pluginId);
735
+ if (!plugin) {
736
+ throw new PluginNotFoundError(`Plugin ${pluginId} not found`);
737
+ }
738
+ const manifest = this.manifests.get(pluginId);
739
+ const currentConfig = this.configurations.get(pluginId);
740
+ if (!manifest || !currentConfig) {
741
+ throw new PluginNotFoundError(`Plugin ${pluginId} not found`);
742
+ }
743
+ const newConfig = {
744
+ ...currentConfig,
745
+ ...config
746
+ };
747
+ this.validateConfig(pluginId, newConfig, manifest.defaultConfig);
748
+ this.configurations.set(pluginId, newConfig);
749
+ if (plugin.initialize) {
750
+ await plugin.initialize(newConfig);
751
+ }
752
+ }
753
+ /**
754
+ * Register multiple plugins at once
755
+ */
756
+ registerPluginBatch(registrations) {
757
+ for (const reg of registrations) {
758
+ this.registerPlugin(reg.package, reg.config);
759
+ }
760
+ }
761
+ /**
762
+ * Unregister a plugin
763
+ */
764
+ async unregisterPlugin(pluginId) {
765
+ const plugin = this.plugins.get(pluginId);
766
+ if (!plugin) {
767
+ throw new PluginNotFoundError(`Plugin ${pluginId} is not registered`);
768
+ }
769
+ const manifest = this.manifests.get(pluginId);
770
+ if (!manifest) {
771
+ throw new PluginNotFoundError(`Manifest for plugin ${pluginId} not found`);
772
+ }
773
+ for (const [otherId, otherManifest] of this.manifests.entries()) {
774
+ if (otherId === pluginId) continue;
775
+ const dependsOnThis = [...otherManifest.requires, ...otherManifest.optional].some(
776
+ (cap) => manifest.provides.includes(cap)
777
+ );
778
+ if (dependsOnThis) {
779
+ throw new PluginRegistrationError(
780
+ `Cannot unregister plugin ${pluginId}: plugin ${otherId} depends on it`
781
+ );
782
+ }
783
+ }
784
+ try {
785
+ if (plugin.destroy) {
786
+ await plugin.destroy();
787
+ }
788
+ for (const capability of manifest.provides) {
789
+ this.capabilities.delete(capability);
790
+ }
791
+ this.plugins.delete(pluginId);
792
+ this.manifests.delete(pluginId);
793
+ this.status.delete(pluginId);
794
+ } catch (error) {
795
+ if (error instanceof Error) {
796
+ throw new Error(`Failed to unregister plugin ${pluginId}: ${error.message}`);
797
+ }
798
+ throw error;
799
+ }
800
+ }
801
+ /**
802
+ * Get a plugin instance
803
+ * @param pluginId The ID of the plugin to get
804
+ * @returns The plugin instance or null if not found
805
+ */
806
+ getPlugin(pluginId) {
807
+ const plugin = this.plugins.get(pluginId);
808
+ if (!plugin) {
809
+ return null;
810
+ }
811
+ return plugin;
812
+ }
813
+ /**
814
+ * Get a plugin that provides a specific capability
815
+ * @param capability The capability to get a provider for
816
+ * @returns The plugin providing the capability or null if not found
817
+ */
818
+ getCapabilityProvider(capability) {
819
+ const pluginId = this.capabilities.get(capability);
820
+ if (!pluginId) {
821
+ return null;
822
+ }
823
+ return this.getPlugin(pluginId);
824
+ }
825
+ /**
826
+ * Check if a capability is available
827
+ */
828
+ hasCapability(capability) {
829
+ return this.capabilities.has(capability);
830
+ }
831
+ /**
832
+ * Get all registered plugins
833
+ */
834
+ getAllPlugins() {
835
+ return Array.from(this.plugins.values());
836
+ }
837
+ /**
838
+ * Get plugin status
839
+ */
840
+ getPluginStatus(pluginId) {
841
+ const status = this.status.get(pluginId);
842
+ if (!status) {
843
+ throw new PluginNotFoundError(`Plugin ${pluginId} not found`);
844
+ }
845
+ return status;
846
+ }
847
+ /**
848
+ * Validate plugin object
849
+ */
850
+ validatePlugin(plugin) {
851
+ if (!plugin.id) {
852
+ throw new PluginRegistrationError("Plugin must have an id");
853
+ }
854
+ }
855
+ /**
856
+ * Validate plugin manifest
857
+ */
858
+ validateManifest(manifest) {
859
+ if (!manifest.id) {
860
+ throw new PluginRegistrationError("Manifest must have an id");
861
+ }
862
+ if (!manifest.name) {
863
+ throw new PluginRegistrationError("Manifest must have a name");
864
+ }
865
+ if (!manifest.version) {
866
+ throw new PluginRegistrationError("Manifest must have a version");
867
+ }
868
+ if (!Array.isArray(manifest.provides)) {
869
+ throw new PluginRegistrationError("Manifest must have a provides array");
870
+ }
871
+ if (!Array.isArray(manifest.requires)) {
872
+ throw new PluginRegistrationError("Manifest must have a requires array");
873
+ }
874
+ if (!Array.isArray(manifest.optional)) {
875
+ throw new PluginRegistrationError("Manifest must have an optional array");
876
+ }
877
+ }
878
+ isDestroyed() {
879
+ return this.destroyed;
880
+ }
881
+ /**
882
+ * DESTROY EVERYTHING – waits for any ongoing initialise(), once *
883
+ */
884
+ async destroy() {
885
+ if (this.destroyed) throw new PluginRegistrationError("Registry has already been destroyed");
886
+ this.destroyed = true;
887
+ try {
888
+ await this.initPromise;
889
+ } catch {
890
+ }
891
+ for (const plugin of Array.from(this.plugins.values()).reverse()) {
892
+ await plugin.destroy?.();
893
+ }
894
+ this.store.destroy();
895
+ this.plugins.clear();
896
+ this.manifests.clear();
897
+ this.capabilities.clear();
898
+ this.status.clear();
899
+ this.pendingRegistrations.length = 0;
900
+ this.processingRegistrations.length = 0;
901
+ }
902
+ };
903
+
904
+ // src/lib/utils/plugin-helpers.ts
905
+ function createPluginRegistration(pluginPackage, config) {
906
+ return {
907
+ package: pluginPackage,
908
+ config
909
+ };
910
+ }
911
+
912
+ // src/lib/base/base-plugin.ts
913
+ var BasePlugin = class {
914
+ constructor(id, registry) {
915
+ this.id = id;
916
+ this.registry = registry;
917
+ // Track debounced actions
918
+ this.debouncedActions = {};
919
+ this.unsubscribeFromState = null;
920
+ this.unsubscribeFromCoreStore = null;
921
+ if (id !== this.constructor.id) {
922
+ throw new Error(
923
+ `Plugin ID mismatch: ${id} !== ${this.constructor.id}`
924
+ );
925
+ }
926
+ this.coreStore = this.registry.getStore();
927
+ this.pluginStore = this.coreStore.getPluginStore(this.id);
928
+ this.unsubscribeFromState = this.pluginStore.subscribeToState((action, newState, oldState) => {
929
+ this.onStoreUpdated(oldState, newState);
930
+ });
931
+ this.unsubscribeFromCoreStore = this.coreStore.subscribe((action, newState, oldState) => {
932
+ this.onCoreStoreUpdated(oldState, newState);
933
+ });
934
+ this.readyPromise = new Promise((resolve) => {
935
+ this.readyResolve = resolve;
936
+ });
937
+ this.readyResolve();
938
+ }
939
+ provides() {
940
+ if (!this._capability) {
941
+ const cap = this.buildCapability();
942
+ this._capability = Object.freeze(cap);
943
+ }
944
+ return this._capability;
945
+ }
946
+ /**
947
+ * Get a copy of the current state
948
+ */
949
+ get state() {
950
+ return this.pluginStore.getState();
951
+ }
952
+ /**
953
+ * Get a copy of the current core state
954
+ */
955
+ get coreState() {
956
+ return this.coreStore.getState();
957
+ }
958
+ /**
959
+ * @deprecated use `this.state` Get a copy of the current state
960
+ */
961
+ getState() {
962
+ return this.pluginStore.getState();
963
+ }
964
+ /**
965
+ * @deprecated use `this.coreState` Get a copy of the current core state
966
+ */
967
+ getCoreState() {
968
+ return this.coreStore.getState();
969
+ }
970
+ /**
971
+ * Core Dispatch
972
+ */
973
+ dispatchCoreAction(action) {
974
+ return this.coreStore.dispatch(action);
975
+ }
976
+ /**
977
+ * Dispatch an action
978
+ */
979
+ dispatch(action) {
980
+ return this.pluginStore.dispatch(action);
981
+ }
982
+ /**
983
+ * Dispatch an action with debouncing to prevent rapid repeated calls
984
+ * @param action The action to dispatch
985
+ * @param debounceTime Time in ms to debounce (default: 100ms)
986
+ * @returns boolean indicating whether the action was dispatched or debounced
987
+ */
988
+ debouncedDispatch(action, debounceTime = 100) {
989
+ const now = Date.now();
990
+ const lastActionTime = this.debouncedActions[action.type] || 0;
991
+ if (now - lastActionTime >= debounceTime) {
992
+ this.debouncedActions[action.type] = now;
993
+ this.dispatch(action);
994
+ return true;
995
+ }
996
+ return false;
997
+ }
998
+ /**
999
+ * Subscribe to state changes
1000
+ */
1001
+ subscribe(listener) {
1002
+ return this.pluginStore.subscribeToState(listener);
1003
+ }
1004
+ /**
1005
+ * Subscribe to core store changes
1006
+ */
1007
+ subscribeToCoreStore(listener) {
1008
+ return this.coreStore.subscribe(listener);
1009
+ }
1010
+ /**
1011
+ * Called when the plugin store state is updated
1012
+ * @param oldState Previous state
1013
+ * @param newState New state
1014
+ */
1015
+ onStoreUpdated(oldState, newState) {
1016
+ }
1017
+ /**
1018
+ * Called when the core store state is updated
1019
+ * @param oldState Previous state
1020
+ * @param newState New state
1021
+ */
1022
+ onCoreStoreUpdated(oldState, newState) {
1023
+ }
1024
+ /**
1025
+ * Cleanup method to be called when plugin is being destroyed
1026
+ */
1027
+ destroy() {
1028
+ if (this.unsubscribeFromState) {
1029
+ this.unsubscribeFromState();
1030
+ this.unsubscribeFromState = null;
1031
+ }
1032
+ if (this.unsubscribeFromCoreStore) {
1033
+ this.unsubscribeFromCoreStore();
1034
+ this.unsubscribeFromCoreStore = null;
1035
+ }
1036
+ }
1037
+ /**
1038
+ * Returns a promise that resolves when the plugin is ready
1039
+ */
1040
+ ready() {
1041
+ return this.readyPromise;
1042
+ }
1043
+ /**
1044
+ * Mark the plugin as ready
1045
+ */
1046
+ markReady() {
1047
+ this.readyResolve();
1048
+ }
1049
+ /**
1050
+ * Reset the ready state (useful for plugins that need to reinitialize)
1051
+ */
1052
+ resetReady() {
1053
+ this.readyPromise = new Promise((resolve) => {
1054
+ this.readyResolve = resolve;
1055
+ });
1056
+ }
1057
+ };
1058
+
1059
+ // src/lib/utils/event-control.ts
1060
+ var EventControl = class {
1061
+ constructor(handler, options) {
1062
+ this.handler = handler;
1063
+ this.options = options;
1064
+ this.lastRun = 0;
1065
+ this.handle = (data) => {
1066
+ if (this.options.mode === "debounce") {
1067
+ this.debounce(data);
1068
+ } else {
1069
+ this.throttle(data);
1070
+ }
1071
+ };
1072
+ }
1073
+ debounce(data) {
1074
+ if (this.timeoutId) {
1075
+ window.clearTimeout(this.timeoutId);
1076
+ }
1077
+ this.timeoutId = window.setTimeout(() => {
1078
+ this.handler(data);
1079
+ this.timeoutId = void 0;
1080
+ }, this.options.wait);
1081
+ }
1082
+ throttle(data) {
1083
+ if (this.options.mode === "debounce") return;
1084
+ const now = Date.now();
1085
+ const throttleMode = this.options.throttleMode || "leading-trailing";
1086
+ if (now - this.lastRun >= this.options.wait) {
1087
+ if (throttleMode === "leading-trailing") {
1088
+ this.handler(data);
1089
+ }
1090
+ this.lastRun = now;
1091
+ }
1092
+ if (this.timeoutId) {
1093
+ window.clearTimeout(this.timeoutId);
1094
+ }
1095
+ this.timeoutId = window.setTimeout(
1096
+ () => {
1097
+ this.handler(data);
1098
+ this.lastRun = Date.now();
1099
+ this.timeoutId = void 0;
1100
+ },
1101
+ this.options.wait - (now - this.lastRun)
1102
+ );
1103
+ }
1104
+ destroy() {
1105
+ if (this.timeoutId) {
1106
+ window.clearTimeout(this.timeoutId);
1107
+ }
1108
+ }
1109
+ };
1110
+
1111
+ // src/lib/utils/math.ts
1112
+ function clamp(value, min, max) {
1113
+ return value < min ? min : value > max ? max : value;
1114
+ }
1115
+ function arePropsEqual(a, b, visited) {
1116
+ if (a === b) {
1117
+ return true;
1118
+ }
1119
+ if (a == null || b == null) {
1120
+ return a === b;
1121
+ }
1122
+ const aType = typeof a;
1123
+ const bType = typeof b;
1124
+ if (aType !== bType) return false;
1125
+ if (aType === "object") {
1126
+ if (!visited) visited = /* @__PURE__ */ new Set();
1127
+ const pairId = getPairId(a, b);
1128
+ if (visited.has(pairId)) {
1129
+ return true;
1130
+ }
1131
+ visited.add(pairId);
1132
+ const aIsArray = Array.isArray(a);
1133
+ const bIsArray = Array.isArray(b);
1134
+ if (aIsArray && bIsArray) {
1135
+ return arraysEqualUnordered(a, b, visited);
1136
+ } else if (!aIsArray && !bIsArray) {
1137
+ return objectsEqual(a, b, visited);
1138
+ } else {
1139
+ return false;
1140
+ }
1141
+ }
1142
+ return false;
1143
+ }
1144
+ function getPairId(a, b) {
1145
+ return `${objectId(a)}__${objectId(b)}`;
1146
+ }
1147
+ var objectIdCounter = 0;
1148
+ var objectIds = /* @__PURE__ */ new WeakMap();
1149
+ function objectId(obj) {
1150
+ if (!objectIds.has(obj)) {
1151
+ objectIds.set(obj, ++objectIdCounter);
1152
+ }
1153
+ return objectIds.get(obj);
1154
+ }
1155
+ function arraysEqualUnordered(a, b, visited) {
1156
+ if (a.length !== b.length) return false;
1157
+ const used = new Array(b.length).fill(false);
1158
+ outer: for (let i = 0; i < a.length; i++) {
1159
+ const elemA = a[i];
1160
+ for (let j = 0; j < b.length; j++) {
1161
+ if (used[j]) continue;
1162
+ if (arePropsEqual(elemA, b[j], visited)) {
1163
+ used[j] = true;
1164
+ continue outer;
1165
+ }
1166
+ }
1167
+ return false;
1168
+ }
1169
+ return true;
1170
+ }
1171
+ function objectsEqual(a, b, visited) {
1172
+ const aKeys = Object.keys(a).sort();
1173
+ const bKeys = Object.keys(b).sort();
1174
+ if (aKeys.length !== bKeys.length) return false;
1175
+ for (let i = 0; i < aKeys.length; i++) {
1176
+ if (aKeys[i] !== bKeys[i]) return false;
1177
+ }
1178
+ for (const key of aKeys) {
1179
+ const valA = a[key];
1180
+ const valB = b[key];
1181
+ if (!arePropsEqual(valA, valB, visited)) {
1182
+ return false;
1183
+ }
1184
+ }
1185
+ return true;
1186
+ }
1187
+
1188
+ // src/lib/utils/eventing.ts
1189
+ function createEmitter() {
1190
+ const listeners = /* @__PURE__ */ new Set();
1191
+ const on = (l) => {
1192
+ listeners.add(l);
1193
+ return () => listeners.delete(l);
1194
+ };
1195
+ return {
1196
+ emit: (v = void 0) => listeners.forEach((l) => l(v)),
1197
+ on,
1198
+ off: (l) => listeners.delete(l),
1199
+ clear: () => listeners.clear()
1200
+ };
1201
+ }
1202
+ function createBehaviorEmitter(initial, equality = arePropsEqual) {
1203
+ const listeners = /* @__PURE__ */ new Set();
1204
+ const proxyMap = /* @__PURE__ */ new Map();
1205
+ let _value = initial;
1206
+ const notify = (v) => listeners.forEach((l) => l(v));
1207
+ const baseOn = (listener, options) => {
1208
+ let realListener = listener;
1209
+ let destroy = () => {
1210
+ };
1211
+ if (options) {
1212
+ const ctl = new EventControl(listener, options);
1213
+ realListener = ctl.handle;
1214
+ destroy = () => ctl.destroy();
1215
+ proxyMap.set(listener, { wrapped: realListener, destroy });
1216
+ }
1217
+ if (_value !== void 0) realListener(_value);
1218
+ listeners.add(realListener);
1219
+ return () => {
1220
+ listeners.delete(realListener);
1221
+ destroy();
1222
+ proxyMap.delete(listener);
1223
+ };
1224
+ };
1225
+ return {
1226
+ /* emitter behaviour ---------------------------------------- */
1227
+ get value() {
1228
+ return _value;
1229
+ },
1230
+ emit(v = void 0) {
1231
+ if (_value === void 0 || !equality(_value, v)) {
1232
+ _value = v;
1233
+ notify(v);
1234
+ }
1235
+ },
1236
+ on: baseOn,
1237
+ off(listener) {
1238
+ const proxy = proxyMap.get(listener);
1239
+ if (proxy) {
1240
+ listeners.delete(proxy.wrapped);
1241
+ proxy.destroy();
1242
+ proxyMap.delete(listener);
1243
+ } else {
1244
+ listeners.delete(listener);
1245
+ }
1246
+ },
1247
+ clear() {
1248
+ listeners.clear();
1249
+ proxyMap.forEach((p) => p.destroy());
1250
+ proxyMap.clear();
1251
+ },
1252
+ /* derived hook --------------------------------------------- */
1253
+ select(selector, eq = arePropsEqual) {
1254
+ return (listener, options) => {
1255
+ let prev;
1256
+ if (_value !== void 0) {
1257
+ const mapped = selector(_value);
1258
+ prev = mapped;
1259
+ listener(mapped);
1260
+ }
1261
+ return baseOn(
1262
+ (next) => {
1263
+ const mapped = selector(next);
1264
+ if (prev === void 0 || !eq(prev, mapped)) {
1265
+ prev = mapped;
1266
+ listener(mapped);
1267
+ }
1268
+ },
1269
+ options
1270
+ );
1271
+ };
1272
+ }
1273
+ };
1274
+ }
1275
+ // Annotate the CommonJS export names for ESM import in node:
1276
+ 0 && (module.exports = {
1277
+ BasePlugin,
1278
+ CapabilityConflictError,
1279
+ CapabilityNotFoundError,
1280
+ CircularDependencyError,
1281
+ DependencyResolver,
1282
+ EventControl,
1283
+ LOAD_DOCUMENT,
1284
+ PluginConfigurationError,
1285
+ PluginInitializationError,
1286
+ PluginNotFoundError,
1287
+ PluginRegistrationError,
1288
+ PluginRegistry,
1289
+ SET_DOCUMENT,
1290
+ SET_DOCUMENT_ERROR,
1291
+ SET_PAGES,
1292
+ SET_ROTATION,
1293
+ SET_SCALE,
1294
+ arePropsEqual,
1295
+ clamp,
1296
+ createBehaviorEmitter,
1297
+ createEmitter,
1298
+ createPluginRegistration,
1299
+ getPagesWithRotatedSize,
1300
+ initialCoreState,
1301
+ loadDocument,
1302
+ setDocument,
1303
+ setDocumentError,
1304
+ setPages,
1305
+ setRotation,
1306
+ setScale
1307
+ });
1308
+ //# sourceMappingURL=index.cjs.map