docs/index.md:7:JSON REST API is a lightweight, plugin-based framework that makes building REST APIs incredibly simple. With automatic validation, smart relationships, and native JSON:API support, you can focus on your business logic instead of boilerplate.
docs/index.md:30:    <p>Extend your API with powerful plugins. Authentication, validation, CORS, and more - just plug and play.</p>
docs/index.md:49:    <h3>🎯 JSON:API Compliant</h3>
docs/index.md:50:    <p>Follow industry standards with native JSON:API support. Compatible with any JSON:API client library.</p>
docs/index.md:110:}).on('error', (err) => {
docs/index.md:111:  console.error('Failed to start server:', err);
docs/COMPARISON.md:1:# JSON:API Library Comparison
docs/COMPARISON.md:3:This document compares json-rest-api with other JSON:API libraries in the Node.js ecosystem, highlighting key differences and advantages.
docs/COMPARISON.md:7:json-rest-api is a full-featured, plugin-based JSON:API implementation built on the hooked-api framework. Unlike other solutions, it offers a unique combination of compliance, flexibility, and developer experience.
docs/COMPARISON.md:14:| Full JSON:API 1.1 compliance | ✅ | ⚠️ (serialization only) | ✅ | ✅ | ⚠️ |
docs/COMPARISON.md:27:| Deep includes | ✅ | ⚠️ | ✅ | ⚠️ | ✅ |
docs/COMPARISON.md:44:**json-api-serializer** is a serialization-only library that focuses on converting JavaScript objects to/from JSON:API format.
docs/COMPARISON.md:48:- **You still need**: Routing, database layer, relationship handling, query parsing, error handling
docs/COMPARISON.md:56:  author: { ref: 'id', included: true }
docs/COMPARISON.md:90:**nestjs-json-api** provides JSON:API support specifically for NestJS applications using decorators.
docs/COMPARISON.md:94:- **Architecture**: Class-based with decorators vs plugin-based composition
docs/COMPARISON.md:132:**@jsonapi/server** is a lightweight JSON:API framework that provides basic compliance.
docs/COMPARISON.md:135:- **Features**: json-rest-api offers many more features (file uploads, WebSockets, bulk operations)
docs/COMPARISON.md:142:**fortune.js** is a hypermedia API framework with optional JSON:API support.
docs/COMPARISON.md:146:- **Focus**: Generic hypermedia vs dedicated JSON:API implementation
docs/COMPARISON.md:157:// JSON:API mode (for HTTP clients)
docs/COMPARISON.md:183:  simplifiedTransport: false // HTTP endpoints use JSON:API format
docs/COMPARISON.md:189:Unmatched flexibility through composition:
docs/COMPARISON.md:274:  queryParams: { include: ['commentable'] } // Works with any type
docs/COMPARISON.md:319:    include: [
docs/COMPARISON.md:332:// 4. Returns properly structured JSON:API response
docs/COMPARISON.md:341:- ✅ Full JSON:API compliance with minimal setup
docs/COMPARISON.md:344:- ✅ Advanced features like file uploads, WebSockets, bulk operations
docs/COMPARISON.md:345:- ✅ Both JSON:API and simplified object formats
docs/COMPARISON.md:352:- ❌ You need a different hypermedia format (not JSON:API)
docs/COMPARISON.md:425:json-rest-api stands out as the most comprehensive JSON:API solution for Node.js:
docs/COMPARISON.md:433:For teams building JSON:API services in 2025, json-rest-api offers the best combination of compliance, features, and developer experience.
docs/ONBOARDING.md:65:4. **error** - Global error handling hook
docs/ONBOARDING.md:66:   - Allows plugins to intercept and handle errors
docs/ONBOARDING.md:147:- Transforms between formats (JSON:API ↔ simplified)
docs/ONBOARDING.md:154:- Transforms between database format and JSON:API
docs/ONBOARDING.md:270:    } catch (error) {
docs/ONBOARDING.md:271:      // Handle errors
docs/ONBOARDING.md:272:      handleError(error, req, res);
docs/ONBOARDING.md:306:1. **Plugin composition** - Each plugin does one thing well
README.md:3:A powerful REST API plugin for [hooked-api](https://github.com/mobily-enterprises/hooked-api) that provides JSON:API-compliant endpoints with minimal configuration. This library makes it easy to create fully-featured REST APIs with support for relationships, filtering, sorting, pagination, and file uploads.
README.md:9:* **JSON:API Compliant** - Full support for the JSON:API specification
README.md:11:* **Advanced Querying** - Filtering, sorting, pagination, and field selection (sparse fieldsets)
README.md:14:* **Validation** - Schema-based validation with detailed error messages and custom rules
README.md:15:* **Simplified Mode** - A developer-friendly option to work with plain JavaScript objects instead of verbose JSON:API structure
README.md:60:}).on('error', (err) => {
README.md:61:  console.error('Failed to start server:', err);
docs/QUICKSTART.md:63:}).on('error', (err) => {
docs/QUICKSTART.md:64:  console.error('Failed to start server:', err);
docs/QUICKSTART.md:147:// Method 3: Full JSON:API mode (standards compliant)
docs/QUICKSTART.md:159:console.log('JSON:API response:', inspect(oxfordResult));
docs/QUICKSTART.md:173:// Get publisher with included authors
docs/QUICKSTART.md:176:  include: ['authors']
docs/QUICKSTART.md:198:3. **Full JSON:API mode**: Set `simplified: false` to use the complete JSON:API specification format for both requests and responses. This provides full standards compliance and access to all JSON:API features.
docs/QUICKSTART.md:204:JSON:API response: {
docs/QUICKSTART.md:248:# Get a specific publisher with included authors
docs/QUICKSTART.md:249:curl "http://localhost:3000/api/publishers/1?include=authors"
docs/QUICKSTART.md:257:# Get authors with sparse fields (only name and surname)
docs/GUIDE/GUIDE_1_Initial_Setup.md:83:  4. `warn` - Only warnings and errors
docs/GUIDE/GUIDE_1_Initial_Setup.md:84:  5. `error` - Only error messages
docs/GUIDE/GUIDE_1_Initial_Setup.md:92:  logLevel: 'warn'  // Only show warnings and errors
docs/GUIDE/GUIDE_1_Initial_Setup.md:98:- logLevel: `warn` - Only see warnings and errors
docs/GUIDE/GUIDE_1_Initial_Setup.md:99:- logLevel: `error` - Only see errors
docs/GUIDE/GUIDE_1_Initial_Setup.md:192:In the examples above, we're using the API in **simplified mode** (which is the default for programmatic usage). Simplified mode is a convenience feature that allows you to work with plain JavaScript objects instead of the full JSON:API document structure. However, it's important to understand that internally, everything is still processed as proper JSON:API documents.
docs/GUIDE/GUIDE_1_Initial_Setup.md:233:// Expected Output (a  full JSON:API record):
docs/GUIDE/GUIDE_1_Initial_Setup.md:245:(Note that the full JSON:API record includes links to resources, which are automatically generated based on the request headers.)
docs/GUIDE/GUIDE_1_Initial_Setup.md:249:- Input requires the full JSON:API document structure with `data`, `type`, and `attributes`
docs/GUIDE/GUIDE_1_Initial_Setup.md:250:- Output returns the full JSON:API response with the same nested structure (and links)
docs/GUIDE/GUIDE_1_Initial_Setup.md:260:     simplifiedApi: false,      // All API calls will use JSON:API format by default
docs/GUIDE/GUIDE_1_Initial_Setup.md:272:     simplifiedApi: false,      // API calls to this resource use JSON:API format
docs/GUIDE/GUIDE_1_Initial_Setup.md:289:        simplifiedApi: false,      // API calls to this resource use JSON:API format
docs/GUIDE/GUIDE_1_Initial_Setup.md:328:By default, `simplifiedApi` is `true` for programmatic usage, making it easier to work with the API in your code while still maintaining full JSON:API compliance internally.
docs/GUIDE/GUIDE_1_Initial_Setup.md:413:console.log('Full JSON:API response:', inspect(fullJsonApi));
docs/GUIDE/GUIDE_1_Initial_Setup.md:415:// Full JSON:API response: {
docs/GUIDE/GUIDE_1_Initial_Setup.md:435:console.log('Minimal JSON:API response:', inspect(minimalJsonApi));
docs/GUIDE/GUIDE_1_Initial_Setup.md:437:// Minimal JSON:API response: { id: '5', type: 'countries' }
docs/GUIDE/GUIDE_1_Initial_Setup.md:569:}).on('error', (err) => {
docs/GUIDE/GUIDE_1_Initial_Setup.md:570:  console.error('Failed to start server:', err);
docs/GUIDE/GUIDE_1_Initial_Setup.md:649:- **HTTP/REST**: `simplifiedTransport` defaults to `false` (JSON:API compliance)
docs/GUIDE/GUIDE_1_Initial_Setup.md:651:This means that by default, HTTP endpoints expect and return proper JSON:API format:
docs/GUIDE/GUIDE_1_Initial_Setup.md:653:Most production servers will keep `simplifiedTransport: false` to maintain JSON:API compliance for client applications. You can enable simplified mode for HTTP if needed:
docs/GUIDE/GUIDE_1_Initial_Setup.md:776:| `includeDepthLimit` | `vars.includeDepthLimit` | `3` | Maximum depth for nested relationship includes | ✓ |
docs/GUIDE/GUIDE_1_Initial_Setup.md:777:| `enablePaginationCounts` | `vars.enablePaginationCounts` | `true` | Whether to include total count in pagination metadata | ✓ |
docs/GUIDE/GUIDE_1_Initial_Setup.md:780:| `idProperty` | `vars.idProperty` | `'id'` | Name of the ID field in resources | ✓ |
docs/GUIDE/GUIDE_1_Initial_Setup.md:826:- **`api.helpers.newTransaction()`** - Creates a new database transaction for atomic operations
docs/GUIDE/GUIDE_1_Initial_Setup.md:830:    // Use transaction in multiple operations
docs/GUIDE/GUIDE_1_Initial_Setup.md:831:    await api.resources.countries.post({ name: 'France', code: 'FR' }, { transaction: trx });
docs/GUIDE/GUIDE_1_Initial_Setup.md:832:    await api.resources.publishers.post({ name: 'French Press', country: 1 }, { transaction: trx });
docs/GUIDE/GUIDE_1_Initial_Setup.md:834:  } catch (error) {
docs/GUIDE/GUIDE_1_Initial_Setup.md:835:    await trx.rollback();
docs/GUIDE/GUIDE_1_Initial_Setup.md:836:    throw error;
docs/GUIDE/GUIDE_1_Initial_Setup.md:863:- **`api.http.express.notFoundRouter`** - Express middleware for handling 404 errors
docs/GUIDE/GUIDE_1_Initial_Setup.md:943:- Managing database transactions
docs/GUIDE/GUIDE_2_1_The_Starting_Point.md:39:}).on('error', (err) => {
docs/GUIDE/GUIDE_2_1_The_Starting_Point.md:40:  console.error('Failed to start server:', err);
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:77:Let's create a comprehensive dataset that includes all relationship types. We'll use this same dataset throughout the chapter to demonstrate how each operation affects the data.
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:187:    include: ['author', 'publisher', 'genres'] 
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:225:    include: ['author', 'publisher', 'genres'] 
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:314:  // publisher_id and genres NOT included!
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:325:Again, PUT is a **complete replacement** operation. Since we didn't include `publisher_id` or `genres` in our request, the API treats this as "I want a book with no publisher and no genres." The result:
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:404:- You must include ALL data you want to keep
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:786:3. **Always include relationships you want to keep with PUT** - They will be cleared otherwise
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:787:4. **Remember required fields** - PUT must include all required fields like author_id
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:3:Pagination and ordering are essential features for working with large datasets. The json-rest-api library provides powerful pagination that applies not just to main resources, but also to included relationships. This means you can limit and order both parent records AND their children independently.
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:21:      include: {
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:22:        limit: 5,  // Default limit for included comments
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:31:      include: {
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:675:- **Multi-field sorting is fully supported** - When sorting by multiple fields (e.g., `sort: ['category', 'name']`), the cursor includes all sort fields and correctly handles records with duplicate values in the first sort field
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:879:When you include relationships in your query, they use the default `limit` and `orderBy` values defined in the relationship schema. These defaults are applied automatically and cannot be overridden in the query:
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:888://     include: {
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:895:// When you include comments, these defaults are automatically applied:
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:898:    include: ['comments'],     // Just specify which relationships to include
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:902:// HTTP: GET /api/posts?include=comments&page[number]=1&page[size]=3
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:924:    include: ['comments', 'tags'],
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:972:    self: '/api/posts?include=comments&&sort=-created_at&page[number]=1&page[size]=3',
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:973:    first: '/api/posts?include=comments&&sort=-created_at&page[number]=1&page[size]=3',
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:974:    last: '/api/posts?include=comments&&sort=-created_at&page[number]=9&page[size]=3',
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:975:    next: '/api/posts?include=comments&&sort=-created_at&page[number]=2&page[size]=3'
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1018:    self: '/api/posts?include=comments&include=tags&&sort=-created_at&page[number]=1&page[size]=2',
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1019:    first: '/api/posts?include=comments&include=tags&&sort=-created_at&page[number]=1&page[size]=2',
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1020:    last: '/api/posts?include=comments&include=tags&&sort=-created_at&page[number]=13&page[size]=2',
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1021:    next: '/api/posts?include=comments&include=tags&&sort=-created_at&page[number]=2&page[size]=2'
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1028:The API automatically generates pagination links following JSON:API specification:
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1037:  simplified: false  // Use JSON:API format to see links
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1040:// Returns (JSON:API): {
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1094:    include: ['comments', 'tags']
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1097:// HTTP: GET /api/posts?filter[title]=Even&sort=-view_count,published_at&page[number]=1&page[size]=3&include=comments,tags
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1220:    self: '/api/posts?filters[title]=Even&sort=-view_count&sort=published_at&include=comments&include=tags&&page[number]=1&page[size]=3',
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1221:    first: '/api/posts?filters[title]=Even&sort=-view_count&sort=published_at&include=comments&include=tags&&page[number]=1&page[size]=3',
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1222:    last: '/api/posts?filters[title]=Even&sort=-view_count&sort=published_at&include=comments&include=tags&&page[number]=4&page[size]=3',
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1223:    next: '/api/posts?filters[title]=Even&sort=-view_count&sort=published_at&include=comments&include=tags&&page[number]=2&page[size]=3'
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1232:- The system automatically uses SQL window functions for efficient pagination of included records when supported by the database
docs/API.md:37:   - [Sparse Fieldsets](#sparse-fieldsets)
docs/API.md:41:7. [Error Handling](#error-handling)
docs/API.md:64:    include: Array,      // Relationship paths to include
docs/API.md:71:  transaction: Object    // Database transaction object
docs/API.md:78:| `queryParams.include` | Array | No | Relationship paths to include (e.g., `['author', 'comments.user']`) |
docs/API.md:88:| `transaction` | Object | No | Database transaction object |
docs/API.md:92:**JSON:API Mode (simplified: false):**
docs/API.md:110:  included: [
docs/API.md:162:GET /articles?include=author&fields[articles]=title,content&fields[users]=name&filter[status]=published&sort=-created-at&page[number]=1&page[size]=10
docs/API.md:215:    include: ['author', 'comments.user'],
docs/API.md:225:// GET /articles?include=author,comments.user&fields[articles]=title,summary&fields[users]=name,avatar&fields[comments]=content,created-at
docs/API.md:228:**JSON:API Mode Query:**
docs/API.md:230:// Force JSON:API response format
docs/API.md:234:    include: ['author']
docs/API.md:239:// Returns full JSON:API structure with type, id, attributes, relationships
docs/API.md:275:    include: Array,      // Relationship paths to include
docs/API.md:279:  transaction: Object    // Database transaction object
docs/API.md:287:| `queryParams.include` | Array | No | Relationship paths to include |
docs/API.md:290:| `transaction` | Object | No | Database transaction object |
docs/API.md:294:**JSON:API Mode (simplified: false):**
docs/API.md:310:  included: [
docs/API.md:339:GET /articles/1?include=author&fields[articles]=title,content
docs/API.md:362:    include: ['author', 'comments']
docs/API.md:367:// GET /articles/1?include=author,comments
docs/API.md:376:    include: ['author'],
docs/API.md:385:// GET /articles/1?include=author&fields[articles]=title,summary&fields[users]=name
docs/API.md:388:**JSON:API Mode Get:**
docs/API.md:390:// Get in JSON:API format
docs/API.md:394:    include: ['author', 'tags']
docs/API.md:399:// Returns full JSON:API document structure
docs/API.md:417:  inputRecord: Object,      // Required: Resource data (JSON:API or simplified)
docs/API.md:419:    include: Array,         // For response formatting
docs/API.md:423:  transaction: Object,      // Database transaction object
docs/API.md:431:| `inputRecord.data` | Object | Yes (JSON:API) | Resource data container |
docs/API.md:432:| `inputRecord.data.type` | String | Yes (JSON:API) | Resource type |
docs/API.md:433:| `inputRecord.data.attributes` | Object | Yes (JSON:API) | Resource attributes |
docs/API.md:435:| `queryParams` | Object | No | For includes/fields in response |
docs/API.md:437:| `transaction` | Object | No | Database transaction object |
docs/API.md:493:**Create with JSON:API Format:**
docs/API.md:495:// Create article with JSON:API format
docs/API.md:527:    include: ['author', 'tags']
docs/API.md:580:    include: Array,         // For response formatting
docs/API.md:584:  transaction: Object,      // Database transaction object
docs/API.md:593:| `inputRecord.data.id` | String | Yes (JSON:API) | Must match the resource ID |
docs/API.md:594:| `inputRecord.data.type` | String | Yes (JSON:API) | Resource type |
docs/API.md:595:| `inputRecord.data.attributes` | Object | Yes (JSON:API) | All resource attributes |
docs/API.md:599:| `transaction` | Object | No | Database transaction object |
docs/API.md:648:**Replace with JSON:API Format:**
docs/API.md:650:// Replace article with JSON:API format
docs/API.md:707:    include: Array,         // For response formatting
docs/API.md:711:  transaction: Object,      // Database transaction object
docs/API.md:720:| `inputRecord.data.id` | String | Yes (JSON:API) | Resource ID |
docs/API.md:721:| `inputRecord.data.type` | String | Yes (JSON:API) | Resource type |
docs/API.md:726:| `transaction` | Object | No | Database transaction object |
docs/API.md:779:**Update with JSON:API Format:**
docs/API.md:781:// Update with JSON:API format
docs/API.md:841:  transaction: Object   // Database transaction object
docs/API.md:848:| `transaction` | Object | No | Database transaction object |
docs/API.md:878:// Delete within a transaction
docs/API.md:879:const trx = await knex.transaction();
docs/API.md:884:    transaction: trx
docs/API.md:890:    transaction: trx
docs/API.md:894:} catch (error) {
docs/API.md:895:  await trx.rollback();
docs/API.md:896:  throw error;
docs/API.md:901:The library automatically manages transaction commits when you don't provide one:
docs/API.md:902:- If you provide a transaction, you're responsible for committing/rolling back
docs/API.md:903:- If you don't provide a transaction, the library creates one and auto-commits
docs/API.md:925:  transaction: Object          // Database transaction object
docs/API.md:934:| `transaction` | Object | No | Database transaction object |
docs/API.md:938:JSON:API response with related resources (supports all query features like filtering, pagination, etc.)
docs/API.md:1004:  transaction: Object          // Database transaction object
docs/API.md:1010:JSON:API relationship document with resource identifiers only
docs/API.md:1078:  transaction: Object          // Database transaction object
docs/API.md:1135:  transaction: Object                   // Database transaction object
docs/API.md:1216:  transaction: Object          // Database transaction object
docs/API.md:1305:14. afterCommit (if transaction was created)
docs/API.md:1323:14. afterCommit (if transaction was created)
docs/API.md:1341:14. afterCommit (if transaction was created)
docs/API.md:1353:8. afterCommit (if transaction was created)
docs/API.md:1425:  transaction: {...},          // Database transaction
docs/API.md:1460:  transaction: {...},
docs/API.md:1467:- Throw errors for custom validation
docs/API.md:1489:  transaction: {...}
docs/API.md:1493:**Purpose:** Authorization checks - throw error to deny access
docs/API.md:1514:    include: ['author'],
docs/API.md:1520:  transaction: {...},
docs/API.md:1548:  record: {                    // Full JSON:API record
docs/API.md:1554:  isMainResource: true,        // vs included resource
docs/API.md:1556:  requestedFields: [...],      // Fields requested via sparse fieldsets
docs/API.md:1598:  if (context.requestedComputedFields.includes('word_count')) {
docs/API.md:1603:  if (context.requestedComputedFields.includes('reading_time')) {
docs/API.md:1619:    included: [...],
docs/API.md:1647:  error: {...},                // For rollback
docs/API.md:1899:// Response includes:
docs/API.md:1946:// Response includes:
docs/API.md:1988:// With includes - specify fields for each type
docs/API.md:1991:    include: ['author', 'category'],
docs/API.md:2014:    include: ['author']
docs/API.md:2021:    include: ['author', 'category', 'tags']
docs/API.md:2026:// GET /articles?include=author,category,tags
docs/API.md:2035:    include: ['author.profile', 'comments.user', 'category.parent']
docs/API.md:2039:// Deep nesting (limited by includeDepthLimit)
docs/API.md:2042:    include: ['comments.user.profile.avatar']
docs/API.md:2047:// GET /articles?include=comments.user.profile.avatar
docs/API.md:2052:Some implementations support filtering included resources:
docs/API.md:2055:// Custom hook to filter included resources
docs/API.md:2057:  if (context.result.included) {
docs/API.md:2058:    // Filter included comments to only show approved
docs/API.md:2059:    context.result.included = context.result.included.filter(resource => {
docs/API.md:2079:  simplifiedTransport: false,       // Use JSON:API for HTTP transport (default: false)
docs/API.md:2101:  includeDepthLimit: 3,             // Maximum relationship nesting depth
docs/API.md:2107:  exposeErrors: false,              // Include error details in responses
docs/API.md:2127:  idProperty: 'id',                 // Custom primary key field (default: 'id')
docs/API.md:2257:      includeDeleted: false
docs/API.md:2281:  idProperty: 'user_id',  // Use 'user_id' instead of 'id'
docs/API.md:2347:The library uses standard JSON:API error format:
docs/API.md:2353:  errors: [
docs/API.md:2376:  errors: [{
docs/API.md:2389:  errors: [{
docs/API.md:2401:  errors: [{
docs/API.md:2413:  errors: [{
docs/API.md:2429:    const error = new Error('Title too short');
docs/API.md:2430:    error.status = 422;
docs/API.md:2431:    error.code = 'TITLE_TOO_SHORT';
docs/API.md:2432:    error.pointer = '/data/attributes/title';
docs/API.md:2433:    throw error;
docs/API.md:2437:// Custom error transformation
docs/API.md:2438:api.hook('errorTransform', async (error, context) => {
docs/API.md:2440:    status: error.status || '500',
docs/API.md:2441:    code: error.code || 'INTERNAL_ERROR',
docs/API.md:2442:    title: error.title || 'Error',
docs/API.md:2443:    detail: error.message,
docs/API.md:2458:All methods support database transactions with automatic management:
docs/API.md:2461:// Automatic transaction (recommended)
docs/API.md:2467:  // No transaction provided - library creates and auto-commits
docs/API.md:2470:// Manual transaction management
docs/API.md:2471:const trx = await knex.transaction();
docs/API.md:2479:    transaction: trx  // Provide transaction
docs/API.md:2489:      transaction: trx  // Same transaction
docs/API.md:2494:} catch (error) {
docs/API.md:2495:  await trx.rollback();
docs/API.md:2496:  throw error;
docs/API.md:2500:**Important:** When you provide a transaction, you're responsible for committing/rolling back. When you don't provide one, the library auto-commits.
docs/API.md:2507:// Batch create with transaction
docs/API.md:2509:  const trx = await knex.transaction();
docs/API.md:2516:        transaction: trx,
docs/API.md:2524:  } catch (error) {
docs/API.md:2525:    await trx.rollback();
docs/API.md:2526:    throw error;
docs/API.md:2532:  const trx = await knex.transaction();
docs/API.md:2538:        transaction: trx,
docs/API.md:2544:  } catch (error) {
docs/API.md:2545:    await trx.rollback();
docs/API.md:2546:    throw error;
docs/API.md:2620:    include: ['commentable'],  // Includes the related article/video/photo
docs/API.md:2636:  includeDeleted: false  // Default behavior
docs/API.md:2654:      include_deleted: true
docs/API.md:2683:  if (context.requestedComputedFields.includes('can_edit')) {
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:64:// Get the French publisher and include its authors (simplified mode output)
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:68:    include: ['authors'] // Use the relationship name 'authors' defined in the publishers schema
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:107:## Including `hasMany` Records (`include`)
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:109:To retrieve related `hasMany` resources, you'll use the `include` query parameter from the "one" side of the one-to-many relationship (e.g., fetching a publisher and including its authors).
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:113:Using the exact same data as before, you can change the query to `include` countries:
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:116:// Get the French publisher and include its authors (simplified mode output)
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:120:    include: ['authors'] // Use the relationship name 'authors' defined in the publishers schema
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:125:// Get the French publisher and include its authors (simplified mode output)
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:129:    include: ['authors'] // Use the relationship name 'authors' defined in the publishers schema
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:155:  included: [
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:179:// Query all publishers and include their authors (simplified mode output)
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:182:    include: ['authors']
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:185:// HTTP: GET /api/publishers?include=authors
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:205:// Query all publishers and include their authors (non-simplified, full JSON:API output)
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:208:    include: ['authors']
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:212:// HTTP: GET /api/publishers?include=authors
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:213:// Returns (JSON:API): {
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:235://   included: [
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:320:  included: [
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:343:  links: { self: '/api/publishers?include=authors' }
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:349:In non-simplified (full JSON:API) mode, `hasMany` relationships in the `data` section of the parent resource only contain an empty `data` array or `links` to the related endpoint (e.g., `authors: { links: { related: '/api/publishers/1/authors' } }`). The actual related `author` resources are placed in the top-level `included` array. This is standard JSON:API behavior to avoid duplicating large amounts of data. The `included` array ensures that each included resource appears only once, even if referenced by multiple parent resources.
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:3:The REST API Relationships Plugin adds JSON:API compliant relationship endpoints to your API, allowing clients to view and manage relationships between resources as first-class citizens. This guide shows you how to use these powerful features with our book catalog system.
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:17:- [Error Handling](#error-handling)
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:27:4. **Atomic Operations**: Manage relationships in isolation with proper transaction support
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:102:  simplified: false  // Use full JSON:API format
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:152:    fields: { authors: 'name' }  // Optional: sparse fieldsets
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:393:Common errors you might encounter:
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:402:} catch (error) {
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:416:} catch (error) {
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:428:} catch (error) {
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:497:The plugin automatically handles transactions for data integrity:
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:515:The REST API Relationships Plugin transforms relationships from second-class citizens to fully manageable resources. It provides efficient, standards-compliant endpoints that make working with related data intuitive and performant. By following JSON:API specifications, it ensures your API remains consistent and predictable for clients.
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:5:Let's expand our schema definitions to include `publishers` and link them to `countries`. These schemas will be defined **once** here and reused throughout this section.
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:93:## Including `belongsTo` Records (`include`)
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:95:To retrieve related `belongsTo` resources, use the `include` query parameter.
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:97:When fetching data programmatically, `simplified` mode is `true` by default. This means that instead of a separate `included` array (as in full JSON:API), related `belongsTo` resources are **denormalized and embedded directly** within the main resource's object structure, providing a very convenient and flat data structure for immediate use.
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:119:// Get a publisher and include its country (simplified mode output)
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:123:    include: ['country'] // Use the 'as' alias defined in the schema
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:128:// Query all publishers and include their countries (simplified mode output)
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:131:    include: ['country']
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:134:// HTTP: GET /api/publishers?include=country
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:145:// Query all publishers and include their countries (JSON:API format)
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:148:    include: ['country']
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:152:// HTTP: GET /api/publishers?include=country
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:153:// Returns (JSON:API): {
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:164://   included: [
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:174:Here is the expected output. Notice how the last call shows the non-simplified version of the response, which is muc more verbose. However, it has one _major_ advantage: it only includes the information about France _once_. It might seem like a small gain here, but when you have complex queries where the `belongsTo` table has a lot of data, the saving is much more evident.
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:288:  included: [
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:311:  links: { self: '/api/publishers?include=country' }
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:319:You can apply **sparse fieldsets** not only to the primary resource but also to the included `belongsTo` resources. This is powerful for fine-tuning your API responses and reducing payload sizes.
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:336:// Get a publisher, include its country, but only retrieve publisher name and country code
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:337:const sparsePublisher = await api.resources.publishers.get({
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:340:    include: ['country'],
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:348:console.log('Sparse Publisher and Country:', inspect(sparsePublisher));
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:351:// Query all publishers, include their countries, but only retrieve publisher name and country code
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:352:const sparsePublishersQuery = await api.resources.publishers.query({
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:354:    include: ['country'],
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:362:// HTTP: GET /api/publishers?include=country&fields[publishers]=name&fields[countries]=code,name
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:369:console.log('Sparse Publishers Query (all results):', inspect(sparsePublishersQuery));
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:375:When you specify `fields: { countries: ['code'] }`, this instruction applies to *all* `country` resources present in the API response, whether `country` is the primary resource you are querying directly, or if it's included as a related resource. This ensures consistent data representation across the entire response.
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:144:  const { data, error } = await supabase.auth.signUp({
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:148:  if (error) throw error
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:154:  const { data, error } = await supabase.auth.signInWithPassword({
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:158:  if (error) throw error
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:249:    const { data, error } = await supabase.auth.signInWithOAuth({
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:255:    if (error) throw error
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:308:  ownershipField: 'user_id'       // Field for ownership checks (default: 'user_id')
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:469:// Also enable auto-ownership
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:198:## Including Many-to-Many Records (`include`)
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:206:console.log('Book without includes (simplified):', inspect(book_simplified));
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:207:console.log('Book without includes (non-simplified):', inspect(book_non_simplified));
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:213:Book without includes (simplified):
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:222:Book without includes (non-simplified):
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:246:## Including Many to many Records (`include`)
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:248:To retrieve the full related resources through a many-to-many relationship, use the `include` query parameter:
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:251:// Get a book with its authors included
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:254:  queryParams: { include: ['authors'] } 
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:259:  queryParams: { include: ['authors'] }, 
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:265:  queryParams: { include: ['authors'] } 
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:269:  queryParams: { include: ['authors'] }, 
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:325:  included: [
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:409:  links: { self: '/api/books?include=authors' }
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:474:  included: [
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:509:  links: { self: '/api/books?include=authors' }
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:514:The include system automatically handles the JOIN through the pivot table. In simplified mode, the related resources are embedded directly. In JSON:API mode, they appear in the `included` array.
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:520:Using the schema definitions from above, which include cross-table search fields:
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:543:// 3. Combine searches: Find books by Neil that include full author data
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:547:    include: ['authors'] 
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:690:  links: { self: '/api/books?filters[authorName]=Neil&include=authors' }
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:90:- Schema includes `reviewable_type` and `reviewable_id` fields
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:101:`json-rest-api` provides flexibility in how you provide data for polymorphic relationships when creating new records, depending on whether you're using the simplified API mode (default for programmatic calls) or the strict JSON:API format.
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:125:// Add reviews using non-simplified (JSON:API standard) input with relationships object
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:174:// Returns (JSON:API): {
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:204:* **Non-Simplified Mode (JSON:API Standard):** This adheres to the JSON:API specification, where relationships are explicitly defined in the `relationships` object with a resource identifier object (`{ type: 'resourceType', id: 'resourceId' }`). This is typically used when interacting with the API via HTTP or when strict JSON:API compliance is required.
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:314:When you fetch the french author after adding the reviews, you only get the review _ids_ and not the full review data. This is expected. To fetch the actual reviews, you will need to `include` them.
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:316:## Including Polymorphic Records (`include`)
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:318:To retrieve related polymorphic resources (e.g., getting a publisher and including all its reviews, or getting an author and including all their reviews), you'll use the **`include` query parameter** from the "one" side of the polymorphic relationship.
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:325:const frenchAuthor1_with_reviews_and_includes_simplified = await api.resources.authors.get({ id: frenchAuthor1_ns.id, queryParams: { include: ['reviews'] } });
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:326:const frenchAuthor1_with_reviews_and_includes_non_simplified = await api.resources.authors.get({ id: frenchAuthor1_ns.id, queryParams: { include: ['reviews'] }, simplified: false });
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:328:const french_authors_with_includes_simplified = await api.resources.authors.query({queryParams: { include: ['reviews'] } });
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:329:// HTTP: GET /api/authors?include=reviews
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:337:const french_authors_with_includes_non_simplified = await api.resources.authors.query({queryParams: { include: ['reviews'] }, simplified: false });
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:338:// HTTP: GET /api/authors?include=reviews
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:339:// Returns (JSON:API): {
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:348://   included: [
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:356:console.log(inspect(frenchAuthor1_with_reviews_and_includes_simplified));
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:358:console.log(inspect(frenchAuthor1_with_reviews_and_includes_non_simplified))
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:361:console.log(inspect(french_authors_with_includes_simplified));
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:363:console.log(inspect(french_authors_with_includes_non_simplified))
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:409:  included: [
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:500:  included: [
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:516:  links: { self: '/api/authors?include=reviews' }
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:522:When you explicitly request **non-simplified output**, the polymorphic `hasMany` relationships will appear in the **`included` array** at the top level of the JSON:API document. The parent resource's `relationships` object will contain links to the related endpoint but not the full related data itself.
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:530:This happens when you want to search reviews, and you want to also search in the reviewed item's information. For example searching for 'Apress' would return all reviews where the publisher name includes `Apress`. this is a very common scenario.  This is achieved via cross-table filtering.
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:699:// Returns (JSON:API): {
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:718:// Returns (JSON:API): {
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:730:// Returns (JSON:API): {
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:860:The output demonstrates how polymorphic search works across both simplified and JSON:API formats:
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:869:In the simplified format, notice how the polymorphic fields are included:
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:41:You can do the same thing talking to the server directly (although you will be dealing with JSON:API results).
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:120:As you can see, using PUT on Italy was a problem: since put didn't include the `code` field, and since PUT assumes a FULL record, the `code` field was reset to null. On the other hand, since the method PATCH assumes a partial update, the update for Germany did not _not_ overwrite the `code` field. This is a very important distinction, and it's the reason why most clients avoid PUT calls.
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:373:// Result: The effective searchSchema includes:
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:617:The JSON:API specification includes a powerful feature called "sparse fieldsets" that allows you to request only specific fields from a resource. This is essential for optimizing API performance by reducing payload sizes and network traffic.
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:621:By default, API responses include all fields defined in the schema. With sparse fieldsets, you can specify exactly which fields you want returned. The `id` field is always included automatically as it's required by JSON:API.
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:770:const sparseSearch = await api.resources.countries.query({
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:781:console.log('Sparse search result:', inspect(sparseSearch));
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:782:// Note: sparseSearch contains { data, meta, links }
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:787:1. **The `id` field is always included** - This is required by the JSON:API specification
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:790:4. **Works with relationships** - When we cover relationships, you'll see how to apply sparse fieldsets to related resources too
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:794:When using the HTTP API, sparse fieldsets are specified as comma-separated values:
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:7:Inside hooks, JSON:API format is king _regardless of simplified mode_.
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:9:  - **Input**: How parameters are passed to the API (simplified data is converted to JSON:API before entering the lifecycle)
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:10:  - **Output**: How the record is returned to the client (JSON:API data is converted to simplified format before returning)
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:11:Hook context _always_ contains: Full JSON:API formatted records with `data`, `type`, `attributes`, and `relationships`. So, as far as the hooks are concerned, `context.inputRecord` is _always_ a full JSON:API object.
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:13:This means when writing hooks, you always work with the standard JSON:API structure:
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:15:// In a hook, the record is ALWAYS JSON:API format:
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:18:    // Even in simplified mode, inputRecord has JSON:API structure
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:27:    // This is called for ALL records (main and included/child records)
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:154:    // This will enrich all attributes (main and included) when fetched
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:158:      // This wordCount is calculated on-the-fly and included in the response
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:208:      return vars.allowedStatuses.includes(status);
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:297:5. **Handle Errors Gracefully** - Thrown errors in hooks will stop the operation and return to the client
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:316:  - `idProperty` - Primary key field name
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:319:- `transaction` (object/null) - Database transaction if provided
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:320:- `db` (object) - Database connection (knex instance or transaction)
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:324:- `inputRecord` (object) - The JSON:API formatted input containing:
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:334:  - `include` - Relationships to include
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:338:- `record` (object) - The fetched/created record in JSON:API format
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:350:6. **Transaction hooks** - Commit/rollback for write operations
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:369:  - `idProperty` - Primary key field name
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:372:  - `include` - Relationships to include
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:376:- `transaction` (object/null) - Database transaction if provided
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:377:- `db` (object) - Database connection (knex instance or transaction)
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:387:- `queryParams.include` - Modify includes
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:423:- `record` (object) - The JSON:API formatted response
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:425:  - `included` - Array of included resources
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:485:  - `include` - Relationships to include
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:486:- `transaction` (object/null) - Database transaction if provided
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:495:- `queryParams.include` - Modify includes
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:511:- `record` (object) - The fetched JSON:API record
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:514:- Can throw errors to deny access
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:546:- `record` (object) - The JSON:API formatted response
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:590:- `inputRecord` (object) - The JSON:API formatted input data
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:595:- `queryParams` (object) - Contains `fields`, `include` for response
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:596:- `transaction` (object) - Database transaction (created if not provided)
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:597:- `shouldCommit` (boolean) - Whether to commit the transaction
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:613:**Note**: `context.minimalRecord` is always a JSON:API resource object; for POST requests it is a snapshot of the incoming payload (`{ type, id?, attributes, relationships }`).
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:690:- `record` (object) - The JSON:API formatted response
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:700:**When**: After the transaction is committed (only if `shouldCommit` is true)  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:726:**When**: If an error occurs and transaction is rolled back  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:729:**Context**: All accumulated context plus error information  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:757:- `inputRecord` (object) - The JSON:API formatted input data
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:760:- `queryParams` (object) - Contains `fields`, `include` for response
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:761:- `transaction` (object) - Database transaction
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:762:- `shouldCommit` (boolean) - Whether to commit the transaction
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:832:- `record` (object) - The JSON:API formatted response
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:850:**When**: After the transaction is committed  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:856:**When**: If an error occurs and transaction is rolled back  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:857:**Context**: All accumulated context plus error information  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:885:- `inputRecord` (object) - The JSON:API formatted input data (partial)
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:888:- `queryParams` (object) - Contains `fields`, `include` for response
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:889:- `transaction` (object) - Database transaction
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:890:- `shouldCommit` (boolean) - Whether to commit the transaction
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:957:- `record` (object) - The JSON:API formatted response
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:975:**When**: After the transaction is committed  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:981:**When**: If an error occurs and transaction is rolled back  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:982:**Context**: All accumulated context plus error information  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1010:- `transaction` (object) - Database transaction
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1011:- `shouldCommit` (boolean) - Whether to commit the transaction
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1018:- Can throw errors to prevent deletion
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1093:**When**: After the transaction is committed  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1101:**When**: If an error occurs and transaction is rolled back  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1102:**Context**: All accumulated context plus error information  
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1121:The `enrichAttributes` hook is the correct way to add or modify attributes on records. This hook is called for ALL records - both main records and included/related records.
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1141:- This hook is called for EVERY record (main and included)
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1308:Throwing an error in any hook will:
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1310:- Trigger rollback for write operations
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1311:- Return the error to the client
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1328:- Use database transactions appropriately
docs/GUIDE/index.md:25:   Implement drag-and-drop functionality with automatic position management.
docs/GUIDE/index.md:37:  Handle multiple records efficiently with bulk create, update, and delete operations.
docs/GUIDE/index.md:39:- **[CORS Configuration](GUIDE_X_Cors.md)**  
docs/GUIDE/GUIDE_2_Data_And_Relations.md:3:The true power of json-rest-api lies in its sophisticated handling of relationships between resources. Unlike traditional ORMs that focus on object mapping, this library provides a fully JSON:API compliant REST interface that elegantly manages complex data relationships. This guide will walk you through defining datasets and explore all the possible relationship types available in the system.
docs/GUIDE/GUIDE_2_Data_And_Relations.md:47:- Limits and ordering for included relationships
docs/GUIDE/GUIDE_2_Data_And_Relations.md:48:- Combining filters, sorting, pagination, and includes
docs/GUIDE/GUIDE_X_Cors.md:1:# CORS Plugin Guide
docs/GUIDE/GUIDE_X_Cors.md:3:Cross-Origin Resource Sharing (CORS) is essential for APIs accessed by web browsers. This guide shows you how to use the CORS plugin to enable cross-origin requests in your JSON REST API.
docs/GUIDE/GUIDE_X_Cors.md:13:## Intro: What is CORS
docs/GUIDE/GUIDE_X_Cors.md:15:CORS stands for **Cross-Origin Resource Sharing**. It's a security feature implemented by web browsers to protect users from malicious websites.
docs/GUIDE/GUIDE_X_Cors.md:17:**The server ALWAYS responds to requests regardless of origin.** CORS is enforced by the BROWSER, not the server. This is a crucial distinction:
docs/GUIDE/GUIDE_X_Cors.md:20:- 🛡️ **Browsers**: Block the response from reaching JavaScript if CORS headers don't allow it
docs/GUIDE/GUIDE_X_Cors.md:21:- 🔓 **Non-browser clients** (cURL, Postman, mobile apps): Not affected by CORS at all
docs/GUIDE/GUIDE_X_Cors.md:28:- Without CORS, the browser will block requests from the web app to the API
docs/GUIDE/GUIDE_X_Cors.md:30:CORS allows servers to specify which origins (domains) are permitted to access their resources. Here's the flow:
docs/GUIDE/GUIDE_X_Cors.md:34:3. **Server Response**: The server ALWAYS responds with data + CORS headers
docs/GUIDE/GUIDE_X_Cors.md:50:Without CORS headers from the API:
docs/GUIDE/GUIDE_X_Cors.md:54:❌ Console error: "CORS policy: No 'Access-Control-Allow-Origin' header"
docs/GUIDE/GUIDE_X_Cors.md:59:With CORS headers from the API:
docs/GUIDE/GUIDE_X_Cors.md:62:✅ API includes: Access-Control-Allow-Origin: https://coolweather.app
docs/GUIDE/GUIDE_X_Cors.md:67:**CORS does NOT protect your API from:**
docs/GUIDE/GUIDE_X_Cors.md:74:**CORS ONLY protects:**
docs/GUIDE/GUIDE_X_Cors.md:78:**Therefore:** CORS is about protecting users, not protecting your API. You still need:
docs/GUIDE/GUIDE_X_Cors.md:86:The CORS plugin automatically handles preflight requests and adds appropriate headers to all responses.
docs/GUIDE/GUIDE_X_Cors.md:88:The basic CORS setup creates a permissive configuration suitable for development:
docs/GUIDE/GUIDE_X_Cors.md:102:The combination of `origin: '*'` (wildcard) and `credentials: true` is actually invalid according to CORS specification. When credentials are enabled, browsers require a specific origin. The CORS plugin handles this by dynamically setting the origin to match the request.
docs/GUIDE/GUIDE_X_Cors.md:106:Here's a complete API setup with CORS enabled with the most permissive setup:
docs/GUIDE/GUIDE_X_Cors.md:148:app.listen(3000).on('error', (err) => {
docs/GUIDE/GUIDE_X_Cors.md:149:  console.error('Failed to start server:', err);
docs/GUIDE/GUIDE_X_Cors.md:233:The CORS plugin accepts various configuration options.
docs/GUIDE/GUIDE_X_Cors.md:249:Each example below shows different ways to configure CORS for specific scenarios. We'll explain what each does, why you'd use it, and provide real-world context.
docs/GUIDE/GUIDE_X_Cors.md:303:- For each allowed origin, the response includes that specific origin
docs/GUIDE/GUIDE_X_Cors.md:415:      if (origin.includes('localhost') && process.env.NODE_ENV === 'development') {
docs/GUIDE/GUIDE_X_Cors.md:483:These are headers the browser is allowed to include in requests:
docs/GUIDE/GUIDE_X_Cors.md:523:These examples show typical CORS configurations for different scenarios you'll encounter in real projects.
docs/GUIDE/GUIDE_X_Cors.md:547:- ✅ Any developer can work with the API without CORS issues
docs/GUIDE/GUIDE_X_Cors.md:742:These patterns show sophisticated CORS configurations for complex real-world scenarios.
docs/GUIDE/GUIDE_X_Cors.md:747:2. **Per-Route CORS**: Some endpoints need different CORS rules
docs/GUIDE/GUIDE_X_Cors.md:748:3. **Authentication Integration**: CORS and auth systems must work together
docs/GUIDE/GUIDE_X_Cors.md:787:          return allowed.includes(origin) || partners.includes(origin);
docs/GUIDE/GUIDE_X_Cors.md:803:### Per-Route CORS (Using Hooks)
docs/GUIDE/GUIDE_X_Cors.md:805:**Important Understanding:** The CORS plugin is essentially a sophisticated header management system. It:
docs/GUIDE/GUIDE_X_Cors.md:812:**Why You'd Need Per-Route CORS:**
docs/GUIDE/GUIDE_X_Cors.md:823:  // Add extra CORS headers for admin routes
docs/GUIDE/GUIDE_X_Cors.md:832:1. Hooks into the response pipeline AFTER the CORS plugin
docs/GUIDE/GUIDE_X_Cors.md:834:3. Overrides the CORS headers for stricter control
docs/GUIDE/GUIDE_X_Cors.md:840:// Different CORS policies for different route types
docs/GUIDE/GUIDE_X_Cors.md:842:  order: -900 // Run after CORS plugin (which is -1000)
docs/GUIDE/GUIDE_X_Cors.md:856:    if (adminOrigins.includes(request.headers.origin)) {
docs/GUIDE/GUIDE_X_Cors.md:860:      // Remove CORS headers entirely - block the request
docs/GUIDE/GUIDE_X_Cors.md:871:    if (partner && partner.allowedOrigins.includes(request.headers.origin)) {
docs/GUIDE/GUIDE_X_Cors.md:889:    // Special CORS for file uploads
docs/GUIDE/GUIDE_X_Cors.md:903:However, this must happen before the CORS plugin is registered, since the order in which URLs are added will matter.
docs/GUIDE/GUIDE_X_Cors.md:905:### CORS with Authentication
docs/GUIDE/GUIDE_X_Cors.md:907:**The Critical Relationship Between CORS and Authentication**
docs/GUIDE/GUIDE_X_Cors.md:909:CORS and authentication are deeply intertwined. When your API uses authentication, CORS configuration becomes security-critical.
docs/GUIDE/GUIDE_X_Cors.md:913:1. **Credentials in CORS** means:
docs/GUIDE/GUIDE_X_Cors.md:924:   - Wrong CORS + auth = security vulnerability
docs/GUIDE/GUIDE_X_Cors.md:926:   - User's cookies would be automatically included
docs/GUIDE/GUIDE_X_Cors.md:928:**How Authentication Flows Work with CORS:**
docs/GUIDE/GUIDE_X_Cors.md:934:   3. Browser includes Origin: https://app.example.com
docs/GUIDE/GUIDE_X_Cors.md:945:   2. Browser automatically includes auth cookie
docs/GUIDE/GUIDE_X_Cors.md:946:   3. Browser includes Origin: https://app.example.com
docs/GUIDE/GUIDE_X_Cors.md:948:   5. API responds with user data and CORS headers
docs/GUIDE/GUIDE_X_Cors.md:963:      if (knownOrigins.includes(origin)) {
docs/GUIDE/GUIDE_X_Cors.md:1000:      return allowedOrigins.includes(origin);
docs/GUIDE/GUIDE_X_Cors.md:1031:**Note:** These examples show CORS configuration patterns for different authentication methods. JWT generation itself is not part of this library - you'll use your own authentication service (Supabase, Auth0, Firebase Auth, etc.) or implement your own JWT generation.
docs/GUIDE/GUIDE_X_Cors.md:1048:2. **Bearer Token Pattern (No CORS Credentials):**
docs/GUIDE/GUIDE_X_Cors.md:1065:    // Bearer token auth - no CORS credentials needed
docs/GUIDE/GUIDE_X_Cors.md:1068:    // Cookie auth - needs CORS credentials
docs/GUIDE/GUIDE_X_Cors.md:1074:### CORS with Base URL
docs/GUIDE/GUIDE_X_Cors.md:1076:The CORS plugin works seamlessly with Express base paths.
docs/GUIDE/GUIDE_X_Cors.md:1095:**How CORS Works with Base URLs:**
docs/GUIDE/GUIDE_X_Cors.md:1097:The CORS plugin automatically handles the base path. When you set a base path, CORS headers are applied to ALL routes under that path.
docs/GUIDE/GUIDE_X_Cors.md:1106:// CORS plugin handles the base path automatically
docs/GUIDE/GUIDE_X_Cors.md:1113:// Client requests to /v1/api/articles will have proper CORS headers
docs/GUIDE/GUIDE_X_Cors.md:1123:2. CORS plugin registers OPTIONS handler for `/v1/*`
docs/GUIDE/GUIDE_X_Cors.md:1125:3. All requests under `/v1` get CORS headers
docs/GUIDE/GUIDE_X_Cors.md:1146:// API v2 with different CORS
docs/GUIDE/GUIDE_X_Cors.md:1165:// GET /v1/api/users - CORS allows legacy.example.com
docs/GUIDE/GUIDE_X_Cors.md:1166:// GET /v2/api/users - CORS blocks legacy.example.com
docs/GUIDE/GUIDE_X_Cors.md:1171:1. CORS applies to the entire base path, not individual routes
docs/GUIDE/GUIDE_X_Cors.md:1172:2. You cannot have different CORS settings for routes under the same base path
docs/GUIDE/GUIDE_X_Cors.md:1173:3. The base path is transparent to CORS origin checks
docs/GUIDE/GUIDE_X_Cors.md:1182:| "CORS header 'Access-Control-Allow-Origin' missing" | Origin not allowed | Check origin configuration, ensure it matches exactly |
docs/GUIDE/GUIDE_X_Cors.md:1184:| "Multiple CORS headers" | Multiple CORS middleware | Ensure CORS plugin is installed only once |
docs/GUIDE/GUIDE_X_Cors.md:1185:| Preflight fails with 404 | OPTIONS route not registered | Check that CORS plugin is installed after transport plugin |
docs/GUIDE/GUIDE_X_Cors.md:1188:### Debug CORS Issues
docs/GUIDE/GUIDE_X_Cors.md:1198:// The CORS plugin will log:
docs/GUIDE/GUIDE_X_Cors.md:1207:2024-01-15T10:23:45.123Z [DEBUG] [my-api:plugin:rest-api-cors] CORS OPTIONS request { origin: 'https://app.example.com' }
docs/GUIDE/GUIDE_X_Cors.md:1208:2024-01-15T10:23:45.124Z [DEBUG] [my-api:plugin:rest-api-cors] CORS processing response {
docs/GUIDE/GUIDE_X_Cors.md:1213:2024-01-15T10:23:45.125Z [WARN] [my-api:plugin:rest-api-cors] CORS origin not allowed {
docs/GUIDE/GUIDE_X_Cors.md:1226:   - Shows CORS headers being added to regular requests
docs/GUIDE/GUIDE_X_Cors.md:1240:    console.log('CORS Debug:', {
docs/GUIDE/GUIDE_X_Cors.md:1252:### Testing CORS
docs/GUIDE/GUIDE_X_Cors.md:1254:**Understanding CORS Testing**
docs/GUIDE/GUIDE_X_Cors.md:1256:CORS is enforced by browsers, not servers. This creates interesting testing scenarios:
docs/GUIDE/GUIDE_X_Cors.md:1257:- **cURL/Postman**: Requests always work (no CORS enforcement)
docs/GUIDE/GUIDE_X_Cors.md:1258:- **Browser**: Requests blocked if CORS headers are wrong
docs/GUIDE/GUIDE_X_Cors.md:1259:- **Server**: Always sends CORS headers, doesn't block requests
docs/GUIDE/GUIDE_X_Cors.md:1271:- Default CORS configuration (allows all origins)
docs/GUIDE/GUIDE_X_Cors.md:1312:1. **cURL Always Succeeds** - Even with wrong CORS headers:
docs/GUIDE/GUIDE_X_Cors.md:1319:2. **Browser Testing is Required** for real CORS validation:
docs/GUIDE/GUIDE_X_Cors.md:1321:   // In browser console (will fail if CORS is wrong)
docs/GUIDE/GUIDE_X_Cors.md:1325:     .catch(e => console.error('CORS Error:', e));
docs/GUIDE/GUIDE_X_Cors.md:1346:2. Check Response Headers for CORS headers
docs/GUIDE/GUIDE_X_Cors.md:1347:3. For failed requests, check Console for CORS errors
docs/GUIDE/GUIDE_X_Cors.md:1349:### Common CORS Headers Explained
docs/GUIDE/GUIDE_X_Cors.md:1351:**Understanding Each CORS Header in Detail**
docs/GUIDE/GUIDE_X_Cors.md:1353:#### Core CORS Headers
docs/GUIDE/GUIDE_X_Cors.md:1370:if (allowedOrigins.includes(request.headers.origin)) {
docs/GUIDE/GUIDE_X_Cors.md:1374:| `Access-Control-Allow-Credentials` | **Security Critical** - Allows browser to include cookies and auth headers | `true` |
docs/GUIDE/GUIDE_X_Cors.md:1401:- Must include the requested method
docs/GUIDE/GUIDE_X_Cors.md:1414:| `Access-Control-Allow-Headers` | **Preflight Only** - Lists headers the client can include | `Content-Type, Authorization, X-Requested-With` |
docs/GUIDE/GUIDE_X_Cors.md:1418:- Must include all headers the client will send
docs/GUIDE/GUIDE_X_Cors.md:1496:- Prevents wrong CORS headers being cached
docs/GUIDE/GUIDE_X_Cors.md:1497:- Should always include `Origin` when CORS headers vary
docs/GUIDE/GUIDE_X_Cors.md:1511:#### Additional CORS Headers
docs/GUIDE/GUIDE_X_Cors.md:1517:| `Origin` | **Request** - Identifies requesting origin | All CORS requests |
docs/GUIDE/GUIDE_X_Cors.md:1537:CORS is a security feature, but misconfiguration can create vulnerabilities. Here's what you need to know:
docs/GUIDE/GUIDE_X_Cors.md:1579:  credentials: 'include'  // Would send victim's cookies
docs/GUIDE/GUIDE_X_Cors.md:1616:      return origin.includes('mycompany'); // subdomain.mycompany.evil.com would pass!
docs/GUIDE/GUIDE_X_Cors.md:1640:      console.warn('CORS: Rejected origin:', origin);
docs/GUIDE/GUIDE_X_Cors.md:1767:**The Rule:** CORS is not a replacement for HTTPS. Always use HTTPS in production.
docs/GUIDE/GUIDE_X_Cors.md:1770:CORS only controls which websites can access your API from a browser. It doesn't:
docs/GUIDE/GUIDE_X_Cors.md:1776:**What CORS Does vs What HTTPS Does:**
docs/GUIDE/GUIDE_X_Cors.md:1778:| Security Aspect | CORS | HTTPS |
docs/GUIDE/GUIDE_X_Cors.md:1798:        return allowedOrigins.includes(origin);
docs/GUIDE/GUIDE_X_Cors.md:1814:**Common HTTPS + CORS Issues:**
docs/GUIDE/GUIDE_X_Cors.md:1838:**The Rule:** Periodically review and audit your CORS configuration to remove unused origins and tighten security.
docs/GUIDE/GUIDE_X_Cors.md:1864:  console.log('=== CORS Origin Audit ===');
docs/GUIDE/GUIDE_X_Cors.md:1904:        console.error(`SECURITY RISK: ${origin} may be compromised`);
docs/GUIDE/GUIDE_X_Cors.md:1907:    } catch (error) {
docs/GUIDE/GUIDE_X_Cors.md:1908:      console.error(`Cannot verify ${origin}: ${error.message}`);
docs/GUIDE/GUIDE_X_Cors.md:1937:// This ensures all server instances have consistent CORS settings
docs/GUIDE/GUIDE_X_Cors.md:1948:    origin.includes('ngrok.io') ||
docs/GUIDE/GUIDE_X_Cors.md:1949:    origin.includes('localhost.run') ||
docs/GUIDE/GUIDE_X_Cors.md:1950:    origin.includes('.local') ||
docs/GUIDE/GUIDE_X_Bulk_Operations.md:3:The **Bulk Operations Plugin** enables efficient processing of multiple records in a single request, supporting atomic transactions, batch processing, and error handling. This guide demonstrates how to create, update, and delete multiple records efficiently.
docs/GUIDE/GUIDE_X_Bulk_Operations.md:14:- **bulkPost**: Create multiple records
docs/GUIDE/GUIDE_X_Bulk_Operations.md:15:- **bulkPatch**: Update multiple records
docs/GUIDE/GUIDE_X_Bulk_Operations.md:16:- **bulkDelete**: Delete multiple records
docs/GUIDE/GUIDE_X_Bulk_Operations.md:24:import { BulkOperationsPlugin } from 'json-rest-api/plugins/core/bulk-operations-plugin.js';
docs/GUIDE/GUIDE_X_Bulk_Operations.md:50:  'bulk-operations': {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:52:    defaultAtomic: true,        // Default transaction mode
docs/GUIDE/GUIDE_X_Bulk_Operations.md:68:| `enableOptimizations` | boolean | true | Enable database-specific bulk optimizations when available |
docs/GUIDE/GUIDE_X_Bulk_Operations.md:147:## Bulk Create (bulkPost)
docs/GUIDE/GUIDE_X_Bulk_Operations.md:149:Create multiple records in a single operation. The `bulkPost` method accepts an array of JSON:API documents.
docs/GUIDE/GUIDE_X_Bulk_Operations.md:155:const bulkCreateResult = await api.scopes.authors.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:164:console.log(inspect(bulkCreateResult));
docs/GUIDE/GUIDE_X_Bulk_Operations.md:195:const publisherResult = await api.scopes.publishers.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:222:const partialResult = await api.scopes.authors.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:238://   errors: [{
docs/GUIDE/GUIDE_X_Bulk_Operations.md:240://     status: 'error',
docs/GUIDE/GUIDE_X_Bulk_Operations.md:241://     error: {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:256:## Bulk Update (bulkPatch)
docs/GUIDE/GUIDE_X_Bulk_Operations.md:264:const bulkUpdateResult = await api.scopes.authors.bulkPatch({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:286:console.log('Updated authors:', bulkUpdateResult.meta.succeeded);
docs/GUIDE/GUIDE_X_Bulk_Operations.md:295:const bookResults = await api.scopes.books.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:316:const updateOps = await api.scopes.books.bulkPatch({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:351:const errorResult = await api.scopes.authors.bulkPatch({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:360:console.log(inspect(errorResult));
docs/GUIDE/GUIDE_X_Bulk_Operations.md:361:// Shows successful updates and errors for failed operations
docs/GUIDE/GUIDE_X_Bulk_Operations.md:364:## Bulk Delete (bulkDelete)
docs/GUIDE/GUIDE_X_Bulk_Operations.md:372:const bulkDeleteResult = await api.scopes.authors.bulkDelete({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:377:console.log(inspect(bulkDeleteResult));
docs/GUIDE/GUIDE_X_Bulk_Operations.md:397:  await api.scopes.countries.bulkDelete({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:401:} catch (error) {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:402:  console.log('Cannot delete:', error.message);
docs/GUIDE/GUIDE_X_Bulk_Operations.md:407:await api.scopes.books.bulkDelete({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:413:await api.scopes.countries.bulkDelete({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:424:const mixedResult = await api.scopes.authors.bulkDelete({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:439://   errors: [
docs/GUIDE/GUIDE_X_Bulk_Operations.md:440://     { index: 1, id: '999', status: 'error', error: { code: 'REST_API_RESOURCE', message: 'Resource not found' } },
docs/GUIDE/GUIDE_X_Bulk_Operations.md:441://     { index: 3, id: '888', status: 'error', error: { code: 'REST_API_RESOURCE', message: 'Resource not found' } }
docs/GUIDE/GUIDE_X_Bulk_Operations.md:448:When using the Express plugin, bulk operations are available via HTTP endpoints:
docs/GUIDE/GUIDE_X_Bulk_Operations.md:463:  console.log('API with bulk operations running on http://localhost:3000');
docs/GUIDE/GUIDE_X_Bulk_Operations.md:464:}).on('error', (err) => {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:465:  console.error('Failed to start server:', err);
docs/GUIDE/GUIDE_X_Bulk_Operations.md:473:POST /api/authors/bulk
docs/GUIDE/GUIDE_X_Bulk_Operations.md:484:POST /api/authors/bulk?atomic=false
docs/GUIDE/GUIDE_X_Bulk_Operations.md:490:PATCH /api/authors/bulk
docs/GUIDE/GUIDE_X_Bulk_Operations.md:504:DELETE /api/authors/bulk
docs/GUIDE/GUIDE_X_Bulk_Operations.md:526:  'bulk-operations': {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:538:const result = await api.scopes.authors.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:551:// Add a hook that runs for each bulk operation
docs/GUIDE/GUIDE_X_Bulk_Operations.md:552:api.addHook('beforePost', 'bulkTracking', {}, async ({ context, params }) => {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:553:  if (context.bulkOperation) {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:554:    console.log(`Processing bulk item ${context.bulkIndex + 1}`);
docs/GUIDE/GUIDE_X_Bulk_Operations.md:561:Implement robust error handling for bulk operations:
docs/GUIDE/GUIDE_X_Bulk_Operations.md:566:    const result = await api.scopes.authors.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:568:      atomic: false  // Continue on errors
docs/GUIDE/GUIDE_X_Bulk_Operations.md:574:    // Handle errors if any
docs/GUIDE/GUIDE_X_Bulk_Operations.md:575:    if (result.errors && result.errors.length > 0) {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:576:      console.error('Import errors:');
docs/GUIDE/GUIDE_X_Bulk_Operations.md:577:      result.errors.forEach(error => {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:578:        console.error(`  Row ${error.index}: ${error.error.message}`);
docs/GUIDE/GUIDE_X_Bulk_Operations.md:583:        result.errors.some(e => e.index === index)
docs/GUIDE/GUIDE_X_Bulk_Operations.md:588:  } catch (error) {
docs/GUIDE/GUIDE_X_Bulk_Operations.md:589:    // Handle complete failure (e.g., database connection error)
docs/GUIDE/GUIDE_X_Bulk_Operations.md:590:    console.error('Bulk import failed completely:', error.message);
docs/GUIDE/GUIDE_X_Bulk_Operations.md:591:    throw error;
docs/GUIDE/GUIDE_X_Bulk_Operations.md:600:3. **Enable Optimizations**: The plugin uses database-specific bulk insert optimizations when available
docs/GUIDE/GUIDE_X_Bulk_Operations.md:611:  const countryResult = await api.scopes.countries.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:618:  const publisherResult = await api.scopes.publishers.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:625:  const authorResult = await api.scopes.authors.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:632:  const bookResult = await api.scopes.books.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:657:  const relationshipResult = await api.scopes.book_authors.bulkPost({
docs/GUIDE/GUIDE_X_Bulk_Operations.md:676:    errors: bookResult.errors || []
docs/GUIDE/GUIDE_X_Bulk_Operations.md:719:- **Three Core Operations**: bulkPost, bulkPatch, and bulkDelete
docs/GUIDE/GUIDE_X_Bulk_Operations.md:723:- **Full JSON:API Support**: Maintains compatibility with standard format
docs/GUIDE/GUIDE_X_Bulk_Operations.md:724:- **HTTP Endpoints**: RESTful API for bulk operations
docs/GUIDE/GUIDE_X_Bulk_Operations.md:726:Use bulk operations when you need to:
docs/GUIDE/GUIDE_X_Bulk_Operations.md:732:Remember to consider transaction modes, error handling, and performance implications when designing your bulk operation workflows.
docs/GUIDE/GUIDE_6_Positioning.md:11:5. [Position Grouping](#position-grouping)
docs/GUIDE/GUIDE_6_Positioning.md:23:- **Fractional indexing**: Insert items between any two positions without updating other records
docs/GUIDE/GUIDE_6_Positioning.md:26:- **Automatic positioning**: Items without explicit positions are placed appropriately
docs/GUIDE/GUIDE_6_Positioning.md:31:Traditional integer-based positioning requires updating multiple records when inserting:
docs/GUIDE/GUIDE_6_Positioning.md:35:UPDATE tasks SET position = position + 1 WHERE position >= 3;
docs/GUIDE/GUIDE_6_Positioning.md:36:INSERT INTO tasks (title, position) VALUES ('New Task', 3);
docs/GUIDE/GUIDE_6_Positioning.md:43:INSERT INTO tasks (title, position) VALUES ('New Task', 'a0m');
docs/GUIDE/GUIDE_6_Positioning.md:60:import { PositioningPlugin } from './plugins/core/rest-api-positioning-plugin.js';
docs/GUIDE/GUIDE_6_Positioning.md:70:// Add positioning capabilities
docs/GUIDE/GUIDE_6_Positioning.md:88:    // 'position' field is automatically added
docs/GUIDE/GUIDE_6_Positioning.md:101:// Response includes position: "a0"
docs/GUIDE/GUIDE_6_Positioning.md:109:// Gets position: "a1"
docs/GUIDE/GUIDE_6_Positioning.md:117:// Gets position: "a0m" (between "a0" and "a1")
docs/GUIDE/GUIDE_6_Positioning.md:122:Lists are automatically sorted by position:
docs/GUIDE/GUIDE_6_Positioning.md:127:// Returns tasks in position order:
docs/GUIDE/GUIDE_6_Positioning.md:130:    { "id": 1, "attributes": { "title": "First task", "position": "a0" } },
docs/GUIDE/GUIDE_6_Positioning.md:131:    { "id": 3, "attributes": { "title": "Middle task", "position": "a0m" } },
docs/GUIDE/GUIDE_6_Positioning.md:132:    { "id": 2, "attributes": { "title": "Last task", "position": "a1" } }
docs/GUIDE/GUIDE_6_Positioning.md:143:  // Position field name (default: 'position')
docs/GUIDE/GUIDE_6_Positioning.md:146:  // Grouping fields - create separate position sequences per group
docs/GUIDE/GUIDE_6_Positioning.md:149:  // Resources to exclude from positioning
docs/GUIDE/GUIDE_6_Positioning.md:158:  // Default position for new items without beforeId (default: 'last')
docs/GUIDE/GUIDE_6_Positioning.md:164:  // Maximum position string length before rebalancing (default: 50)
docs/GUIDE/GUIDE_6_Positioning.md:171:Position grouping is one of the most powerful features. It maintains separate position sequences for different combinations of field values.
docs/GUIDE/GUIDE_6_Positioning.md:175:When you configure filters like `['status', 'projectId']`, the plugin creates independent position sequences for each unique combination:
docs/GUIDE/GUIDE_6_Positioning.md:177:- Project 1 + Status "todo" → positions: a0, a1, a2...
docs/GUIDE/GUIDE_6_Positioning.md:178:- Project 1 + Status "done" → positions: a0, a1, a2... (separate sequence!)
docs/GUIDE/GUIDE_6_Positioning.md:179:- Project 2 + Status "todo" → positions: a0, a1, a2... (another separate sequence!)
docs/GUIDE/GUIDE_6_Positioning.md:182:- The first item in each group gets position "a0"
docs/GUIDE/GUIDE_6_Positioning.md:183:- Items can have the same position value if they're in different groups
docs/GUIDE/GUIDE_6_Positioning.md:184:- Moving between groups requires explicit positioning with `beforeId`
docs/GUIDE/GUIDE_6_Positioning.md:203:Each board/column combination maintains its own positions:
docs/GUIDE/GUIDE_6_Positioning.md:236:**Important Behavior**: When you change a filter field value (like moving a card between columns), the item keeps its existing position value. The plugin does NOT automatically reassign positions when filter values change.
docs/GUIDE/GUIDE_6_Positioning.md:244:// Result: Card moves to in-progress but KEEPS its existing position (e.g., "a0m")
docs/GUIDE/GUIDE_6_Positioning.md:248:To move an item to a specific position in the new group, you MUST provide a `beforeId`:
docs/GUIDE/GUIDE_6_Positioning.md:251:// Move card and position it correctly
docs/GUIDE/GUIDE_6_Positioning.md:265:**Why this behavior?** The plugin cannot guess where you want the item positioned in the new group. Should it go first? Last? In the middle? You must explicitly specify the desired position.
docs/GUIDE/GUIDE_6_Positioning.md:274:// Each tenant has independent position sequences
docs/GUIDE/GUIDE_6_Positioning.md:275:// Tenant A's positions don't affect Tenant B's positions
docs/GUIDE/GUIDE_6_Positioning.md:291:// Add at specific position
docs/GUIDE/GUIDE_6_Positioning.md:295:// Note: Manual position values are ignored!
docs/GUIDE/GUIDE_6_Positioning.md:296:// The plugin always calculates positions to ensure consistency
docs/GUIDE/GUIDE_6_Positioning.md:298:{ "name": "New item", "position": "a0abc" }  // 'position' will be recalculated!
docs/GUIDE/GUIDE_6_Positioning.md:312:// Update other fields without changing position
docs/GUIDE/GUIDE_6_Positioning.md:320:// Item moves to 'done' group but KEEPS its position value!
docs/GUIDE/GUIDE_6_Positioning.md:326:The plugin works with both JSON:API and simplified formats:
docs/GUIDE/GUIDE_6_Positioning.md:337:// JSON:API format
docs/GUIDE/GUIDE_6_Positioning.md:355:The plugin NEVER changes an item's position unless you explicitly request it with `beforeId`. This means:
docs/GUIDE/GUIDE_6_Positioning.md:357:- Changing filter fields (status, category, etc.) does NOT reposition the item
docs/GUIDE/GUIDE_6_Positioning.md:358:- The item keeps its position value when moving between groups
docs/GUIDE/GUIDE_6_Positioning.md:359:- You must provide `beforeId` to position items in their new group
docs/GUIDE/GUIDE_6_Positioning.md:363:Each combination of filter values creates a completely separate position space:
docs/GUIDE/GUIDE_6_Positioning.md:366:// These items can all have position "a0" because they're in different groups:
docs/GUIDE/GUIDE_6_Positioning.md:367:item1: { projectId: 1, status: 'todo', position: 'a0' }
docs/GUIDE/GUIDE_6_Positioning.md:368:item2: { projectId: 1, status: 'done', position: 'a0' }  // Different status
docs/GUIDE/GUIDE_6_Positioning.md:369:item3: { projectId: 2, status: 'todo', position: 'a0' }  // Different project
docs/GUIDE/GUIDE_6_Positioning.md:374:The `beforeId` only works within the same position group:
docs/GUIDE/GUIDE_6_Positioning.md:388:The plugin always calculates positions to ensure consistency:
docs/GUIDE/GUIDE_6_Positioning.md:391:// This position value will be ignored:
docs/GUIDE/GUIDE_6_Positioning.md:395:  "position": "zzz"  // Ignored! Plugin calculates actual position
docs/GUIDE/GUIDE_6_Positioning.md:401:Null values in filter fields create their own position group:
docs/GUIDE/GUIDE_6_Positioning.md:404:// These are THREE different position groups:
docs/GUIDE/GUIDE_6_Positioning.md:461:async function addToPlaylist(songId, position = null) {
docs/GUIDE/GUIDE_6_Positioning.md:466:      beforeId: position  // null = add to end
docs/GUIDE/GUIDE_6_Positioning.md:491:The plugin uses fractional indexing to generate position keys that can be infinitely subdivided:
docs/GUIDE/GUIDE_6_Positioning.md:493:1. **Initial positions**: First item gets "a0", second gets "a1", etc.
docs/GUIDE/GUIDE_6_Positioning.md:508:3. **Determines if positioning is needed**:
docs/GUIDE/GUIDE_6_Positioning.md:509:   - For POST: Always calculates position
docs/GUIDE/GUIDE_6_Positioning.md:511:   - Changing filter fields alone does NOT trigger repositioning
docs/GUIDE/GUIDE_6_Positioning.md:512:4. **Query database** for items in the same position group (based on filter fields)
docs/GUIDE/GUIDE_6_Positioning.md:513:5. **Calculate position**:
docs/GUIDE/GUIDE_6_Positioning.md:518:6. **Store position** in the position field
docs/GUIDE/GUIDE_6_Positioning.md:519:7. **Save record** with calculated position
docs/GUIDE/GUIDE_6_Positioning.md:521:**Key Insight**: The position is calculated relative to other items in the same "position group" (items with matching filter field values). An item with `status: 'todo'` has no position relationship with items where `status: 'done'`.
docs/GUIDE/GUIDE_6_Positioning.md:528:CREATE INDEX idx_tasks_positioning ON tasks(status, projectId, position);
docs/GUIDE/GUIDE_6_Positioning.md:533:- Finding specific positions for insertion
docs/GUIDE/GUIDE_6_Positioning.md:540:If you have existing integer positions, you can migrate gradually:
docs/GUIDE/GUIDE_6_Positioning.md:543:// 1. Add the plugin (it works alongside existing positions)
docs/GUIDE/GUIDE_6_Positioning.md:548:// 2. New items will get fractional positions
docs/GUIDE/GUIDE_6_Positioning.md:549:// 3. Existing integer positions still work (treated as strings)
docs/GUIDE/GUIDE_6_Positioning.md:570:The plugin automatically adds the position field to schemas:
docs/GUIDE/GUIDE_6_Positioning.md:581:// 'position' field is automatically added to the schema
docs/GUIDE/GUIDE_6_Positioning.md:591:-- For ungrouped positioning
docs/GUIDE/GUIDE_6_Positioning.md:592:CREATE INDEX ON items(position);
docs/GUIDE/GUIDE_6_Positioning.md:594:-- For grouped positioning
docs/GUIDE/GUIDE_6_Positioning.md:595:CREATE INDEX ON items(status, projectId, position);
docs/GUIDE/GUIDE_6_Positioning.md:601:- **Inserting items**: O(log n) to find position + O(1) to insert
docs/GUIDE/GUIDE_6_Positioning.md:602:- **Moving items**: O(log n) to find positions + O(1) to update
docs/GUIDE/GUIDE_6_Positioning.md:608:- Starting positions: "a0", "a1" (2 characters)
docs/GUIDE/GUIDE_6_Positioning.md:616:2. **Avoid manual positions** unless migrating data
docs/GUIDE/GUIDE_6_Positioning.md:617:3. **Let the plugin handle positioning** for consistency
docs/GUIDE/GUIDE_6_Positioning.md:618:4. **Monitor position lengths** in high-activity systems
docs/GUIDE/GUIDE_6_Positioning.md:626:- Verify the position field contains valid fractional keys
docs/GUIDE/GUIDE_6_Positioning.md:627:- Ensure you're querying within the correct position group
docs/GUIDE/GUIDE_6_Positioning.md:628:- Remember: position values are strings, sorted lexicographically ("a10" comes before "a2"!)
docs/GUIDE/GUIDE_6_Positioning.md:631:- The position field must exist in your schema
docs/GUIDE/GUIDE_6_Positioning.md:633:- The plugin will throw an error if the field is missing
docs/GUIDE/GUIDE_6_Positioning.md:634:- Look for plugin initialization errors in logs
docs/GUIDE/GUIDE_6_Positioning.md:637:- Ensure the target item exists in the same position group
docs/GUIDE/GUIDE_6_Positioning.md:640:- Note: You cannot position relative to items in different groups
docs/GUIDE/GUIDE_6_Positioning.md:642:**Items appear in wrong position after moving between groups**
docs/GUIDE/GUIDE_6_Positioning.md:643:- This is expected behavior! Items keep their position when filter values change
docs/GUIDE/GUIDE_6_Positioning.md:649:- Monitor position string lengths
docs/GUIDE/GUIDE_6_Positioning.md:654:Enable debug logging to see position calculations:
docs/GUIDE/GUIDE_6_Positioning.md:665:For advanced use cases, you can work directly with positions:
docs/GUIDE/GUIDE_6_Positioning.md:670:// Generate a position between two items
docs/GUIDE/GUIDE_6_Positioning.md:673:// Generate first position
docs/GUIDE/GUIDE_6_Positioning.md:676:// Generate last position after 'z5'
docs/GUIDE/GUIDE_6_Positioning.md:682:The Positioning Plugin provides a production-ready solution for maintaining custom order in your REST API resources. With fractional indexing and position grouping, it handles complex ordering requirements while maintaining excellent performance and avoiding conflicts.
docs/GUIDE/GUIDE_6_Positioning.md:688:- **Grouped positioning** - Separate sequences per category
docs/GUIDE/GUIDE_6_Positioning.md:692:The plugin integrates seamlessly with the REST API plugin ecosystem, requiring minimal configuration while providing powerful positioning capabilities for modern applications.
docs/GUIDE/GUIDE_X_SocketIO.md:31:The Socket.IO plugin is included in json-rest-api core plugins. To use it, you need to:
docs/GUIDE/GUIDE_X_SocketIO.md:35:npm install socket.io @socket.io/redis-adapter redis
docs/GUIDE/GUIDE_X_SocketIO.md:44:import { SocketIOPlugin } from 'json-rest-api/plugins/socketio';
docs/GUIDE/GUIDE_X_SocketIO.md:59:const server = app.listen(3000).on('error', (err) => {
docs/GUIDE/GUIDE_X_SocketIO.md:60:  console.error('Failed to start server:', err);
docs/GUIDE/GUIDE_X_SocketIO.md:66:  path: '/socket.io',           // Socket.IO path (default: '/socket.io')
docs/GUIDE/GUIDE_X_SocketIO.md:67:  cors: {                       // CORS configuration
docs/GUIDE/GUIDE_X_SocketIO.md:84:| `path` | string | '/socket.io' | URL path for Socket.IO endpoint |
docs/GUIDE/GUIDE_X_SocketIO.md:85:| `cors` | object | `{ origin: '*', methods: ['GET', 'POST'] }` | CORS configuration |
docs/GUIDE/GUIDE_X_SocketIO.md:117:socket.emit('subscription.update', {
docs/GUIDE/GUIDE_X_SocketIO.md:166:socket.emit('subscribe', {
docs/GUIDE/GUIDE_X_SocketIO.md:180:// In a transaction
docs/GUIDE/GUIDE_X_SocketIO.md:181:const trx = await knex.transaction();
docs/GUIDE/GUIDE_X_SocketIO.md:186:    transaction: trx
docs/GUIDE/GUIDE_X_SocketIO.md:193:} catch (error) {
docs/GUIDE/GUIDE_X_SocketIO.md:194:  await trx.rollback();
docs/GUIDE/GUIDE_X_SocketIO.md:225:socket.emit('subscribe', {
docs/GUIDE/GUIDE_X_SocketIO.md:263:      return title.includes(search) || 
docs/GUIDE/GUIDE_X_SocketIO.md:264:             desc.includes(search) || 
docs/GUIDE/GUIDE_X_SocketIO.md:265:             tags.includes(search);
docs/GUIDE/GUIDE_X_SocketIO.md:302:      const { user_id, include_private } = value;
docs/GUIDE/GUIDE_X_SocketIO.md:304:      if (!include_private) {
docs/GUIDE/GUIDE_X_SocketIO.md:311:      const { user_id, include_private } = value;
docs/GUIDE/GUIDE_X_SocketIO.md:314:      return include_private && record.shared_with?.includes(user_id);
docs/GUIDE/GUIDE_X_SocketIO.md:326:socket.emit('subscribe', {
docs/GUIDE/GUIDE_X_SocketIO.md:334:// Returns error:
docs/GUIDE/GUIDE_X_SocketIO.md:336:  error: {
docs/GUIDE/GUIDE_X_SocketIO.md:357:const socket = io('http://localhost:3000', {
docs/GUIDE/GUIDE_X_SocketIO.md:364:socket.on('connect_error', (error) => {
docs/GUIDE/GUIDE_X_SocketIO.md:365:  console.error('Authentication failed:', error.message);
docs/GUIDE/GUIDE_X_SocketIO.md:381:      return auth.roles?.includes('admin');
docs/GUIDE/GUIDE_X_SocketIO.md:419:// Now ALL subscriptions automatically include workspace filter
docs/GUIDE/GUIDE_X_SocketIO.md:420:socket.emit('subscribe', {
docs/GUIDE/GUIDE_X_SocketIO.md:443:        if (!auth.roles?.includes('admin')) {
docs/GUIDE/GUIDE_X_SocketIO.md:495:Each socket can have multiple subscriptions with different filters:
docs/GUIDE/GUIDE_X_SocketIO.md:499:const sub1 = await subscribeToResource(socket, {
docs/GUIDE/GUIDE_X_SocketIO.md:504:const sub2 = await subscribeToResource(socket, {
docs/GUIDE/GUIDE_X_SocketIO.md:509:const sub3 = await subscribeToResource(socket, {
docs/GUIDE/GUIDE_X_SocketIO.md:515:socket.emit('unsubscribe', { 
docs/GUIDE/GUIDE_X_SocketIO.md:522:While notifications don't include data, subscriptions can store include/fields preferences:
docs/GUIDE/GUIDE_X_SocketIO.md:525:// Subscribe with preferred includes and fields
docs/GUIDE/GUIDE_X_SocketIO.md:526:socket.emit('subscribe', {
docs/GUIDE/GUIDE_X_SocketIO.md:529:  include: ['author', 'comments.user'],
docs/GUIDE/GUIDE_X_SocketIO.md:538:socket.on('subscription.update', async (notification) => {
docs/GUIDE/GUIDE_X_SocketIO.md:541:    'include=author,comments.user&' +
docs/GUIDE/GUIDE_X_SocketIO.md:557:socket.on('subscription.created', (response) => {
docs/GUIDE/GUIDE_X_SocketIO.md:562:socket.on('connect', async () => {
docs/GUIDE/GUIDE_X_SocketIO.md:565:      socket, 
docs/GUIDE/GUIDE_X_SocketIO.md:571:      console.error(`Failed to restore ${failed.length} subscriptions`);
docs/GUIDE/GUIDE_X_SocketIO.md:576:async function restoreSubscriptions(socket, subscriptions) {
docs/GUIDE/GUIDE_X_SocketIO.md:578:    socket.emit('restore-subscriptions', 
docs/GUIDE/GUIDE_X_SocketIO.md:588:The plugin provides detailed error information:
docs/GUIDE/GUIDE_X_SocketIO.md:591:socket.on('subscription.error', (error) => {
docs/GUIDE/GUIDE_X_SocketIO.md:592:  switch (error.code) {
docs/GUIDE/GUIDE_X_SocketIO.md:594:      console.error(`Resource type '${error.resource}' doesn't exist`);
docs/GUIDE/GUIDE_X_SocketIO.md:598:      console.error('You lack permission to subscribe to this resource');
docs/GUIDE/GUIDE_X_SocketIO.md:602:      console.error('Filter validation failed:', error.details);
docs/GUIDE/GUIDE_X_SocketIO.md:606:      console.error(`Filter requires 'filterRecord' for WebSocket support`);
docs/GUIDE/GUIDE_X_SocketIO.md:610:      console.error('Resource does not have searchSchema defined');
docs/GUIDE/GUIDE_X_SocketIO.md:621:import { io } from 'socket.io-client';
docs/GUIDE/GUIDE_X_SocketIO.md:624:const socket = io('http://localhost:3000', {
docs/GUIDE/GUIDE_X_SocketIO.md:631:socket.on('connect', () => {
docs/GUIDE/GUIDE_X_SocketIO.md:635:socket.on('connect_error', (error) => {
docs/GUIDE/GUIDE_X_SocketIO.md:636:  console.error('Connection failed:', error.message);
docs/GUIDE/GUIDE_X_SocketIO.md:639:socket.on('disconnect', (reason) => {
docs/GUIDE/GUIDE_X_SocketIO.md:648:async function subscribeToResource(socket, options) {
docs/GUIDE/GUIDE_X_SocketIO.md:650:    socket.emit('subscribe', options, (response) => {
docs/GUIDE/GUIDE_X_SocketIO.md:651:      if (response.error) {
docs/GUIDE/GUIDE_X_SocketIO.md:652:        reject(response.error);
docs/GUIDE/GUIDE_X_SocketIO.md:662:  const subscription = await subscribeToResource(socket, {
docs/GUIDE/GUIDE_X_SocketIO.md:671:} catch (error) {
docs/GUIDE/GUIDE_X_SocketIO.md:672:  console.error('Subscription failed:', error);
docs/GUIDE/GUIDE_X_SocketIO.md:680:socket.on('subscription.update', async (notification) => {
docs/GUIDE/GUIDE_X_SocketIO.md:739:    this.socket = io(apiUrl, {
docs/GUIDE/GUIDE_X_SocketIO.md:747:    this.socket.on('connect', () => {
docs/GUIDE/GUIDE_X_SocketIO.md:752:    this.socket.on('subscription.update', (notification) => {
docs/GUIDE/GUIDE_X_SocketIO.md:756:    this.socket.on('subscription.created', (response) => {
docs/GUIDE/GUIDE_X_SocketIO.md:763:      this.socket.emit('subscribe', {
docs/GUIDE/GUIDE_X_SocketIO.md:768:        if (response.error) {
docs/GUIDE/GUIDE_X_SocketIO.md:769:          reject(response.error);
docs/GUIDE/GUIDE_X_SocketIO.md:829:      this.socket.emit('restore-subscriptions', 
docs/GUIDE/GUIDE_X_SocketIO.md:832:          if (response.error) {
docs/GUIDE/GUIDE_X_SocketIO.md:833:            console.error('Failed to restore subscriptions:', response.error);
docs/GUIDE/GUIDE_X_SocketIO.md:860:      include: 'author,comments'
docs/GUIDE/GUIDE_X_SocketIO.md:871:Each socket is limited to 100 subscriptions to prevent memory exhaustion:
docs/GUIDE/GUIDE_X_SocketIO.md:875:socket.emit('subscribe', { resource: 'posts' }, (response) => {
docs/GUIDE/GUIDE_X_SocketIO.md:876:  if (response.error?.code === 'SUBSCRIPTION_LIMIT_EXCEEDED') {
docs/GUIDE/GUIDE_X_SocketIO.md:877:    console.error('Too many active subscriptions');
docs/GUIDE/GUIDE_X_SocketIO.md:905:socket.on('subscription.update', (notification) => {
docs/GUIDE/GUIDE_X_SocketIO.md:938:- Are you in a transaction that hasn't committed?
docs/GUIDE/GUIDE_X_SocketIO.md:940:**2. "UNSUPPORTED_FILTER" errors**
docs/GUIDE/GUIDE_X_SocketIO.md:945:// ❌ This will error for WebSocket
docs/GUIDE/GUIDE_X_SocketIO.md:989:localStorage.debug = 'socket.io-client:*';
docs/GUIDE/GUIDE_X_SocketIO.md:997:  const socket = io('http://localhost:3000', {
docs/GUIDE/GUIDE_X_SocketIO.md:1002:    socket.on('connect', async () => {
docs/GUIDE/GUIDE_X_SocketIO.md:1006:      socket.emit('subscribe', {
docs/GUIDE/GUIDE_X_SocketIO.md:1010:        if (response.error) {
docs/GUIDE/GUIDE_X_SocketIO.md:1011:          console.error('✗ Subscription failed:', response.error);
docs/GUIDE/GUIDE_X_SocketIO.md:1012:          reject(response.error);
docs/GUIDE/GUIDE_X_SocketIO.md:1017:          socket.once('subscription.update', (notification) => {
docs/GUIDE/GUIDE_X_SocketIO.md:1019:            socket.close();
docs/GUIDE/GUIDE_X_SocketIO.md:1029:    socket.on('connect_error', (error) => {
docs/GUIDE/GUIDE_X_SocketIO.md:1030:      console.error('✗ Connection failed:', error.message);
docs/GUIDE/GUIDE_X_SocketIO.md:1031:      reject(error);
docs/GUIDE/GUIDE_3_Field_Transformations.md:349:  // - context: request context with knex, transaction, etc.
docs/GUIDE/GUIDE_3_Field_Transformations.md:361:4. **Sparse Fieldsets**: Work seamlessly with JSON:API sparse fieldsets
docs/GUIDE/GUIDE_3_Field_Transformations.md:533:      // Note: cost, supplierApiKey not included
docs/GUIDE/GUIDE_3_Field_Transformations.md:594:Computed fields are virtual fields that don't exist in your database but are calculated on-the-fly from other fields. They're computed after the database load, every time they're requested, and can depend on other fields (including hidden ones). Computed fields work seamlessly with sparse fieldsets and are calculated for both main resources and included resources.
docs/GUIDE/GUIDE_3_Field_Transformations.md:682:// Note: 'cost' is not included (normallyHidden)
docs/GUIDE/GUIDE_3_Field_Transformations.md:710:When you request a computed field via sparse fieldsets, the system automatically fetches its dependencies:
docs/GUIDE/GUIDE_3_Field_Transformations.md:714:const sparseProduct = await api.resources.products.get({
docs/GUIDE/GUIDE_3_Field_Transformations.md:720:console.log('Product with sparse fields:', sparseProduct);
docs/GUIDE/GUIDE_3_Field_Transformations.md:728:console.log('Product with sparse fields includig cost:', productWithCost);
docs/GUIDE/GUIDE_3_Field_Transformations.md:734:Product with sparse fields: {
docs/GUIDE/GUIDE_3_Field_Transformations.md:741:Product with sparse fields includig cost: {
docs/GUIDE/GUIDE_3_Field_Transformations.md:752:Computed fields work seamlessly with included resources:
docs/GUIDE/GUIDE_3_Field_Transformations.md:759:    include: ['reviews']
docs/GUIDE/GUIDE_3_Field_Transformations.md:764:// Use sparse fieldsets on included resources
docs/GUIDE/GUIDE_3_Field_Transformations.md:768:    include: ['reviews'],
docs/GUIDE/GUIDE_3_Field_Transformations.md:833:Computed fields handle errors gracefully:
docs/GUIDE/GUIDE_3_Field_Transformations.md:848:console.log('Fetched review (with error in helpfulness score):', fetchedReviewWithError
docs/GUIDE/GUIDE_3_Field_Transformations.md:854:Fetched review (with error in helpfulness score): {
docs/GUIDE/GUIDE_3_Field_Transformations.md:872:3. **Works with Sparse Fieldsets** - You can request computed fields just like regular fields using sparse fieldsets.
docs/GUIDE/GUIDE_3_Field_Transformations.md:876:5. **Included Resources** - Computed fields are calculated for all resources, whether they're the main resource or included via relationships.
docs/GUIDE/GUIDE_3_Field_Transformations.md:878:6. **Error Handling** - If a computation fails, the field is set to `null` and an error is logged, but the request continues.
docs/GUIDE/GUIDE_3_Field_Transformations.md:998:  queryParams: { include: ['posts'] }
docs/GUIDE/GUIDE_3_Field_Transformations.md:1069:  queryParams: { include: ['posts'] }
docs/GUIDE/GUIDE_3_Field_Transformations.md:1099:    include: ['author'],
docs/GUIDE/GUIDE_3_Field_Transformations.md:1105:// Both posts and included authors have getters applied
docs/mini-guides/file-uploads-complete.md:110:}).on('error', (err) => {
docs/mini-guides/file-uploads-complete.md:111:  console.error('Failed to start server:', err);
docs/mini-guides/file-uploads-complete.md:132:The example includes a simple HTML form for testing file uploads without needing external tools.
docs/GUIDE/GUIDE_X_File_Uploads.md:106:Storage adapters handle where and how files are saved. The library includes two built-in adapters.
docs/GUIDE/GUIDE_X_File_Uploads.md:140:**Note**: The included S3Storage is a mock implementation for demonstration. For production use, you'll need to implement the actual AWS SDK calls.
docs/GUIDE/GUIDE_X_File_Uploads.md:321:      return req.headers['content-type']?.includes('multipart/form-data');
docs/GUIDE/GUIDE_X_File_Uploads.md:393:When validation fails, you'll get appropriate error responses:
docs/GUIDE/GUIDE_X_File_Uploads.md:397:  "errors": [{
docs/GUIDE/GUIDE_X_File_Uploads.md:460:app.listen(3000).on('error', (err) => {
docs/GUIDE/GUIDE_X_File_Uploads.md:461:  console.error('Failed to start server:', err);
docs/GUIDE/GUIDE_X_File_Uploads.md:613:#### 4. "File too large" errors
docs/GUIDE/GUIDE_X_File_Uploads.md:650:Use the included example to test your setup:
docs/GUIDE/GUIDE_X_File_Uploads.md:716:7. **Serve files with proper headers** - Use Content-Disposition for downloads
docs/GUIDE/GUIDE_X_URL_Management.md:5:The JSON REST API automatically generates appropriate URLs for all resources in JSON:API responses. URLs are calculated **per-request** based on the incoming request headers, ensuring correct URLs in all deployment scenarios without configuration.
docs/GUIDE/GUIDE_X_URL_Management.md:93:        if (host?.includes('tenant-a')) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:9:3. [JSON:API Response Format](#jsonapi-response-format)
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:18:Storage plugins are the bridge between the JSON REST API library and your data source. The REST API plugin handles all the HTTP routing, validation, permissions, and JSON:API formatting, while your storage plugin is responsible for actually storing and retrieving data.
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:35:4. Your helper returns data in JSON:API format
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:51:  // - context.schemaInfo.idProperty: string - Primary key field name
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:52:  // - context.db: any - Database connection (if using transactions)
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:59:Retrieve a single resource with full JSON:API features.
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:66:  // - context.queryParams.include: string[] - Related resources to include
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:70:  // Returns: JSON:API document with single resource
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:73:  //   included: [...] // if includes requested
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:85:  // Returns: JSON:API resource object or null
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:104:  // - context.queryParams.include: string[] - Related resources
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:107:  // Returns: JSON:API document with resource array
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:110:  //   included: [...],
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:123:  // - context.inputRecord: JSON:API document with new resource
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:137:  // - context.inputRecord: JSON:API document with full resource
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:140:  // Returns: void (throws error if not found when updating)
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:151:  // - context.inputRecord: JSON:API document with partial updates
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:153:  // Returns: void (throws error if not found)
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:165:  // Returns: { success: true } (throws error if not found)
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:169:## JSON:API Response Format
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:171:Your storage plugin must return data in proper JSON:API format. Here are the key structures:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:227:  included: [
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:284:        const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:292:      // Build JSON:API response
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:322:      // Handle includes
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:323:      const included = [];
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:324:      if (context.queryParams.include?.length > 0) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:325:        for (const includePath of context.queryParams.include) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:326:          const relName = includePath.split('.')[0];
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:338:                included.push({
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:349:      // Apply sparse fieldsets
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:353:          Object.entries(data.attributes).filter(([key]) => fields.includes(key))
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:359:        ...(included.length > 0 && { included })
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:377:      // Only include belongsTo relationships for minimal
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:405:            if (field.includes('.')) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:475:      // Handle includes
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:476:      const included = [];
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:477:      if (context.queryParams.include?.length > 0) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:478:        const includedIds = new Set();
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:481:          for (const includePath of context.queryParams.include) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:482:            const relName = includePath.split('.')[0];
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:492:                if (!includedIds.has(key)) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:493:                  includedIds.add(key);
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:497:                    included.push({
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:529:        ...(included.length > 0 && { included }),
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:576:        const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:615:        const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:654:        const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:702:        const error = await response.json().catch(() => ({ errors: [{ title: 'Request failed' }] }));
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:703:        const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:705:          error.errors?.[0]?.title || 'Remote API error',
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:707:            subtype: response.status === 404 ? 'not_found' : 'remote_error',
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:709:            errors: error.errors
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:722:      } catch (error) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:723:        if (error.statusCode === 404) return false;
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:724:        throw error;
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:732:      // Add include parameter
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:733:      if (context.queryParams.include?.length > 0) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:734:        queryParams.set('include', context.queryParams.include.join(','));
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:737:      // Add sparse fieldsets
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:779:      // Add includes
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:780:      if (context.queryParams.include?.length > 0) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:781:        queryParams.set('include', context.queryParams.include.join(','));
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:784:      // Add sparse fieldsets
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:799:        included: response.included,
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:849:If your storage supports transactions, the `context.db` parameter will automatically contain the transaction when one is active:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:853:  // context.db is automatically the transaction if one is active,
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:854:  // or the base connection if not in a transaction
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:857:  // For storage that supports transactions:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:858:  if (context.transaction) {
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:859:    // We're in a transaction - ensure all operations use it
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:862:    // No transaction - use regular connection
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:881:  // Filter attributes to only include requested fields
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:883:    Object.entries(data.attributes).filter(([key]) => fields.includes(key))
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:887:  // The REST API plugin will handle nested field filtering for included resources
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:893:The REST API plugin handles computed fields, but you may need to ensure dependency fields are included:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:897:// Always include these fields even if not explicitly requested
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:911:Always use the proper error classes:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:914:import { RestApiResourceError, RestApiValidationError } from '../../lib/rest-api-errors.js';
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:916:// For not found errors
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:923:// For validation errors
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1000:JSON:API requires IDs to be strings, but your storage might use numbers:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1012:Relationships must follow the JSON:API format exactly:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1042:Errors should include proper subtypes:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1047:'validation_error' - Input validation failed
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1054:Always include pagination metadata for queries:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1067:When returning included resources, avoid duplicates:
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1070:const includedMap = new Map();
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1072:includedMap.set(`${type}:${id}`, resource);
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1073:const included = Array.from(includedMap.values());
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1080:- Use bulk operations where possible
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1090:- idProperty: Primary key field name (might not be 'id')
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1098:Creating a custom storage plugin gives you complete control over how data is stored and retrieved while leveraging all the features of the JSON REST API library. The key is to properly implement the 8 required helpers and ensure all responses follow the JSON:API specification.
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1102:- Always return proper JSON:API formatted responses
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1103:- Use the provided error classes for consistency
docs/GUIDE/GUIDE_X_Multihome.md:22:- **Automatic Query Filtering**: Every database query automatically includes a WHERE clause for the tenant ID
docs/GUIDE/GUIDE_X_Multihome.md:44:Every database query is automatically modified to include the tenant filter:
docs/GUIDE/GUIDE_X_Multihome.md:51:SELECT * FROM posts WHERE status = 'published' AND multihome_id = 'mobily'
docs/GUIDE/GUIDE_X_Multihome.md:64:{ "title": "My Post", "content": "...", "multihome_id": "mobily" }
docs/GUIDE/GUIDE_X_Multihome.md:76:import { MultiHomePlugin } from './plugins/core/multihome-plugin.js';
docs/GUIDE/GUIDE_X_Multihome.md:124:| `field` | string | `'multihome_id'` | The database field name for tenant ID |
docs/GUIDE/GUIDE_X_Multihome.md:176:  if (subdomain && !['www', 'api'].includes(subdomain)) {
docs/GUIDE/GUIDE_X_Multihome.md:296:// Result: 404 Not Found (not a security error message)
docs/GUIDE/GUIDE_X_Multihome.md:320:log.error('Multihome security violation attempt', {
docs/GUIDE/GUIDE_X_Multihome.md:335:// Error: No multihome context available - cannot execute query
docs/GUIDE/GUIDE_X_Multihome.md:385:const config = api.multihome.getConfig();
docs/GUIDE/GUIDE_X_Multihome.md:398:The plugin sets these variables accessible via `api.vars.multihome`:
docs/GUIDE/GUIDE_X_Multihome.md:425:#### 1. "No multihome context available"
docs/GUIDE/GUIDE_X_Multihome.md:447:**Solution**: Don't include tenant_id in your requests - it's set automatically.
docs/GUIDE/GUIDE_X_Multihome.md:457:  console.log('Current tenant:', context.auth?.multihome_id);
docs/GUIDE/GUIDE_X_Multihome.md:481:Always include the tenant field in your schemas:
docs/GUIDE/GUIDE_X_Multihome.md:489:    tenant_id: { type: 'string', required: true } // Always include
docs/GUIDE/GUIDE_X_Multihome.md:576:import { MultiHomePlugin } from './plugins/core/multihome-plugin.js';
docs/GUIDE/GUIDE_X_Multihome.md:617:    if (subdomain && !['www', 'api', 'app'].includes(subdomain)) {
docs/GUIDE/GUIDE_X_Multihome.md:653:}).on('error', (err) => {
docs/GUIDE/GUIDE_X_Multihome.md:654:  console.error('Failed to start server:', err);
