Skip to content

Commit a317466

Browse files
Copilothotlong
andcommitted
feat: add sideEffects: false to 24 library package.json files for tree-shaking
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent b649561 commit a317466

27 files changed

Lines changed: 215 additions & 0 deletions

File tree

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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"title": "Guides",
3+
"pages": [
4+
"error-handling"
5+
]
6+
}

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",

packages/drivers/excel/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"license": "MIT",
1616
"main": "dist/index.js",
1717
"types": "dist/index.d.ts",
18+
"sideEffects": false,
1819
"exports": {
1920
".": {
2021
"types": "./dist/index.d.ts",

packages/drivers/fs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"license": "MIT",
1515
"main": "dist/index.js",
1616
"types": "dist/index.d.ts",
17+
"sideEffects": false,
1718
"exports": {
1819
".": {
1920
"types": "./dist/index.d.ts",

packages/drivers/memory/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"license": "MIT",
1616
"main": "dist/index.js",
1717
"types": "dist/index.d.ts",
18+
"sideEffects": false,
1819
"exports": {
1920
".": {
2021
"types": "./dist/index.d.ts",

packages/drivers/mongo/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"license": "MIT",
1616
"main": "dist/index.js",
1717
"types": "dist/index.d.ts",
18+
"sideEffects": false,
1819
"exports": {
1920
".": {
2021
"types": "./dist/index.d.ts",

packages/drivers/pg-wasm/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"license": "MIT",
2020
"main": "dist/index.js",
2121
"types": "dist/index.d.ts",
22+
"sideEffects": false,
2223
"exports": {
2324
".": {
2425
"types": "./dist/index.d.ts",

packages/drivers/redis/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"license": "MIT",
1515
"main": "dist/index.js",
1616
"types": "dist/index.d.ts",
17+
"sideEffects": false,
1718
"exports": {
1819
".": {
1920
"types": "./dist/index.d.ts",

packages/drivers/sql/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"license": "MIT",
1818
"main": "dist/index.js",
1919
"types": "dist/index.d.ts",
20+
"sideEffects": false,
2021
"exports": {
2122
".": {
2223
"types": "./dist/index.d.ts",

0 commit comments

Comments
 (0)