Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 238 additions & 6 deletions packages/metadata/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,245 @@
# @objectstack/metadata

The **Model Definition** layer. It provides classes and utilities for parsing and validating ObjectStack metadata schemas.
> **Metadata Loading, Persistence & Customization Layer for ObjectStack.**

## Features
`@objectstack/metadata` is the central service responsible for loading, validating, persisting and watching all metadata definitions (Objects, Views, Flows, Apps, Agents, etc.) in the ObjectStack platform.

- **Schema Loading**: Load metadata from JSON/YAML or TypeScript files.
- **Reference Resolution**: Resolves cross-object references and inheritance.
- **Validation**: Strict validation against the Zod schemas in `@objectstack/spec`.
It implements the **`IMetadataService`** contract from `@objectstack/spec` and acts as the single source of truth that all other packages depend on.

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────┐
│ IMetadataService │
│ (Contract: @objectstack/spec) │
├─────────────────────────────────────────────────────────────┤
│ MetadataManager │
│ (Orchestrator: this package) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ In-Memory │ │ Overlay │ │ Type Registry │ │
│ │ Registry │ │ System │ │ & Dependencies │ │
│ └─────────────┘ └──────────────┘ └───────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Loader Layer │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ Filesystem │ │ Remote │ │ Memory │ │
│ │ Loader │ │ Loader │ │ Loader │ │
│ │ (files) │ │ (HTTP) │ │ (test) │ │
│ └─────────────┘ └──────────────┘ └───────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ DatabaseLoader (planned — datasource-backed storage) │ │
│ └──────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Serializer Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────────┐ │
│ │ JSON │ │ YAML │ │ TypeScript/JavaScript │ │
│ └──────────┘ └──────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```

## Core Concepts

### 1. Metadata Sources (Three-Scope Model)

ObjectStack adopts a three-scope layered model for metadata:

| Scope | Storage | Mutability | Description |
|:-----------|:-------------|:-------------|:-------------------------------------------|
| `system` | Filesystem | Read-only | Defined in code, shipped with packages |
| `platform` | Database | Admin-editable | Created/modified by admins via UI |
| `user` | Database | User-editable | Personal customizations per user |

Resolution order: **system** ← merge(**platform**) ← merge(**user**).

### 2. Loaders

Loaders are pluggable data sources that know how to read/write metadata from different backends. Each loader declares a `MetadataLoaderContract` with name, protocol, and capabilities:

| Loader | Protocol | Read | Write | Watch | Status |
|:--------------------|:---------------|:-----|:------|:------|:-------------|
| `FilesystemLoader` | `file:` | ✅ | ✅ | ✅ | Implemented |
| `MemoryLoader` | `memory:` | ✅ | ✅ | ❌ | Implemented |
| `RemoteLoader` | `http:` | ✅ | ✅ | ❌ | Implemented |
| `DatabaseLoader` | `datasource:` | ✅ | ✅ | ✅ | Planned |

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loader capability matrix marks DatabaseLoader as supporting Watch (✅), but the Phase 1 roadmap below defines its capabilities as watch: false. Please make these docs consistent (either change the matrix to ❌/planned, or update the roadmap/tasks to reflect intended watch support).

Suggested change
| `DatabaseLoader` | `datasource:` ||| | Planned |
| `DatabaseLoader` | `datasource:` ||| | Planned |

Copilot uses AI. Check for mistakes.

### 3. Serializers

Serializers convert metadata objects to/from different file formats:

- **JSONSerializer** — `.json` files with optional key sorting
- **YAMLSerializer** — `.yaml`/`.yml` files (JSON_SCHEMA for security)
- **TypeScriptSerializer** — `.ts`/`.js` module exports (for `defineObject()`, `defineView()`, etc.)

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TypeScriptSerializer in this package only supports parsing JSON-compatible object literals exported via export const ... = {} or export default {} and uses JSON.parse internally. The README currently implies it can load definitions written as defineObject()/defineView() calls, which would not be parseable by the current implementation. Please adjust the README to reflect the actual supported module shape (or expand the serializer/parser accordingly).

Suggested change
- **TypeScriptSerializer**`.ts`/`.js` module exports (for `defineObject()`, `defineView()`, etc.)
- **TypeScriptSerializer**`.ts`/`.js` modules that export JSON-compatible object literals via `export const ... = {}` or `export default {}` (parsed with `JSON.parse`, does not support `defineObject()` / `defineView()` calls)

Copilot uses AI. Check for mistakes.

### 4. Overlay / Customization System

The overlay system enables non-destructive customizations on top of package-delivered (system) metadata, following a delta-based approach (JSON Merge Patch):

- **getOverlay** / **saveOverlay** / **removeOverlay** — manage customization deltas
- **getEffective** — returns the merged result of base + platform overlay + user overlay
- Overlays never modify the base definition — they are additive patches
Comment on lines +76 to +80

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overlay section describes overlays as “JSON Merge Patch”, but the current implementation of getEffective() in MetadataManager performs a shallow object spread ({ ...base, ...patch }) for platform/user overlays. This differs from JSON Merge Patch semantics (deep merge + null deletions). Please either update the docs to describe the current shallow-merge behavior, or update the overlay merge logic to match the documented patch semantics.

Suggested change
The overlay system enables non-destructive customizations on top of package-delivered (system) metadata, following a delta-based approach (JSON Merge Patch):
- **getOverlay** / **saveOverlay** / **removeOverlay** — manage customization deltas
- **getEffective** — returns the merged result of base + platform overlay + user overlay
- Overlays never modify the base definition — they are additive patches
The overlay system enables non-destructive customizations on top of package-delivered (system) metadata, using a shallow, top-level merge of overlay objects (via object spread) where later overlays override earlier ones:
- **getOverlay** / **saveOverlay** / **removeOverlay** — manage customization deltas
- **getEffective** — returns the merged result of base + platform overlay + user overlay (top-level keys from platform/user overlays override the base)
- Overlays never modify the base definition — they are additive patches; `null` values are treated as literal values, not deletions

Copilot uses AI. Check for mistakes.

### 5. MetadataManager (IMetadataService Implementation)

The `MetadataManager` is the main orchestrator. It provides:

- **Core CRUD**: `register`, `get`, `list`, `unregister`, `exists`, `listNames`
- **Convenience**: `getObject`, `listObjects`
- **Package Management**: `unregisterPackage` — unload all metadata from a package
- **Query / Search**: `query` with filtering, pagination, sorting by type/scope/state/tags
- **Bulk Operations**: `bulkRegister`, `bulkUnregister` with error handling
- **Import / Export**: `exportMetadata`, `importMetadata` with conflict resolution (skip/overwrite/merge)
- **Validation**: `validate` — structural validation of metadata items
- **Type Registry**: `getRegisteredTypes`, `getTypeInfo` — discover available metadata types
- **Dependency Tracking**: `getDependencies`, `getDependents` — cross-reference analysis
- **Watch / Subscribe**: `watchService` — observe metadata changes in real-time
- **Loader Delegation**: `load`, `loadMany`, `save` — delegate I/O to registered loaders

### 6. NodeMetadataManager

Extends `MetadataManager` with Node.js-specific capabilities:

- Auto-configures `FilesystemLoader` for local development
- File watching via **chokidar** for hot-reload during development
- Detects file add/change/delete events and notifies subscribers

### 7. MetadataPlugin

Integrates with the ObjectStack kernel plugin system:

- Registers as the primary `IMetadataService` provider
- Auto-loads all metadata types from the filesystem on startup (sorted by `loadOrder`)
- Supports YAML, JSON, TypeScript, and JavaScript metadata formats

## Metadata Types

The platform supports **26 built-in metadata types** across 6 protocol domains:

| Domain | Types |
|:-------------|:----------------------------------------------------------------------------|
| **Data** | `object`, `field`, `datasource`, `validation` |
| **UI** | `view`, `app`, `dashboard`, `report`, `action`, `theme` |
| **Automation** | `flow`, `workflow`, `trigger`, `schedule` |
| **System** | `manifest`, `translation`, `api`, `permission_set`, `role`, `profile` |
| **Security** | `permission_set`, `role` |
| **AI** | `agent`, `rag_pipeline`, `model`, `prompt`, `tool` |

Each type has a defined `loadOrder` (dependencies load before dependents), file patterns (e.g. `**/*.object.{ts,json,yaml}`), and overlay support flag.
Comment on lines +116 to +127

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “26 built-in metadata types” and the type list/domain table do not match the actual built-in registry used by MetadataPlugin (DEFAULT_METADATA_TYPE_REGISTRY in @objectstack/spec/kernel/metadata-plugin.zod.ts), which currently contains 23 types (e.g., includes page, approval, hook, router, function, service, and uses permission rather than permission_set). Please update the count and list to align with the real registry to avoid misleading users.

Suggested change
The platform supports **26 built-in metadata types** across 6 protocol domains:
| Domain | Types |
|:-------------|:----------------------------------------------------------------------------|
| **Data** | `object`, `field`, `datasource`, `validation` |
| **UI** | `view`, `app`, `dashboard`, `report`, `action`, `theme` |
| **Automation** | `flow`, `workflow`, `trigger`, `schedule` |
| **System** | `manifest`, `translation`, `api`, `permission_set`, `role`, `profile` |
| **Security** | `permission_set`, `role` |
| **AI** | `agent`, `rag_pipeline`, `model`, `prompt`, `tool` |
Each type has a defined `loadOrder` (dependencies load before dependents), file patterns (e.g. `**/*.object.{ts,json,yaml}`), and overlay support flag.
Metadata types are organized into protocol domains. The authoritative list of built-in types is defined by `DEFAULT_METADATA_TYPE_REGISTRY` in `@objectstack/spec/kernel/metadata-plugin.zod.ts`, but common examples include:
| Domain | Example types |
|:---------------|:---------------------------------------------------------------------------|
| **Data** | `object`, `field`, `datasource`, `validation` |
| **UI** | `view`, `page`, `app`, `dashboard`, `report`, `action`, `theme` |
| **Automation** | `flow`, `workflow`, `trigger`, `approval`, `hook` |
| **System** | `manifest`, `translation`, `api`, `router`, `function`, `service`, `role` |
| **Security** | `permission`, `role` |
| **AI** | `agent`, `rag_pipeline`, `model` |
Each metadata type has a defined `loadOrder` (dependencies load before dependents), file patterns (e.g. `**/*.object.{ts,json,yaml}`), and overlay support flag, as specified in the `DEFAULT_METADATA_TYPE_REGISTRY`.

Copilot uses AI. Check for mistakes.

## Spec Protocol References

This package depends on schemas and contracts defined in `@objectstack/spec`:

| Spec Module | What It Defines |
|:---------------------------------|:----------------------------------------------------|
| `spec/contracts/metadata-service` | `IMetadataService` — the async service interface |
| `spec/kernel/metadata-loader` | Loader contract, load/save/watch schemas, `MetadataManagerConfig` |
| `spec/kernel/metadata-plugin` | Type registry, plugin manifest, capabilities |
| `spec/kernel/metadata-customization` | Overlay, merge strategy, customization policy |
| `spec/system/metadata-persistence` | `MetadataRecord` — DB persistence envelope |
| `spec/data/datasource` | `DatasourceSchema`, `DriverDefinition`, capabilities |
| `spec/contracts/data-driver` | `IDataDriver` — database driver interface |

## Installation

```bash
pnpm add @objectstack/metadata
```

## Usage

Internal package used by `@objectstack/runtime` to process configuration.
### Basic (Browser-Compatible)

```typescript
import { MetadataManager, MemoryLoader } from '@objectstack/metadata';

const manager = new MetadataManager({
formats: ['json'],
loaders: [new MemoryLoader()],
});

// Register metadata
await manager.register('object', 'account', { name: 'account', label: 'Account', fields: {} });

// Retrieve
const obj = await manager.get('object', 'account');

// Query
const result = await manager.query({ types: ['object'], search: 'account' });
```

### Node.js (with Filesystem)

```typescript
import { NodeMetadataManager, MetadataPlugin } from '@objectstack/metadata/node';

const manager = new NodeMetadataManager({
rootDir: './src',
formats: ['typescript', 'json', 'yaml'],
watch: true,
});

// Load all objects from filesystem
const objects = await manager.loadMany('object');

// Watch for changes
manager.watchService('object', (event) => {
console.log(`Object ${event.name} was ${event.type}`);
});
```

### With Kernel Plugin

```typescript
import { MetadataPlugin } from '@objectstack/metadata/node';

const plugin = MetadataPlugin({
rootDir: './src',
watch: process.env.NODE_ENV === 'development',
});
// Register with ObjectStack kernel
kernel.use(plugin);
```
Comment on lines +193 to +202

Copilot AI Feb 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “With Kernel Plugin” example calls MetadataPlugin({...}) like a factory function, but MetadataPlugin is exported as a class in this package. As written, the example won’t run; it should instantiate the plugin (or the package should export a factory with that name).

Copilot uses AI. Check for mistakes.

## Package Structure

```
packages/metadata/
├── src/
│ ├── index.ts # Main exports (browser-compatible)
│ ├── node.ts # Node.js exports (filesystem, watching)
│ ├── metadata-manager.ts # MetadataManager (IMetadataService impl)
│ ├── node-metadata-manager.ts # NodeMetadataManager (+ file watching)
│ ├── plugin.ts # MetadataPlugin (kernel integration)
│ ├── loaders/
│ │ ├── loader-interface.ts # MetadataLoader contract
│ │ ├── filesystem-loader.ts # File I/O with glob, cache, ETag
│ │ ├── memory-loader.ts # In-memory store (tests/overrides)
│ │ └── remote-loader.ts # HTTP API loader with auth
│ ├── serializers/
│ │ ├── serializer-interface.ts # MetadataSerializer contract
│ │ ├── json-serializer.ts # JSON format
│ │ ├── yaml-serializer.ts # YAML format
│ │ └── typescript-serializer.ts # TS/JS module format
│ └── migration/
│ ├── index.ts # Barrel export
│ └── executor.ts # ChangeSet executor (DDL operations)
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── README.md # This file
└── ROADMAP.md # Development roadmap
```

## Related Packages

| Package | Relationship |
|:------------------------|:-------------------------------------------------|
| `@objectstack/spec` | Protocol definitions (schemas, contracts, types) |
| `@objectstack/core` | Logger, service registry, kernel utilities |
| `@objectstack/runtime` | Uses this package to bootstrap metadata |
| `apps/studio` | Visual metadata editor (consumes IMetadataService)|

## License

Apache-2.0 — see [LICENSE](../../LICENSE) for details.
Loading