json-rest-api 1.0.14 → 1.0.15

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.
Files changed (118) hide show
  1. package/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +11 -2
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +1 -1
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +8 -8
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,34 +1,34 @@
1
- const STORAGE_MODE = process.env.JSON_REST_API_STORAGE === 'anyapi' ? 'anyapi' : 'knex';
2
- const DEFAULT_TENANT = 'default';
1
+ const STORAGE_MODE = process.env.JSON_REST_API_STORAGE === 'anyapi' ? 'anyapi' : 'knex'
2
+ const DEFAULT_TENANT = 'default'
3
3
 
4
- const tableMetadata = new Map();
5
- let resetMetadataFn = null;
6
- const linkMappings = new Map();
7
- let currentTenant = DEFAULT_TENANT;
4
+ const tableMetadata = new Map()
5
+ let resetMetadataFn = null
6
+ const linkMappings = new Map()
7
+ let currentTenant = DEFAULT_TENANT
8
8
 
9
9
  export const storageMode = {
10
10
  mode: STORAGE_MODE,
11
- isAnyApi() {
12
- return STORAGE_MODE === 'anyapi';
11
+ isAnyApi () {
12
+ return STORAGE_MODE === 'anyapi'
13
13
  },
14
14
  defaultTenant: DEFAULT_TENANT,
15
- get currentTenant() {
16
- return currentTenant;
15
+ get currentTenant () {
16
+ return currentTenant
17
17
  },
18
- registerReset(fn) {
19
- resetMetadataFn = fn;
18
+ registerReset (fn) {
19
+ resetMetadataFn = fn
20
20
  },
21
- setCurrentTenant(tenantId) {
22
- currentTenant = tenantId || DEFAULT_TENANT;
21
+ setCurrentTenant (tenantId) {
22
+ currentTenant = tenantId || DEFAULT_TENANT
23
23
  },
24
- registerTable(tableName, resourceName, tenantId = currentTenant || DEFAULT_TENANT) {
25
- if (!tableName || !resourceName) return;
24
+ registerTable (tableName, resourceName, tenantId = currentTenant || DEFAULT_TENANT) {
25
+ if (!tableName || !resourceName) return
26
26
  tableMetadata.set(tableName, {
27
27
  resource: resourceName,
28
28
  tenantId: tenantId || DEFAULT_TENANT,
29
- });
29
+ })
30
30
  },
31
- registerLink(
31
+ registerLink (
32
32
  tableName,
33
33
  ownerResource,
34
34
  relationshipName,
@@ -36,30 +36,30 @@ export const storageMode = {
36
36
  inverseRelationshipKey,
37
37
  tenantId = currentTenant || DEFAULT_TENANT
38
38
  ) {
39
- if (!tableName || !ownerResource || !relationshipName) return;
39
+ if (!tableName || !ownerResource || !relationshipName) return
40
40
  linkMappings.set(tableName, {
41
41
  ownerResource,
42
42
  relationshipName,
43
43
  relationshipKey,
44
44
  inverseRelationshipKey,
45
45
  tenantId: tenantId || DEFAULT_TENANT,
46
- });
46
+ })
47
47
  },
48
- getResourceForTable(tableName) {
49
- return tableMetadata.get(tableName)?.resource;
48
+ getResourceForTable (tableName) {
49
+ return tableMetadata.get(tableName)?.resource
50
50
  },
51
- getTenantForTable(tableName) {
52
- return tableMetadata.get(tableName)?.tenantId || DEFAULT_TENANT;
51
+ getTenantForTable (tableName) {
52
+ return tableMetadata.get(tableName)?.tenantId || DEFAULT_TENANT
53
53
  },
54
- getLinkInfo(tableName) {
55
- return linkMappings.get(tableName);
54
+ getLinkInfo (tableName) {
55
+ return linkMappings.get(tableName)
56
56
  },
57
- clearRegistry() {
58
- tableMetadata.clear();
59
- linkMappings.clear();
60
- currentTenant = DEFAULT_TENANT;
57
+ clearRegistry () {
58
+ tableMetadata.clear()
59
+ linkMappings.clear()
60
+ currentTenant = DEFAULT_TENANT
61
61
  if (resetMetadataFn) {
62
- resetMetadataFn();
62
+ resetMetadataFn()
63
63
  }
64
64
  },
65
- };
65
+ }
@@ -1,101 +1,101 @@
1
- import assert from 'node:assert/strict';
2
- import { storageMode } from './storage-mode.js';
1
+ import assert from 'node:assert/strict'
2
+ import { storageMode } from './storage-mode.js'
3
3
 
4
4
  /**
5
5
  * Validates that a response follows JSON:API structure
6
6
  */
7
- export function validateJsonApiStructure(response, isCollection = false) {
8
- assert(response, 'Response should exist');
9
-
7
+ export function validateJsonApiStructure (response, isCollection = false) {
8
+ assert(response, 'Response should exist')
9
+
10
10
  if (isCollection) {
11
- assert(Array.isArray(response.data), 'Response data should be an array for collections');
11
+ assert(Array.isArray(response.data), 'Response data should be an array for collections')
12
12
  } else {
13
- assert(response.data && typeof response.data === 'object', 'Response data should be an object');
14
- assert(!Array.isArray(response.data), 'Response data should not be an array for single resources');
13
+ assert(response.data && typeof response.data === 'object', 'Response data should be an object')
14
+ assert(!Array.isArray(response.data), 'Response data should not be an array for single resources')
15
15
  }
16
-
16
+
17
17
  // Validate each resource object
18
- const resources = isCollection ? response.data : [response.data];
18
+ const resources = isCollection ? response.data : [response.data]
19
19
  resources.forEach(resource => {
20
- assert(resource.type, 'Resource should have a type');
21
- assert(resource.id, 'Resource should have an id');
22
- assert(resource.attributes && typeof resource.attributes === 'object', 'Resource should have attributes object');
23
-
20
+ assert(resource.type, 'Resource should have a type')
21
+ assert(resource.id, 'Resource should have an id')
22
+ assert(resource.attributes && typeof resource.attributes === 'object', 'Resource should have attributes object')
23
+
24
24
  if (resource.relationships) {
25
- assert(typeof resource.relationships === 'object', 'Relationships should be an object');
25
+ assert(typeof resource.relationships === 'object', 'Relationships should be an object')
26
26
  Object.values(resource.relationships).forEach(rel => {
27
- assert(rel.data !== undefined || rel.links !== undefined || rel.meta !== undefined,
28
- 'Relationship should have data, links, or meta');
29
- });
27
+ assert(rel.data !== undefined || rel.links !== undefined || rel.meta !== undefined,
28
+ 'Relationship should have data, links, or meta')
29
+ })
30
30
  }
31
- });
32
-
31
+ })
32
+
33
33
  if (response.included) {
34
- assert(Array.isArray(response.included), 'Included should be an array');
34
+ assert(Array.isArray(response.included), 'Included should be an array')
35
35
  response.included.forEach(resource => {
36
- assert(resource.type && resource.id, 'Included resources should have type and id');
37
- });
36
+ assert(resource.type && resource.id, 'Included resources should have type and id')
37
+ })
38
38
  }
39
39
  }
40
40
 
41
41
  /**
42
42
  * Creates a resource identifier for JSON:API relationships
43
43
  */
44
- export function resourceIdentifier(type, id) {
45
- return { type, id: String(id) };
44
+ export function resourceIdentifier (type, id) {
45
+ return { type, id: String(id) }
46
46
  }
47
47
 
48
48
  /**
49
49
  * Cleans all records from the given tables
50
50
  */
51
- export async function cleanTables(knex, tableNames) {
51
+ export async function cleanTables (knex, tableNames) {
52
52
  if (storageMode.isAnyApi()) {
53
- const resourceGroups = new Map();
53
+ const resourceGroups = new Map()
54
54
 
55
55
  for (const table of tableNames) {
56
- const resource = storageMode.getResourceForTable(table);
57
- if (!resource) continue;
58
- const tenantId = storageMode.getTenantForTable(table);
59
- const key = `${tenantId}::${resource}`;
56
+ const resource = storageMode.getResourceForTable(table)
57
+ if (!resource) continue
58
+ const tenantId = storageMode.getTenantForTable(table)
59
+ const key = `${tenantId}::${resource}`
60
60
  if (!resourceGroups.has(key)) {
61
- resourceGroups.set(key, { resource, tenantId });
61
+ resourceGroups.set(key, { resource, tenantId })
62
62
  }
63
63
  }
64
64
 
65
65
  for (const { resource, tenantId } of resourceGroups.values()) {
66
66
  await knex('any_records')
67
67
  .where({ tenant_id: tenantId, resource })
68
- .delete();
68
+ .delete()
69
69
  }
70
70
 
71
71
  for (const table of tableNames) {
72
- const linkInfo = storageMode.getLinkInfo(table);
72
+ const linkInfo = storageMode.getLinkInfo(table)
73
73
  if (linkInfo) {
74
- const tenantId = linkInfo.tenantId || storageMode.defaultTenant;
75
- const relationshipKey = linkInfo.relationshipKey
76
- || `${tenantId}:${linkInfo.ownerResource}:${linkInfo.relationshipName}`;
74
+ const tenantId = linkInfo.tenantId || storageMode.defaultTenant
75
+ const relationshipKey = linkInfo.relationshipKey ||
76
+ `${tenantId}:${linkInfo.ownerResource}:${linkInfo.relationshipName}`
77
77
  await knex('any_links')
78
78
  .where({ tenant_id: tenantId })
79
79
  .andWhere((builder) => {
80
80
  builder.where('relationship', relationshipKey)
81
- .orWhere('inverse_relationship', relationshipKey);
81
+ .orWhere('inverse_relationship', relationshipKey)
82
82
  if (linkInfo.inverseRelationshipKey) {
83
83
  builder.orWhere('relationship', linkInfo.inverseRelationshipKey)
84
- .orWhere('inverse_relationship', linkInfo.inverseRelationshipKey);
84
+ .orWhere('inverse_relationship', linkInfo.inverseRelationshipKey)
85
85
  }
86
86
  })
87
87
  .delete()
88
- .catch(() => {});
88
+ .catch(() => {})
89
89
  }
90
90
  try {
91
- await knex(table).delete();
91
+ await knex(table).delete()
92
92
  } catch (error) {
93
93
  // Ignore missing tables in anyapi mode
94
94
  }
95
95
  }
96
96
  } else {
97
97
  for (const table of tableNames) {
98
- await knex(table).delete();
98
+ await knex(table).delete()
99
99
  }
100
100
  }
101
101
  }
@@ -103,23 +103,23 @@ export async function cleanTables(knex, tableNames) {
103
103
  /**
104
104
  * Counts records in a table
105
105
  */
106
- export async function countRecords(knex, tableName) {
106
+ export async function countRecords (knex, tableName) {
107
107
  if (storageMode.isAnyApi()) {
108
- const resource = storageMode.getResourceForTable(tableName);
109
- const linkInfo = storageMode.getLinkInfo(tableName);
110
- const tenantId = storageMode.getTenantForTable(tableName);
108
+ const resource = storageMode.getResourceForTable(tableName)
109
+ const linkInfo = storageMode.getLinkInfo(tableName)
110
+ const tenantId = storageMode.getTenantForTable(tableName)
111
111
  if (linkInfo) {
112
- const relationshipKey = linkInfo.relationshipKey
113
- || `${tenantId}:${linkInfo.ownerResource}:${linkInfo.relationshipName}`;
112
+ const relationshipKey = linkInfo.relationshipKey ||
113
+ `${tenantId}:${linkInfo.ownerResource}:${linkInfo.relationshipName}`
114
114
  const result = await knex('any_links')
115
115
  .where({ tenant_id: tenantId })
116
116
  .andWhere((builder) => {
117
117
  builder.where('relationship', relationshipKey)
118
- .orWhere('inverse_relationship', relationshipKey);
118
+ .orWhere('inverse_relationship', relationshipKey)
119
119
  })
120
120
  .count('* as count')
121
- .first();
122
- return parseInt(result?.count ?? 0, 10);
121
+ .first()
122
+ return parseInt(result?.count ?? 0, 10)
123
123
  }
124
124
  if (resource) {
125
125
  const result = await knex('any_records')
@@ -128,76 +128,76 @@ export async function countRecords(knex, tableName) {
128
128
  resource,
129
129
  })
130
130
  .count('* as count')
131
- .first();
132
- return parseInt(result.count);
131
+ .first()
132
+ return parseInt(result.count)
133
133
  }
134
134
  try {
135
- const result = await knex(tableName).count('* as count').first();
136
- return parseInt(result?.count || 0);
135
+ const result = await knex(tableName).count('* as count').first()
136
+ return parseInt(result?.count || 0)
137
137
  } catch (error) {
138
- return 0;
138
+ return 0
139
139
  }
140
140
  }
141
141
 
142
- const result = await knex(tableName).count('* as count').first();
143
- return parseInt(result.count);
142
+ const result = await knex(tableName).count('* as count').first()
143
+ return parseInt(result.count)
144
144
  }
145
145
 
146
146
  /**
147
147
  * Helper to create a JSON:API document for POST/PUT/PATCH
148
148
  */
149
- export function createJsonApiDocument(type, attributes, relationships = {}) {
149
+ export function createJsonApiDocument (type, attributes, relationships = {}) {
150
150
  const doc = {
151
151
  data: {
152
152
  type,
153
153
  attributes
154
154
  }
155
- };
156
-
155
+ }
156
+
157
157
  if (Object.keys(relationships).length > 0) {
158
- doc.data.relationships = relationships;
158
+ doc.data.relationships = relationships
159
159
  }
160
-
161
- return doc;
160
+
161
+ return doc
162
162
  }
163
163
 
164
164
  /**
165
165
  * Helper to create a relationship object
166
166
  */
167
- export function createRelationship(resourceIdentifier) {
167
+ export function createRelationship (resourceIdentifier) {
168
168
  return {
169
169
  data: resourceIdentifier
170
- };
170
+ }
171
171
  }
172
172
 
173
173
  /**
174
174
  * Helper to create a to-many relationship object
175
175
  */
176
- export function createToManyRelationship(resourceIdentifiers) {
176
+ export function createToManyRelationship (resourceIdentifiers) {
177
177
  return {
178
178
  data: resourceIdentifiers
179
- };
179
+ }
180
180
  }
181
181
 
182
182
  /**
183
183
  * Asserts that a resource has specific attributes
184
184
  */
185
- export function assertResourceAttributes(resource, expectedAttributes) {
185
+ export function assertResourceAttributes (resource, expectedAttributes) {
186
186
  Object.entries(expectedAttributes).forEach(([key, value]) => {
187
- assert.deepEqual(resource.attributes[key], value,
188
- `Attribute ${key} should equal ${JSON.stringify(value)}`);
189
- });
187
+ assert.deepEqual(resource.attributes[key], value,
188
+ `Attribute ${key} should equal ${JSON.stringify(value)}`)
189
+ })
190
190
  }
191
191
 
192
192
  /**
193
193
  * Asserts that a resource has a specific relationship
194
194
  */
195
- export function assertResourceRelationship(resource, relationshipName, expectedIdentifier) {
196
- assert(resource.relationships, 'Resource should have relationships');
197
- assert(resource.relationships[relationshipName], `Resource should have ${relationshipName} relationship`);
195
+ export function assertResourceRelationship (resource, relationshipName, expectedIdentifier) {
196
+ assert(resource.relationships, 'Resource should have relationships')
197
+ assert(resource.relationships[relationshipName], `Resource should have ${relationshipName} relationship`)
198
198
  assert.deepEqual(
199
199
  resource.relationships[relationshipName].data,
200
200
  expectedIdentifier,
201
201
  `Relationship ${relationshipName} should match expected identifier`
202
- );
202
+ )
203
203
  }