Skip to content

Commit ad5623d

Browse files
authored
Merge pull request #361 from objectstack-ai/copilot/complete-objectql-next-phase
2 parents bcba3d9 + 41c5931 commit ad5623d

161 files changed

Lines changed: 1434 additions & 891 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
---
2+
title: "Architecture Overview"
3+
description: "Understanding ObjectQL's layered architecture — Types, Core, Platform, Drivers, and Protocols"
4+
---
5+
6+
# Architecture Overview
7+
8+
ObjectQL follows a strict **layered architecture** that enforces unidirectional dependencies, prevents circular imports, and keeps the type system as the single source of truth. Every package belongs to one of four layers.
9+
10+
## High-Level Architecture
11+
12+
```
13+
┌─────────────────────────────────────────────────────────────────┐
14+
│ Tools & DX │
15+
│ CLI · VSCode Extension · create-objectql │
16+
└──────────────────────────┬──────────────────────────────────────┘
17+
│ uses
18+
┌──────────────────────────▼──────────────────────────────────────┐
19+
│ Protocols │
20+
│ GraphQL · OData V4 · JSON-RPC · Sync Protocol │
21+
└──────────────────────────┬──────────────────────────────────────┘
22+
│ uses
23+
┌──────────────────────────▼──────────────────────────────────────┐
24+
│ Drivers │
25+
│ SQL · MongoDB · Redis · Memory · Excel · FS · PG-WASM · SQLite │
26+
└──────────────────────────┬──────────────────────────────────────┘
27+
│ implements
28+
┌──────────────────────────▼──────────────────────────────────────┐
29+
│ Foundation Layer │
30+
│ │
31+
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────────┐ │
32+
│ │ Types │◄──│ Core │◄──│ Platform-Node │ │
33+
│ │ (Zero Deps) │ │ (Engine) │ │ (Node.js Bridge) │ │
34+
│ └─────────────┘ └─────────────┘ └──────────────────────┘ │
35+
│ │
36+
│ Plugins: Formula · Validator · Security · Multitenancy · Sync │
37+
└─────────────────────────────────────────────────────────────────┘
38+
```
39+
40+
## Layer 1: Foundation
41+
42+
The Foundation layer is the architectural core, following the **Trinity Architecture** pattern.
43+
44+
### @objectql/types — "The Constitution"
45+
46+
The single source of truth for all TypeScript interfaces, enums, and custom errors. This package has **zero production dependencies** and must never import from any sibling `@objectql/*` package.
47+
48+
Key exports include object/field definitions, query and filter types, driver abstractions, validation rules, hook interfaces, and the `ObjectQLError` class.
49+
50+
```typescript
51+
import { ObjectConfig, FieldConfig, ValidationRule } from '@objectql/types';
52+
```
53+
54+
### @objectql/core — "The Runtime Engine"
55+
56+
Implements the ORM kernel: `ObjectQL` class, `Validator`, `ObjectRepository`, formula engine, metadata registry, and query compilation. The core is **platform-agnostic** — it contains no Node.js native modules.
57+
58+
```typescript
59+
import { ObjectQL, Validator, SchemaRegistry } from '@objectql/core';
60+
```
61+
62+
### @objectql/platform-node — "The Node.js Bridge"
63+
64+
Bridges the platform-agnostic core to Node.js via file system access (`fs`, `path`, `glob`), YAML loading, and plugin discovery. Use this package only in Node.js server environments.
65+
66+
```typescript
67+
import { ObjectLoader } from '@objectql/platform-node';
68+
```
69+
70+
### Foundation Plugins
71+
72+
Core behavior is extended through plugins that depend only on `@objectql/types` and `@objectql/core`:
73+
74+
| Plugin | Purpose |
75+
|--------|---------|
76+
| `plugin-formula` | Computed fields and dynamic formulas |
77+
| `plugin-validator` | Metadata-driven validation engine |
78+
| `plugin-security` | RBAC, field masking, row-level security |
79+
| `plugin-multitenancy` | Tenant isolation and routing |
80+
| `plugin-sync` | Offline-first mutation logging and conflict resolution |
81+
82+
## Layer 2: Drivers
83+
84+
Drivers implement the `DriverInterface` defined in `@objectql/types`. Each driver translates abstract query ASTs into database-specific operations. Drivers depend on **types only** — never on core.
85+
86+
| Driver | Backend | Environment |
87+
|--------|---------|-------------|
88+
| `driver-sql` | PostgreSQL, MySQL, SQLite via Knex | Node.js |
89+
| `driver-mongo` | MongoDB | Node.js |
90+
| `driver-redis` | Redis | Node.js |
91+
| `driver-memory` | In-memory (Mingo) | Universal |
92+
| `driver-excel` | XLSX files | Node.js |
93+
| `driver-fs` | JSON/YAML files | Node.js |
94+
| `driver-pg-wasm` | PostgreSQL via WASM | Browser |
95+
| `driver-sqlite-wasm` | SQLite via WASM | Browser |
96+
97+
## Layer 3: Protocols
98+
99+
Protocols expose ObjectQL operations over network transports. They depend on **types + core**.
100+
101+
| Protocol | Transport | Compliance |
102+
|----------|-----------|------------|
103+
| `protocol-graphql` | HTTP (Query/Mutation) | 85% |
104+
| `protocol-odata-v4` | HTTP (REST) | 80% |
105+
| `protocol-json-rpc` | HTTP / WebSocket | 90% |
106+
| `protocol-sync` | HTTP / WebSocket | Sync protocol |
107+
108+
## Layer 4: Tools & DX
109+
110+
Developer-facing tools that consume all lower layers:
111+
112+
- **`@objectql/cli`** — Project scaffolding, code generation, migrations, REPL
113+
- **`@objectql/create`**`npm create objectql` initializer
114+
- **`vscode-objectql`** — Schema validation, IntelliSense, snippets
115+
116+
## Dependency Flow
117+
118+
Dependencies flow strictly **downward**. No package may import from a package in the same or higher layer.
119+
120+
```
121+
Types ← Core ← Platform-Node
122+
↑ ↑
123+
│ └──── Protocols
124+
125+
└──────────── Drivers
126+
```
127+
128+
| Package | May Import From |
129+
|---------|-----------------|
130+
| `@objectql/types` | Nothing (`@objectstack/spec` for type derivation only) |
131+
| `@objectql/core` | `@objectql/types`, `@objectstack/*` runtime packages |
132+
| Drivers | `@objectql/types` |
133+
| Protocols | `@objectql/types`, `@objectql/core` |
134+
| Platform-Node | `@objectql/types`, `@objectql/core` |
135+
| Tools | Any lower layer |
136+
137+
## ObjectStack Kernel Integration
138+
139+
ObjectQL plugs into the broader **ObjectStack** ecosystem through the kernel pattern. The `ObjectStackKernel` boots applications, plugins, and drivers from declarative configuration manifests:
140+
141+
```typescript
142+
import { ObjectStackKernel } from '@objectstack/runtime';
143+
import { InMemoryDriver } from '@objectstack/driver-memory';
144+
145+
const kernel = new ObjectStackKernel([
146+
AppManifest,
147+
new InMemoryDriver(),
148+
]);
149+
150+
await kernel.start();
151+
```
152+
153+
The kernel handles lifecycle management, dependency injection, and plugin coordination — keeping ObjectQL's core engine focused purely on data operations.
154+
155+
## Next Steps
156+
157+
- **[Foundation Deep Dive](/docs/architecture)** — Trinity Architecture details and code examples
158+
- **[Error Handling Guide](/docs/guides/error-handling)**`ObjectQLError` patterns and error codes
159+
- **[Driver Development](/docs/extending/custom-drivers)** — Implementing a custom driver
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
---
2+
title: "Error Handling"
3+
description: "Structured error handling with ObjectQLError — error codes, patterns, and best practices"
4+
---
5+
6+
ObjectQL uses a structured error handling pattern built around the `ObjectQLError` class. All packages in the ecosystem throw `ObjectQLError` instead of plain `Error` objects, providing consistent error codes, messages, and optional details across every layer.
7+
8+
---
9+
10+
## ObjectQLError Class
11+
12+
```typescript
13+
import { ObjectQLError, ApiErrorCode } from '@objectql/types';
14+
15+
throw new ObjectQLError({
16+
code: ApiErrorCode.NOT_FOUND,
17+
message: 'Object "orders" not found in metadata registry',
18+
details: { field: 'objectName', reason: 'Object not registered' }
19+
});
20+
```
21+
22+
### Constructor
23+
24+
| Parameter | Type | Required | Description |
25+
|-----------|------|----------|-------------|
26+
| `code` | `ApiErrorCode \| string` || Semantic error code (see taxonomy below) |
27+
| `message` | `string` || Human-readable error description |
28+
| `details` | `ApiErrorDetails` || Structured metadata (field, reason, etc.) |
29+
30+
### Properties
31+
32+
- **`code`** — The error code string (e.g., `'NOT_FOUND'`, `'DRIVER_QUERY_FAILED'`)
33+
- **`message`** — Human-readable error message (inherited from `Error`)
34+
- **`details`** — Optional structured metadata for programmatic error inspection
35+
- **`name`** — Always `'ObjectQLError'` (useful for `instanceof` checks)
36+
- **`stack`** — Stack trace (inherited from `Error`)
37+
38+
---
39+
40+
## Error Code Taxonomy
41+
42+
### Core Error Codes (`ApiErrorCode` enum)
43+
44+
| Code | When to Use |
45+
|------|------------|
46+
| `INVALID_REQUEST` | Malformed request parameters or missing required fields |
47+
| `VALIDATION_ERROR` | Field validation failure (type mismatch, constraint violation) |
48+
| `UNAUTHORIZED` | Missing or invalid authentication credentials |
49+
| `FORBIDDEN` | Authenticated but insufficient permissions (RBAC violation) |
50+
| `NOT_FOUND` | Object, record, or resource does not exist |
51+
| `CONFLICT` | Duplicate key, optimistic concurrency conflict |
52+
| `INTERNAL_ERROR` | Unexpected internal error (catch-all) |
53+
| `DATABASE_ERROR` | Generic database-level error |
54+
| `RATE_LIMIT_EXCEEDED` | Too many requests |
55+
56+
### Driver Error Codes
57+
58+
| Code | When to Use |
59+
|------|------------|
60+
| `DRIVER_ERROR` | Generic driver error |
61+
| `DRIVER_CONNECTION_FAILED` | Failed to connect to the database |
62+
| `DRIVER_QUERY_FAILED` | Query execution failed (validation, constraint, etc.) |
63+
| `DRIVER_TRANSACTION_FAILED` | Transaction begin/commit/rollback failed |
64+
| `DRIVER_UNSUPPORTED_OPERATION` | Operation not supported by this driver |
65+
66+
### Protocol Error Codes
67+
68+
| Code | When to Use |
69+
|------|------------|
70+
| `PROTOCOL_ERROR` | Generic protocol error |
71+
| `PROTOCOL_INVALID_REQUEST` | Invalid protocol request format |
72+
| `PROTOCOL_METHOD_NOT_FOUND` | Unknown method/endpoint in the protocol |
73+
| `PROTOCOL_BATCH_ERROR` | Batch operation failure |
74+
75+
### Plugin Error Codes
76+
77+
| Code | When to Use |
78+
|------|------------|
79+
| `TENANT_ISOLATION_VIOLATION` | Cross-tenant data access attempt |
80+
| `TENANT_NOT_FOUND` | Tenant context missing or invalid |
81+
| `WORKFLOW_TRANSITION_DENIED` | Invalid state machine transition |
82+
| `FORMULA_EVALUATION_ERROR` | Formula expression evaluation failed |
83+
84+
### Tool/CLI Error Codes
85+
86+
| Code | When to Use |
87+
|------|------------|
88+
| `CONFIG_ERROR` | Configuration file missing or invalid |
89+
| `SCAFFOLD_ERROR` | Project scaffolding failure |
90+
| `MIGRATION_ERROR` | Migration execution failure |
91+
92+
---
93+
94+
## Error Handling Patterns
95+
96+
### Catching ObjectQLError
97+
98+
```typescript
99+
import { ObjectQLError } from '@objectql/types';
100+
101+
try {
102+
const record = await repo.findOne(id);
103+
} catch (error) {
104+
if (error instanceof ObjectQLError) {
105+
// Structured error — inspect code and details
106+
switch (error.code) {
107+
case 'NOT_FOUND':
108+
return { status: 404, message: error.message };
109+
case 'VALIDATION_ERROR':
110+
return { status: 400, fields: error.details?.fields };
111+
case 'FORBIDDEN':
112+
return { status: 403, permission: error.details?.required_permission };
113+
default:
114+
return { status: 500, message: 'Internal server error' };
115+
}
116+
}
117+
// Unknown error — re-throw
118+
throw error;
119+
}
120+
```
121+
122+
### Throwing in Drivers
123+
124+
```typescript
125+
import { ObjectQLError } from '@objectql/types';
126+
127+
async find(objectName: string, query: UnifiedQuery) {
128+
try {
129+
return await this.db.collection(objectName).find(query.filter).toArray();
130+
} catch (error: any) {
131+
throw new ObjectQLError({
132+
code: 'DRIVER_QUERY_FAILED',
133+
message: `MongoDB query failed on "${objectName}": ${error.message}`,
134+
details: { field: 'query', reason: error.code }
135+
});
136+
}
137+
}
138+
```
139+
140+
### Throwing in Hooks
141+
142+
```typescript
143+
import { ObjectQLError, ApiErrorCode } from '@objectql/types';
144+
145+
export async function beforeInsert(context: HookContext) {
146+
const { doc } = context;
147+
148+
if (!doc.email?.includes('@')) {
149+
throw new ObjectQLError({
150+
code: ApiErrorCode.VALIDATION_ERROR,
151+
message: 'Invalid email address',
152+
details: { field: 'email', reason: 'Must contain @ symbol' }
153+
});
154+
}
155+
}
156+
```
157+
158+
---
159+
160+
## ApiErrorDetails Reference
161+
162+
The `details` object supports these common fields:
163+
164+
| Field | Type | Description |
165+
|-------|------|-------------|
166+
| `field` | `string` | The field that caused the error |
167+
| `reason` | `string` | Human-readable reason for the error |
168+
| `fields` | `Record<string, string>` | Multiple field errors (for bulk validation) |
169+
| `required_permission` | `string` | The permission needed (for FORBIDDEN errors) |
170+
| `user_roles` | `string[]` | Current user's roles (for FORBIDDEN errors) |
171+
| `retry_after` | `number` | Seconds to wait before retrying (for RATE_LIMIT_EXCEEDED) |
172+
173+
Additional custom fields can be added via the index signature `[key: string]: unknown`.
174+
175+
---
176+
177+
## Best Practices
178+
179+
1. **Always use `ObjectQLError`** — Never `throw new Error()` in packages
180+
2. **Choose specific error codes** — Use `DRIVER_QUERY_FAILED` over generic `INTERNAL_ERROR`
181+
3. **Include context in messages** — Include the object name, field name, or operation
182+
4. **Use `details` for machine-readable info** — Error messages are for humans, details are for code
183+
5. **Preserve original error info** — Include `error.message` in your ObjectQLError message
184+
6. **Check with `instanceof`** — Use `error instanceof ObjectQLError` for type-safe handling

content/docs/guides/meta.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"title": "Guides",
3+
"pages": [
4+
"error-handling",
5+
"architecture"
6+
]
7+
}

content/docs/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"modeling",
88
"logic",
99
"data-access",
10+
"guides",
1011
"---Infrastructure---",
1112
"drivers",
1213
"server",

docs/ROADMAP_NEXT_PHASE.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ObjectQL — Next Phase Optimization & Improvement Roadmap
22

3-
> Created: 2026-02-09 | Status: **Proposed**
3+
> Created: 2026-02-09 | Status: **In Progress — Phases 1A, 3, 4 (All Waves), 5A, 6, 7 (partial) Complete**
44
> Current Version: **4.2.0** | @objectstack Platform: **v2.0.1**
55
> Scope: Code quality, type safety, error handling, testing, performance, and DX improvements
66
@@ -135,9 +135,9 @@ type PluginErrorCode =
135135
```
136136

137137
**Success Criteria:**
138-
- [ ] Zero `throw new Error(` in `packages/` (excluding test files)
139-
- [ ] All error codes documented in `@objectql/types`
140-
- [ ] Existing tests still pass after migration
138+
- [x] Zero `throw new Error(` in `packages/` (excluding test files)
139+
- [x] All error codes documented in `@objectql/types`
140+
- [x] Existing tests still pass after migration
141141

142142
### 1B. Reduce `any` type usage
143143

@@ -457,6 +457,10 @@ Phase 6 (Documentation) ────────┐ Depends on Phase 5 (protoc
457457
Phase 7 (Performance) ────────┘ Independent, can start earlier
458458
```
459459

460+
> **Completed**: Phases 1A, 3, 4 (all waves), 5A, 6 (error-handling + architecture guides)
461+
> **Completed (partial)**: Phase 2 (create tests), Phase 7 (sideEffects)
462+
> **Remaining**: Phase 1B (any reduction), Phase 5B (protocol refinement), Phase 7 (benchmarks)
463+
460464
### E. Risk Assessment
461465

462466
| Risk | Likelihood | Impact | Mitigation |

0 commit comments

Comments
 (0)