diff --git a/Plugins/CSVExportPlugin/Info.plist b/Plugins/CSVExportPlugin/Info.plist
index f6d13edb8..f50f93f63 100644
--- a/Plugins/CSVExportPlugin/Info.plist
+++ b/Plugins/CSVExportPlugin/Info.plist
@@ -4,5 +4,9 @@
TableProPluginKitVersion
9
+ TableProProvidesExportFormatIds
+
+ csv
+
diff --git a/Plugins/ClickHouseDriverPlugin/Info.plist b/Plugins/ClickHouseDriverPlugin/Info.plist
index f6d13edb8..6b37ff721 100644
--- a/Plugins/ClickHouseDriverPlugin/Info.plist
+++ b/Plugins/ClickHouseDriverPlugin/Info.plist
@@ -4,5 +4,9 @@
TableProPluginKitVersion
9
+ TableProProvidesDatabaseTypeIds
+
+ ClickHouse
+
diff --git a/Plugins/JSONExportPlugin/Info.plist b/Plugins/JSONExportPlugin/Info.plist
index f6d13edb8..f06c7ab70 100644
--- a/Plugins/JSONExportPlugin/Info.plist
+++ b/Plugins/JSONExportPlugin/Info.plist
@@ -4,5 +4,9 @@
TableProPluginKitVersion
9
+ TableProProvidesExportFormatIds
+
+ json
+
diff --git a/Plugins/MQLExportPlugin/Info.plist b/Plugins/MQLExportPlugin/Info.plist
index f6d13edb8..712fd7466 100644
--- a/Plugins/MQLExportPlugin/Info.plist
+++ b/Plugins/MQLExportPlugin/Info.plist
@@ -4,5 +4,9 @@
TableProPluginKitVersion
9
+ TableProProvidesExportFormatIds
+
+ mql
+
diff --git a/Plugins/MySQLDriverPlugin/Info.plist b/Plugins/MySQLDriverPlugin/Info.plist
index f6d13edb8..6da0a44b2 100644
--- a/Plugins/MySQLDriverPlugin/Info.plist
+++ b/Plugins/MySQLDriverPlugin/Info.plist
@@ -4,5 +4,10 @@
TableProPluginKitVersion
9
+ TableProProvidesDatabaseTypeIds
+
+ MySQL
+ MariaDB
+
diff --git a/Plugins/PostgreSQLDriverPlugin/Info.plist b/Plugins/PostgreSQLDriverPlugin/Info.plist
index f6d13edb8..35bc385e1 100644
--- a/Plugins/PostgreSQLDriverPlugin/Info.plist
+++ b/Plugins/PostgreSQLDriverPlugin/Info.plist
@@ -4,5 +4,10 @@
TableProPluginKitVersion
9
+ TableProProvidesDatabaseTypeIds
+
+ PostgreSQL
+ Redshift
+
diff --git a/Plugins/RedisDriverPlugin/Info.plist b/Plugins/RedisDriverPlugin/Info.plist
index 2b8a3c0bb..4c234f85f 100644
--- a/Plugins/RedisDriverPlugin/Info.plist
+++ b/Plugins/RedisDriverPlugin/Info.plist
@@ -18,9 +18,13 @@
$(MARKETING_VERSION)
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
- TableProPluginKitVersion
- 9
NSPrincipalClass
$(PRODUCT_MODULE_NAME).RedisPlugin
+ TableProPluginKitVersion
+ 9
+ TableProProvidesDatabaseTypeIds
+
+ Redis
+
diff --git a/Plugins/SQLExportPlugin/Info.plist b/Plugins/SQLExportPlugin/Info.plist
index f6d13edb8..f69ea8ee0 100644
--- a/Plugins/SQLExportPlugin/Info.plist
+++ b/Plugins/SQLExportPlugin/Info.plist
@@ -4,5 +4,9 @@
TableProPluginKitVersion
9
+ TableProProvidesExportFormatIds
+
+ sql
+
diff --git a/Plugins/SQLImportPlugin/Info.plist b/Plugins/SQLImportPlugin/Info.plist
index f6d13edb8..a3bc134ae 100644
--- a/Plugins/SQLImportPlugin/Info.plist
+++ b/Plugins/SQLImportPlugin/Info.plist
@@ -4,5 +4,9 @@
TableProPluginKitVersion
9
+ TableProProvidesImportFormatIds
+
+ sql
+
diff --git a/Plugins/SQLiteDriverPlugin/Info.plist b/Plugins/SQLiteDriverPlugin/Info.plist
index f6d13edb8..8332ca837 100644
--- a/Plugins/SQLiteDriverPlugin/Info.plist
+++ b/Plugins/SQLiteDriverPlugin/Info.plist
@@ -4,5 +4,9 @@
TableProPluginKitVersion
9
+ TableProProvidesDatabaseTypeIds
+
+ SQLite
+
diff --git a/Plugins/XLSXExportPlugin/Info.plist b/Plugins/XLSXExportPlugin/Info.plist
index f6d13edb8..99937c938 100644
--- a/Plugins/XLSXExportPlugin/Info.plist
+++ b/Plugins/XLSXExportPlugin/Info.plist
@@ -4,5 +4,9 @@
TableProPluginKitVersion
9
+ TableProProvidesExportFormatIds
+
+ xlsx
+
diff --git a/TablePro/Core/Database/DatabaseDriver.swift b/TablePro/Core/Database/DatabaseDriver.swift
index 9708f8eee..7755e9bfd 100644
--- a/TablePro/Core/Database/DatabaseDriver.swift
+++ b/TablePro/Core/Database/DatabaseDriver.swift
@@ -378,7 +378,7 @@ enum DatabaseDriverFactory {
awaitPlugins: Bool
) async throws -> DatabaseDriver {
let pluginId = connection.type.pluginTypeId
- if PluginManager.shared.driverPlugins[pluginId] == nil,
+ if PluginManager.shared.driverPlugin(for: connection.type) == nil,
!PluginManager.shared.hasFinishedInitialLoad {
logger.info("Plugin '\(pluginId)' not loaded yet — waiting for background load")
await PluginManager.shared.waitForInitialLoad()
@@ -391,7 +391,7 @@ enum DatabaseDriverFactory {
passwordOverride: String? = nil
) throws -> DatabaseDriver {
let pluginId = connection.type.pluginTypeId
- guard let plugin = PluginManager.shared.driverPlugins[pluginId] else {
+ guard let plugin = PluginManager.shared.driverPlugin(for: connection.type) else {
if connection.type.isDownloadablePlugin {
throw PluginError.pluginNotInstalled(connection.type.rawValue)
}
diff --git a/TablePro/Core/Plugins/PluginManager+Registration.swift b/TablePro/Core/Plugins/PluginManager+Registration.swift
index 7a665f240..3bc0d3e3e 100644
--- a/TablePro/Core/Plugins/PluginManager+Registration.swift
+++ b/TablePro/Core/Plugins/PluginManager+Registration.swift
@@ -214,7 +214,36 @@ extension PluginManager {
// MARK: - Plugin Property Lookups
func driverPlugin(for databaseType: DatabaseType) -> (any DriverPlugin)? {
- driverPlugins[databaseType.pluginTypeId]
+ let typeId = databaseType.pluginTypeId
+ if let driver = driverPlugins[typeId] { return driver }
+ activateDriver(databaseTypeId: typeId)
+ return driverPlugins[typeId]
+ }
+
+ func exportPlugin(forFormat formatId: String) -> (any ExportFormatPlugin)? {
+ if let plugin = exportPlugins[formatId] { return plugin }
+ activateExportFormat(formatId)
+ return exportPlugins[formatId]
+ }
+
+ func importPlugin(forFormat formatId: String) -> (any ImportFormatPlugin)? {
+ if let plugin = importPlugins[formatId] { return plugin }
+ activateImportFormat(formatId)
+ return importPlugins[formatId]
+ }
+
+ func allExportPlugins() -> [any ExportFormatPlugin] {
+ for formatId in allLazyExportFormatIds() {
+ activateExportFormat(formatId)
+ }
+ return Array(exportPlugins.values)
+ }
+
+ func allImportPlugins() -> [any ImportFormatPlugin] {
+ for formatId in allLazyImportFormatIds() {
+ activateImportFormat(formatId)
+ }
+ return Array(importPlugins.values)
}
/// Returns a temporary plugin driver for query building (buildBrowseQuery), or nil
diff --git a/TablePro/Core/Plugins/PluginManager+Validation.swift b/TablePro/Core/Plugins/PluginManager+Validation.swift
index 76fc8cdd2..f28fddce0 100644
--- a/TablePro/Core/Plugins/PluginManager+Validation.swift
+++ b/TablePro/Core/Plugins/PluginManager+Validation.swift
@@ -15,6 +15,7 @@ extension PluginManager {
func validateDependencies() {
let loadedIds = Set(plugins.map(\.id))
for plugin in plugins where plugin.isEnabled {
+ guard plugin.bundle.isLoaded else { continue }
guard let principalClass = plugin.bundle.principalClass as? any TableProPlugin.Type else { continue }
let deps = principalClass.dependencies
for dep in deps {
diff --git a/TablePro/Core/Plugins/PluginManager.swift b/TablePro/Core/Plugins/PluginManager.swift
index d15d329d1..f38c32b30 100644
--- a/TablePro/Core/Plugins/PluginManager.swift
+++ b/TablePro/Core/Plugins/PluginManager.swift
@@ -84,6 +84,11 @@ final class PluginManager {
private var pendingPluginURLs: [(url: URL, source: PluginSource)] = []
+ @ObservationIgnored private var lazyDriverURLs: [String: URL] = [:]
+ @ObservationIgnored private var lazyExportURLs: [String: URL] = [:]
+ @ObservationIgnored private var lazyImportURLs: [String: URL] = [:]
+ @ObservationIgnored private var activatedBundleIds: Set = []
+
var queryBuildingDriverCache: [String: (any PluginDatabaseDriver)?] = [:]
init(
@@ -156,24 +161,192 @@ final class PluginManager {
func loadPlugins() {
migrateDisabledPluginsKey()
discoverAllPlugins()
- let pending = pendingPluginURLs
+ var lazyPending: [(url: URL, source: PluginSource, manifest: PluginManifest)] = []
+ var eagerPending: [(url: URL, source: PluginSource)] = []
+ for entry in pendingPluginURLs {
+ if let bundle = Bundle(url: entry.url),
+ let manifest = PluginManifest(bundle: bundle),
+ manifest.supportsLazyLoad {
+ lazyPending.append((url: entry.url, source: entry.source, manifest: manifest))
+ } else {
+ eagerPending.append(entry)
+ }
+ }
+ pendingPluginURLs.removeAll()
+
+ for entry in lazyPending {
+ registerLazyManifest(at: entry.url, source: entry.source, manifest: entry.manifest)
+ }
+
Task {
if !self.rejectedPlugins.isEmpty {
await self.autoUpdateRejectedPlugins()
}
- let validated = await Self.validateAndLoadBundles(pending)
- self.pendingPluginURLs.removeAll()
+ let validated = await Self.validateAndLoadBundles(eagerPending)
self.needsRestartStorage = false
self.registerValidatedBundles(validated)
self.validateDependencies()
self.hasFinishedInitialLoad = true
- Self.logger.info("Loaded \(self.plugins.count) plugin(s): \(self.driverPlugins.count) driver(s), \(self.exportPlugins.count) export format(s), \(self.importPlugins.count) import format(s)")
+ let lazyCount = lazyPending.count
+ let eagerCount = validated.count
+ Self.logger.info("Loaded \(self.plugins.count) plugin(s): \(lazyCount) lazy + \(eagerCount) eager (\(self.driverPlugins.count) driver(s) active, \(self.exportPlugins.count) export(s) active, \(self.importPlugins.count) import(s) active)")
if !self.rejectedPlugins.isEmpty {
NotificationCenter.default.post(name: .pluginsRejected, object: self.rejectedPlugins)
}
}
}
+ // MARK: - Lazy Plugin Activation
+
+ private func registerLazyManifest(at url: URL, source: PluginSource, manifest: PluginManifest) {
+ guard let bundle = Bundle(url: url) else { return }
+ do {
+ try Self.validateBundleVersions(bundle, source: source)
+ } catch {
+ Self.logger.error("Lazy plugin '\(manifest.bundleId)' failed version check: \(error.localizedDescription)")
+ if source == .userInstalled {
+ rejectedPlugins.append(RejectedPlugin(
+ url: url,
+ bundleId: manifest.bundleId,
+ registryId: Self.readRegistryMetadata(for: url)?.pluginId,
+ name: manifest.bundleId,
+ reason: error.localizedDescription,
+ isOutdated: (error as? PluginError)?.isOutdated ?? false
+ ))
+ }
+ return
+ }
+ if source == .userInstalled {
+ do {
+ try verifyCodeSignature(bundle: bundle)
+ } catch {
+ Self.logger.error("Lazy plugin '\(manifest.bundleId)' failed code-sign check: \(error.localizedDescription)")
+ rejectedPlugins.append(RejectedPlugin(
+ url: url,
+ bundleId: manifest.bundleId,
+ registryId: Self.readRegistryMetadata(for: url)?.pluginId,
+ name: manifest.bundleId,
+ reason: error.localizedDescription,
+ isOutdated: false
+ ))
+ return
+ }
+ }
+
+ let bundleId = manifest.bundleId
+ if source == .userInstalled,
+ let existing = plugins.first(where: { $0.id == bundleId }),
+ existing.source == .builtIn
+ {
+ Self.logger.info("Skipping user-installed lazy '\(bundleId)': built-in version already registered")
+ return
+ }
+
+ let primaryTypeId = manifest.providedDatabaseTypeIds.first
+ let additionalTypeIds = Array(manifest.providedDatabaseTypeIds.dropFirst())
+ let registrySnapshot = primaryTypeId.flatMap {
+ PluginMetadataRegistry.shared.snapshot(forTypeId: $0)
+ }
+
+ var capabilities: [PluginCapability] = []
+ if !manifest.providedDatabaseTypeIds.isEmpty { capabilities.append(.databaseDriver) }
+ if !manifest.providedExportFormatIds.isEmpty { capabilities.append(.exportFormat) }
+ if !manifest.providedImportFormatIds.isEmpty { capabilities.append(.importFormat) }
+
+ let info = bundle.infoDictionary ?? [:]
+ let version = Self.readRegistryMetadata(for: url)?.version
+ ?? (info["CFBundleShortVersionString"] as? String)
+ ?? "1.0.0"
+ let displayName = registrySnapshot?.displayName
+ ?? bundleId.split(separator: ".").last.map(String.init)
+ ?? bundleId
+ let pluginIconName = registrySnapshot?.iconName ?? "puzzlepiece"
+ let defaultPort = registrySnapshot?.defaultPort
+ let pluginDescription = registrySnapshot?.connection.tagline ?? ""
+
+ let entry = PluginEntry(
+ id: bundleId,
+ bundle: bundle,
+ url: url,
+ source: source,
+ name: displayName,
+ version: version,
+ pluginDescription: pluginDescription,
+ capabilities: capabilities,
+ isEnabled: !disabledPluginIds.contains(bundleId),
+ databaseTypeId: primaryTypeId,
+ additionalTypeIds: additionalTypeIds,
+ pluginIconName: pluginIconName,
+ defaultPort: defaultPort
+ )
+ plugins.append(entry)
+
+ for typeId in manifest.providedDatabaseTypeIds {
+ lazyDriverURLs[typeId] = url
+ }
+ for formatId in manifest.providedExportFormatIds {
+ lazyExportURLs[formatId] = url
+ }
+ for formatId in manifest.providedImportFormatIds {
+ lazyImportURLs[formatId] = url
+ }
+ Self.logger.debug("Registered lazy plugin '\(bundleId)': drivers=\(manifest.providedDatabaseTypeIds), exports=\(manifest.providedExportFormatIds), imports=\(manifest.providedImportFormatIds)")
+ }
+
+ func activateDriver(databaseTypeId typeId: String) {
+ guard driverPlugins[typeId] == nil else { return }
+ guard let url = lazyDriverURLs[typeId] else { return }
+ activateLazyBundle(at: url)
+ }
+
+ func activateExportFormat(_ formatId: String) {
+ guard exportPlugins[formatId] == nil else { return }
+ guard let url = lazyExportURLs[formatId] else { return }
+ activateLazyBundle(at: url)
+ }
+
+ func activateImportFormat(_ formatId: String) {
+ guard importPlugins[formatId] == nil else { return }
+ guard let url = lazyImportURLs[formatId] else { return }
+ activateLazyBundle(at: url)
+ }
+
+ func allLazyExportFormatIds() -> [String] {
+ Array(lazyExportURLs.keys)
+ }
+
+ func allLazyImportFormatIds() -> [String] {
+ Array(lazyImportURLs.keys)
+ }
+
+ private func activateLazyBundle(at url: URL) {
+ guard let bundle = Bundle(url: url) else { return }
+ let bundleId = bundle.bundleIdentifier ?? url.lastPathComponent
+ guard !activatedBundleIds.contains(bundleId) else { return }
+
+ guard bundle.load() else {
+ Self.logger.error("Failed to load lazy bundle '\(bundleId)' at \(url.lastPathComponent)")
+ return
+ }
+
+ guard let principalClass = bundle.principalClass as? any TableProPlugin.Type else {
+ Self.logger.error("Lazy plugin '\(bundleId)' has no TableProPlugin principal class")
+ return
+ }
+
+ validateCapabilityDeclarations(principalClass, pluginId: bundleId)
+
+ let isEnabled = plugins.first(where: { $0.id == bundleId })?.isEnabled ?? false
+ if isEnabled {
+ let instance = principalClass.init()
+ registerCapabilities(instance, pluginId: bundleId)
+ }
+
+ activatedBundleIds.insert(bundleId)
+ queryBuildingDriverCache.removeAll()
+ Self.logger.info("Activated plugin '\(bundleId)' on demand")
+ }
+
private struct ValidatedBundle: @unchecked Sendable {
let url: URL
let source: PluginSource
diff --git a/TablePro/Core/Plugins/PluginManifest.swift b/TablePro/Core/Plugins/PluginManifest.swift
new file mode 100644
index 000000000..667406026
--- /dev/null
+++ b/TablePro/Core/Plugins/PluginManifest.swift
@@ -0,0 +1,28 @@
+//
+// PluginManifest.swift
+// TablePro
+//
+
+import Foundation
+
+internal struct PluginManifest {
+ let bundleId: String
+ let providedDatabaseTypeIds: [String]
+ let providedExportFormatIds: [String]
+ let providedImportFormatIds: [String]
+
+ var supportsLazyLoad: Bool {
+ !providedDatabaseTypeIds.isEmpty
+ || !providedExportFormatIds.isEmpty
+ || !providedImportFormatIds.isEmpty
+ }
+
+ init?(bundle: Bundle) {
+ guard let id = bundle.bundleIdentifier else { return nil }
+ let info = bundle.infoDictionary ?? [:]
+ bundleId = id
+ providedDatabaseTypeIds = info["TableProProvidesDatabaseTypeIds"] as? [String] ?? []
+ providedExportFormatIds = info["TableProProvidesExportFormatIds"] as? [String] ?? []
+ providedImportFormatIds = info["TableProProvidesImportFormatIds"] as? [String] ?? []
+ }
+}
diff --git a/TablePro/Core/Services/Export/ExportService.swift b/TablePro/Core/Services/Export/ExportService.swift
index 4c1d21863..ff0f96f7e 100644
--- a/TablePro/Core/Services/Export/ExportService.swift
+++ b/TablePro/Core/Services/Export/ExportService.swift
@@ -97,7 +97,7 @@ final class ExportService {
throw ExportError.noTablesSelected
}
- guard let plugin = PluginManager.shared.exportPlugins[config.formatId] else {
+ guard let plugin = PluginManager.shared.exportPlugin(forFormat: config.formatId) else {
throw ExportError.formatNotFound(config.formatId)
}
@@ -185,7 +185,7 @@ final class ExportService {
config: ExportConfiguration,
to url: URL
) async throws {
- guard let plugin = PluginManager.shared.exportPlugins[config.formatId] else {
+ guard let plugin = PluginManager.shared.exportPlugin(forFormat: config.formatId) else {
throw ExportError.formatNotFound(config.formatId)
}
@@ -261,7 +261,7 @@ final class ExportService {
config: ExportConfiguration,
to url: URL
) async throws {
- guard let plugin = PluginManager.shared.exportPlugins[config.formatId] else {
+ guard let plugin = PluginManager.shared.exportPlugin(forFormat: config.formatId) else {
throw ExportError.formatNotFound(config.formatId)
}
guard let driver else {
diff --git a/TablePro/Core/Services/Export/ImportService.swift b/TablePro/Core/Services/Export/ImportService.swift
index 7810791e3..4a0cd5e58 100644
--- a/TablePro/Core/Services/Export/ImportService.swift
+++ b/TablePro/Core/Services/Export/ImportService.swift
@@ -54,7 +54,7 @@ final class ImportService {
ownsDecompressedFile: Bool = false,
knownStatementCount: Int? = nil
) async throws -> PluginImportResult {
- guard let plugin = PluginManager.shared.importPlugins[formatId] else {
+ guard let plugin = PluginManager.shared.importPlugin(forFormat: formatId) else {
throw PluginImportError.importFailed("Import format '\(formatId)' not found")
}
diff --git a/TablePro/Models/Export/ExportModels.swift b/TablePro/Models/Export/ExportModels.swift
index 9902724c7..87763dc5e 100644
--- a/TablePro/Models/Export/ExportModels.swift
+++ b/TablePro/Models/Export/ExportModels.swift
@@ -23,14 +23,14 @@ struct ExportConfiguration {
var fileName: String = "export"
var fullFileName: String {
- guard let plugin = PluginManager.shared.exportPlugins[formatId] else {
+ guard let plugin = PluginManager.shared.exportPlugin(forFormat: formatId) else {
return "\(fileName).\(formatId)"
}
return "\(fileName).\(plugin.currentFileExtension)"
}
var fileExtension: String {
- guard let plugin = PluginManager.shared.exportPlugins[formatId] else {
+ guard let plugin = PluginManager.shared.exportPlugin(forFormat: formatId) else {
return formatId
}
return plugin.currentFileExtension
diff --git a/TablePro/Views/Export/ExportDialog.swift b/TablePro/Views/Export/ExportDialog.swift
index 87acdef6f..44f0c962e 100644
--- a/TablePro/Views/Export/ExportDialog.swift
+++ b/TablePro/Views/Export/ExportDialog.swift
@@ -162,7 +162,7 @@ struct ExportDialog: View {
private var availableFormats: [any ExportFormatPlugin] {
let dbTypeId = connection.type.rawValue
- return PluginManager.shared.exportPlugins.values
+ return PluginManager.shared.allExportPlugins()
.filter { plugin in
let pluginType = type(of: plugin)
if !pluginType.supportedDatabaseTypeIds.isEmpty {
@@ -185,7 +185,7 @@ struct ExportDialog: View {
}
private var currentPlugin: (any ExportFormatPlugin)? {
- PluginManager.shared.exportPlugins[config.formatId]
+ PluginManager.shared.exportPlugin(forFormat: config.formatId)
}
// MARK: - Layout Constants
@@ -280,7 +280,7 @@ struct ExportDialog: View {
Picker("", selection: $config.formatId) {
ForEach(availableFormatIds, id: \.self) { formatId in
- if let plugin = PluginManager.shared.exportPlugins[formatId] {
+ if let plugin = PluginManager.shared.exportPlugin(forFormat: formatId) {
if isProGatedFormat(formatId) {
Text("\(type(of: plugin).formatDisplayName) (Pro)").tag(formatId)
} else {
diff --git a/TablePro/Views/Export/ExportTableTreeView.swift b/TablePro/Views/Export/ExportTableTreeView.swift
index 3bc4929ea..e01c07599 100644
--- a/TablePro/Views/Export/ExportTableTreeView.swift
+++ b/TablePro/Views/Export/ExportTableTreeView.swift
@@ -15,12 +15,12 @@ struct ExportTableTreeView: View {
let formatId: String
private var optionColumns: [PluginExportOptionColumn] {
- guard let plugin = PluginManager.shared.exportPlugins[formatId] else { return [] }
+ guard let plugin = PluginManager.shared.exportPlugin(forFormat: formatId) else { return [] }
return type(of: plugin).perTableOptionColumns
}
private var currentPlugin: (any ExportFormatPlugin)? {
- PluginManager.shared.exportPlugins[formatId]
+ PluginManager.shared.exportPlugin(forFormat: formatId)
}
var body: some View {
diff --git a/TablePro/Views/Import/ImportDialog.swift b/TablePro/Views/Import/ImportDialog.swift
index 2ea685f8f..318e81a74 100644
--- a/TablePro/Views/Import/ImportDialog.swift
+++ b/TablePro/Views/Import/ImportDialog.swift
@@ -122,7 +122,7 @@ struct ImportDialog: View {
private var availableFormats: [any ImportFormatPlugin] {
let dbTypeId = connection.type.rawValue
- return PluginManager.shared.importPlugins.values
+ return PluginManager.shared.allImportPlugins()
.filter { plugin in
let supported = type(of: plugin).supportedDatabaseTypeIds
let excluded = type(of: plugin).excludedDatabaseTypeIds
@@ -138,7 +138,7 @@ struct ImportDialog: View {
}
private var currentPlugin: (any ImportFormatPlugin)? {
- PluginManager.shared.importPlugins[selectedFormatId]
+ PluginManager.shared.importPlugin(forFormat: selectedFormatId)
}
// MARK: - View Components
diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift
index 96eb56084..a5b3ba9fc 100644
--- a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift
+++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift
@@ -123,7 +123,7 @@ extension MainContentCoordinator {
}
let panel = NSOpenPanel()
var contentTypes: [UTType] = []
- for (_, plugin) in PluginManager.shared.importPlugins {
+ for plugin in PluginManager.shared.allImportPlugins() {
for ext in type(of: plugin).acceptedFileExtensions {
if let utType = UTType(filenameExtension: ext) {
contentTypes.append(utType)