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