json-rest-api 1.0.29 → 2.0.0

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 (292) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +744 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +634 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +100 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
  38. package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +36 -0
  44. package/index.js +1 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +64 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -177
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +25 -16
  71. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  72. package/plugins/core/lib/dbIntrospection.js +228 -21
  73. package/plugins/core/lib/dbTablesOperations.js +409 -92
  74. package/plugins/core/lib/fractional-positioning.js +12 -48
  75. package/plugins/core/lib/querying/identifier-query.js +19 -0
  76. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  77. package/plugins/core/lib/querying/include-to-many.js +428 -0
  78. package/plugins/core/lib/querying/include-to-one.js +579 -0
  79. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  80. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  81. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  82. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  83. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  84. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  85. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  86. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  87. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  88. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  89. package/plugins/core/lib/querying/query-constraint.js +27 -0
  90. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  93. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  94. package/plugins/core/lib/querying/url-helpers.js +11 -4
  95. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  96. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  97. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  98. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  99. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  100. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  101. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  102. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  103. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  104. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  105. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  106. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  107. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  108. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  109. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  110. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  111. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  112. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  113. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  114. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  115. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  116. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  117. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  118. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  119. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  120. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  121. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  122. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  123. package/plugins/core/lib/writing/resource-version.js +172 -0
  124. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  125. package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
  126. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  127. package/plugins/core/rest-api-cors-plugin.js +52 -205
  128. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  129. package/plugins/core/rest-api-label-plugin.js +25 -38
  130. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  131. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  132. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  133. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  134. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  135. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  136. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  137. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  138. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  139. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  140. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  141. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  142. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  143. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  144. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  145. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  146. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  147. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  148. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  149. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  150. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  151. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  152. package/plugins/core/rest-api-plugin.js +32 -46
  153. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  154. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  155. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  156. package/plugins/core/socketio-plugin.js +364 -344
  157. package/plugins/storage/local-storage.js +40 -36
  158. package/types/autofilter.d.ts +57 -0
  159. package/types/bulk-methods.d.ts +63 -0
  160. package/types/errors.d.ts +118 -0
  161. package/types/fastify-options.d.ts +19 -0
  162. package/types/file-detectors.d.ts +33 -0
  163. package/types/file-storage.d.ts +48 -0
  164. package/types/hook-context.d.ts +180 -0
  165. package/types/plugin-options.d.ts +53 -0
  166. package/types/relationship-methods.d.ts +74 -0
  167. package/types/representations.d.ts +50 -0
  168. package/types/resource-methods.d.ts +95 -0
  169. package/types/resource-schema.d.ts +157 -0
  170. package/types/row-policy.d.ts +31 -0
  171. package/types/runtime.d.ts +132 -0
  172. package/types/transactions.d.ts +21 -0
  173. package/.claude/agents/library-guide-expert.md +0 -30
  174. package/.claude/agents/rest-api-internals-expert.md +0 -54
  175. package/.claude/settings.local.json +0 -70
  176. package/AGENTS.md +0 -34
  177. package/CLAUDE.md +0 -160
  178. package/CRUSH.md +0 -55
  179. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  180. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  181. package/TODO.todo +0 -13
  182. package/connectors_ideas/fastify.js +0 -217
  183. package/connectors_ideas/koa-plugin.js +0 -112
  184. package/docs/.bundle/config +0 -2
  185. package/docs/COMPARISON.md +0 -433
  186. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  187. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  188. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  189. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  190. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  191. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  192. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  193. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  194. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  195. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  196. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  197. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  198. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  199. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  200. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  201. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  202. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  203. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  204. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  205. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  206. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  207. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  208. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  209. package/docs/Gemfile +0 -6
  210. package/docs/Gemfile.lock +0 -308
  211. package/docs/ONBOARDING.md +0 -315
  212. package/docs/_config.yml +0 -36
  213. package/docs/_layouts/default.html +0 -123
  214. package/docs/apple-touch-icon.svg +0 -4
  215. package/docs/assets/css/style.scss +0 -392
  216. package/docs/favicon.svg +0 -4
  217. package/docs/index.md +0 -164
  218. package/docs/mini-guides/file-uploads-complete.md +0 -161
  219. package/eslint.config.js +0 -13
  220. package/examples/url-override-example.js +0 -137
  221. package/fixing_plan.md +0 -112
  222. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  223. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  224. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  225. package/plugins/storage/s3-storage.js +0 -93
  226. package/quickTest.js +0 -136
  227. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  228. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  229. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  230. package/scripts/run-docs.js +0 -133
  231. package/test-debug.js +0 -59
  232. package/tests/README.md +0 -202
  233. package/tests/TEST_TEMPLATE.test.js +0 -178
  234. package/tests/anyapi-basic.test.js +0 -105
  235. package/tests/anyapi-cursor-pagination.test.js +0 -148
  236. package/tests/anyapi-custom-idproperty.test.js +0 -163
  237. package/tests/autofilter.test.js +0 -463
  238. package/tests/bulk-operations.test.js +0 -657
  239. package/tests/computed-fields.test.js +0 -586
  240. package/tests/cors.test.js +0 -834
  241. package/tests/curl.test.js +0 -467
  242. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  243. package/tests/custom-id-simplified-post.test.js +0 -75
  244. package/tests/custom-idproperty-relationships.test.js +0 -932
  245. package/tests/db-introspection.test.js +0 -521
  246. package/tests/db-table-operations.test.js +0 -798
  247. package/tests/fastify-plugin.test.js +0 -184
  248. package/tests/field-getters.test.js +0 -370
  249. package/tests/field-setters.test.js +0 -306
  250. package/tests/file-handling.test.js +0 -155
  251. package/tests/fixtures/api-configs.js +0 -2566
  252. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  253. package/tests/helpers/fake-fastify.js +0 -99
  254. package/tests/helpers/storage-mode.js +0 -65
  255. package/tests/helpers/test-utils.js +0 -203
  256. package/tests/http-connectors-parity.test.js +0 -395
  257. package/tests/id-normalization.test.js +0 -444
  258. package/tests/include-depth-validation.test.js +0 -331
  259. package/tests/includes.test.js +0 -427
  260. package/tests/local-storage.test.js +0 -96
  261. package/tests/logical-id-write-validation.test.js +0 -151
  262. package/tests/nested-includes.test.js +0 -617
  263. package/tests/pagination-boundaries.test.js +0 -154
  264. package/tests/pagination-contract.test.js +0 -122
  265. package/tests/pagination-cursor-multifield.test.js +0 -641
  266. package/tests/pagination-enhanced.test.js +0 -563
  267. package/tests/polymorphic-relationships.test.js +0 -347
  268. package/tests/polymorphic-simple.test.js +0 -148
  269. package/tests/positioning-advanced.test.js +0 -692
  270. package/tests/positioning-extended.test.js +0 -915
  271. package/tests/positioning-multifilter.test.js +0 -699
  272. package/tests/positioning.test.js +0 -560
  273. package/tests/post-commit-hooks.test.js +0 -120
  274. package/tests/projected-fields.test.js +0 -319
  275. package/tests/public-exports.test.js +0 -15
  276. package/tests/put-create-parity.test.js +0 -216
  277. package/tests/queries.test.js +0 -521
  278. package/tests/query-limits.test.js +0 -584
  279. package/tests/relationship-endpoints.test.js +0 -620
  280. package/tests/relationship-include-storage-adapter.test.js +0 -56
  281. package/tests/relationships.test.js +0 -681
  282. package/tests/rest-api.test.js +0 -337
  283. package/tests/return-record-settings.test.js +0 -184
  284. package/tests/row-policy.test.js +0 -489
  285. package/tests/s3-storage.test.js +0 -38
  286. package/tests/searchschema-knex-behavior.test.js +0 -187
  287. package/tests/searchschema-merge.test.js +0 -151
  288. package/tests/socketio.test.js +0 -860
  289. package/tests/storage-mapping.test.js +0 -362
  290. package/tests/temporal-boundaries.test.js +0 -208
  291. package/tests/test-development-guide.md +0 -370
  292. package/tests/virtual-fields.test.js +0 -404
@@ -1,7 +1,15 @@
1
+ // @ts-check
2
+ /** @import {
3
+ * Identifier, LifecycleResource, RelationshipWriteArguments, WriteRelationshipContext
4
+ * } from './lifecycle-types.js' */
5
+ import { lockRelationshipParent } from '../lib/writing/relationship-processor.js'
6
+ import { advanceResourceVersion } from '../lib/writing/resource-version.js'
7
+ import { beginWriteTransaction } from '../../../lib/error-context.js'
8
+ import { rejectRemovedOptions, resolveFormat } from '../lib/querying-writing/response-options.js'
9
+ import { findRelationshipDefinition } from '../lib/querying-writing/relationship-contracts.js'
1
10
  import { RestApiResourceError, RestApiValidationError } from '../../../lib/rest-api-errors.js'
2
11
  import {
3
12
  commitOwnedTransaction,
4
- findRelationshipDefinition,
5
13
  getVisibleRelationshipParent,
6
14
  handleWriteMethodError,
7
15
  validateRelationshipRoutePayload
@@ -10,32 +18,33 @@ import {
10
18
  normalizeRelationshipIdentifiers,
11
19
  requireExistingResourceId
12
20
  } from '../lib/querying-writing/resource-id-normalization.js'
21
+ import { updateReverseRelationship } from '../lib/writing/reverse-relationship-manipulations.js'
22
+ import { invalidateRemovedPivotVersions } from '../lib/writing/many-to-many-manipulations.js'
23
+ import { getStorageColumn } from '../lib/storage/storage-mapping.js'
24
+ import { RELATIONSHIP_WRITE_BATCH_SIZE } from '../lib/querying-writing/knex-constants.js'
13
25
 
14
26
  /**
15
- * DELETE RELATIONSHIP
16
- * Removes specific members from a to-many relationship
17
- * DELETE /api/articles/1/relationships/tags
18
- *
19
- * @param {string} id - The ID of the resource
20
- * @param {string} relationshipName - The name of the relationship
21
- * @param {array} relationshipData - Array of resource identifiers to remove
22
- * @returns {Promise<void>} 204 No Content
27
+ * Remove the identifiers in params.relationshipData from a to-many relationship.
28
+ * Authorization and parent/version locking precede mutation. The method
29
+ * returns no resource and completes only an owned transaction.
30
+ * @param {RelationshipWriteArguments<Identifier[]>} args
23
31
  */
24
- export default async function deleteRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api, log }) {
25
- context.method = 'deleteRelationship'
26
- context.id = requireExistingResourceId(params.id, {
32
+ export default async function deleteRelationshipMethod ({ params, context: callerContext, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api, log }) {
33
+ rejectRemovedOptions(params)
34
+ if (params.format !== undefined) resolveFormat(params.format)
35
+ callerContext.method = 'deleteRelationship'
36
+ callerContext.scopeName = scopeName
37
+ callerContext.id = requireExistingResourceId(params.id, {
27
38
  scopeOptions,
28
39
  vars,
29
40
  scopeName
30
41
  })
31
- context.relationshipName = params.relationshipName
32
- context.schemaInfo = scopes[scopeName].vars.schemaInfo
42
+ callerContext.relationshipName = params.relationshipName
43
+ callerContext.schemaInfo = scope.vars.schemaInfo
33
44
 
34
- // Transaction handling
35
- context.transaction = params.transaction ||
36
- (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
37
- context.shouldCommit = !params.transaction && !!context.transaction
38
- context.db = context.transaction || api.knex.instance
45
+ await beginWriteTransaction(callerContext, params.transaction, helpers.newTransaction, runHooks)
46
+ callerContext.db = callerContext.transaction || api.knex.instance
47
+ const context = /** @type {WriteRelationshipContext} */ (callerContext)
39
48
 
40
49
  try {
41
50
  // Validate
@@ -61,7 +70,7 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
61
70
  operation: 'deleteRelationship',
62
71
  relationshipData: params.relationshipData
63
72
  })
64
- params.relationshipData = normalizeRelationshipIdentifiers(params.relationshipData, { api })
73
+ params.relationshipData = /** @type {Identifier[]} */ (normalizeRelationshipIdentifiers(params.relationshipData, { api }))
65
74
 
66
75
  // Check permissions
67
76
  await runHooks('checkPermissions')
@@ -78,6 +87,14 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
78
87
  if (!parentRecord) {
79
88
  throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
80
89
  }
90
+ context.minimalRecord = parentRecord
91
+ context.originalMinimalRecord = parentRecord
92
+
93
+ await runHooks('beforeDataCall')
94
+ await runHooks('beforeDataCallDeleteRelationship')
95
+
96
+ const revision = await advanceResourceVersion({ scopeName, context, helpers, expectedVersion: params.expectedVersion })
97
+ if (revision === undefined) await lockRelationshipParent({ context, helpers, scopeName })
81
98
 
82
99
  // Remove relationships
83
100
  if (relDef?.through) {
@@ -92,45 +109,30 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
92
109
  } else {
93
110
  const knex = api.knex?.instance || helpers.db
94
111
  const pivotResource = relDef.through
95
- const pivotScope = api.resources[pivotResource]
112
+ const pivotScope = /** @type {LifecycleResource} */ (api.resources[pivotResource])
96
113
  const pivotTable = pivotScope?.vars?.schemaInfo?.tableName || pivotResource
97
- const localKey = relDef.foreignKey
98
- const foreignKey = relDef.otherKey
114
+ const localKey = getStorageColumn(pivotScope.vars.schemaInfo, /** @type {string} */ (relDef.foreignKey))
115
+ const foreignKey = getStorageColumn(pivotScope.vars.schemaInfo, /** @type {string} */ (relDef.otherKey))
99
116
 
100
- for (const identifier of params.relationshipData) {
101
- await knex(pivotTable)
117
+ const ids = [...new Set(params.relationshipData.map(identifier => identifier.id))]
118
+ for (let offset = 0; offset < ids.length; offset += RELATIONSHIP_WRITE_BATCH_SIZE) {
119
+ const removals = knex(pivotTable)
102
120
  .where(localKey, context.id)
103
- .where(foreignKey, identifier.id)
104
- .delete()
121
+ .whereIn(foreignKey, ids.slice(offset, offset + RELATIONSHIP_WRITE_BATCH_SIZE))
105
122
  .transacting(context.transaction)
123
+ await invalidateRemovedPivotVersions(api, relDef, removals, context.transaction)
124
+ await removals.delete()
106
125
  }
107
126
  }
108
127
  } else {
109
- // Null out foreign keys for hasMany
110
- const targetType = relDef.target
111
- for (const identifier of params.relationshipData) {
112
- await api.resources[targetType].patch({
113
- id: identifier.id,
114
- inputRecord: {
115
- data: {
116
- type: targetType,
117
- id: identifier.id,
118
- attributes: { [relDef.foreignKey]: null }
119
- }
120
- },
121
- transaction: context.transaction,
122
- simplified: false
123
- }, { ...context })
124
- }
128
+ await updateReverseRelationship({ api, helpers, context, scopeName, relDef, relData: params.relationshipData, operation: 'remove' })
125
129
  }
126
130
 
127
131
  await runHooks('finish')
128
132
  await runHooks('finishDeleteRelationship')
129
133
 
130
- await commitOwnedTransaction(context, runHooks)
131
-
132
- // 204 No Content
134
+ await commitOwnedTransaction(context)
133
135
  } catch (error) {
134
- await handleWriteMethodError(error, context, 'DELETE_RELATIONSHIP', scopeName, log, runHooks)
136
+ await handleWriteMethodError(error, context, 'DELETE_RELATIONSHIP', scopeName, log)
135
137
  }
136
138
  }
@@ -1,52 +1,56 @@
1
+ // @ts-check
2
+ /** @import {
3
+ * IdentityContext, LifecycleArguments
4
+ * } from './lifecycle-types.js' */
5
+ import { initializeResourceVersion, applyResourceVersion, captureInverseVersions, invalidateInverseVersions } from '../lib/writing/resource-version.js'
6
+ import { beginWriteTransaction } from '../../../lib/error-context.js'
7
+ import { rejectRemovedOptions, resolveFormat } from '../lib/querying-writing/response-options.js'
1
8
  import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
2
- import { commitOwnedTransaction, handleWriteMethodError } from './common.js'
9
+ import { commitOwnedTransaction, handleWriteMethodError, writePrecondition } from './common.js'
3
10
  import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
4
11
 
5
12
  /**
6
- * DELETE
7
- * Permanently deletes a resource.
8
- * Returns 204 No Content on success, indicating the resource has been removed.
9
- * This method does not return the deleted resource.
13
+ * Delete an existing resource and complete only an owned transaction.
14
+ * The programmatic method returns no resource; connectors choose HTTP status.
15
+ * @param {LifecycleArguments} args
10
16
  */
11
17
  export default async function deleteMethod ({
12
18
  params,
13
- context,
19
+ context: callerContext,
14
20
  vars,
15
21
  helpers,
16
22
  scope,
17
23
  scopes,
18
24
  runHooks,
19
- apiOptions,
20
- pluginOptions,
21
25
  scopeOptions,
22
26
  scopeName,
23
27
  api,
24
28
  log
25
29
  }) {
26
30
  // Make the method available to all hooks
27
- context.method = 'delete'
31
+ rejectRemovedOptions(params)
32
+ if (params.format !== undefined) resolveFormat(params.format)
33
+ callerContext.method = 'delete'
28
34
 
29
35
  // Set the ID in context
30
- context.id = requireExistingResourceId(params.id, {
36
+ callerContext.id = requireExistingResourceId(params.id, {
31
37
  scopeOptions,
32
38
  vars,
33
39
  scopeName
34
40
  })
35
41
 
36
42
  // Set scopeName in context (needed for broadcasting)
37
- context.scopeName = scopeName
43
+ callerContext.scopeName = scopeName
38
44
 
39
45
  // Set schema info even for DELETE (needed by storage layer)
40
- context.schemaInfo = scopes[scopeName].vars.schemaInfo
46
+ callerContext.schemaInfo = scope.vars.schemaInfo
41
47
 
42
- // Transaction handling
43
- context.transaction = params.transaction ||
44
- (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
45
- context.shouldCommit = !params.transaction && !!context.transaction
46
- context.db = context.transaction || api.knex.instance
48
+ await beginWriteTransaction(callerContext, params.transaction, helpers.newTransaction, runHooks)
49
+ callerContext.db = callerContext.transaction || api.knex.instance
50
+ const context = /** @type {IdentityContext} */ (callerContext)
47
51
 
48
52
  try {
49
- // No payload validation needed for DELETE
53
+ const versionState = initializeResourceVersion({ context, expectedVersion: params.expectedVersion })
50
54
 
51
55
  // Fetch minimal record for authorization and logging
52
56
  const minimalRecord = await helpers.dataGetMinimal({
@@ -69,12 +73,13 @@ export default async function deleteMethod ({
69
73
  context.originalMinimalRecord = minimalRecord
70
74
  context.minimalRecord = minimalRecord
71
75
 
72
- // Centralised checkPermissions function
73
76
  await scope.checkPermissions({
74
77
  method: 'delete',
75
78
  originalContext: context,
76
79
  })
77
80
 
81
+ await params[writePrecondition]?.()
82
+
78
83
  // Before data operations
79
84
  await runHooks('beforeDataCall')
80
85
  await runHooks('beforeDataCallDelete')
@@ -82,24 +87,25 @@ export default async function deleteMethod ({
82
87
  // Initialize record context for hooks
83
88
  context.record = {}
84
89
 
90
+ const inverseVersions = await captureInverseVersions({ api, helpers, context, scopeName })
91
+ await applyResourceVersion({ state: versionState, context, helpers, scopeName })
92
+
85
93
  // Call the storage helper
86
94
  await helpers.dataDelete({
87
95
  scopeName,
88
96
  context
89
97
  })
90
98
 
99
+ await invalidateInverseVersions({ state: inverseVersions, context, helpers, api, isDelete: true })
100
+
91
101
  await runHooks('afterDataCallDelete')
92
102
  await runHooks('afterDataCall')
93
103
 
94
- // No return record for DELETE (204 No Content)
95
-
96
104
  await runHooks('finish')
97
105
  await runHooks('finishDelete')
98
106
 
99
- await commitOwnedTransaction(context, runHooks)
100
-
101
- // DELETE typically returns void/undefined (204 No Content)
107
+ await commitOwnedTransaction(context)
102
108
  } catch (error) {
103
- await handleWriteMethodError(error, context, 'DELETE', scopeName, log, runHooks)
109
+ await handleWriteMethodError(error, context, 'DELETE', scopeName, log)
104
110
  }
105
111
  }
@@ -1,57 +1,69 @@
1
- import { filterHiddenFields } from '../lib/querying-writing/field-utils.js'
2
- import { isRestApiError } from '../../../lib/error-context.js'
3
-
4
- /**
5
- * enrichAttributes
6
- * Runs the enrichAttributes hook for a specific scope to allow plugins to modify attributes
7
- * before they are returned to the client. This is a scope method so each resource type
8
- * can have its own attribute enrichment logic.
9
- *
10
- *
11
- */
1
+ // @ts-check
2
+ /** @import {
3
+ * EnrichmentArguments, Field, LifecycleContext, LifecycleResource, LifecycleSchema
4
+ * } from './lifecycle-types.js' */
5
+ import { filterHiddenFields, getResourceFieldset, parseFieldset } from '../lib/querying-writing/field-utils.js'
6
+ import { wrapUnexpectedError } from '../../../lib/error-context.js'
7
+ import { getFieldDependencyClosure } from '../lib/querying-writing/schema-helpers.js'
8
+ import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js'
9
+
10
+ // Resolve field dependencies before filtering and running resource enrichment hooks.
11
+ /** @param {EnrichmentArguments} args */
12
12
  export default async function enrichAttributesMethod ({ context, params, runHooks, scopeName, scopes, api, helpers }) {
13
- // Extract parameters passed to enrichAttributes
14
- // - attributes: The raw attributes from database
15
- // - parentContext: The context from the calling method (has queryParams, transaction, etc.)
16
- // - requestedComputedFields: Which computed fields to calculate (from sparse fieldsets)
17
- // - isMainResource: Whether this is the main resource or an included one
18
- // - computedDependencies: Fields fetched only for computation (to be removed)
19
- // Extract parameters passed to enrichAttributes
20
- // - attributes: The raw attributes from database
21
- // - parentContext: The context from the calling method (has queryParams, transaction, etc.)
22
- // - requestedComputedFields: Which computed fields to calculate (from sparse fieldsets)
23
- // - isMainResource: Whether this is the main resource or an included one
24
- // - computedDependencies: Fields fetched only for computation (to be removed)
25
- const { attributes, parentContext, requestedComputedFields, isMainResource, computedDependencies } = params || {}
26
-
27
- // Return empty object if no attributes provided
13
+ const { id, attributes, parentContext, requestedComputedFields, isMainResource } = params || {}
14
+
28
15
  if (!attributes) {
29
16
  return {}
30
17
  }
31
18
 
32
- // Get schema, computed field definitions
33
- const schemaStructure = scopes[scopeName]?.vars?.schemaInfo?.schemaStructure || {}
34
- const computedFields = scopes[scopeName]?.vars?.schemaInfo?.computed || {}
35
- const fieldGetters = scopes[scopeName]?.vars?.schemaInfo?.fieldGetters || {}
36
- const sortedGetterFields = scopes[scopeName]?.vars?.schemaInfo?.sortedGetterFields || []
37
- const queryFields = scopes[scopeName]?.vars?.queryFields || {}
19
+ const schemaInfo = scopes[scopeName]?.vars?.schemaInfo || /** @type {Partial<LifecycleSchema>} */ ({})
20
+ const schemaStructure = schemaInfo.schemaStructure || {}
21
+ const computedFields = schemaInfo.computed || {}
22
+ const fieldGetters = schemaInfo.fieldGetters || {}
23
+ const sortedGetterFields = schemaInfo.sortedGetterFields || []
38
24
 
39
- // Make a copy of attributes for transformation
40
25
  const transformedAttributes = { ...attributes }
26
+ const resourceFieldset = getResourceFieldset(parentContext?.queryParams?.fields, scopeName)
27
+ const requestedFields = parseFieldset(resourceFieldset)
28
+ const selectedComputedFields = requestedComputedFields ?? getRequestedComputedFields(
29
+ scopeName, resourceFieldset, computedFields
30
+ )
31
+ const selectedVirtualFields = Object.keys(schemaStructure).filter(name => {
32
+ const definition = /** @type {Field} */ (schemaStructure[name])
33
+ return definition.virtual === true && definition.hidden !== true &&
34
+ (requestedFields === null ? definition.normallyHidden !== true : requestedFields.includes(name))
35
+ })
36
+ const neededFields = getFieldDependencyClosure(schemaInfo, [
37
+ ...Object.keys(attributes), ...selectedComputedFields, ...selectedVirtualFields
38
+ ])
39
+
40
+ // Virtual input belongs only to the resource that was written.
41
+ const inputData = parentContext?.inputRecord?.data
42
+ if (isMainResource !== false && inputData?.type === scopeName &&
43
+ inputData.id !== undefined && String(inputData.id) === String(id) && inputData.attributes) {
44
+ const inputAttrs = inputData.attributes
45
+ Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
46
+ if (fieldDef.virtual === true && neededFields.has(fieldName) && Object.hasOwn(inputAttrs, fieldName) && inputAttrs[fieldName] !== undefined && inputAttrs[fieldName] !== null) {
47
+ if (!Object.hasOwn(transformedAttributes, fieldName)) {
48
+ transformedAttributes[fieldName] = inputAttrs[fieldName]
49
+ }
50
+ }
51
+ })
52
+ }
41
53
 
42
- // STEP 1: Apply field getters in dependency order (before filtering)
43
- // This ensures getters see all fields including hidden ones
54
+ const originalAttributes = { ...transformedAttributes }
55
+ // Getters run in dependency order while hidden dependencies are still available.
44
56
  for (const fieldName of sortedGetterFields) {
45
- // Only process if field exists in attributes
46
- if (fieldName in transformedAttributes) {
47
- const getterInfo = fieldGetters[fieldName]
57
+ if (Object.hasOwn(transformedAttributes, fieldName)) {
58
+ const getterInfo = /** @type {NonNullable<LifecycleSchema['fieldGetters'][string]>} */ (fieldGetters[fieldName])
48
59
  try {
49
60
  const getterContext = {
50
- attributes: transformedAttributes, // Current state with previous getters applied
61
+ id,
62
+ attributes: transformedAttributes,
51
63
  fieldName,
52
- originalValue: attributes[fieldName], // Original untransformed value
53
- originalAttributes: attributes, // All original attributes
54
- record: transformedAttributes, // Alias for compatibility
64
+ originalValue: originalAttributes[fieldName],
65
+ originalAttributes,
66
+ record: transformedAttributes,
55
67
  parentContext,
56
68
  scopeName,
57
69
  api,
@@ -59,128 +71,55 @@ export default async function enrichAttributesMethod ({ context, params, runHook
59
71
  isMainResource: isMainResource !== false
60
72
  }
61
73
 
62
- // Apply getter (can be async)
63
74
  transformedAttributes[fieldName] = await getterInfo.getter(
64
75
  transformedAttributes[fieldName],
65
76
  getterContext
66
77
  )
67
78
  } catch (error) {
68
- if (isRestApiError(error)) throw error
69
- console.error(`Error in getter for field '${fieldName}' in ${scopeName}:`, error)
70
- // Keep current value on error (don't break the whole request)
79
+ throw wrapUnexpectedError(error, {
80
+ message: `Getter for field '${fieldName}' failed`,
81
+ context: { scopeName, fieldName, phase: 'getter' }
82
+ })
71
83
  }
72
84
  }
73
85
  }
74
86
 
75
- // Filter hidden fields from attributes based on visibility rules
76
- // This removes hidden:true fields and normallyHidden:true fields (unless requested)
77
- const requestedFields = parentContext?.queryParams?.fields?.[scopeName]
78
- const filteredAttributes = filterHiddenFields(
79
- transformedAttributes,
80
- { structure: { ...schemaStructure, ...queryFields } },
81
- requestedFields
82
- )
83
-
84
- // Handle virtual fields - they need to be added from parent context if available
85
- // Virtual fields come from input and need to be preserved in responses
86
- if (parentContext && parentContext.inputRecord?.data?.attributes) {
87
- const inputAttrs = parentContext.inputRecord.data.attributes
88
- Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
89
- if (fieldDef.virtual === true && fieldName in inputAttrs && inputAttrs[fieldName] !== undefined && inputAttrs[fieldName] !== null) {
90
- // Add virtual field to filtered attributes if not already there
91
- if (!(fieldName in filteredAttributes)) {
92
- filteredAttributes[fieldName] = inputAttrs[fieldName]
93
- }
94
- }
95
- })
96
- }
97
-
98
- // Now handle sparse fieldsets for virtual fields
99
- if (requestedFields && requestedFields.length > 0) {
100
- const requestedFieldsList = typeof requestedFields === 'string'
101
- ? requestedFields.split(',').map(f => f.trim())
102
- : requestedFields
87
+ const fieldsToCompute = (schemaInfo.sortedComputedFields || []).filter(field => neededFields.has(field))
103
88
 
104
- // Remove virtual fields that weren't requested
105
- Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
106
- if (fieldDef.virtual === true && fieldName in filteredAttributes) {
107
- if (!requestedFieldsList.includes(fieldName)) {
108
- delete filteredAttributes[fieldName]
109
- }
110
- }
111
- })
112
- }
113
-
114
- // Determine which computed fields to calculate
115
- // We only compute fields that are requested to optimize performance
116
- let fieldsToCompute = []
117
- if (requestedComputedFields) {
118
- // Explicit list provided (from sparse fieldsets)
119
- // Example: ?fields[products]=name,profit_margin -> only compute profit_margin
120
- fieldsToCompute = requestedComputedFields
121
- } else if (isMainResource || !parentContext?.queryParams?.fields) {
122
- // No sparse fieldsets or this is the main resource - compute all fields
123
- // This ensures all computed fields are available when no filtering is applied
124
- fieldsToCompute = Object.keys(computedFields)
125
- }
126
-
127
- // Create compute context with all available resources
128
- // IMPORTANT: We pass the TRANSFORMED attributes (after getters) to compute functions
129
- // This ensures computed fields see the getter-processed values
130
- // Example: if price getter converts string to number, profit_margin sees the number
89
+ // Computations receive getter-transformed values and earlier computed results.
131
90
  const computeContext = {
132
- attributes: transformedAttributes, // Transformed attributes after getters
133
- record: { ...transformedAttributes }, // Full record for convenience
91
+ id,
92
+ attributes: transformedAttributes,
93
+ record: { ...transformedAttributes },
134
94
  context: parentContext,
135
- helpers, // API helpers for complex operations
136
- api, // Full API instance
95
+ helpers,
96
+ api,
137
97
  }
138
98
 
139
- // Auto-compute fields that have compute functions
140
99
  for (const fieldName of fieldsToCompute) {
141
100
  const fieldDef = computedFields[fieldName]
142
101
  if (fieldDef && fieldDef.compute) {
143
102
  try {
144
- // Call the compute function with full context
145
- // Example: profit_margin compute gets { attributes: { price: 100, cost: 60 } }
146
- // and returns: ((100 - 60) / 100 * 100) = "40.00"
147
- filteredAttributes[fieldName] = await fieldDef.compute(computeContext)
103
+ const value = await fieldDef.compute(computeContext)
104
+ transformedAttributes[fieldName] = value
105
+ computeContext.record[fieldName] = value
148
106
  } catch (error) {
149
- if (isRestApiError(error)) throw error
150
- // Log error but don't fail the request - computed fields shouldn't break API
151
- console.error(`Error computing field '${fieldName}' for ${scopeName}:`, error)
152
- filteredAttributes[fieldName] = null
107
+ throw wrapUnexpectedError(error, {
108
+ message: `Computation for field '${fieldName}' failed`,
109
+ context: { scopeName, fieldName, phase: 'computed' }
110
+ })
153
111
  }
154
112
  }
155
113
  }
156
114
 
157
- // Remove fields that were only fetched as dependencies
158
- // This is the key to the dependency resolution feature:
159
- // 1. We fetched dependencies from DB (e.g., 'cost' for profit_margin)
160
- // 2. We used them in compute functions
161
- // 3. Now we remove them if they weren't explicitly requested
162
- // Example: User requests profit_margin, we fetch cost, compute, then remove cost
163
- const finalAttributes = { ...filteredAttributes }
164
- if (requestedFields && computedDependencies && computedDependencies.length > 0) {
165
- // Parse requested fields if it's a string
166
- const requested = typeof requestedFields === 'string'
167
- ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
168
- : requestedFields
169
-
170
- for (const dep of computedDependencies) {
171
- // Only remove if it wasn't explicitly requested
172
- // Example: 'cost' is removed unless user explicitly asked for it
173
- if (!requested.includes(dep)) {
174
- delete finalAttributes[dep]
175
- }
176
- }
177
- }
178
-
179
- if (requestedFields && requestedFields.length > 0) {
180
- const requestedList = typeof requestedFields === 'string'
181
- ? requestedFields.split(',').map((field) => field.trim()).filter((field) => field)
182
- : requestedFields
183
- const allowed = new Set(requestedList)
115
+ // Remove private fields and values fetched only for computation.
116
+ const finalAttributes = filterHiddenFields(
117
+ transformedAttributes,
118
+ { structure: schemaInfo.outputFields },
119
+ requestedFields
120
+ )
121
+ if (requestedFields !== null) {
122
+ const allowed = new Set(requestedFields)
184
123
  Object.keys(finalAttributes).forEach((key) => {
185
124
  if (!allowed.has(key)) {
186
125
  delete finalAttributes[key]
@@ -188,20 +127,41 @@ export default async function enrichAttributesMethod ({ context, params, runHook
188
127
  })
189
128
  }
190
129
 
191
- // Create context for enrichAttributes hooks
192
130
  Object.assign(context, {
193
131
  parentContext,
194
- attributes: finalAttributes, // Use the final attributes after dependency removal
132
+ attributes: finalAttributes,
195
133
  computedFields,
196
- requestedComputedFields: fieldsToCompute,
134
+ requestedComputedFields: selectedComputedFields,
197
135
  scopeName,
198
136
  helpers,
199
137
  api
200
138
  })
201
139
 
202
- // Run enrichAttributes hooks for additional/override computations
203
140
  await runHooks('enrichAttributes')
204
141
 
205
- // Return the attributes from context, which hooks may have modified
206
- return context.attributes
207
- };
142
+ return /** @type {import('../lib/storage/storage-types.js').StorageRow} */ (context.attributes)
143
+ }
144
+
145
+ // Preserve include order and each resource's enrichment hooks and fieldset.
146
+ /** @param {LifecycleContext} context @param {Record<string, LifecycleResource>} scopes */
147
+ export async function enrichIncludedAttributes (context, scopes) {
148
+ for (const entry of (context.record?.included || [])) {
149
+ const entryScope = /** @type {LifecycleResource} */ (scopes[entry.type])
150
+ const entryComputed = entryScope.vars.schemaInfo?.computed || {}
151
+ const entryRequestedFields = getResourceFieldset(context.queryParams?.fields, entry.type)
152
+ const entryRequestedComputed = getRequestedComputedFields(
153
+ entry.type,
154
+ entryRequestedFields,
155
+ entryComputed
156
+ )
157
+
158
+ entry.attributes = await entryScope.enrichAttributes({
159
+ id: entry.id,
160
+ attributes: entry.attributes,
161
+ parentContext: context,
162
+ requestedComputedFields: entryRequestedComputed,
163
+ isMainResource: false,
164
+ computedDependencies: entry.__$jsonrestapi_computed_deps$__
165
+ })
166
+ }
167
+ }