json-rest-api 1.0.21 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/audit/00-charter.md +103 -0
- package/audit/01-scope-map.md +84 -0
- package/audit/02-coverage-matrix.csv +174 -0
- package/audit/03-invariants.md +123 -0
- package/audit/04-standards-rubric.md +56 -0
- package/audit/05-competitor-rubric.md +77 -0
- package/audit/06-findings.md +160 -0
- package/audit/07-risk-register.csv +10 -0
- package/audit/08-remediation-roadmap.md +177 -0
- package/audit/09-signoff-check.md +23 -0
- package/audit/10-final-synthesis.md +29 -0
- package/audit/evidence/code/all-files-json-rest-api.txt +168 -0
- package/audit/evidence/code/all-files-json-rest-schema.txt +12 -0
- package/audit/evidence/code/boundary-leak-checks.txt +72 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +480 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +532 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +175 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +138 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +222 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +342 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +304 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +188 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +336 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +165 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +265 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +369 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +72 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +139 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +45 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +396 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +162 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +1314 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +488 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +484 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +857 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +565 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +485 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +560 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +167 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +109 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +1064 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +1983 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +328 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +119 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +160 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +300 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +245 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +459 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +237 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +1135 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +314 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +7 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +71 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +102 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +49 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +39 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +23 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +13 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +639 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +117 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +104 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +199 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +353 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +61 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +184 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +55 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +191 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +110 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +168 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +253 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +207 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +12 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +165 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +668 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +621 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +218 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +124 -0
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +269 -0
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +242 -0
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +77 -0
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +238 -0
- package/audit/evidence/code/critical-pass1-summary.csv +67 -0
- package/audit/evidence/code/critical-pass2-summary.csv +67 -0
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +57 -0
- package/audit/evidence/code/error-model-signals.txt +98 -0
- package/audit/evidence/code/git-files-json-rest-api.txt +171 -0
- package/audit/evidence/code/git-files-json-rest-schema.txt +27 -0
- package/audit/evidence/code/hook-flow-signals.txt +282 -0
- package/audit/evidence/code/idproperty-signals.txt +157 -0
- package/audit/evidence/code/import-cycles-non-anyapi.txt +3 -0
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +96 -0
- package/audit/evidence/code/imports-json-rest-api.txt +354 -0
- package/audit/evidence/code/imports-json-rest-schema.txt +15 -0
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +160 -0
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +13 -0
- package/audit/evidence/code/non-anyapi-core-files.txt +101 -0
- package/audit/evidence/code/permission-signals.txt +193 -0
- package/audit/evidence/code/query-include-signals.txt +687 -0
- package/audit/evidence/code/transaction-signals.txt +28 -0
- package/audit/evidence/code/validation-signals.txt +650 -0
- package/audit/evidence/code/write-path-signals.txt +61 -0
- package/audit/evidence/docs/docs-claims-signals.txt +1075 -0
- package/audit/evidence/docs/schema-docs-claims-signals.txt +179 -0
- package/audit/evidence/external/corpus-freeze.md +21 -0
- package/audit/evidence/external/npm-competitor-metadata.txt +70 -0
- package/audit/evidence/external/npm-download-signals.txt +23 -0
- package/audit/evidence/external/npm-jsonapi-server.txt +8 -0
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +38 -0
- package/audit/evidence/external/readme-fortune-signals.txt +26 -0
- package/audit/evidence/external/readme-fortune.md +165 -0
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +80 -0
- package/audit/evidence/external/readme-json-api-serializer.md +690 -0
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +22 -0
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +63 -0
- package/audit/evidence/external/readme-jsonapi-serializer.md +315 -0
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +19 -0
- package/audit/evidence/external/readme-jsonapi-server.md +104 -0
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +4 -0
- package/audit/evidence/external/readme-nestjs-json-api.md +94 -0
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +47 -0
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +447 -0
- package/audit/evidence/external/standard-jsonapi-snippets.txt +84 -0
- package/audit/evidence/external/standard-rfc-snippets.txt +168 -0
- package/audit/evidence/external/standards-sources.txt +6 -0
- package/audit/evidence/runtime/baseline-commits.txt +2 -0
- package/audit/evidence/runtime/db-adapter-assumptions.txt +3 -0
- package/audit/evidence/runtime/environment.txt +4 -0
- package/audit/evidence/runtime/invariant-runtime-mapping.md +38 -0
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +1 -0
- package/audit/evidence/runtime/probe-patch-relationship-error.log +76 -0
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +67 -0
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +1 -0
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +62 -0
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +47 -0
- package/audit/evidence/runtime/test-exitcodes.txt +4 -0
- package/audit/evidence/runtime/test-summary.md +107 -0
- package/audit/evidence/runtime/trace-anchors.txt +61 -0
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +125 -0
- package/audit/evidence/tests/failing-tests-summary.txt +25 -0
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +1 -0
- package/audit/evidence/tests/npm-test-json-rest-api.log +5885 -0
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +1 -0
- package/audit/evidence/tests/npm-test-json-rest-schema.log +356 -0
- package/audit/evidence/tests/query-include-test-signals.txt +948 -0
- package/audit/evidence/tests/relationship-test-signals.txt +223 -0
- package/audit/evidence/tests/skipped-tests.txt +10 -0
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +30 -0
- package/audit/evidence/tests/test-suite-list.txt +32 -0
- package/audit/subsystems/docs-drift.md +49 -0
- package/audit/subsystems/pass2-cross-review.md +131 -0
- package/audit/subsystems/plugins-access.md +51 -0
- package/audit/subsystems/plugins-bulk.md +50 -0
- package/audit/subsystems/plugins-cors.md +48 -0
- package/audit/subsystems/plugins-file.md +53 -0
- package/audit/subsystems/plugins-multihome.md +50 -0
- package/audit/subsystems/plugins-positioning.md +54 -0
- package/audit/subsystems/plugins-socketio.md +50 -0
- package/audit/subsystems/query-engine.md +61 -0
- package/audit/subsystems/relationships.md +55 -0
- package/audit/subsystems/rest-orchestration.md +66 -0
- package/audit/subsystems/schema-core.md +64 -0
- package/audit/subsystems/storage-knex.md +57 -0
- package/audit/subsystems/tests.md +48 -0
- package/audit/subsystems/transport-express.md +55 -0
- package/audit/traces/trace-read-get.md +22 -0
- package/audit/traces/trace-read-query.md +25 -0
- package/audit/traces/trace-relationship-endpoints.md +31 -0
- package/audit/traces/trace-write-delete.md +17 -0
- package/audit/traces/trace-write-patch.md +26 -0
- package/audit/traces/trace-write-post.md +22 -0
- package/audit/traces/trace-write-put.md +23 -0
- package/package.json +3 -3
|
@@ -0,0 +1,1075 @@
|
|
|
1
|
+
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.
|
|
2
|
+
docs/index.md:30: <p>Extend your API with powerful plugins. Authentication, validation, CORS, and more - just plug and play.</p>
|
|
3
|
+
docs/index.md:49: <h3>🎯 JSON:API Compliant</h3>
|
|
4
|
+
docs/index.md:50: <p>Follow industry standards with native JSON:API support. Compatible with any JSON:API client library.</p>
|
|
5
|
+
docs/index.md:110:}).on('error', (err) => {
|
|
6
|
+
docs/index.md:111: console.error('Failed to start server:', err);
|
|
7
|
+
docs/COMPARISON.md:1:# JSON:API Library Comparison
|
|
8
|
+
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.
|
|
9
|
+
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.
|
|
10
|
+
docs/COMPARISON.md:14:| Full JSON:API 1.1 compliance | ✅ | ⚠️ (serialization only) | ✅ | ✅ | ⚠️ |
|
|
11
|
+
docs/COMPARISON.md:27:| Deep includes | ✅ | ⚠️ | ✅ | ⚠️ | ✅ |
|
|
12
|
+
docs/COMPARISON.md:44:**json-api-serializer** is a serialization-only library that focuses on converting JavaScript objects to/from JSON:API format.
|
|
13
|
+
docs/COMPARISON.md:48:- **You still need**: Routing, database layer, relationship handling, query parsing, error handling
|
|
14
|
+
docs/COMPARISON.md:56: author: { ref: 'id', included: true }
|
|
15
|
+
docs/COMPARISON.md:90:**nestjs-json-api** provides JSON:API support specifically for NestJS applications using decorators.
|
|
16
|
+
docs/COMPARISON.md:94:- **Architecture**: Class-based with decorators vs plugin-based composition
|
|
17
|
+
docs/COMPARISON.md:132:**@jsonapi/server** is a lightweight JSON:API framework that provides basic compliance.
|
|
18
|
+
docs/COMPARISON.md:135:- **Features**: json-rest-api offers many more features (file uploads, WebSockets, bulk operations)
|
|
19
|
+
docs/COMPARISON.md:142:**fortune.js** is a hypermedia API framework with optional JSON:API support.
|
|
20
|
+
docs/COMPARISON.md:146:- **Focus**: Generic hypermedia vs dedicated JSON:API implementation
|
|
21
|
+
docs/COMPARISON.md:157:// JSON:API mode (for HTTP clients)
|
|
22
|
+
docs/COMPARISON.md:183: simplifiedTransport: false // HTTP endpoints use JSON:API format
|
|
23
|
+
docs/COMPARISON.md:189:Unmatched flexibility through composition:
|
|
24
|
+
docs/COMPARISON.md:274: queryParams: { include: ['commentable'] } // Works with any type
|
|
25
|
+
docs/COMPARISON.md:319: include: [
|
|
26
|
+
docs/COMPARISON.md:332:// 4. Returns properly structured JSON:API response
|
|
27
|
+
docs/COMPARISON.md:341:- ✅ Full JSON:API compliance with minimal setup
|
|
28
|
+
docs/COMPARISON.md:344:- ✅ Advanced features like file uploads, WebSockets, bulk operations
|
|
29
|
+
docs/COMPARISON.md:345:- ✅ Both JSON:API and simplified object formats
|
|
30
|
+
docs/COMPARISON.md:352:- ❌ You need a different hypermedia format (not JSON:API)
|
|
31
|
+
docs/COMPARISON.md:425:json-rest-api stands out as the most comprehensive JSON:API solution for Node.js:
|
|
32
|
+
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.
|
|
33
|
+
docs/ONBOARDING.md:65:4. **error** - Global error handling hook
|
|
34
|
+
docs/ONBOARDING.md:66: - Allows plugins to intercept and handle errors
|
|
35
|
+
docs/ONBOARDING.md:147:- Transforms between formats (JSON:API ↔ simplified)
|
|
36
|
+
docs/ONBOARDING.md:154:- Transforms between database format and JSON:API
|
|
37
|
+
docs/ONBOARDING.md:270: } catch (error) {
|
|
38
|
+
docs/ONBOARDING.md:271: // Handle errors
|
|
39
|
+
docs/ONBOARDING.md:272: handleError(error, req, res);
|
|
40
|
+
docs/ONBOARDING.md:306:1. **Plugin composition** - Each plugin does one thing well
|
|
41
|
+
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.
|
|
42
|
+
README.md:9:* **JSON:API Compliant** - Full support for the JSON:API specification
|
|
43
|
+
README.md:11:* **Advanced Querying** - Filtering, sorting, pagination, and field selection (sparse fieldsets)
|
|
44
|
+
README.md:14:* **Validation** - Schema-based validation with detailed error messages and custom rules
|
|
45
|
+
README.md:15:* **Simplified Mode** - A developer-friendly option to work with plain JavaScript objects instead of verbose JSON:API structure
|
|
46
|
+
README.md:60:}).on('error', (err) => {
|
|
47
|
+
README.md:61: console.error('Failed to start server:', err);
|
|
48
|
+
docs/QUICKSTART.md:63:}).on('error', (err) => {
|
|
49
|
+
docs/QUICKSTART.md:64: console.error('Failed to start server:', err);
|
|
50
|
+
docs/QUICKSTART.md:147:// Method 3: Full JSON:API mode (standards compliant)
|
|
51
|
+
docs/QUICKSTART.md:159:console.log('JSON:API response:', inspect(oxfordResult));
|
|
52
|
+
docs/QUICKSTART.md:173:// Get publisher with included authors
|
|
53
|
+
docs/QUICKSTART.md:176: include: ['authors']
|
|
54
|
+
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.
|
|
55
|
+
docs/QUICKSTART.md:204:JSON:API response: {
|
|
56
|
+
docs/QUICKSTART.md:248:# Get a specific publisher with included authors
|
|
57
|
+
docs/QUICKSTART.md:249:curl "http://localhost:3000/api/publishers/1?include=authors"
|
|
58
|
+
docs/QUICKSTART.md:257:# Get authors with sparse fields (only name and surname)
|
|
59
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:83: 4. `warn` - Only warnings and errors
|
|
60
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:84: 5. `error` - Only error messages
|
|
61
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:92: logLevel: 'warn' // Only show warnings and errors
|
|
62
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:98:- logLevel: `warn` - Only see warnings and errors
|
|
63
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:99:- logLevel: `error` - Only see errors
|
|
64
|
+
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.
|
|
65
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:233:// Expected Output (a full JSON:API record):
|
|
66
|
+
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.)
|
|
67
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:249:- Input requires the full JSON:API document structure with `data`, `type`, and `attributes`
|
|
68
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:250:- Output returns the full JSON:API response with the same nested structure (and links)
|
|
69
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:260: simplifiedApi: false, // All API calls will use JSON:API format by default
|
|
70
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:272: simplifiedApi: false, // API calls to this resource use JSON:API format
|
|
71
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:289: simplifiedApi: false, // API calls to this resource use JSON:API format
|
|
72
|
+
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.
|
|
73
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:413:console.log('Full JSON:API response:', inspect(fullJsonApi));
|
|
74
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:415:// Full JSON:API response: {
|
|
75
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:435:console.log('Minimal JSON:API response:', inspect(minimalJsonApi));
|
|
76
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:437:// Minimal JSON:API response: { id: '5', type: 'countries' }
|
|
77
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:569:}).on('error', (err) => {
|
|
78
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:570: console.error('Failed to start server:', err);
|
|
79
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:649:- **HTTP/REST**: `simplifiedTransport` defaults to `false` (JSON:API compliance)
|
|
80
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:651:This means that by default, HTTP endpoints expect and return proper JSON:API format:
|
|
81
|
+
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:
|
|
82
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:776:| `includeDepthLimit` | `vars.includeDepthLimit` | `3` | Maximum depth for nested relationship includes | ✓ |
|
|
83
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:777:| `enablePaginationCounts` | `vars.enablePaginationCounts` | `true` | Whether to include total count in pagination metadata | ✓ |
|
|
84
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:780:| `idProperty` | `vars.idProperty` | `'id'` | Name of the ID field in resources | ✓ |
|
|
85
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:826:- **`api.helpers.newTransaction()`** - Creates a new database transaction for atomic operations
|
|
86
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:830: // Use transaction in multiple operations
|
|
87
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:831: await api.resources.countries.post({ name: 'France', code: 'FR' }, { transaction: trx });
|
|
88
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:832: await api.resources.publishers.post({ name: 'French Press', country: 1 }, { transaction: trx });
|
|
89
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:834: } catch (error) {
|
|
90
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:835: await trx.rollback();
|
|
91
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:836: throw error;
|
|
92
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:863:- **`api.http.express.notFoundRouter`** - Express middleware for handling 404 errors
|
|
93
|
+
docs/GUIDE/GUIDE_1_Initial_Setup.md:943:- Managing database transactions
|
|
94
|
+
docs/GUIDE/GUIDE_2_1_The_Starting_Point.md:39:}).on('error', (err) => {
|
|
95
|
+
docs/GUIDE/GUIDE_2_1_The_Starting_Point.md:40: console.error('Failed to start server:', err);
|
|
96
|
+
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.
|
|
97
|
+
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:187: include: ['author', 'publisher', 'genres']
|
|
98
|
+
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:225: include: ['author', 'publisher', 'genres']
|
|
99
|
+
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:314: // publisher_id and genres NOT included!
|
|
100
|
+
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:
|
|
101
|
+
docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md:404:- You must include ALL data you want to keep
|
|
102
|
+
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
|
|
103
|
+
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
|
|
104
|
+
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.
|
|
105
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:21: include: {
|
|
106
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:22: limit: 5, // Default limit for included comments
|
|
107
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:31: include: {
|
|
108
|
+
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
|
|
109
|
+
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:
|
|
110
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:888:// include: {
|
|
111
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:895:// When you include comments, these defaults are automatically applied:
|
|
112
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:898: include: ['comments'], // Just specify which relationships to include
|
|
113
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:902:// HTTP: GET /api/posts?include=comments&page[number]=1&page[size]=3
|
|
114
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:924: include: ['comments', 'tags'],
|
|
115
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:972: self: '/api/posts?include=comments&&sort=-created_at&page[number]=1&page[size]=3',
|
|
116
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:973: first: '/api/posts?include=comments&&sort=-created_at&page[number]=1&page[size]=3',
|
|
117
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:974: last: '/api/posts?include=comments&&sort=-created_at&page[number]=9&page[size]=3',
|
|
118
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:975: next: '/api/posts?include=comments&&sort=-created_at&page[number]=2&page[size]=3'
|
|
119
|
+
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',
|
|
120
|
+
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',
|
|
121
|
+
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',
|
|
122
|
+
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'
|
|
123
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1028:The API automatically generates pagination links following JSON:API specification:
|
|
124
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1037: simplified: false // Use JSON:API format to see links
|
|
125
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1040:// Returns (JSON:API): {
|
|
126
|
+
docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md:1094: include: ['comments', 'tags']
|
|
127
|
+
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
|
|
128
|
+
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',
|
|
129
|
+
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',
|
|
130
|
+
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',
|
|
131
|
+
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'
|
|
132
|
+
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
|
|
133
|
+
docs/API.md:37: - [Sparse Fieldsets](#sparse-fieldsets)
|
|
134
|
+
docs/API.md:41:7. [Error Handling](#error-handling)
|
|
135
|
+
docs/API.md:64: include: Array, // Relationship paths to include
|
|
136
|
+
docs/API.md:71: transaction: Object // Database transaction object
|
|
137
|
+
docs/API.md:78:| `queryParams.include` | Array | No | Relationship paths to include (e.g., `['author', 'comments.user']`) |
|
|
138
|
+
docs/API.md:88:| `transaction` | Object | No | Database transaction object |
|
|
139
|
+
docs/API.md:92:**JSON:API Mode (simplified: false):**
|
|
140
|
+
docs/API.md:110: included: [
|
|
141
|
+
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
|
|
142
|
+
docs/API.md:215: include: ['author', 'comments.user'],
|
|
143
|
+
docs/API.md:225:// GET /articles?include=author,comments.user&fields[articles]=title,summary&fields[users]=name,avatar&fields[comments]=content,created-at
|
|
144
|
+
docs/API.md:228:**JSON:API Mode Query:**
|
|
145
|
+
docs/API.md:230:// Force JSON:API response format
|
|
146
|
+
docs/API.md:234: include: ['author']
|
|
147
|
+
docs/API.md:239:// Returns full JSON:API structure with type, id, attributes, relationships
|
|
148
|
+
docs/API.md:275: include: Array, // Relationship paths to include
|
|
149
|
+
docs/API.md:279: transaction: Object // Database transaction object
|
|
150
|
+
docs/API.md:287:| `queryParams.include` | Array | No | Relationship paths to include |
|
|
151
|
+
docs/API.md:290:| `transaction` | Object | No | Database transaction object |
|
|
152
|
+
docs/API.md:294:**JSON:API Mode (simplified: false):**
|
|
153
|
+
docs/API.md:310: included: [
|
|
154
|
+
docs/API.md:339:GET /articles/1?include=author&fields[articles]=title,content
|
|
155
|
+
docs/API.md:362: include: ['author', 'comments']
|
|
156
|
+
docs/API.md:367:// GET /articles/1?include=author,comments
|
|
157
|
+
docs/API.md:376: include: ['author'],
|
|
158
|
+
docs/API.md:385:// GET /articles/1?include=author&fields[articles]=title,summary&fields[users]=name
|
|
159
|
+
docs/API.md:388:**JSON:API Mode Get:**
|
|
160
|
+
docs/API.md:390:// Get in JSON:API format
|
|
161
|
+
docs/API.md:394: include: ['author', 'tags']
|
|
162
|
+
docs/API.md:399:// Returns full JSON:API document structure
|
|
163
|
+
docs/API.md:417: inputRecord: Object, // Required: Resource data (JSON:API or simplified)
|
|
164
|
+
docs/API.md:419: include: Array, // For response formatting
|
|
165
|
+
docs/API.md:423: transaction: Object, // Database transaction object
|
|
166
|
+
docs/API.md:431:| `inputRecord.data` | Object | Yes (JSON:API) | Resource data container |
|
|
167
|
+
docs/API.md:432:| `inputRecord.data.type` | String | Yes (JSON:API) | Resource type |
|
|
168
|
+
docs/API.md:433:| `inputRecord.data.attributes` | Object | Yes (JSON:API) | Resource attributes |
|
|
169
|
+
docs/API.md:435:| `queryParams` | Object | No | For includes/fields in response |
|
|
170
|
+
docs/API.md:437:| `transaction` | Object | No | Database transaction object |
|
|
171
|
+
docs/API.md:493:**Create with JSON:API Format:**
|
|
172
|
+
docs/API.md:495:// Create article with JSON:API format
|
|
173
|
+
docs/API.md:527: include: ['author', 'tags']
|
|
174
|
+
docs/API.md:580: include: Array, // For response formatting
|
|
175
|
+
docs/API.md:584: transaction: Object, // Database transaction object
|
|
176
|
+
docs/API.md:593:| `inputRecord.data.id` | String | Yes (JSON:API) | Must match the resource ID |
|
|
177
|
+
docs/API.md:594:| `inputRecord.data.type` | String | Yes (JSON:API) | Resource type |
|
|
178
|
+
docs/API.md:595:| `inputRecord.data.attributes` | Object | Yes (JSON:API) | All resource attributes |
|
|
179
|
+
docs/API.md:599:| `transaction` | Object | No | Database transaction object |
|
|
180
|
+
docs/API.md:648:**Replace with JSON:API Format:**
|
|
181
|
+
docs/API.md:650:// Replace article with JSON:API format
|
|
182
|
+
docs/API.md:707: include: Array, // For response formatting
|
|
183
|
+
docs/API.md:711: transaction: Object, // Database transaction object
|
|
184
|
+
docs/API.md:720:| `inputRecord.data.id` | String | Yes (JSON:API) | Resource ID |
|
|
185
|
+
docs/API.md:721:| `inputRecord.data.type` | String | Yes (JSON:API) | Resource type |
|
|
186
|
+
docs/API.md:726:| `transaction` | Object | No | Database transaction object |
|
|
187
|
+
docs/API.md:779:**Update with JSON:API Format:**
|
|
188
|
+
docs/API.md:781:// Update with JSON:API format
|
|
189
|
+
docs/API.md:841: transaction: Object // Database transaction object
|
|
190
|
+
docs/API.md:848:| `transaction` | Object | No | Database transaction object |
|
|
191
|
+
docs/API.md:878:// Delete within a transaction
|
|
192
|
+
docs/API.md:879:const trx = await knex.transaction();
|
|
193
|
+
docs/API.md:884: transaction: trx
|
|
194
|
+
docs/API.md:890: transaction: trx
|
|
195
|
+
docs/API.md:894:} catch (error) {
|
|
196
|
+
docs/API.md:895: await trx.rollback();
|
|
197
|
+
docs/API.md:896: throw error;
|
|
198
|
+
docs/API.md:901:The library automatically manages transaction commits when you don't provide one:
|
|
199
|
+
docs/API.md:902:- If you provide a transaction, you're responsible for committing/rolling back
|
|
200
|
+
docs/API.md:903:- If you don't provide a transaction, the library creates one and auto-commits
|
|
201
|
+
docs/API.md:925: transaction: Object // Database transaction object
|
|
202
|
+
docs/API.md:934:| `transaction` | Object | No | Database transaction object |
|
|
203
|
+
docs/API.md:938:JSON:API response with related resources (supports all query features like filtering, pagination, etc.)
|
|
204
|
+
docs/API.md:1004: transaction: Object // Database transaction object
|
|
205
|
+
docs/API.md:1010:JSON:API relationship document with resource identifiers only
|
|
206
|
+
docs/API.md:1078: transaction: Object // Database transaction object
|
|
207
|
+
docs/API.md:1135: transaction: Object // Database transaction object
|
|
208
|
+
docs/API.md:1216: transaction: Object // Database transaction object
|
|
209
|
+
docs/API.md:1305:14. afterCommit (if transaction was created)
|
|
210
|
+
docs/API.md:1323:14. afterCommit (if transaction was created)
|
|
211
|
+
docs/API.md:1341:14. afterCommit (if transaction was created)
|
|
212
|
+
docs/API.md:1353:8. afterCommit (if transaction was created)
|
|
213
|
+
docs/API.md:1425: transaction: {...}, // Database transaction
|
|
214
|
+
docs/API.md:1460: transaction: {...},
|
|
215
|
+
docs/API.md:1467:- Throw errors for custom validation
|
|
216
|
+
docs/API.md:1489: transaction: {...}
|
|
217
|
+
docs/API.md:1493:**Purpose:** Authorization checks - throw error to deny access
|
|
218
|
+
docs/API.md:1514: include: ['author'],
|
|
219
|
+
docs/API.md:1520: transaction: {...},
|
|
220
|
+
docs/API.md:1548: record: { // Full JSON:API record
|
|
221
|
+
docs/API.md:1554: isMainResource: true, // vs included resource
|
|
222
|
+
docs/API.md:1556: requestedFields: [...], // Fields requested via sparse fieldsets
|
|
223
|
+
docs/API.md:1598: if (context.requestedComputedFields.includes('word_count')) {
|
|
224
|
+
docs/API.md:1603: if (context.requestedComputedFields.includes('reading_time')) {
|
|
225
|
+
docs/API.md:1619: included: [...],
|
|
226
|
+
docs/API.md:1647: error: {...}, // For rollback
|
|
227
|
+
docs/API.md:1899:// Response includes:
|
|
228
|
+
docs/API.md:1946:// Response includes:
|
|
229
|
+
docs/API.md:1988:// With includes - specify fields for each type
|
|
230
|
+
docs/API.md:1991: include: ['author', 'category'],
|
|
231
|
+
docs/API.md:2014: include: ['author']
|
|
232
|
+
docs/API.md:2021: include: ['author', 'category', 'tags']
|
|
233
|
+
docs/API.md:2026:// GET /articles?include=author,category,tags
|
|
234
|
+
docs/API.md:2035: include: ['author.profile', 'comments.user', 'category.parent']
|
|
235
|
+
docs/API.md:2039:// Deep nesting (limited by includeDepthLimit)
|
|
236
|
+
docs/API.md:2042: include: ['comments.user.profile.avatar']
|
|
237
|
+
docs/API.md:2047:// GET /articles?include=comments.user.profile.avatar
|
|
238
|
+
docs/API.md:2052:Some implementations support filtering included resources:
|
|
239
|
+
docs/API.md:2055:// Custom hook to filter included resources
|
|
240
|
+
docs/API.md:2057: if (context.result.included) {
|
|
241
|
+
docs/API.md:2058: // Filter included comments to only show approved
|
|
242
|
+
docs/API.md:2059: context.result.included = context.result.included.filter(resource => {
|
|
243
|
+
docs/API.md:2079: simplifiedTransport: false, // Use JSON:API for HTTP transport (default: false)
|
|
244
|
+
docs/API.md:2101: includeDepthLimit: 3, // Maximum relationship nesting depth
|
|
245
|
+
docs/API.md:2107: exposeErrors: false, // Include error details in responses
|
|
246
|
+
docs/API.md:2127: idProperty: 'id', // Custom primary key field (default: 'id')
|
|
247
|
+
docs/API.md:2257: includeDeleted: false
|
|
248
|
+
docs/API.md:2281: idProperty: 'user_id', // Use 'user_id' instead of 'id'
|
|
249
|
+
docs/API.md:2347:The library uses standard JSON:API error format:
|
|
250
|
+
docs/API.md:2353: errors: [
|
|
251
|
+
docs/API.md:2376: errors: [{
|
|
252
|
+
docs/API.md:2389: errors: [{
|
|
253
|
+
docs/API.md:2401: errors: [{
|
|
254
|
+
docs/API.md:2413: errors: [{
|
|
255
|
+
docs/API.md:2429: const error = new Error('Title too short');
|
|
256
|
+
docs/API.md:2430: error.status = 422;
|
|
257
|
+
docs/API.md:2431: error.code = 'TITLE_TOO_SHORT';
|
|
258
|
+
docs/API.md:2432: error.pointer = '/data/attributes/title';
|
|
259
|
+
docs/API.md:2433: throw error;
|
|
260
|
+
docs/API.md:2437:// Custom error transformation
|
|
261
|
+
docs/API.md:2438:api.hook('errorTransform', async (error, context) => {
|
|
262
|
+
docs/API.md:2440: status: error.status || '500',
|
|
263
|
+
docs/API.md:2441: code: error.code || 'INTERNAL_ERROR',
|
|
264
|
+
docs/API.md:2442: title: error.title || 'Error',
|
|
265
|
+
docs/API.md:2443: detail: error.message,
|
|
266
|
+
docs/API.md:2458:All methods support database transactions with automatic management:
|
|
267
|
+
docs/API.md:2461:// Automatic transaction (recommended)
|
|
268
|
+
docs/API.md:2467: // No transaction provided - library creates and auto-commits
|
|
269
|
+
docs/API.md:2470:// Manual transaction management
|
|
270
|
+
docs/API.md:2471:const trx = await knex.transaction();
|
|
271
|
+
docs/API.md:2479: transaction: trx // Provide transaction
|
|
272
|
+
docs/API.md:2489: transaction: trx // Same transaction
|
|
273
|
+
docs/API.md:2494:} catch (error) {
|
|
274
|
+
docs/API.md:2495: await trx.rollback();
|
|
275
|
+
docs/API.md:2496: throw error;
|
|
276
|
+
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.
|
|
277
|
+
docs/API.md:2507:// Batch create with transaction
|
|
278
|
+
docs/API.md:2509: const trx = await knex.transaction();
|
|
279
|
+
docs/API.md:2516: transaction: trx,
|
|
280
|
+
docs/API.md:2524: } catch (error) {
|
|
281
|
+
docs/API.md:2525: await trx.rollback();
|
|
282
|
+
docs/API.md:2526: throw error;
|
|
283
|
+
docs/API.md:2532: const trx = await knex.transaction();
|
|
284
|
+
docs/API.md:2538: transaction: trx,
|
|
285
|
+
docs/API.md:2544: } catch (error) {
|
|
286
|
+
docs/API.md:2545: await trx.rollback();
|
|
287
|
+
docs/API.md:2546: throw error;
|
|
288
|
+
docs/API.md:2620: include: ['commentable'], // Includes the related article/video/photo
|
|
289
|
+
docs/API.md:2636: includeDeleted: false // Default behavior
|
|
290
|
+
docs/API.md:2654: include_deleted: true
|
|
291
|
+
docs/API.md:2683: if (context.requestedComputedFields.includes('can_edit')) {
|
|
292
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:64:// Get the French publisher and include its authors (simplified mode output)
|
|
293
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:68: include: ['authors'] // Use the relationship name 'authors' defined in the publishers schema
|
|
294
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:107:## Including `hasMany` Records (`include`)
|
|
295
|
+
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).
|
|
296
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:113:Using the exact same data as before, you can change the query to `include` countries:
|
|
297
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:116:// Get the French publisher and include its authors (simplified mode output)
|
|
298
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:120: include: ['authors'] // Use the relationship name 'authors' defined in the publishers schema
|
|
299
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:125:// Get the French publisher and include its authors (simplified mode output)
|
|
300
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:129: include: ['authors'] // Use the relationship name 'authors' defined in the publishers schema
|
|
301
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:155: included: [
|
|
302
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:179:// Query all publishers and include their authors (simplified mode output)
|
|
303
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:182: include: ['authors']
|
|
304
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:185:// HTTP: GET /api/publishers?include=authors
|
|
305
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:205:// Query all publishers and include their authors (non-simplified, full JSON:API output)
|
|
306
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:208: include: ['authors']
|
|
307
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:212:// HTTP: GET /api/publishers?include=authors
|
|
308
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:213:// Returns (JSON:API): {
|
|
309
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:235:// included: [
|
|
310
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:320: included: [
|
|
311
|
+
docs/GUIDE/GUIDE_2_4_HasMany_Records.md:343: links: { self: '/api/publishers?include=authors' }
|
|
312
|
+
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.
|
|
313
|
+
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.
|
|
314
|
+
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:17:- [Error Handling](#error-handling)
|
|
315
|
+
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:27:4. **Atomic Operations**: Manage relationships in isolation with proper transaction support
|
|
316
|
+
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:102: simplified: false // Use full JSON:API format
|
|
317
|
+
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:152: fields: { authors: 'name' } // Optional: sparse fieldsets
|
|
318
|
+
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:393:Common errors you might encounter:
|
|
319
|
+
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:402:} catch (error) {
|
|
320
|
+
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:416:} catch (error) {
|
|
321
|
+
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:428:} catch (error) {
|
|
322
|
+
docs/GUIDE/GUIDE_2_9_Relationships_Urls.md:497:The plugin automatically handles transactions for data integrity:
|
|
323
|
+
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.
|
|
324
|
+
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.
|
|
325
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:93:## Including `belongsTo` Records (`include`)
|
|
326
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:95:To retrieve related `belongsTo` resources, use the `include` query parameter.
|
|
327
|
+
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.
|
|
328
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:119:// Get a publisher and include its country (simplified mode output)
|
|
329
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:123: include: ['country'] // Use the 'as' alias defined in the schema
|
|
330
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:128:// Query all publishers and include their countries (simplified mode output)
|
|
331
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:131: include: ['country']
|
|
332
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:134:// HTTP: GET /api/publishers?include=country
|
|
333
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:145:// Query all publishers and include their countries (JSON:API format)
|
|
334
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:148: include: ['country']
|
|
335
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:152:// HTTP: GET /api/publishers?include=country
|
|
336
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:153:// Returns (JSON:API): {
|
|
337
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:164:// included: [
|
|
338
|
+
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.
|
|
339
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:288: included: [
|
|
340
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:311: links: { self: '/api/publishers?include=country' }
|
|
341
|
+
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.
|
|
342
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:336:// Get a publisher, include its country, but only retrieve publisher name and country code
|
|
343
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:337:const sparsePublisher = await api.resources.publishers.get({
|
|
344
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:340: include: ['country'],
|
|
345
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:348:console.log('Sparse Publisher and Country:', inspect(sparsePublisher));
|
|
346
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:351:// Query all publishers, include their countries, but only retrieve publisher name and country code
|
|
347
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:352:const sparsePublishersQuery = await api.resources.publishers.query({
|
|
348
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:354: include: ['country'],
|
|
349
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:362:// HTTP: GET /api/publishers?include=country&fields[publishers]=name&fields[countries]=code,name
|
|
350
|
+
docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md:369:console.log('Sparse Publishers Query (all results):', inspect(sparsePublishersQuery));
|
|
351
|
+
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.
|
|
352
|
+
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:144: const { data, error } = await supabase.auth.signUp({
|
|
353
|
+
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:148: if (error) throw error
|
|
354
|
+
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:154: const { data, error } = await supabase.auth.signInWithPassword({
|
|
355
|
+
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:158: if (error) throw error
|
|
356
|
+
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:249: const { data, error } = await supabase.auth.signInWithOAuth({
|
|
357
|
+
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:255: if (error) throw error
|
|
358
|
+
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:308: ownershipField: 'user_id' // Field for ownership checks (default: 'user_id')
|
|
359
|
+
docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md:469:// Also enable auto-ownership
|
|
360
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:198:## Including Many-to-Many Records (`include`)
|
|
361
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:206:console.log('Book without includes (simplified):', inspect(book_simplified));
|
|
362
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:207:console.log('Book without includes (non-simplified):', inspect(book_non_simplified));
|
|
363
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:213:Book without includes (simplified):
|
|
364
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:222:Book without includes (non-simplified):
|
|
365
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:246:## Including Many to many Records (`include`)
|
|
366
|
+
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:
|
|
367
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:251:// Get a book with its authors included
|
|
368
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:254: queryParams: { include: ['authors'] }
|
|
369
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:259: queryParams: { include: ['authors'] },
|
|
370
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:265: queryParams: { include: ['authors'] }
|
|
371
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:269: queryParams: { include: ['authors'] },
|
|
372
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:325: included: [
|
|
373
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:409: links: { self: '/api/books?include=authors' }
|
|
374
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:474: included: [
|
|
375
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:509: links: { self: '/api/books?include=authors' }
|
|
376
|
+
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.
|
|
377
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:520:Using the schema definitions from above, which include cross-table search fields:
|
|
378
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:543:// 3. Combine searches: Find books by Neil that include full author data
|
|
379
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:547: include: ['authors']
|
|
380
|
+
docs/GUIDE/GUIDE_2_6_Many_To_Many.md:690: links: { self: '/api/books?filters[authorName]=Neil&include=authors' }
|
|
381
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:90:- Schema includes `reviewable_type` and `reviewable_id` fields
|
|
382
|
+
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.
|
|
383
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:125:// Add reviews using non-simplified (JSON:API standard) input with relationships object
|
|
384
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:174:// Returns (JSON:API): {
|
|
385
|
+
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.
|
|
386
|
+
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.
|
|
387
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:316:## Including Polymorphic Records (`include`)
|
|
388
|
+
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.
|
|
389
|
+
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'] } });
|
|
390
|
+
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 });
|
|
391
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:328:const french_authors_with_includes_simplified = await api.resources.authors.query({queryParams: { include: ['reviews'] } });
|
|
392
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:329:// HTTP: GET /api/authors?include=reviews
|
|
393
|
+
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 });
|
|
394
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:338:// HTTP: GET /api/authors?include=reviews
|
|
395
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:339:// Returns (JSON:API): {
|
|
396
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:348:// included: [
|
|
397
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:356:console.log(inspect(frenchAuthor1_with_reviews_and_includes_simplified));
|
|
398
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:358:console.log(inspect(frenchAuthor1_with_reviews_and_includes_non_simplified))
|
|
399
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:361:console.log(inspect(french_authors_with_includes_simplified));
|
|
400
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:363:console.log(inspect(french_authors_with_includes_non_simplified))
|
|
401
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:409: included: [
|
|
402
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:500: included: [
|
|
403
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:516: links: { self: '/api/authors?include=reviews' }
|
|
404
|
+
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.
|
|
405
|
+
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.
|
|
406
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:699:// Returns (JSON:API): {
|
|
407
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:718:// Returns (JSON:API): {
|
|
408
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:730:// Returns (JSON:API): {
|
|
409
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:860:The output demonstrates how polymorphic search works across both simplified and JSON:API formats:
|
|
410
|
+
docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md:869:In the simplified format, notice how the polymorphic fields are included:
|
|
411
|
+
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).
|
|
412
|
+
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.
|
|
413
|
+
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:373:// Result: The effective searchSchema includes:
|
|
414
|
+
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.
|
|
415
|
+
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.
|
|
416
|
+
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:770:const sparseSearch = await api.resources.countries.query({
|
|
417
|
+
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:781:console.log('Sparse search result:', inspect(sparseSearch));
|
|
418
|
+
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:782:// Note: sparseSearch contains { data, meta, links }
|
|
419
|
+
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
|
|
420
|
+
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
|
|
421
|
+
docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md:794:When using the HTTP API, sparse fieldsets are specified as comma-separated values:
|
|
422
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:7:Inside hooks, JSON:API format is king _regardless of simplified mode_.
|
|
423
|
+
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)
|
|
424
|
+
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)
|
|
425
|
+
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.
|
|
426
|
+
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:
|
|
427
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:15:// In a hook, the record is ALWAYS JSON:API format:
|
|
428
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:18: // Even in simplified mode, inputRecord has JSON:API structure
|
|
429
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:27: // This is called for ALL records (main and included/child records)
|
|
430
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:154: // This will enrich all attributes (main and included) when fetched
|
|
431
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:158: // This wordCount is calculated on-the-fly and included in the response
|
|
432
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:208: return vars.allowedStatuses.includes(status);
|
|
433
|
+
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
|
|
434
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:316: - `idProperty` - Primary key field name
|
|
435
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:319:- `transaction` (object/null) - Database transaction if provided
|
|
436
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:320:- `db` (object) - Database connection (knex instance or transaction)
|
|
437
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:324:- `inputRecord` (object) - The JSON:API formatted input containing:
|
|
438
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:334: - `include` - Relationships to include
|
|
439
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:338:- `record` (object) - The fetched/created record in JSON:API format
|
|
440
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:350:6. **Transaction hooks** - Commit/rollback for write operations
|
|
441
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:369: - `idProperty` - Primary key field name
|
|
442
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:372: - `include` - Relationships to include
|
|
443
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:376:- `transaction` (object/null) - Database transaction if provided
|
|
444
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:377:- `db` (object) - Database connection (knex instance or transaction)
|
|
445
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:387:- `queryParams.include` - Modify includes
|
|
446
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:423:- `record` (object) - The JSON:API formatted response
|
|
447
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:425: - `included` - Array of included resources
|
|
448
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:485: - `include` - Relationships to include
|
|
449
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:486:- `transaction` (object/null) - Database transaction if provided
|
|
450
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:495:- `queryParams.include` - Modify includes
|
|
451
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:511:- `record` (object) - The fetched JSON:API record
|
|
452
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:514:- Can throw errors to deny access
|
|
453
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:546:- `record` (object) - The JSON:API formatted response
|
|
454
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:590:- `inputRecord` (object) - The JSON:API formatted input data
|
|
455
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:595:- `queryParams` (object) - Contains `fields`, `include` for response
|
|
456
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:596:- `transaction` (object) - Database transaction (created if not provided)
|
|
457
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:597:- `shouldCommit` (boolean) - Whether to commit the transaction
|
|
458
|
+
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 }`).
|
|
459
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:690:- `record` (object) - The JSON:API formatted response
|
|
460
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:700:**When**: After the transaction is committed (only if `shouldCommit` is true)
|
|
461
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:726:**When**: If an error occurs and transaction is rolled back
|
|
462
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:729:**Context**: All accumulated context plus error information
|
|
463
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:757:- `inputRecord` (object) - The JSON:API formatted input data
|
|
464
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:760:- `queryParams` (object) - Contains `fields`, `include` for response
|
|
465
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:761:- `transaction` (object) - Database transaction
|
|
466
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:762:- `shouldCommit` (boolean) - Whether to commit the transaction
|
|
467
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:832:- `record` (object) - The JSON:API formatted response
|
|
468
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:850:**When**: After the transaction is committed
|
|
469
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:856:**When**: If an error occurs and transaction is rolled back
|
|
470
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:857:**Context**: All accumulated context plus error information
|
|
471
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:885:- `inputRecord` (object) - The JSON:API formatted input data (partial)
|
|
472
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:888:- `queryParams` (object) - Contains `fields`, `include` for response
|
|
473
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:889:- `transaction` (object) - Database transaction
|
|
474
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:890:- `shouldCommit` (boolean) - Whether to commit the transaction
|
|
475
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:957:- `record` (object) - The JSON:API formatted response
|
|
476
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:975:**When**: After the transaction is committed
|
|
477
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:981:**When**: If an error occurs and transaction is rolled back
|
|
478
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:982:**Context**: All accumulated context plus error information
|
|
479
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1010:- `transaction` (object) - Database transaction
|
|
480
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1011:- `shouldCommit` (boolean) - Whether to commit the transaction
|
|
481
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1018:- Can throw errors to prevent deletion
|
|
482
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1093:**When**: After the transaction is committed
|
|
483
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1101:**When**: If an error occurs and transaction is rolled back
|
|
484
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1102:**Context**: All accumulated context plus error information
|
|
485
|
+
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.
|
|
486
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1141:- This hook is called for EVERY record (main and included)
|
|
487
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1308:Throwing an error in any hook will:
|
|
488
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1310:- Trigger rollback for write operations
|
|
489
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1311:- Return the error to the client
|
|
490
|
+
docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md:1328:- Use database transactions appropriately
|
|
491
|
+
docs/GUIDE/index.md:25: Implement drag-and-drop functionality with automatic position management.
|
|
492
|
+
docs/GUIDE/index.md:37: Handle multiple records efficiently with bulk create, update, and delete operations.
|
|
493
|
+
docs/GUIDE/index.md:39:- **[CORS Configuration](GUIDE_X_Cors.md)**
|
|
494
|
+
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.
|
|
495
|
+
docs/GUIDE/GUIDE_2_Data_And_Relations.md:47:- Limits and ordering for included relationships
|
|
496
|
+
docs/GUIDE/GUIDE_2_Data_And_Relations.md:48:- Combining filters, sorting, pagination, and includes
|
|
497
|
+
docs/GUIDE/GUIDE_X_Cors.md:1:# CORS Plugin Guide
|
|
498
|
+
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.
|
|
499
|
+
docs/GUIDE/GUIDE_X_Cors.md:13:## Intro: What is CORS
|
|
500
|
+
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.
|
|
501
|
+
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:
|
|
502
|
+
docs/GUIDE/GUIDE_X_Cors.md:20:- 🛡️ **Browsers**: Block the response from reaching JavaScript if CORS headers don't allow it
|
|
503
|
+
docs/GUIDE/GUIDE_X_Cors.md:21:- 🔓 **Non-browser clients** (cURL, Postman, mobile apps): Not affected by CORS at all
|
|
504
|
+
docs/GUIDE/GUIDE_X_Cors.md:28:- Without CORS, the browser will block requests from the web app to the API
|
|
505
|
+
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:
|
|
506
|
+
docs/GUIDE/GUIDE_X_Cors.md:34:3. **Server Response**: The server ALWAYS responds with data + CORS headers
|
|
507
|
+
docs/GUIDE/GUIDE_X_Cors.md:50:Without CORS headers from the API:
|
|
508
|
+
docs/GUIDE/GUIDE_X_Cors.md:54:❌ Console error: "CORS policy: No 'Access-Control-Allow-Origin' header"
|
|
509
|
+
docs/GUIDE/GUIDE_X_Cors.md:59:With CORS headers from the API:
|
|
510
|
+
docs/GUIDE/GUIDE_X_Cors.md:62:✅ API includes: Access-Control-Allow-Origin: https://coolweather.app
|
|
511
|
+
docs/GUIDE/GUIDE_X_Cors.md:67:**CORS does NOT protect your API from:**
|
|
512
|
+
docs/GUIDE/GUIDE_X_Cors.md:74:**CORS ONLY protects:**
|
|
513
|
+
docs/GUIDE/GUIDE_X_Cors.md:78:**Therefore:** CORS is about protecting users, not protecting your API. You still need:
|
|
514
|
+
docs/GUIDE/GUIDE_X_Cors.md:86:The CORS plugin automatically handles preflight requests and adds appropriate headers to all responses.
|
|
515
|
+
docs/GUIDE/GUIDE_X_Cors.md:88:The basic CORS setup creates a permissive configuration suitable for development:
|
|
516
|
+
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.
|
|
517
|
+
docs/GUIDE/GUIDE_X_Cors.md:106:Here's a complete API setup with CORS enabled with the most permissive setup:
|
|
518
|
+
docs/GUIDE/GUIDE_X_Cors.md:148:app.listen(3000).on('error', (err) => {
|
|
519
|
+
docs/GUIDE/GUIDE_X_Cors.md:149: console.error('Failed to start server:', err);
|
|
520
|
+
docs/GUIDE/GUIDE_X_Cors.md:233:The CORS plugin accepts various configuration options.
|
|
521
|
+
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.
|
|
522
|
+
docs/GUIDE/GUIDE_X_Cors.md:303:- For each allowed origin, the response includes that specific origin
|
|
523
|
+
docs/GUIDE/GUIDE_X_Cors.md:415: if (origin.includes('localhost') && process.env.NODE_ENV === 'development') {
|
|
524
|
+
docs/GUIDE/GUIDE_X_Cors.md:483:These are headers the browser is allowed to include in requests:
|
|
525
|
+
docs/GUIDE/GUIDE_X_Cors.md:523:These examples show typical CORS configurations for different scenarios you'll encounter in real projects.
|
|
526
|
+
docs/GUIDE/GUIDE_X_Cors.md:547:- ✅ Any developer can work with the API without CORS issues
|
|
527
|
+
docs/GUIDE/GUIDE_X_Cors.md:742:These patterns show sophisticated CORS configurations for complex real-world scenarios.
|
|
528
|
+
docs/GUIDE/GUIDE_X_Cors.md:747:2. **Per-Route CORS**: Some endpoints need different CORS rules
|
|
529
|
+
docs/GUIDE/GUIDE_X_Cors.md:748:3. **Authentication Integration**: CORS and auth systems must work together
|
|
530
|
+
docs/GUIDE/GUIDE_X_Cors.md:787: return allowed.includes(origin) || partners.includes(origin);
|
|
531
|
+
docs/GUIDE/GUIDE_X_Cors.md:803:### Per-Route CORS (Using Hooks)
|
|
532
|
+
docs/GUIDE/GUIDE_X_Cors.md:805:**Important Understanding:** The CORS plugin is essentially a sophisticated header management system. It:
|
|
533
|
+
docs/GUIDE/GUIDE_X_Cors.md:812:**Why You'd Need Per-Route CORS:**
|
|
534
|
+
docs/GUIDE/GUIDE_X_Cors.md:823: // Add extra CORS headers for admin routes
|
|
535
|
+
docs/GUIDE/GUIDE_X_Cors.md:832:1. Hooks into the response pipeline AFTER the CORS plugin
|
|
536
|
+
docs/GUIDE/GUIDE_X_Cors.md:834:3. Overrides the CORS headers for stricter control
|
|
537
|
+
docs/GUIDE/GUIDE_X_Cors.md:840:// Different CORS policies for different route types
|
|
538
|
+
docs/GUIDE/GUIDE_X_Cors.md:842: order: -900 // Run after CORS plugin (which is -1000)
|
|
539
|
+
docs/GUIDE/GUIDE_X_Cors.md:856: if (adminOrigins.includes(request.headers.origin)) {
|
|
540
|
+
docs/GUIDE/GUIDE_X_Cors.md:860: // Remove CORS headers entirely - block the request
|
|
541
|
+
docs/GUIDE/GUIDE_X_Cors.md:871: if (partner && partner.allowedOrigins.includes(request.headers.origin)) {
|
|
542
|
+
docs/GUIDE/GUIDE_X_Cors.md:889: // Special CORS for file uploads
|
|
543
|
+
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.
|
|
544
|
+
docs/GUIDE/GUIDE_X_Cors.md:905:### CORS with Authentication
|
|
545
|
+
docs/GUIDE/GUIDE_X_Cors.md:907:**The Critical Relationship Between CORS and Authentication**
|
|
546
|
+
docs/GUIDE/GUIDE_X_Cors.md:909:CORS and authentication are deeply intertwined. When your API uses authentication, CORS configuration becomes security-critical.
|
|
547
|
+
docs/GUIDE/GUIDE_X_Cors.md:913:1. **Credentials in CORS** means:
|
|
548
|
+
docs/GUIDE/GUIDE_X_Cors.md:924: - Wrong CORS + auth = security vulnerability
|
|
549
|
+
docs/GUIDE/GUIDE_X_Cors.md:926: - User's cookies would be automatically included
|
|
550
|
+
docs/GUIDE/GUIDE_X_Cors.md:928:**How Authentication Flows Work with CORS:**
|
|
551
|
+
docs/GUIDE/GUIDE_X_Cors.md:934: 3. Browser includes Origin: https://app.example.com
|
|
552
|
+
docs/GUIDE/GUIDE_X_Cors.md:945: 2. Browser automatically includes auth cookie
|
|
553
|
+
docs/GUIDE/GUIDE_X_Cors.md:946: 3. Browser includes Origin: https://app.example.com
|
|
554
|
+
docs/GUIDE/GUIDE_X_Cors.md:948: 5. API responds with user data and CORS headers
|
|
555
|
+
docs/GUIDE/GUIDE_X_Cors.md:963: if (knownOrigins.includes(origin)) {
|
|
556
|
+
docs/GUIDE/GUIDE_X_Cors.md:1000: return allowedOrigins.includes(origin);
|
|
557
|
+
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.
|
|
558
|
+
docs/GUIDE/GUIDE_X_Cors.md:1048:2. **Bearer Token Pattern (No CORS Credentials):**
|
|
559
|
+
docs/GUIDE/GUIDE_X_Cors.md:1065: // Bearer token auth - no CORS credentials needed
|
|
560
|
+
docs/GUIDE/GUIDE_X_Cors.md:1068: // Cookie auth - needs CORS credentials
|
|
561
|
+
docs/GUIDE/GUIDE_X_Cors.md:1074:### CORS with Base URL
|
|
562
|
+
docs/GUIDE/GUIDE_X_Cors.md:1076:The CORS plugin works seamlessly with Express base paths.
|
|
563
|
+
docs/GUIDE/GUIDE_X_Cors.md:1095:**How CORS Works with Base URLs:**
|
|
564
|
+
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.
|
|
565
|
+
docs/GUIDE/GUIDE_X_Cors.md:1106:// CORS plugin handles the base path automatically
|
|
566
|
+
docs/GUIDE/GUIDE_X_Cors.md:1113:// Client requests to /v1/api/articles will have proper CORS headers
|
|
567
|
+
docs/GUIDE/GUIDE_X_Cors.md:1123:2. CORS plugin registers OPTIONS handler for `/v1/*`
|
|
568
|
+
docs/GUIDE/GUIDE_X_Cors.md:1125:3. All requests under `/v1` get CORS headers
|
|
569
|
+
docs/GUIDE/GUIDE_X_Cors.md:1146:// API v2 with different CORS
|
|
570
|
+
docs/GUIDE/GUIDE_X_Cors.md:1165:// GET /v1/api/users - CORS allows legacy.example.com
|
|
571
|
+
docs/GUIDE/GUIDE_X_Cors.md:1166:// GET /v2/api/users - CORS blocks legacy.example.com
|
|
572
|
+
docs/GUIDE/GUIDE_X_Cors.md:1171:1. CORS applies to the entire base path, not individual routes
|
|
573
|
+
docs/GUIDE/GUIDE_X_Cors.md:1172:2. You cannot have different CORS settings for routes under the same base path
|
|
574
|
+
docs/GUIDE/GUIDE_X_Cors.md:1173:3. The base path is transparent to CORS origin checks
|
|
575
|
+
docs/GUIDE/GUIDE_X_Cors.md:1182:| "CORS header 'Access-Control-Allow-Origin' missing" | Origin not allowed | Check origin configuration, ensure it matches exactly |
|
|
576
|
+
docs/GUIDE/GUIDE_X_Cors.md:1184:| "Multiple CORS headers" | Multiple CORS middleware | Ensure CORS plugin is installed only once |
|
|
577
|
+
docs/GUIDE/GUIDE_X_Cors.md:1185:| Preflight fails with 404 | OPTIONS route not registered | Check that CORS plugin is installed after transport plugin |
|
|
578
|
+
docs/GUIDE/GUIDE_X_Cors.md:1188:### Debug CORS Issues
|
|
579
|
+
docs/GUIDE/GUIDE_X_Cors.md:1198:// The CORS plugin will log:
|
|
580
|
+
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' }
|
|
581
|
+
docs/GUIDE/GUIDE_X_Cors.md:1208:2024-01-15T10:23:45.124Z [DEBUG] [my-api:plugin:rest-api-cors] CORS processing response {
|
|
582
|
+
docs/GUIDE/GUIDE_X_Cors.md:1213:2024-01-15T10:23:45.125Z [WARN] [my-api:plugin:rest-api-cors] CORS origin not allowed {
|
|
583
|
+
docs/GUIDE/GUIDE_X_Cors.md:1226: - Shows CORS headers being added to regular requests
|
|
584
|
+
docs/GUIDE/GUIDE_X_Cors.md:1240: console.log('CORS Debug:', {
|
|
585
|
+
docs/GUIDE/GUIDE_X_Cors.md:1252:### Testing CORS
|
|
586
|
+
docs/GUIDE/GUIDE_X_Cors.md:1254:**Understanding CORS Testing**
|
|
587
|
+
docs/GUIDE/GUIDE_X_Cors.md:1256:CORS is enforced by browsers, not servers. This creates interesting testing scenarios:
|
|
588
|
+
docs/GUIDE/GUIDE_X_Cors.md:1257:- **cURL/Postman**: Requests always work (no CORS enforcement)
|
|
589
|
+
docs/GUIDE/GUIDE_X_Cors.md:1258:- **Browser**: Requests blocked if CORS headers are wrong
|
|
590
|
+
docs/GUIDE/GUIDE_X_Cors.md:1259:- **Server**: Always sends CORS headers, doesn't block requests
|
|
591
|
+
docs/GUIDE/GUIDE_X_Cors.md:1271:- Default CORS configuration (allows all origins)
|
|
592
|
+
docs/GUIDE/GUIDE_X_Cors.md:1312:1. **cURL Always Succeeds** - Even with wrong CORS headers:
|
|
593
|
+
docs/GUIDE/GUIDE_X_Cors.md:1319:2. **Browser Testing is Required** for real CORS validation:
|
|
594
|
+
docs/GUIDE/GUIDE_X_Cors.md:1321: // In browser console (will fail if CORS is wrong)
|
|
595
|
+
docs/GUIDE/GUIDE_X_Cors.md:1325: .catch(e => console.error('CORS Error:', e));
|
|
596
|
+
docs/GUIDE/GUIDE_X_Cors.md:1346:2. Check Response Headers for CORS headers
|
|
597
|
+
docs/GUIDE/GUIDE_X_Cors.md:1347:3. For failed requests, check Console for CORS errors
|
|
598
|
+
docs/GUIDE/GUIDE_X_Cors.md:1349:### Common CORS Headers Explained
|
|
599
|
+
docs/GUIDE/GUIDE_X_Cors.md:1351:**Understanding Each CORS Header in Detail**
|
|
600
|
+
docs/GUIDE/GUIDE_X_Cors.md:1353:#### Core CORS Headers
|
|
601
|
+
docs/GUIDE/GUIDE_X_Cors.md:1370:if (allowedOrigins.includes(request.headers.origin)) {
|
|
602
|
+
docs/GUIDE/GUIDE_X_Cors.md:1374:| `Access-Control-Allow-Credentials` | **Security Critical** - Allows browser to include cookies and auth headers | `true` |
|
|
603
|
+
docs/GUIDE/GUIDE_X_Cors.md:1401:- Must include the requested method
|
|
604
|
+
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` |
|
|
605
|
+
docs/GUIDE/GUIDE_X_Cors.md:1418:- Must include all headers the client will send
|
|
606
|
+
docs/GUIDE/GUIDE_X_Cors.md:1496:- Prevents wrong CORS headers being cached
|
|
607
|
+
docs/GUIDE/GUIDE_X_Cors.md:1497:- Should always include `Origin` when CORS headers vary
|
|
608
|
+
docs/GUIDE/GUIDE_X_Cors.md:1511:#### Additional CORS Headers
|
|
609
|
+
docs/GUIDE/GUIDE_X_Cors.md:1517:| `Origin` | **Request** - Identifies requesting origin | All CORS requests |
|
|
610
|
+
docs/GUIDE/GUIDE_X_Cors.md:1537:CORS is a security feature, but misconfiguration can create vulnerabilities. Here's what you need to know:
|
|
611
|
+
docs/GUIDE/GUIDE_X_Cors.md:1579: credentials: 'include' // Would send victim's cookies
|
|
612
|
+
docs/GUIDE/GUIDE_X_Cors.md:1616: return origin.includes('mycompany'); // subdomain.mycompany.evil.com would pass!
|
|
613
|
+
docs/GUIDE/GUIDE_X_Cors.md:1640: console.warn('CORS: Rejected origin:', origin);
|
|
614
|
+
docs/GUIDE/GUIDE_X_Cors.md:1767:**The Rule:** CORS is not a replacement for HTTPS. Always use HTTPS in production.
|
|
615
|
+
docs/GUIDE/GUIDE_X_Cors.md:1770:CORS only controls which websites can access your API from a browser. It doesn't:
|
|
616
|
+
docs/GUIDE/GUIDE_X_Cors.md:1776:**What CORS Does vs What HTTPS Does:**
|
|
617
|
+
docs/GUIDE/GUIDE_X_Cors.md:1778:| Security Aspect | CORS | HTTPS |
|
|
618
|
+
docs/GUIDE/GUIDE_X_Cors.md:1798: return allowedOrigins.includes(origin);
|
|
619
|
+
docs/GUIDE/GUIDE_X_Cors.md:1814:**Common HTTPS + CORS Issues:**
|
|
620
|
+
docs/GUIDE/GUIDE_X_Cors.md:1838:**The Rule:** Periodically review and audit your CORS configuration to remove unused origins and tighten security.
|
|
621
|
+
docs/GUIDE/GUIDE_X_Cors.md:1864: console.log('=== CORS Origin Audit ===');
|
|
622
|
+
docs/GUIDE/GUIDE_X_Cors.md:1904: console.error(`SECURITY RISK: ${origin} may be compromised`);
|
|
623
|
+
docs/GUIDE/GUIDE_X_Cors.md:1907: } catch (error) {
|
|
624
|
+
docs/GUIDE/GUIDE_X_Cors.md:1908: console.error(`Cannot verify ${origin}: ${error.message}`);
|
|
625
|
+
docs/GUIDE/GUIDE_X_Cors.md:1937:// This ensures all server instances have consistent CORS settings
|
|
626
|
+
docs/GUIDE/GUIDE_X_Cors.md:1948: origin.includes('ngrok.io') ||
|
|
627
|
+
docs/GUIDE/GUIDE_X_Cors.md:1949: origin.includes('localhost.run') ||
|
|
628
|
+
docs/GUIDE/GUIDE_X_Cors.md:1950: origin.includes('.local') ||
|
|
629
|
+
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.
|
|
630
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:14:- **bulkPost**: Create multiple records
|
|
631
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:15:- **bulkPatch**: Update multiple records
|
|
632
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:16:- **bulkDelete**: Delete multiple records
|
|
633
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:24:import { BulkOperationsPlugin } from 'json-rest-api/plugins/core/bulk-operations-plugin.js';
|
|
634
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:50: 'bulk-operations': {
|
|
635
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:52: defaultAtomic: true, // Default transaction mode
|
|
636
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:68:| `enableOptimizations` | boolean | true | Enable database-specific bulk optimizations when available |
|
|
637
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:147:## Bulk Create (bulkPost)
|
|
638
|
+
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.
|
|
639
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:155:const bulkCreateResult = await api.scopes.authors.bulkPost({
|
|
640
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:164:console.log(inspect(bulkCreateResult));
|
|
641
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:195:const publisherResult = await api.scopes.publishers.bulkPost({
|
|
642
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:222:const partialResult = await api.scopes.authors.bulkPost({
|
|
643
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:238:// errors: [{
|
|
644
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:240:// status: 'error',
|
|
645
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:241:// error: {
|
|
646
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:256:## Bulk Update (bulkPatch)
|
|
647
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:264:const bulkUpdateResult = await api.scopes.authors.bulkPatch({
|
|
648
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:286:console.log('Updated authors:', bulkUpdateResult.meta.succeeded);
|
|
649
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:295:const bookResults = await api.scopes.books.bulkPost({
|
|
650
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:316:const updateOps = await api.scopes.books.bulkPatch({
|
|
651
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:351:const errorResult = await api.scopes.authors.bulkPatch({
|
|
652
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:360:console.log(inspect(errorResult));
|
|
653
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:361:// Shows successful updates and errors for failed operations
|
|
654
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:364:## Bulk Delete (bulkDelete)
|
|
655
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:372:const bulkDeleteResult = await api.scopes.authors.bulkDelete({
|
|
656
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:377:console.log(inspect(bulkDeleteResult));
|
|
657
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:397: await api.scopes.countries.bulkDelete({
|
|
658
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:401:} catch (error) {
|
|
659
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:402: console.log('Cannot delete:', error.message);
|
|
660
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:407:await api.scopes.books.bulkDelete({
|
|
661
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:413:await api.scopes.countries.bulkDelete({
|
|
662
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:424:const mixedResult = await api.scopes.authors.bulkDelete({
|
|
663
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:439:// errors: [
|
|
664
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:440:// { index: 1, id: '999', status: 'error', error: { code: 'REST_API_RESOURCE', message: 'Resource not found' } },
|
|
665
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:441:// { index: 3, id: '888', status: 'error', error: { code: 'REST_API_RESOURCE', message: 'Resource not found' } }
|
|
666
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:448:When using the Express plugin, bulk operations are available via HTTP endpoints:
|
|
667
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:463: console.log('API with bulk operations running on http://localhost:3000');
|
|
668
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:464:}).on('error', (err) => {
|
|
669
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:465: console.error('Failed to start server:', err);
|
|
670
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:473:POST /api/authors/bulk
|
|
671
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:484:POST /api/authors/bulk?atomic=false
|
|
672
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:490:PATCH /api/authors/bulk
|
|
673
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:504:DELETE /api/authors/bulk
|
|
674
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:526: 'bulk-operations': {
|
|
675
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:538:const result = await api.scopes.authors.bulkPost({
|
|
676
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:551:// Add a hook that runs for each bulk operation
|
|
677
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:552:api.addHook('beforePost', 'bulkTracking', {}, async ({ context, params }) => {
|
|
678
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:553: if (context.bulkOperation) {
|
|
679
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:554: console.log(`Processing bulk item ${context.bulkIndex + 1}`);
|
|
680
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:561:Implement robust error handling for bulk operations:
|
|
681
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:566: const result = await api.scopes.authors.bulkPost({
|
|
682
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:568: atomic: false // Continue on errors
|
|
683
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:574: // Handle errors if any
|
|
684
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:575: if (result.errors && result.errors.length > 0) {
|
|
685
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:576: console.error('Import errors:');
|
|
686
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:577: result.errors.forEach(error => {
|
|
687
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:578: console.error(` Row ${error.index}: ${error.error.message}`);
|
|
688
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:583: result.errors.some(e => e.index === index)
|
|
689
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:588: } catch (error) {
|
|
690
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:589: // Handle complete failure (e.g., database connection error)
|
|
691
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:590: console.error('Bulk import failed completely:', error.message);
|
|
692
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:591: throw error;
|
|
693
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:600:3. **Enable Optimizations**: The plugin uses database-specific bulk insert optimizations when available
|
|
694
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:611: const countryResult = await api.scopes.countries.bulkPost({
|
|
695
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:618: const publisherResult = await api.scopes.publishers.bulkPost({
|
|
696
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:625: const authorResult = await api.scopes.authors.bulkPost({
|
|
697
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:632: const bookResult = await api.scopes.books.bulkPost({
|
|
698
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:657: const relationshipResult = await api.scopes.book_authors.bulkPost({
|
|
699
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:676: errors: bookResult.errors || []
|
|
700
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:719:- **Three Core Operations**: bulkPost, bulkPatch, and bulkDelete
|
|
701
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:723:- **Full JSON:API Support**: Maintains compatibility with standard format
|
|
702
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:724:- **HTTP Endpoints**: RESTful API for bulk operations
|
|
703
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:726:Use bulk operations when you need to:
|
|
704
|
+
docs/GUIDE/GUIDE_X_Bulk_Operations.md:732:Remember to consider transaction modes, error handling, and performance implications when designing your bulk operation workflows.
|
|
705
|
+
docs/GUIDE/GUIDE_6_Positioning.md:11:5. [Position Grouping](#position-grouping)
|
|
706
|
+
docs/GUIDE/GUIDE_6_Positioning.md:23:- **Fractional indexing**: Insert items between any two positions without updating other records
|
|
707
|
+
docs/GUIDE/GUIDE_6_Positioning.md:26:- **Automatic positioning**: Items without explicit positions are placed appropriately
|
|
708
|
+
docs/GUIDE/GUIDE_6_Positioning.md:31:Traditional integer-based positioning requires updating multiple records when inserting:
|
|
709
|
+
docs/GUIDE/GUIDE_6_Positioning.md:35:UPDATE tasks SET position = position + 1 WHERE position >= 3;
|
|
710
|
+
docs/GUIDE/GUIDE_6_Positioning.md:36:INSERT INTO tasks (title, position) VALUES ('New Task', 3);
|
|
711
|
+
docs/GUIDE/GUIDE_6_Positioning.md:43:INSERT INTO tasks (title, position) VALUES ('New Task', 'a0m');
|
|
712
|
+
docs/GUIDE/GUIDE_6_Positioning.md:60:import { PositioningPlugin } from './plugins/core/rest-api-positioning-plugin.js';
|
|
713
|
+
docs/GUIDE/GUIDE_6_Positioning.md:70:// Add positioning capabilities
|
|
714
|
+
docs/GUIDE/GUIDE_6_Positioning.md:88: // 'position' field is automatically added
|
|
715
|
+
docs/GUIDE/GUIDE_6_Positioning.md:101:// Response includes position: "a0"
|
|
716
|
+
docs/GUIDE/GUIDE_6_Positioning.md:109:// Gets position: "a1"
|
|
717
|
+
docs/GUIDE/GUIDE_6_Positioning.md:117:// Gets position: "a0m" (between "a0" and "a1")
|
|
718
|
+
docs/GUIDE/GUIDE_6_Positioning.md:122:Lists are automatically sorted by position:
|
|
719
|
+
docs/GUIDE/GUIDE_6_Positioning.md:127:// Returns tasks in position order:
|
|
720
|
+
docs/GUIDE/GUIDE_6_Positioning.md:130: { "id": 1, "attributes": { "title": "First task", "position": "a0" } },
|
|
721
|
+
docs/GUIDE/GUIDE_6_Positioning.md:131: { "id": 3, "attributes": { "title": "Middle task", "position": "a0m" } },
|
|
722
|
+
docs/GUIDE/GUIDE_6_Positioning.md:132: { "id": 2, "attributes": { "title": "Last task", "position": "a1" } }
|
|
723
|
+
docs/GUIDE/GUIDE_6_Positioning.md:143: // Position field name (default: 'position')
|
|
724
|
+
docs/GUIDE/GUIDE_6_Positioning.md:146: // Grouping fields - create separate position sequences per group
|
|
725
|
+
docs/GUIDE/GUIDE_6_Positioning.md:149: // Resources to exclude from positioning
|
|
726
|
+
docs/GUIDE/GUIDE_6_Positioning.md:158: // Default position for new items without beforeId (default: 'last')
|
|
727
|
+
docs/GUIDE/GUIDE_6_Positioning.md:164: // Maximum position string length before rebalancing (default: 50)
|
|
728
|
+
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.
|
|
729
|
+
docs/GUIDE/GUIDE_6_Positioning.md:175:When you configure filters like `['status', 'projectId']`, the plugin creates independent position sequences for each unique combination:
|
|
730
|
+
docs/GUIDE/GUIDE_6_Positioning.md:177:- Project 1 + Status "todo" → positions: a0, a1, a2...
|
|
731
|
+
docs/GUIDE/GUIDE_6_Positioning.md:178:- Project 1 + Status "done" → positions: a0, a1, a2... (separate sequence!)
|
|
732
|
+
docs/GUIDE/GUIDE_6_Positioning.md:179:- Project 2 + Status "todo" → positions: a0, a1, a2... (another separate sequence!)
|
|
733
|
+
docs/GUIDE/GUIDE_6_Positioning.md:182:- The first item in each group gets position "a0"
|
|
734
|
+
docs/GUIDE/GUIDE_6_Positioning.md:183:- Items can have the same position value if they're in different groups
|
|
735
|
+
docs/GUIDE/GUIDE_6_Positioning.md:184:- Moving between groups requires explicit positioning with `beforeId`
|
|
736
|
+
docs/GUIDE/GUIDE_6_Positioning.md:203:Each board/column combination maintains its own positions:
|
|
737
|
+
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.
|
|
738
|
+
docs/GUIDE/GUIDE_6_Positioning.md:244:// Result: Card moves to in-progress but KEEPS its existing position (e.g., "a0m")
|
|
739
|
+
docs/GUIDE/GUIDE_6_Positioning.md:248:To move an item to a specific position in the new group, you MUST provide a `beforeId`:
|
|
740
|
+
docs/GUIDE/GUIDE_6_Positioning.md:251:// Move card and position it correctly
|
|
741
|
+
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.
|
|
742
|
+
docs/GUIDE/GUIDE_6_Positioning.md:274:// Each tenant has independent position sequences
|
|
743
|
+
docs/GUIDE/GUIDE_6_Positioning.md:275:// Tenant A's positions don't affect Tenant B's positions
|
|
744
|
+
docs/GUIDE/GUIDE_6_Positioning.md:291:// Add at specific position
|
|
745
|
+
docs/GUIDE/GUIDE_6_Positioning.md:295:// Note: Manual position values are ignored!
|
|
746
|
+
docs/GUIDE/GUIDE_6_Positioning.md:296:// The plugin always calculates positions to ensure consistency
|
|
747
|
+
docs/GUIDE/GUIDE_6_Positioning.md:298:{ "name": "New item", "position": "a0abc" } // 'position' will be recalculated!
|
|
748
|
+
docs/GUIDE/GUIDE_6_Positioning.md:312:// Update other fields without changing position
|
|
749
|
+
docs/GUIDE/GUIDE_6_Positioning.md:320:// Item moves to 'done' group but KEEPS its position value!
|
|
750
|
+
docs/GUIDE/GUIDE_6_Positioning.md:326:The plugin works with both JSON:API and simplified formats:
|
|
751
|
+
docs/GUIDE/GUIDE_6_Positioning.md:337:// JSON:API format
|
|
752
|
+
docs/GUIDE/GUIDE_6_Positioning.md:355:The plugin NEVER changes an item's position unless you explicitly request it with `beforeId`. This means:
|
|
753
|
+
docs/GUIDE/GUIDE_6_Positioning.md:357:- Changing filter fields (status, category, etc.) does NOT reposition the item
|
|
754
|
+
docs/GUIDE/GUIDE_6_Positioning.md:358:- The item keeps its position value when moving between groups
|
|
755
|
+
docs/GUIDE/GUIDE_6_Positioning.md:359:- You must provide `beforeId` to position items in their new group
|
|
756
|
+
docs/GUIDE/GUIDE_6_Positioning.md:363:Each combination of filter values creates a completely separate position space:
|
|
757
|
+
docs/GUIDE/GUIDE_6_Positioning.md:366:// These items can all have position "a0" because they're in different groups:
|
|
758
|
+
docs/GUIDE/GUIDE_6_Positioning.md:367:item1: { projectId: 1, status: 'todo', position: 'a0' }
|
|
759
|
+
docs/GUIDE/GUIDE_6_Positioning.md:368:item2: { projectId: 1, status: 'done', position: 'a0' } // Different status
|
|
760
|
+
docs/GUIDE/GUIDE_6_Positioning.md:369:item3: { projectId: 2, status: 'todo', position: 'a0' } // Different project
|
|
761
|
+
docs/GUIDE/GUIDE_6_Positioning.md:374:The `beforeId` only works within the same position group:
|
|
762
|
+
docs/GUIDE/GUIDE_6_Positioning.md:388:The plugin always calculates positions to ensure consistency:
|
|
763
|
+
docs/GUIDE/GUIDE_6_Positioning.md:391:// This position value will be ignored:
|
|
764
|
+
docs/GUIDE/GUIDE_6_Positioning.md:395: "position": "zzz" // Ignored! Plugin calculates actual position
|
|
765
|
+
docs/GUIDE/GUIDE_6_Positioning.md:401:Null values in filter fields create their own position group:
|
|
766
|
+
docs/GUIDE/GUIDE_6_Positioning.md:404:// These are THREE different position groups:
|
|
767
|
+
docs/GUIDE/GUIDE_6_Positioning.md:461:async function addToPlaylist(songId, position = null) {
|
|
768
|
+
docs/GUIDE/GUIDE_6_Positioning.md:466: beforeId: position // null = add to end
|
|
769
|
+
docs/GUIDE/GUIDE_6_Positioning.md:491:The plugin uses fractional indexing to generate position keys that can be infinitely subdivided:
|
|
770
|
+
docs/GUIDE/GUIDE_6_Positioning.md:493:1. **Initial positions**: First item gets "a0", second gets "a1", etc.
|
|
771
|
+
docs/GUIDE/GUIDE_6_Positioning.md:508:3. **Determines if positioning is needed**:
|
|
772
|
+
docs/GUIDE/GUIDE_6_Positioning.md:509: - For POST: Always calculates position
|
|
773
|
+
docs/GUIDE/GUIDE_6_Positioning.md:511: - Changing filter fields alone does NOT trigger repositioning
|
|
774
|
+
docs/GUIDE/GUIDE_6_Positioning.md:512:4. **Query database** for items in the same position group (based on filter fields)
|
|
775
|
+
docs/GUIDE/GUIDE_6_Positioning.md:513:5. **Calculate position**:
|
|
776
|
+
docs/GUIDE/GUIDE_6_Positioning.md:518:6. **Store position** in the position field
|
|
777
|
+
docs/GUIDE/GUIDE_6_Positioning.md:519:7. **Save record** with calculated position
|
|
778
|
+
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'`.
|
|
779
|
+
docs/GUIDE/GUIDE_6_Positioning.md:528:CREATE INDEX idx_tasks_positioning ON tasks(status, projectId, position);
|
|
780
|
+
docs/GUIDE/GUIDE_6_Positioning.md:533:- Finding specific positions for insertion
|
|
781
|
+
docs/GUIDE/GUIDE_6_Positioning.md:540:If you have existing integer positions, you can migrate gradually:
|
|
782
|
+
docs/GUIDE/GUIDE_6_Positioning.md:543:// 1. Add the plugin (it works alongside existing positions)
|
|
783
|
+
docs/GUIDE/GUIDE_6_Positioning.md:548:// 2. New items will get fractional positions
|
|
784
|
+
docs/GUIDE/GUIDE_6_Positioning.md:549:// 3. Existing integer positions still work (treated as strings)
|
|
785
|
+
docs/GUIDE/GUIDE_6_Positioning.md:570:The plugin automatically adds the position field to schemas:
|
|
786
|
+
docs/GUIDE/GUIDE_6_Positioning.md:581:// 'position' field is automatically added to the schema
|
|
787
|
+
docs/GUIDE/GUIDE_6_Positioning.md:591:-- For ungrouped positioning
|
|
788
|
+
docs/GUIDE/GUIDE_6_Positioning.md:592:CREATE INDEX ON items(position);
|
|
789
|
+
docs/GUIDE/GUIDE_6_Positioning.md:594:-- For grouped positioning
|
|
790
|
+
docs/GUIDE/GUIDE_6_Positioning.md:595:CREATE INDEX ON items(status, projectId, position);
|
|
791
|
+
docs/GUIDE/GUIDE_6_Positioning.md:601:- **Inserting items**: O(log n) to find position + O(1) to insert
|
|
792
|
+
docs/GUIDE/GUIDE_6_Positioning.md:602:- **Moving items**: O(log n) to find positions + O(1) to update
|
|
793
|
+
docs/GUIDE/GUIDE_6_Positioning.md:608:- Starting positions: "a0", "a1" (2 characters)
|
|
794
|
+
docs/GUIDE/GUIDE_6_Positioning.md:616:2. **Avoid manual positions** unless migrating data
|
|
795
|
+
docs/GUIDE/GUIDE_6_Positioning.md:617:3. **Let the plugin handle positioning** for consistency
|
|
796
|
+
docs/GUIDE/GUIDE_6_Positioning.md:618:4. **Monitor position lengths** in high-activity systems
|
|
797
|
+
docs/GUIDE/GUIDE_6_Positioning.md:626:- Verify the position field contains valid fractional keys
|
|
798
|
+
docs/GUIDE/GUIDE_6_Positioning.md:627:- Ensure you're querying within the correct position group
|
|
799
|
+
docs/GUIDE/GUIDE_6_Positioning.md:628:- Remember: position values are strings, sorted lexicographically ("a10" comes before "a2"!)
|
|
800
|
+
docs/GUIDE/GUIDE_6_Positioning.md:631:- The position field must exist in your schema
|
|
801
|
+
docs/GUIDE/GUIDE_6_Positioning.md:633:- The plugin will throw an error if the field is missing
|
|
802
|
+
docs/GUIDE/GUIDE_6_Positioning.md:634:- Look for plugin initialization errors in logs
|
|
803
|
+
docs/GUIDE/GUIDE_6_Positioning.md:637:- Ensure the target item exists in the same position group
|
|
804
|
+
docs/GUIDE/GUIDE_6_Positioning.md:640:- Note: You cannot position relative to items in different groups
|
|
805
|
+
docs/GUIDE/GUIDE_6_Positioning.md:642:**Items appear in wrong position after moving between groups**
|
|
806
|
+
docs/GUIDE/GUIDE_6_Positioning.md:643:- This is expected behavior! Items keep their position when filter values change
|
|
807
|
+
docs/GUIDE/GUIDE_6_Positioning.md:649:- Monitor position string lengths
|
|
808
|
+
docs/GUIDE/GUIDE_6_Positioning.md:654:Enable debug logging to see position calculations:
|
|
809
|
+
docs/GUIDE/GUIDE_6_Positioning.md:665:For advanced use cases, you can work directly with positions:
|
|
810
|
+
docs/GUIDE/GUIDE_6_Positioning.md:670:// Generate a position between two items
|
|
811
|
+
docs/GUIDE/GUIDE_6_Positioning.md:673:// Generate first position
|
|
812
|
+
docs/GUIDE/GUIDE_6_Positioning.md:676:// Generate last position after 'z5'
|
|
813
|
+
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.
|
|
814
|
+
docs/GUIDE/GUIDE_6_Positioning.md:688:- **Grouped positioning** - Separate sequences per category
|
|
815
|
+
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.
|
|
816
|
+
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:
|
|
817
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:35:npm install socket.io @socket.io/redis-adapter redis
|
|
818
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:44:import { SocketIOPlugin } from 'json-rest-api/plugins/socketio';
|
|
819
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:59:const server = app.listen(3000).on('error', (err) => {
|
|
820
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:60: console.error('Failed to start server:', err);
|
|
821
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:66: path: '/socket.io', // Socket.IO path (default: '/socket.io')
|
|
822
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:67: cors: { // CORS configuration
|
|
823
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:84:| `path` | string | '/socket.io' | URL path for Socket.IO endpoint |
|
|
824
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:85:| `cors` | object | `{ origin: '*', methods: ['GET', 'POST'] }` | CORS configuration |
|
|
825
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:117:socket.emit('subscription.update', {
|
|
826
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:166:socket.emit('subscribe', {
|
|
827
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:180:// In a transaction
|
|
828
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:181:const trx = await knex.transaction();
|
|
829
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:186: transaction: trx
|
|
830
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:193:} catch (error) {
|
|
831
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:194: await trx.rollback();
|
|
832
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:225:socket.emit('subscribe', {
|
|
833
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:263: return title.includes(search) ||
|
|
834
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:264: desc.includes(search) ||
|
|
835
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:265: tags.includes(search);
|
|
836
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:302: const { user_id, include_private } = value;
|
|
837
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:304: if (!include_private) {
|
|
838
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:311: const { user_id, include_private } = value;
|
|
839
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:314: return include_private && record.shared_with?.includes(user_id);
|
|
840
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:326:socket.emit('subscribe', {
|
|
841
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:334:// Returns error:
|
|
842
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:336: error: {
|
|
843
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:357:const socket = io('http://localhost:3000', {
|
|
844
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:364:socket.on('connect_error', (error) => {
|
|
845
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:365: console.error('Authentication failed:', error.message);
|
|
846
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:381: return auth.roles?.includes('admin');
|
|
847
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:419:// Now ALL subscriptions automatically include workspace filter
|
|
848
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:420:socket.emit('subscribe', {
|
|
849
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:443: if (!auth.roles?.includes('admin')) {
|
|
850
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:495:Each socket can have multiple subscriptions with different filters:
|
|
851
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:499:const sub1 = await subscribeToResource(socket, {
|
|
852
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:504:const sub2 = await subscribeToResource(socket, {
|
|
853
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:509:const sub3 = await subscribeToResource(socket, {
|
|
854
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:515:socket.emit('unsubscribe', {
|
|
855
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:522:While notifications don't include data, subscriptions can store include/fields preferences:
|
|
856
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:525:// Subscribe with preferred includes and fields
|
|
857
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:526:socket.emit('subscribe', {
|
|
858
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:529: include: ['author', 'comments.user'],
|
|
859
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:538:socket.on('subscription.update', async (notification) => {
|
|
860
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:541: 'include=author,comments.user&' +
|
|
861
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:557:socket.on('subscription.created', (response) => {
|
|
862
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:562:socket.on('connect', async () => {
|
|
863
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:565: socket,
|
|
864
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:571: console.error(`Failed to restore ${failed.length} subscriptions`);
|
|
865
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:576:async function restoreSubscriptions(socket, subscriptions) {
|
|
866
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:578: socket.emit('restore-subscriptions',
|
|
867
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:588:The plugin provides detailed error information:
|
|
868
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:591:socket.on('subscription.error', (error) => {
|
|
869
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:592: switch (error.code) {
|
|
870
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:594: console.error(`Resource type '${error.resource}' doesn't exist`);
|
|
871
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:598: console.error('You lack permission to subscribe to this resource');
|
|
872
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:602: console.error('Filter validation failed:', error.details);
|
|
873
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:606: console.error(`Filter requires 'filterRecord' for WebSocket support`);
|
|
874
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:610: console.error('Resource does not have searchSchema defined');
|
|
875
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:621:import { io } from 'socket.io-client';
|
|
876
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:624:const socket = io('http://localhost:3000', {
|
|
877
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:631:socket.on('connect', () => {
|
|
878
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:635:socket.on('connect_error', (error) => {
|
|
879
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:636: console.error('Connection failed:', error.message);
|
|
880
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:639:socket.on('disconnect', (reason) => {
|
|
881
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:648:async function subscribeToResource(socket, options) {
|
|
882
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:650: socket.emit('subscribe', options, (response) => {
|
|
883
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:651: if (response.error) {
|
|
884
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:652: reject(response.error);
|
|
885
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:662: const subscription = await subscribeToResource(socket, {
|
|
886
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:671:} catch (error) {
|
|
887
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:672: console.error('Subscription failed:', error);
|
|
888
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:680:socket.on('subscription.update', async (notification) => {
|
|
889
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:739: this.socket = io(apiUrl, {
|
|
890
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:747: this.socket.on('connect', () => {
|
|
891
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:752: this.socket.on('subscription.update', (notification) => {
|
|
892
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:756: this.socket.on('subscription.created', (response) => {
|
|
893
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:763: this.socket.emit('subscribe', {
|
|
894
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:768: if (response.error) {
|
|
895
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:769: reject(response.error);
|
|
896
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:829: this.socket.emit('restore-subscriptions',
|
|
897
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:832: if (response.error) {
|
|
898
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:833: console.error('Failed to restore subscriptions:', response.error);
|
|
899
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:860: include: 'author,comments'
|
|
900
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:871:Each socket is limited to 100 subscriptions to prevent memory exhaustion:
|
|
901
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:875:socket.emit('subscribe', { resource: 'posts' }, (response) => {
|
|
902
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:876: if (response.error?.code === 'SUBSCRIPTION_LIMIT_EXCEEDED') {
|
|
903
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:877: console.error('Too many active subscriptions');
|
|
904
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:905:socket.on('subscription.update', (notification) => {
|
|
905
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:938:- Are you in a transaction that hasn't committed?
|
|
906
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:940:**2. "UNSUPPORTED_FILTER" errors**
|
|
907
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:945:// ❌ This will error for WebSocket
|
|
908
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:989:localStorage.debug = 'socket.io-client:*';
|
|
909
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:997: const socket = io('http://localhost:3000', {
|
|
910
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1002: socket.on('connect', async () => {
|
|
911
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1006: socket.emit('subscribe', {
|
|
912
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1010: if (response.error) {
|
|
913
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1011: console.error('✗ Subscription failed:', response.error);
|
|
914
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1012: reject(response.error);
|
|
915
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1017: socket.once('subscription.update', (notification) => {
|
|
916
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1019: socket.close();
|
|
917
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1029: socket.on('connect_error', (error) => {
|
|
918
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1030: console.error('✗ Connection failed:', error.message);
|
|
919
|
+
docs/GUIDE/GUIDE_X_SocketIO.md:1031: reject(error);
|
|
920
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:349: // - context: request context with knex, transaction, etc.
|
|
921
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:361:4. **Sparse Fieldsets**: Work seamlessly with JSON:API sparse fieldsets
|
|
922
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:533: // Note: cost, supplierApiKey not included
|
|
923
|
+
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.
|
|
924
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:682:// Note: 'cost' is not included (normallyHidden)
|
|
925
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:710:When you request a computed field via sparse fieldsets, the system automatically fetches its dependencies:
|
|
926
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:714:const sparseProduct = await api.resources.products.get({
|
|
927
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:720:console.log('Product with sparse fields:', sparseProduct);
|
|
928
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:728:console.log('Product with sparse fields includig cost:', productWithCost);
|
|
929
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:734:Product with sparse fields: {
|
|
930
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:741:Product with sparse fields includig cost: {
|
|
931
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:752:Computed fields work seamlessly with included resources:
|
|
932
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:759: include: ['reviews']
|
|
933
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:764:// Use sparse fieldsets on included resources
|
|
934
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:768: include: ['reviews'],
|
|
935
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:833:Computed fields handle errors gracefully:
|
|
936
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:848:console.log('Fetched review (with error in helpfulness score):', fetchedReviewWithError
|
|
937
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:854:Fetched review (with error in helpfulness score): {
|
|
938
|
+
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.
|
|
939
|
+
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.
|
|
940
|
+
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.
|
|
941
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:998: queryParams: { include: ['posts'] }
|
|
942
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:1069: queryParams: { include: ['posts'] }
|
|
943
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:1099: include: ['author'],
|
|
944
|
+
docs/GUIDE/GUIDE_3_Field_Transformations.md:1105:// Both posts and included authors have getters applied
|
|
945
|
+
docs/mini-guides/file-uploads-complete.md:110:}).on('error', (err) => {
|
|
946
|
+
docs/mini-guides/file-uploads-complete.md:111: console.error('Failed to start server:', err);
|
|
947
|
+
docs/mini-guides/file-uploads-complete.md:132:The example includes a simple HTML form for testing file uploads without needing external tools.
|
|
948
|
+
docs/GUIDE/GUIDE_X_File_Uploads.md:106:Storage adapters handle where and how files are saved. The library includes two built-in adapters.
|
|
949
|
+
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.
|
|
950
|
+
docs/GUIDE/GUIDE_X_File_Uploads.md:321: return req.headers['content-type']?.includes('multipart/form-data');
|
|
951
|
+
docs/GUIDE/GUIDE_X_File_Uploads.md:393:When validation fails, you'll get appropriate error responses:
|
|
952
|
+
docs/GUIDE/GUIDE_X_File_Uploads.md:397: "errors": [{
|
|
953
|
+
docs/GUIDE/GUIDE_X_File_Uploads.md:460:app.listen(3000).on('error', (err) => {
|
|
954
|
+
docs/GUIDE/GUIDE_X_File_Uploads.md:461: console.error('Failed to start server:', err);
|
|
955
|
+
docs/GUIDE/GUIDE_X_File_Uploads.md:613:#### 4. "File too large" errors
|
|
956
|
+
docs/GUIDE/GUIDE_X_File_Uploads.md:650:Use the included example to test your setup:
|
|
957
|
+
docs/GUIDE/GUIDE_X_File_Uploads.md:716:7. **Serve files with proper headers** - Use Content-Disposition for downloads
|
|
958
|
+
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.
|
|
959
|
+
docs/GUIDE/GUIDE_X_URL_Management.md:93: if (host?.includes('tenant-a')) {
|
|
960
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:9:3. [JSON:API Response Format](#jsonapi-response-format)
|
|
961
|
+
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.
|
|
962
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:35:4. Your helper returns data in JSON:API format
|
|
963
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:51: // - context.schemaInfo.idProperty: string - Primary key field name
|
|
964
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:52: // - context.db: any - Database connection (if using transactions)
|
|
965
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:59:Retrieve a single resource with full JSON:API features.
|
|
966
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:66: // - context.queryParams.include: string[] - Related resources to include
|
|
967
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:70: // Returns: JSON:API document with single resource
|
|
968
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:73: // included: [...] // if includes requested
|
|
969
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:85: // Returns: JSON:API resource object or null
|
|
970
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:104: // - context.queryParams.include: string[] - Related resources
|
|
971
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:107: // Returns: JSON:API document with resource array
|
|
972
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:110: // included: [...],
|
|
973
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:123: // - context.inputRecord: JSON:API document with new resource
|
|
974
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:137: // - context.inputRecord: JSON:API document with full resource
|
|
975
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:140: // Returns: void (throws error if not found when updating)
|
|
976
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:151: // - context.inputRecord: JSON:API document with partial updates
|
|
977
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:153: // Returns: void (throws error if not found)
|
|
978
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:165: // Returns: { success: true } (throws error if not found)
|
|
979
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:169:## JSON:API Response Format
|
|
980
|
+
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:
|
|
981
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:227: included: [
|
|
982
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:284: const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
|
|
983
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:292: // Build JSON:API response
|
|
984
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:322: // Handle includes
|
|
985
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:323: const included = [];
|
|
986
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:324: if (context.queryParams.include?.length > 0) {
|
|
987
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:325: for (const includePath of context.queryParams.include) {
|
|
988
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:326: const relName = includePath.split('.')[0];
|
|
989
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:338: included.push({
|
|
990
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:349: // Apply sparse fieldsets
|
|
991
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:353: Object.entries(data.attributes).filter(([key]) => fields.includes(key))
|
|
992
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:359: ...(included.length > 0 && { included })
|
|
993
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:377: // Only include belongsTo relationships for minimal
|
|
994
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:405: if (field.includes('.')) {
|
|
995
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:475: // Handle includes
|
|
996
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:476: const included = [];
|
|
997
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:477: if (context.queryParams.include?.length > 0) {
|
|
998
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:478: const includedIds = new Set();
|
|
999
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:481: for (const includePath of context.queryParams.include) {
|
|
1000
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:482: const relName = includePath.split('.')[0];
|
|
1001
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:492: if (!includedIds.has(key)) {
|
|
1002
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:493: includedIds.add(key);
|
|
1003
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:497: included.push({
|
|
1004
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:529: ...(included.length > 0 && { included }),
|
|
1005
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:576: const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
|
|
1006
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:615: const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
|
|
1007
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:654: const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
|
|
1008
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:702: const error = await response.json().catch(() => ({ errors: [{ title: 'Request failed' }] }));
|
|
1009
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:703: const { RestApiResourceError } = await import('../../lib/rest-api-errors.js');
|
|
1010
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:705: error.errors?.[0]?.title || 'Remote API error',
|
|
1011
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:707: subtype: response.status === 404 ? 'not_found' : 'remote_error',
|
|
1012
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:709: errors: error.errors
|
|
1013
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:722: } catch (error) {
|
|
1014
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:723: if (error.statusCode === 404) return false;
|
|
1015
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:724: throw error;
|
|
1016
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:732: // Add include parameter
|
|
1017
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:733: if (context.queryParams.include?.length > 0) {
|
|
1018
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:734: queryParams.set('include', context.queryParams.include.join(','));
|
|
1019
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:737: // Add sparse fieldsets
|
|
1020
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:779: // Add includes
|
|
1021
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:780: if (context.queryParams.include?.length > 0) {
|
|
1022
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:781: queryParams.set('include', context.queryParams.include.join(','));
|
|
1023
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:784: // Add sparse fieldsets
|
|
1024
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:799: included: response.included,
|
|
1025
|
+
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:
|
|
1026
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:853: // context.db is automatically the transaction if one is active,
|
|
1027
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:854: // or the base connection if not in a transaction
|
|
1028
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:857: // For storage that supports transactions:
|
|
1029
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:858: if (context.transaction) {
|
|
1030
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:859: // We're in a transaction - ensure all operations use it
|
|
1031
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:862: // No transaction - use regular connection
|
|
1032
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:881: // Filter attributes to only include requested fields
|
|
1033
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:883: Object.entries(data.attributes).filter(([key]) => fields.includes(key))
|
|
1034
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:887: // The REST API plugin will handle nested field filtering for included resources
|
|
1035
|
+
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:
|
|
1036
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:897:// Always include these fields even if not explicitly requested
|
|
1037
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:911:Always use the proper error classes:
|
|
1038
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:914:import { RestApiResourceError, RestApiValidationError } from '../../lib/rest-api-errors.js';
|
|
1039
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:916:// For not found errors
|
|
1040
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:923:// For validation errors
|
|
1041
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1000:JSON:API requires IDs to be strings, but your storage might use numbers:
|
|
1042
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1012:Relationships must follow the JSON:API format exactly:
|
|
1043
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1042:Errors should include proper subtypes:
|
|
1044
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1047:'validation_error' - Input validation failed
|
|
1045
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1054:Always include pagination metadata for queries:
|
|
1046
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1067:When returning included resources, avoid duplicates:
|
|
1047
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1070:const includedMap = new Map();
|
|
1048
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1072:includedMap.set(`${type}:${id}`, resource);
|
|
1049
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1073:const included = Array.from(includedMap.values());
|
|
1050
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1080:- Use bulk operations where possible
|
|
1051
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1090:- idProperty: Primary key field name (might not be 'id')
|
|
1052
|
+
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.
|
|
1053
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1102:- Always return proper JSON:API formatted responses
|
|
1054
|
+
docs/GUIDE/GUIDE_5_Non-Db_Resources.md:1103:- Use the provided error classes for consistency
|
|
1055
|
+
docs/GUIDE/GUIDE_X_Multihome.md:22:- **Automatic Query Filtering**: Every database query automatically includes a WHERE clause for the tenant ID
|
|
1056
|
+
docs/GUIDE/GUIDE_X_Multihome.md:44:Every database query is automatically modified to include the tenant filter:
|
|
1057
|
+
docs/GUIDE/GUIDE_X_Multihome.md:51:SELECT * FROM posts WHERE status = 'published' AND multihome_id = 'mobily'
|
|
1058
|
+
docs/GUIDE/GUIDE_X_Multihome.md:64:{ "title": "My Post", "content": "...", "multihome_id": "mobily" }
|
|
1059
|
+
docs/GUIDE/GUIDE_X_Multihome.md:76:import { MultiHomePlugin } from './plugins/core/multihome-plugin.js';
|
|
1060
|
+
docs/GUIDE/GUIDE_X_Multihome.md:124:| `field` | string | `'multihome_id'` | The database field name for tenant ID |
|
|
1061
|
+
docs/GUIDE/GUIDE_X_Multihome.md:176: if (subdomain && !['www', 'api'].includes(subdomain)) {
|
|
1062
|
+
docs/GUIDE/GUIDE_X_Multihome.md:296:// Result: 404 Not Found (not a security error message)
|
|
1063
|
+
docs/GUIDE/GUIDE_X_Multihome.md:320:log.error('Multihome security violation attempt', {
|
|
1064
|
+
docs/GUIDE/GUIDE_X_Multihome.md:335:// Error: No multihome context available - cannot execute query
|
|
1065
|
+
docs/GUIDE/GUIDE_X_Multihome.md:385:const config = api.multihome.getConfig();
|
|
1066
|
+
docs/GUIDE/GUIDE_X_Multihome.md:398:The plugin sets these variables accessible via `api.vars.multihome`:
|
|
1067
|
+
docs/GUIDE/GUIDE_X_Multihome.md:425:#### 1. "No multihome context available"
|
|
1068
|
+
docs/GUIDE/GUIDE_X_Multihome.md:447:**Solution**: Don't include tenant_id in your requests - it's set automatically.
|
|
1069
|
+
docs/GUIDE/GUIDE_X_Multihome.md:457: console.log('Current tenant:', context.auth?.multihome_id);
|
|
1070
|
+
docs/GUIDE/GUIDE_X_Multihome.md:481:Always include the tenant field in your schemas:
|
|
1071
|
+
docs/GUIDE/GUIDE_X_Multihome.md:489: tenant_id: { type: 'string', required: true } // Always include
|
|
1072
|
+
docs/GUIDE/GUIDE_X_Multihome.md:576:import { MultiHomePlugin } from './plugins/core/multihome-plugin.js';
|
|
1073
|
+
docs/GUIDE/GUIDE_X_Multihome.md:617: if (subdomain && !['www', 'api', 'app'].includes(subdomain)) {
|
|
1074
|
+
docs/GUIDE/GUIDE_X_Multihome.md:653:}).on('error', (err) => {
|
|
1075
|
+
docs/GUIDE/GUIDE_X_Multihome.md:654: console.error('Failed to start server:', err);
|