diff --git a/.gitignore b/.gitignore index b7a74c9e8..90d567ecc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,14 @@ Thumbs.db # Logs *.log npm-debug.log* + +# Testing +coverage/ +.nyc_output/ + +# Temporary files +*.tmp +.cache/ yarn-debug.log* yarn-error.log* diff --git a/README.md b/README.md index 6588438e0..6e791cbb3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,134 @@ +# @objectstack/spec + +[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +> ObjectStack Protocol & Specification - The Constitution of the ObjectStack Ecosystem + +## 📜 Overview + +This package defines the **core interfaces, schemas, and conventions** for the ObjectStack ecosystem. It serves as the "Constitution" - the shared language that ObjectOS, ObjectStudio, ObjectCloud, and all third-party plugins use to communicate. + +**Guiding Principle:** *"Strict Types, No Logic"* + +This package contains: +- ✅ TypeScript Interfaces (Shared types) +- ✅ Zod Schemas (Validation rules with type inference) +- ✅ Constants (Convention configurations) + +This package does NOT contain: +- ❌ Database connections +- ❌ UI components +- ❌ Runtime business logic + +## 🚀 Installation + +```bash +npm install @objectstack/spec +``` + +## 📦 What's Inside + +### 1. Manifest Schema (`ManifestSchema`) + +Defines the structure of a package configuration file. All packages (apps, plugins, drivers, modules) must conform to this schema. + +```typescript +import { ManifestSchema, type ObjectStackManifest } from '@objectstack/spec'; + +// Validate a manifest +const manifest: ObjectStackManifest = { + id: 'com.example.myapp', + version: '1.0.0', + type: 'plugin', + name: 'My App', + permissions: ['system.user.read'], + menus: [ + { label: 'Dashboard', path: '/dashboard', icon: 'home' } + ] +}; + +// Validate with Zod +ManifestSchema.parse(manifest); +``` + +### 2. Plugin Runtime Interface (`ObjectStackPlugin`) + +Defines the contract that every plugin must implement to be loaded by ObjectOS. + +```typescript +import { ObjectStackPlugin, PluginContext } from '@objectstack/spec'; + +export default function createPlugin(): ObjectStackPlugin { + return { + async onInstall(ctx: PluginContext) { + ctx.logger.info('Plugin installed'); + }, + + async onEnable(ctx: PluginContext) { + ctx.logger.info('Plugin enabled'); + }, + + async onDisable(ctx: PluginContext) { + ctx.logger.info('Plugin disabled'); + } + }; +} +``` + +### 3. Directory Conventions (`PKG_CONVENTIONS`) + +Defines the "Law of Location" - where things must be in ObjectStack packages. + +```typescript +import { PKG_CONVENTIONS } from '@objectstack/spec'; + +console.log(PKG_CONVENTIONS.DIRS.SCHEMA); // 'src/schemas' +console.log(PKG_CONVENTIONS.DIRS.TRIGGERS); // 'src/triggers' +console.log(PKG_CONVENTIONS.FILES.MANIFEST); // 'objectstack.config.ts' +``` + +## 📚 API Reference + +### Schemas + +- `ManifestSchema` - Zod schema for package manifests +- `MenuItemSchema` - Zod schema for menu items +- `ObjectStackManifest` - TypeScript type for manifests +- `MenuItem` - TypeScript type for menu items + +### Types + +- `ObjectStackPlugin` - Plugin interface with lifecycle methods +- `PluginContext` - Context provided to plugin methods +- `PluginFactory` - Plugin factory function type +- `PluginLogger` - Logger interface +- `ObjectQLClient` - Database client interface +- `ObjectOSKernel` - OS kernel interface + +### Constants + +- `PKG_CONVENTIONS` - Directory and file conventions +- `PackageDirectory` - Type for package directories +- `PackageFile` - Type for package files + +## 🏗️ Development + +```bash +# Install dependencies +npm install + +# Build the project +npm run build + +# Watch mode for development +npm run dev + +# Clean build artifacts +npm run clean +``` + +## 📄 License # ObjectStack Specification The ObjectStack Protocol & Specification repository defines the core type definitions and interfaces for the ObjectStack ecosystem. This repository serves as the "Constitution" of the system, providing the contract between backend (ObjectQL) parsers and frontend (ObjectUI) renderers. diff --git a/package.json b/package.json index e8fb4a918..7bacc9a85 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,32 @@ { "name": "@objectstack/spec", "version": "0.1.0", - "description": "ObjectStack Protocol & Specification - Type definitions and schemas", + "description": "ObjectStack Protocol & Specification - TypeScript Interfaces, JSON Schemas, and Convention Configurations", "main": "dist/index.js", "types": "dist/index.d.ts", - "files": [ - "dist", - "src" - ], "scripts": { "build": "tsc", + "dev": "tsc --watch", "clean": "rm -rf dist", - "changeset": "changeset", - "version": "changeset version", - "release": "npm run build && changeset publish" - }, - "repository": { - "type": "git", - "url": "https://github.com/objectstack-ai/spec.git" + "prepublishOnly": "npm run clean && npm run build" }, "keywords": [ "objectstack", + "protocol", "specification", - "types", - "metamodel" + "schema", + "types" ], "author": "ObjectStack", "license": "MIT", "devDependencies": { - "@changesets/cli": "^2.29.8", - "typescript": "^5.9.3" + "@types/node": "^20.10.0", + "typescript": "^5.3.0" + }, + "dependencies": { + "zod": "^3.22.4" + }, + "engines": { + "node": ">=18.0.0" } } diff --git a/src/constants/paths.ts b/src/constants/paths.ts new file mode 100644 index 000000000..f312cbd65 --- /dev/null +++ b/src/constants/paths.ts @@ -0,0 +1,79 @@ +/** + * Package conventions and directory structure constants. + * These define the "Law of Location" - where things must be in ObjectStack packages. + * + * These paths are the source of truth used by: + * - ObjectOS Runtime (to locate package components) + * - ObjectStack CLI (to scaffold and validate packages) + * - ObjectStudio IDE (to provide intelligent navigation and validation) + */ +export const PKG_CONVENTIONS = { + /** + * Standard directories within ObjectStack packages. + * All packages MUST follow these conventions for the runtime to locate resources. + */ + DIRS: { + /** + * Location for schema definitions (Zod schemas, JSON schemas). + * Path: src/schemas + */ + SCHEMA: 'src/schemas', + + /** + * Location for server-side code and triggers. + * Path: src/server + */ + SERVER: 'src/server', + + /** + * Location for server-side trigger functions. + * Path: src/triggers + */ + TRIGGERS: 'src/triggers', + + /** + * Location for client-side code. + * Path: src/client + */ + CLIENT: 'src/client', + + /** + * Location for client-side page components. + * Path: src/client/pages + */ + PAGES: 'src/client/pages', + + /** + * Location for static assets (images, fonts, etc.). + * Path: assets + */ + ASSETS: 'assets', + }, + + /** + * Standard file names within ObjectStack packages. + */ + FILES: { + /** + * Package manifest configuration file. + * File: objectstack.config.ts + */ + MANIFEST: 'objectstack.config.ts', + + /** + * Main entry point for the package. + * File: src/index.ts + */ + ENTRY: 'src/index.ts', + }, +} as const; + +/** + * Type helper to extract directory path values. + */ +export type PackageDirectory = typeof PKG_CONVENTIONS.DIRS[keyof typeof PKG_CONVENTIONS.DIRS]; + +/** + * Type helper to extract file path values. + */ +export type PackageFile = typeof PKG_CONVENTIONS.FILES[keyof typeof PKG_CONVENTIONS.FILES]; diff --git a/src/index.ts b/src/index.ts index 42100fee2..f3cd7e96f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,42 @@ /** - * ObjectStack Specification + * @objectstack/spec * - * This module provides the core type definitions and interfaces for the ObjectStack ecosystem. - * It defines the contract between backend (ObjectQL) parsers and frontend (ObjectUI) renderers. + * ObjectStack Protocol & Specification * - * @packageDocumentation + * This package contains the core interfaces, schemas, and conventions for the ObjectStack ecosystem. + * It defines the "Constitution" of the system - the shared language that ObjectOS, ObjectStudio, + * ObjectCloud, and all third-party plugins use to communicate. + * + * This package contains: + * - TypeScript Interfaces (Shared types) + * - Zod Schemas (Validation rules with type inference) + * - Constants (Convention configurations) + * + * Guiding Principle: "Strict Types, No Logic" + * This package has NO database connections, NO UI components, and NO runtime business logic. */ -export * from './types'; +// Export schemas +export { + ManifestSchema, + MenuItemSchema, + type ObjectStackManifest, + type MenuItem, +} from './schemas/manifest.zod'; + +// Export types +export { + type ObjectStackPlugin, + type PluginContext, + type PluginFactory, + type PluginLogger, + type ObjectQLClient, + type ObjectOSKernel, +} from './types/plugin'; + +// Export constants +export { + PKG_CONVENTIONS, + type PackageDirectory, + type PackageFile, +} from './constants/paths'; diff --git a/src/schemas/manifest.zod.ts b/src/schemas/manifest.zod.ts new file mode 100644 index 000000000..660c8fde0 --- /dev/null +++ b/src/schemas/manifest.zod.ts @@ -0,0 +1,86 @@ +import { z } from 'zod'; + +/** + * Schema for menu items in ObjectStack packages. + * Defines navigation structure that can be injected into the UI. + */ +export const MenuItemSchema = z.object({ + /** Display label for the menu item */ + label: z.string().describe('Display label for the menu item'), + /** Navigation path (route) for the menu item */ + path: z.string().describe('Navigation path (route) for the menu item'), + /** Optional icon identifier for the menu item */ + icon: z.string().optional().describe('Optional icon identifier for the menu item'), +}); + +/** + * Schema for the ObjectStack Manifest. + * This defines the structure of a package configuration in the ObjectStack ecosystem. + * All packages (apps, plugins, drivers, modules) must conform to this schema. + */ +export const ManifestSchema = z.object({ + /** + * Unique package identifier using reverse domain notation. + * Example: "com.example.crm" + */ + id: z.string().describe('Unique package identifier (reverse domain style)'), + + /** + * Package version following semantic versioning (major.minor.patch). + * Example: "1.0.0" + */ + version: z.string().regex(/^\d+\.\d+\.\d+$/).describe('Package version (semantic versioning)'), + + /** + * Type of the package in the ObjectStack ecosystem. + * - app: Standalone application + * - plugin: Extension to ObjectOS + * - driver: Low-level integration driver + * - module: Reusable code module + */ + type: z.enum(['app', 'plugin', 'driver', 'module']).describe('Type of package'), + + /** + * Human-readable name of the package. + */ + name: z.string().describe('Human-readable package name'), + + /** + * Brief description of the package functionality. + */ + description: z.string().optional().describe('Package description'), + + /** + * Array of permission strings that the package requires. + * Example: ["system.user.read", "system.data.write"] + */ + permissions: z.array(z.string()).optional().describe('Array of required permission strings'), + + /** + * Navigation menu structure that the package contributes to the UI. + */ + menus: z.array(MenuItemSchema).optional().describe('Navigation menu structure'), + + /** + * Glob patterns specifying ObjectQL schemas files. + * Example: `["./src/schemas/*.gql", "./src/schemas/**\/*.graphql"]` + */ + entities: z.array(z.string()).optional().describe('Glob patterns for ObjectQL schemas files'), + + /** + * Extension points contributed by this package. + * Allows packages to extend UI components, add functionality, etc. + */ + extensions: z.record(z.any()).optional().describe('Extension points and contributions'), +}); + +/** + * TypeScript type inferred from the ManifestSchema. + * Use this type for type-safe manifest handling in TypeScript code. + */ +export type ObjectStackManifest = z.infer; + +/** + * TypeScript type for menu items. + */ +export type MenuItem = z.infer; diff --git a/src/types/plugin.ts b/src/types/plugin.ts new file mode 100644 index 000000000..dcbd21981 --- /dev/null +++ b/src/types/plugin.ts @@ -0,0 +1,148 @@ +/** + * Runtime interfaces for ObjectStack plugins. + * These define the contract that every plugin must implement to be loaded by ObjectOS. + */ + +/** + * Logger interface provided to plugins for structured logging. + */ +export interface PluginLogger { + /** Log an informational message */ + info(message: string, meta?: Record): void; + /** Log a warning message */ + warn(message: string, meta?: Record): void; + /** Log an error message */ + error(message: string, error?: Error, meta?: Record): void; + /** Log a debug message */ + debug(message: string, meta?: Record): void; +} + +/** + * ObjectQL Client interface for database operations. + * Provides a GraphQL-like query interface to the ObjectStack data layer. + */ +export interface ObjectQLClient { + /** + * Execute a query against the ObjectQL engine. + * @param query - The ObjectQL query string + * @param variables - Optional variables for the query + * @returns Promise resolving to query results + */ + query(query: string, variables?: Record): Promise; + + /** + * Execute a mutation against the ObjectQL engine. + * @param mutation - The ObjectQL mutation string + * @param variables - Optional variables for the mutation + * @returns Promise resolving to mutation results + */ + mutate(mutation: string, variables?: Record): Promise; +} + +/** + * ObjectOS Kernel interface. + * Provides access to core operating system services. + */ +export interface ObjectOSKernel { + /** + * Get a reference to another installed plugin by its ID. + * @param pluginId - The unique identifier of the plugin + * @returns The plugin instance or null if not found + */ + getPlugin(pluginId: string): any | null; + + /** + * Emit an event to the system event bus. + * @param event - The event name + * @param data - The event payload + */ + emit(event: string, data?: any): void; + + /** + * Subscribe to system events. + * @param event - The event name to listen for + * @param handler - Callback function to handle the event + * @returns Unsubscribe function + */ + on(event: string, handler: (data: any) => void): () => void; +} + +/** + * Plugin Context provided to plugin lifecycle methods. + * This context gives plugins access to the ObjectStack runtime environment. + */ +export interface PluginContext { + /** + * ObjectQL client for database operations. + * Use this to query and mutate data in the ObjectStack data layer. + */ + ql: ObjectQLClient; + + /** + * ObjectOS kernel for system-level operations. + * Use this to interact with other plugins and system services. + */ + os: ObjectOSKernel; + + /** + * Logger instance for structured logging. + * All logs are automatically tagged with the plugin ID. + */ + logger: PluginLogger; + + /** + * The unique identifier of this plugin. + */ + pluginId: string; + + /** + * Plugin configuration values (from manifest or runtime config). + */ + config: Record; +} + +/** + * ObjectStackPlugin interface. + * Every plugin must implement this interface to be loaded by ObjectOS. + * + * Lifecycle: + * 1. onInstall - Called once when the plugin is first installed + * 2. onEnable - Called when the plugin is enabled (on every startup if auto-enabled) + * 3. onDisable - Called when the plugin is disabled or before uninstall + */ +export interface ObjectStackPlugin { + /** + * Called once when the plugin is first installed. + * Use this to set up initial data, create tables, register schemas, etc. + * + * @param ctx - Plugin context with access to ql, os, and logger + * @returns Promise that resolves when installation is complete + */ + onInstall(ctx: PluginContext): Promise; + + /** + * Called when the plugin is enabled. + * This is called on every system startup if the plugin is set to auto-enable. + * Use this to register event handlers, start background tasks, etc. + * + * @param ctx - Plugin context with access to ql, os, and logger + * @returns Promise that resolves when plugin is ready + */ + onEnable(ctx: PluginContext): Promise; + + /** + * Called when the plugin is disabled. + * Use this to clean up resources, unregister handlers, stop background tasks, etc. + * Should gracefully handle being called multiple times. + * + * @param ctx - Plugin context with access to ql, os, and logger + * @returns Promise that resolves when cleanup is complete + */ + onDisable(ctx: PluginContext): Promise; +} + +/** + * Plugin factory function type. + * A plugin module should export a default function that creates a plugin instance. + */ +export type PluginFactory = () => ObjectStackPlugin; diff --git a/tsconfig.json b/tsconfig.json index 61a295967..2039e6bc1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,8 @@ { "compilerOptions": { "target": "ES2020", - "module": "ESNext", + "module": "commonjs", "lib": ["ES2020"], - "moduleResolution": "node", "declaration": true, "declarationMap": true, "outDir": "./dist", @@ -12,6 +11,7 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", "resolveJsonModule": true, "noUnusedLocals": true, "noUnusedParameters": true,