diff --git a/.changeset/v11-remove-iuiservice.md b/.changeset/v11-remove-iuiservice.md new file mode 100644 index 0000000000..51f1ea6dc3 --- /dev/null +++ b/.changeset/v11-remove-iuiservice.md @@ -0,0 +1,17 @@ +--- +"@objectstack/spec": major +"@objectstack/plugin-dev": major +--- + +Remove the deprecated `IUIService` contract (use `IMetadataService`) — 11.0. + +`IUIService` (spec `contracts/ui-service.ts`) was superseded by `IMetadataService` +(views/dashboards are metadata: `metadata.get('view', …)` / `register(…)`). This +removes the dead interface and its dev stub: + +- spec: delete `contracts/ui-service.ts` + its barrel export. +- plugin-dev: drop the bespoke `ui` dev stub (`createUIStub`). `'ui'` remains a + `CoreServiceName`, so dev mode still registers a generic stub for it via the + fallback path; only the obsolete view/dashboard methods are gone. + +Use `IMetadataService` for view/dashboard CRUD. diff --git a/packages/plugins/plugin-dev/src/dev-plugin.test.ts b/packages/plugins/plugin-dev/src/dev-plugin.test.ts index 6fd4b410d4..f9219c6e07 100644 --- a/packages/plugins/plugin-dev/src/dev-plugin.test.ts +++ b/packages/plugins/plugin-dev/src/dev-plugin.test.ts @@ -176,13 +176,6 @@ describe('DevPlugin', () => { expect(i18n.t('missing', 'en')).toBe('missing'); expect(Array.isArray(i18n.getLocales())).toBe(true); - // ── Verify IUIService contract ── - const ui = registeredServices.get('ui'); - ui.registerView('test_view', { name: 'test_view', object: 'account' }); - expect(ui.getView('test_view')).toBeDefined(); - expect(Array.isArray(ui.listViews())).toBe(true); - expect(ui.listViews('account')).toHaveLength(1); - // ── Verify IWorkflowService contract ── const workflow = registeredServices.get('workflow'); const transResult = await workflow.transition({ recordId: 'r1', object: 'order', targetState: 'approved' }); diff --git a/packages/plugins/plugin-dev/src/dev-plugin.ts b/packages/plugins/plugin-dev/src/dev-plugin.ts index ea93e70bad..275b1abd26 100644 --- a/packages/plugins/plugin-dev/src/dev-plugin.ts +++ b/packages/plugins/plugin-dev/src/dev-plugin.ts @@ -160,25 +160,6 @@ function createI18nStub() { }; } -/** IUIService — delegates to IMetadataService when available, falls back to in-memory Map */ -function createUIStub() { - const views = new Map(); - const dashboards = new Map(); - return { - _dev: true, _serviceName: 'ui', - _deprecated: 'Use IMetadataService instead. This stub will be removed in v4.0.0.', - getView(name: string) { return views.get(name); }, - listViews(object?: string) { - const all = [...views.values()]; - return object ? all.filter(v => v.object === object) : all; - }, - getDashboard(name: string) { return dashboards.get(name); }, - listDashboards() { return [...dashboards.values()]; }, - registerView(name: string, definition: unknown) { views.set(name, definition); }, - registerDashboard(name: string, definition: unknown) { dashboards.set(name, definition); }, - }; -} - /** IWorkflowService — in-memory workflow state stub */ function createWorkflowStub() { const states = new Map(); // recordKey → currentState @@ -299,7 +280,6 @@ const DEV_STUB_FACTORIES: Record Record> = { 'notification': createNotificationStub, 'ai': createAIStub, 'i18n': createI18nStub, - 'ui': createUIStub, 'workflow': createWorkflowStub, 'metadata': createMetadataStub, 'data': createDataStub, diff --git a/packages/spec/api-surface.json b/packages/spec/api-surface.json index 20d4c0b300..55b80a2615 100644 --- a/packages/spec/api-surface.json +++ b/packages/spec/api-surface.json @@ -3498,7 +3498,6 @@ "ITeamGraphService (interface)", "ITenantRouter (interface)", "ITypedEventEmitter (interface)", - "IUIService (interface)", "IWorkflowService (interface)", "ImportObjectOpts (interface)", "ImportObjectResult (interface)", diff --git a/packages/spec/src/contracts/index.ts b/packages/spec/src/contracts/index.ts index 0448fc76cf..eaffbe30c9 100644 --- a/packages/spec/src/contracts/index.ts +++ b/packages/spec/src/contracts/index.ts @@ -31,7 +31,6 @@ export * from './job-service.js'; export * from './ai-service.js'; export * from './llm-adapter.js'; export * from './i18n-service.js'; -export * from './ui-service.js'; export * from './workflow-service.js'; export * from './feed-service.js'; export * from './export-service.js'; diff --git a/packages/spec/src/contracts/ui-service.test.ts b/packages/spec/src/contracts/ui-service.test.ts deleted file mode 100644 index caecb988b8..0000000000 --- a/packages/spec/src/contracts/ui-service.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import type { IUIService } from './ui-service'; - -describe('UI Service Contract', () => { - it('should allow a minimal IUIService implementation with required methods', () => { - const service: IUIService = { - getView: (_name) => undefined, - listViews: (_object?) => [], - }; - - expect(typeof service.getView).toBe('function'); - expect(typeof service.listViews).toBe('function'); - }); - - it('should allow a full implementation with optional methods', () => { - const service: IUIService = { - getView: () => undefined, - listViews: () => [], - getDashboard: (_name) => undefined, - listDashboards: () => [], - registerView: (_name, _definition) => {}, - registerDashboard: (_name, _definition) => {}, - }; - - expect(service.getDashboard).toBeDefined(); - expect(service.listDashboards).toBeDefined(); - expect(service.registerView).toBeDefined(); - expect(service.registerDashboard).toBeDefined(); - }); - - it('should register and retrieve views', () => { - const views = new Map(); - - const service: IUIService = { - getView: (name) => views.get(name), - listViews: (object?) => { - const all = Array.from(views.values()); - if (object) return all.filter((v: any) => v.object === object); - return all; - }, - registerView: (name, definition) => { views.set(name, definition); }, - }; - - const viewDef = { name: 'account_list', object: 'account', type: 'grid', columns: ['name', 'email'] }; - service.registerView!('account_list', viewDef); - - expect(service.getView('account_list')).toEqual(viewDef); - expect(service.listViews()).toHaveLength(1); - expect(service.listViews('account')).toHaveLength(1); - expect(service.listViews('contact')).toHaveLength(0); - }); - - it('should manage dashboards', () => { - const dashboards = new Map(); - - const service: IUIService = { - getView: () => undefined, - listViews: () => [], - getDashboard: (name) => dashboards.get(name), - listDashboards: () => Array.from(dashboards.values()), - registerDashboard: (name, definition) => { dashboards.set(name, definition); }, - }; - - service.registerDashboard!('sales_overview', { - name: 'sales_overview', - label: 'Sales Overview', - widgets: [{ type: 'chart', title: 'Revenue' }], - }); - - expect(service.getDashboard!('sales_overview')).toBeDefined(); - expect(service.listDashboards!()).toHaveLength(1); - expect(service.getDashboard!('missing')).toBeUndefined(); - }); - - it('should return undefined for non-existent views', () => { - const service: IUIService = { - getView: () => undefined, - listViews: () => [], - }; - - expect(service.getView('nonexistent')).toBeUndefined(); - }); -}); diff --git a/packages/spec/src/contracts/ui-service.ts b/packages/spec/src/contracts/ui-service.ts deleted file mode 100644 index 87a31a3204..0000000000 --- a/packages/spec/src/contracts/ui-service.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * IUIService - UI Metadata Service Contract - * - * Defines the interface for managing UI metadata (views, dashboards, layouts) - * in ObjectStack. Concrete implementations (database-backed, in-memory, etc.) - * should implement this interface. - * - * Follows Dependency Inversion Principle - plugins depend on this interface, - * not on concrete UI metadata storage implementations. - * - * Aligned with CoreServiceName 'ui' in core-services.zod.ts. - */ - -/** - * @deprecated Use IMetadataService instead. - * - getView(name) → metadata.get('view', name) - * - listViews(object?) → metadata.listViews(object?) - * - registerView(name, def) → metadata.register('view', name, def) - * - getDashboard(name) → metadata.get('dashboard', name) - * - listDashboards() → metadata.listDashboards() - * - registerDashboard(name, def) → metadata.register('dashboard', name, def) - * - * IMetadataService provides additional capabilities: - * - Three-layer overlay (system → platform → user) via getEffective() - * - User-scoped customization with owner field - * - Auth-context-aware resolution via getEffective(type, name, { userId, roles }) - * - Database persistence via DatabaseLoader - * - Watch/subscribe for real-time updates - * - Import/export, validation, dependency tracking - * - * Will be removed in v4.0.0. - */ -export interface IUIService { - /** - * Get a view definition by name - * @param name - View name (snake_case) - * @returns View definition, or undefined if not found - */ - getView(name: string): unknown | undefined; - - /** - * List view definitions, optionally filtered by object - * @param object - Optional object name to filter views for - * @returns Array of view definitions - */ - listViews(object?: string): unknown[]; - - /** - * Get a dashboard definition by name - * @param name - Dashboard name (snake_case) - * @returns Dashboard definition, or undefined if not found - */ - getDashboard?(name: string): unknown | undefined; - - /** - * List all dashboard definitions - * @returns Array of dashboard definitions - */ - listDashboards?(): unknown[]; - - /** - * Register a view definition - * @param name - View name (snake_case) - * @param definition - View definition object - */ - registerView?(name: string, definition: unknown): void; - - /** - * Register a dashboard definition - * @param name - Dashboard name (snake_case) - * @param definition - Dashboard definition object - */ - registerDashboard?(name: string, definition: unknown): void; -}