From 1687fc96037fea477289bdad86ae2b5385e6c4fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 08:55:05 +0000 Subject: [PATCH 1/6] Initial plan From 479c1bbf8944a3c519583350db9d07a960a637cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 09:00:59 +0000 Subject: [PATCH 2/6] Add more system and integration connector documentation Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- content/docs/references/data/meta.json | 2 ++ .../docs/references/data/search-engine.mdx | 33 ------------------- 2 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 content/docs/references/data/search-engine.mdx diff --git a/content/docs/references/data/meta.json b/content/docs/references/data/meta.json index 3e5f65882..154d3d2c8 100644 --- a/content/docs/references/data/meta.json +++ b/content/docs/references/data/meta.json @@ -5,7 +5,9 @@ "dataset", "document", "driver", + "driver-mongo", "driver-nosql", + "driver-postgres", "driver-sql", "external-lookup", "field", diff --git a/content/docs/references/data/search-engine.mdx b/content/docs/references/data/search-engine.mdx deleted file mode 100644 index 8fb79fba7..000000000 --- a/content/docs/references/data/search-engine.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Search Engine -description: Search Engine protocol schemas ---- - -# Search Engine - - -**Source:** `packages/spec/src/data/search-engine.zod.ts` - - -## TypeScript Usage - -```typescript -import { SearchConfigSchema } from '@objectstack/spec/data'; -import type { SearchConfig } from '@objectstack/spec/data'; - -// Validate data -const result = SearchConfigSchema.parse(data); -``` - ---- - -## SearchConfig - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **fields** | `string[]` | ✅ | Fields to index for full-text search weighting | -| **displayFields** | `string[]` | optional | Fields to display in search result cards | -| **filters** | `string[]` | optional | Default filters for search results | - From e2e642c3c414543fa504829013c0f3bc376ed4b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 09:03:21 +0000 Subject: [PATCH 3/6] Fix connector.mdx path and update integration meta.json Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- content/docs/references/automation/connector.mdx | 6 +++--- content/docs/references/integration/meta.json | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/content/docs/references/automation/connector.mdx b/content/docs/references/automation/connector.mdx index ad2b8c0ce..0bfe1b0ef 100644 --- a/content/docs/references/automation/connector.mdx +++ b/content/docs/references/automation/connector.mdx @@ -6,14 +6,14 @@ description: Connector protocol schemas # Connector -**Source:** `packages/spec/src/automation/connector.zod.ts` +**Source:** `packages/spec/src/integration/connector.zod.ts` ## TypeScript Usage ```typescript -import { AuthenticationSchema, ConflictResolutionSchema, ConnectorSchema, DataSyncConfigSchema } from '@objectstack/spec/automation'; -import type { Authentication, ConflictResolution, Connector, DataSyncConfig } from '@objectstack/spec/automation'; +import { AuthenticationSchema, ConflictResolutionSchema, ConnectorSchema, DataSyncConfigSchema } from '@objectstack/spec/integration'; +import type { Authentication, ConflictResolution, Connector, DataSyncConfig } from '@objectstack/spec/integration'; // Validate data const result = AuthenticationSchema.parse(data); diff --git a/content/docs/references/integration/meta.json b/content/docs/references/integration/meta.json index c07e1f0e2..90f5b0b97 100644 --- a/content/docs/references/integration/meta.json +++ b/content/docs/references/integration/meta.json @@ -1,6 +1,7 @@ { "title": "Integration Protocol", "pages": [ - "connector" + "connector", + "connector-file-storage" ] } \ No newline at end of file From f4588f316828e3bdcd2421ec3389c7fd1dcfc697 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 09:05:02 +0000 Subject: [PATCH 4/6] Add comprehensive documentation for missing schemas Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- content/docs/references/data/driver-mongo.mdx | 147 ++++++ .../docs/references/data/driver-postgres.mdx | 133 ++++++ .../integration/connector-file-storage.mdx | 381 ++++++++++++++++ .../references/system/metadata-loader.mdx | 327 ++++++++++++++ .../system/plugin-lifecycle-events.mdx | 309 +++++++++++++ .../references/system/plugin-validator.mdx | 297 ++++++++++++ .../references/system/service-registry.mdx | 425 ++++++++++++++++++ .../system/startup-orchestrator.mdx | 299 ++++++++++++ 8 files changed, 2318 insertions(+) create mode 100644 content/docs/references/data/driver-mongo.mdx create mode 100644 content/docs/references/data/driver-postgres.mdx create mode 100644 content/docs/references/integration/connector-file-storage.mdx create mode 100644 content/docs/references/system/metadata-loader.mdx create mode 100644 content/docs/references/system/plugin-lifecycle-events.mdx create mode 100644 content/docs/references/system/plugin-validator.mdx create mode 100644 content/docs/references/system/service-registry.mdx create mode 100644 content/docs/references/system/startup-orchestrator.mdx diff --git a/content/docs/references/data/driver-mongo.mdx b/content/docs/references/data/driver-mongo.mdx new file mode 100644 index 000000000..811afe48c --- /dev/null +++ b/content/docs/references/data/driver-mongo.mdx @@ -0,0 +1,147 @@ +--- +title: MongoDB Driver +description: MongoDB driver configuration schema +--- + +# MongoDB Driver + + +**Source:** `packages/spec/src/data/driver/mongo.zod.ts` + + +## Overview + +MongoDB driver configuration schema defines connection settings and capabilities specific to MongoDB databases. + +## TypeScript Usage + +```typescript +import { MongoConfigSchema, MongoDriverSpec } from '@objectstack/spec/data/driver/mongo'; +import type { MongoConfig } from '@objectstack/spec/data/driver/mongo'; + +// Validate MongoDB configuration +const config: MongoConfig = MongoConfigSchema.parse({ + database: 'myapp', + host: '127.0.0.1', + port: 27017, + username: 'admin', + password: 'secret', +}); +``` + +--- + +## MongoConfig + +MongoDB connection configuration schema. + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **url** | `string` | optional | Connection URI. Format: mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] | +| **database** | `string` | ✅ | Database name (required) | +| **host** | `string` | optional | Hostname (default: '127.0.0.1') | +| **port** | `number` | optional | Port number (default: 27017) | +| **username** | `string` | optional | Username for authentication | +| **password** | `string` | optional | Password for authentication | +| **authSource** | `string` | optional | Authentication database (defaults to admin or database name) | +| **options** | `Record` | optional | Extra driver options (ssl, poolSize, etc) | + +--- + +## MongoDriverSpec + +The static definition of MongoDB driver capabilities. + +### Capabilities + +| Capability | Supported | Description | +| :--- | :--- | :--- | +| **transactions** | ✅ | ACID transactions | +| **fullTextSearch** | ✅ | Full-text search indexes | +| **geoSpatial** | ✅ | Geospatial queries | +| **aggregation** | ✅ | Aggregation pipeline | +| **mutableSchema** | ✅ | Dynamic schema changes | +| **jsonField** | ✅ | Native JSON/BSON storage | +| **crossObjectJoin** | ✅ | $lookup aggregation | + +--- + +## Examples + +### Basic Configuration + +```typescript +const config: MongoConfig = { + database: 'crm', + host: '127.0.0.1', + port: 27017, + username: 'app_user', + password: 'secure_password', +}; +``` + +### Connection URI + +```typescript +const config: MongoConfig = { + url: 'mongodb://user:password@localhost:27017/mydb', + database: 'mydb', // Still required +}; +``` + +### Replica Set Configuration + +```typescript +const config: MongoConfig = { + url: 'mongodb://node1:27017,node2:27017,node3:27017/mydb?replicaSet=rs0', + database: 'mydb', + options: { + replicaSet: 'rs0', + readPreference: 'secondaryPreferred', + }, +}; +``` + +### SSL Configuration + +```typescript +const config: MongoConfig = { + database: 'production', + host: 'mongodb.example.com', + port: 27017, + username: 'app_user', + password: 'secure_password', + authSource: 'admin', + options: { + ssl: true, + sslValidate: true, + sslCA: '/path/to/ca.pem', + }, +}; +``` + +### Connection Pool Options + +```typescript +const config: MongoConfig = { + database: 'myapp', + host: 'localhost', + port: 27017, + options: { + poolSize: 20, + socketTimeoutMS: 30000, + connectTimeoutMS: 10000, + serverSelectionTimeoutMS: 5000, + }, +}; +``` + +--- + +## Related + +- [Driver](/docs/references/data/driver) - Base driver protocol +- [Driver NoSQL](/docs/references/data/driver-nosql) - NoSQL driver interface +- [Datasource](/docs/references/system/datasource) - Datasource configuration diff --git a/content/docs/references/data/driver-postgres.mdx b/content/docs/references/data/driver-postgres.mdx new file mode 100644 index 000000000..a35c21af2 --- /dev/null +++ b/content/docs/references/data/driver-postgres.mdx @@ -0,0 +1,133 @@ +--- +title: PostgreSQL Driver +description: PostgreSQL driver configuration schema +--- + +# PostgreSQL Driver + + +**Source:** `packages/spec/src/data/driver/postgres.zod.ts` + + +## Overview + +PostgreSQL driver configuration schema defines connection settings and options specific to PostgreSQL databases. + +## TypeScript Usage + +```typescript +import { PostgresConfigSchema } from '@objectstack/spec/data/driver/postgres'; +import type { PostgresConfig } from '@objectstack/spec/data/driver/postgres'; + +// Validate PostgreSQL configuration +const config: PostgresConfig = PostgresConfigSchema.parse({ + database: 'myapp', + host: 'localhost', + port: 5432, + username: 'postgres', + password: 'secret', + ssl: false, +}); +``` + +--- + +## PostgresConfig + +PostgreSQL connection configuration schema. + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **url** | `string` | optional | Connection URI. Format: postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...] | +| **database** | `string` | ✅ | Database name | +| **host** | `string` | optional | Hostname or IP address (default: 'localhost') | +| **port** | `number` | optional | Port number (default: 5432) | +| **username** | `string` | optional | Authentication username | +| **password** | `string` | optional | Authentication password | +| **schema** | `string` | optional | Default schema for tables (default: 'public') | +| **ssl** | `boolean \| object` | optional | Enable SSL/TLS connection | +| **applicationName** | `string` | optional | Sets the application_name configuration parameter | +| **max** | `number` | optional | Maximum number of clients in pool (default: 10) | +| **min** | `number` | optional | Minimum number of clients in pool (default: 0) | +| **idleTimeoutMillis** | `number` | optional | Idle timeout in milliseconds | +| **connectionTimeoutMillis** | `number` | optional | Connection timeout in milliseconds | +| **statementTimeout** | `number` | optional | Statement timeout in milliseconds | + +### SSL Configuration + +When `ssl` is an object, it can contain: + +| Property | Type | Description | +| :--- | :--- | :--- | +| **rejectUnauthorized** | `boolean` | Whether to reject unauthorized certificates | +| **ca** | `string` | CA certificate | +| **key** | `string` | Client key | +| **cert** | `string` | Client certificate | + +--- + +## Examples + +### Basic Configuration + +```typescript +const config: PostgresConfig = { + database: 'crm', + host: 'localhost', + port: 5432, + username: 'app_user', + password: 'secure_password', +}; +``` + +### Connection URI + +```typescript +const config: PostgresConfig = { + url: 'postgresql://user:password@localhost:5432/mydb', + database: 'mydb', // Required even with URL +}; +``` + +### SSL Configuration + +```typescript +const config: PostgresConfig = { + database: 'production', + host: 'db.example.com', + port: 5432, + username: 'app_user', + password: 'secure_password', + ssl: { + rejectUnauthorized: true, + ca: '-----BEGIN CERTIFICATE-----\n...', + }, +}; +``` + +### Connection Pool Configuration + +```typescript +const config: PostgresConfig = { + database: 'myapp', + host: 'localhost', + port: 5432, + username: 'postgres', + password: 'secret', + max: 20, // Max 20 connections + min: 5, // Keep at least 5 connections + idleTimeoutMillis: 30000, // Close idle connections after 30s + connectionTimeoutMillis: 2000, // Timeout connection attempts after 2s + statementTimeout: 5000, // Abort slow queries after 5s +}; +``` + +--- + +## Related + +- [Driver](/docs/references/data/driver) - Base driver protocol +- [Driver SQL](/docs/references/data/driver-sql) - SQL driver interface +- [Datasource](/docs/references/system/datasource) - Datasource configuration diff --git a/content/docs/references/integration/connector-file-storage.mdx b/content/docs/references/integration/connector-file-storage.mdx new file mode 100644 index 000000000..0a007c5ea --- /dev/null +++ b/content/docs/references/integration/connector-file-storage.mdx @@ -0,0 +1,381 @@ +--- +title: File Storage Connector +description: File storage system connector protocol +--- + +# File Storage Connector + + +**Source:** `packages/spec/src/integration/connector/file-storage.zod.ts` + + +## Overview + +File Storage Connector protocol defines a specialized connector for file storage systems (S3, Azure Blob, Google Cloud Storage, etc.). It extends the base connector with file-specific features like multipart uploads, versioning, and metadata extraction. + +## TypeScript Usage + +```typescript +import { + FileStorageConnectorSchema, + FileStorageProviderSchema, + StorageBucketSchema, + FileMetadataConfigSchema, + MultipartUploadConfigSchema +} from '@objectstack/spec/integration/connector/file-storage'; + +import type { + FileStorageConnector, + FileStorageProvider, + StorageBucket, + FileMetadataConfig +} from '@objectstack/spec/integration/connector/file-storage'; +``` + +--- + +## Schemas + +### FileStorageProvider + +Supported file storage providers. + +**Values:** +- `s3` - Amazon S3 +- `azure_blob` - Azure Blob Storage +- `gcs` - Google Cloud Storage +- `dropbox` - Dropbox +- `box` - Box +- `onedrive` - Microsoft OneDrive +- `google_drive` - Google Drive +- `sharepoint` - SharePoint +- `ftp` - FTP/SFTP +- `local` - Local file system +- `custom` - Custom file storage + +--- + +### FileAccessPattern + +File access control patterns. + +**Values:** +- `public_read` - Public read access +- `private` - Private access +- `authenticated_read` - Requires authentication +- `bucket_owner_read` - Bucket owner has read access +- `bucket_owner_full` - Bucket owner has full control + +--- + +### StorageBucket + +Bucket/container configuration for file storage. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **name** | `string` | ✅ | Bucket identifier in ObjectStack (snake_case) | +| **label** | `string` | ✅ | Display label | +| **bucketName** | `string` | ✅ | Actual bucket/container name in storage system | +| **region** | `string` | optional | Storage region | +| **enabled** | `boolean` | optional | Enable sync for this bucket (default: true) | +| **prefix** | `string` | optional | Prefix/path within bucket | +| **accessPattern** | `FileAccessPattern` | optional | Access pattern | +| **fileFilters** | `FileFilterConfig` | optional | File filter configuration | + +--- + +### FileMetadataConfig + +File metadata extraction configuration. + +#### Properties + +| Property | Type | Required | Default | Description | +| :--- | :--- | :--- | :--- | :--- | +| **extractMetadata** | `boolean` | optional | true | Extract file metadata | +| **metadataFields** | `string[]` | optional | - | Metadata fields to extract | +| **customMetadata** | `Record` | optional | - | Custom metadata key-value pairs | + +**Metadata Fields:** +- `content_type` - MIME type +- `file_size` - File size in bytes +- `last_modified` - Last modification date +- `etag` - Entity tag +- `checksum` - File checksum +- `creator` - File creator +- `created_at` - Creation date +- `custom` - Custom metadata + +--- + +### MultipartUploadConfig + +Multipart upload configuration for large files. + +#### Properties + +| Property | Type | Required | Default | Description | +| :--- | :--- | :--- | :--- | :--- | +| **enabled** | `boolean` | optional | true | Enable multipart uploads | +| **partSize** | `number` | optional | 5MB | Part size in bytes (min 5MB) | +| **maxConcurrentParts** | `number` | optional | 5 | Maximum concurrent part uploads | +| **threshold** | `number` | optional | 100MB | File size threshold for multipart upload | + +--- + +### FileVersioningConfig + +File versioning configuration. + +#### Properties + +| Property | Type | Required | Default | Description | +| :--- | :--- | :--- | :--- | :--- | +| **enabled** | `boolean` | optional | false | Enable file versioning | +| **maxVersions** | `number` | optional | - | Maximum versions to retain (1-100) | +| **retentionDays** | `number` | optional | - | Version retention period in days | + +--- + +## FileStorageConnector + +Complete file storage connector configuration. + +### Additional Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `'file_storage'` | ✅ | Connector type | +| **provider** | `FileStorageProvider` | ✅ | File storage provider | +| **buckets** | `StorageBucket[]` | ✅ | Buckets/containers to sync | +| **storageConfig** | `object` | optional | Storage configuration | +| **metadataConfig** | `FileMetadataConfig` | optional | Metadata extraction | +| **multipartConfig** | `MultipartUploadConfig` | optional | Multipart upload | +| **versioningConfig** | `FileVersioningConfig` | optional | File versioning | +| **encryption** | `object` | optional | Server-side encryption | +| **lifecyclePolicy** | `object` | optional | File lifecycle policy | +| **contentProcessing** | `object` | optional | Content processing | + +--- + +## Examples + +### Amazon S3 Connector + +```typescript +const s3Connector: FileStorageConnector = { + name: 's3_documents', + label: 'S3 Document Storage', + type: 'file_storage', + provider: 's3', + enabled: true, + + storageConfig: { + region: 'us-east-1', + }, + + buckets: [ + { + name: 'customer_documents', + label: 'Customer Documents', + bucketName: 'myapp-customer-docs', + region: 'us-east-1', + enabled: true, + prefix: 'documents/', + accessPattern: 'private', + } + ], + + metadataConfig: { + extractMetadata: true, + metadataFields: ['content_type', 'file_size', 'last_modified'], + }, + + multipartConfig: { + enabled: true, + partSize: 10 * 1024 * 1024, // 10MB + maxConcurrentParts: 5, + threshold: 100 * 1024 * 1024, // 100MB + }, + + encryption: { + enabled: true, + algorithm: 'aws:kms', + kmsKeyId: 'arn:aws:kms:us-east-1:123456789:key/abc-123', + }, +}; +``` + +### Azure Blob Storage + +```typescript +const azureConnector: FileStorageConnector = { + name: 'azure_files', + label: 'Azure File Storage', + type: 'file_storage', + provider: 'azure_blob', + enabled: true, + + buckets: [ + { + name: 'attachments', + label: 'Email Attachments', + bucketName: 'email-attachments', + enabled: true, + accessPattern: 'private', + } + ], + + versioningConfig: { + enabled: true, + maxVersions: 10, + retentionDays: 90, + }, +}; +``` + +### Google Cloud Storage + +```typescript +const gcsConnector: FileStorageConnector = { + name: 'gcs_media', + label: 'GCS Media Storage', + type: 'file_storage', + provider: 'gcs', + enabled: true, + + buckets: [ + { + name: 'user_uploads', + label: 'User Uploads', + bucketName: 'myapp-user-uploads', + enabled: true, + accessPattern: 'authenticated_read', + fileFilters: { + allowedExtensions: ['.jpg', '.png', '.pdf'], + maxFileSize: 50 * 1024 * 1024, // 50MB + }, + } + ], + + contentProcessing: { + generateThumbnails: true, + thumbnailSizes: [ + { width: 150, height: 150 }, + { width: 300, height: 300 }, + ], + virusScan: true, + }, +}; +``` + +### With File Filters + +```typescript +const filteredConnector: FileStorageConnector = { + name: 'filtered_storage', + label: 'Filtered Storage', + type: 'file_storage', + provider: 's3', + enabled: true, + + buckets: [ + { + name: 'documents', + label: 'Documents', + bucketName: 'my-docs', + enabled: true, + fileFilters: { + includePatterns: ['*.pdf', '*.docx', '*.xlsx'], + excludePatterns: ['temp/*', '*/draft/*'], + minFileSize: 1024, // 1KB + maxFileSize: 100 * 1024 * 1024, // 100MB + allowedExtensions: ['.pdf', '.docx', '.xlsx'], + blockedExtensions: ['.exe', '.bat', '.sh'], + }, + } + ], +}; +``` + +### With Lifecycle Policy + +```typescript +const lifecycleConnector: FileStorageConnector = { + name: 'archive_storage', + label: 'Archive Storage', + type: 'file_storage', + provider: 's3', + enabled: true, + + buckets: [ + { + name: 'logs', + label: 'Application Logs', + bucketName: 'app-logs', + enabled: true, + } + ], + + lifecyclePolicy: { + enabled: true, + archiveAfterDays: 30, + deleteAfterDays: 365, + }, +}; +``` + +--- + +## Best Practices + +### 1. Enable Multipart for Large Files + +```typescript +multipartConfig: { + enabled: true, + partSize: 10 * 1024 * 1024, // 10MB parts + threshold: 50 * 1024 * 1024, // Use multipart for files > 50MB +} +``` + +### 2. Use File Filters + +```typescript +fileFilters: { + allowedExtensions: ['.pdf', '.jpg', '.png'], + maxFileSize: 100 * 1024 * 1024, // 100MB limit + excludePatterns: ['temp/*', '*/cache/*'], +} +``` + +### 3. Enable Encryption + +```typescript +encryption: { + enabled: true, + algorithm: 'aws:kms', // Use KMS for better security + kmsKeyId: 'your-kms-key-id', +} +``` + +### 4. Configure Versioning Carefully + +```typescript +versioningConfig: { + enabled: true, + maxVersions: 5, // Keep only recent versions + retentionDays: 30, // Auto-delete old versions +} +``` + +--- + +## Related + +- [Connector](/docs/references/integration/connector) - Base connector protocol +- [Object Storage](/docs/references/integration/object-storage) - Object storage integration +- [Datasource](/docs/references/system/datasource) - Datasource configuration diff --git a/content/docs/references/system/metadata-loader.mdx b/content/docs/references/system/metadata-loader.mdx new file mode 100644 index 000000000..44ed1bcfc --- /dev/null +++ b/content/docs/references/system/metadata-loader.mdx @@ -0,0 +1,327 @@ +--- +title: Metadata Loader +description: Metadata loading and persistence protocol +--- + +# Metadata Loader + + +**Source:** `packages/spec/src/system/metadata-loader.zod.ts` + + +## Overview + +Metadata Loader protocol defines the standard interface for loading and saving metadata in ObjectStack. It enables consistent metadata operations across different storage backends (filesystem, HTTP, S3, databases) and serialization formats (JSON, YAML, TypeScript). + +## TypeScript Usage + +```typescript +import { + MetadataFormatSchema, + MetadataStatsSchema, + MetadataLoadOptionsSchema +} from '@objectstack/spec/system'; + +import type { + MetadataFormat, + MetadataStats, + MetadataLoadOptions +} from '@objectstack/spec/system'; + +// Load metadata with options +const options: MetadataLoadOptions = { + patterns: ['**/*.object.ts', '**/*.object.json'], + validate: true, + useCache: true, + recursive: true +}; +``` + +--- + +## Schemas + +### MetadataFormat + +Supported serialization formats for metadata. + +**Values:** +- `json` - JSON format +- `yaml` - YAML format +- `typescript` - TypeScript files (.ts) +- `javascript` - JavaScript files (.js) + +#### Example + +```typescript +const format: MetadataFormat = 'json'; +``` + +--- + +### MetadataStats + +Information about a metadata item without loading its full content. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **size** | `number` | ✅ | File size in bytes | +| **modifiedAt** | `Date` | ✅ | Last modified date | +| **etag** | `string` | ✅ | Entity tag for cache validation | +| **format** | `MetadataFormat` | ✅ | Serialization format | +| **path** | `string` | optional | File system path | +| **metadata** | `Record` | optional | Provider-specific metadata | + +#### Example + +```json +{ + "size": 2048, + "modifiedAt": "2026-01-31T08:00:00Z", + "etag": "abc123def456", + "format": "json", + "path": "/app/metadata/customer.object.json" +} +``` + +--- + +### MetadataLoadOptions + +Options for loading metadata. + +#### Properties + +| Property | Type | Required | Default | Description | +| :--- | :--- | :--- | :--- | :--- | +| **patterns** | `string[]` | optional | - | File glob patterns to match | +| **ifNoneMatch** | `string` | optional | - | ETag for conditional request | +| **ifModifiedSince** | `Date` | optional | - | Only load if modified after this date | +| **validate** | `boolean` | optional | true | Validate against schema | +| **useCache** | `boolean` | optional | true | Enable caching | +| **filter** | `string` | optional | - | Filter predicate as string | +| **limit** | `number` | optional | - | Maximum items to load | +| **recursive** | `boolean` | optional | true | Search subdirectories | + +#### Example + +```typescript +const options: MetadataLoadOptions = { + patterns: ['**/*.object.ts', '**/*.object.json'], + ifModifiedSince: new Date('2026-01-01'), + validate: true, + useCache: true, + recursive: true, + limit: 100 +}; +``` + +--- + +## Usage Examples + +### Load All Objects + +```typescript +import { MetadataLoader } from '@objectstack/core'; + +const loader = new MetadataLoader(); + +const objects = await loader.load({ + patterns: ['**/*.object.ts'], + validate: true, + recursive: true +}); + +console.log(`Loaded ${objects.length} objects`); +``` + +### Load with Filtering + +```typescript +const objects = await loader.load({ + patterns: ['**/*.object.ts'], + filter: "(item) => item.name.startsWith('sys_')", + limit: 50 +}); +``` + +### Conditional Loading (Cache Validation) + +```typescript +const stats = await loader.getStats('/metadata/customer.object.json'); + +const objects = await loader.load({ + patterns: ['customer.object.json'], + ifNoneMatch: stats.etag, + ifModifiedSince: stats.modifiedAt +}); + +// Returns empty if not modified +if (objects.length === 0) { + console.log('Using cached data'); +} +``` + +### Load from Multiple Formats + +```typescript +const metadata = await loader.load({ + patterns: [ + '**/*.object.ts', // TypeScript + '**/*.object.json', // JSON + '**/*.object.yaml' // YAML + ], + validate: true +}); +``` + +### Get Metadata Statistics + +```typescript +const stats = await loader.getStats('/metadata/customer.object.json'); + +console.log(`Size: ${stats.size} bytes`); +console.log(`Modified: ${stats.modifiedAt}`); +console.log(`Format: ${stats.format}`); +console.log(`ETag: ${stats.etag}`); +``` + +--- + +## Glob Patterns + +### Common Patterns + +```typescript +// All object files +patterns: ['**/*.object.ts'] + +// All files in specific directory +patterns: ['src/domains/**/*.object.ts'] + +// Multiple file types +patterns: ['**/*.object.{ts,json,yaml}'] + +// Exclude patterns (using filter) +patterns: ['**/*.object.ts'] +filter: "(item) => !item.name.startsWith('test_')" +``` + +### Pattern Examples + +| Pattern | Matches | +| :--- | :--- | +| `**/*.object.ts` | All .object.ts files recursively | +| `src/**/*.object.ts` | All .object.ts in src/ directory | +| `*.object.json` | .object.json files in current directory only | +| `{a,b}/*.ts` | .ts files in a/ or b/ directories | + +--- + +## Best Practices + +### 1. Use Specific Patterns + +```typescript +// ✅ Good - specific patterns +patterns: ['src/domains/**/*.object.ts'] + +// ❌ Avoid - too broad +patterns: ['**/*'] +``` + +### 2. Enable Validation + +```typescript +// ✅ Always validate in production +const options: MetadataLoadOptions = { + validate: true, + patterns: ['**/*.object.ts'] +}; +``` + +### 3. Use Caching + +```typescript +// ✅ Enable caching for performance +const options: MetadataLoadOptions = { + useCache: true, + ifModifiedSince: lastLoadTime +}; +``` + +### 4. Limit Results for Large Datasets + +```typescript +// ✅ Use pagination for large datasets +const options: MetadataLoadOptions = { + patterns: ['**/*.object.ts'], + limit: 100 +}; +``` + +### 5. Handle Load Errors + +```typescript +try { + const metadata = await loader.load(options); + // Process metadata +} catch (error) { + if (error.code === 'VALIDATION_FAILED') { + // Handle validation errors + console.error('Invalid metadata:', error.details); + } else { + // Handle other errors + throw error; + } +} +``` + +--- + +## Storage Backends + +### File System + +```typescript +const loader = new FileSystemMetadataLoader({ + basePath: '/app/metadata' +}); +``` + +### HTTP/Remote + +```typescript +const loader = new HttpMetadataLoader({ + baseUrl: 'https://api.example.com/metadata' +}); +``` + +### S3 + +```typescript +const loader = new S3MetadataLoader({ + bucket: 'my-metadata-bucket', + prefix: 'metadata/' +}); +``` + +### Database + +```typescript +const loader = new DatabaseMetadataLoader({ + table: 'metadata', + connectionString: 'postgresql://...' +}); +``` + +--- + +## Related + +- [Manifest](/docs/references/system/manifest) - Plugin manifest specification +- [Plugin](/docs/references/system/plugin) - Plugin protocol +- [Object](/docs/references/data/object) - Object metadata schema diff --git a/content/docs/references/system/plugin-lifecycle-events.mdx b/content/docs/references/system/plugin-lifecycle-events.mdx new file mode 100644 index 000000000..4965dc895 --- /dev/null +++ b/content/docs/references/system/plugin-lifecycle-events.mdx @@ -0,0 +1,309 @@ +--- +title: Plugin Lifecycle Events +description: Plugin lifecycle event schemas +--- + +# Plugin Lifecycle Events + + +**Source:** `packages/spec/src/system/plugin-lifecycle-events.zod.ts` + + +## Overview + +Plugin lifecycle events protocol defines Zod schemas for all event data structures emitted during plugin lifecycle phases. These schemas provide runtime validation and type safety for event handlers. + +## TypeScript Usage + +```typescript +import { + PluginRegisteredEventSchema, + PluginLifecyclePhaseEventSchema, + PluginErrorEventSchema, + ServiceRegisteredEventSchema, + HookRegisteredEventSchema, + KernelReadyEventSchema, + PluginLifecycleEventType +} from '@objectstack/spec/system'; + +import type { + PluginRegisteredEvent, + PluginLifecyclePhaseEvent, + PluginErrorEvent, + EventPhase +} from '@objectstack/spec/system'; + +// Validate event data +const event = PluginRegisteredEventSchema.parse({ + pluginName: 'crm-plugin', + timestamp: Date.now(), + version: '1.0.0' +}); +``` + +--- + +## Event Types + +### PluginLifecycleEventType + +Enumeration of all possible plugin lifecycle event types: + +**Kernel Events:** +- `kernel:ready` - Kernel initialization completed +- `kernel:shutdown` - Kernel is shutting down +- `kernel:before-init` - Before kernel initialization +- `kernel:after-init` - After kernel initialization + +**Plugin Events:** +- `plugin:registered` - Plugin registered with kernel +- `plugin:before-init` - Before plugin initialization +- `plugin:init` - During plugin initialization +- `plugin:after-init` - After plugin initialization +- `plugin:before-start` - Before plugin startup +- `plugin:started` - Plugin started successfully +- `plugin:after-start` - After plugin startup +- `plugin:before-destroy` - Before plugin destruction +- `plugin:destroyed` - Plugin destroyed +- `plugin:after-destroy` - After plugin destruction +- `plugin:error` - Plugin encountered an error + +**Service Events:** +- `service:registered` - Service registered in DI container +- `service:unregistered` - Service unregistered from DI container + +**Hook Events:** +- `hook:registered` - Hook registered +- `hook:triggered` - Hook triggered + +--- + +## Event Schemas + +### PluginRegisteredEvent + +Emitted when a plugin is registered with the kernel. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **pluginName** | `string` | ✅ | Name of the plugin | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds | +| **version** | `string` | optional | Plugin version | + +#### Example + +```json +{ + "pluginName": "crm-plugin", + "timestamp": 1706659200000, + "version": "1.0.0" +} +``` + +--- + +### PluginLifecyclePhaseEvent + +Emitted during plugin lifecycle phases (init, start, destroy). + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **pluginName** | `string` | ✅ | Name of the plugin | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds | +| **duration** | `number` | optional | Duration of the phase in milliseconds | +| **phase** | `'init' \| 'start' \| 'destroy'` | optional | Lifecycle phase | + +#### Example + +```json +{ + "pluginName": "crm-plugin", + "timestamp": 1706659200000, + "duration": 1250, + "phase": "init" +} +``` + +--- + +### PluginErrorEvent + +Emitted when a plugin encounters an error. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **pluginName** | `string` | ✅ | Name of the plugin | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds | +| **error** | `Error` | ✅ | Error object | +| **phase** | `'init' \| 'start' \| 'destroy'` | ✅ | Lifecycle phase where error occurred | +| **errorMessage** | `string` | optional | Error message (for serialization) | +| **errorStack** | `string` | optional | Error stack trace (for debugging) | + +#### Example + +```json +{ + "pluginName": "crm-plugin", + "timestamp": 1706659200000, + "error": "Error: Connection failed", + "phase": "start", + "errorMessage": "Connection failed", + "errorStack": "Error: Connection failed\n at ..." +} +``` + +--- + +### ServiceRegisteredEvent + +Emitted when a service is registered in the DI container. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **serviceName** | `string` | ✅ | Name of the registered service | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds | +| **serviceType** | `string` | optional | Type or interface name of the service | + +#### Example + +```json +{ + "serviceName": "database", + "timestamp": 1706659200000, + "serviceType": "IDataEngine" +} +``` + +--- + +### ServiceUnregisteredEvent + +Emitted when a service is unregistered from the DI container. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **serviceName** | `string` | ✅ | Name of the unregistered service | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds | + +#### Example + +```json +{ + "serviceName": "database", + "timestamp": 1706659200000 +} +``` + +--- + +### HookRegisteredEvent + +Emitted when a hook is registered. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **hookName** | `string` | ✅ | Name of the hook | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds | +| **handlerCount** | `number` | ✅ | Number of handlers registered for this hook | + +#### Example + +```json +{ + "hookName": "data.beforeInsert", + "timestamp": 1706659200000, + "handlerCount": 3 +} +``` + +--- + +### HookTriggeredEvent + +Emitted when a hook is triggered. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **hookName** | `string` | ✅ | Name of the hook | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds | +| **args** | `any[]` | ✅ | Arguments passed to hook handlers | +| **handlerCount** | `number` | optional | Number of handlers that will handle this event | + +#### Example + +```json +{ + "hookName": "data.beforeInsert", + "timestamp": 1706659200000, + "args": [{ "object": "customer", "data": {...} }], + "handlerCount": 3 +} +``` + +--- + +### KernelReadyEvent + +Emitted when the kernel initialization is complete. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds | +| **duration** | `number` | optional | Total initialization duration in milliseconds | +| **pluginCount** | `number` | optional | Number of plugins initialized | + +#### Example + +```json +{ + "timestamp": 1706659200000, + "duration": 5400, + "pluginCount": 12 +} +``` + +--- + +### KernelShutdownEvent + +Emitted when the kernel is shutting down. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds | +| **reason** | `string` | optional | Reason for kernel shutdown | + +#### Example + +```json +{ + "timestamp": 1706659200000, + "reason": "SIGTERM received" +} +``` + +--- + +## Related + +- [Plugin](/docs/references/system/plugin) - Plugin protocol +- [Startup Orchestrator](/docs/references/system/startup-orchestrator) - Plugin startup orchestration +- [Events](/docs/references/system/events) - Event system protocol diff --git a/content/docs/references/system/plugin-validator.mdx b/content/docs/references/system/plugin-validator.mdx new file mode 100644 index 000000000..8f59deeae --- /dev/null +++ b/content/docs/references/system/plugin-validator.mdx @@ -0,0 +1,297 @@ +--- +title: Plugin Validator +description: Plugin validation protocol +--- + +# Plugin Validator + + +**Source:** `packages/spec/src/system/plugin-validator.zod.ts` + + +## Overview + +Plugin Validator protocol defines schemas for validating plugin configurations, manifests, and metadata. It provides structured error and warning reporting for plugin validation operations. + +## TypeScript Usage + +```typescript +import { + ValidationErrorSchema, + ValidationWarningSchema, + ValidationResultSchema +} from '@objectstack/spec/system'; + +import type { + ValidationError, + ValidationWarning, + ValidationResult +} from '@objectstack/spec/system'; + +// Validate plugin +const result: ValidationResult = { + valid: false, + errors: [{ + field: 'name', + message: 'Plugin name is required', + code: 'REQUIRED_FIELD' + }], + warnings: [{ + field: 'description', + message: 'Description is recommended', + code: 'MISSING_DESCRIPTION' + }] +}; +``` + +--- + +## Schemas + +### ValidationError + +Represents a single validation error that prevents plugin from loading. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **field** | `string` | ✅ | Field name that failed validation | +| **message** | `string` | ✅ | Human-readable error message | +| **code** | `string` | optional | Machine-readable error code | + +#### Example + +```json +{ + "field": "version", + "message": "Invalid semver format", + "code": "INVALID_VERSION" +} +``` + +--- + +### ValidationWarning + +Represents a non-fatal validation warning. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **field** | `string` | ✅ | Field name with warning | +| **message** | `string` | ✅ | Human-readable warning message | +| **code** | `string` | optional | Machine-readable warning code | + +#### Example + +```json +{ + "field": "description", + "message": "Description is empty", + "code": "MISSING_DESCRIPTION" +} +``` + +--- + +### ValidationResult + +Result of plugin validation operation. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **valid** | `boolean` | ✅ | Whether the plugin passed validation | +| **errors** | `ValidationError[]` | optional | Validation errors (block loading) | +| **warnings** | `ValidationWarning[]` | optional | Validation warnings (non-blocking) | + +#### Example + +```json +{ + "valid": false, + "errors": [{ + "field": "name", + "message": "Plugin name is required", + "code": "REQUIRED_FIELD" + }], + "warnings": [{ + "field": "description", + "message": "Description is recommended", + "code": "MISSING_DESCRIPTION" + }] +} +``` + +--- + +## Common Error Codes + +### Required Field Errors + +| Code | Description | +| :--- | :--- | +| `REQUIRED_FIELD` | Required field is missing | +| `INVALID_FORMAT` | Field format is invalid | +| `INVALID_TYPE` | Field has wrong type | + +### Version Errors + +| Code | Description | +| :--- | :--- | +| `INVALID_VERSION` | Version is not valid semver | +| `VERSION_MISMATCH` | Version conflicts with dependencies | + +### Dependency Errors + +| Code | Description | +| :--- | :--- | +| `MISSING_DEPENDENCY` | Required dependency not found | +| `CIRCULAR_DEPENDENCY` | Circular dependency detected | +| `INCOMPATIBLE_VERSION` | Dependency version incompatible | + +### Capability Errors + +| Code | Description | +| :--- | :--- | +| `INVALID_CAPABILITY` | Unknown capability declared | +| `CAPABILITY_CONFLICT` | Conflicting capabilities | + +--- + +## Usage Examples + +### Basic Validation + +```typescript +import { PluginValidator } from '@objectstack/core'; + +const validator = new PluginValidator(); + +const result = validator.validate(pluginManifest); + +if (!result.valid) { + console.error('Validation failed:'); + result.errors?.forEach(err => { + console.error(`- ${err.field}: ${err.message} (${err.code})`); + }); +} + +if (result.warnings?.length) { + console.warn('Validation warnings:'); + result.warnings.forEach(warn => { + console.warn(`- ${warn.field}: ${warn.message}`); + }); +} +``` + +### Validation with Error Handling + +```typescript +try { + const result = validator.validate(pluginManifest); + + if (!result.valid) { + throw new ValidationError('Plugin validation failed', result.errors); + } + + // Continue with plugin loading + await loadPlugin(pluginManifest); +} catch (error) { + if (error instanceof ValidationError) { + // Handle validation errors + logValidationErrors(error.errors); + } else { + // Handle other errors + throw error; + } +} +``` + +### Custom Validation Rules + +```typescript +const validator = new PluginValidator({ + customRules: [ + { + field: 'name', + validate: (value) => { + if (!value.startsWith('objectstack-')) { + return { + valid: false, + error: { + field: 'name', + message: 'Plugin name must start with "objectstack-"', + code: 'INVALID_PREFIX' + } + }; + } + return { valid: true }; + } + } + ] +}); +``` + +--- + +## Best Practices + +### 1. Always Check Validation Results + +```typescript +const result = validator.validate(manifest); + +if (!result.valid) { + // Never ignore validation errors + throw new Error('Plugin validation failed'); +} +``` + +### 2. Log Warnings + +```typescript +if (result.warnings?.length) { + // Log warnings for debugging + logger.warn('Plugin validation warnings:', result.warnings); +} +``` + +### 3. Use Error Codes + +```typescript +// Check specific error codes +const hasVersionError = result.errors?.some(e => + e.code === 'INVALID_VERSION' +); + +if (hasVersionError) { + // Handle version errors specifically +} +``` + +### 4. Provide Helpful Error Messages + +```typescript +const customValidator = { + validate: (value) => ({ + valid: false, + error: { + field: 'dependencies', + message: 'Dependency "@objectstack/core" version "^2.0.0" is incompatible with current version "1.5.0"', + code: 'INCOMPATIBLE_VERSION' + } + }) +}; +``` + +--- + +## Related + +- [Plugin](/docs/references/system/plugin) - Plugin protocol +- [Manifest](/docs/references/system/manifest) - Plugin manifest specification +- [Plugin Capability](/docs/references/system/plugin-capability) - Plugin capabilities diff --git a/content/docs/references/system/service-registry.mdx b/content/docs/references/system/service-registry.mdx new file mode 100644 index 000000000..7b70885fe --- /dev/null +++ b/content/docs/references/system/service-registry.mdx @@ -0,0 +1,425 @@ +--- +title: Service Registry +description: Service registry protocol +--- + +# Service Registry + + +**Source:** `packages/spec/src/system/service-registry.zod.ts` + + +## Overview + +Service Registry protocol defines schemas for service registration, dependency injection, and service discovery. It provides configuration and metadata structures for managing services in the ObjectStack kernel. + +## TypeScript Usage + +```typescript +import { + ServiceMetadataSchema, + ServiceRegistryConfigSchema, + ServiceScopeType +} from '@objectstack/spec/system'; + +import type { + ServiceMetadata, + ServiceRegistryConfig, + ServiceScopeType +} from '@objectstack/spec/system'; +``` + +--- + +## Schemas + +### ServiceScopeType + +Different service scoping strategies. + +**Values:** +- `singleton` - Single instance shared across the application +- `transient` - New instance created each time +- `scoped` - Instance per scope (request, session, transaction, etc.) + +#### Example + +```typescript +const scope: ServiceScopeType = 'singleton'; +``` + +--- + +### ServiceMetadata + +Metadata about a registered service. + +#### Properties + +| Property | Type | Required | Default | Description | +| :--- | :--- | :--- | :--- | :--- | +| **name** | `string` | ✅ | - | Unique service name identifier | +| **scope** | `ServiceScopeType` | optional | 'singleton' | Service scope type | +| **type** | `string` | optional | - | Service type or interface name | +| **registeredAt** | `number` | optional | - | Unix timestamp in milliseconds when service was registered | +| **metadata** | `Record` | optional | - | Additional service-specific metadata | + +#### Example + +```json +{ + "name": "database", + "scope": "singleton", + "type": "IDataEngine", + "registeredAt": 1706659200000, + "metadata": { + "driver": "postgres", + "version": "1.0.0" + } +} +``` + +--- + +### ServiceRegistryConfig + +Configuration for service registry behavior. + +#### Properties + +| Property | Type | Required | Default | Description | +| :--- | :--- | :--- | :--- | :--- | +| **strictMode** | `boolean` | optional | true | Throw errors on invalid operations | +| **allowOverwrite** | `boolean` | optional | false | Allow overwriting existing service registrations | +| **enableLogging** | `boolean` | optional | true | Enable service registration/resolution logging | +| **scopeTypes** | `string[]` | optional | - | Custom scope types supported | + +#### Example + +```json +{ + "strictMode": true, + "allowOverwrite": false, + "enableLogging": true, + "scopeTypes": ["singleton", "transient", "request", "session"] +} +``` + +--- + +## Usage Examples + +### Register Singleton Service + +```typescript +import { ServiceRegistry } from '@objectstack/core'; + +const registry = new ServiceRegistry(); + +// Register a singleton service +registry.register('database', dataEngineInstance, { + scope: 'singleton', + type: 'IDataEngine', + metadata: { + driver: 'postgres', + connectionPool: true + } +}); +``` + +### Register Transient Service + +```typescript +// Register a transient service (new instance each time) +registry.register('logger', LoggerFactory, { + scope: 'transient', + type: 'ILogger' +}); +``` + +### Register Scoped Service + +```typescript +// Register a scoped service (per request) +registry.register('userContext', UserContextFactory, { + scope: 'scoped', + type: 'IUserContext', + metadata: { + scopeType: 'request' + } +}); +``` + +### Resolve Service + +```typescript +// Resolve a service +const database = registry.resolve('database'); + +// Resolve with type checking +const logger = registry.resolve('logger'); +``` + +### Get Service Metadata + +```typescript +const metadata = registry.getMetadata('database'); + +console.log(`Service: ${metadata.name}`); +console.log(`Type: ${metadata.type}`); +console.log(`Scope: ${metadata.scope}`); +console.log(`Registered at: ${new Date(metadata.registeredAt!)}`); +``` + +### List All Services + +```typescript +const services = registry.list(); + +services.forEach(metadata => { + console.log(`${metadata.name} (${metadata.scope})`); +}); +``` + +### Unregister Service + +```typescript +registry.unregister('database'); +``` + +--- + +## Service Scopes + +### Singleton + +Single instance shared across the entire application. + +```typescript +registry.register('config', configInstance, { + scope: 'singleton' +}); + +const config1 = registry.resolve('config'); +const config2 = registry.resolve('config'); +// config1 === config2 (same instance) +``` + +### Transient + +New instance created for each resolution. + +```typescript +registry.register('logger', LoggerFactory, { + scope: 'transient' +}); + +const logger1 = registry.resolve('logger'); +const logger2 = registry.resolve('logger'); +// logger1 !== logger2 (different instances) +``` + +### Scoped + +Instance per scope (request, session, transaction, etc.). + +```typescript +// Create a request scope +const requestScope = registry.createScope('request'); + +registry.register('userContext', UserContextFactory, { + scope: 'scoped' +}); + +// Same instance within scope +const ctx1 = requestScope.resolve('userContext'); +const ctx2 = requestScope.resolve('userContext'); +// ctx1 === ctx2 + +// Different instance in different scope +const requestScope2 = registry.createScope('request'); +const ctx3 = requestScope2.resolve('userContext'); +// ctx1 !== ctx3 +``` + +--- + +## Configuration Examples + +### Strict Mode + +```typescript +// Throw errors on invalid operations +const registry = new ServiceRegistry({ + strictMode: true +}); + +// This will throw if service doesn't exist +const service = registry.resolve('non-existent'); // Error! + +// This will throw if service already exists +registry.register('database', db1); +registry.register('database', db2); // Error! +``` + +### Permissive Mode + +```typescript +// Allow more flexible operations +const registry = new ServiceRegistry({ + strictMode: false, + allowOverwrite: true +}); + +// Returns undefined instead of throwing +const service = registry.resolve('non-existent'); // undefined + +// Allows overwriting +registry.register('database', db1); +registry.register('database', db2); // OK, replaces db1 +``` + +### Custom Scope Types + +```typescript +const registry = new ServiceRegistry({ + scopeTypes: ['singleton', 'transient', 'request', 'session', 'transaction'] +}); + +// Register with custom scope +registry.register('transactionManager', factory, { + scope: 'transaction' +}); +``` + +--- + +## Best Practices + +### 1. Use Singleton for Stateless Services + +```typescript +// ✅ Good - configuration is stateless and shared +registry.register('config', configInstance, { + scope: 'singleton' +}); + +// ✅ Good - shared connection pool +registry.register('database', dataEngine, { + scope: 'singleton' +}); +``` + +### 2. Use Transient for Stateful Services + +```typescript +// ✅ Good - each logger can have its own state +registry.register('logger', LoggerFactory, { + scope: 'transient' +}); +``` + +### 3. Use Scoped for Request-Specific Data + +```typescript +// ✅ Good - user context per request +registry.register('userContext', UserContextFactory, { + scope: 'scoped', + metadata: { scopeType: 'request' } +}); + +// ✅ Good - transaction per request +registry.register('transaction', TransactionFactory, { + scope: 'scoped', + metadata: { scopeType: 'request' } +}); +``` + +### 4. Include Type Information + +```typescript +// ✅ Good - document the interface +registry.register('database', dataEngine, { + scope: 'singleton', + type: 'IDataEngine', + metadata: { + driver: 'postgres', + version: '1.0.0' + } +}); +``` + +### 5. Handle Resolution Errors + +```typescript +try { + const service = registry.resolve('database'); +} catch (error) { + if (error.code === 'SERVICE_NOT_FOUND') { + console.error('Database service not registered'); + } else { + throw error; + } +} +``` + +--- + +## Dependency Injection Patterns + +### Constructor Injection + +```typescript +class UserService { + constructor( + private database: IDataEngine, + private logger: ILogger + ) {} +} + +// Register factory that resolves dependencies +registry.register('userService', () => { + const database = registry.resolve('database'); + const logger = registry.resolve('logger'); + return new UserService(database, logger); +}, { scope: 'singleton' }); +``` + +### Property Injection + +```typescript +class OrderService { + database?: IDataEngine; + logger?: ILogger; + + init() { + this.database = registry.resolve('database'); + this.logger = registry.resolve('logger'); + } +} +``` + +### Method Injection + +```typescript +class ReportService { + generate(database: IDataEngine, logger: ILogger) { + // Use injected dependencies + } +} + +// Resolve and inject at call time +const reportService = new ReportService(); +reportService.generate( + registry.resolve('database'), + registry.resolve('logger') +); +``` + +--- + +## Related + +- [Plugin](/docs/references/system/plugin) - Plugin protocol +- [Startup Orchestrator](/docs/references/system/startup-orchestrator) - Plugin startup +- [Plugin Lifecycle Events](/docs/references/system/plugin-lifecycle-events) - Service registration events diff --git a/content/docs/references/system/startup-orchestrator.mdx b/content/docs/references/system/startup-orchestrator.mdx new file mode 100644 index 000000000..01aa874b8 --- /dev/null +++ b/content/docs/references/system/startup-orchestrator.mdx @@ -0,0 +1,299 @@ +--- +title: Startup Orchestrator +description: Plugin startup orchestration protocol +--- + +# Startup Orchestrator + + +**Source:** `packages/spec/src/system/startup-orchestrator.zod.ts` + + +## Overview + +Startup Orchestrator protocol defines schemas for orchestrating plugin startup sequences, including dependency resolution, health checks, and rollback on failure. + +## TypeScript Usage + +```typescript +import { + StartupOptionsSchema, + HealthStatusSchema, + PluginStartupResultSchema, + StartupOrchestrationResultSchema +} from '@objectstack/spec/system'; + +import type { + StartupOptions, + StartupOptionsInput, + HealthStatus, + PluginStartupResult, + StartupOrchestrationResult +} from '@objectstack/spec/system'; + +// Configure startup options +const options: StartupOptionsInput = { + timeout: 30000, + rollbackOnFailure: true, + healthCheck: true, + parallel: false +}; + +const validated = StartupOptionsSchema.parse(options); +``` + +--- + +## Schemas + +### StartupOptions + +Configuration for plugin startup orchestration. + +#### Properties + +| Property | Type | Required | Default | Description | +| :--- | :--- | :--- | :--- | :--- | +| **timeout** | `number` | optional | 30000 | Maximum time (ms) to wait for each plugin to start | +| **rollbackOnFailure** | `boolean` | optional | true | Whether to rollback (destroy) already-started plugins on failure | +| **healthCheck** | `boolean` | optional | false | Whether to run health checks after startup | +| **parallel** | `boolean` | optional | false | Whether to start plugins in parallel when dependencies allow | +| **context** | `any` | optional | - | Custom context to pass to plugin lifecycle methods | + +#### Example + +```typescript +const options: StartupOptions = { + timeout: 30000, + rollbackOnFailure: true, + healthCheck: false, + parallel: false +}; +``` + +--- + +### HealthStatus + +Health status for a plugin after startup. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **healthy** | `boolean` | ✅ | Whether the plugin is healthy | +| **timestamp** | `number` | ✅ | Unix timestamp in milliseconds when health check was performed | +| **details** | `Record` | optional | Optional plugin-specific health details | +| **message** | `string` | optional | Error message if plugin is unhealthy | + +#### Example + +```json +{ + "healthy": true, + "timestamp": 1706659200000, + "details": { + "databaseConnected": true, + "memoryUsage": 45.2 + } +} +``` + +--- + +### PluginStartupResult + +Result of a single plugin startup operation. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **plugin** | `object` | ✅ | Plugin metadata (name, version) | +| **plugin.name** | `string` | ✅ | Plugin name | +| **plugin.version** | `string` | optional | Plugin version | +| **success** | `boolean` | ✅ | Whether the plugin started successfully | +| **duration** | `number` | ✅ | Time taken to start the plugin in milliseconds | +| **error** | `Error` | optional | Error object if startup failed | +| **health** | `HealthStatus` | optional | Health status after startup (if healthCheck enabled) | + +#### Example + +```json +{ + "plugin": { + "name": "crm-plugin", + "version": "1.0.0" + }, + "success": true, + "duration": 1250, + "health": { + "healthy": true, + "timestamp": 1706659200000 + } +} +``` + +--- + +### StartupOrchestrationResult + +Overall result of orchestrating startup for multiple plugins. + +#### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **results** | `PluginStartupResult[]` | ✅ | Startup results for each plugin | +| **totalDuration** | `number` | ✅ | Total time taken for all plugins in milliseconds | +| **allSuccessful** | `boolean` | ✅ | Whether all plugins started successfully | +| **rolledBack** | `string[]` | optional | Names of plugins that were rolled back | + +#### Example + +```json +{ + "results": [ + { + "plugin": { "name": "plugin1" }, + "success": true, + "duration": 1200 + }, + { + "plugin": { "name": "plugin2" }, + "success": true, + "duration": 850 + } + ], + "totalDuration": 2050, + "allSuccessful": true +} +``` + +--- + +## Usage Examples + +### Basic Sequential Startup + +```typescript +import { StartupOrchestrator } from '@objectstack/core'; + +const orchestrator = new StartupOrchestrator(kernel); + +const result = await orchestrator.startPlugins({ + timeout: 30000, + rollbackOnFailure: true, + parallel: false +}); + +if (result.allSuccessful) { + console.log(`All ${result.results.length} plugins started in ${result.totalDuration}ms`); +} else { + console.error('Some plugins failed to start'); +} +``` + +### Parallel Startup with Health Checks + +```typescript +const result = await orchestrator.startPlugins({ + timeout: 60000, + rollbackOnFailure: true, + healthCheck: true, + parallel: true +}); + +result.results.forEach(r => { + if (r.success && r.health?.healthy) { + console.log(`✓ ${r.plugin.name} - healthy (${r.duration}ms)`); + } else if (r.success && !r.health?.healthy) { + console.warn(`⚠ ${r.plugin.name} - started but unhealthy`); + } else { + console.error(`✗ ${r.plugin.name} - failed:`, r.error); + } +}); +``` + +### Custom Context + +```typescript +const result = await orchestrator.startPlugins({ + timeout: 30000, + rollbackOnFailure: true, + context: { + environment: 'production', + region: 'us-east-1', + requestId: 'abc-123' + } +}); +``` + +### Handling Rollback + +```typescript +const result = await orchestrator.startPlugins({ + timeout: 30000, + rollbackOnFailure: true +}); + +if (!result.allSuccessful && result.rolledBack) { + console.log(`Rolled back plugins: ${result.rolledBack.join(', ')}`); +} +``` + +--- + +## Best Practices + +### 1. Set Appropriate Timeouts + +```typescript +// Short timeout for simple plugins +const quickStartup = { timeout: 10000 }; + +// Longer timeout for complex plugins (databases, external services) +const complexStartup = { timeout: 60000 }; +``` + +### 2. Enable Health Checks for Critical Systems + +```typescript +const result = await orchestrator.startPlugins({ + healthCheck: true, // Always check health for production + rollbackOnFailure: true +}); +``` + +### 3. Use Parallel Startup Carefully + +```typescript +// Only use parallel when plugins are truly independent +const result = await orchestrator.startPlugins({ + parallel: true, // Faster, but requires careful dependency management + timeout: 30000 +}); +``` + +### 4. Always Handle Errors + +```typescript +const result = await orchestrator.startPlugins(options); + +for (const r of result.results) { + if (!r.success) { + logger.error(`Plugin ${r.plugin.name} failed:`, { + error: r.error, + duration: r.duration + }); + } +} +``` + +--- + +## Related + +- [Plugin](/docs/references/system/plugin) - Plugin protocol +- [Plugin Lifecycle Events](/docs/references/system/plugin-lifecycle-events) - Lifecycle event schemas +- [Manifest](/docs/references/system/manifest) - Plugin manifest specification From 5e8fd7dc9ab307d554bd8a1d09928b451cd0c121 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 09:06:33 +0000 Subject: [PATCH 5/6] Fix code review feedback: health check example and KMS ARN format Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- content/docs/references/integration/connector-file-storage.mdx | 2 +- content/docs/references/system/startup-orchestrator.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/references/integration/connector-file-storage.mdx b/content/docs/references/integration/connector-file-storage.mdx index 0a007c5ea..42036607c 100644 --- a/content/docs/references/integration/connector-file-storage.mdx +++ b/content/docs/references/integration/connector-file-storage.mdx @@ -204,7 +204,7 @@ const s3Connector: FileStorageConnector = { encryption: { enabled: true, algorithm: 'aws:kms', - kmsKeyId: 'arn:aws:kms:us-east-1:123456789:key/abc-123', + kmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/abc-123', }, }; ``` diff --git a/content/docs/references/system/startup-orchestrator.mdx b/content/docs/references/system/startup-orchestrator.mdx index 01aa874b8..8a8c82723 100644 --- a/content/docs/references/system/startup-orchestrator.mdx +++ b/content/docs/references/system/startup-orchestrator.mdx @@ -66,7 +66,7 @@ Configuration for plugin startup orchestration. const options: StartupOptions = { timeout: 30000, rollbackOnFailure: true, - healthCheck: false, + healthCheck: true, // Enable health checks for production parallel: false }; ``` From 3f30779ed5ca08f65b5e83cfd718a0ef148846e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 09:07:33 +0000 Subject: [PATCH 6/6] Use realistic KMS key ID format in example Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- content/docs/references/integration/connector-file-storage.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/references/integration/connector-file-storage.mdx b/content/docs/references/integration/connector-file-storage.mdx index 42036607c..8f3201047 100644 --- a/content/docs/references/integration/connector-file-storage.mdx +++ b/content/docs/references/integration/connector-file-storage.mdx @@ -204,7 +204,7 @@ const s3Connector: FileStorageConnector = { encryption: { enabled: true, algorithm: 'aws:kms', - kmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/abc-123', + kmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012', }, }; ```