From b08e4af54bff6504e720e10a3e4465f07c277267 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 08:48:11 +0000 Subject: [PATCH 1/3] Initial plan From 2f47c3a85d08f9c4c1e62a90ee1ed62d070f0622 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 08:52:05 +0000 Subject: [PATCH 2/3] feat: implement ObjectStack spec with schemas, types, and conventions Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- .gitignore | 27 +++++++ README.md | 134 +++++++++++++++++++++++++++++++- package-lock.json | 63 +++++++++++++++ package.json | 32 ++++++++ src/constants/paths.ts | 79 +++++++++++++++++++ src/index.ts | 42 ++++++++++ src/schemas/manifest.zod.ts | 86 +++++++++++++++++++++ src/types/plugin.ts | 148 ++++++++++++++++++++++++++++++++++++ tsconfig.json | 23 ++++++ 9 files changed, 633 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/constants/paths.ts create mode 100644 src/index.ts create mode 100644 src/schemas/manifest.zod.ts create mode 100644 src/types/plugin.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..346059657 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Dependencies +node_modules/ + +# Build output +dist/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* + +# Testing +coverage/ +.nyc_output/ + +# Temporary files +*.tmp +.cache/ diff --git a/README.md b/README.md index d20fbd02e..7027ff5ce 100644 --- a/README.md +++ b/README.md @@ -1 +1,133 @@ -# spec \ No newline at end of file +# @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 + +MIT \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..0d7101408 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "@objectstack/spec", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@objectstack/spec", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "typescript": "^5.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..7bacc9a85 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "@objectstack/spec", + "version": "0.1.0", + "description": "ObjectStack Protocol & Specification - TypeScript Interfaces, JSON Schemas, and Convention Configurations", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf dist", + "prepublishOnly": "npm run clean && npm run build" + }, + "keywords": [ + "objectstack", + "protocol", + "specification", + "schema", + "types" + ], + "author": "ObjectStack", + "license": "MIT", + "devDependencies": { + "@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 new file mode 100644 index 000000000..f3cd7e96f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,42 @@ +/** + * @objectstack/spec + * + * ObjectStack Protocol & Specification + * + * 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 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..85c715ab5 --- /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 schema files. + * Example: ["./src/schema/*.gql", "./schema/ ** /*.graphql"] + */ + entities: z.array(z.string()).optional().describe('Glob patterns for ObjectQL schema 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 new file mode 100644 index 000000000..2039e6bc1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} From 6853b996aaaa233372de187014a91cd8f126fee6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 08:54:35 +0000 Subject: [PATCH 3/3] fix: improve documentation consistency in manifest schema Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- src/schemas/manifest.zod.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/schemas/manifest.zod.ts b/src/schemas/manifest.zod.ts index 85c715ab5..660c8fde0 100644 --- a/src/schemas/manifest.zod.ts +++ b/src/schemas/manifest.zod.ts @@ -62,10 +62,10 @@ export const ManifestSchema = z.object({ menus: z.array(MenuItemSchema).optional().describe('Navigation menu structure'), /** - * Glob patterns specifying ObjectQL schema files. - * Example: ["./src/schema/*.gql", "./schema/ ** /*.graphql"] + * Glob patterns specifying ObjectQL schemas files. + * Example: `["./src/schemas/*.gql", "./src/schemas/**\/*.graphql"]` */ - entities: z.array(z.string()).optional().describe('Glob patterns for ObjectQL schema files'), + entities: z.array(z.string()).optional().describe('Glob patterns for ObjectQL schemas files'), /** * Extension points contributed by this package.