From 778e98134c7f1ff174ac834e18574a35fc2f8367 Mon Sep 17 00:00:00 2001 From: maaz519 Date: Sat, 18 Jul 2026 01:28:42 +0530 Subject: [PATCH] feat: export messaging-ui public API and enforce transport boundary Co-Authored-By: Claude Opus 4.8 (1M context) --- .../iios-messaging-ui/src/boundary.test.ts | 41 +++++++++++++++++++ packages/iios-messaging-ui/src/index.ts | 25 ++++++++++- packages/iios-messaging-ui/tsup.config.ts | 2 +- scripts/check-import-boundary.mjs | 4 ++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 packages/iios-messaging-ui/src/boundary.test.ts diff --git a/packages/iios-messaging-ui/src/boundary.test.ts b/packages/iios-messaging-ui/src/boundary.test.ts new file mode 100644 index 0000000..4efa2de --- /dev/null +++ b/packages/iios-messaging-ui/src/boundary.test.ts @@ -0,0 +1,41 @@ +// @vitest-environment node +import { describe, it, expect } from 'vitest'; +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { join } from 'node:path'; + +// This package is ESM ("type": "module") — __dirname does not exist here. +const SRC = fileURLToPath(new URL('.', import.meta.url)); +const ADAPTERS = join(SRC, 'adapters'); + +function tsFilesIn(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) out.push(...tsFilesIn(full)); + else if (/\.tsx?$/.test(full)) out.push(full); + } + return out; +} + +// The whole premise of this package: core renders, adapters transport. If core ever +// imports a transport, an app on a different backend pays for socket code it cannot +// use — which is exactly how iios-message-web welded itself to MessageSocket. +describe('transport boundary', () => { + const coreFiles = tsFilesIn(SRC).filter((f) => !f.startsWith(ADAPTERS) && !/\.test\.tsx?$/.test(f)); + + it('has core files to check', () => { + expect(coreFiles.length).toBeGreaterThan(0); + }); + + it('core never imports a transport package', () => { + const offenders: string[] = []; + for (const file of coreFiles) { + const content = readFileSync(file, 'utf8'); + if (/@insignia\/iios-kernel-client|socket\.io|@abe-kap\/appshell-sdk/.test(content)) { + offenders.push(file.replace(SRC, 'src')); + } + } + expect(offenders).toEqual([]); + }); +}); diff --git a/packages/iios-messaging-ui/src/index.ts b/packages/iios-messaging-ui/src/index.ts index cb0ff5c..802d60d 100644 --- a/packages/iios-messaging-ui/src/index.ts +++ b/packages/iios-messaging-ui/src/index.ts @@ -1 +1,24 @@ -export {}; +// Public API. Components land in Plan 2; adapters/kernel in Plan 3. +// +// `runAdapterConformance` is deliberately NOT exported here — it imports vitest and +// ships from the './conformance' subpath so consumers never pull a test runner into +// their production bundle. +export { MessagingProvider, useAdapter } from './provider'; +export { useConversations } from './hooks/use-conversations'; +export { useMessages } from './hooks/use-messages'; +export { isOwnMessage } from './types'; + +export type { MessagingAdapter } from './adapter'; +export type { ConversationsState } from './hooks/use-conversations'; +export type { MessagesState, UiMessage } from './hooks/use-messages'; +export type { + Attachment, + Conversation, + Membership, + Message, + MessageEvent, + Person, + Reaction, + SendOpts, + Unsubscribe, +} from './types'; diff --git a/packages/iios-messaging-ui/tsup.config.ts b/packages/iios-messaging-ui/tsup.config.ts index b444403..3baff55 100644 --- a/packages/iios-messaging-ui/tsup.config.ts +++ b/packages/iios-messaging-ui/tsup.config.ts @@ -6,7 +6,7 @@ export default defineConfig({ // entry, never reachable from `index`, because it imports vitest, and bundling // that into the main barrel would drag a test runner into every consumer's // production build. - entry: ['src/index.ts'], + entry: ['src/index.ts', 'src/adapters/mock.ts', 'src/conformance.ts'], format: ['esm'], dts: true, clean: true, diff --git a/scripts/check-import-boundary.mjs b/scripts/check-import-boundary.mjs index 8294bc1..bfde152 100644 --- a/scripts/check-import-boundary.mjs +++ b/scripts/check-import-boundary.mjs @@ -20,6 +20,10 @@ const ALLOWED = { '@insignia/iios-adapter-sdk': ['@insignia/iios-contracts'], '@insignia/iios-service': ['@insignia/iios-contracts', '@insignia/iios-testkit', '@insignia/iios-adapter-sdk'], '@insignia/iios-message-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'], + // Core is transport-free; only ./adapters/kernel may import the kernel client. + // This map is package-level and cannot express that subpath rule — the finer + // constraint is enforced by packages/iios-messaging-ui/src/boundary.test.ts. + '@insignia/iios-messaging-ui': ['@insignia/iios-kernel-client'], '@insignia/iios-inbox-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'], '@insignia/iios-community-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'], '@insignia/iios-ai-web': ['@insignia/iios-contracts', '@insignia/iios-kernel-client'],