diff --git a/.gitignore b/.gitignore index 0882a88..f691d37 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ dist !examples/*/dist .idea packages/**/coverage +packages/@pandino/*/dist +packages/@pandino/*/types **/.DS_Store diff --git a/.prettierrc b/.prettierrc index f2614ff..236d414 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,5 +4,5 @@ "semi": true, "trailingComma": "all", "singleQuote": true, - "printWidth": 120 + "printWidth": 160 } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d24d191..46f427b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,19 +1,35 @@ # Contributing to Pandino +## Environment + +If you don't have it already, we recommend installing [nvm](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating). + +Once installed, you can issue the following command to set up the appropriate NodeJS version: + +```bash +nvm use +``` + +For package management we use [PNPM](https://pnpm.io/) instead of NPM. If you don't have it already, you can install it via: + +```bash +npm i - g pnpm +``` + ## CMDs ``` # Install deps: -npm i +pnpm i # Format code: -npm run format +pnpm run format # Build all packages (excluding example project): -npm run build +pnpm run build -# Build single package, e.g.: -npm run build --workspace @pandino/pandino +# Run tests +pnpm run test ``` ## Key Architectural Decisions diff --git a/package.json b/package.json index 77159a8..03bfe1c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@changesets/cli": "^2.27.9", - "@types/node": "^20.17.0", + "@types/node": "^20.17.1", "prettier": "^3.3.3", "rimraf": "^5.0.10" }, diff --git a/packages/@pandino/bundle-installer-dom/src/index.ts b/packages/@pandino/bundle-installer-dom/src/index.ts index 3a81b30..6ea87cd 100644 --- a/packages/@pandino/bundle-installer-dom/src/index.ts +++ b/packages/@pandino/bundle-installer-dom/src/index.ts @@ -38,20 +38,13 @@ export default class PandinoBundleInstallerDomActivator implements BundleActivat const callback = async () => { if (documentDefinedManifest.hasAttribute('src')) { - locations = await this.fetcher!.fetch( - this.context!.getProperty(DEPLOYMENT_ROOT_PROP), - documentDefinedManifest.getAttribute('src') as string, - ); + locations = await this.fetcher!.fetch(this.context!.getProperty(DEPLOYMENT_ROOT_PROP), documentDefinedManifest.getAttribute('src') as string); } else { locations = documentDefinedManifest ? JSON.parse(documentDefinedManifest.textContent!) : []; } - const installList = locations.filter( - (manifestLocation) => !this.installedManifestList.includes(manifestLocation), - ); - const uninstallList = this.installedManifestList.filter( - (manifestLocation) => !locations.includes(manifestLocation), - ); + const installList = locations.filter((manifestLocation) => !this.installedManifestList.includes(manifestLocation)); + const uninstallList = this.installedManifestList.filter((manifestLocation) => !locations.includes(manifestLocation)); await Promise.all(uninstallList.map((manifestLocation) => this.uninstall(manifestLocation))); await Promise.all(installList.map((manifestLocation) => this.install(manifestLocation))); diff --git a/packages/@pandino/bundle-installer-nodejs/src/activator.ts b/packages/@pandino/bundle-installer-nodejs/src/activator.ts index 1ae41ff..de3bc8c 100644 --- a/packages/@pandino/bundle-installer-nodejs/src/activator.ts +++ b/packages/@pandino/bundle-installer-nodejs/src/activator.ts @@ -17,11 +17,7 @@ export class Activator implements BundleActivator { this.logger = context.getService(this.loggerReference)!; this.fetcherReference = context.getServiceReference(FRAMEWORK_MANIFEST_FETCHER)!; this.fetcher = context.getService(this.fetcherReference)!; - this.installerService = new InstallerService( - this.context.getProperty(DEPLOYMENT_ROOT_PROP), - this.context, - this.logger, - ); + this.installerService = new InstallerService(this.context.getProperty(DEPLOYMENT_ROOT_PROP), this.context, this.logger); this.installerService.watch(); } diff --git a/packages/@pandino/configuration-management/package.json b/packages/@pandino/configuration-management/package.json index 89a4b37..1862777 100644 --- a/packages/@pandino/configuration-management/package.json +++ b/packages/@pandino/configuration-management/package.json @@ -49,6 +49,7 @@ "@pandino/filters": "workspace:^", "@pandino/pandino": "workspace:^", "@pandino/pandino-api": "workspace:^", + "@pandino/persistence-manager-memory": "workspace:^", "@pandino/persistence-manager-api": "workspace:^", "@pandino/rollup-plugin-generate-manifest": "workspace:^", "dts-bundle-generator": "^9.5.1", diff --git a/packages/@pandino/configuration-management/src/__mocks__/mock-bundle-context.ts b/packages/@pandino/configuration-management/src/__mocks__/mock-bundle-context.ts index 44084d0..174589e 100644 --- a/packages/@pandino/configuration-management/src/__mocks__/mock-bundle-context.ts +++ b/packages/@pandino/configuration-management/src/__mocks__/mock-bundle-context.ts @@ -21,10 +21,7 @@ export class MockBundleContext implements BundleContext { private properties: Record = {}; private serviceListeners: ServiceListener[] = []; private readonly registrations: ServiceRegistration[] = []; - private readonly refMap: Map> = new Map< - string[] | string, - ServiceReference - >(); + private readonly refMap: Map> = new Map>(); private readonly serviceMap: Map, any> = new Map, any>(); private bundle?: Bundle; @@ -82,11 +79,7 @@ export class MockBundleContext implements BundleContext { return Promise.resolve(undefined); } - registerService( - identifiers: string[] | string, - service: S, - properties: ServiceProperties = {}, - ): ServiceRegistration { + registerService(identifiers: string[] | string, service: S, properties: ServiceProperties = {}): ServiceRegistration { const ref: ServiceReference = { getBundle: () => this.getBundle(), getProperties: () => properties, @@ -143,10 +136,7 @@ export class MockBundleContext implements BundleContext { return undefined as any; } - trackService( - identifierOrFilter: string, - customizer: Partial>, - ): ServiceTracker { + trackService(identifierOrFilter: string, customizer: Partial>): ServiceTracker { return undefined as any; } } diff --git a/packages/@pandino/configuration-management/src/activator.ts b/packages/@pandino/configuration-management/src/activator.ts index 1092d22..732b1e8 100644 --- a/packages/@pandino/configuration-management/src/activator.ts +++ b/packages/@pandino/configuration-management/src/activator.ts @@ -1,13 +1,5 @@ import { FRAMEWORK_EVALUATE_FILTER, FRAMEWORK_LOGGER, OBJECTCLASS } from '@pandino/pandino-api'; -import type { - BundleActivator, - BundleContext, - Logger, - ServiceEvent, - ServiceListener, - ServiceReference, - ServiceRegistration, -} from '@pandino/pandino-api'; +import type { BundleActivator, BundleContext, Logger, ServiceEvent, ServiceListener, ServiceReference, ServiceRegistration } from '@pandino/pandino-api'; import type { FilterEvaluator } from '@pandino/filters'; import type { ConfigurationAdmin } from '@pandino/configuration-management-api'; import { CONFIG_ADMIN_INTERFACE_KEY } from '@pandino/configuration-management-api'; @@ -42,9 +34,7 @@ export class Activator implements BundleActivator { if (this.persistenceManagerReference) { this.logger.info( - `Activating Configuration Management with immediate Persistence Manager Reference: ${this.persistenceManagerReference.getProperty( - OBJECTCLASS, - )}`, + `Activating Configuration Management with immediate Persistence Manager Reference: ${this.persistenceManagerReference.getProperty(OBJECTCLASS)}`, ); this.persistenceManager = context.getService(this.persistenceManagerReference); if (this.persistenceManager) { @@ -107,10 +97,7 @@ export class Activator implements BundleActivator { if (!this.pmUsed && this.context) { this.configManager = new ConfigurationManager(this.context, this.logger!, this.evaluateFilter!, pm); this.configAdmin = new ConfigurationAdminImpl(this.configManager, this.context.getBundle(), this.logger!); - this.configAdminRegistration = this.context.registerService( - CONFIG_ADMIN_INTERFACE_KEY, - this.configAdmin, - ); + this.configAdminRegistration = this.context.registerService(CONFIG_ADMIN_INTERFACE_KEY, this.configAdmin); this.configManager.initReferencesAddedBeforeManagerActivation(); this.context.addServiceListener(this.configManager); } else { diff --git a/packages/@pandino/configuration-management/src/configuration-cache.ts b/packages/@pandino/configuration-management/src/configuration-cache.ts index 13166df..737a33d 100644 --- a/packages/@pandino/configuration-management/src/configuration-cache.ts +++ b/packages/@pandino/configuration-management/src/configuration-cache.ts @@ -16,12 +16,7 @@ export class ConfigurationCache { this.cm = cm; this.persistenceManager.getProperties().forEach((props: ServiceProperties) => { - const configuration = new ConfigurationImpl( - this.cm, - props[SERVICE_PID], - this.context.getBundle().getLocation(), - props, - ); + const configuration = new ConfigurationImpl(this.cm, props[SERVICE_PID], this.context.getBundle().getLocation(), props); this.cache.set(props[SERVICE_PID], configuration); }); } diff --git a/packages/@pandino/configuration-management/src/configuration-impl.test.ts b/packages/@pandino/configuration-management/src/configuration-impl.test.ts index f278373..8f5b712 100644 --- a/packages/@pandino/configuration-management/src/configuration-impl.test.ts +++ b/packages/@pandino/configuration-management/src/configuration-impl.test.ts @@ -1,41 +1,86 @@ -import { describe, beforeEach, expect, it, vi } from 'vitest'; -import type { Bundle, BundleContext, Logger, ServiceProperties, ServiceReference } from '@pandino/pandino-api'; +import { describe, beforeEach, expect, it, vi, afterEach } from 'vitest'; +import { + Bundle, + BUNDLE_ACTIVATOR, + BUNDLE_SYMBOLICNAME, + BUNDLE_VERSION, + BundleContext, + type BundleImporter, + BundleManifestHeaders, + FrameworkConfigMap, + LOG_LEVEL_PROP, + LogLevel, + PANDINO_BUNDLE_IMPORTER_PROP, + PANDINO_MANIFEST_FETCHER_PROP, + PROVIDE_CAPABILITY, + REQUIRE_CAPABILITY, + ServiceProperties, + ServiceReference, +} from '@pandino/pandino-api'; import { SERVICE_PID } from '@pandino/pandino-api'; import { + CONFIG_ADMIN_INTERFACE_KEY, CONFIGURATION_LISTENER_INTERFACE_KEY, + ConfigurationAdmin, MANAGED_SERVICE_INTERFACE_KEY, } from '@pandino/configuration-management-api'; -import type { - Configuration, - ConfigurationEvent, - ConfigurationEventType, - ConfigurationListener, - ManagedService, -} from '@pandino/configuration-management-api'; -import { evaluateFilter } from '@pandino/filters'; -import { MockBundleContext } from './__mocks__/mock-bundle-context'; -import { MockBundle } from './__mocks__/mock-bundle'; -import { MockPersistenceManager } from './__mocks__/mock-persistence-manager'; +import PMActivator from '@pandino/persistence-manager-memory'; +import type { Configuration, ConfigurationEvent, ConfigurationEventType, ConfigurationListener, ManagedService } from '@pandino/configuration-management-api'; +import Pandino from '@pandino/pandino'; import { ConfigurationAdminImpl } from './configuration-admin-impl'; -import { ConfigurationManager } from './configuration-manager'; +import { Activator as CMActivator } from './activator'; describe('ConfigurationImpl', () => { - let context: BundleContext; + let params: FrameworkConfigMap; let bundle: Bundle; + let pandino: Pandino; + let pandinoContext: BundleContext; + const pmHeaders: () => BundleManifestHeaders = () => ({ + [BUNDLE_SYMBOLICNAME]: '@pandino/persistence-manager-memory', + [BUNDLE_VERSION]: '0.0.0', + [BUNDLE_ACTIVATOR]: new PMActivator(), + [PROVIDE_CAPABILITY]: '@pandino/persistence-manager;type="in-memory";objectClass="@pandino/persistence-manager/PersistenceManager"', + }); + const cmHeaders: () => BundleManifestHeaders = () => ({ + [BUNDLE_SYMBOLICNAME]: '@pandino/configuration-management', + [BUNDLE_VERSION]: '0.0.0', + [BUNDLE_ACTIVATOR]: new CMActivator(), + [REQUIRE_CAPABILITY]: '@pandino/persistence-manager;filter:=(objectClass=@pandino/persistence-manager/PersistenceManager)', + [PROVIDE_CAPABILITY]: + '@pandino/configuration-management;objectClass:Array="@pandino/configuration-management/ConfigurationAdmin,@pandino/configuration-management/ManagedService,@pandino/configuration-management/ConfigurationListener"', + }); + const importer: BundleImporter = { + import: (activatorLocation: string, manifestLocation: string, deploymentRoot?: string) => { + // this won't be called if the activator is an instance and not a string + return Promise.resolve({ + default: null as unknown as any, + }); + }, + }; let configAdmin: ConfigurationAdminImpl; - let cm: ConfigurationManager; - let mockDebug = vi.fn(); - let logger: Logger = { - debug: mockDebug, - } as unknown as Logger; - - beforeEach(() => { - mockDebug.mockClear(); - context = new MockBundleContext(); - bundle = new MockBundle(context as MockBundleContext, 'test.bundle.location', '@test/my-bundle', '0.0.0'); - cm = new ConfigurationManager(context, logger, evaluateFilter, new MockPersistenceManager('{}')); - context.addServiceListener(cm); - configAdmin = new ConfigurationAdminImpl(cm, bundle, logger); + + beforeEach(async () => { + params = { + [PANDINO_MANIFEST_FETCHER_PROP]: vi.fn() as any, + [PANDINO_BUNDLE_IMPORTER_PROP]: importer, + [LOG_LEVEL_PROP]: LogLevel.WARN, + }; + pandino = new Pandino(params); + + await pandino.init(); + await pandino.start(); + + pandinoContext = pandino.getBundleContext(); + const [pmb, cmb] = await installDepBundles(pandinoContext); + bundle = pandino; + const ref = pandinoContext.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + configAdmin = pandinoContext.getService(ref) as ConfigurationAdminImpl; + }); + + afterEach(() => { + pandino.stop(); + pandino = undefined; + pandinoContext = undefined; }); it('basic creation of configuration', () => { @@ -61,7 +106,7 @@ describe('ConfigurationImpl', () => { // configuration didn't register a location testConfiguration(configuration, 'test.pid', undefined, undefined); - context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -100,7 +145,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -128,7 +173,7 @@ describe('ConfigurationImpl', () => { updated: mockUpdated, }; configuration.update(); - context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -143,7 +188,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - const registration = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + const registration = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -167,7 +212,7 @@ describe('ConfigurationImpl', () => { prop2: 'test', }); - expect(registration.getProperties()).toEqual({ + expect(registration.getProperties()).toMatchObject({ [SERVICE_PID]: 'test.pid', }); }); @@ -183,11 +228,11 @@ describe('ConfigurationImpl', () => { const service2: ManagedService = { updated: mockUpdated2, }; - const registration1 = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service1, { + const registration1 = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service1, { [SERVICE_PID]: 'test.pid', name: 'service1', }); - const registration2 = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service2, { + const registration2 = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service2, { [SERVICE_PID]: 'test.pid', name: 'service2', }); @@ -219,11 +264,11 @@ describe('ConfigurationImpl', () => { prop2: 'test', }); - expect(registration1.getProperties()).toEqual({ + expect(registration1.getProperties()).toMatchObject({ name: 'service1', [SERVICE_PID]: 'test.pid', }); - expect(registration2.getProperties()).toEqual({ + expect(registration2.getProperties()).toMatchObject({ name: 'service2', [SERVICE_PID]: 'test.pid', }); @@ -235,7 +280,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -261,11 +306,13 @@ describe('ConfigurationImpl', () => { }); it('events', () => { + const mockUpdatedEvent = vi.fn(); const mockConfigurationEvent = vi.fn(); - const listener: ConfigurationListener = { + const service: ManagedService & ConfigurationListener = { + updated: mockUpdatedEvent, configurationEvent: mockConfigurationEvent, }; - context.registerService(CONFIGURATION_LISTENER_INTERFACE_KEY, listener, { + const registration = pandinoContext.registerService([MANAGED_SERVICE_INTERFACE_KEY, CONFIGURATION_LISTENER_INTERFACE_KEY], service, { [SERVICE_PID]: 'test.pid', }); @@ -273,26 +320,27 @@ describe('ConfigurationImpl', () => { testConfigurationEvent(mockConfigurationEvent, 0); - const service: ManagedService = { - updated: vi.fn(), - }; - const registration = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { - [SERVICE_PID]: 'test.pid', - }); - const reference = registration.getReference(); + expect(registration.getReference().hasObjectClass(MANAGED_SERVICE_INTERFACE_KEY)).toBe(true); + expect(registration.getReference().hasObjectClass(CONFIGURATION_LISTENER_INTERFACE_KEY)).toBe(true); - testConfigurationEvent(mockConfigurationEvent, 1, 'UPDATED', reference); + // const managedReference = pandinoContext.getServiceReference(MANAGED_SERVICE_INTERFACE_KEY); + // const listenerReference = pandinoContext.getServiceReference(CONFIGURATION_LISTENER_INTERFACE_KEY); + + expect(service.updated).toHaveBeenCalledTimes(1); + expect(service.configurationEvent).toHaveBeenCalledTimes(0); + testConfigurationEvent(mockConfigurationEvent, 0, 'UPDATED', registration.getReference()); configuration.update({ prop1: true, prop2: 'test', }); - testConfigurationEvent(mockConfigurationEvent, 2, 'UPDATED', reference); + expect(service.updated).toHaveBeenCalledTimes(2); + testConfigurationEvent(mockConfigurationEvent, 1, 'UPDATED', registration.getReference()); configuration.delete(); - testConfigurationEvent(mockConfigurationEvent, 3, 'DELETED', reference); + testConfigurationEvent(mockConfigurationEvent, 2, 'DELETED', registration.getReference()); }); it('targetpid matching use-case based on bundle symbolic name', () => { @@ -301,7 +349,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - const registration = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + const registration = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -327,7 +375,7 @@ describe('ConfigurationImpl', () => { prop2: 'test', }); - expect(registration.getProperties()).toEqual({ + expect(registration.getProperties()).toMatchObject({ [SERVICE_PID]: 'test.pid', }); }); @@ -338,7 +386,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - const registration = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + const registration = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -357,7 +405,7 @@ describe('ConfigurationImpl', () => { prop2: 'test', }); - expect(registration.getProperties()).toEqual({ + expect(registration.getProperties()).toMatchObject({ [SERVICE_PID]: 'test.pid', }); }); @@ -371,12 +419,7 @@ describe('ConfigurationImpl', () => { } } - function testConfiguration( - configuration: Configuration, - pid: string, - location?: string, - properties?: ServiceProperties, - ): void { + function testConfiguration(configuration: Configuration, pid: string, location?: string, properties?: ServiceProperties): void { expect(configuration).toBeDefined(); expect(configuration.getPid()).toEqual(pid); expect(configuration.getBundleLocation()).toEqual(location); @@ -397,4 +440,10 @@ describe('ConfigurationImpl', () => { expect(event.getReference()).toEqual(reference); } } + + async function installDepBundles(ctx: BundleContext): Promise { + const pmb = await ctx.installBundle(pmHeaders()); + const cmb = await ctx.installBundle(cmHeaders()); + return [pmb, cmb]; + } }); diff --git a/packages/@pandino/configuration-management/src/configuration-impl.ts b/packages/@pandino/configuration-management/src/configuration-impl.ts index c905b99..1fda77e 100644 --- a/packages/@pandino/configuration-management/src/configuration-impl.ts +++ b/packages/@pandino/configuration-management/src/configuration-impl.ts @@ -11,12 +11,7 @@ export class ConfigurationImpl implements Configuration { private location?: string; private properties?: ServiceProperties; - constructor( - configurationManager: ConfigurationManager, - pid: string, - location?: string, - properties?: ServiceProperties, - ) { + constructor(configurationManager: ConfigurationManager, pid: string, location?: string, properties?: ServiceProperties) { this.configurationManager = configurationManager; this.pid = new TargetedPID(pid); this.location = location; diff --git a/packages/@pandino/configuration-management/src/configuration-manager.test.ts b/packages/@pandino/configuration-management/src/configuration-manager.test.ts index e40550e..ebf1d9a 100644 --- a/packages/@pandino/configuration-management/src/configuration-manager.test.ts +++ b/packages/@pandino/configuration-management/src/configuration-manager.test.ts @@ -24,7 +24,7 @@ describe('ConfigurationManager', function () { bundle = new MockBundle(context as MockBundleContext, 'test.bundle.location', '@test/my-bundle', '0.0.0'); persistenceManager = new MockPersistenceManager(`{ "my.component.pid": { - "service.pid": "my.component.pid", + "${SERVICE_PID}": "my.component.pid", "port" : 300 } }`); @@ -34,7 +34,7 @@ describe('ConfigurationManager', function () { it('listConfigurations()', () => { persistenceManager = new MockPersistenceManager(`{ "my.component.pid": { - "service.pid": "my.component.pid", + "${SERVICE_PID}": "my.component.pid", "port" : 300, "collection" : [2, 3, 4], "complex": { @@ -43,7 +43,7 @@ describe('ConfigurationManager', function () { } }, "my.other.pid": { - "service.pid": "my.other.pid", + "${SERVICE_PID}": "my.other.pid", "key": "value" } }`); @@ -58,17 +58,17 @@ describe('ConfigurationManager', function () { collection: [2, 3, 4], complex: { a: 1, b: 'two' }, port: 300, - 'service.pid': 'my.component.pid', + [SERVICE_PID]: 'my.component.pid', }); expect(config2.getPid()).toEqual('my.other.pid'); - expect(config2.getProperties()).toEqual({ key: 'value', 'service.pid': 'my.other.pid' }); + expect(config2.getProperties()).toEqual({ key: 'value', [SERVICE_PID]: 'my.other.pid' }); }); it('listConfigurations() with filter', () => { persistenceManager = new MockPersistenceManager(`{ "my.component.pid": { - "service.pid": "my.component.pid", + "${SERVICE_PID}": "my.component.pid", "port" : 300, "collection" : [2, 3, 4], "complex": { @@ -77,19 +77,19 @@ describe('ConfigurationManager', function () { } }, "my.other.pid": { - "service.pid": "my.other.pid", + "${SERVICE_PID}": "my.other.pid", "key": "value" } }`); cm = new ConfigurationManager(context, logger, evaluateFilter, persistenceManager); - const configurations = cm.listConfigurations('(key=value)'); + const configurations = cm.listConfigurations(`(${SERVICE_PID}=my.other.pid)`); expect(configurations.length).toEqual(1); const [config2] = configurations; expect(config2.getPid()).toEqual('my.other.pid'); - expect(config2.getProperties()).toEqual({ key: 'value', 'service.pid': 'my.other.pid' }); + expect(config2.getProperties()).toEqual({ key: 'value', [SERVICE_PID]: 'my.other.pid' }); }); it('getConfiguration()', () => { @@ -156,7 +156,8 @@ describe('ConfigurationManager', function () { expect((cm as any).eventListeners.size).toEqual(1); expect((cm as any).eventListeners.has('mock.pid')).toEqual(true); expect((cm as any).eventListeners.get('mock.pid').length).toEqual(1); - expect((cm as any).eventListeners.get('mock.pid')[0]).toEqual(mockService); + const asd = (cm as any).eventListeners.get('mock.pid')[0]; + expect((cm as any).eventListeners.get('mock.pid')[0]).toEqual(mockServiceReference); const unregisteringEvent: ServiceEvent = { getType: () => 'UNREGISTERING', @@ -217,7 +218,7 @@ describe('ConfigurationManager', function () { it('some references had pre-stored configurations, while others did not', () => { persistenceManager = new MockPersistenceManager(`{ "my.component.pid": { - "service.pid": "my.component.pid", + "${SERVICE_PID}": "my.component.pid", "port" : 300 } }`); diff --git a/packages/@pandino/configuration-management/src/configuration-manager.ts b/packages/@pandino/configuration-management/src/configuration-manager.ts index e5eafb5..a82075f 100644 --- a/packages/@pandino/configuration-management/src/configuration-manager.ts +++ b/packages/@pandino/configuration-management/src/configuration-manager.ts @@ -1,19 +1,7 @@ import { SERVICE_PID } from '@pandino/pandino-api'; -import type { - BundleContext, - Logger, - ServiceEvent, - ServiceEventType, - ServiceListener, - ServiceReference, -} from '@pandino/pandino-api'; +import type { BundleContext, Logger, ServiceEvent, ServiceEventType, ServiceListener, ServiceReference } from '@pandino/pandino-api'; import { MANAGED_SERVICE_INTERFACE_KEY } from '@pandino/configuration-management-api'; -import type { - ConfigurationEvent, - ConfigurationEventType, - ConfigurationListener, - ManagedService, -} from '@pandino/configuration-management-api'; +import type { ConfigurationEvent, ConfigurationEventType, ConfigurationListener, ManagedService } from '@pandino/configuration-management-api'; import type { FilterEvaluator } from '@pandino/filters'; import type { PersistenceManager } from '@pandino/persistence-manager-api'; import { ConfigurationImpl } from './configuration-impl'; @@ -25,11 +13,8 @@ export class ConfigurationManager implements ServiceListener { private readonly context: BundleContext; private readonly logger: Logger; private readonly evaluateFilter: FilterEvaluator; - private readonly managedReferences: Map>> = new Map< - string, - Array> - >(); - private readonly eventListeners: Map = new Map(); + private readonly managedReferences: Map>> = new Map>>(); + private readonly eventListeners: Map[]> = new Map[]>(); private readonly configurationCache: ConfigurationCache; constructor(context: BundleContext, logger: Logger, evaluateFilter: FilterEvaluator, pm: PersistenceManager) { @@ -42,9 +27,7 @@ export class ConfigurationManager implements ServiceListener { initReferencesAddedBeforeManagerActivation(): void { const nonConfiguredReferences: ServiceReference[] = []; const freshReferences: ServiceReference[] = []; - const references = this.context - .getServiceReferences(MANAGED_SERVICE_INTERFACE_KEY) - .filter((ref) => ref.getProperty(SERVICE_PID)); + const references = this.context.getServiceReferences(MANAGED_SERVICE_INTERFACE_KEY).filter((ref) => ref.getProperty(SERVICE_PID)); for (const config of this.configurationCache.values()) { // multiple references can have the same pid const configuredReferences = references.filter((ref) => ref.getProperty(SERVICE_PID) === config.getPid()); @@ -92,9 +75,9 @@ export class ConfigurationManager implements ServiceListener { ? this.configurationCache.get(refPid)! : this.internalCreateConfiguration(refPid, reference.getBundle()?.getLocation()); this.handleManagedServiceEvent(event.getType(), refPid, reference, service, config); - } else if (typeof (service as ConfigurationListener).configurationEvent === 'function') { - const configurationListener: ConfigurationListener = service; - this.handleConfigurationEventListenerEvent(event.getType(), refPid, configurationListener); + } + if (typeof (service as ConfigurationListener).configurationEvent === 'function') { + this.handleConfigurationEventListenerEvent(event, refPid); } } } @@ -149,24 +132,20 @@ export class ConfigurationManager implements ServiceListener { } } - private handleConfigurationEventListenerEvent( - eventType: ServiceEventType, - refPid: string, - configurationListener: ConfigurationListener, - ): void { - if (eventType === 'REGISTERED') { + private handleConfigurationEventListenerEvent(event: ServiceEvent, refPid: string): void { + if (event.getType() === 'REGISTERED') { if (!this.eventListeners.has(refPid)) { this.eventListeners.set(refPid, []); } const listeners = this.eventListeners.get(refPid); - if (Array.isArray(listeners) && !listeners.includes(configurationListener)) { - listeners.push(configurationListener); + if (Array.isArray(listeners) && !listeners.includes(event.getServiceReference())) { + listeners.push(event.getServiceReference()); } - } else if (eventType === 'UNREGISTERING') { + } else if (event.getType() === 'UNREGISTERING') { if (this.eventListeners.has(refPid)) { const listeners = this.eventListeners.get(refPid); if (Array.isArray(listeners)) { - const listenerIdx = listeners.findIndex((l) => l === configurationListener); + const listenerIdx = listeners.findIndex((l) => l === event.getServiceReference()); if (listenerIdx > -1) { listeners.splice(listenerIdx, 1); } @@ -205,9 +184,7 @@ export class ConfigurationManager implements ServiceListener { if (filterString) { this.logger.debug(`Listing configurations matching ${filterString}`); - return Array.from(this.configurationCache.values()).filter((config) => - this.evaluateFilter(config.getProperties(), filterString), - ); + return Array.from(this.configurationCache.values()).filter((config) => this.evaluateFilter(config.getProperties(), filterString)); } return [...this.configurationCache.values()]; @@ -215,6 +192,7 @@ export class ConfigurationManager implements ServiceListener { deleteConfiguration(pid: string): void { if (this.configurationCache.has(pid)) { + const listeners = this.eventListeners.get(pid); this.configurationCache.delete(pid); if (this.managedReferences.has(pid)) { const refs = this.managedReferences.get(pid); @@ -224,10 +202,15 @@ export class ConfigurationManager implements ServiceListener { if (service) { service.updated(undefined); } - this.fireConfigurationChangeEvent('DELETED', pid, ref); + // this.fireConfigurationChangeEvent('DELETED', pid, ref); } } } + if (Array.isArray(listeners)) { + for (const listener of listeners) { + this.fireConfigurationChangeEvent('DELETED', pid, listener); + } + } } this.logger.debug(`Attempted to delete already removed configuration for pid: ${pid}, ignoring.`); } @@ -257,17 +240,6 @@ export class ConfigurationManager implements ServiceListener { return new ConfigurationImpl(this, pid, bundleLocation); } - // private storeConfiguration(configuration: ConfigurationImpl): ConfigurationImpl { - // const pid = configuration.getPid(); - // const existing = this.configurationCache.get(pid); - // if (existing) { - // return existing as ConfigurationImpl; - // } - // - // this.configurationCache.set(pid, configuration); - // return configuration; - // } - private fireConfigurationChangeEvent(type: ConfigurationEventType, pid: string, ref: ServiceReference): void { const event: ConfigurationEvent = { getPid(): string { @@ -282,7 +254,10 @@ export class ConfigurationManager implements ServiceListener { }; const listeners = this.eventListeners.get(pid) || []; for (const listener of listeners) { - listener.configurationEvent(event); + const service = this.context.getService(listener); + if (service) { + service!.configurationEvent(event); + } } } } diff --git a/packages/@pandino/event-admin/src/activator.ts b/packages/@pandino/event-admin/src/activator.ts index 44a7172..cf399cf 100644 --- a/packages/@pandino/event-admin/src/activator.ts +++ b/packages/@pandino/event-admin/src/activator.ts @@ -1,24 +1,11 @@ import { FRAMEWORK_EVALUATE_FILTER, FRAMEWORK_LOGGER, SERVICE_LISTENER_INTERFACE_KEY } from '@pandino/pandino-api'; -import type { - BundleActivator, - BundleContext, - Logger, - ServiceListener, - ServiceReference, - ServiceRegistration, -} from '@pandino/pandino-api'; +import type { BundleActivator, BundleContext, Logger, ServiceListener, ServiceReference, ServiceRegistration } from '@pandino/pandino-api'; import type { FilterEvaluator } from '@pandino/filters'; import { EVENT_ADMIN_INTERFACE_KEY, EVENT_FACTORY_INTERFACE_KEY } from '@pandino/event-api'; import type { EventAdmin, EventFactory } from '@pandino/event-api'; import { EventAdminImpl } from './event-admin-impl'; import { EventFactoryImpl } from './event-factory-impl'; -import { - AbstractAdapter, - BundleEventAdapter, - FrameworkEventAdapter, - LogEventAdapter, - ServiceEventAdapter, -} from './adapters'; +import { AbstractAdapter, BundleEventAdapter, FrameworkEventAdapter, LogEventAdapter, ServiceEventAdapter } from './adapters'; export class Activator implements BundleActivator { private eventAdminRegistration?: ServiceRegistration; @@ -37,10 +24,7 @@ export class Activator implements BundleActivator { this.evaluateFilter = context.getService(this.evaluateFilterService)!; this.eventAdmin = new EventAdminImpl(context, this.logger!, this.evaluateFilter); const eventFactoryImpl = new EventFactoryImpl(this.evaluateFilter); - this.eventAdminRegistration = context.registerService( - [EVENT_ADMIN_INTERFACE_KEY, SERVICE_LISTENER_INTERFACE_KEY], - this.eventAdmin, - ); + this.eventAdminRegistration = context.registerService([EVENT_ADMIN_INTERFACE_KEY, SERVICE_LISTENER_INTERFACE_KEY], this.eventAdmin); this.eventFactoryRegistration = context.registerService(EVENT_FACTORY_INTERFACE_KEY, eventFactoryImpl); context.addServiceListener(this.eventAdmin); diff --git a/packages/@pandino/event-admin/src/adapters/log-event-adapter.ts b/packages/@pandino/event-admin/src/adapters/log-event-adapter.ts index 532e93e..c351b4b 100644 --- a/packages/@pandino/event-admin/src/adapters/log-event-adapter.ts +++ b/packages/@pandino/event-admin/src/adapters/log-event-adapter.ts @@ -10,16 +10,7 @@ import { } from '@pandino/pandino-api'; import { LOG_READER_SERVICE_INTERFACE_KEY } from '@pandino/log-api'; import type { LogEntry, LogListener, LogReaderService } from '@pandino/log-api'; -import { - BUNDLE_ID, - EventAdmin, - EventFactory, - LOG_EVENT_INTERFACE_KEY, - MESSAGE, - SERVICE, - SERVICE_OBJECTCLASS, - TIMESTAMP, -} from '@pandino/event-api'; +import { BUNDLE_ID, EventAdmin, EventFactory, LOG_EVENT_INTERFACE_KEY, MESSAGE, SERVICE, SERVICE_OBJECTCLASS, TIMESTAMP } from '@pandino/event-api'; import { AbstractAdapter } from './abstract-adapter'; export class LogEventAdapter extends AbstractAdapter implements ServiceListener { diff --git a/packages/@pandino/event-admin/src/event-admin-impl.test.ts b/packages/@pandino/event-admin/src/event-admin-impl.test.ts index bc8238b..39a52b8 100644 --- a/packages/@pandino/event-admin/src/event-admin-impl.test.ts +++ b/packages/@pandino/event-admin/src/event-admin-impl.test.ts @@ -308,11 +308,7 @@ describe('EventAdminImpl', () => { }); }); - function createRegistration( - topic: string | string[], - externalMock?: any, - filter?: string, - ): EventHandlerRegistrationInfo { + function createRegistration(topic: string | string[], externalMock?: any, filter?: string): EventHandlerRegistrationInfo { return { [EVENT_TOPIC]: topic, [EVENT_FILTER]: filter, diff --git a/packages/@pandino/filters/src/filters.test.ts b/packages/@pandino/filters/src/filters.test.ts index 8292672..ea5f635 100644 --- a/packages/@pandino/filters/src/filters.test.ts +++ b/packages/@pandino/filters/src/filters.test.ts @@ -85,6 +85,11 @@ describe('filters', () => { expect(evaluateFilter({ sn: 'jones' }, query)).toEqual(false); }); + it('simple equality with dot keys', () => { + const query = '(my.key=smith)'; + expect(evaluateFilter({ 'my.key': 'smith' }, query)).toEqual(true); + }); + it('multi-valued keys', () => { const query = '(gn=Rick)'; let data = { gn: ['Richard', 'Dick', 'Rick', 'Ricky'] }; diff --git a/packages/@pandino/filters/src/filters.ts b/packages/@pandino/filters/src/filters.ts index 511fe72..5e6cefa 100644 --- a/packages/@pandino/filters/src/filters.ts +++ b/packages/@pandino/filters/src/filters.ts @@ -124,11 +124,11 @@ function evaluateComparison(comparison: FilterNode, data: any): boolean { let value = comparison.value; // Traverse the nested attributes to get the actual value - const attributePath = attribute ? attribute.split('.') : []; - let current = data; - while (attributePath.length > 0 && current) { - current = current[attributePath.shift()!]; - } + // const attributePath = attribute ? attribute.split('.') : []; + let current = attribute ? data[attribute] : undefined; + // while (attributePath.length > 0 && current) { + // current = current[attributePath.shift()!]; + // } // Handle missing attributes gracefully if (current === undefined) { @@ -228,45 +228,13 @@ export function evaluateSemver(version: string, operator: SemVerOperator, target // Evaluate the comparison based on the specified operator switch (operator) { case 'lte': - return vMajor < tMajor - ? true - : vMajor > tMajor - ? false - : vMinor < tMinor - ? true - : vMinor > tMinor - ? false - : vPatch <= tPatch; + return vMajor < tMajor ? true : vMajor > tMajor ? false : vMinor < tMinor ? true : vMinor > tMinor ? false : vPatch <= tPatch; case 'lt': - return vMajor < tMajor - ? true - : vMajor > tMajor - ? false - : vMinor < tMinor - ? true - : vMinor > tMinor - ? false - : vPatch < tPatch; + return vMajor < tMajor ? true : vMajor > tMajor ? false : vMinor < tMinor ? true : vMinor > tMinor ? false : vPatch < tPatch; case 'gte': - return vMajor > tMajor - ? true - : vMajor < tMajor - ? false - : vMinor > tMinor - ? true - : vMinor < tMinor - ? false - : vPatch >= tPatch; + return vMajor > tMajor ? true : vMajor < tMajor ? false : vMinor > tMinor ? true : vMinor < tMinor ? false : vPatch >= tPatch; case 'gt': - return vMajor > tMajor - ? true - : vMajor < tMajor - ? false - : vMinor > tMinor - ? true - : vMinor < tMinor - ? false - : vPatch > tPatch; + return vMajor > tMajor ? true : vMajor < tMajor ? false : vMinor > tMinor ? true : vMinor < tMinor ? false : vPatch > tPatch; case 'eq': return vMajor === tMajor && vMinor === tMinor && vPatch === tPatch; default: diff --git a/packages/@pandino/pandino-api/src/bundle/bundle-context.ts b/packages/@pandino/pandino-api/src/bundle/bundle-context.ts index bea9e00..9cf1a43 100644 --- a/packages/@pandino/pandino-api/src/bundle/bundle-context.ts +++ b/packages/@pandino/pandino-api/src/bundle/bundle-context.ts @@ -239,11 +239,7 @@ export interface BundleContext extends BundleReference { * @returns {ServiceRegistration} object for use by the bundle registering the service to update the service's * properties or to unregister the service. */ - registerService( - identifiers: string[] | string, - service: S | ServiceFactory, - properties?: ServiceProperties, - ): ServiceRegistration; + registerService(identifiers: string[] | string, service: S | ServiceFactory, properties?: ServiceProperties): ServiceRegistration; /** * Returns a {@code ServiceReference} object for a service that implements and was registered under the specified class. diff --git a/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.test.ts b/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.test.ts index fc5833d..c0cbaad 100644 --- a/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.test.ts @@ -75,16 +75,12 @@ describe('BundleContextImpl', () => { const serviceChangedListener: ServiceListener = { serviceChanged, }; - const mockGetService = vi - .fn() - .mockImplementation((bundle: Bundle, registration: ServiceRegistration) => ({ - execute: () => true, - })); - const mockUngetService = vi - .fn() - .mockImplementation((bundle: Bundle, registration: ServiceRegistration, service: MockService) => { - return; - }); + const mockGetService = vi.fn().mockImplementation((bundle: Bundle, registration: ServiceRegistration) => ({ + execute: () => true, + })); + const mockUngetService = vi.fn().mockImplementation((bundle: Bundle, registration: ServiceRegistration, service: MockService) => { + return; + }); let params: FrameworkConfigMap; let logger: Logger; let pandino: Pandino; @@ -269,6 +265,16 @@ describe('BundleContextImpl', () => { expect(reference.getUsingBundles().length).toEqual(0); }); + it('getServiceReference() for multiple interfaces', () => { + bundleContext.registerService(['@scope/bundle/service1', '@scope/bundle/service2'], mockService); + const reference1: ServiceReference = bundleContext.getServiceReference('@scope/bundle/service1'); + const reference2: ServiceReference = bundleContext.getServiceReference('@scope/bundle/service2'); + + expect(reference1).toBeDefined(); + expect(reference2).toBeDefined(); + expect(reference1).toEqual(reference2); + }); + it('getServiceReferences() with proper filter', () => { const otherMockService: MockService = { execute(): boolean { diff --git a/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.ts b/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.ts index 6ae51f7..a004ed9 100644 --- a/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.ts @@ -95,9 +95,7 @@ export class BundleContextImpl implements BundleContext { if (typeof locationOrHeaders === 'string') { this.logger.debug(`Installing Bundle from location: ${locationOrHeaders}`); } else { - this.logger.debug( - `Installing Bundle: ${locationOrHeaders[BUNDLE_SYMBOLICNAME]}: ${locationOrHeaders[BUNDLE_VERSION]}`, - ); + this.logger.debug(`Installing Bundle: ${locationOrHeaders[BUNDLE_SYMBOLICNAME]}: ${locationOrHeaders[BUNDLE_VERSION]}`); } this.checkValidity(); @@ -142,11 +140,7 @@ export class BundleContextImpl implements BundleContext { return this.pandino.getAllowedServiceReferences(this.bundle, identifier, filter, true); } - registerService( - identifiers: string[] | string, - service: S | ServiceFactory, - properties?: ServiceProperties, - ): ServiceRegistration { + registerService(identifiers: string[] | string, service: S | ServiceFactory, properties?: ServiceProperties): ServiceRegistration { this.checkValidity(); return this.pandino.registerService(this, identifiers, service, properties || {}); } @@ -225,15 +219,11 @@ export class BundleContextImpl implements BundleContext { } modifiedBundle(bundle: Bundle, event: BundleEvent, object: T) { - customizer.modifiedBundle - ? customizer.modifiedBundle(bundle, event, object) - : super.modifiedBundle(bundle, event, object); + customizer.modifiedBundle ? customizer.modifiedBundle(bundle, event, object) : super.modifiedBundle(bundle, event, object); } removedBundle(bundle: Bundle, event: BundleEvent, object: T) { - customizer.removedBundle - ? customizer.removedBundle(bundle, event, object) - : super.removedBundle(bundle, event, object); + customizer.removedBundle ? customizer.removedBundle(bundle, event, object) : super.removedBundle(bundle, event, object); } })(); @@ -242,10 +232,7 @@ export class BundleContextImpl implements BundleContext { return tracker; } - trackService( - identifierOrFilter: string | FilterNode, - customizer: Partial>, - ): ServiceTracker { + trackService(identifierOrFilter: string | FilterNode, customizer: Partial>): ServiceTracker { this.checkValidity(); const self = this; @@ -260,15 +247,11 @@ export class BundleContextImpl implements BundleContext { } modifiedService(reference: ServiceReference, service: T) { - customizer.modifiedService - ? customizer.modifiedService(reference, service) - : super.modifiedService(reference, service); + customizer.modifiedService ? customizer.modifiedService(reference, service) : super.modifiedService(reference, service); } removedService(reference: ServiceReference, service: T) { - customizer.removedService - ? customizer.removedService(reference, service) - : super.removedService(reference, service); + customizer.removedService ? customizer.removedService(reference, service) : super.removedService(reference, service); } })(); diff --git a/packages/@pandino/pandino/src/lib/framework/bundle-impl.ts b/packages/@pandino/pandino/src/lib/framework/bundle-impl.ts index eacb22a..656e043 100644 --- a/packages/@pandino/pandino/src/lib/framework/bundle-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/bundle-impl.ts @@ -1,12 +1,5 @@ import { Logger, BUNDLE_ACTIVATOR } from '@pandino/pandino-api'; -import type { - Bundle, - BundleActivator, - BundleContext, - BundleManifestHeaders, - BundleState, - ServiceReference, -} from '@pandino/pandino-api'; +import type { Bundle, BundleActivator, BundleContext, BundleManifestHeaders, BundleState, ServiceReference } from '@pandino/pandino-api'; import { evaluateSemver } from '@pandino/filters'; import { Pandino } from '../../pandino'; import { BundleRevisionImpl } from './bundle-revision-impl'; @@ -145,11 +138,7 @@ export class BundleImpl implements Bundle { } private createRevision(headers?: BundleManifestHeaders): BundleRevisionImpl { - const revision = new BundleRevisionImpl( - this, - this.getBundleId() + '.' + this.revisions.length, - headers || this.headers, - ); + const revision = new BundleRevisionImpl(this, this.getBundleId() + '.' + this.revisions.length, headers || this.headers); let bundleVersion = revision.getVersion(); bundleVersion = !bundleVersion ? '0.0.0' : bundleVersion; diff --git a/packages/@pandino/pandino/src/lib/framework/bundle-revision-impl.test.ts b/packages/@pandino/pandino/src/lib/framework/bundle-revision-impl.test.ts index fe98ebe..bfe70c4 100644 --- a/packages/@pandino/pandino/src/lib/framework/bundle-revision-impl.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/bundle-revision-impl.test.ts @@ -1,11 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { - BUNDLE_ACTIVATIONPOLICY, - BUNDLE_SYMBOLICNAME, - BUNDLE_VERSION, - PROVIDE_CAPABILITY, - REQUIRE_CAPABILITY, -} from '@pandino/pandino-api'; +import { BUNDLE_ACTIVATIONPOLICY, BUNDLE_SYMBOLICNAME, BUNDLE_VERSION, PROVIDE_CAPABILITY, REQUIRE_CAPABILITY } from '@pandino/pandino-api'; import type { ActivationPolicy, BundleManifestHeaders } from '@pandino/pandino-api'; import { BundleRevisionImpl } from './bundle-revision-impl'; import { BundleCapabilityImpl } from './wiring'; diff --git a/packages/@pandino/pandino/src/lib/framework/bundle-revision-impl.ts b/packages/@pandino/pandino/src/lib/framework/bundle-revision-impl.ts index c7e681c..5240e3e 100644 --- a/packages/@pandino/pandino/src/lib/framework/bundle-revision-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/bundle-revision-impl.ts @@ -27,11 +27,7 @@ export class BundleRevisionImpl implements BundleRevision, Resource { this.id = id; this.headerMap = headerMap ?? {}; - const mp: ManifestParser = new ManifestParserImpl( - bundle.getFramework().getConfig(), - this, - this.headerMap as BundleManifestHeaders, - ); + const mp: ManifestParser = new ManifestParserImpl(bundle.getFramework().getConfig(), this, this.headerMap as BundleManifestHeaders); this.manifestVersion = mp.getManifestVersion(); this.version = mp.getBundleVersion(); @@ -49,9 +45,7 @@ export class BundleRevisionImpl implements BundleRevision, Resource { if (other === undefined || other === null || !(other instanceof BundleRevisionImpl)) { return false; } - return ( - this.getSymbolicName() === other.getSymbolicName() && evaluateSemver(this.getVersion(), 'eq', other.getVersion()) - ); + return this.getSymbolicName() === other.getSymbolicName() && evaluateSemver(this.getVersion(), 'eq', other.getVersion()); } getBundle(): Bundle { diff --git a/packages/@pandino/pandino/src/lib/framework/bundle-tracker-impl.test.ts b/packages/@pandino/pandino/src/lib/framework/bundle-tracker-impl.test.ts index dcbc697..16dd4cc 100644 --- a/packages/@pandino/pandino/src/lib/framework/bundle-tracker-impl.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/bundle-tracker-impl.test.ts @@ -10,14 +10,7 @@ import { PANDINO_BUNDLE_IMPORTER_PROP, PANDINO_MANIFEST_FETCHER_PROP, } from '@pandino/pandino-api'; -import type { - Bundle, - BundleActivator, - BundleContext, - BundleEvent, - BundleImporter, - BundleManifestHeaders, -} from '@pandino/pandino-api'; +import type { Bundle, BundleActivator, BundleContext, BundleEvent, BundleImporter, BundleManifestHeaders } from '@pandino/pandino-api'; import { Pandino } from '../../pandino'; describe('BundleTrackerImpl', () => { diff --git a/packages/@pandino/pandino/src/lib/framework/bundle-tracker-impl.ts b/packages/@pandino/pandino/src/lib/framework/bundle-tracker-impl.ts index 3e71fa4..b33b8be 100644 --- a/packages/@pandino/pandino/src/lib/framework/bundle-tracker-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/bundle-tracker-impl.ts @@ -1,12 +1,4 @@ -import type { - Bundle, - BundleContext, - BundleEvent, - BundleListener, - BundleState, - BundleTracker, - BundleTrackerCustomizer, -} from '@pandino/pandino-api'; +import type { Bundle, BundleContext, BundleEvent, BundleListener, BundleState, BundleTracker, BundleTrackerCustomizer } from '@pandino/pandino-api'; import { AbstractTracked } from './abstract-tracked'; export class BundleTrackerImpl implements BundleTracker { diff --git a/packages/@pandino/pandino/src/lib/framework/bundle-wiring-impl.ts b/packages/@pandino/pandino/src/lib/framework/bundle-wiring-impl.ts index f53c548..fc8e384 100644 --- a/packages/@pandino/pandino/src/lib/framework/bundle-wiring-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/bundle-wiring-impl.ts @@ -1,10 +1,4 @@ -import { - EFFECTIVE_DIRECTIVE, - EFFECTIVE_RESOLVE, - HOST_NAMESPACE, - PACKAGE_NAMESPACE, - RESOLUTION_DIRECTIVE, -} from '@pandino/pandino-api'; +import { EFFECTIVE_DIRECTIVE, EFFECTIVE_RESOLVE, HOST_NAMESPACE, PACKAGE_NAMESPACE, RESOLUTION_DIRECTIVE } from '@pandino/pandino-api'; import type { Bundle, BundleState } from '@pandino/pandino-api'; import { BundleRevisionImpl } from './bundle-revision-impl'; import { BundleImpl } from './bundle-impl'; @@ -27,12 +21,7 @@ export class BundleWiringImpl implements BundleWiring { private readonly resolvedReqs: Array = []; private isDisposed = false; - constructor( - configMap: Record, - resolver: StatefulResolver, - revision: BundleRevisionImpl, - wires: Array = [], - ) { + constructor(configMap: Record, resolver: StatefulResolver, revision: BundleRevisionImpl, wires: Array = []) { this.configMap = configMap; this.resolver = resolver; this.revision = revision; @@ -81,8 +70,7 @@ export class BundleWiringImpl implements BundleWiring { isCurrent(): boolean { const bundle = this.getBundle(); - const current: BundleRevision | undefined = - bundle.getState() === 'UNINSTALLED' ? undefined : (bundle as BundleImpl).getCurrentRevision(); + const current: BundleRevision | undefined = bundle.getState() === 'UNINSTALLED' ? undefined : (bundle as BundleImpl).getCurrentRevision(); return current ? current.getWiring() === this : false; } @@ -156,9 +144,7 @@ export class BundleWiringImpl implements BundleWiring { } allWireProvidersInAnyState(states: BundleState[] = []): boolean { - return this.wires.every( - (w) => !!w.getProvider().getBundle() && states.includes(w.getProvider().getBundle()!.getState()), - ); + return this.wires.every((w) => !!w.getProvider().getBundle() && states.includes(w.getProvider().getBundle()!.getState())); } toString(): string { diff --git a/packages/@pandino/pandino/src/lib/framework/event-dispatcher.ts b/packages/@pandino/pandino/src/lib/framework/event-dispatcher.ts index 365548b..113ac4f 100644 --- a/packages/@pandino/pandino/src/lib/framework/event-dispatcher.ts +++ b/packages/@pandino/pandino/src/lib/framework/event-dispatcher.ts @@ -34,25 +34,19 @@ export class EventDispatcher { } fireServiceEvent(event: ServiceEvent, oldProps: Record): void { - const listeners: Map> = new Map>( - this.svcListeners.entries(), - ); + const listeners: Map> = new Map>(this.svcListeners.entries()); EventDispatcher.fireEventImmediately('SERVICE', listeners, event, oldProps); } fireFrameworkEvent(event: FrameworkEvent, source: BundleImpl): void { - const listeners: Map> = new Map>( - this.fwkListeners.entries(), - ); + const listeners: Map> = new Map>(this.fwkListeners.entries()); EventDispatcher.fireEventImmediately('FRAMEWORK', listeners, event, source); } fireBundleEvent(event: BundleEvent, source?: BundleImpl): void { - const listeners: Map> = new Map>( - this.bndListeners.entries(), - ); + const listeners: Map> = new Map>(this.bndListeners.entries()); EventDispatcher.fireEventImmediately('BUNDLE', listeners, event, source); } @@ -125,11 +119,7 @@ export class EventDispatcher { } } - private static invokeFrameworkListenerCallback( - bundle: Bundle, - listener: FrameworkListener, - event: FrameworkEventImpl, - ): void { + private static invokeFrameworkListenerCallback(bundle: Bundle, listener: FrameworkListener, event: FrameworkEventImpl): void { const validBundleStateTypes: BundleState[] = ['STARTING', 'ACTIVE']; if (validBundleStateTypes.includes(bundle.getState())) { if (listener.isSync) { @@ -232,13 +222,8 @@ export class EventDispatcher { this.svcListeners = EventDispatcher.removeListenerInfos(this.svcListeners, bc); } - private static removeListenerInfos( - listeners: Map>, - bc: BundleContext, - ): Map> { - const copy: Map> = new Map>( - listeners.entries(), - ); + private static removeListenerInfos(listeners: Map>, bc: BundleContext): Map> { + const copy: Map> = new Map>(listeners.entries()); copy.delete(bc); return copy; } @@ -248,9 +233,7 @@ export class EventDispatcher { bc: BundleContext, idx: number, ): Map> { - const copy: Map> = new Map>( - listeners.entries(), - ); + const copy: Map> = new Map>(listeners.entries()); const infos: Array = [...copy.get(bc)!]; copy.delete(bc); if (Array.isArray(infos)) { @@ -278,13 +261,7 @@ export class EventDispatcher { if (info.getBundleContext().equals(bc) && info.getListener() === listener) { // The spec says to update the filter in this case. const oldFilter = info.getParsedFilter(); - const newInfo = new ListenerInfo( - info.getBundle()!, - info.getBundleContext(), - info.getListener(), - undefined, - filter, - ); + const newInfo = new ListenerInfo(info.getBundle()!, info.getBundleContext(), info.getListener(), undefined, filter); this.svcListeners = EventDispatcher.updateListenerInfo(this.svcListeners, i, newInfo); return oldFilter; } @@ -311,10 +288,7 @@ export class EventDispatcher { return listeners; } - private static addListenerInfo( - listeners: Map>, - info: ListenerInfo, - ): Map> { + private static addListenerInfo(listeners: Map>, info: ListenerInfo): Map> { if (!listeners.has(info.getBundleContext())) { listeners.set(info.getBundleContext(), []); } diff --git a/packages/@pandino/pandino/src/lib/framework/service-reference-impl.ts b/packages/@pandino/pandino/src/lib/framework/service-reference-impl.ts index 53851ce..186102c 100644 --- a/packages/@pandino/pandino/src/lib/framework/service-reference-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/service-reference-impl.ts @@ -1,10 +1,4 @@ -import { - SERVICE_DEFAULT_RANK, - SERVICE_ID, - SERVICE_RANKING, - PACKAGE_NAMESPACE, - OBJECTCLASS, -} from '@pandino/pandino-api'; +import { SERVICE_DEFAULT_RANK, SERVICE_ID, SERVICE_RANKING, PACKAGE_NAMESPACE, OBJECTCLASS } from '@pandino/pandino-api'; import type { Bundle, ServiceProperties, ServiceReference } from '@pandino/pandino-api'; import { ServiceRegistrationImpl } from './service-registration-impl'; import { BundleCapabilityImpl } from './wiring/bundle-capability-impl'; @@ -154,10 +148,7 @@ export class ServiceReferenceImpl extends BundleCapabilityImpl implements Servic const wires = wiring.getRequiredWires(undefined); if (Array.isArray(wires)) { for (const w of wires) { - if ( - w.getCapability().getNamespace() === PACKAGE_NAMESPACE && - w.getCapability().getAttributes()[PACKAGE_NAMESPACE] === name - ) { + if (w.getCapability().getNamespace() === PACKAGE_NAMESPACE && w.getCapability().getAttributes()[PACKAGE_NAMESPACE] === name) { return w; } } diff --git a/packages/@pandino/pandino/src/lib/framework/service-registration-impl.ts b/packages/@pandino/pandino/src/lib/framework/service-registration-impl.ts index 37232e9..3643f78 100644 --- a/packages/@pandino/pandino/src/lib/framework/service-registration-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/service-registration-impl.ts @@ -1,19 +1,5 @@ -import { - OBJECTCLASS, - SCOPE_SINGLETON, - SERVICE_BUNDLEID, - SERVICE_ID, - SERVICE_SCOPE, - SCOPE_BUNDLE, - SCOPE_PROTOTYPE, -} from '@pandino/pandino-api'; -import type { - Bundle, - ServiceProperties, - ServiceReference, - ServiceRegistration, - ServiceFactory, -} from '@pandino/pandino-api'; +import { OBJECTCLASS, SCOPE_SINGLETON, SERVICE_BUNDLEID, SERVICE_ID, SERVICE_SCOPE, SCOPE_BUNDLE, SCOPE_PROTOTYPE } from '@pandino/pandino-api'; +import type { Bundle, ServiceProperties, ServiceReference, ServiceRegistration, ServiceFactory } from '@pandino/pandino-api'; import { ServiceReferenceImpl } from './service-reference-impl'; import { ServiceRegistry } from './service-registry'; import { ServiceRegistryImpl } from './service-registry-impl'; @@ -29,14 +15,7 @@ export class ServiceRegistrationImpl implements ServiceRegistration { private readonly ref: ServiceReferenceImpl; private isUnregistering = false; - constructor( - registry: ServiceRegistry, - bundle: Bundle, - classNames: string | string[], - serviceId: number, - svcObj: any, - dict?: ServiceProperties, - ) { + constructor(registry: ServiceRegistry, bundle: Bundle, classNames: string | string[], serviceId: number, svcObj: any, dict?: ServiceProperties) { this.registry = registry; this.bundle = bundle; this.classes = classNames; @@ -74,9 +53,7 @@ export class ServiceRegistrationImpl implements ServiceRegistration { try { this.ungetFactoryUnchecked(relBundle, svcObj); } catch (e) { - (this.registry as ServiceRegistryImpl) - .getLogger() - .error('ServiceRegistrationImpl: Error ungetting service.', e); + (this.registry as ServiceRegistryImpl).getLogger().error('ServiceRegistrationImpl: Error ungetting service.', e); } } } diff --git a/packages/@pandino/pandino/src/lib/framework/service-registry-impl.test.ts b/packages/@pandino/pandino/src/lib/framework/service-registry-impl.test.ts index d5c3972..996c444 100644 --- a/packages/@pandino/pandino/src/lib/framework/service-registry-impl.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/service-registry-impl.test.ts @@ -1,20 +1,6 @@ import { describe, beforeEach, expect, it, vi } from 'vitest'; -import { - OBJECTCLASS, - SCOPE_PROTOTYPE, - SCOPE_SINGLETON, - SERVICE_BUNDLEID, - SERVICE_ID, - SERVICE_SCOPE, -} from '@pandino/pandino-api'; -import type { - Bundle, - PrototypeServiceFactory, - ServiceEvent, - ServiceProperties, - ServiceReference, - ServiceRegistration, -} from '@pandino/pandino-api'; +import { OBJECTCLASS, SCOPE_PROTOTYPE, SCOPE_SINGLETON, SERVICE_BUNDLEID, SERVICE_ID, SERVICE_SCOPE } from '@pandino/pandino-api'; +import type { Bundle, PrototypeServiceFactory, ServiceEvent, ServiceProperties, ServiceReference, ServiceRegistration } from '@pandino/pandino-api'; import { ServiceRegistryImpl } from './service-registry-impl'; import type { ServiceRegistry } from './service-registry'; import type { ServiceRegistryCallbacks } from './service-registry-callbacks'; @@ -58,11 +44,7 @@ describe('ServiceRegistryImpl', () => { }); it('registerService() general use-cases', () => { - const reg: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - helloService, - ); + const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService); const ref: ServiceReference = reg.getReference(); expect(sr.getRegisteredServices(bundle1).length).toEqual(1); @@ -80,16 +62,11 @@ describe('ServiceRegistryImpl', () => { }); it('registerService() with custom props', () => { - const reg: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - helloService, - { - propNum: 1, - propBool: true, - propStr: 'yayy', - }, - ); + const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService, { + propNum: 1, + propBool: true, + propStr: 'yayy', + }); const ref: ServiceReference = reg.getReference(); expect(ref.getPropertyKeys()).toEqual(['propNum', 'propBool', 'propStr', ...defaultPropKeys]); @@ -99,16 +76,8 @@ describe('ServiceRegistryImpl', () => { }); it('multiple service registrations', () => { - const regHello: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - helloService, - ); - const regWelcome: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/welcome-impl', - welcomeService, - ); + const regHello: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService); + const regWelcome: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/welcome-impl', welcomeService); expect(sr.getRegisteredServices(bundle1).length).toEqual(2); @@ -116,12 +85,22 @@ describe('ServiceRegistryImpl', () => { expect(regWelcome.getProperty(OBJECTCLASS)).toEqual('@pandino/pandino/welcome-impl'); }); + it('multiple interface implementation registration', () => { + const cls1 = '@pandino/pandino/one'; + const cls2 = '@pandino/pandino/two'; + const regMulti: ServiceRegistration = sr.registerService(bundle1, [cls1, cls2], helloService); + + expect(sr.getRegisteredServices(bundle1).length).toEqual(1); + + expect(sr.getServiceReferences(cls1).length).toEqual(1); + expect(sr.getServiceReferences(cls2).length).toEqual(1); + expect(regMulti.getProperty(OBJECTCLASS)).toEqual([cls1, cls2]); + expect(regMulti.getReference().hasObjectClass(cls1)).toEqual(true); + expect(regMulti.getReference().hasObjectClass(cls2)).toEqual(true); + }); + it('unregisterService()', () => { - const reg: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - helloService, - ); + const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService); const ref: ServiceReference = reg.getReference(); sr.getService(bundle1, ref); @@ -137,16 +116,8 @@ describe('ServiceRegistryImpl', () => { }); it('unregisterServices()', () => { - const regHello: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - helloService, - ); - const regWelcome: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/welcome-impl', - welcomeService, - ); + const regHello: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService); + const regWelcome: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/welcome-impl', welcomeService); const refHello: ServiceReference = regHello.getReference(); const refWelcome: ServiceReference = regWelcome.getReference(); @@ -165,11 +136,7 @@ describe('ServiceRegistryImpl', () => { }); it('usageCount calculation', () => { - const reg: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - helloService, - ); + const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService); const ref: ServiceReference = reg.getReference(); const uc1 = sr.obtainUsageCount(bundle1, ref, helloService); const uc2 = sr.obtainUsageCount(bundle2, ref, helloService); @@ -188,11 +155,7 @@ describe('ServiceRegistryImpl', () => { }); it('getService() returns singleton service', () => { - const reg: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - helloService, - ); + const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService); const ref: ServiceReference = reg.getReference(); const service1 = sr.getService(bundle1, ref); @@ -204,11 +167,7 @@ describe('ServiceRegistryImpl', () => { }); it('unregister() removes Reference from ServiceRegistration', () => { - const reg: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - helloService, - ); + const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService); const ref: ServiceReference = reg.getReference(); reg.unregister(); @@ -229,11 +188,7 @@ describe('ServiceRegistryImpl', () => { serviceChanged: mockServiceChanged, }; sr = new ServiceRegistryImpl(null, callbacks); - const reg: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - helloService, - ); + const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService); const ref: ServiceReference = reg.getReference(); const oldProps = { objectClass: '@pandino/pandino/hello-impl', @@ -268,14 +223,9 @@ describe('ServiceRegistryImpl', () => { }, ungetService(bundle: Bundle, registration: ServiceRegistration, service: HelloService) {}, }; - const reg: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - prototypeFactory, - { - [SERVICE_SCOPE]: SCOPE_PROTOTYPE, - }, - ); + const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', prototypeFactory, { + [SERVICE_SCOPE]: SCOPE_PROTOTYPE, + }); const ref: ServiceReference = reg.getReference(); const service1 = sr.getService(bundle1, ref, true); const service2 = sr.getService(bundle1, ref, true); @@ -293,14 +243,9 @@ describe('ServiceRegistryImpl', () => { }, ungetService(bundle: Bundle, registration: ServiceRegistration, service: string) {}, }; - const reg: ServiceRegistration = sr.registerService( - bundle1, - '@pandino/pandino/hello-impl', - prototypeFactory, - { - [SERVICE_SCOPE]: SCOPE_PROTOTYPE, - }, - ); + const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', prototypeFactory, { + [SERVICE_SCOPE]: SCOPE_PROTOTYPE, + }); const ref: ServiceReference = reg.getReference(); sr.getService(bundle1, ref, true); diff --git a/packages/@pandino/pandino/src/lib/framework/service-registry-impl.ts b/packages/@pandino/pandino/src/lib/framework/service-registry-impl.ts index c1474f0..0c74a9f 100644 --- a/packages/@pandino/pandino/src/lib/framework/service-registry-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/service-registry-impl.ts @@ -16,10 +16,7 @@ import type { UsageCount } from './usage-count'; export class ServiceRegistryImpl implements ServiceRegistry { private readonly logger: Logger; private readonly callbacks: ServiceRegistryCallbacks; - private readonly regsMap: Map>> = new Map< - Bundle, - Array> - >(); + private readonly regsMap: Map>> = new Map>>(); private readonly regCapSet: CapabilitySet = new CapabilitySet([OBJECTCLASS]); private readonly inUseMap: Map = new Map(); private currentServiceId = 0; @@ -129,12 +126,7 @@ export class ServiceRegistryImpl implements ServiceRegistry { return bundles; } - registerService( - bundle: Bundle, - classNames: string | string[], - svcObj: any, - dict?: ServiceProperties, - ): ServiceRegistration { + registerService(bundle: Bundle, classNames: string | string[], svcObj: any, dict?: ServiceProperties): ServiceRegistration { const reg = new ServiceRegistrationImpl(this, bundle, classNames, ++this.currentServiceId, svcObj, dict); if (!this.regsMap.has(bundle)) { @@ -146,10 +138,7 @@ export class ServiceRegistryImpl implements ServiceRegistry { // TODO: implement check if same service gets registered or not! if (!regs) { // this.logger.warn(`There are no registrations for bundle! (${bundle.getSymbolicName()})`); - } else if ( - regs && - !regs.find((r) => r.getReference().getProperty(SERVICE_ID) === reg.getReference().getProperty(SERVICE_ID)) - ) { + } else if (regs && !regs.find((r) => r.getReference().getProperty(SERVICE_ID) === reg.getReference().getProperty(SERVICE_ID))) { regs.push(reg); } else { this.logger.warn(`Service already registered, skipping! (${reg.getReference().getProperty(SERVICE_ID)})`); @@ -270,22 +259,14 @@ export class ServiceRegistryImpl implements ServiceRegistry { * object will be created, but this can only be done if the {@code isPrototype} parameter is not {@code undefined}. * If {@code isPrototype} is {@code TRUE} then a new UsageCount object will always be created. */ - obtainUsageCount( - bundle: Bundle, - ref: ServiceReference, - svcObj: any, - isPrototype = false, - ): UsageCount | undefined { + obtainUsageCount(bundle: Bundle, ref: ServiceReference, svcObj: any, isPrototype = false): UsageCount | undefined { let usage: UsageCount; const usages = this.inUseMap.get(bundle); if (!isPrototype && Array.isArray(usages)) { for (const usage of usages) { - if ( - usage.getReference().compareTo(ref) === 0 && - ((!svcObj && !usage.isPrototype()) || usage.getService() === svcObj) - ) { + if (usage.getReference().compareTo(ref) === 0 && ((!svcObj && !usage.isPrototype()) || usage.getService() === svcObj)) { return usage; } } diff --git a/packages/@pandino/pandino/src/lib/framework/service-registry.ts b/packages/@pandino/pandino/src/lib/framework/service-registry.ts index 30e4a3e..7d5504a 100644 --- a/packages/@pandino/pandino/src/lib/framework/service-registry.ts +++ b/packages/@pandino/pandino/src/lib/framework/service-registry.ts @@ -4,24 +4,14 @@ import type { Capability } from './resource'; export interface ServiceRegistry { getRegisteredServices(bundle: Bundle): ServiceReference[]; - registerService( - bundle: Bundle, - classNames: string | string[], - svcObj: any, - dict?: ServiceProperties, - ): ServiceRegistration; + registerService(bundle: Bundle, classNames: string | string[], svcObj: any, dict?: ServiceProperties): ServiceRegistration; servicePropertiesModified(reg: ServiceRegistration, oldProps: ServiceProperties): void; getServiceReferences(identifier?: string, filter?: string): Array; getService(bundle: Bundle, ref: ServiceReference, isServiceObjects?: boolean): S | undefined; getUsingBundles(ref: ServiceReference): Bundle[]; unregisterService(bundle: Bundle, reg: ServiceRegistration): void; ungetService(bundle: Bundle, ref: ServiceReference, svcObj: any): boolean; - obtainUsageCount( - bundle: Bundle, - ref: ServiceReference, - svcObj: any, - isPrototype?: boolean, - ): UsageCount | undefined; + obtainUsageCount(bundle: Bundle, ref: ServiceReference, svcObj: any, isPrototype?: boolean): UsageCount | undefined; unregisterServices(bundle: Bundle): void; ungetServices(bundle: Bundle): void; } diff --git a/packages/@pandino/pandino/src/lib/framework/service-tracker-impl.ts b/packages/@pandino/pandino/src/lib/framework/service-tracker-impl.ts index 3cd547d..d57dac1 100644 --- a/packages/@pandino/pandino/src/lib/framework/service-tracker-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/service-tracker-impl.ts @@ -1,11 +1,5 @@ import { SERVICE_ID, SERVICE_RANKING } from '@pandino/pandino-api'; -import type { - ServiceEvent, - ServiceListener, - ServiceReference, - ServiceTracker, - ServiceTrackerCustomizer, -} from '@pandino/pandino-api'; +import type { ServiceEvent, ServiceListener, ServiceReference, ServiceTracker, ServiceTrackerCustomizer } from '@pandino/pandino-api'; import { serializeFilter } from '@pandino/filters'; import type { FilterNode } from '@pandino/filters'; import { AbstractTracked } from './abstract-tracked'; @@ -24,12 +18,7 @@ export class ServiceTrackerImpl implements ServiceTracker { this.context = context; this.customizer = customizer || this; this.listenerFilter = typeof filter === 'string' ? filter : serializeFilter(filter); - this.filter = - typeof filter === 'string' - ? this.listenerFilter - ? context.createFilter(this.listenerFilter) - : undefined - : filter; + this.filter = typeof filter === 'string' ? (this.listenerFilter ? context.createFilter(this.listenerFilter) : undefined) : filter; } open(): void { diff --git a/packages/@pandino/pandino/src/lib/framework/stateful-resolver.test.ts b/packages/@pandino/pandino/src/lib/framework/stateful-resolver.test.ts index 7bb3974..25de3f0 100644 --- a/packages/@pandino/pandino/src/lib/framework/stateful-resolver.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/stateful-resolver.test.ts @@ -45,15 +45,8 @@ describe('StatefulResolver', () => { isAttributeMandatory: () => false, getResource: () => undefined, }; - const allProvidedCapabilities: BundleCapability[] = [ - capability1 as BundleCapability, - capability2 as BundleCapability, - capability3 as BundleCapability, - ]; - const result: BundleWire[] = StatefulResolver.getResolvableWires( - revision as BundleRevision, - allProvidedCapabilities, - ); + const allProvidedCapabilities: BundleCapability[] = [capability1 as BundleCapability, capability2 as BundleCapability, capability3 as BundleCapability]; + const result: BundleWire[] = StatefulResolver.getResolvableWires(revision as BundleRevision, allProvidedCapabilities); expect(result.length).toEqual(1); diff --git a/packages/@pandino/pandino/src/lib/framework/stateful-resolver.ts b/packages/@pandino/pandino/src/lib/framework/stateful-resolver.ts index ec85188..f031c0e 100644 --- a/packages/@pandino/pandino/src/lib/framework/stateful-resolver.ts +++ b/packages/@pandino/pandino/src/lib/framework/stateful-resolver.ts @@ -31,9 +31,7 @@ export class StatefulResolver { const bundleWiring = this.resolve(revision as BundleRevisionImpl); if (bundleWiring) { - this.logger.debug( - `Bundle Wiring created for Revision: ${revision.getSymbolicName()}: ${revision.getVersion().toString()}`, - ); + this.logger.debug(`Bundle Wiring created for Revision: ${revision.getSymbolicName()}: ${revision.getVersion().toString()}`); const bundle = bundleWiring.getRevision().getBundle(); if (bundle) { @@ -46,14 +44,10 @@ export class StatefulResolver { this.logger.error(err); } } else { - this.logger.debug( - `Bundle not found in Revision: ${revision.getSymbolicName()}: ${revision.getVersion().toString()}`, - ); + this.logger.debug(`Bundle not found in Revision: ${revision.getSymbolicName()}: ${revision.getVersion().toString()}`); } } else { - this.logger.debug( - `No Wiring found for Revision: ${revision.getSymbolicName()}: ${revision.getVersion().toString()}`, - ); + this.logger.debug(`No Wiring found for Revision: ${revision.getSymbolicName()}: ${revision.getVersion().toString()}`); } } @@ -75,9 +69,7 @@ export class StatefulResolver { .map((rev) => rev.getWiring()!) .filter((wiring) => wiring.isInUse()); for (const wiring of wirings) { - const wire = wiring - .getRequiredWires(undefined) - .find((wire: BundleWire) => wire.getProvider().equals(bundle.getCurrentRevision())); + const wire = wiring.getRequiredWires(undefined).find((wire: BundleWire) => wire.getProvider().equals(bundle.getCurrentRevision())); if (wire) { bundles.push(wire.getRequirer().getBundle() as BundleImpl); } @@ -122,9 +114,7 @@ export class StatefulResolver { const wires: Array = []; for (const req of requirements) { const filter = (req as BundleRequirementImpl).getFilter(); - const providedCap = allProvidedCapabilities.find( - (p) => p.getNamespace() === req.getNamespace() && (filter ? CapabilitySet.matches(p, filter) : true), - ); + const providedCap = allProvidedCapabilities.find((p) => p.getNamespace() === req.getNamespace() && (filter ? CapabilitySet.matches(p, filter) : true)); if (providedCap) { const wire = new BundleWireImpl(req.getResource(), req, providedCap?.getResource()!, providedCap); wires.push(wire); diff --git a/packages/@pandino/pandino/src/lib/framework/util/listener-info.ts b/packages/@pandino/pandino/src/lib/framework/util/listener-info.ts index 6cff376..871fcb3 100644 --- a/packages/@pandino/pandino/src/lib/framework/util/listener-info.ts +++ b/packages/@pandino/pandino/src/lib/framework/util/listener-info.ts @@ -8,13 +8,7 @@ export class ListenerInfo { private readonly listener: ServiceListener | BundleListener | FrameworkListener; private readonly filter?: FilterNode; - constructor( - bundle: Bundle, - context: BundleContext, - listener: ServiceListener | BundleListener | FrameworkListener, - info?: ListenerInfo, - filter?: string, - ) { + constructor(bundle: Bundle, context: BundleContext, listener: ServiceListener | BundleListener | FrameworkListener, info?: ListenerInfo, filter?: string) { if (info) { this.bundle = info.bundle; this.context = info.context; diff --git a/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/manifest-parser-impl.test.ts b/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/manifest-parser-impl.test.ts index ccdabb5..5b07fc2 100644 --- a/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/manifest-parser-impl.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/manifest-parser-impl.test.ts @@ -94,10 +94,8 @@ describe('ManifestParserImp', () => { const headers: BundleManifestHeaders = { [BUNDLE_MANIFESTVERSION]: '2', [BUNDLE_SYMBOLICNAME]: '@scope/example/test/sample', - [PROVIDE_CAPABILITY]: - 'com.example;theArray:Array="red,green,blue";theNumber:number=111;version:SemVer=1.2.3', - [REQUIRE_CAPABILITY]: - 'com.example.other;theArray:Array="1,2,3";theNumber:number=999;com.example.other.bla="str"', + [PROVIDE_CAPABILITY]: 'com.example;theArray:Array="red,green,blue";theNumber:number=111;version:SemVer=1.2.3', + [REQUIRE_CAPABILITY]: 'com.example.other;theArray:Array="1,2,3";theNumber:number=999;com.example.other.bla="str"', }; const mockBundleRevision = { getSymbolicName: vi.fn().mockReturnValue('@scope/example/test/sample'), diff --git a/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/manifest-parser-impl.ts b/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/manifest-parser-impl.ts index b7ef9f3..407c16a 100644 --- a/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/manifest-parser-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/manifest-parser-impl.ts @@ -88,9 +88,7 @@ export class ManifestParserImpl implements ManifestParser { requireCaps.push(...ManifestParserImpl.getRequiredClauses(part.trim(), owner)); } } else if (typeof headerMap[REQUIRE_CAPABILITY] === 'string') { - requireCaps.push( - ...ManifestParserImpl.getRequiredClauses((headerMap[REQUIRE_CAPABILITY] as string).trim(), owner), - ); + requireCaps.push(...ManifestParserImpl.getRequiredClauses((headerMap[REQUIRE_CAPABILITY] as string).trim(), owner)); } // Parse Provide-Capability. @@ -100,9 +98,7 @@ export class ManifestParserImpl implements ManifestParser { provideCaps.push(...ManifestParserImpl.getProviderClauses(part.trim(), owner)); } } else if (typeof headerMap[PROVIDE_CAPABILITY] === 'string') { - provideCaps.push( - ...ManifestParserImpl.getProviderClauses((headerMap[PROVIDE_CAPABILITY] as string).trim(), owner), - ); + provideCaps.push(...ManifestParserImpl.getProviderClauses((headerMap[PROVIDE_CAPABILITY] as string).trim(), owner)); } // Combine all requirements. @@ -173,10 +169,7 @@ export class ManifestParserImpl implements ManifestParser { return manifestVersion?.trim(); } - private static parseBundleSymbolicName( - owner: BundleRevision, - headerMap: Record, - ): BundleCapabilityImpl | never { + private static parseBundleSymbolicName(owner: BundleRevision, headerMap: Record): BundleCapabilityImpl | never { const clauses = this.normalizeCapabilityClauses(this.parseStandardHeader(headerMap[BUNDLE_SYMBOLICNAME])); if (clauses.length > 0) { if (clauses.length > 1) { @@ -400,9 +393,7 @@ export class ManifestParserImpl implements ManifestParser { private parseActivationPolicy(headerMap: Record): void { this.activationPolicy = 'EAGER_ACTIVATION'; - const clauses: Array = ManifestParserImpl.parseStandardHeader( - headerMap[BUNDLE_ACTIVATIONPOLICY], - ); + const clauses: Array = ManifestParserImpl.parseStandardHeader(headerMap[BUNDLE_ACTIVATIONPOLICY]); if (clauses.length > 0) { for (const path of clauses[0].paths) { diff --git a/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/parsed-header-clause.ts b/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/parsed-header-clause.ts index 2ae0baa..8c8924a 100644 --- a/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/parsed-header-clause.ts +++ b/packages/@pandino/pandino/src/lib/framework/util/manifest-parser/parsed-header-clause.ts @@ -4,12 +4,7 @@ export class ParsedHeaderClause { public readonly attrs: Record; public readonly types: Record; - constructor( - paths: Array, - dirs: Record, - attrs: Record, - types: Record, - ) { + constructor(paths: Array, dirs: Record, attrs: Record, types: Record) { this.paths = paths; this.dirs = dirs; this.attrs = attrs; diff --git a/packages/@pandino/pandino/src/lib/framework/wiring/bundle-capability-impl.test.ts b/packages/@pandino/pandino/src/lib/framework/wiring/bundle-capability-impl.test.ts index b7f68ab..7408866 100644 --- a/packages/@pandino/pandino/src/lib/framework/wiring/bundle-capability-impl.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/wiring/bundle-capability-impl.test.ts @@ -19,13 +19,7 @@ import { PANDINO_MANIFEST_FETCHER_PROP, USES_DIRECTIVE, } from '@pandino/pandino-api'; -import type { - BundleActivator, - BundleImporter, - BundleManifestHeaders, - FrameworkConfigMap, - Logger, -} from '@pandino/pandino-api'; +import type { BundleActivator, BundleImporter, BundleManifestHeaders, FrameworkConfigMap, Logger } from '@pandino/pandino-api'; describe('BundleCapabilityImpl', () => { const dummyActivator: BundleActivator = { @@ -101,9 +95,7 @@ describe('BundleCapabilityImpl', () => { expect(capability.isAttributeMandatory('attr1')).toEqual(false); expect(capability.isAttributeMandatory('attr2')).toEqual(true); expect(capability.getUses()).toEqual(['uses']); - expect(capability.toString()).toEqual( - '[@scope/bundle: 1.2.3 (R 1)] test.namespace; attr1=111; attr2=yayy; attr3=true', - ); + expect(capability.toString()).toEqual('[@scope/bundle: 1.2.3 (R 1)] test.namespace; attr1=111; attr2=yayy; attr3=true'); }); it('equals', () => { diff --git a/packages/@pandino/pandino/src/lib/framework/wiring/bundle-capability-impl.ts b/packages/@pandino/pandino/src/lib/framework/wiring/bundle-capability-impl.ts index 56e59a8..ae1e747 100644 --- a/packages/@pandino/pandino/src/lib/framework/wiring/bundle-capability-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/wiring/bundle-capability-impl.ts @@ -30,12 +30,7 @@ export class BundleCapabilityImpl implements BundleCapability { private readonly uses: string[] = []; private readonly mandatory: Set = new Set(); - constructor( - revision?: BundleRevision, - namespace?: string, - dirs: Record = {}, - attrs: Record = {}, - ) { + constructor(revision?: BundleRevision, namespace?: string, dirs: Record = {}, attrs: Record = {}) { this.revision = revision; this.namespace = namespace; this.dirs = dirs; @@ -71,10 +66,7 @@ export class BundleCapabilityImpl implements BundleCapability { if (!this.revision || !other.revision) { return false; } - return ( - evaluateSemver(this.revision.getVersion(), 'eq', other.revision.getVersion()) && - this.getNamespace() === other.getNamespace() - ); + return evaluateSemver(this.revision.getVersion(), 'eq', other.revision.getVersion()) && this.getNamespace() === other.getNamespace(); } getAttributes(): Record { @@ -117,11 +109,7 @@ export class BundleCapabilityImpl implements BundleCapability { return `${list.join('; ')}`; } - public static addIdentityCapability( - owner: BundleRevision, - headerMap: BundleConfigMap, - bundleCap: BundleCapability, - ): BundleCapability { + public static addIdentityCapability(owner: BundleRevision, headerMap: BundleConfigMap, bundleCap: BundleCapability): BundleCapability { const attrs: BundleConfigMap = { ...bundleCap.getAttributes() }; attrs[IDENTITY_NAMESPACE] = bundleCap.getAttributes()[BUNDLE_NAMESPACE]; diff --git a/packages/@pandino/pandino/src/lib/framework/wiring/bundle-requirement-impl.ts b/packages/@pandino/pandino/src/lib/framework/wiring/bundle-requirement-impl.ts index 034d4db..941eced 100644 --- a/packages/@pandino/pandino/src/lib/framework/wiring/bundle-requirement-impl.ts +++ b/packages/@pandino/pandino/src/lib/framework/wiring/bundle-requirement-impl.ts @@ -14,20 +14,13 @@ export class BundleRequirementImpl implements BundleRequirement { private readonly dirs: Record = {}; private readonly attrs: Record = {}; - constructor( - revision: BundleRevision, - namespace: string, - dirs: Record = {}, - attrs: Record = {}, - filter?: FilterNode, - ) { + constructor(revision: BundleRevision, namespace: string, dirs: Record = {}, attrs: Record = {}, filter?: FilterNode) { this.revision = revision; this.namespace = namespace; this.dirs = dirs; this.attrs = attrs; this.filter = filter ? serializeFilter(filter) : serializeFilter(convert(this.attrs)); - this.optional = - this.dirs.hasOwnProperty(RESOLUTION_DIRECTIVE) && this.dirs[RESOLUTION_DIRECTIVE] === RESOLUTION_OPTIONAL; + this.optional = this.dirs.hasOwnProperty(RESOLUTION_DIRECTIVE) && this.dirs[RESOLUTION_DIRECTIVE] === RESOLUTION_OPTIONAL; } getAttributes(): Record { diff --git a/packages/@pandino/pandino/src/lib/framework/wiring/bundle-wire-impl.test.ts b/packages/@pandino/pandino/src/lib/framework/wiring/bundle-wire-impl.test.ts index 6443c7f..2a4be7d 100644 --- a/packages/@pandino/pandino/src/lib/framework/wiring/bundle-wire-impl.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/wiring/bundle-wire-impl.test.ts @@ -63,9 +63,7 @@ describe('BundleWireImpl', () => { expect(wire.getProvider()).toEqual(provider); expect(wire.getRequirement()).toEqual(req); expect(wire.getRequirer()).toEqual(requirer); - expect(wire.toString()).toEqual( - '[@mock/symbolic-name: 0.0.0 (R 1)] ns.one; (power=SuperSaiyan) -> [@mock/symbolic-name: 0.0.0 (R 2)]', - ); + expect(wire.toString()).toEqual('[@mock/symbolic-name: 0.0.0 (R 1)] ns.one; (power=SuperSaiyan) -> [@mock/symbolic-name: 0.0.0 (R 2)]'); expect(wire.equals(wire)).toEqual(true); expect(wire.equals(undefined)).toEqual(false); expect(wire.equals(null)).toEqual(false); diff --git a/packages/@pandino/pandino/src/pandino.test.ts b/packages/@pandino/pandino/src/pandino.test.ts index 1d2616b..32c62a0 100644 --- a/packages/@pandino/pandino/src/pandino.test.ts +++ b/packages/@pandino/pandino/src/pandino.test.ts @@ -71,8 +71,7 @@ describe('Pandino', () => { [BUNDLE_NAME]: 'My Independent Bundle', }; const bundleRequiresCapability = 'pet.grooming;filter:="(&(type=cat)(rate<=20))"'; - const bundleProvidesCapability = - 'pet.grooming;type:Array="dog,cat";length:number=800;soap="organic";rate:number="10"'; + const bundleProvidesCapability = 'pet.grooming;type:Array="dog,cat";length:number=800;soap="organic";rate:number="10"'; let helloService: HelloService; let welcomeService: WelcomeService; @@ -577,15 +576,11 @@ describe('Pandino', () => { }); (bundle as BundleImpl).setState('STARTING'); - expect(bundle.uninstall()).rejects.toThrow( - 'Bundle @scope/bundle-1.2.3 cannot be uninstalled, since it is either STARTING or STOPPING.', - ); + expect(bundle.uninstall()).rejects.toThrow('Bundle @scope/bundle-1.2.3 cannot be uninstalled, since it is either STARTING or STOPPING.'); (bundle as BundleImpl).setState('STOPPING'); - expect(bundle.uninstall()).rejects.toThrow( - 'Bundle @scope/bundle-1.2.3 cannot be uninstalled, since it is either STARTING or STOPPING.', - ); + expect(bundle.uninstall()).rejects.toThrow('Bundle @scope/bundle-1.2.3 cannot be uninstalled, since it is either STARTING or STOPPING.'); }); it('stopping bundle unregisters all services', async () => { @@ -596,14 +591,8 @@ describe('Pandino', () => { const bundle = pandino.getBundleContext().getBundles()[0]; const context = bundle.getBundleContext(); - const regHello: ServiceRegistration = context.registerService( - '@pandino/pandino/hello-impl', - helloService, - ); - const regWelcome: ServiceRegistration = context.registerService( - '@pandino/pandino/welcome-impl', - welcomeService, - ); + const regHello: ServiceRegistration = context.registerService('@pandino/pandino/hello-impl', helloService); + const regWelcome: ServiceRegistration = context.registerService('@pandino/pandino/welcome-impl', welcomeService); const refHello: ServiceReference = regHello.getReference(); const refWelcome: ServiceReference = regWelcome.getReference(); @@ -628,14 +617,8 @@ describe('Pandino', () => { const bundle = pandino.getBundleContext().getBundles()[0]; const context = bundle.getBundleContext(); - const regHello: ServiceRegistration = context.registerService( - '@pandino/pandino/hello-impl', - helloService, - ); - const regWelcome: ServiceRegistration = context.registerService( - '@pandino/pandino/welcome-impl', - welcomeService, - ); + const regHello: ServiceRegistration = context.registerService('@pandino/pandino/hello-impl', helloService); + const regWelcome: ServiceRegistration = context.registerService('@pandino/pandino/welcome-impl', welcomeService); const refHello: ServiceReference = regHello.getReference(); const refWelcome: ServiceReference = regWelcome.getReference(); diff --git a/packages/@pandino/pandino/src/pandino.ts b/packages/@pandino/pandino/src/pandino.ts index 21182ae..07d2ae4 100644 --- a/packages/@pandino/pandino/src/pandino.ts +++ b/packages/@pandino/pandino/src/pandino.ts @@ -76,12 +76,8 @@ export class Pandino extends BundleImpl implements Framework { constructor(configMap: FrameworkConfigMap) { const deploymentRoot: string | undefined = configMap[DEPLOYMENT_ROOT_PROP]; const logger: Logger = configMap[LOG_LOGGER_PROP] ? configMap[LOG_LOGGER_PROP]! : new ConsoleLogger(); - const fetcher: ManifestFetcher = configMap[PANDINO_MANIFEST_FETCHER_PROP] - ? configMap[PANDINO_MANIFEST_FETCHER_PROP] - : new VoidFetcher(); - const importer: BundleImporter = configMap[PANDINO_BUNDLE_IMPORTER_PROP] - ? configMap[PANDINO_BUNDLE_IMPORTER_PROP] - : new VoidImporter(); + const fetcher: ManifestFetcher = configMap[PANDINO_MANIFEST_FETCHER_PROP] ? configMap[PANDINO_MANIFEST_FETCHER_PROP] : new VoidFetcher(); + const importer: BundleImporter = configMap[PANDINO_BUNDLE_IMPORTER_PROP] ? configMap[PANDINO_BUNDLE_IMPORTER_PROP] : new VoidImporter(); logger.setLogLevel(configMap[LOG_LEVEL_PROP] || LogLevel.LOG); if (!configMap[PANDINO_ACTIVATOR_RESOLVERS]) { @@ -199,9 +195,7 @@ export class Pandino extends BundleImpl implements Framework { } const resolvedHeaders: BundleManifestHeaders = - typeof locationOrHeaders === 'string' - ? await this.fetcher.fetch(locationOrHeaders, this.getDeploymentRoot()) - : locationOrHeaders; + typeof locationOrHeaders === 'string' ? await this.fetcher.fetch(locationOrHeaders, this.getDeploymentRoot()) : locationOrHeaders; let bundle: BundleImpl; let existing = this.isBundlePresent(resolvedHeaders); @@ -209,15 +203,7 @@ export class Pandino extends BundleImpl implements Framework { const id = this.getNextId(); // FIXME: this could cause issues for loading JS via explicit Header spec! const manifestLocation = typeof locationOrHeaders === 'string' ? locationOrHeaders : ''; - bundle = new BundleImpl( - this.logger, - id, - resolvedHeaders, - manifestLocation, - this.getDeploymentRoot(), - this, - origin, - ); + bundle = new BundleImpl(this.logger, id, resolvedHeaders, manifestLocation, this.getDeploymentRoot(), this, origin); this.bundles.push(bundle); this.fireBundleEvent('INSTALLED', bundle, origin); this.logger.info(`Installed Bundle: ${resolvedHeaders[BUNDLE_SYMBOLICNAME]}: ${resolvedHeaders[BUNDLE_VERSION]}`); @@ -237,9 +223,7 @@ export class Pandino extends BundleImpl implements Framework { async updateBundle(bundle: BundleImpl, headers: BundleManifestHeaders, origin?: Bundle): Promise { if (bundle.getState() === 'STARTING' || bundle.getState() === 'STOPPING') { - throw new Error( - 'Bundle ' + bundle.getUniqueIdentifier() + ' cannot be updated, since it is either STARTING or STOPPING.', - ); + throw new Error('Bundle ' + bundle.getUniqueIdentifier() + ' cannot be updated, since it is either STARTING or STOPPING.'); } let rethrow: Error | undefined; const oldState: BundleState = bundle.getState(); @@ -273,11 +257,7 @@ export class Pandino extends BundleImpl implements Framework { let rethrow: Error | undefined; const validStates: BundleState[] = ['INSTALLED']; if (!validStates.includes(bundle.getState())) { - throw new Error( - `Cannot start ${bundle.getUniqueIdentifier()}, because it\'s not in any of the valid states: ${validStates.join( - ', ', - )}.`, - ); + throw new Error(`Cannot start ${bundle.getUniqueIdentifier()}, because it\'s not in any of the valid states: ${validStates.join(', ')}.`); } bundle.setBundleContext(new BundleContextImpl(this.logger, bundle, this)); @@ -499,16 +479,12 @@ export class Pandino extends BundleImpl implements Framework { if (bundle.getState() === 'UNINSTALLED') { throw new Error('Cannot uninstall an uninstalled bundle.'); } else { - throw new Error( - `Bundle ${bundle.getUniqueIdentifier()} cannot be uninstalled because it is in an undesired state: ${bundle.getState()}`, - ); + throw new Error(`Bundle ${bundle.getUniqueIdentifier()} cannot be uninstalled because it is in an undesired state: ${bundle.getState()}`); } } if (bundle.getState() === 'STARTING' || bundle.getState() === 'STOPPING') { - throw new Error( - 'Bundle ' + bundle.getUniqueIdentifier() + ' cannot be uninstalled, since it is either STARTING or STOPPING.', - ); + throw new Error('Bundle ' + bundle.getUniqueIdentifier() + ' cannot be uninstalled, since it is either STARTING or STOPPING.'); } let errored = null; @@ -531,23 +507,13 @@ export class Pandino extends BundleImpl implements Framework { } } - getAllowedServiceReferences( - bundle: BundleImpl, - className?: string, - filter?: string, - checkAssignable = false, - ): ServiceReference[] { + getAllowedServiceReferences(bundle: BundleImpl, className?: string, filter?: string, checkAssignable = false): ServiceReference[] { const refs: ServiceReference[] = this.getServiceReferences(bundle, className, filter, checkAssignable); return refs ?? []; } - private getServiceReferences( - bundle: BundleImpl, - className?: string, - filter?: string, - checkAssignable = false, - ): ServiceReference[] { + private getServiceReferences(bundle: BundleImpl, className?: string, filter?: string, checkAssignable = false): ServiceReference[] { const refList = this.registry.getServiceReferences(className, filter) as unknown as ServiceReference[]; const effectiveRefList: ServiceReference[] = []; @@ -584,11 +550,7 @@ export class Pandino extends BundleImpl implements Framework { this.logger.debug(`Attempting to load Activator from: ${activatorDefinition}`); let activatorInstance: any; - const activatorModule = await this.importer.import( - activatorDefinition, - impl.getLocation(), - impl.getDeploymentRoot(), - ); + const activatorModule = await this.importer.import(activatorDefinition, impl.getLocation(), impl.getDeploymentRoot()); const bundleType: BundleType = impl.getHeaders()[BUNDLE_TYPE] || 'esm'; const activatorResolver: ActivatorResolver = this.configMap.get(PANDINO_ACTIVATOR_RESOLVERS)[bundleType]; @@ -645,12 +607,7 @@ export class Pandino extends BundleImpl implements Framework { return this.registry.ungetService(bundle, ref, srvObj); } - registerService( - context: BundleContextImpl, - identifier: string[] | string, - svcObj: S | ServiceFactory, - dict: Record, - ): ServiceRegistration { + registerService(context: BundleContextImpl, identifier: string[] | string, svcObj: S | ServiceFactory, dict: Record): ServiceRegistration { let reg = this.registry.registerService(context.getBundle()!, identifier, svcObj, dict); this.fireServiceEvent(new ServiceEventImpl('REGISTERED', reg.getReference()), {}); diff --git a/packages/@pandino/persistence-manager-localstorage/src/activator.ts b/packages/@pandino/persistence-manager-localstorage/src/activator.ts index 0c1f72e..e16ae04 100644 --- a/packages/@pandino/persistence-manager-localstorage/src/activator.ts +++ b/packages/@pandino/persistence-manager-localstorage/src/activator.ts @@ -1,11 +1,5 @@ import { FRAMEWORK_LOGGER } from '@pandino/pandino-api'; -import type { - BundleActivator, - BundleContext, - Logger, - ServiceReference, - ServiceRegistration, -} from '@pandino/pandino-api'; +import type { BundleActivator, BundleContext, Logger, ServiceReference, ServiceRegistration } from '@pandino/pandino-api'; import { INTERFACE_KEY, SERVICE_DISCRIMINATOR_PROPERTY } from '@pandino/persistence-manager-api'; import type { PersistenceManager } from '@pandino/persistence-manager-api'; import { LocalstoragePersistenceManager } from './service'; diff --git a/packages/@pandino/scr-api/LICENSE b/packages/@pandino/scr-api/LICENSE new file mode 100644 index 0000000..e48e096 --- /dev/null +++ b/packages/@pandino/scr-api/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. diff --git a/packages/@pandino/scr-api/README.md b/packages/@pandino/scr-api/README.md new file mode 100644 index 0000000..3b876a1 --- /dev/null +++ b/packages/@pandino/scr-api/README.md @@ -0,0 +1,19 @@ +# SCR API + +[![build-test](https://github.com/BlackBeltTechnology/pandino/actions/workflows/build-test.yml/badge.svg)](https://github.com/BlackBeltTechnology/pandino/actions/workflows/build-test.yml) +[![license](https://img.shields.io/badge/license-EPL%20v2.0-blue.svg)](https://github.com/BlackBeltTechnology/pandino) +[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) + +WIP + +## Sources + +- https://github.com/osgi/osgi/tree/main/org.osgi.service.component +- https://github.com/apache/felix-dev/tree/master/scr +- http://blog.amitinside.com/OSGi-Annotations/ +- https://github.com/rbuckton/reflect-metadata#api +- https://fireship.io/lessons/ts-decorators-by-example/ + +## License + +Eclipse Public License - v 2.0 diff --git a/packages/@pandino/scr-api/package.json b/packages/@pandino/scr-api/package.json new file mode 100644 index 0000000..e2599d2 --- /dev/null +++ b/packages/@pandino/scr-api/package.json @@ -0,0 +1,56 @@ +{ + "name": "@pandino/scr-api", + "version": "0.8.31", + "description": "Pandino Service Component Runtime API", + "main": "dist/@pandino/scr-api.cjs", + "module": "dist/@pandino/scr-api.mjs", + "types": "types/index.d.ts", + "type": "module", + "exports": { + ".": { + "require": "./dist/@pandino/scr-api.cjs", + "import": "./dist/@pandino/scr-api.mjs" + } + }, + "scripts": { + "build": "rimraf dist && tsc && vite build", + "test": "vitest run", + "test:dev": "vitest", + "tsc": "tsc" + }, + "keywords": [ + "pandino", + "api", + "scr", + "service-component-runtime", + "declarative-services" + ], + "author": "Norbert Herczeg ", + "license": "EPL-2.0", + "homepage": "https://github.com/BlackBeltTechnology/pandino", + "repository": { + "type": "git", + "url": "https://github.com/BlackBeltTechnology/pandino.git", + "directory": "packages/@pandino/scr-api" + }, + "bugs": { + "url": "https://github.com/BlackBeltTechnology/pandino/issues" + }, + "engines": { + "node": ">=20" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], + "devDependencies": { + "@pandino/pandino-api": "workspace:^", + "reflect-metadata": "^0.2.2", + "rimraf": "^5.0.10", + "typescript": "^5.6.3", + "vite": "^5.4.10", + "vitest": "^2.1.3" + } +} diff --git a/packages/@pandino/scr-api/src/DecoratedQueue.ts b/packages/@pandino/scr-api/src/DecoratedQueue.ts new file mode 100644 index 0000000..bb8e83a --- /dev/null +++ b/packages/@pandino/scr-api/src/DecoratedQueue.ts @@ -0,0 +1,46 @@ +import type { BundleContext } from '@pandino/pandino-api'; +import type { ComponentRegistrar } from './interfaces'; + +export interface DecoratedQueue { + add(target: any): void; + init(context: BundleContext, registrar: ComponentRegistrar): void; + isInitialized(): boolean; +} + +export class DecoratedQueueImpl implements DecoratedQueue { + private decorated: Map = new Map(); + private registrar?: ComponentRegistrar; + private context?: BundleContext; + + add(target: any): void { + if (!this.decorated.has(target)) { + this.decorated.set(target, false); + } + this.processPending(); + } + + init(context: BundleContext, registrar: ComponentRegistrar): void { + this.registrar = registrar; + this.context = context; + this.processPending(); + } + + isInitialized(): boolean { + return !!this.registrar && !!this.context; + } + + private processPending(): void { + if (this.isInitialized()) { + const processedTargets: any[] = []; + for (const [target, processed] of this.decorated) { + if (!processed) { + this.registrar!.registerComponent(target, this.context!); + } + processedTargets.push(target); + } + for (const t of processedTargets) { + this.decorated.delete(t); + } + } + } +} diff --git a/packages/@pandino/scr-api/src/__fixtures__/hostAndGuest.ts b/packages/@pandino/scr-api/src/__fixtures__/hostAndGuest.ts new file mode 100644 index 0000000..235a566 --- /dev/null +++ b/packages/@pandino/scr-api/src/__fixtures__/hostAndGuest.ts @@ -0,0 +1,42 @@ +import { Activate, Component, Deactivate, Modified, Reference } from '../decorators'; +import { SERVICE_RANKING } from '@pandino/pandino-api'; + +export const DOES_STUFF_INTERFACE_KEY = '@test/DoesStuff'; + +export interface DoesStuff { + doesStuff(): boolean; +} + +@Component({ name: '@test/Guest', service: DOES_STUFF_INTERFACE_KEY }) +export class Guest implements DoesStuff { + doesStuff(): boolean { + return false; + } +} + +@Component({ name: '@test/Host', property: { [SERVICE_RANKING]: 10 } }) +export class Host { + @Reference({ service: DOES_STUFF_INTERFACE_KEY, cardinality: 'MANDATORY' }) + private guest?: DoesStuff; + + private noop = 'noop'; + + @Activate() + onActivate() { + console.log('Activating Host'); + } + + @Deactivate() + onDeactivate() { + console.log('Deactivating Host'); + } + + @Modified() + onModified() { + console.log('Modified!'); + } + + test(): boolean { + return this.guest!.doesStuff(); + } +} diff --git a/packages/@pandino/scr-api/src/constants.ts b/packages/@pandino/scr-api/src/constants.ts new file mode 100644 index 0000000..27a15e2 --- /dev/null +++ b/packages/@pandino/scr-api/src/constants.ts @@ -0,0 +1,30 @@ +export const COMPONENT_CAPABILITY_NAME = 'pandino.component'; +export const COMPONENT_ID = 'component.id'; +export const COMPONENT_NAME = 'component.name'; + +export type DeactivationReason = 'BUNDLE_STOPPED' | 'CONFIGURATION_DELETED' | 'CONFIGURATION_MODIFIED' | 'DISABLED' | 'DISPOSED' | 'REFERENCE' | 'UNSPECIFIED'; + +export const SERVICE_COMPONENT = 'Service-Component'; + +export const $$PANDINO_META = Symbol.for('$$PANDINO_SCR_META'); +export const COMPONENT_REGISTRAR_INTERFACE_KEY = '@pandino/scr-api/ComponentRegistrar'; + +export const COMPONENT_KEY_NAME = 'pandino:scr:Component.name'; +export const COMPONENT_KEY_SERVICE = 'pandino:scr:Component.service'; +export const COMPONENT_KEY_PROPERTY = 'pandino:scr:Component.property'; +export const COMPONENT_KEY_CONFIGURATION_PID = 'pandino:scr:Component.configurationPid'; +export const COMPONENT_KEY_CONFIGURATION_POLICY = 'pandino:scr:Component.configurationPolicy'; + +export const COMPONENT_ACTIVATE_KEY_METHOD = 'pandino:scr:Component.Activate'; +export const COMPONENT_DEACTIVATE_KEY_METHOD = 'pandino:scr:Component.Deactivate'; +export const COMPONENT_MODIFIED_KEY_METHOD = 'pandino:scr:Component.Modified'; + +export const REFERENCE_KEY_SERVICE = 'pandino:scr:Reference.service'; +export const REFERENCE_KEY_TARGET = 'pandino:scr:Reference.target'; +export const REFERENCE_KEY_CARDINALITY = 'pandino:scr:Reference.cardinality'; +export const REFERENCE_KEY_POLICY = 'pandino:scr:Reference.policy'; +export const REFERENCE_KEY_POLICY_OPTION = 'pandino:scr:Reference.policyOption'; +export const REFERENCE_KEY_SCOPE = 'pandino:scr:Reference.scope'; +export const REFERENCE_KEY_BIND = 'pandino:scr:Reference.bind'; +export const REFERENCE_KEY_UPDATED = 'pandino:scr:Reference.updated'; +export const REFERENCE_KEY_UNBIND = 'pandino:scr:Reference.unbind'; diff --git a/packages/@pandino/scr-api/src/decorators.test.ts b/packages/@pandino/scr-api/src/decorators.test.ts new file mode 100644 index 0000000..fca1d16 --- /dev/null +++ b/packages/@pandino/scr-api/src/decorators.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from 'vitest'; +import { SERVICE_RANKING } from '@pandino/pandino-api'; +import { DOES_STUFF_INTERFACE_KEY, Host } from './__fixtures__/hostAndGuest'; +import { + $$PANDINO_META, + COMPONENT_ACTIVATE_KEY_METHOD, + COMPONENT_DEACTIVATE_KEY_METHOD, + COMPONENT_KEY_CONFIGURATION_PID, + COMPONENT_KEY_CONFIGURATION_POLICY, + COMPONENT_KEY_NAME, + COMPONENT_KEY_PROPERTY, + COMPONENT_KEY_SERVICE, + COMPONENT_MODIFIED_KEY_METHOD, + REFERENCE_KEY_CARDINALITY, + REFERENCE_KEY_POLICY, + REFERENCE_KEY_POLICY_OPTION, + REFERENCE_KEY_SCOPE, + REFERENCE_KEY_SERVICE, +} from './constants'; +import type { InternalMetaData, InternalReferenceMetaData } from './internal-interfaces'; + +describe('Decorators', () => { + it('@Component', () => { + const classMetaData: InternalMetaData = Host.prototype[$$PANDINO_META]; + + expect(classMetaData[COMPONENT_KEY_NAME]).toEqual('@test/Host'); + expect(classMetaData[COMPONENT_KEY_SERVICE]).toEqual('@test/Host'); + expect(classMetaData[COMPONENT_KEY_CONFIGURATION_PID]).toEqual('@test/Host'); + expect(classMetaData[COMPONENT_KEY_CONFIGURATION_POLICY]).toEqual('OPTIONAL'); + expect(classMetaData[COMPONENT_KEY_PROPERTY]).toEqual({ [SERVICE_RANKING]: 10 }); + }); + + it('@Activate', () => { + const classMetaData: InternalMetaData = Host.prototype[$$PANDINO_META]; + expect(classMetaData).toBeDefined(); + expect(classMetaData[COMPONENT_ACTIVATE_KEY_METHOD]).toEqual({ method: 'onActivate' }); + }); + + it('@Deactivate', () => { + const classMetaData: InternalMetaData = Host.prototype[$$PANDINO_META]; + expect(classMetaData).toBeDefined(); + expect(classMetaData[COMPONENT_DEACTIVATE_KEY_METHOD]).toEqual({ method: 'onDeactivate' }); + }); + + it('@Modified', () => { + const classMetaData: InternalMetaData = Host.prototype[$$PANDINO_META]; + expect(classMetaData).toBeDefined(); + expect(classMetaData[COMPONENT_MODIFIED_KEY_METHOD]).toEqual({ method: 'onModified' }); + }); + + it('@Reference', () => { + const classMetaData: InternalMetaData = Host.prototype[$$PANDINO_META]; + expect(classMetaData).toBeDefined(); + + const guestMetaData: InternalReferenceMetaData = classMetaData.references['guest']; + expect(guestMetaData).toBeDefined(); + expect(guestMetaData[REFERENCE_KEY_SERVICE]).toEqual(DOES_STUFF_INTERFACE_KEY); + expect(guestMetaData[REFERENCE_KEY_CARDINALITY]).toEqual('MANDATORY'); + expect(guestMetaData[REFERENCE_KEY_POLICY]).toEqual('STATIC'); + expect(guestMetaData[REFERENCE_KEY_POLICY_OPTION]).toEqual('GREEDY'); + expect(guestMetaData[REFERENCE_KEY_SCOPE]).toEqual('SINGLETON'); + }); + + it('instance info', () => { + const host = new Host(); + expect(host).toBeDefined(); + expect(host[$$PANDINO_META]).toBeDefined(); + expect((host[$$PANDINO_META] as InternalMetaData)[COMPONENT_KEY_NAME]).toEqual('@test/Host'); + }); + + it('instance info for symbol', () => { + const host = new Host(); + expect(host).toBeDefined(); + expect(host[Symbol.for('$$PANDINO_SCR_META')]).toBeDefined(); + }); +}); diff --git a/packages/@pandino/scr-api/src/decorators.ts b/packages/@pandino/scr-api/src/decorators.ts new file mode 100644 index 0000000..5d5e41b --- /dev/null +++ b/packages/@pandino/scr-api/src/decorators.ts @@ -0,0 +1,177 @@ +import type { ComponentProps, ReferenceProps } from './interfaces'; +import { + $$PANDINO_META, + COMPONENT_ACTIVATE_KEY_METHOD, + COMPONENT_DEACTIVATE_KEY_METHOD, + COMPONENT_KEY_CONFIGURATION_PID, + COMPONENT_KEY_CONFIGURATION_POLICY, + COMPONENT_KEY_NAME, + COMPONENT_KEY_PROPERTY, + COMPONENT_KEY_SERVICE, + COMPONENT_MODIFIED_KEY_METHOD, + REFERENCE_KEY_BIND, + REFERENCE_KEY_CARDINALITY, + REFERENCE_KEY_POLICY, + REFERENCE_KEY_POLICY_OPTION, + REFERENCE_KEY_SCOPE, + REFERENCE_KEY_SERVICE, + REFERENCE_KEY_TARGET, + REFERENCE_KEY_UNBIND, + REFERENCE_KEY_UPDATED, +} from './constants'; +import type { InternalMetaData, InternalReferenceMetaData } from './internal-interfaces'; +import { decoratedQueue } from './state'; + +export function Component(props: ComponentProps) { + return function any>(target: T): T { + const originalConstructor = target; + + function modifiedConstructor(...args: any[]) { + const instance = new originalConstructor(...args); + + // do stuff here + + return instance; + } + + modifiedConstructor.prototype = originalConstructor.prototype; + + let internalMeta = getOrInitInternalMetaData(modifiedConstructor.prototype); + + internalMeta[COMPONENT_KEY_NAME] = props.name; + internalMeta[COMPONENT_KEY_SERVICE] = + typeof props.service === 'string' || (Array.isArray(props.service) && props.service.length > 0) ? props.service : props.name; + internalMeta[COMPONENT_KEY_CONFIGURATION_PID] = props.configurationPid ?? internalMeta[COMPONENT_KEY_SERVICE]; + internalMeta[COMPONENT_KEY_CONFIGURATION_POLICY] = props.configurationPolicy ?? 'OPTIONAL'; + + if (props.property) { + internalMeta[COMPONENT_KEY_PROPERTY] = props.property; + } + + decoratedQueue.add(modifiedConstructor); + + return modifiedConstructor as unknown as T; + }; +} + +/** + * https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-field.injection + * https://osgi.github.io/osgi/cmpn/service.component.html#service.component-reference.policy + * + * @param {ReferenceProps} props + * @constructor + */ +export function Reference(props: ReferenceProps) { + return function (target: any, key: string | symbol) { + const internalMeta = getOrInitInternalMetaData(target); + + const referenceMetaData: InternalReferenceMetaData = { + [REFERENCE_KEY_SERVICE]: props.service, + [REFERENCE_KEY_CARDINALITY]: props.cardinality ? props.cardinality : 'MANDATORY', + [REFERENCE_KEY_POLICY]: props.policy ? props.policy : 'STATIC', + [REFERENCE_KEY_POLICY_OPTION]: props.policyOption ? props.policyOption : 'GREEDY', + [REFERENCE_KEY_SCOPE]: props.scope ? props.scope : 'SINGLETON', + }; + + if (props.target) { + referenceMetaData[REFERENCE_KEY_TARGET] = props.target; + } + + if (props.bind) { + referenceMetaData[REFERENCE_KEY_BIND] = props.bind; + } + + if (props.updated) { + referenceMetaData[REFERENCE_KEY_UPDATED] = props.updated; + } + + if (props.unbind) { + referenceMetaData[REFERENCE_KEY_UNBIND] = props.unbind; + } + + internalMeta.references[key] = referenceMetaData; + }; +} + +/** + * https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-activation + * + * @constructor + */ +export function Activate() { + return function (target: Object, key: string | symbol, descriptor: PropertyDescriptor) { + const internalMeta = getOrInitInternalMetaData(target); + const original = descriptor.value; + + descriptor.value = function (...args: any[]) { + return original.apply(this, args); + }; + + internalMeta[COMPONENT_ACTIVATE_KEY_METHOD] = { + method: key, + }; + + return descriptor; + }; +} + +/** + * https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-deactivation + * + * @constructor + */ +export function Deactivate() { + return function (target: Object, key: string | symbol, descriptor: PropertyDescriptor) { + const internalMeta = getOrInitInternalMetaData(target); + const original = descriptor.value; + + descriptor.value = function (...args: any[]) { + return original.apply(this, args); + }; + + internalMeta[COMPONENT_DEACTIVATE_KEY_METHOD] = { + method: key, + }; + + return descriptor; + }; +} + +/** + * https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-component.annotations + * https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-modification + * https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#org.osgi.service.component.annotations.Modified + * + * @constructor + */ +export function Modified() { + return function (target: Object, key: string | symbol, descriptor: PropertyDescriptor) { + const internalMeta = getOrInitInternalMetaData(target); + const original = descriptor.value; + + descriptor.value = function (...args: any[]) { + return original.apply(this, args); + }; + + internalMeta[COMPONENT_MODIFIED_KEY_METHOD] = { + method: key, + }; + + return descriptor; + }; +} + +function getOrInitInternalMetaData(target: any): InternalMetaData { + let internalMeta: InternalMetaData; + + if (!target[$$PANDINO_META]) { + internalMeta = { + references: {}, + } as unknown as InternalMetaData; + target[$$PANDINO_META] = internalMeta; + } else { + internalMeta = target[$$PANDINO_META]; + } + + return internalMeta; +} diff --git a/packages/@pandino/scr-api/src/index.ts b/packages/@pandino/scr-api/src/index.ts new file mode 100644 index 0000000..7142454 --- /dev/null +++ b/packages/@pandino/scr-api/src/index.ts @@ -0,0 +1,5 @@ +export * from './constants'; +export * from './decorators'; +export * from './interfaces'; +export * from './internal-interfaces'; +export { registerDecoratorHandler } from './state'; diff --git a/packages/@pandino/scr-api/src/interfaces.ts b/packages/@pandino/scr-api/src/interfaces.ts new file mode 100644 index 0000000..4bd9c7c --- /dev/null +++ b/packages/@pandino/scr-api/src/interfaces.ts @@ -0,0 +1,132 @@ +import type { BundleContext, ServiceProperties, ServiceReference } from '@pandino/pandino-api'; + +export type ConfigurationPolicy = 'IGNORE' | 'OPTIONAL' | 'REQUIRE'; +export type ReferenceCardinality = 'MANDATORY' | 'OPTIONAL' | 'MULTIPLE'; + +/** + * The static policy is the most simple policy and is the default policy. + * + * A component instance never sees any of the dynamics. Component configurations are deactivated before any bound + * service for a reference having a static policy becomes unavailable. If a target service is available to replace the + * bound service which became unavailable, the component configuration must be reactivated and bound to the replacement + * service. + * + * The dynamic policy is slightly more complex since the component implementation must properly handle changes in the + * set of bound services. With the dynamic policy, SCR can change the set of bound services without deactivating a + * component configuration. If the component uses method injection to access services, then the component instance will + * be notified of changes in the set of bound services by calls to the bind and unbind methods. + */ +export type ReferencePolicy = 'STATIC' | 'DYNAMIC'; + +/** + * The reluctant policy option is the default policy option for both static and dynamic reference policies. + * + * When a new target service for a reference becomes available, references having the reluctant policy option for the + * static policy or the dynamic policy with a unary cardinality will ignore the new target service. + * + * The greedy policy option is a valid policy option for both static and dynamic reference policies. + * + * When a new target service for a reference becomes available, references having the greedy policy option will bind the + * new target service. + */ +export type ReferencePolicyOption = 'RELUCTANT' | 'GREEDY'; +export type ReferenceScope = 'SINGLETON' | 'BUNDLE' | 'PROTOTYPE'; +export type ComponentConfigurationState = 'UNSATISFIED_CONFIGURATION' | 'UNSATISFIED_REFERENCE' | 'SATISFIED' | 'ACTIVE' | 'FAILED_ACTIVATION'; + +export interface ComponentConfiguration { + getId(): number; + getPID(): string | string[]; + getConfigurationPolicy(): ConfigurationPolicy; + getProperties(): ServiceProperties; + getSatisfiedReferences(): SatisfiedReference[]; + getUnsatisfiedReferences(): UnsatisfiedReference[]; + getService(): ServiceReference | undefined; + getState(): ComponentConfigurationState; +} + +export interface SatisfiedReference { + getName(): string; + getTarget(): string | undefined; + getBoundServices(): ServiceReference[]; +} + +export interface UnsatisfiedReference { + getName(): string; + getTarget(): string | undefined; +} + +export interface ComponentInstance { + /** + * Returns the component instance of the activated component configuration. + */ + getInstance(): S; +} + +/** + * The Component Context can be made available to a component instance during activation, modification, and + * deactivation. It provides the interface to the execution context of the component, much like the Bundle Context + * provides a bundle the interface to the Framework. A Component Context should therefore be regarded as a capability + * and not shared with other components or bundles. + * + * Each distinct component instance receives a unique Component Context. Component Contexts are not reused and must be + * discarded when the component configuration is deactivated. + */ +export interface ComponentContext { + /** + * Returns the BundleContext of the bundle which declares this component. + * + * @return BundleContext + */ + getBundleContext(): BundleContext; + + /** + * Returns the Component Instance object for the component instance associated with this Component Context. + */ + getComponentInstance(): ComponentInstance; + + /** + * Returns the component properties for this Component Context. + * + * @return ServiceProperties + */ + getProperties(): ServiceProperties; + + /** + * If the component instance is registered as a service using the service element, then this method returns the + * service reference of the service provided by this component instance. + * + * @return ServiceReference + */ + getServiceReference(): ServiceReference | undefined; +} + +export interface ComponentProps { + name: string; // Should be a fully qualified type name for the component + service?: string | string[]; // Used as objectClass, typically the interface(es) which the class implements + property?: ServiceProperties; + configurationPid?: string | string[]; // If no value is specified, the name of this Component is used as the configuration PID of this Component. + + /** + * https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-configuration.changes + */ + configurationPolicy?: ConfigurationPolicy; // Default: OPTIONAL +} + +/** + * https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#org.osgi.service.component.annotations.Reference + */ +export interface ReferenceProps { + service: string; // the objectClass of the type on which this decorator is defined on + target?: string; // filter + cardinality?: ReferenceCardinality; // default: MANDATORY + policy?: ReferencePolicy; // default: STATIC + policyOption?: ReferencePolicyOption; // default: GREEDY + scope?: ReferenceScope; // default: SINGLETON + bind?: string; + updated?: string; + unbind?: string; +} + +export interface ComponentRegistrar { + registerComponent(target: any, bundleContext: BundleContext): void; +} diff --git a/packages/@pandino/scr-api/src/internal-interfaces.ts b/packages/@pandino/scr-api/src/internal-interfaces.ts new file mode 100644 index 0000000..7c030cb --- /dev/null +++ b/packages/@pandino/scr-api/src/internal-interfaces.ts @@ -0,0 +1,57 @@ +import type { ServiceProperties } from '@pandino/pandino-api'; +import { + COMPONENT_ACTIVATE_KEY_METHOD, + COMPONENT_DEACTIVATE_KEY_METHOD, + COMPONENT_KEY_CONFIGURATION_PID, + COMPONENT_KEY_CONFIGURATION_POLICY, + COMPONENT_KEY_NAME, + COMPONENT_KEY_PROPERTY, + COMPONENT_KEY_SERVICE, + COMPONENT_MODIFIED_KEY_METHOD, + REFERENCE_KEY_BIND, + REFERENCE_KEY_CARDINALITY, + REFERENCE_KEY_POLICY, + REFERENCE_KEY_POLICY_OPTION, + REFERENCE_KEY_SCOPE, + REFERENCE_KEY_SERVICE, + REFERENCE_KEY_TARGET, + REFERENCE_KEY_UNBIND, + REFERENCE_KEY_UPDATED, +} from './constants'; +import type { ConfigurationPolicy, ReferenceCardinality, ReferencePolicy, ReferencePolicyOption, ReferenceScope } from './interfaces'; + +export interface InternalMetaData { + [COMPONENT_KEY_NAME]: string; + [COMPONENT_KEY_SERVICE]: string | string[]; + [COMPONENT_KEY_CONFIGURATION_PID]: string | string[]; + [COMPONENT_KEY_PROPERTY]: ServiceProperties; + [COMPONENT_KEY_CONFIGURATION_POLICY]: ConfigurationPolicy; + references: Record; + [COMPONENT_ACTIVATE_KEY_METHOD]?: InternalActivatorMetaData; + [COMPONENT_DEACTIVATE_KEY_METHOD]?: InternalDeActivatorMetaData; + [COMPONENT_MODIFIED_KEY_METHOD]?: InternalModifiedMetaData; +} + +export interface InternalReferenceMetaData { + [REFERENCE_KEY_SERVICE]: string; + [REFERENCE_KEY_TARGET]?: string; + [REFERENCE_KEY_CARDINALITY]: ReferenceCardinality; + [REFERENCE_KEY_POLICY]: ReferencePolicy; + [REFERENCE_KEY_POLICY_OPTION]: ReferencePolicyOption; + [REFERENCE_KEY_SCOPE]: ReferenceScope; + [REFERENCE_KEY_BIND]?: string; + [REFERENCE_KEY_UPDATED]?: string; + [REFERENCE_KEY_UNBIND]?: string; +} + +export interface InternalActivatorMetaData { + method: string | symbol; +} + +export interface InternalDeActivatorMetaData { + method: string | symbol; +} + +export interface InternalModifiedMetaData { + method: string | symbol; +} diff --git a/packages/@pandino/scr-api/src/state.test.ts b/packages/@pandino/scr-api/src/state.test.ts new file mode 100644 index 0000000..a810d91 --- /dev/null +++ b/packages/@pandino/scr-api/src/state.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { BundleContext, ServiceReference } from '@pandino/pandino-api'; +import { registerDecoratorHandler } from './state'; +import { COMPONENT_REGISTRAR_INTERFACE_KEY } from './constants'; +import type { ComponentRegistrar } from './interfaces'; +import { Guest, Host } from './__fixtures__/hostAndGuest'; + +describe('Decorator registration', () => { + const mockGetServiceReference = vi.fn().mockImplementation((identifier: string): ServiceReference | undefined => { + if (identifier === COMPONENT_REGISTRAR_INTERFACE_KEY) { + return mockRegistrarServiceReference; + } + return undefined; + }); + const mockGetService = vi.fn().mockImplementation((reference: ServiceReference): any | undefined => { + if (mockRegistrarServiceReference && reference === mockRegistrarServiceReference) { + return mockRegistrar; + } + return undefined; + }); + const mockUngetService = vi.fn().mockImplementation((reference: ServiceReference): boolean => { + return true; + }); + const mockRegisterComponent = vi.fn().mockImplementation((target: any, bundleContext: BundleContext) => { + return undefined; + }); + + const mockRegistrar: ComponentRegistrar = { + registerComponent: mockRegisterComponent, + }; + const mockRegistrarServiceReference: ServiceReference = {} as unknown as ServiceReference; + + describe('lifecycle', () => { + let deregisterCallback: () => void; + let mockContext: Partial; + + it('registration is not called until the handler is initialized', () => { + expect(mockRegisterComponent).toHaveBeenCalledTimes(0); + }); + + it('handler initialization', () => { + mockContext = { + getServiceReference: mockGetServiceReference, + getService: mockGetService, + ungetService: mockUngetService, + }; + deregisterCallback = registerDecoratorHandler(mockContext as BundleContext); + + // expect(() => { + // registerDecoratorHandler(mockContext as BundleContext); + // }).toThrow('Decorator handler already initialized!'); + + expect(mockGetServiceReference).toHaveBeenCalledTimes(1); + expect(mockGetServiceReference).toHaveBeenCalledWith(COMPONENT_REGISTRAR_INTERFACE_KEY); + expect(mockGetService).toHaveBeenCalledTimes(1); + expect(mockGetService).toHaveBeenCalledWith(mockRegistrarServiceReference); + }); + + it('components are registered', () => { + expect(mockRegisterComponent).toHaveBeenCalledTimes(2); + expect(mockRegisterComponent).toHaveBeenCalledWith(Guest, mockContext); + expect(mockRegisterComponent).toHaveBeenCalledWith(Host, mockContext); + }); + + it('clean up references emulating bundle de-activation', () => { + expect(typeof deregisterCallback).toEqual('function'); + + deregisterCallback(); + + expect(mockUngetService).toHaveBeenCalledTimes(1); + expect(mockUngetService).toHaveBeenCalledWith(mockRegistrarServiceReference); + }); + }); +}); diff --git a/packages/@pandino/scr-api/src/state.ts b/packages/@pandino/scr-api/src/state.ts new file mode 100644 index 0000000..fef6a88 --- /dev/null +++ b/packages/@pandino/scr-api/src/state.ts @@ -0,0 +1,27 @@ +import type { BundleContext } from '@pandino/pandino-api'; +import { COMPONENT_REGISTRAR_INTERFACE_KEY } from './constants'; +import type { ComponentRegistrar } from './interfaces'; +import type { DecoratedQueue } from './DecoratedQueue'; +import { DecoratedQueueImpl } from './DecoratedQueue'; + +export const decoratedQueue: DecoratedQueue = new DecoratedQueueImpl(); + +export function registerDecoratorHandler(bundleContext: BundleContext): () => void { + const registrarReference = bundleContext.getServiceReference(COMPONENT_REGISTRAR_INTERFACE_KEY); + let registrar: ComponentRegistrar | undefined; + if (registrarReference) { + registrar = bundleContext.getService(registrarReference); + if (registrar) { + decoratedQueue.init(bundleContext, registrar); + } + } + return () => { + if (registrarReference) { + try { + bundleContext.ungetService(registrarReference); + } catch (e) { + console.error(`An error occurred during the cleanup of registerDecoratorHandler(): ${e}`); + } + } + }; +} diff --git a/packages/@pandino/scr-api/tsconfig.json b/packages/@pandino/scr-api/tsconfig.json new file mode 100644 index 0000000..1e044aa --- /dev/null +++ b/packages/@pandino/scr-api/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "rootDir": "./src", + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true, + "types": ["vite/client", "node"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "declaration": true, + "declarationDir": "types", + "emitDeclarationOnly": true + }, + "include": ["src"], + "exclude": ["**/*.test.ts", "node_modules", "test/**", ".history/**"] +} diff --git a/packages/@pandino/scr-api/vite.config.ts b/packages/@pandino/scr-api/vite.config.ts new file mode 100644 index 0000000..286436f --- /dev/null +++ b/packages/@pandino/scr-api/vite.config.ts @@ -0,0 +1,36 @@ +import { resolve } from "node:path"; +import { defineConfig } from "vite"; +// @ts-ignore +import packageJson from "./package.json"; + +const getPackageName = () => { + return packageJson.name; +}; + +const getPackageNameCamelCase = () => { + try { + return getPackageName().replace(/@/g, '').replace(/[\/\-]/g, '_').toUpperCase(); + } catch (err) { + throw new Error("Name property in package.json is missing."); + } +}; + +const fileName = { + es: `${getPackageName()}.mjs`, + cjs: `${getPackageName()}.cjs`, + umd: `${getPackageName()}.umd.js`, +}; + +const formats = Object.keys(fileName) as Array; + +export default defineConfig(({ mode }) => ({ + base: "./", + build: { + lib: { + entry: resolve(__dirname, "src/index.ts"), + name: getPackageNameCamelCase(), + formats, + fileName: (format) => fileName[format], + }, + }, +})); diff --git a/packages/@pandino/scr/LICENSE b/packages/@pandino/scr/LICENSE new file mode 100644 index 0000000..e48e096 --- /dev/null +++ b/packages/@pandino/scr/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. diff --git a/packages/@pandino/scr/README.md b/packages/@pandino/scr/README.md new file mode 100644 index 0000000..161773e --- /dev/null +++ b/packages/@pandino/scr/README.md @@ -0,0 +1,269 @@ +# scr + +[![build-test](https://github.com/BlackBeltTechnology/pandino/actions/workflows/build-test.yml/badge.svg)](https://github.com/BlackBeltTechnology/pandino/actions/workflows/build-test.yml) +[![license](https://img.shields.io/badge/license-EPL%20v2.0-blue.svg)](https://github.com/BlackBeltTechnology/pandino) +[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) + +WIP + +## Sources + +- https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-service.component.runtime + +## Notes + +https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-components + +**112.2 Components** + +Component configurations are activated and deactivated under the full control of SCR. + +SCR must activate a component configuration when the component is enabled and the component configuration is satisfied +and a component configuration is needed. During the life time of a component configuration, SCR can notify the component +of changes in its bound references. + +SCR will deactivate a previously activated component configuration when the component becomes disabled, the component +configuration becomes unsatisfied, or the component configuration is no longer needed. + +If an activated component configuration's configuration properties change, SCR must either notify the component +configuration of the change, if the component description specifies a method to be notified of such changes, or +deactivate the component configuration and then attempt to reactivate the component configuration using the new +configuration information. + +**112.2.2 Immediate Component** + +An immediate component is activated as soon as its dependencies are satisfied. + +If an immediate component has no dependencies, it is activated immediately. + +A component is an immediate component if it is not a factory component and either does not specify a service or +specifies a service and the immediate attribute of the component element set to true. If an immediate component +configuration is satisfied and specifies a service, SCR must register the component configuration as a service in the +service registry and then activate the component configuration. + +**112.3 References to Services** + +The services that are selected by a reference are called the target services. These are the services selected by the +`BundleContext.getServiceReferences` method where + +- the first argument is the reference's interface and +- the second argument is the reference's target property, which must be a valid filter. + +A component configuration becomes satisfied when each specified reference is satisfied. + +A reference is satisfied if it specifies optional cardinality or when the number of target services is equal to or more +than the minimum cardinality of the reference. + +An activated component configuration that becomes unsatisfied must be deactivated. + +**112.3.10 Selecting Target Services** + +The target services for a reference are constrained by the reference's interface name and target property. + +By specifying a filter in the target property, the programmer and deployer can constrain the set of services that should +be part of the target services. + +**112.3.11 Circular References** + +SCR must ensure that a component instance is never accessible to another component instance or as a service until it has +been fully activated, that is it has returned from its activate method if it has one. + +Circular references must be detected by SCR when it attempts to satisfy component configurations and SCR must fail to +satisfy the references involved in the cycle and log an error message with the Log Service, if present. + +However, if one of the references in the cycle has optional cardinality SCR must break the cycle. + +The reference with the optional cardinality can be satisfied and bound to zero target services. Therefore the cycle is +broken and the other references may be satisfied. + +**112.5.6 Activation** + +Activating a component configuration consists of the following steps: + +- Load the component implementation class. +- Compute the bound services. See Bound Services. +- Create the component context. See Component Context. +- Construct the component instance. See Constructor Injection. +- Set the activation fields, if any. See Activation Objects. +- Bind the bound services. See Binding Services. +- Call the activate method, if any. See Activate Method. Calling the activate method signals the completion of activating the component instance. + +Component instances must never be reused. + +Each time a component configuration is activated, SCR must create a new component instance to use with the activated +component configuration. + +A component instance must complete activation before it can be deactivated. + +Once the component configuration is deactivated or fails to activate due to an exception, SCR must unbind all the +component's bound services and discard all references to the component instance associated with the activation. + +**112.5.8 Component Context** + +The Component Context can be made available to a component instance during activation, modification, and deactivation. + +It provides the interface to the execution context of the component, much like the Bundle Context provides a bundle the +interface to the Framework. A Component Context should therefore be regarded as a capability and not shared with other +components or bundles. + +Each distinct component instance receives a unique Component Context. + +Component Contexts are not reused and must be discarded when the component configuration is deactivated. + +**112.5.12 Bound Service Replacement** + +If an active component configuration has a dynamic reference with unary cardinality and the bound service is modified or +unregistered and ceases to be a target service, or the policy-option is greedy and a better target service becomes +available then SCR must attempt to replace the bound service with a new bound service. + +If the dynamic reference falls below the minimum cardinality, the component configuration must be deactivated because +the cardinality constraints will be violated. + +If a component configuration has a static reference and a bound service is modified or unregistered and ceases to be a +target service, or the policy-option is greedy and a better target service becomes available then SCR must deactivate +the component configuration. + +Afterwards, SCR must attempt to activate the component configuration again if another target service can be used as a +replacement for the outgoing service. + +**112.5.15 Modified Method** + +If a modified method is located, SCR must call this method to notify the component configuration of changes to the +component properties. If the modified method throws an exception, SCR must log an error message containing the exception +with the Log Service, if present and continue processing the modification. + +**112.5.16 Deactivation** + +Deactivating a component configuration consists of the following steps: + +- Call the deactivate method, if present. See Deactivate Method. +- Unbind any bound services. See Unbinding. +- Release all references to the component instance and component context. + +A component instance must complete activation or modification before it can be deactivated. + +A component configuration can be deactivated for a variety of reasons. The deactivation reason can be received by the +deactivate method. The following reason values are defined: + +- `DEACTIVATION_REASON_UNSPECIFIED` - Unspecified. +- `DEACTIVATION_REASON_DISABLED` - The component was disabled. +- `DEACTIVATION_REASON_REFERENCE` - A reference became unsatisfied. +- `DEACTIVATION_REASON_CONFIGURATION_MODIFIED` - A configuration was changed. +- `DEACTIVATION_REASON_CONFIGURATION_DELETED` - A configuration was deleted. +- `DEACTIVATION_REASON_DISPOSED` - The component was disposed. +- `DEACTIVATION_REASON_BUNDLE_STOPPED` - The bundle was stopped. + +Once the component configuration is deactivated, SCR must discard all references to the component instance and component +context associated with the activation. + +**112.5.17 Deactivate Method** + +The deactivate method can take zero or more parameters. Each parameter must be assignable from one of the following types: + +- One of the activation object types. +- `number` - The reason the component configuration is being deactivated. See Deactivation. + +**112.6 Component Properties** + +SCR always adds the following component properties, which cannot be overridden: + +- component.name - The component name. +- component.id - A unique value ( Long) that is larger than all previously assigned values. + +**112.7.1 Configuration Changes** + +SCR must track changes in the Configuration objects matching the configuration PIDs of a component description. + +Changes include the creating, updating and deleting of Configuration objects matching the configuration PIDs. + +The actions SCR must take when a configuration change for a component configuration occurs are based upon how the +configuration-policy and modified attributes are specified in the component description, whether a component +configuration becomes satisfied, remains satisfied or becomes unsatisfied and the type and number of matching +Configuration objects. + +With targeted PIDs, multiple Configuration objects can exist which can match a configuration PID. + +Creation of a Configuration object with a better matching PID than a Configuration object currently being used by a +component configuration results in a configuration change for the component configuration with the new Configuration +object replacing the currently used Configuration object. + +Deletion of a Configuration object currently being used by a component configuration when there is another Configuration +object matching the configuration PID also results in a configuration change for the component configuration with the +Configuration object having the best matching PID replacing the currently used, and now deleted, Configuration object. + +**112.7.1.1 Ignore Configuration Policy** + +For configuration-policy of ignore, component configurations are unaffected by configuration changes since the component +properties do not include properties from Configuration objects. + +**112.7.1.2 Require Configuration Policy** + +For configuration-policy of require, component configurations require a Configuration object for each specified +configuration PID. + +A configuration change can cause a component configuration to become unsatisfied if any of the following occur: + +- Each configuration PID of the component description does not have a matching Configuration object. +- A target property change results in a bound service of a static reference ceasing to be a target service. +- A target property change results in unbound target services for a static reference with the greedy policy option. +- A target property change or minimum cardinality property change results in a reference falling below the minimum cardinality. +- The component description does not specify the modified attribute. + +**112.7.1.3 Optional Configuration Policy** + +For configuration-policy of optional, component configurations do not require Configuration objects. + +Since matching Configuration objects are optional, component configurations can be satisfied with zero or more matched +configuration PIDs. + +If a Configuration object is then created which matches a configuration PID, this is a configuration change for the +component configurations that are not using the created Configuration object. + +If a Configuration object is deleted which matches a configuration PID, this is a configuration change for the component +configurations using the deleted Configuration object. + +A configuration change can cause a component configuration to become unsatisfied if any of the following occur: + +- A target property change results in a bound service of a static reference ceasing to be a target service. +- A target property change results in unbound target services for a static reference with the greedy policy option. +- A target property change or minimum cardinality property change results in a reference falling below the minimum cardinality. +- The component description does not specify the modified attribute. + +**112.7.1.4 Configuration Change Actions** + +If a component configuration becomes unsatisfied: + +- SCR must deactivate the component configuration +- If the component configuration was not created from a factory component, SCR must attempt to satisfy the component configuration with the current configuration state. + +If a component configuration remains satisfied: + +- If the component configuration has been activated, the modified method is called to provide the updated component properties. See Modification for more information. +- If the component configuration is registered as a service, SCR must modify the service properties. + +**112.9.2 Starting and Stopping SCR** + +When SCR is implemented as a bundle, any component configurations activated by SCR must be deactivated when the SCR +bundle is stopped. + +When the SCR bundle is started, it must process any components that are declared in bundles that are started. This +includes bundles which are started and are awaiting lazy activation. + +**112.9.7 Capabilities** + +SCR must provide the following capabilities. + +- A capability in the osgi.extender namespace declaring an extender with the name `COMPONENT_CAPABILITY_NAME`. + +A bundle that contains service components should require the osgi.extender capability from SCR. + +SCR must only process a bundle's service components if one of the following is true: + +- The bundle's wiring has a required wire for at least one osgi.extender capability with the name osgi.component and the first of these required wires is wired to SCR. +- The bundle's wiring has no required wire for an osgi.extender capability with the name osgi.component. + +Otherwise, SCR must not process the bundle's service components. + +## License + +Eclipse Public License - v 2.0 diff --git a/packages/@pandino/scr/package.json b/packages/@pandino/scr/package.json new file mode 100644 index 0000000..c71cf5f --- /dev/null +++ b/packages/@pandino/scr/package.json @@ -0,0 +1,68 @@ +{ + "name": "@pandino/scr", + "version": "0.8.31", + "description": "Manage services in a declarative way", + "module": "./dist/@pandino/scr.mjs", + "types": "dist/@pandino/scr.d.ts", + "type": "module", + "exports": { + ".": { + "require": "./dist/@pandino/scr.cjs", + "import": "./dist/@pandino/scr.mjs" + } + }, + "scripts": { + "build": "rimraf dist && tsc && vite build", + "test": "vitest run", + "test:dev": "vitest", + "tsc": "tsc" + }, + "keywords": [ + "pandino", + "bundle", + "service", + "scr", + "declarative-services", + "service-component-runtime" + ], + "author": "Norbert Herczeg ", + "license": "EPL-2.0", + "homepage": "https://github.com/BlackBeltTechnology/pandino", + "repository": { + "type": "git", + "url": "https://github.com/BlackBeltTechnology/pandino.git", + "directory": "packages/@pandino/scr" + }, + "bugs": { + "url": "https://github.com/BlackBeltTechnology/pandino/issues" + }, + "engines": { + "node": ">=20" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], + "devDependencies": { + "@pandino/configuration-management": "workspace:^", + "@pandino/configuration-management-api": "workspace:^", + "@pandino/pandino": "workspace:^", + "@pandino/pandino-api": "workspace:^", + "@pandino/persistence-manager-api": "workspace:^", + "@pandino/persistence-manager-memory": "workspace:^", + "@pandino/rollup-plugin-generate-manifest": "workspace:^", + "@pandino/scr-api": "workspace:^", + "rimraf": "^5.0.10", + "typescript": "^5.6.3", + "vite": "^5.4.10", + "vitest": "^2.1.3" + }, + "pandino": { + "manifest": { + "Require-Capability": "@pandino/configuration-management;objectClass:Array=\"@pandino/configuration-management/ConfigurationAdmin,@pandino/configuration-management/ConfigurationListener\"\"", + "Provide-Capability": "@pandino/extender;pandino.extender=\"pandino.component\"" + } + } +} diff --git a/packages/@pandino/scr/src/Activator.ts b/packages/@pandino/scr/src/Activator.ts new file mode 100644 index 0000000..17245c5 --- /dev/null +++ b/packages/@pandino/scr/src/Activator.ts @@ -0,0 +1,67 @@ +import { FRAMEWORK_LOGGER, FRAMEWORK_SERVICE_UTILS, ServiceUtils } from '@pandino/pandino-api'; +import type { BundleActivator, BundleContext, Logger, ServiceReference, ServiceRegistration } from '@pandino/pandino-api'; +import type { ServiceComponentRuntime } from './ServiceComponentRuntime'; +import { ServiceComponentRuntimeImpl } from './ServiceComponentRuntimeImpl'; +import { SCR_INTERFACE_KEY } from './constants'; +import { CONFIG_ADMIN_INTERFACE_KEY } from '@pandino/configuration-management-api'; +import type { ConfigurationAdmin } from '@pandino/configuration-management-api'; +import { ComponentRegistrarImpl } from './ComponentRegistrarImpl'; +import { COMPONENT_REGISTRAR_INTERFACE_KEY } from '@pandino/scr-api'; +import type { ComponentRegistrar } from '@pandino/scr-api'; +import { registerDecoratorHandler } from '@pandino/scr-api'; + +export class Activator implements BundleActivator { + private serviceRegistration?: ServiceRegistration; + private service?: ServiceComponentRuntime; + private componentRegistrarRegistration?: ServiceRegistration; + private componentRegistrar?: ComponentRegistrar; + private loggerReference?: ServiceReference; + private logger?: Logger; + private serviceUtilsReference?: ServiceReference; + private serviceUtils?: ServiceUtils; + private configAdminReference?: ServiceReference; + private configAdmin?: ConfigurationAdmin; + private decoratorHandlerUngetter?: () => void; + + async start(context: BundleContext) { + this.loggerReference = context.getServiceReference(FRAMEWORK_LOGGER)!; + this.logger = context.getService(this.loggerReference)!; + this.configAdminReference = context.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + this.configAdmin = context.getService(this.configAdminReference)!; + this.serviceUtilsReference = context.getServiceReference(FRAMEWORK_SERVICE_UTILS)!; + this.serviceUtils = context.getService(this.serviceUtilsReference)!; + this.service = new ServiceComponentRuntimeImpl(context, this.logger, this.configAdmin, this.serviceUtils); + this.serviceRegistration = context.registerService(SCR_INTERFACE_KEY, this.service, {}); + this.componentRegistrar = new ComponentRegistrarImpl(this.service); + this.componentRegistrarRegistration = context.registerService(COMPONENT_REGISTRAR_INTERFACE_KEY, this.componentRegistrar); + this.decoratorHandlerUngetter = registerDecoratorHandler(context); + + try { + this.service.processComponents(); + } catch (e: any) { + this.logger.error(e); + } + } + + async stop(context: BundleContext) { + try { + this.service?.releaseComponents(); + } catch (e: any) { + this.logger?.error(e); + } + if (this.loggerReference) { + context.ungetService(this.loggerReference); + } + if (this.serviceUtilsReference) { + context.ungetService(this.serviceUtilsReference); + } + if (this.configAdminReference) { + context.ungetService(this.configAdminReference); + } + this.serviceRegistration?.unregister(); + this.componentRegistrarRegistration?.unregister(); + if (this.decoratorHandlerUngetter) { + this.decoratorHandlerUngetter(); + } + } +} diff --git a/packages/@pandino/scr/src/ComponentConfigurationImpl.ts b/packages/@pandino/scr/src/ComponentConfigurationImpl.ts new file mode 100644 index 0000000..6cf84df --- /dev/null +++ b/packages/@pandino/scr/src/ComponentConfigurationImpl.ts @@ -0,0 +1,359 @@ +import type { + ComponentConfiguration, + ComponentConfigurationState, + ComponentContext, + ComponentInstance, + ConfigurationPolicy, + DeactivationReason, + InternalActivatorMetaData, + InternalDeActivatorMetaData, + InternalMetaData, + InternalModifiedMetaData, + InternalReferenceMetaData, + SatisfiedReference, + UnsatisfiedReference, +} from '@pandino/scr-api'; +import { + COMPONENT_ACTIVATE_KEY_METHOD, + COMPONENT_DEACTIVATE_KEY_METHOD, + COMPONENT_KEY_CONFIGURATION_POLICY, + COMPONENT_KEY_NAME, + COMPONENT_KEY_PROPERTY, + COMPONENT_KEY_SERVICE, + COMPONENT_MODIFIED_KEY_METHOD, + REFERENCE_KEY_CARDINALITY, + REFERENCE_KEY_SERVICE, + REFERENCE_KEY_TARGET, +} from '@pandino/scr-api'; +import type { + BundleContext, + Logger, + ServiceEvent, + ServiceListener, + ServiceProperties, + ServiceReference, + ServiceRegistration, + ServiceUtils, +} from '@pandino/pandino-api'; +import { SERVICE_PID } from '@pandino/pandino-api'; +import { ComponentContextImpl } from './ComponentContextImpl'; +import type { Configuration, ConfigurationAdmin, ConfigurationEvent, ConfigurationListener, ManagedService } from '@pandino/configuration-management-api'; +import { ComponentInstanceImpl } from './ComponentInstanceImpl'; +import { SatisfiedReferenceImpl } from './SatisfiedReferenceImpl'; +import { UnsatisfiedReferenceImpl } from './UnsatisfiedReferenceImpl'; + +export class ComponentConfigurationImpl implements ComponentConfiguration, ConfigurationListener, ManagedService { + private readonly id: number; + private readonly pid: string; + private readonly internalMetaData: InternalMetaData; + private readonly configurationPolicy: ConfigurationPolicy; + // @ts-ignore + private readonly componentContext: ComponentContext; + private readonly declaringBundleContext: BundleContext; + private readonly configAdmin: ConfigurationAdmin; + private readonly serviceUtils: ServiceUtils; + // @ts-ignore + private readonly instance: ComponentInstance; + private readonly logger: Logger; + private configuration?: Configuration; + private state: ComponentConfigurationState = 'UNSATISFIED_CONFIGURATION'; + private serviceRegistration?: ServiceRegistration; + private satisfiedReferences: SatisfiedReference[] = []; + private unsatisfiedReferences: UnsatisfiedReference[] = []; + private isActive: boolean = false; + private readonly serviceListeners: Map = new Map(); + + constructor( + pid: string, + input: InternalMetaData, + targetConstructor: any, + declaringBundleContext: BundleContext, + configAdmin: ConfigurationAdmin, + logger: Logger, + serviceUtils: ServiceUtils, + ) { + this.pid = pid; + this.id = new Date().valueOf(); + this.logger = logger; + this.declaringBundleContext = declaringBundleContext; + this.configAdmin = configAdmin; + this.internalMetaData = input; + this.serviceUtils = serviceUtils; + this.configurationPolicy = this.internalMetaData[COMPONENT_KEY_CONFIGURATION_POLICY]; + + if (this.configurationRequiredAndNotSatisfied()) { + this.state = 'UNSATISFIED_CONFIGURATION'; + this.isActive = false; + return; + } else { + if (this.configurationRequiredAndSatisfied() || this.configurationPolicy === 'OPTIONAL') { + this.configuration = this.configAdmin.getConfiguration(Array.isArray(this.pid) ? this.pid[0] : this.pid); + this.configuration.update({ + ...this.internalMetaData[COMPONENT_KEY_PROPERTY], + ...this.configuration.getProperties(), + }); + } + + this.instance = new ComponentInstanceImpl(targetConstructor); + this.componentContext = new ComponentContextImpl(this, declaringBundleContext, this.instance); + + this.updateRefs(); + this.updateState(); + this.setUpServiceListeners(); + } + } + + updated(properties?: ServiceProperties): void { + // Do nothing, we are only interested in events in the `configurationEvent` method. + } + + private updateState(): void { + if (this.configurationRequiredAndNotSatisfied()) { + this.state = 'UNSATISFIED_CONFIGURATION'; + this.deactivate('CONFIGURATION_DELETED'); + } else if (!this.allMandatoryRefsSatisfied()) { + this.state = 'UNSATISFIED_REFERENCE'; + this.deactivate('REFERENCE'); + } else { + this.state = 'SATISFIED'; + this.activate(); + } + } + + private setUpServiceListeners(): void { + const fields = Object.keys(this.internalMetaData.references); + for (const field of fields) { + const refMeta: InternalReferenceMetaData = this.internalMetaData.references[field]; + if (!this.serviceListeners.has(field)) { + const listener: ServiceListener = { + isSync: true, + serviceChanged: (event: ServiceEvent) => { + this.onServiceListenerChanged(field, refMeta[REFERENCE_KEY_SERVICE], event, refMeta[REFERENCE_KEY_TARGET]); + }, + }; + this.serviceListeners.set(field, listener); + } + } + } + + private activate(): void { + if (!this.isActive) { + try { + // 112.5.9 Activation Objects + this.serviceRegistration = this.declaringBundleContext.registerService(this.internalMetaData[COMPONENT_KEY_SERVICE], this.instance.getInstance(), { + ...this.getProperties(), + }); + this.invokeActivateIfPresent(this.componentContext, this.declaringBundleContext, this.serviceRegistration.getProperties()); + this.isActive = true; + this.state = 'ACTIVE'; + } catch (e: any) { + this.serviceRegistration?.unregister(); + this.isActive = false; + this.state = 'FAILED_ACTIVATION'; + this.logger.error(`Error during @Activate of Component (${this.internalMetaData[COMPONENT_KEY_NAME]}): ${e}`); + } + } + } + + public deactivate(reason: DeactivationReason) { + if (this.isActive) { + try { + // 112.5.16 Deactivation + this.invokeDeactivateIfPresent(this.componentContext, this.declaringBundleContext); + for (const sRef of this.satisfiedReferences) { + for (const bRef of sRef.getBoundServices()) { + this.declaringBundleContext.ungetService(bRef); + } + } + this.serviceRegistration?.unregister(); + } catch (e: any) { + this.logger.error(`Error during @Deactivate of Component (${this.internalMetaData[COMPONENT_KEY_NAME]}): ${e}`); + } + this.isActive = false; + if (this.configurationRequiredAndNotSatisfied()) { + this.state = 'UNSATISFIED_CONFIGURATION'; + } else if (!this.allMandatoryRefsSatisfied()) { + this.state = 'UNSATISFIED_REFERENCE'; + } + } + } + + private configurationRequiredAndSatisfied(): boolean { + const configurations = this.configAdmin.listConfigurations(`(${SERVICE_PID}=${this.pid})`); + return this.configurationPolicy === 'REQUIRE' && configurations.length > 0; + } + + private configurationRequiredAndNotSatisfied(): boolean { + const configurations = this.configAdmin.listConfigurations(`(${SERVICE_PID}=${this.pid})`); + return this.configurationPolicy === 'REQUIRE' && configurations.length === 0; + } + + private allMandatoryRefsSatisfied(): boolean { + const fields = Object.keys(this.internalMetaData.references); + for (const field of fields) { + const refMeta: InternalReferenceMetaData = this.internalMetaData.references[field]; + if (refMeta[REFERENCE_KEY_CARDINALITY] === 'MANDATORY') { + if ( + !this.unsatisfiedReferences.some( + (r) => r.getName() === refMeta[REFERENCE_KEY_SERVICE] && (!refMeta[REFERENCE_KEY_TARGET] || r.getTarget() === refMeta[REFERENCE_KEY_TARGET]), + ) + ) { + return false; + } + } + } + return true; + } + + private updateRefs(): void { + this.satisfiedReferences = []; + this.unsatisfiedReferences = []; + const fields = Object.keys(this.internalMetaData.references); + + for (const field of fields) { + const refMeta: InternalReferenceMetaData = this.internalMetaData.references[field]; + const references = this.declaringBundleContext.getAllServiceReferences(refMeta[REFERENCE_KEY_SERVICE], refMeta[REFERENCE_KEY_TARGET]); + if (references.length > 0) { + if (!this.satisfiedReferences.some((s) => (s as SatisfiedReferenceImpl).equals(refMeta))) { + const satRef = new SatisfiedReferenceImpl(refMeta[REFERENCE_KEY_SERVICE], refMeta[REFERENCE_KEY_TARGET], references); + this.satisfiedReferences.push(satRef); + } + } else { + const existingIdx = this.satisfiedReferences.findIndex((s) => (s as SatisfiedReferenceImpl).equals(refMeta)); + if (existingIdx > -1) { + const satRef = this.satisfiedReferences[existingIdx]; + this.satisfiedReferences.splice(existingIdx, 1); + for (const bound of satRef.getBoundServices()) { + this.declaringBundleContext.ungetService(bound); + } + } + if (!this.unsatisfiedReferences.some((s) => (s as SatisfiedReferenceImpl).equals(refMeta))) { + const unsatRef = new UnsatisfiedReferenceImpl(refMeta[REFERENCE_KEY_SERVICE], refMeta[REFERENCE_KEY_TARGET]); + this.unsatisfiedReferences.push(unsatRef); + } + } + this.updateReferenceField(field, references); + } + } + + private updateReferenceField(field: string, references: ServiceReference[]) { + const inst: any = this.instance.getInstance(); + const ref = this.serviceUtils.getBestServiceReference(references); + if (ref) { + inst[field] = this.declaringBundleContext.getService(ref); + } else { + inst[field] = undefined; + } + } + + private componentHasModifiedMethod(): boolean { + return !!this.internalMetaData[COMPONENT_MODIFIED_KEY_METHOD]; + } + + private invokeModifiedIfPresent(componentContext: ComponentContext, bundleContext: BundleContext, properties?: ServiceProperties): void { + if (this.componentHasModifiedMethod() && this.configurationPolicy !== 'IGNORE') { + const modifiedMeta = this.getInternalModifiedMetaData()!; + // @ts-ignore + this.instance.getInstance()[modifiedMeta.method as keyof S](componentContext, bundleContext, { + ...properties, + ...this.getProperties(), + }); + } + } + + private componentHasActivateMethod(): boolean { + return !!this.internalMetaData[COMPONENT_ACTIVATE_KEY_METHOD]; + } + + private invokeActivateIfPresent(componentContext: ComponentContext, bundleContext: BundleContext, properties?: ServiceProperties): void { + if (this.componentHasActivateMethod()) { + const activatorMeta = this.getInternalActivatorMetaData()!; + // @ts-ignore + this.instance.getInstance()[activatorMeta.method as keyof S](componentContext, bundleContext, properties); + } + } + + private componentHasDeactivateMethod(): boolean { + return !!this.internalMetaData[COMPONENT_DEACTIVATE_KEY_METHOD]; + } + + private invokeDeactivateIfPresent(componentContext: ComponentContext, bundleContext: BundleContext): void { + if (this.componentHasDeactivateMethod()) { + const deActivatorMeta = this.getInternalDeactivatorMetaData()!; + // @ts-ignore + this.instance.getInstance()[deActivatorMeta.method as keyof S](componentContext, bundleContext); + } + } + + private getInternalModifiedMetaData(): InternalModifiedMetaData | undefined { + return this.internalMetaData[COMPONENT_MODIFIED_KEY_METHOD]; + } + + private getInternalActivatorMetaData(): InternalActivatorMetaData | undefined { + return this.internalMetaData[COMPONENT_ACTIVATE_KEY_METHOD]; + } + + private getInternalDeactivatorMetaData(): InternalDeActivatorMetaData | undefined { + return this.internalMetaData[COMPONENT_DEACTIVATE_KEY_METHOD]; + } + + getId(): number { + return this.id; + } + + getPID(): string | string[] { + return this.pid; + } + + getConfigurationPolicy(): ConfigurationPolicy { + throw this.configurationPolicy; + } + + getService(): ServiceReference | undefined { + return this.serviceRegistration?.getReference(); + } + + getProperties(): ServiceProperties { + if (this.configuration) { + return { + ...this.internalMetaData[COMPONENT_KEY_PROPERTY], + ...this.configuration.getProperties(), + }; + } + return this.internalMetaData[COMPONENT_KEY_PROPERTY]; + } + + getSatisfiedReferences(): SatisfiedReference[] { + return this.satisfiedReferences; + } + + getState(): ComponentConfigurationState { + return this.state; + } + + getUnsatisfiedReferences(): UnsatisfiedReference[] { + return this.unsatisfiedReferences; + } + + configurationEvent(event: ConfigurationEvent): void { + if (event.getType() === 'UPDATED') { + if (!this.isActive) { + if (this.configurationPolicy === 'REQUIRE') { + this.activate(); + } + } else { + this.invokeModifiedIfPresent(this.componentContext, this.declaringBundleContext, event.getReference().getProperties()); + } + } else if (event.getType() === 'DELETED') { + if (this.configurationPolicy === 'REQUIRE') { + this.deactivate('CONFIGURATION_DELETED'); + } + } + } + + onServiceListenerChanged(field: string, service: string, event: ServiceEvent, target?: string): void { + // todo call potential service handler method once introduced + this.updateRefs(); + this.updateState(); + } +} diff --git a/packages/@pandino/scr/src/ComponentContextImpl.ts b/packages/@pandino/scr/src/ComponentContextImpl.ts new file mode 100644 index 0000000..70b6ece --- /dev/null +++ b/packages/@pandino/scr/src/ComponentContextImpl.ts @@ -0,0 +1,31 @@ +import type { ComponentContext, ComponentInstance } from '@pandino/scr-api'; +import type { BundleContext, ServiceProperties, ServiceReference } from '@pandino/pandino-api'; +import { ComponentConfigurationImpl } from './ComponentConfigurationImpl'; + +export class ComponentContextImpl implements ComponentContext { + private readonly config: ComponentConfigurationImpl; + private readonly context: BundleContext; + private readonly instance: ComponentInstance; + + constructor(config: ComponentConfigurationImpl, context: BundleContext, instance: ComponentInstance) { + this.config = config; + this.context = context; + this.instance = instance; + } + + getBundleContext(): BundleContext { + return this.context; + } + + getComponentInstance(): ComponentInstance { + return this.instance; + } + + getProperties(): ServiceProperties { + return this.config.getProperties(); + } + + getServiceReference(): ServiceReference | undefined { + return this.config.getService(); + } +} diff --git a/packages/@pandino/scr/src/ComponentInstanceImpl.ts b/packages/@pandino/scr/src/ComponentInstanceImpl.ts new file mode 100644 index 0000000..44c3e78 --- /dev/null +++ b/packages/@pandino/scr/src/ComponentInstanceImpl.ts @@ -0,0 +1,15 @@ +import { ComponentInstance } from '@pandino/scr-api'; + +export class ComponentInstanceImpl implements ComponentInstance { + private readonly clazz: new (...args: any[]) => S; + private readonly instance: S; + + constructor(clazz: new (...args: any[]) => S) { + this.clazz = clazz; + this.instance = new this.clazz(); + } + + getInstance(): S { + return this.instance; + } +} diff --git a/packages/@pandino/scr/src/ComponentRegistrarImpl.ts b/packages/@pandino/scr/src/ComponentRegistrarImpl.ts new file mode 100644 index 0000000..e1bf289 --- /dev/null +++ b/packages/@pandino/scr/src/ComponentRegistrarImpl.ts @@ -0,0 +1,15 @@ +import type { ComponentRegistrar } from '@pandino/scr-api'; +import type { BundleContext } from '@pandino/pandino-api'; +import type { ServiceComponentRuntime } from './ServiceComponentRuntime'; + +export class ComponentRegistrarImpl implements ComponentRegistrar { + private readonly scr: ServiceComponentRuntime; + + constructor(scr: ServiceComponentRuntime) { + this.scr = scr; + } + + registerComponent(target: any, bundleContext: BundleContext): void { + this.scr.processComponent(target, bundleContext); + } +} diff --git a/packages/@pandino/scr/src/SatisfiedReferenceImpl.ts b/packages/@pandino/scr/src/SatisfiedReferenceImpl.ts new file mode 100644 index 0000000..d986645 --- /dev/null +++ b/packages/@pandino/scr/src/SatisfiedReferenceImpl.ts @@ -0,0 +1,41 @@ +import { REFERENCE_KEY_SERVICE, REFERENCE_KEY_TARGET } from '@pandino/scr-api'; +import type { InternalReferenceMetaData, SatisfiedReference } from '@pandino/scr-api'; +import type { ServiceReference } from '@pandino/pandino-api'; + +export class SatisfiedReferenceImpl implements SatisfiedReference { + private readonly name: string; + private readonly target?: string; + private boundServices: ServiceReference[] = []; + + constructor(name: string, target?: string, boundServices?: ServiceReference[]) { + this.name = name; + this.target = target; + if (Array.isArray(boundServices)) { + this.boundServices = boundServices; + } + } + + addBoundService(boundService: ServiceReference): void { + this.boundServices.push(boundService); + } + + setBoundServices(boundServices: ServiceReference[]): void { + this.boundServices = boundServices; + } + + getBoundServices(): ServiceReference[] { + return this.boundServices; + } + + getName(): string { + return this.name; + } + + getTarget(): string | undefined { + return this.target; + } + + equals(refMeta: InternalReferenceMetaData): boolean { + return this.name === refMeta[REFERENCE_KEY_SERVICE] && this.target === refMeta[REFERENCE_KEY_TARGET]; + } +} diff --git a/packages/@pandino/scr/src/ServiceComponentRuntime.ts b/packages/@pandino/scr/src/ServiceComponentRuntime.ts new file mode 100644 index 0000000..db32bf3 --- /dev/null +++ b/packages/@pandino/scr/src/ServiceComponentRuntime.ts @@ -0,0 +1,9 @@ +import type { BundleContext } from '@pandino/pandino-api'; +import type { ComponentConfiguration } from '@pandino/scr-api'; + +export interface ServiceComponentRuntime { + processComponent(target: any, bundleContext: BundleContext): void; + processComponents(): void; + releaseComponent(component: ComponentConfiguration): void; + releaseComponents(): void; +} diff --git a/packages/@pandino/scr/src/ServiceComponentRuntimeImpl.ts b/packages/@pandino/scr/src/ServiceComponentRuntimeImpl.ts new file mode 100644 index 0000000..8912af3 --- /dev/null +++ b/packages/@pandino/scr/src/ServiceComponentRuntimeImpl.ts @@ -0,0 +1,87 @@ +import type { BundleContext, Logger, ServiceRegistration, ServiceUtils } from '@pandino/pandino-api'; +import { SERVICE_PID } from '@pandino/pandino-api'; +import { + CONFIGURATION_LISTENER_INTERFACE_KEY, + ConfigurationAdmin, + ConfigurationListener, + MANAGED_SERVICE_INTERFACE_KEY, +} from '@pandino/configuration-management-api'; +import { + $$PANDINO_META, + COMPONENT_KEY_CONFIGURATION_PID, + COMPONENT_KEY_CONFIGURATION_POLICY, + ComponentConfiguration, + InternalMetaData, +} from '@pandino/scr-api'; +import type { ServiceComponentRuntime } from './ServiceComponentRuntime'; +import { ComponentConfigurationImpl } from './ComponentConfigurationImpl'; + +export class ServiceComponentRuntimeImpl implements ServiceComponentRuntime { + // @ts-ignore + private readonly context: BundleContext; + private readonly logger: Logger; + private readonly serviceUtils: ServiceUtils; + private readonly configAdmin: ConfigurationAdmin; + private readonly configurationListenerRegistrations: Map> = new Map< + any, + ServiceRegistration + >(); + private readonly configurations: Map> = new Map>(); + + constructor(context: BundleContext, logger: Logger, configAdmin: ConfigurationAdmin, serviceUtils: ServiceUtils) { + this.context = context; + this.logger = logger; + this.configAdmin = configAdmin; + this.serviceUtils = serviceUtils; + } + + processComponent(target: any, bundleContext: BundleContext): void { + if (target.prototype[$$PANDINO_META]) { + const rawData: InternalMetaData = target.prototype[$$PANDINO_META]; + const pid = rawData[COMPONENT_KEY_CONFIGURATION_PID]; + const pids: string[] = Array.isArray(pid) ? pid : [pid]; + + for (const p of pids) { + const componentConfiguration = new ComponentConfigurationImpl(p, rawData, target, bundleContext, this.configAdmin, this.logger, this.serviceUtils); + this.configurations.set(target, componentConfiguration); + if (rawData[COMPONENT_KEY_CONFIGURATION_POLICY] !== 'IGNORE') { + const listenerRegistration = bundleContext.registerService( + [MANAGED_SERVICE_INTERFACE_KEY, CONFIGURATION_LISTENER_INTERFACE_KEY], + componentConfiguration, + { + [SERVICE_PID]: p, + }, + ); + this.configurationListenerRegistrations.set(target, listenerRegistration); + } + } + } + } + + releaseComponent(config: ComponentConfiguration): void { + const ref = config.getService(); + if (ref) { + try { + (config as ComponentConfigurationImpl).deactivate('BUNDLE_STOPPED'); + } catch (e) { + this.logger.error(`Error releasing component: ${e}`); + } + } + } + + processComponents(): void {} + + releaseComponents(): void { + for (const [target, config] of this.configurations) { + try { + const configListenerReg = this.configurationListenerRegistrations.get(target); + configListenerReg?.unregister(); + this.releaseComponent(config); + } catch (e: any) { + this.logger.error(e); + } + } + this.configurations.clear(); + this.configurationListenerRegistrations.clear(); + } +} diff --git a/packages/@pandino/scr/src/UnsatisfiedReferenceImpl.ts b/packages/@pandino/scr/src/UnsatisfiedReferenceImpl.ts new file mode 100644 index 0000000..24876b9 --- /dev/null +++ b/packages/@pandino/scr/src/UnsatisfiedReferenceImpl.ts @@ -0,0 +1,19 @@ +import { UnsatisfiedReference } from '@pandino/scr-api'; + +export class UnsatisfiedReferenceImpl implements UnsatisfiedReference { + private readonly name: string; + private readonly target?: string; + + constructor(name: string, target?: string) { + this.name = name; + this.target = target; + } + + getName(): string { + return this.name; + } + + getTarget(): string | undefined { + return this.target; + } +} diff --git a/packages/@pandino/scr/src/constants.ts b/packages/@pandino/scr/src/constants.ts new file mode 100644 index 0000000..3eee2bb --- /dev/null +++ b/packages/@pandino/scr/src/constants.ts @@ -0,0 +1 @@ +export const SCR_INTERFACE_KEY = '@pandino/scr/ServiceComponentRuntime'; diff --git a/packages/@pandino/scr/src/index.test.ts b/packages/@pandino/scr/src/index.test.ts new file mode 100644 index 0000000..f0ee385 --- /dev/null +++ b/packages/@pandino/scr/src/index.test.ts @@ -0,0 +1,427 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import Pandino from '@pandino/pandino'; +import { + BUNDLE_ACTIVATOR, + BUNDLE_SYMBOLICNAME, + BUNDLE_VERSION, + LOG_LEVEL_PROP, + LogLevel, + OBJECTCLASS, + PANDINO_BUNDLE_IMPORTER_PROP, + PANDINO_MANIFEST_FETCHER_PROP, + PROVIDE_CAPABILITY, + REQUIRE_CAPABILITY, + SERVICE_PID, + ServiceProperties, +} from '@pandino/pandino-api'; +import type { Bundle, BundleContext, BundleImporter, BundleManifestHeaders, FrameworkConfigMap } from '@pandino/pandino-api'; +import PMActivator from '@pandino/persistence-manager-memory'; +import CMActivator from '@pandino/configuration-management'; +import { ComponentContext, Deactivate, Modified } from '@pandino/scr-api'; +import { Activate, Component } from '@pandino/scr-api'; +import type { ConfigurationAdmin } from '@pandino/configuration-management-api'; +import { CONFIG_ADMIN_INTERFACE_KEY } from '@pandino/configuration-management-api'; +import { Activator as SCRActivator } from './Activator'; +import { SCR_INTERFACE_KEY } from './constants'; + +const CMP_ONE_KEY = '@test/cmp-one'; +interface CmpOne { + hello(inp: string): string; +} + +const CMP_TWO_KEY = '@test/cmp-two'; +interface CmpTwo { + bello(inp: string): number; +} + +describe('SCR', () => { + let params: FrameworkConfigMap; + let pandino: Pandino; + let pandinoContext: BundleContext; + const pmHeaders: () => BundleManifestHeaders = () => ({ + [BUNDLE_SYMBOLICNAME]: '@pandino/persistence-manager-memory', + [BUNDLE_VERSION]: '0.0.0', + [BUNDLE_ACTIVATOR]: new PMActivator(), + [PROVIDE_CAPABILITY]: '@pandino/persistence-manager;type="in-memory";objectClass="@pandino/persistence-manager/PersistenceManager"', + }); + const cmHeaders: () => BundleManifestHeaders = () => ({ + [BUNDLE_SYMBOLICNAME]: '@pandino/configuration-management', + [BUNDLE_VERSION]: '0.0.0', + [BUNDLE_ACTIVATOR]: new CMActivator(), + [REQUIRE_CAPABILITY]: '@pandino/persistence-manager;filter:=(objectClass=@pandino/persistence-manager/PersistenceManager)', + [PROVIDE_CAPABILITY]: + '@pandino/configuration-management;objectClass:Array="@pandino/configuration-management/ConfigurationAdmin,@pandino/configuration-management/ManagedService,@pandino/configuration-management/ConfigurationListener"', + }); + const scrHeaders: () => BundleManifestHeaders = () => ({ + [BUNDLE_SYMBOLICNAME]: '@pandino/scr', + [BUNDLE_VERSION]: '0.0.0', + [BUNDLE_ACTIVATOR]: new SCRActivator(), + [REQUIRE_CAPABILITY]: + '@pandino/configuration-management;objectClass:Array="@pandino/configuration-management/ConfigurationAdmin,@pandino/configuration-management/ConfigurationListener""', + [PROVIDE_CAPABILITY]: '@pandino/extender;pandino.extender="pandino.component"', + }); + const importer: BundleImporter = { + import: (activatorLocation: string, manifestLocation: string, deploymentRoot?: string) => { + // this won't be called if the activator is an instance and not a string + return Promise.resolve({ + default: null as unknown as any, + }); + }, + }; + + beforeEach(async () => { + params = { + [PANDINO_MANIFEST_FETCHER_PROP]: vi.fn() as any, + [PANDINO_BUNDLE_IMPORTER_PROP]: importer, + [LOG_LEVEL_PROP]: LogLevel.WARN, + }; + pandino = new Pandino(params); + + await pandino.init(); + await pandino.start(); + + pandinoContext = pandino.getBundleContext(); + }); + + afterEach(() => { + pandino.stop(); + pandino = undefined; + pandinoContext = undefined; + }); + + it('SCR is only available if ConfigAdmin is present', async () => { + await pandinoContext.installBundle(scrHeaders()); + + expect(pandinoContext.getServiceReference(SCR_INTERFACE_KEY)).toBeUndefined(); + + const [_, configAdminBundle] = await installDepBundles(pandinoContext); + + expect(pandinoContext.getServiceReference(SCR_INTERFACE_KEY)).toBeDefined(); + + await pandino.uninstallBundle(configAdminBundle as any); + + expect(pandinoContext.getServiceReference(SCR_INTERFACE_KEY)).toBeUndefined(); + }); + + it('@Component is registering', async () => { + await prepareSCR(pandinoContext); + + expect(pandinoContext.getServiceReference(CMP_ONE_KEY)).toBeUndefined(); + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + } + + const ref = pandinoContext.getServiceReference(CMP_ONE_KEY); + expect(ref).toBeDefined(); + + const service = pandinoContext.getService(ref); + expect(service.hello('me')).toEqual('ME'); + }); + + it('@Component is not registering if configuration policy is require and missing', async () => { + await prepareSCR(pandinoContext); + + expect(pandinoContext.getServiceReference(CMP_ONE_KEY)).toBeUndefined(); + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY, configurationPolicy: 'REQUIRE' }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + } + + expect(pandinoContext.getServiceReference(CMP_ONE_KEY)).toBeUndefined(); + }); + + it('@Component is registering if configuration policy is require and is present', async () => { + await prepareSCR(pandinoContext); + + const configAdminRef = pandinoContext.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + const configAdmin = pandinoContext.getService(configAdminRef)!; + + const config = configAdmin.getConfiguration(CMP_ONE_KEY); + config.update({ + [SERVICE_PID]: CMP_ONE_KEY, + yolo: 'hello', + }); + + expect(pandinoContext.getServiceReference(CMP_ONE_KEY)).toBeUndefined(); + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY, configurationPolicy: 'REQUIRE' }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + } + + expect(pandinoContext.getServiceReference(CMP_ONE_KEY)).toBeDefined(); + }); + + it('@Component is registering with pid property if present', async () => { + await prepareSCR(pandinoContext); + + const configAdminRef = pandinoContext.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + const configAdmin = pandinoContext.getService(configAdminRef)!; + const pid = 'custom-pid'; + + const config = configAdmin.getConfiguration(pid); + config.update({ + yolo: 'hello', + }); + + expect(pandinoContext.getServiceReference(CMP_ONE_KEY)).toBeUndefined(); + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY, configurationPid: pid }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + } + + const ref = pandinoContext.getServiceReference(CMP_ONE_KEY); + expect(ref).toBeDefined(); + + expect(ref.getProperty(SERVICE_PID)).toEqual(pid); + }); + + it('@Activate is called', async () => { + await prepareSCR(pandinoContext); + const activateSpy = vi.fn(); + let cmpCtx: ComponentContext | undefined; + let bndCtx: BundleContext | undefined; + let props: ServiceProperties | undefined; + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY, property: { xOne: 1, xTwo: false } }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + @Activate() + onActivate(componentContext: ComponentContext, bundleContext: BundleContext, properties?: ServiceProperties) { + activateSpy(); + cmpCtx = componentContext; + bndCtx = bundleContext; + props = properties; + } + } + + const ref = pandinoContext.getServiceReference(CMP_ONE_KEY); + expect(ref).toBeDefined(); + expect(ref.getProperty(OBJECTCLASS)).toEqual(CMP_ONE_KEY); + + expect(activateSpy).toHaveBeenCalledTimes(1); + expect(cmpCtx).toBeDefined(); + expect(bndCtx).toBeDefined(); + expect(props).toMatchObject({ + [SERVICE_PID]: CMP_ONE_KEY, + [OBJECTCLASS]: CMP_ONE_KEY, + xOne: 1, + xTwo: false, + }); + }); + + it('@Deactivate is called when SCR Bundle is uninstalled', async () => { + const [pmb, cmb, scr] = await prepareSCR(pandinoContext); + const deActivateSpy = vi.fn(); + let cmpCtx: ComponentContext | undefined; + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY, property: { xOne: 1, xTwo: false } }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + + @Deactivate() + onDeactivate(componentContext: ComponentContext) { + deActivateSpy(); + cmpCtx = componentContext; + } + } + + expect(deActivateSpy).toHaveBeenCalledTimes(0); + + await pandino.uninstallBundle(scr as any); + + expect(deActivateSpy).toHaveBeenCalledTimes(1); + expect(cmpCtx).toBeDefined(); + }); + + it('@Deactivate is called when required configuration is deleted', async () => { + await prepareSCR(pandinoContext); + const activateSpy = vi.fn(); + const deActivateSpy = vi.fn(); + const configAdminRef = pandinoContext.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + const configAdmin = pandinoContext.getService(configAdminRef)!; + const pid = 'custom-pid'; + + const config = configAdmin.getConfiguration(pid); + config.update({ + yolo: 'hello', + }); + + @Component({ name: 'test-comp-one-impl', configurationPolicy: 'REQUIRE', service: CMP_ONE_KEY, configurationPid: pid }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + + @Activate() + onActivate(componentContext: ComponentContext, bundleContext: BundleContext, properties?: ServiceProperties) { + activateSpy(); + } + + @Deactivate() + onDeactivate() { + deActivateSpy(); + } + } + + expect(activateSpy).toHaveBeenCalledTimes(1); + expect(deActivateSpy).toHaveBeenCalledTimes(0); + + config.delete(); + + expect(deActivateSpy).toHaveBeenCalledTimes(1); + expect(pandinoContext.getServiceReference(CMP_ONE_KEY)).toBeUndefined(); + }); + + it('@Deactivate is not called when optional configuration is deleted', async () => { + await prepareSCR(pandinoContext); + const activateSpy = vi.fn(); + const deActivateSpy = vi.fn(); + const configAdminRef = pandinoContext.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + const configAdmin = pandinoContext.getService(configAdminRef)!; + const pid = 'custom-pid'; + + const config = configAdmin.getConfiguration(pid); + config.update({ + yolo: 'hello', + }); + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY, configurationPid: pid }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + + @Activate() + onActivate(componentContext: ComponentContext, bundleContext: BundleContext, properties?: ServiceProperties) { + activateSpy(); + } + + @Deactivate() + onDeactivate() { + deActivateSpy(); + } + } + + expect(activateSpy).toHaveBeenCalledTimes(1); + expect(deActivateSpy).toHaveBeenCalledTimes(0); + + config.delete(); + + expect(deActivateSpy).toHaveBeenCalledTimes(0); + expect(pandinoContext.getServiceReference(CMP_ONE_KEY)).toBeDefined(); + }); + + it('@Modified is called when configuration changes', async () => { + await prepareSCR(pandinoContext); + const modifiedSpy = vi.fn(); + const modifiedProps: ServiceProperties[] = []; + const configAdminRef = pandinoContext.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + const configAdmin = pandinoContext.getService(configAdminRef)!; + + const config = configAdmin.getConfiguration(CMP_ONE_KEY); + config.update({ + [SERVICE_PID]: CMP_ONE_KEY, + yolo: 'hello', + }); + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + + @Modified() + onModified(componentContext: ComponentContext, bundleContext: BundleContext, properties?: ServiceProperties) { + modifiedSpy(componentContext, bundleContext, properties); + modifiedProps.push(properties); + } + } + + expect(modifiedSpy).toHaveBeenCalledTimes(0); + expect(modifiedProps.length).toEqual(0); + + config.update({ + yolo: 'bello', + }); + + expect(modifiedSpy).toHaveBeenCalledTimes(1); + expect(modifiedProps.length).toEqual(1); + expect(modifiedProps[0]).toMatchObject({ + yolo: 'bello', + }); + + config.delete(); + + expect(modifiedSpy).toHaveBeenCalledTimes(1); + }); + + it('@Modified is not called when configuration changes and ConfigurationPolicy is IGNORE', async () => { + await prepareSCR(pandinoContext); + const modifiedSpy = vi.fn(); + const modifiedProps: ServiceProperties[] = []; + const configAdminRef = pandinoContext.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + const configAdmin = pandinoContext.getService(configAdminRef)!; + + const config = configAdmin.getConfiguration(CMP_ONE_KEY); + config.update({ + [SERVICE_PID]: CMP_ONE_KEY, + yolo: 'hello', + }); + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY, configurationPolicy: 'IGNORE' }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + + @Modified() + onModified(componentContext: ComponentContext, bundleContext: BundleContext, properties?: ServiceProperties) { + modifiedSpy(componentContext, bundleContext, properties); + modifiedProps.push(properties); + } + } + + expect(modifiedSpy).toHaveBeenCalledTimes(0); + expect(modifiedProps.length).toEqual(0); + + config.update({ + yolo: 'bello', + }); + + expect(modifiedSpy).toHaveBeenCalledTimes(0); + expect(modifiedProps.length).toEqual(0); + + config.delete(); + + expect(modifiedSpy).toHaveBeenCalledTimes(0); + }); + + async function installDepBundles(ctx: BundleContext): Promise { + const pmb = await ctx.installBundle(pmHeaders()); + const cmb = await ctx.installBundle(cmHeaders()); + return [pmb, cmb]; + } + + async function prepareSCR(ctx: BundleContext): Promise { + const [pmb, cmb] = await installDepBundles(ctx); + const scr = await ctx.installBundle(scrHeaders()); + return [pmb, cmb, scr]; + } +}); diff --git a/packages/@pandino/scr/src/index.ts b/packages/@pandino/scr/src/index.ts new file mode 100644 index 0000000..987498f --- /dev/null +++ b/packages/@pandino/scr/src/index.ts @@ -0,0 +1,3 @@ +import { Activator } from './Activator'; + +export default Activator; diff --git a/packages/@pandino/scr/tsconfig.json b/packages/@pandino/scr/tsconfig.json new file mode 100644 index 0000000..f31f954 --- /dev/null +++ b/packages/@pandino/scr/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "rootDir": "./src", + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true, + "types": ["vite/client", "node"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "declaration": true, + "declarationDir": "types", + "emitDeclarationOnly": true + }, + "include": ["src"], + "exclude": ["**/*.test.ts", "node_modules", "test/**", ".history/**"] +} diff --git a/packages/@pandino/scr/vite.config.ts b/packages/@pandino/scr/vite.config.ts new file mode 100644 index 0000000..89d6314 --- /dev/null +++ b/packages/@pandino/scr/vite.config.ts @@ -0,0 +1,40 @@ +import { resolve } from "node:path"; +import { defineConfig } from "vite"; +import generateManifest from '@pandino/rollup-plugin-generate-manifest'; +// @ts-ignore +import packageJson from "./package.json"; + +const getPackageName = () => { + return packageJson.name; +}; + +const getPackageNameCamelCase = () => { + try { + return getPackageName().replace(/@/g, '').replace(/[\/\-]/g, '_').toUpperCase(); + } catch (err) { + throw new Error("Name property in package.json is missing."); + } +}; + +const fileName = { + es: `${getPackageName()}.mjs`, + cjs: `${getPackageName()}.cjs`, + umd: `${getPackageName()}.umd.js`, +}; + +const formats = Object.keys(fileName) as Array; + +export default defineConfig(({ mode }) => ({ + base: "./", + build: { + lib: { + entry: resolve(__dirname, "src/index.ts"), + name: getPackageNameCamelCase(), + formats, + fileName: (format) => fileName[format], + }, + }, + plugins: [ + generateManifest(), + ], +})); diff --git a/packages/@pandino/webpack-plugin-generate-manifest/src/index.js b/packages/@pandino/webpack-plugin-generate-manifest/src/index.js index ff86037..8eddc91 100644 --- a/packages/@pandino/webpack-plugin-generate-manifest/src/index.js +++ b/packages/@pandino/webpack-plugin-generate-manifest/src/index.js @@ -12,9 +12,7 @@ class GenerateManifestPlugin { const targetPath = compilation.options.output.path; for (const chunk of Array.from(chunks)) { - const targetFile = Array.from(chunk.files).find( - (f) => f.endsWith('js') || f.endsWith('cjs') || f.endsWith('mjs'), - ); + const targetFile = Array.from(chunk.files).find((f) => f.endsWith('js') || f.endsWith('cjs') || f.endsWith('mjs')); generateManifest(this.options, path.resolve(path.join(targetPath, targetFile))); } }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64effd1..d1e6ba7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^2.27.9 version: 2.27.9 '@types/node': - specifier: ^20.17.0 - version: 20.17.0 + specifier: ^20.17.1 + version: 20.17.1 prettier: specifier: ^3.3.3 version: 3.3.3 @@ -40,7 +40,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/bundle-installer-nodejs: devDependencies: @@ -61,7 +61,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/configuration-management: devDependencies: @@ -80,6 +80,9 @@ importers: '@pandino/persistence-manager-api': specifier: workspace:^ version: link:../persistence-manager-api + '@pandino/persistence-manager-memory': + specifier: workspace:^ + version: link:../persistence-manager-memory '@pandino/rollup-plugin-generate-manifest': specifier: workspace:^ version: link:../rollup-plugin-generate-manifest @@ -94,10 +97,10 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) vitest: specifier: ^2.1.3 - version: 2.1.3(@types/node@22.7.9)(terser@5.36.0) + version: 2.1.3(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/configuration-management-api: devDependencies: @@ -118,7 +121,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/event-admin: devDependencies: @@ -148,10 +151,10 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) vitest: specifier: ^2.1.3 - version: 2.1.3(@types/node@22.7.9)(terser@5.36.0) + version: 2.1.3(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/event-api: devDependencies: @@ -169,7 +172,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/filters: devDependencies: @@ -184,10 +187,10 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) vitest: specifier: ^2.1.3 - version: 2.1.3(@types/node@22.7.9)(terser@5.36.0) + version: 2.1.3(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/loader-configuration-dom: devDependencies: @@ -208,7 +211,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/loader-configuration-nodejs: devDependencies: @@ -250,7 +253,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/manifest-generator: {} @@ -274,10 +277,10 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) vitest: specifier: ^2.1.3 - version: 2.1.3(@types/node@22.7.9)(terser@5.36.0) + version: 2.1.3(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/pandino-api: devDependencies: @@ -292,7 +295,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/persistence-manager-api: devDependencies: @@ -310,7 +313,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/persistence-manager-localstorage: devDependencies: @@ -334,10 +337,10 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) vitest: specifier: ^2.1.3 - version: 2.1.3(@types/node@22.7.9)(terser@5.36.0) + version: 2.1.3(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/persistence-manager-memory: devDependencies: @@ -361,10 +364,10 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) vitest: specifier: ^2.1.3 - version: 2.1.3(@types/node@22.7.9)(terser@5.36.0) + version: 2.1.3(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/react-hooks: devDependencies: @@ -388,7 +391,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/rollup-plugin-generate-manifest: dependencies: @@ -396,6 +399,66 @@ importers: specifier: workspace:^ version: link:../manifest-generator + packages/@pandino/scr: + devDependencies: + '@pandino/configuration-management': + specifier: workspace:^ + version: link:../configuration-management + '@pandino/configuration-management-api': + specifier: workspace:^ + version: link:../configuration-management-api + '@pandino/pandino': + specifier: workspace:^ + version: link:../pandino + '@pandino/pandino-api': + specifier: workspace:^ + version: link:../pandino-api + '@pandino/persistence-manager-api': + specifier: workspace:^ + version: link:../persistence-manager-api + '@pandino/persistence-manager-memory': + specifier: workspace:^ + version: link:../persistence-manager-memory + '@pandino/rollup-plugin-generate-manifest': + specifier: workspace:^ + version: link:../rollup-plugin-generate-manifest + '@pandino/scr-api': + specifier: workspace:^ + version: link:../scr-api + rimraf: + specifier: ^5.0.10 + version: 5.0.10 + typescript: + specifier: ^5.6.3 + version: 5.6.3 + vite: + specifier: ^5.4.10 + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) + vitest: + specifier: ^2.1.3 + version: 2.1.3(@types/node@22.8.0)(terser@5.36.0) + + packages/@pandino/scr-api: + devDependencies: + '@pandino/pandino-api': + specifier: workspace:^ + version: link:../pandino-api + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 + rimraf: + specifier: ^5.0.10 + version: 5.0.10 + typescript: + specifier: ^5.6.3 + version: 5.6.3 + vite: + specifier: ^5.4.10 + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) + vitest: + specifier: ^2.1.3 + version: 2.1.3(@types/node@22.8.0)(terser@5.36.0) + packages/@pandino/umd-activator-resolver-dom: devDependencies: '@pandino/pandino-api': @@ -412,7 +475,7 @@ importers: version: 5.6.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + version: 5.4.10(@types/node@22.8.0)(terser@5.36.0) packages/@pandino/webpack-plugin-generate-manifest: dependencies: @@ -429,8 +492,8 @@ importers: packages: - '@babel/runtime@7.25.9': - resolution: {integrity: sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==} + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} '@changesets/apply-release-plan@7.0.5': @@ -769,8 +832,11 @@ packages: '@types/node@20.17.0': resolution: {integrity: sha512-a7zRo0f0eLo9K5X9Wp5cAqTUNGzuFLDG2R7C4HY2BhcMAsxgSPuRvAC1ZB6QkuUQXf0YZAgfOX2ZyrBa2n4nHQ==} - '@types/node@22.7.9': - resolution: {integrity: sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==} + '@types/node@20.17.1': + resolution: {integrity: sha512-j2VlPv1NnwPJbaCNv69FO/1z4lId0QmGvpT41YxitRtWlg96g/j8qcv2RKsLKe2F6OJgyXhupN1Xo17b2m139Q==} + + '@types/node@22.8.0': + resolution: {integrity: sha512-84rafSBHC/z1i1E3p0cJwKA+CfYDNSXX9WSZBRopjIzLET8oNt6ht2tei4C7izwDeEiLLfdeSVBv1egOH916hg==} '@types/prop-types@15.7.13': resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} @@ -1464,6 +1530,9 @@ packages: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -1820,7 +1889,7 @@ packages: snapshots: - '@babel/runtime@7.25.9': + '@babel/runtime@7.26.0': dependencies: regenerator-runtime: 0.14.1 @@ -2070,14 +2139,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.25.9 + '@babel/runtime': 7.26.0 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.25.9 + '@babel/runtime': 7.26.0 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -2157,7 +2226,11 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/node@22.7.9': + '@types/node@20.17.1': + dependencies: + undici-types: 6.19.8 + + '@types/node@22.8.0': dependencies: undici-types: 6.19.8 @@ -2175,13 +2248,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@22.7.9)(terser@5.36.0))': + '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@22.8.0)(terser@5.36.0))': dependencies: '@vitest/spy': 2.1.3 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: - vite: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + vite: 5.4.10(@types/node@22.8.0)(terser@5.36.0) '@vitest/pretty-format@2.1.3': dependencies: @@ -2653,7 +2726,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.7.9 + '@types/node': 22.8.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -2816,6 +2889,8 @@ snapshots: dependencies: resolve: 1.22.8 + reflect-metadata@0.2.2: {} + regenerator-runtime@0.14.1: {} require-directory@2.1.1: {} @@ -3006,12 +3081,12 @@ snapshots: dependencies: punycode: 2.3.1 - vite-node@2.1.3(@types/node@22.7.9)(terser@5.36.0): + vite-node@2.1.3(@types/node@22.8.0)(terser@5.36.0): dependencies: cac: 6.7.14 debug: 4.3.7 pathe: 1.1.2 - vite: 5.4.10(@types/node@22.7.9)(terser@5.36.0) + vite: 5.4.10(@types/node@22.8.0)(terser@5.36.0) transitivePeerDependencies: - '@types/node' - less @@ -3033,20 +3108,20 @@ snapshots: fsevents: 2.3.3 terser: 5.36.0 - vite@5.4.10(@types/node@22.7.9)(terser@5.36.0): + vite@5.4.10(@types/node@22.8.0)(terser@5.36.0): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.24.0 optionalDependencies: - '@types/node': 22.7.9 + '@types/node': 22.8.0 fsevents: 2.3.3 terser: 5.36.0 - vitest@2.1.3(@types/node@22.7.9)(terser@5.36.0): + vitest@2.1.3(@types/node@22.8.0)(terser@5.36.0): dependencies: '@vitest/expect': 2.1.3 - '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@22.7.9)(terser@5.36.0)) + '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@22.8.0)(terser@5.36.0)) '@vitest/pretty-format': 2.1.3 '@vitest/runner': 2.1.3 '@vitest/snapshot': 2.1.3 @@ -3061,11 +3136,11 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.10(@types/node@22.7.9)(terser@5.36.0) - vite-node: 2.1.3(@types/node@22.7.9)(terser@5.36.0) + vite: 5.4.10(@types/node@22.8.0)(terser@5.36.0) + vite-node: 2.1.3(@types/node@22.8.0)(terser@5.36.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.7.9 + '@types/node': 22.8.0 transitivePeerDependencies: - less - lightningcss