@objectstack/plugin-dev 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dev-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext } from '@objectstack/core';\n\n/**\n * All 17 core kernel service names as defined in CoreServiceName.\n * @see packages/spec/src/system/core-services.zod.ts\n */\nconst CORE_SERVICE_NAMES = [\n 'metadata', 'data', 'auth',\n 'file-storage', 'search', 'cache', 'queue',\n 'automation', 'graphql', 'analytics', 'realtime',\n 'job', 'notification', 'ai', 'i18n', 'ui', 'workflow',\n] as const;\n\n/**\n * Security sub-services registered by the SecurityPlugin.\n */\nconst SECURITY_SERVICE_NAMES = [\n 'security.permissions', 'security.rls', 'security.fieldMasker',\n] as const;\n\n/**\n * Contract-compliant dev stub implementations.\n *\n * Each stub implements the interface defined in `packages/spec/src/contracts/`\n * (e.g. ICacheService, IQueueService, IAutomationService, …) so that\n * downstream code calling these services in dev mode receives the correct\n * return types — not just `undefined`.\n *\n * Where an interface method is optional (marked with `?`), the stub only\n * implements the required methods plus any optional ones that have\n * a trivially useful implementation.\n */\n\n/** ICacheService — in-memory Map-backed stub */\nfunction createCacheStub() {\n const store = new Map<string, { value: unknown; expires?: number }>();\n let hits = 0;\n let misses = 0;\n return {\n _dev: true, _serviceName: 'cache',\n async get<T = unknown>(key: string): Promise<T | undefined> {\n const entry = store.get(key);\n if (!entry || (entry.expires && Date.now() > entry.expires)) {\n store.delete(key);\n misses++;\n return undefined;\n }\n hits++;\n return entry.value as T;\n },\n async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {\n store.set(key, { value, expires: ttl ? Date.now() + ttl * 1000 : undefined });\n },\n async delete(key: string): Promise<boolean> { return store.delete(key); },\n async has(key: string): Promise<boolean> { return store.has(key); },\n async clear(): Promise<void> { store.clear(); },\n async stats() { return { hits, misses, keyCount: store.size }; },\n };\n}\n\n/** IQueueService — in-memory publish/subscribe stub */\nfunction createQueueStub() {\n const handlers = new Map<string, Function[]>();\n let msgId = 0;\n return {\n _dev: true, _serviceName: 'queue',\n async publish<T = unknown>(queue: string, data: T): Promise<string> {\n const id = `dev-msg-${++msgId}`;\n const fns = handlers.get(queue) ?? [];\n for (const fn of fns) fn({ id, data, attempts: 1, timestamp: Date.now() });\n return id;\n },\n async subscribe(queue: string, handler: (msg: any) => Promise<void>): Promise<void> {\n handlers.set(queue, [...(handlers.get(queue) ?? []), handler]);\n },\n async unsubscribe(queue: string): Promise<void> { handlers.delete(queue); },\n async getQueueSize(): Promise<number> { return 0; },\n async purge(queue: string): Promise<void> { handlers.delete(queue); },\n };\n}\n\n/** IJobService — no-op job scheduler stub */\nfunction createJobStub() {\n const jobs = new Map<string, any>();\n return {\n _dev: true, _serviceName: 'job',\n async schedule(name: string, schedule: any, handler: any): Promise<void> { jobs.set(name, { schedule, handler }); },\n async cancel(name: string): Promise<void> { jobs.delete(name); },\n async trigger(name: string, data?: unknown): Promise<void> {\n const job = jobs.get(name);\n if (job?.handler) await job.handler({ jobId: name, data });\n },\n async getExecutions(): Promise<any[]> { return []; },\n async listJobs(): Promise<string[]> { return [...jobs.keys()]; },\n };\n}\n\n/** IStorageService — in-memory file storage stub */\nfunction createStorageStub() {\n const files = new Map<string, { data: Buffer; meta: any }>();\n return {\n _dev: true, _serviceName: 'file-storage',\n async upload(key: string, data: any, options?: any): Promise<void> {\n files.set(key, { data: Buffer.from(data), meta: { contentType: options?.contentType, metadata: options?.metadata } });\n },\n async download(key: string): Promise<Buffer> { return files.get(key)?.data ?? Buffer.alloc(0); },\n async delete(key: string): Promise<void> { files.delete(key); },\n async exists(key: string): Promise<boolean> { return files.has(key); },\n async getInfo(key: string) {\n const f = files.get(key);\n return { key, size: f?.data?.length ?? 0, contentType: f?.meta?.contentType, lastModified: new Date(), metadata: f?.meta?.metadata };\n },\n async list(prefix: string) {\n return [...files.entries()].filter(([k]) => k.startsWith(prefix)).map(([key, f]) =>\n ({ key, size: f.data.length, contentType: f.meta?.contentType, lastModified: new Date() }));\n },\n };\n}\n\n/** ISearchService — in-memory full-text search stub */\nfunction createSearchStub() {\n const indexes = new Map<string, Map<string, Record<string, unknown>>>();\n return {\n _dev: true, _serviceName: 'search',\n async index(object: string, id: string, document: Record<string, unknown>): Promise<void> {\n if (!indexes.has(object)) indexes.set(object, new Map());\n indexes.get(object)!.set(id, document);\n },\n async remove(object: string, id: string): Promise<void> { indexes.get(object)?.delete(id); },\n async search(object: string, query: string) {\n const docs = indexes.get(object) ?? new Map();\n const q = query.toLowerCase();\n const hits = [...docs.entries()]\n .filter(([, doc]) => JSON.stringify(doc).toLowerCase().includes(q))\n .map(([id, doc]) => ({ id, score: 1, document: doc }));\n return { hits, totalHits: hits.length, processingTimeMs: 0 };\n },\n async bulkIndex(object: string, documents: Array<{ id: string; document: Record<string, unknown> }>): Promise<void> {\n if (!indexes.has(object)) indexes.set(object, new Map());\n for (const d of documents) {\n indexes.get(object)!.set(d.id, d.document);\n }\n },\n async deleteIndex(object: string): Promise<void> { indexes.delete(object); },\n };\n}\n\n/** IAutomationService — no-op flow execution stub */\nfunction createAutomationStub() {\n const flows = new Map<string, unknown>();\n return {\n _dev: true, _serviceName: 'automation',\n async execute(_flowName: string) { return { success: true, output: undefined, durationMs: 0 }; },\n async listFlows(): Promise<string[]> { return [...flows.keys()]; },\n registerFlow(name: string, definition: unknown) { flows.set(name, definition); },\n unregisterFlow(name: string) { flows.delete(name); },\n };\n}\n\n/** IGraphQLService — dev stub returning empty data */\nfunction createGraphQLStub() {\n return {\n _dev: true, _serviceName: 'graphql',\n async execute() { return { data: null, errors: [{ message: 'GraphQL not available in dev stub mode' }] }; },\n getSchema() { return 'type Query { _dev: Boolean }'; },\n };\n}\n\n/** IAnalyticsService — dev stub returning empty results */\nfunction createAnalyticsStub() {\n return {\n _dev: true, _serviceName: 'analytics',\n async query() { return { rows: [], fields: [] }; },\n async getMeta() { return []; },\n async generateSql() { return { sql: '', params: [] }; },\n };\n}\n\n/** IRealtimeService — in-memory pub/sub stub */\nfunction createRealtimeStub() {\n const subs = new Map<string, Function>();\n let subId = 0;\n return {\n _dev: true, _serviceName: 'realtime',\n async publish(event: any): Promise<void> { for (const fn of subs.values()) fn(event); },\n async subscribe(_channel: string, handler: Function): Promise<string> {\n const id = `dev-sub-${++subId}`; subs.set(id, handler); return id;\n },\n async unsubscribe(subscriptionId: string): Promise<void> { subs.delete(subscriptionId); },\n };\n}\n\n/** INotificationService — in-memory log stub */\nfunction createNotificationStub() {\n const sent: any[] = [];\n return {\n _dev: true, _serviceName: 'notification',\n async send(message: any) { sent.push(message); return { success: true, messageId: `dev-notif-${sent.length}` }; },\n async sendBatch(messages: any[]) { return messages.map(m => { sent.push(m); return { success: true, messageId: `dev-notif-${sent.length}` }; }); },\n getChannels() { return ['email', 'in-app'] as const; },\n };\n}\n\n/** IAIService — dev stub returning placeholder responses */\nfunction createAIStub() {\n return {\n _dev: true, _serviceName: 'ai',\n async chat() { return { content: '[dev-stub] AI not available in development mode', model: 'dev-stub' }; },\n async complete() { return { content: '[dev-stub] AI not available in development mode', model: 'dev-stub' }; },\n async embed() { return [[0]]; },\n async listModels() { return ['dev-stub']; },\n };\n}\n\n/** II18nService — in-memory translation stub */\nfunction createI18nStub() {\n const translations = new Map<string, Record<string, unknown>>();\n let defaultLocale = 'en';\n return {\n _dev: true, _serviceName: 'i18n',\n t(key: string, locale: string, params?: Record<string, unknown>): string {\n const t = translations.get(locale);\n const val = t?.[key];\n if (typeof val === 'string') {\n return params ? val.replace(/\\{\\{(\\w+)\\}\\}/g, (_, k) => String(params[k] ?? `{{${k}}}`)) : val;\n }\n return key;\n },\n getTranslations(locale: string): Record<string, unknown> { return translations.get(locale) ?? {}; },\n loadTranslations(locale: string, data: Record<string, unknown>) { translations.set(locale, { ...translations.get(locale), ...data }); },\n getLocales() { return [...translations.keys()]; },\n getDefaultLocale() { return defaultLocale; },\n setDefaultLocale(locale: string) { defaultLocale = locale; },\n };\n}\n\n/** IUIService — in-memory UI metadata stub */\nfunction createUIStub() {\n const views = new Map<string, any>();\n const dashboards = new Map<string, any>();\n return {\n _dev: true, _serviceName: 'ui',\n getView(name: string) { return views.get(name); },\n listViews(object?: string) {\n const all = [...views.values()];\n return object ? all.filter(v => v.object === object) : all;\n },\n getDashboard(name: string) { return dashboards.get(name); },\n listDashboards() { return [...dashboards.values()]; },\n registerView(name: string, definition: unknown) { views.set(name, definition); },\n registerDashboard(name: string, definition: unknown) { dashboards.set(name, definition); },\n };\n}\n\n/** IWorkflowService — in-memory workflow state stub */\nfunction createWorkflowStub() {\n const states = new Map<string, string>(); // recordKey → currentState\n const key = (obj: string, id: string) => `${obj}:${id}`;\n return {\n _dev: true, _serviceName: 'workflow',\n async transition(t: any) {\n states.set(key(t.object, t.recordId), t.targetState);\n return { success: true, currentState: t.targetState };\n },\n async getStatus(object: string, recordId: string) {\n return { recordId, object, currentState: states.get(key(object, recordId)) ?? 'draft', availableTransitions: [] };\n },\n async getHistory() { return []; },\n };\n}\n\n/** IMetadataService — in-memory metadata registry stub (fallback) */\nfunction createMetadataStub() {\n const store = new Map<string, Map<string, unknown>>(); // type → (name → def)\n return {\n _dev: true, _serviceName: 'metadata',\n register(type: string, definition: any) {\n if (!store.has(type)) store.set(type, new Map());\n store.get(type)!.set(definition.name ?? '', definition);\n },\n get(type: string, name: string) { return store.get(type)?.get(name); },\n list(type: string) { return [...(store.get(type)?.values() ?? [])]; },\n unregister(type: string, name: string) { store.get(type)?.delete(name); },\n getObject(name: string) { return store.get('object')?.get(name); },\n listObjects() { return [...(store.get('object')?.values() ?? [])]; },\n unregisterPackage() {},\n };\n}\n\n/** IAuthService — dev auth stub returning success for all */\nfunction createAuthStub() {\n return {\n _dev: true, _serviceName: 'auth',\n async handleRequest() { return new Response(JSON.stringify({ success: true }), { status: 200 }); },\n async verify() { return { success: true, user: { id: 'dev-admin', email: 'admin@dev.local', name: 'Admin', roles: ['admin'] } }; },\n async logout() {},\n async getCurrentUser() { return { id: 'dev-admin', email: 'admin@dev.local', name: 'Admin', roles: ['admin'] }; },\n };\n}\n\n/** IDataEngine — minimal no-op data stub (fallback) */\nfunction createDataStub() {\n return {\n _dev: true, _serviceName: 'data',\n async find() { return []; },\n async findOne() { return undefined; },\n async insert(_obj: string, params: any) { return { id: `dev-${Date.now()}`, ...params?.data }; },\n async update(_obj: string, _id: string, params: any) { return params?.data ?? {}; },\n async delete() { return true; },\n async count() { return 0; },\n async aggregate() { return []; },\n };\n}\n\n/** Security sub-service stubs (PermissionEvaluator, RLSCompiler, FieldMasker) */\nfunction createSecurityPermissionsStub() {\n return {\n _dev: true, _serviceName: 'security.permissions',\n resolvePermissionSets() { return []; },\n checkObjectPermission() { return true; },\n getFieldPermissions() { return {}; },\n };\n}\nfunction createSecurityRLSStub() {\n return {\n _dev: true, _serviceName: 'security.rls',\n compileFilter() { return null; },\n getApplicablePolicies() { return []; },\n };\n}\nfunction createSecurityFieldMaskerStub() {\n return {\n _dev: true, _serviceName: 'security.fieldMasker',\n maskResults(results: any) { return results; },\n };\n}\n\n/**\n * Map of service names → contract-compliant stub factory functions.\n * Each factory creates a new instance implementing the protocol interface\n * from `packages/spec/src/contracts/`.\n */\nconst DEV_STUB_FACTORIES: Record<string, () => Record<string, any>> = {\n 'cache': createCacheStub,\n 'queue': createQueueStub,\n 'job': createJobStub,\n 'file-storage': createStorageStub,\n 'search': createSearchStub,\n 'automation': createAutomationStub,\n 'graphql': createGraphQLStub,\n 'analytics': createAnalyticsStub,\n 'realtime': createRealtimeStub,\n 'notification': createNotificationStub,\n 'ai': createAIStub,\n 'i18n': createI18nStub,\n 'ui': createUIStub,\n 'workflow': createWorkflowStub,\n 'metadata': createMetadataStub,\n 'data': createDataStub,\n 'auth': createAuthStub,\n // Security sub-services\n 'security.permissions': createSecurityPermissionsStub,\n 'security.rls': createSecurityRLSStub,\n 'security.fieldMasker': createSecurityFieldMaskerStub,\n};\n\n/**\n * Dev Plugin Options\n *\n * Configuration for the development-mode plugin.\n * All options have sensible defaults — zero-config works out of the box.\n */\nexport interface DevPluginOptions {\n /**\n * Port for the HTTP server.\n * @default 3000\n */\n port?: number;\n\n /**\n * Whether to seed a default admin user for development.\n * Creates `admin@dev.local` / `admin` so devs can skip login.\n * @default true\n */\n seedAdminUser?: boolean;\n\n /**\n * Auth secret for development sessions.\n * @default 'objectstack-dev-secret-DO-NOT-USE-IN-PRODUCTION!!'\n */\n authSecret?: string;\n\n /**\n * Auth base URL.\n * @default 'http://localhost:{port}'\n */\n authBaseUrl?: string;\n\n /**\n * Whether to enable verbose logging.\n * @default true\n */\n verbose?: boolean;\n\n /**\n * Override which services to enable. By default all core services are enabled.\n * Set a service name to `false` to skip it.\n *\n * Available services: 'objectql', 'driver', 'auth', 'server', 'rest',\n * 'dispatcher', 'security', plus any of the 17 CoreServiceName values\n * (e.g. 'cache', 'queue', 'job', 'ui', 'automation', 'workflow', …).\n */\n services?: Partial<Record<string, boolean>>;\n\n /**\n * Additional plugins to load alongside the auto-configured ones.\n * Useful for adding custom project plugins while still getting the dev defaults.\n */\n extraPlugins?: Plugin[];\n\n /**\n * Stack definition to load as a project.\n * When provided, the DevPlugin wraps it in an AppPlugin so that all\n * metadata (objects, views, apps, dashboards, etc.) is registered with\n * the kernel and exposed through the REST/metadata APIs.\n *\n * This is what makes `new DevPlugin({ stack: config })` equivalent to\n * a full `os serve --dev` environment: views can be read, modified, and\n * saved through the API.\n *\n * @example\n * ```ts\n * import config from './objectstack.config';\n * plugins: [new DevPlugin({ stack: config })]\n * ```\n */\n stack?: Record<string, any>;\n}\n\n/**\n * Development Mode Plugin for ObjectStack\n *\n * A convenience plugin that auto-configures the **entire** platform stack\n * for local development, simulating **all 17+ kernel services** so developers\n * can work in a full-featured API environment without external dependencies.\n *\n * Instead of manually wiring:\n *\n * ```ts\n * plugins: [\n * new ObjectQLPlugin(),\n * new DriverPlugin(new InMemoryDriver()),\n * new AuthPlugin({ secret: '...', baseUrl: '...' }),\n * new HonoServerPlugin({ port: 3000 }),\n * createRestApiPlugin(),\n * createDispatcherPlugin(),\n * new SecurityPlugin(),\n * new AppPlugin(config),\n * ]\n * ```\n *\n * You can simply use:\n *\n * ```ts\n * plugins: [new DevPlugin()]\n * ```\n *\n * ## Core services (real implementations)\n *\n * | Service | Package | Description |\n * |--------------|-----------------------------------|-------------------------------------------|\n * | ObjectQL | `@objectstack/objectql` | Data engine (query, CRUD, hooks) |\n * | Driver | `@objectstack/driver-memory` | In-memory database (no DB install) |\n * | Auth | `@objectstack/plugin-auth` | Authentication with dev credentials |\n * | Security | `@objectstack/plugin-security` | RBAC, RLS, field-level masking |\n * | HTTP Server | `@objectstack/plugin-hono-server` | HTTP server on configured port |\n * | REST API | `@objectstack/rest` | Auto-generated CRUD + metadata endpoints |\n * | Dispatcher | `@objectstack/runtime` | Auth, GraphQL, analytics, packages, etc. |\n * | App/Metadata | `@objectstack/runtime` | Project metadata (objects, views, apps) |\n *\n * ## Stub services (contract-compliant in-memory implementations)\n *\n * Any core service not provided by a real plugin is automatically registered\n * as a contract-compliant dev stub that implements the interface from\n * `packages/spec/src/contracts/`. Each stub returns correct types:\n *\n * `cache` (Map-backed), `queue` (in-memory pub/sub), `job` (no-op scheduler),\n * `file-storage` (Map-backed), `search` (in-memory text search),\n * `automation` (no-op flows), `graphql` (placeholder), `analytics` (empty results),\n * `realtime` (in-memory pub/sub), `notification` (log), `ai` (placeholder),\n * `i18n` (Map-backed translations), `ui` (Map-backed views/dashboards),\n * `workflow` (Map-backed state machine)\n *\n * All services can be individually disabled via `options.services`.\n * Peer packages are loaded via dynamic import and silently skipped if missing.\n */\nexport class DevPlugin implements Plugin {\n name = 'com.objectstack.plugin.dev';\n type = 'standard';\n version = '1.0.0';\n\n private options: Required<\n Pick<DevPluginOptions, 'port' | 'seedAdminUser' | 'authSecret' | 'verbose'>\n > & DevPluginOptions;\n\n private childPlugins: Plugin[] = [];\n\n constructor(options: DevPluginOptions = {}) {\n this.options = {\n port: 3000,\n seedAdminUser: true,\n authSecret: 'objectstack-dev-secret-DO-NOT-USE-IN-PRODUCTION!!',\n verbose: true,\n ...options,\n authBaseUrl: options.authBaseUrl ?? `http://localhost:${options.port ?? 3000}`,\n };\n }\n\n /**\n * Init Phase\n *\n * Dynamically imports and instantiates all core plugins.\n * Uses dynamic imports so that peer dependencies remain optional —\n * if a package isn't installed the service is silently skipped.\n */\n async init(ctx: PluginContext): Promise<void> {\n ctx.logger.info('🚀 DevPlugin initializing — auto-configuring all services for development');\n\n const enabled = (name: string) => this.options.services?.[name] !== false;\n\n // 1. ObjectQL Engine (data layer + metadata service)\n if (enabled('objectql')) {\n try {\n const { ObjectQLPlugin } = await import('@objectstack/objectql');\n const qlPlugin = new ObjectQLPlugin();\n this.childPlugins.push(qlPlugin);\n ctx.logger.info(' ✔ ObjectQL engine enabled (data + metadata)');\n } catch {\n ctx.logger.warn(' ✘ @objectstack/objectql not installed — skipping data engine');\n }\n }\n\n // 2. In-Memory Driver\n if (enabled('driver')) {\n try {\n const { DriverPlugin } = await import('@objectstack/runtime') as any;\n const { InMemoryDriver } = await import('@objectstack/driver-memory') as any;\n const driver = new InMemoryDriver();\n const driverPlugin = new DriverPlugin(driver, 'memory');\n this.childPlugins.push(driverPlugin);\n ctx.logger.info(' ✔ InMemoryDriver enabled');\n } catch {\n ctx.logger.warn(' ✘ @objectstack/runtime or @objectstack/driver-memory not installed — skipping driver');\n }\n }\n\n // 3. App Plugin — registers project metadata (objects, views, apps, dashboards, etc.)\n // This is the key piece that enables full API development:\n // once metadata is registered, REST endpoints can read/write views, etc.\n if (this.options.stack) {\n try {\n const { AppPlugin } = await import('@objectstack/runtime') as any;\n const appPlugin = new AppPlugin(this.options.stack);\n this.childPlugins.push(appPlugin);\n ctx.logger.info(' ✔ App metadata loaded from stack definition');\n } catch {\n ctx.logger.warn(' ✘ @objectstack/runtime not installed — skipping app metadata');\n }\n }\n\n // 4. Auth Plugin\n if (enabled('auth')) {\n try {\n const { AuthPlugin } = await import('@objectstack/plugin-auth') as any;\n const authPlugin = new AuthPlugin({\n secret: this.options.authSecret,\n baseUrl: this.options.authBaseUrl,\n });\n this.childPlugins.push(authPlugin);\n ctx.logger.info(' ✔ Auth plugin enabled (dev credentials)');\n } catch {\n ctx.logger.warn(' ✘ @objectstack/plugin-auth not installed — skipping auth');\n }\n }\n\n // 5. Security Plugin (RBAC, RLS, field-level masking)\n if (enabled('security')) {\n try {\n const { SecurityPlugin } = await import('@objectstack/plugin-security') as any;\n const securityPlugin = new SecurityPlugin();\n this.childPlugins.push(securityPlugin);\n ctx.logger.info(' ✔ Security plugin enabled (RBAC, RLS, field masking)');\n } catch {\n ctx.logger.debug(' ℹ @objectstack/plugin-security not installed — skipping security');\n }\n }\n\n // 6. Hono HTTP Server\n if (enabled('server')) {\n try {\n const { HonoServerPlugin } = await import('@objectstack/plugin-hono-server') as any;\n const serverPlugin = new HonoServerPlugin({\n port: this.options.port,\n });\n this.childPlugins.push(serverPlugin);\n ctx.logger.info(` ✔ Hono HTTP server enabled on port ${this.options.port}`);\n } catch {\n ctx.logger.warn(' ✘ @objectstack/plugin-hono-server not installed — skipping HTTP server');\n }\n }\n\n // 7. REST API endpoints (CRUD + metadata read/write)\n if (enabled('rest')) {\n try {\n const { createRestApiPlugin } = await import('@objectstack/rest') as any;\n const restPlugin = createRestApiPlugin();\n this.childPlugins.push(restPlugin);\n ctx.logger.info(' ✔ REST API endpoints enabled (CRUD + metadata)');\n } catch {\n ctx.logger.debug(' ℹ @objectstack/rest not installed — skipping REST endpoints');\n }\n }\n\n // 8. Dispatcher (auth routes, GraphQL, analytics, packages, storage, automation)\n if (enabled('dispatcher')) {\n try {\n const { createDispatcherPlugin } = await import('@objectstack/runtime') as any;\n const dispatcherPlugin = createDispatcherPlugin();\n this.childPlugins.push(dispatcherPlugin);\n ctx.logger.info(' ✔ Dispatcher enabled (auth, GraphQL, analytics, packages, storage)');\n } catch {\n ctx.logger.debug(' ℹ Dispatcher not available — skipping extended API routes');\n }\n }\n\n // Extra user-provided plugins\n if (this.options.extraPlugins) {\n this.childPlugins.push(...this.options.extraPlugins);\n }\n\n // Init all child plugins\n for (const plugin of this.childPlugins) {\n try {\n await plugin.init(ctx);\n } catch (err: any) {\n ctx.logger.error(`Failed to init child plugin ${plugin.name}: ${err.message}`);\n }\n }\n\n // ── Register contract-compliant dev stubs for remaining services ────\n // The kernel defines 17 core services + 3 security services.\n // Real plugins (ObjectQL, Auth, Security, etc.) already registered some.\n // For any service NOT yet registered, we create a contract-compliant\n // dev stub (implementing the interface from packages/spec/src/contracts/)\n // so that the full kernel service map is populated and downstream code\n // receives correct return types (arrays, booleans, objects — not undefined).\n\n const stubNames: string[] = [];\n\n for (const svc of CORE_SERVICE_NAMES) {\n if (!enabled(svc)) continue;\n try {\n ctx.getService(svc);\n // Already registered by a real plugin — skip\n } catch {\n const factory = DEV_STUB_FACTORIES[svc];\n ctx.registerService(svc, factory ? factory() : { _dev: true, _serviceName: svc });\n stubNames.push(svc);\n }\n }\n\n // Security sub-services (if SecurityPlugin wasn't loaded)\n if (enabled('security')) {\n for (const svc of SECURITY_SERVICE_NAMES) {\n try {\n ctx.getService(svc);\n } catch {\n const factory = DEV_STUB_FACTORIES[svc];\n ctx.registerService(svc, factory ? factory() : { _dev: true, _serviceName: svc });\n stubNames.push(svc);\n }\n }\n }\n\n if (stubNames.length > 0) {\n ctx.logger.info(` ✔ Contract-compliant dev stubs registered for: ${stubNames.join(', ')}`);\n }\n\n ctx.logger.info(`DevPlugin initialized ${this.childPlugins.length} plugin(s) + ${stubNames.length} dev stub(s)`);\n }\n\n /**\n * Start Phase\n *\n * Starts all child plugins and optionally seeds the dev admin user.\n */\n async start(ctx: PluginContext): Promise<void> {\n // Start all child plugins\n for (const plugin of this.childPlugins) {\n if (plugin.start) {\n try {\n await plugin.start(ctx);\n } catch (err: any) {\n ctx.logger.error(`Failed to start child plugin ${plugin.name}: ${err.message}`);\n }\n }\n }\n\n // Seed default admin user\n if (this.options.seedAdminUser) {\n await this.seedAdmin(ctx);\n }\n\n ctx.logger.info('─────────────────────────────────────────');\n ctx.logger.info('🟢 ObjectStack Dev Server ready');\n ctx.logger.info(` http://localhost:${this.options.port}`);\n ctx.logger.info('');\n ctx.logger.info(' API: /api/v1/data/:object');\n ctx.logger.info(' Metadata: /api/v1/meta/:type/:name');\n ctx.logger.info(' Discovery: /.well-known/objectstack');\n ctx.logger.info('─────────────────────────────────────────');\n }\n\n /**\n * Destroy Phase\n *\n * Cleans up all child plugins in reverse order.\n */\n async destroy(): Promise<void> {\n for (const plugin of [...this.childPlugins].reverse()) {\n if (plugin.destroy) {\n try {\n await plugin.destroy();\n } catch {\n // Ignore cleanup errors during dev shutdown\n }\n }\n }\n }\n\n /**\n * Seed a default admin user for development.\n */\n private async seedAdmin(ctx: PluginContext): Promise<void> {\n try {\n const dataEngine = ctx.getService<any>('data');\n if (!dataEngine) return;\n\n // Check if admin already exists\n const existing = await dataEngine.find('user', {\n filter: { email: 'admin@dev.local' },\n limit: 1,\n }).catch(() => null);\n\n if (existing?.length) {\n ctx.logger.debug('Dev admin user already exists');\n return;\n }\n\n await dataEngine.insert('user', {\n data: {\n name: 'Admin',\n email: 'admin@dev.local',\n username: 'admin',\n role: 'admin',\n },\n }).catch(() => {\n // Table might not exist yet — that's fine for dev\n });\n\n ctx.logger.info('🔑 Dev admin user seeded: admin@dev.local');\n } catch {\n // Non-fatal — user seeding is best-effort\n ctx.logger.debug('Could not seed admin user (data engine may not be ready)');\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@objectstack/plugin-dev",
3
+ "version": "2.0.6",
4
+ "license": "Apache-2.0",
5
+ "description": "Development Mode Plugin for ObjectStack — auto-enables all services with in-memory implementations",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "dependencies": {
16
+ "@objectstack/core": "2.0.6",
17
+ "@objectstack/spec": "2.0.6"
18
+ },
19
+ "peerDependencies": {
20
+ "@objectstack/driver-memory": "2.0.6",
21
+ "@objectstack/runtime": "2.0.6",
22
+ "@objectstack/objectql": "2.0.6",
23
+ "@objectstack/plugin-auth": "2.0.6",
24
+ "@objectstack/plugin-hono-server": "2.0.6",
25
+ "@objectstack/plugin-security": "2.0.6",
26
+ "@objectstack/rest": "2.0.6"
27
+ },
28
+ "peerDependenciesMeta": {
29
+ "@objectstack/driver-memory": {
30
+ "optional": true
31
+ },
32
+ "@objectstack/objectql": {
33
+ "optional": true
34
+ },
35
+ "@objectstack/runtime": {
36
+ "optional": true
37
+ },
38
+ "@objectstack/plugin-auth": {
39
+ "optional": true
40
+ },
41
+ "@objectstack/plugin-hono-server": {
42
+ "optional": true
43
+ },
44
+ "@objectstack/plugin-security": {
45
+ "optional": true
46
+ },
47
+ "@objectstack/rest": {
48
+ "optional": true
49
+ }
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^25.2.2",
53
+ "typescript": "^5.0.0",
54
+ "vitest": "^4.0.18",
55
+ "@objectstack/objectql": "2.0.6",
56
+ "@objectstack/runtime": "2.0.6",
57
+ "@objectstack/driver-memory": "2.0.6",
58
+ "@objectstack/plugin-auth": "2.0.6",
59
+ "@objectstack/plugin-hono-server": "2.0.6",
60
+ "@objectstack/plugin-security": "2.0.6",
61
+ "@objectstack/rest": "2.0.6"
62
+ },
63
+ "scripts": {
64
+ "build": "tsup --config ../../../tsup.config.ts",
65
+ "test": "vitest run"
66
+ }
67
+ }
@@ -0,0 +1,267 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { DevPlugin } from './dev-plugin';
3
+
4
+ describe('DevPlugin', () => {
5
+ it('should have correct metadata', () => {
6
+ const plugin = new DevPlugin();
7
+ expect(plugin.name).toBe('com.objectstack.plugin.dev');
8
+ expect(plugin.type).toBe('standard');
9
+ expect(plugin.version).toBe('1.0.0');
10
+ });
11
+
12
+ it('should accept default options', () => {
13
+ const plugin = new DevPlugin();
14
+ expect(plugin).toBeDefined();
15
+ });
16
+
17
+ it('should accept custom options including stack', () => {
18
+ const plugin = new DevPlugin({
19
+ port: 4000,
20
+ seedAdminUser: false,
21
+ verbose: false,
22
+ services: { auth: false, dispatcher: false, security: false },
23
+ stack: { manifest: { id: 'test', name: 'test', version: '1.0.0', type: 'app' } },
24
+ });
25
+ expect(plugin).toBeDefined();
26
+ });
27
+
28
+ it('should init with mocked context and handle missing deps gracefully', async () => {
29
+ const ctx: any = {
30
+ logger: {
31
+ info: vi.fn(),
32
+ debug: vi.fn(),
33
+ warn: vi.fn(),
34
+ error: vi.fn(),
35
+ },
36
+ getService: vi.fn().mockImplementation(() => { throw new Error('not found'); }),
37
+ getServices: vi.fn().mockReturnValue(new Map()),
38
+ registerService: vi.fn(),
39
+ hook: vi.fn(),
40
+ trigger: vi.fn(),
41
+ getKernel: vi.fn(),
42
+ };
43
+
44
+ // DevPlugin should not throw even if peer dependencies are missing
45
+ const plugin = new DevPlugin({ seedAdminUser: false });
46
+ await expect(plugin.init(ctx)).resolves.not.toThrow();
47
+ });
48
+
49
+ it('should register contract-compliant dev stubs for all core services', async () => {
50
+ const registeredServices = new Map<string, any>();
51
+ const ctx: any = {
52
+ logger: {
53
+ info: vi.fn(),
54
+ debug: vi.fn(),
55
+ warn: vi.fn(),
56
+ error: vi.fn(),
57
+ },
58
+ getService: vi.fn().mockImplementation((name: string) => {
59
+ if (registeredServices.has(name)) return registeredServices.get(name);
60
+ throw new Error('not found');
61
+ }),
62
+ getServices: vi.fn().mockReturnValue(new Map()),
63
+ registerService: vi.fn().mockImplementation((name: string, svc: any) => {
64
+ registeredServices.set(name, svc);
65
+ }),
66
+ hook: vi.fn(),
67
+ trigger: vi.fn(),
68
+ getKernel: vi.fn(),
69
+ };
70
+
71
+ // Disable real plugins (which need real packages) but allow stubs
72
+ const plugin = new DevPlugin({
73
+ seedAdminUser: false,
74
+ services: {
75
+ objectql: false,
76
+ driver: false,
77
+ auth: false,
78
+ security: false,
79
+ server: false,
80
+ rest: false,
81
+ dispatcher: false,
82
+ },
83
+ });
84
+
85
+ await plugin.init(ctx);
86
+
87
+ // Should have registered stubs for all core + security services
88
+ const stubLog = ctx.logger.info.mock.calls.find(
89
+ (call: any[]) => typeof call[0] === 'string' && call[0].includes('dev stubs registered'),
90
+ );
91
+ expect(stubLog).toBeDefined();
92
+
93
+ // ── Verify ICacheService contract ──
94
+ const cache = registeredServices.get('cache');
95
+ expect(cache._dev).toBe(true);
96
+ await cache.set('k1', 'v1');
97
+ expect(await cache.get('k1')).toBe('v1');
98
+ expect(await cache.has('k1')).toBe(true);
99
+ expect(await cache.delete('k1')).toBe(true);
100
+ expect(await cache.has('k1')).toBe(false);
101
+ const stats = await cache.stats();
102
+ expect(typeof stats.hits).toBe('number');
103
+ expect(typeof stats.misses).toBe('number');
104
+ expect(typeof stats.keyCount).toBe('number');
105
+
106
+ // ── Verify IQueueService contract ──
107
+ const queue = registeredServices.get('queue');
108
+ const msgId = await queue.publish('test-q', { hello: 'world' });
109
+ expect(typeof msgId).toBe('string');
110
+ expect(await queue.getQueueSize('test-q')).toBe(0);
111
+
112
+ // ── Verify IJobService contract ──
113
+ const job = registeredServices.get('job');
114
+ const jobs = await job.listJobs();
115
+ expect(Array.isArray(jobs)).toBe(true);
116
+
117
+ // ── Verify IStorageService contract ──
118
+ const storage = registeredServices.get('file-storage');
119
+ await storage.upload('test.txt', Buffer.from('hello'));
120
+ expect(await storage.exists('test.txt')).toBe(true);
121
+ const info = await storage.getInfo('test.txt');
122
+ expect(info.key).toBe('test.txt');
123
+ expect(info.size).toBeGreaterThan(0);
124
+ const downloaded = await storage.download('test.txt');
125
+ expect(downloaded.toString()).toBe('hello');
126
+
127
+ // ── Verify ISearchService contract ──
128
+ const search = registeredServices.get('search');
129
+ await search.index('users', '1', { name: 'Alice' });
130
+ const searchResult = await search.search('users', 'alice');
131
+ expect(searchResult.hits).toHaveLength(1);
132
+ expect(typeof searchResult.totalHits).toBe('number');
133
+
134
+ // ── Verify IAutomationService contract ──
135
+ const automation = registeredServices.get('automation');
136
+ const execResult = await automation.execute('test_flow');
137
+ expect(execResult.success).toBe(true);
138
+ expect(Array.isArray(await automation.listFlows())).toBe(true);
139
+
140
+ // ── Verify IGraphQLService contract ──
141
+ const gql = registeredServices.get('graphql');
142
+ const gqlResult = await gql.execute({ query: '{ _dev }' });
143
+ expect('data' in gqlResult || 'errors' in gqlResult).toBe(true);
144
+ expect(typeof gql.getSchema()).toBe('string');
145
+
146
+ // ── Verify IAnalyticsService contract ──
147
+ const analytics = registeredServices.get('analytics');
148
+ const analyticsResult = await analytics.query({ cube: 'test' });
149
+ expect(Array.isArray(analyticsResult.rows)).toBe(true);
150
+ expect(Array.isArray(analyticsResult.fields)).toBe(true);
151
+ expect(Array.isArray(await analytics.getMeta())).toBe(true);
152
+
153
+ // ── Verify IRealtimeService contract ──
154
+ const realtime = registeredServices.get('realtime');
155
+ const subId = await realtime.subscribe('ch', () => {});
156
+ expect(typeof subId).toBe('string');
157
+
158
+ // ── Verify INotificationService contract ──
159
+ const notif = registeredServices.get('notification');
160
+ const notifResult = await notif.send({ channel: 'email', to: 'test@dev', body: 'hello' });
161
+ expect(notifResult.success).toBe(true);
162
+ expect(typeof notifResult.messageId).toBe('string');
163
+
164
+ // ── Verify IAIService contract ──
165
+ const ai = registeredServices.get('ai');
166
+ const chatResult = await ai.chat([{ role: 'user', content: 'hi' }]);
167
+ expect(typeof chatResult.content).toBe('string');
168
+ expect(typeof chatResult.model).toBe('string');
169
+ expect(Array.isArray(await ai.listModels())).toBe(true);
170
+
171
+ // ── Verify II18nService contract ──
172
+ const i18n = registeredServices.get('i18n');
173
+ i18n.loadTranslations('en', { 'hello': 'Hello {{name}}' });
174
+ expect(i18n.t('hello', 'en', { name: 'World' })).toBe('Hello World');
175
+ expect(i18n.t('missing', 'en')).toBe('missing');
176
+ expect(Array.isArray(i18n.getLocales())).toBe(true);
177
+
178
+ // ── Verify IUIService contract ──
179
+ const ui = registeredServices.get('ui');
180
+ ui.registerView('test_view', { name: 'test_view', object: 'account' });
181
+ expect(ui.getView('test_view')).toBeDefined();
182
+ expect(Array.isArray(ui.listViews())).toBe(true);
183
+ expect(ui.listViews('account')).toHaveLength(1);
184
+
185
+ // ── Verify IWorkflowService contract ──
186
+ const workflow = registeredServices.get('workflow');
187
+ const transResult = await workflow.transition({ recordId: 'r1', object: 'order', targetState: 'approved' });
188
+ expect(transResult.success).toBe(true);
189
+ expect(transResult.currentState).toBe('approved');
190
+ const status = await workflow.getStatus('order', 'r1');
191
+ expect(status.currentState).toBe('approved');
192
+ expect(Array.isArray(status.availableTransitions)).toBe(true);
193
+
194
+ // ── Verify IMetadataService contract (stub fallback) ──
195
+ const metadata = registeredServices.get('metadata');
196
+ metadata.register('object', { name: 'account' });
197
+ expect(metadata.get('object', 'account')).toBeDefined();
198
+ expect(Array.isArray(metadata.list('object'))).toBe(true);
199
+ expect(Array.isArray(metadata.listObjects())).toBe(true);
200
+
201
+ // Security sub-services are registered by either the real SecurityPlugin
202
+ // or dev stubs (when security is disabled, they're skipped entirely).
203
+ // The stubs follow the same contracts as the real implementations.
204
+ });
205
+
206
+ it('should skip disabled services', async () => {
207
+ const ctx: any = {
208
+ logger: {
209
+ info: vi.fn(),
210
+ debug: vi.fn(),
211
+ warn: vi.fn(),
212
+ error: vi.fn(),
213
+ },
214
+ getService: vi.fn().mockImplementation(() => { throw new Error('not found'); }),
215
+ getServices: vi.fn().mockReturnValue(new Map()),
216
+ registerService: vi.fn(),
217
+ hook: vi.fn(),
218
+ trigger: vi.fn(),
219
+ getKernel: vi.fn(),
220
+ };
221
+
222
+ const plugin = new DevPlugin({
223
+ seedAdminUser: false,
224
+ services: {
225
+ objectql: false,
226
+ driver: false,
227
+ auth: false,
228
+ server: false,
229
+ rest: false,
230
+ dispatcher: false,
231
+ security: false,
232
+ // Disable all core services too
233
+ metadata: false,
234
+ data: false,
235
+ cache: false,
236
+ queue: false,
237
+ job: false,
238
+ 'file-storage': false,
239
+ search: false,
240
+ automation: false,
241
+ graphql: false,
242
+ analytics: false,
243
+ realtime: false,
244
+ notification: false,
245
+ ai: false,
246
+ i18n: false,
247
+ ui: false,
248
+ workflow: false,
249
+ },
250
+ });
251
+
252
+ await plugin.init(ctx);
253
+
254
+ // No child plugins AND no stubs should be registered
255
+ const initLog = ctx.logger.info.mock.calls.find(
256
+ (call: any[]) => typeof call[0] === 'string' && call[0].includes('initialized'),
257
+ );
258
+ expect(initLog).toBeDefined();
259
+ expect(initLog[0]).toContain('0 plugin');
260
+ expect(initLog[0]).toContain('0 dev stub');
261
+ });
262
+
263
+ it('should destroy without errors', async () => {
264
+ const plugin = new DevPlugin();
265
+ await expect(plugin.destroy()).resolves.not.toThrow();
266
+ });
267
+ });