@memberjunction/server 6.1.0-edge.6 → 6.1.0-edge.7

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 (177) hide show
  1. package/README.md +24 -9
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/config.d.ts +36 -0
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +9 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.d.ts +30 -0
  8. package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.d.ts.map +1 -1
  9. package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.js +58 -5
  10. package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.js.map +1 -1
  11. package/dist/generated/graphql-schemas/__mj.d.ts +2842 -2144
  12. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  13. package/dist/generated/graphql-schemas/__mj.js +5334 -2143
  14. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  15. package/dist/generic/ResolverBase.d.ts +119 -4
  16. package/dist/generic/ResolverBase.d.ts.map +1 -1
  17. package/dist/generic/ResolverBase.js +264 -11
  18. package/dist/generic/ResolverBase.js.map +1 -1
  19. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  20. package/dist/generic/RunViewResolver.js +26 -2
  21. package/dist/generic/RunViewResolver.js.map +1 -1
  22. package/dist/generic/refusalExtensions.d.ts +52 -0
  23. package/dist/generic/refusalExtensions.d.ts.map +1 -0
  24. package/dist/generic/refusalExtensions.js +16 -0
  25. package/dist/generic/refusalExtensions.js.map +1 -0
  26. package/dist/index.d.ts +9 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +115 -91
  29. package/dist/index.js.map +1 -1
  30. package/dist/resolvers/EntityRecordNameResolver.d.ts +34 -5
  31. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  32. package/dist/resolvers/EntityRecordNameResolver.js +64 -7
  33. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  34. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -2
  35. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  36. package/dist/resolvers/RealtimeClientSessionResolver.js +80 -4
  37. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  38. package/dist/rest/RealtimeSdpBrokerHandler.d.ts +23 -0
  39. package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -0
  40. package/dist/rest/RealtimeSdpBrokerHandler.js +78 -0
  41. package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -0
  42. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  43. package/dist/serverExtensionReservedRoots.js +2 -3
  44. package/dist/serverExtensionReservedRoots.js.map +1 -1
  45. package/package.json +92 -92
  46. package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +118 -0
  47. package/src/__tests__/RealtimeClientSessionResolver.test.ts +20 -0
  48. package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +191 -0
  49. package/src/__tests__/ResolverBase.filterEscaping.test.ts +63 -0
  50. package/src/__tests__/ResolverBase.readableFieldsTransport.test.ts +106 -0
  51. package/src/__tests__/generatedGraphQLSchema.structural.test.ts +46 -0
  52. package/src/__tests__/resolverBase.fls.test.ts +483 -0
  53. package/src/__tests__/resolverBase.recordChangesFls.test.ts +358 -0
  54. package/src/__tests__/resolverBase.refusalExtensions.test.ts +101 -0
  55. package/src/__tests__/serverExtensionReservedRoots.test.ts +79 -11
  56. package/src/config.ts +12 -0
  57. package/src/entitySubclasses/MJEntityPermissionEntityServer.server.ts +65 -6
  58. package/src/generated/graphql-schemas/__mj.ts +6590 -4287
  59. package/src/generic/ResolverBase.ts +313 -10
  60. package/src/generic/RunViewResolver.ts +28 -2
  61. package/src/generic/refusalExtensions.ts +59 -0
  62. package/src/index.ts +137 -100
  63. package/src/resolvers/EntityRecordNameResolver.ts +74 -8
  64. package/src/resolvers/RealtimeClientSessionResolver.ts +128 -2
  65. package/src/rest/RealtimeSdpBrokerHandler.ts +101 -0
  66. package/src/serverExtensionReservedRoots.ts +2 -3
  67. package/dist/resolvers/RingCentralTelephonyResolver.d.ts +0 -27
  68. package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +0 -1
  69. package/dist/resolvers/RingCentralTelephonyResolver.js +0 -87
  70. package/dist/resolvers/RingCentralTelephonyResolver.js.map +0 -1
  71. package/dist/resolvers/TeamsMeetingsResolver.d.ts +0 -28
  72. package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +0 -1
  73. package/dist/resolvers/TeamsMeetingsResolver.js +0 -91
  74. package/dist/resolvers/TeamsMeetingsResolver.js.map +0 -1
  75. package/dist/resolvers/TelephonyResolver.d.ts +0 -26
  76. package/dist/resolvers/TelephonyResolver.d.ts.map +0 -1
  77. package/dist/resolvers/TelephonyResolver.js +0 -86
  78. package/dist/resolvers/TelephonyResolver.js.map +0 -1
  79. package/dist/resolvers/VonageTelephonyResolver.d.ts +0 -26
  80. package/dist/resolvers/VonageTelephonyResolver.d.ts.map +0 -1
  81. package/dist/resolvers/VonageTelephonyResolver.js +0 -86
  82. package/dist/resolvers/VonageTelephonyResolver.js.map +0 -1
  83. package/dist/telephony/RingCentralTelephonyService.d.ts +0 -115
  84. package/dist/telephony/RingCentralTelephonyService.d.ts.map +0 -1
  85. package/dist/telephony/RingCentralTelephonyService.js +0 -262
  86. package/dist/telephony/RingCentralTelephonyService.js.map +0 -1
  87. package/dist/telephony/TeamsMeetingsRouter.d.ts +0 -40
  88. package/dist/telephony/TeamsMeetingsRouter.d.ts.map +0 -1
  89. package/dist/telephony/TeamsMeetingsRouter.js +0 -112
  90. package/dist/telephony/TeamsMeetingsRouter.js.map +0 -1
  91. package/dist/telephony/TeamsMeetingsService.d.ts +0 -113
  92. package/dist/telephony/TeamsMeetingsService.d.ts.map +0 -1
  93. package/dist/telephony/TeamsMeetingsService.js +0 -196
  94. package/dist/telephony/TeamsMeetingsService.js.map +0 -1
  95. package/dist/telephony/TwilioTelephonyRouter.d.ts +0 -36
  96. package/dist/telephony/TwilioTelephonyRouter.d.ts.map +0 -1
  97. package/dist/telephony/TwilioTelephonyRouter.js +0 -143
  98. package/dist/telephony/TwilioTelephonyRouter.js.map +0 -1
  99. package/dist/telephony/TwilioTelephonyService.d.ts +0 -95
  100. package/dist/telephony/TwilioTelephonyService.d.ts.map +0 -1
  101. package/dist/telephony/TwilioTelephonyService.js +0 -193
  102. package/dist/telephony/TwilioTelephonyService.js.map +0 -1
  103. package/dist/telephony/VonageTelephonyRouter.d.ts +0 -41
  104. package/dist/telephony/VonageTelephonyRouter.d.ts.map +0 -1
  105. package/dist/telephony/VonageTelephonyRouter.js +0 -201
  106. package/dist/telephony/VonageTelephonyRouter.js.map +0 -1
  107. package/dist/telephony/VonageTelephonyService.d.ts +0 -90
  108. package/dist/telephony/VonageTelephonyService.d.ts.map +0 -1
  109. package/dist/telephony/VonageTelephonyService.js +0 -191
  110. package/dist/telephony/VonageTelephonyService.js.map +0 -1
  111. package/dist/telephony/calendar-scheduler.d.ts +0 -67
  112. package/dist/telephony/calendar-scheduler.d.ts.map +0 -1
  113. package/dist/telephony/calendar-scheduler.js +0 -133
  114. package/dist/telephony/calendar-scheduler.js.map +0 -1
  115. package/dist/telephony/index.d.ts +0 -29
  116. package/dist/telephony/index.d.ts.map +0 -1
  117. package/dist/telephony/index.js +0 -38
  118. package/dist/telephony/index.js.map +0 -1
  119. package/dist/telephony/media-upgrade-router.d.ts +0 -47
  120. package/dist/telephony/media-upgrade-router.d.ts.map +0 -1
  121. package/dist/telephony/media-upgrade-router.js +0 -72
  122. package/dist/telephony/media-upgrade-router.js.map +0 -1
  123. package/dist/telephony/ringcentral-runtime.d.ts +0 -14
  124. package/dist/telephony/ringcentral-runtime.d.ts.map +0 -1
  125. package/dist/telephony/ringcentral-runtime.js +0 -18
  126. package/dist/telephony/ringcentral-runtime.js.map +0 -1
  127. package/dist/telephony/teams-meetings-runtime.d.ts +0 -16
  128. package/dist/telephony/teams-meetings-runtime.d.ts.map +0 -1
  129. package/dist/telephony/teams-meetings-runtime.js +0 -20
  130. package/dist/telephony/teams-meetings-runtime.js.map +0 -1
  131. package/dist/telephony/teamsAcsMediaRegistry.d.ts +0 -69
  132. package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +0 -1
  133. package/dist/telephony/teamsAcsMediaRegistry.js +0 -118
  134. package/dist/telephony/teamsAcsMediaRegistry.js.map +0 -1
  135. package/dist/telephony/telephony-runtime.d.ts +0 -14
  136. package/dist/telephony/telephony-runtime.d.ts.map +0 -1
  137. package/dist/telephony/telephony-runtime.js +0 -18
  138. package/dist/telephony/telephony-runtime.js.map +0 -1
  139. package/dist/telephony/twilioMediaRegistry.d.ts +0 -60
  140. package/dist/telephony/twilioMediaRegistry.d.ts.map +0 -1
  141. package/dist/telephony/twilioMediaRegistry.js +0 -106
  142. package/dist/telephony/twilioMediaRegistry.js.map +0 -1
  143. package/dist/telephony/vonage-runtime.d.ts +0 -14
  144. package/dist/telephony/vonage-runtime.d.ts.map +0 -1
  145. package/dist/telephony/vonage-runtime.js +0 -18
  146. package/dist/telephony/vonage-runtime.js.map +0 -1
  147. package/dist/telephony/vonageMediaRegistry.d.ts +0 -78
  148. package/dist/telephony/vonageMediaRegistry.d.ts.map +0 -1
  149. package/dist/telephony/vonageMediaRegistry.js +0 -158
  150. package/dist/telephony/vonageMediaRegistry.js.map +0 -1
  151. package/src/__tests__/media-upgrade-router.test.ts +0 -43
  152. package/src/__tests__/teams-meetings-service.test.ts +0 -65
  153. package/src/__tests__/teamsAcsMediaRegistry.test.ts +0 -82
  154. package/src/__tests__/twilio-telephony-service.test.ts +0 -23
  155. package/src/__tests__/twilioMediaRegistry.test.ts +0 -103
  156. package/src/__tests__/vonageMediaRegistry.test.ts +0 -180
  157. package/src/resolvers/RingCentralTelephonyResolver.ts +0 -64
  158. package/src/resolvers/TeamsMeetingsResolver.ts +0 -68
  159. package/src/resolvers/TelephonyResolver.ts +0 -63
  160. package/src/resolvers/VonageTelephonyResolver.ts +0 -63
  161. package/src/telephony/RingCentralTelephonyService.ts +0 -342
  162. package/src/telephony/TeamsMeetingsRouter.ts +0 -143
  163. package/src/telephony/TeamsMeetingsService.ts +0 -268
  164. package/src/telephony/TwilioTelephonyRouter.ts +0 -184
  165. package/src/telephony/TwilioTelephonyService.ts +0 -261
  166. package/src/telephony/VonageTelephonyRouter.ts +0 -239
  167. package/src/telephony/VonageTelephonyService.ts +0 -259
  168. package/src/telephony/calendar-scheduler.ts +0 -176
  169. package/src/telephony/index.ts +0 -43
  170. package/src/telephony/media-upgrade-router.ts +0 -79
  171. package/src/telephony/ringcentral-runtime.ts +0 -22
  172. package/src/telephony/teams-meetings-runtime.ts +0 -24
  173. package/src/telephony/teamsAcsMediaRegistry.ts +0 -152
  174. package/src/telephony/telephony-runtime.ts +0 -22
  175. package/src/telephony/twilioMediaRegistry.ts +0 -137
  176. package/src/telephony/vonage-runtime.ts +0 -22
  177. package/src/telephony/vonageMediaRegistry.ts +0 -200
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/server",
3
- "version": "6.1.0-edge.6",
3
+ "version": "6.1.0-edge.7",
4
4
  "description": "MemberJunction: This project provides API access via GraphQL to the common data store.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./src/index.ts",
@@ -19,97 +19,97 @@
19
19
  "@as-integrations/express5": "^1.0.0",
20
20
  "@graphql-tools/schema": "^10.0.38",
21
21
  "@graphql-tools/utils": "^11.0.0",
22
- "@memberjunction/actions": "6.1.0-edge.6",
23
- "@memberjunction/actions-apollo": "6.1.0-edge.6",
24
- "@memberjunction/actions-base": "6.1.0-edge.6",
25
- "@memberjunction/actions-bizapps-accounting": "6.1.0-edge.6",
26
- "@memberjunction/actions-bizapps-crm": "6.1.0-edge.6",
27
- "@memberjunction/actions-bizapps-formbuilders": "6.1.0-edge.6",
28
- "@memberjunction/actions-bizapps-lms": "6.1.0-edge.6",
29
- "@memberjunction/actions-bizapps-social": "6.1.0-edge.6",
30
- "@memberjunction/ai": "6.1.0-edge.6",
31
- "@memberjunction/ai-agent-manager": "6.1.0-edge.6",
32
- "@memberjunction/ai-agent-manager-actions": "6.1.0-edge.6",
33
- "@memberjunction/ai-agents": "6.1.0-edge.6",
34
- "@memberjunction/ai-bridge-base": "6.1.0-edge.6",
35
- "@memberjunction/ai-bridge-ringcentral": "6.1.0-edge.6",
36
- "@memberjunction/ai-bridge-server": "6.1.0-edge.6",
37
- "@memberjunction/ai-bridge-teams": "6.1.0-edge.6",
38
- "@memberjunction/ai-bridge-twilio": "6.1.0-edge.6",
39
- "@memberjunction/ai-bridge-vonage": "6.1.0-edge.6",
40
- "@memberjunction/ai-core-plus": "6.1.0-edge.6",
41
- "@memberjunction/ai-engine-base": "6.1.0-edge.6",
42
- "@memberjunction/ai-mcp-client": "6.1.0-edge.6",
43
- "@memberjunction/ai-prompts": "6.1.0-edge.6",
44
- "@memberjunction/ai-provider-bundle": "6.1.0-edge.6",
45
- "@memberjunction/ai-vector-sync": "6.1.0-edge.6",
46
- "@memberjunction/ai-vectordb": "6.1.0-edge.6",
47
- "@memberjunction/ai-vectors-pinecone": "6.1.0-edge.6",
48
- "@memberjunction/aiengine": "6.1.0-edge.6",
49
- "@memberjunction/api-keys": "6.1.0-edge.6",
50
- "@memberjunction/auth-providers": "6.1.0-edge.6",
51
- "@memberjunction/clustering-engine": "6.1.0-edge.6",
52
- "@memberjunction/codegen-lib": "6.1.0-edge.6",
53
- "@memberjunction/communication-engine": "6.1.0-edge.6",
54
- "@memberjunction/communication-ms-graph": "6.1.0-edge.6",
55
- "@memberjunction/communication-sendgrid": "6.1.0-edge.6",
56
- "@memberjunction/communication-types": "6.1.0-edge.6",
57
- "@memberjunction/component-registry-client-sdk": "6.1.0-edge.6",
58
- "@memberjunction/computer-use": "6.1.0-edge.6",
59
- "@memberjunction/computer-use-engine": "6.1.0-edge.6",
60
- "@memberjunction/config": "6.1.0-edge.6",
61
- "@memberjunction/core": "6.1.0-edge.6",
62
- "@memberjunction/core-actions": "6.1.0-edge.6",
63
- "@memberjunction/core-entities": "6.1.0-edge.6",
64
- "@memberjunction/core-entities-server": "6.1.0-edge.6",
65
- "@memberjunction/credentials": "6.1.0-edge.6",
66
- "@memberjunction/data-context": "6.1.0-edge.6",
67
- "@memberjunction/data-context-server": "6.1.0-edge.6",
68
- "@memberjunction/doc-utils": "6.1.0-edge.6",
69
- "@memberjunction/encryption": "6.1.0-edge.6",
70
- "@memberjunction/entity-communications-base": "6.1.0-edge.6",
71
- "@memberjunction/entity-communications-server": "6.1.0-edge.6",
72
- "@memberjunction/esignature": "6.1.0-edge.6",
73
- "@memberjunction/external-change-detection": "6.1.0-edge.6",
74
- "@memberjunction/generic-database-provider": "6.1.0-edge.6",
75
- "@memberjunction/global": "6.1.0-edge.6",
76
- "@memberjunction/graphql-dataprovider": "6.1.0-edge.6",
77
- "@memberjunction/integration-engine": "6.1.0-edge.6",
78
- "@memberjunction/integration-engine-base": "6.1.0-edge.6",
79
- "@memberjunction/integration-progress-artifacts": "6.1.0-edge.6",
80
- "@memberjunction/integration-schema-builder": "6.1.0-edge.6",
81
- "@memberjunction/interactive-component-types": "6.1.0-edge.6",
82
- "@memberjunction/lists": "6.1.0-edge.6",
83
- "@memberjunction/lists-base": "6.1.0-edge.6",
84
- "@memberjunction/livekit-room-server": "6.1.0-edge.6",
85
- "@memberjunction/network-utils": "6.1.0-edge.6",
86
- "@memberjunction/notifications": "6.1.0-edge.6",
87
- "@memberjunction/postgresql-dataprovider": "6.1.0-edge.6",
88
- "@memberjunction/queue": "6.1.0-edge.6",
89
- "@memberjunction/record-comparison": "6.1.0-edge.6",
90
- "@memberjunction/redis-provider": "6.1.0-edge.6",
91
- "@memberjunction/remote-browser-base": "6.1.0-edge.6",
92
- "@memberjunction/remote-browser-cdp": "6.1.0-edge.6",
93
- "@memberjunction/remote-browser-selfhost": "6.1.0-edge.6",
94
- "@memberjunction/remote-browser-server": "6.1.0-edge.6",
95
- "@memberjunction/scheduling-actions": "6.1.0-edge.6",
96
- "@memberjunction/scheduling-base-types": "6.1.0-edge.6",
97
- "@memberjunction/scheduling-engine": "6.1.0-edge.6",
98
- "@memberjunction/scheduling-engine-base": "6.1.0-edge.6",
99
- "@memberjunction/schema-engine": "6.1.0-edge.6",
100
- "@memberjunction/search-engine": "6.1.0-edge.6",
101
- "@memberjunction/server-extensions-core": "6.1.0-edge.6",
102
- "@memberjunction/sql-dialect": "6.1.0-edge.6",
103
- "@memberjunction/sql-parser": "6.1.0-edge.6",
104
- "@memberjunction/sqlserver-dataprovider": "6.1.0-edge.6",
105
- "@memberjunction/storage": "6.1.0-edge.6",
106
- "@memberjunction/tag-engine": "6.1.0-edge.6",
107
- "@memberjunction/tag-engine-base": "6.1.0-edge.6",
108
- "@memberjunction/task-graph": "6.1.0-edge.6",
109
- "@memberjunction/templates": "6.1.0-edge.6",
110
- "@memberjunction/testing-engine": "6.1.0-edge.6",
111
- "@memberjunction/testing-engine-base": "6.1.0-edge.6",
112
- "@memberjunction/version-history": "6.1.0-edge.6",
22
+ "@memberjunction/actions": "6.1.0-edge.7",
23
+ "@memberjunction/actions-apollo": "6.1.0-edge.7",
24
+ "@memberjunction/actions-base": "6.1.0-edge.7",
25
+ "@memberjunction/actions-bizapps-accounting": "6.1.0-edge.7",
26
+ "@memberjunction/actions-bizapps-crm": "6.1.0-edge.7",
27
+ "@memberjunction/actions-bizapps-formbuilders": "6.1.0-edge.7",
28
+ "@memberjunction/actions-bizapps-lms": "6.1.0-edge.7",
29
+ "@memberjunction/actions-bizapps-social": "6.1.0-edge.7",
30
+ "@memberjunction/ai": "6.1.0-edge.7",
31
+ "@memberjunction/ai-agent-manager": "6.1.0-edge.7",
32
+ "@memberjunction/ai-agent-manager-actions": "6.1.0-edge.7",
33
+ "@memberjunction/ai-agents": "6.1.0-edge.7",
34
+ "@memberjunction/ai-bridge-base": "6.1.0-edge.7",
35
+ "@memberjunction/ai-bridge-ringcentral": "6.1.0-edge.7",
36
+ "@memberjunction/ai-bridge-server": "6.1.0-edge.7",
37
+ "@memberjunction/ai-bridge-teams": "6.1.0-edge.7",
38
+ "@memberjunction/ai-bridge-twilio": "6.1.0-edge.7",
39
+ "@memberjunction/ai-bridge-vonage": "6.1.0-edge.7",
40
+ "@memberjunction/ai-core-plus": "6.1.0-edge.7",
41
+ "@memberjunction/ai-engine-base": "6.1.0-edge.7",
42
+ "@memberjunction/ai-mcp-client": "6.1.0-edge.7",
43
+ "@memberjunction/ai-prompts": "6.1.0-edge.7",
44
+ "@memberjunction/ai-provider-bundle": "6.1.0-edge.7",
45
+ "@memberjunction/ai-vector-sync": "6.1.0-edge.7",
46
+ "@memberjunction/ai-vectordb": "6.1.0-edge.7",
47
+ "@memberjunction/ai-vectors-pinecone": "6.1.0-edge.7",
48
+ "@memberjunction/aiengine": "6.1.0-edge.7",
49
+ "@memberjunction/api-keys": "6.1.0-edge.7",
50
+ "@memberjunction/auth-providers": "6.1.0-edge.7",
51
+ "@memberjunction/clustering-engine": "6.1.0-edge.7",
52
+ "@memberjunction/codegen-lib": "6.1.0-edge.7",
53
+ "@memberjunction/communication-engine": "6.1.0-edge.7",
54
+ "@memberjunction/communication-ms-graph": "6.1.0-edge.7",
55
+ "@memberjunction/communication-sendgrid": "6.1.0-edge.7",
56
+ "@memberjunction/communication-types": "6.1.0-edge.7",
57
+ "@memberjunction/component-registry-client-sdk": "6.1.0-edge.7",
58
+ "@memberjunction/computer-use": "6.1.0-edge.7",
59
+ "@memberjunction/computer-use-engine": "6.1.0-edge.7",
60
+ "@memberjunction/config": "6.1.0-edge.7",
61
+ "@memberjunction/core": "6.1.0-edge.7",
62
+ "@memberjunction/core-actions": "6.1.0-edge.7",
63
+ "@memberjunction/core-entities": "6.1.0-edge.7",
64
+ "@memberjunction/core-entities-server": "6.1.0-edge.7",
65
+ "@memberjunction/credentials": "6.1.0-edge.7",
66
+ "@memberjunction/data-context": "6.1.0-edge.7",
67
+ "@memberjunction/data-context-server": "6.1.0-edge.7",
68
+ "@memberjunction/doc-utils": "6.1.0-edge.7",
69
+ "@memberjunction/encryption": "6.1.0-edge.7",
70
+ "@memberjunction/entity-communications-base": "6.1.0-edge.7",
71
+ "@memberjunction/entity-communications-server": "6.1.0-edge.7",
72
+ "@memberjunction/esignature": "6.1.0-edge.7",
73
+ "@memberjunction/external-change-detection": "6.1.0-edge.7",
74
+ "@memberjunction/generic-database-provider": "6.1.0-edge.7",
75
+ "@memberjunction/global": "6.1.0-edge.7",
76
+ "@memberjunction/graphql-dataprovider": "6.1.0-edge.7",
77
+ "@memberjunction/integration-engine": "6.1.0-edge.7",
78
+ "@memberjunction/integration-engine-base": "6.1.0-edge.7",
79
+ "@memberjunction/integration-progress-artifacts": "6.1.0-edge.7",
80
+ "@memberjunction/integration-schema-builder": "6.1.0-edge.7",
81
+ "@memberjunction/interactive-component-types": "6.1.0-edge.7",
82
+ "@memberjunction/lists": "6.1.0-edge.7",
83
+ "@memberjunction/lists-base": "6.1.0-edge.7",
84
+ "@memberjunction/livekit-room-server": "6.1.0-edge.7",
85
+ "@memberjunction/network-utils": "6.1.0-edge.7",
86
+ "@memberjunction/notifications": "6.1.0-edge.7",
87
+ "@memberjunction/postgresql-dataprovider": "6.1.0-edge.7",
88
+ "@memberjunction/queue": "6.1.0-edge.7",
89
+ "@memberjunction/record-comparison": "6.1.0-edge.7",
90
+ "@memberjunction/redis-provider": "6.1.0-edge.7",
91
+ "@memberjunction/remote-browser-base": "6.1.0-edge.7",
92
+ "@memberjunction/remote-browser-cdp": "6.1.0-edge.7",
93
+ "@memberjunction/remote-browser-selfhost": "6.1.0-edge.7",
94
+ "@memberjunction/remote-browser-server": "6.1.0-edge.7",
95
+ "@memberjunction/scheduling-actions": "6.1.0-edge.7",
96
+ "@memberjunction/scheduling-base-types": "6.1.0-edge.7",
97
+ "@memberjunction/scheduling-engine": "6.1.0-edge.7",
98
+ "@memberjunction/scheduling-engine-base": "6.1.0-edge.7",
99
+ "@memberjunction/schema-engine": "6.1.0-edge.7",
100
+ "@memberjunction/search-engine": "6.1.0-edge.7",
101
+ "@memberjunction/server-extensions-core": "6.1.0-edge.7",
102
+ "@memberjunction/sql-dialect": "6.1.0-edge.7",
103
+ "@memberjunction/sql-parser": "6.1.0-edge.7",
104
+ "@memberjunction/sqlserver-dataprovider": "6.1.0-edge.7",
105
+ "@memberjunction/storage": "6.1.0-edge.7",
106
+ "@memberjunction/tag-engine": "6.1.0-edge.7",
107
+ "@memberjunction/tag-engine-base": "6.1.0-edge.7",
108
+ "@memberjunction/task-graph": "6.1.0-edge.7",
109
+ "@memberjunction/templates": "6.1.0-edge.7",
110
+ "@memberjunction/testing-engine": "6.1.0-edge.7",
111
+ "@memberjunction/testing-engine-base": "6.1.0-edge.7",
112
+ "@memberjunction/version-history": "6.1.0-edge.7",
113
113
  "@octokit/auth-app": "^7.1.5",
114
114
  "@octokit/rest": "^21.1.1",
115
115
  "@types/compression": "^1.8.1",
@@ -0,0 +1,118 @@
1
+ // ResolverBase transitively pulls in type-graphql decorators, which need the
2
+ // Reflect.metadata polyfill at import time.
3
+ import 'reflect-metadata';
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import { CompositeKey } from '@memberjunction/core';
6
+ import type { DatabaseProviderBase, EntityInfo, UserInfo } from '@memberjunction/core';
7
+ import { EntityRecordNameResolver } from '../resolvers/EntityRecordNameResolver.js';
8
+
9
+ /**
10
+ * `GetEntityRecordName` must not hand back a name the caller cannot READ.
11
+ *
12
+ * This resolver predates field-level security and took no user context at all, which made it an
13
+ * open side channel: the name never travelled through `MapFieldNamesToCodeNames`, the boundary
14
+ * that strips denied fields, so a caller denied READ on an entity's name field could still obtain
15
+ * that name from here.
16
+ *
17
+ * FLS turned the latent gap into a live bypass, and the shape of it is worth remembering: the
18
+ * form-field FK control falls through to this query PRECISELY WHEN the joined display column is
19
+ * denied. The fallback that exists to handle a denial was the thing that defeated it. Found in
20
+ * manual Phase 5 testing — a Contract form showed its Client's name while the grid, correctly,
21
+ * did not.
22
+ */
23
+
24
+ const DENIED_USER = { ID: 'u-1', Email: 'restricted@test' } as unknown as UserInfo;
25
+
26
+ /** An entity whose name field is denied or allowed, per the flag. */
27
+ function entityInfo(opts: { fls: boolean; deniedNames?: string[]; nameField?: string }): EntityInfo {
28
+ const nameFieldName = opts.nameField ?? 'Name';
29
+ return {
30
+ Name: 'Clients',
31
+ EnableFieldLevelSecurity: opts.fls,
32
+ Fields: [
33
+ { Name: 'ID', IsNameField: false },
34
+ { Name: nameFieldName, IsNameField: true },
35
+ { Name: 'Industry', IsNameField: false },
36
+ ],
37
+ GetDeniedReadFields: () => new Set(opts.deniedNames ?? []),
38
+ } as unknown as EntityInfo;
39
+ }
40
+
41
+ /** A provider that would happily return the name — the resolver must decide not to ask. */
42
+ function providerWith(entity: EntityInfo, getName = vi.fn().mockResolvedValue('Acme Manufacturing')) {
43
+ return {
44
+ Entities: [entity],
45
+ GetEntityRecordName: getName,
46
+ } as unknown as DatabaseProviderBase & { GetEntityRecordName: typeof getName };
47
+ }
48
+
49
+ /** Exposes the inner method so the test does not need a GraphQL context. */
50
+ class Probe extends EntityRecordNameResolver {
51
+ public Inner(md: DatabaseProviderBase, entityName: string, pk: CompositeKey, user?: UserInfo) {
52
+ // The resolver resolves the acting user from the payload; supply it directly.
53
+ vi.spyOn(this as never, 'GetUserFromPayload').mockReturnValue(user as never);
54
+ return this.InnerGetEntityRecordName(md, entityName, pk, { email: 'restricted@test' } as never);
55
+ }
56
+ }
57
+
58
+ const KEY = new CompositeKey([{ FieldName: 'ID', Value: 'c-1' }]);
59
+
60
+ describe('GetEntityRecordName — field-security gate', () => {
61
+ it('withholds the name when the name field is read-denied', async () => {
62
+ const getName = vi.fn().mockResolvedValue('Acme Manufacturing');
63
+ const md = providerWith(entityInfo({ fls: true, deniedNames: ['name'] }), getName);
64
+
65
+ const result = await new Probe().Inner(md, 'Clients', KEY, DENIED_USER);
66
+
67
+ expect(result.Success).toBe(false);
68
+ expect(result.RecordName).toBeUndefined();
69
+ // Not merely filtered after the fact — the lookup must not run at all. The provider's
70
+ // record-name cache is keyed by entity + primary key and NOT by user, so a name fetched
71
+ // here would be visible to any later caller.
72
+ expect(getName).not.toHaveBeenCalled();
73
+ });
74
+
75
+ it('returns the name when the user may read the name field', async () => {
76
+ const md = providerWith(entityInfo({ fls: true, deniedNames: ['industry'] }));
77
+
78
+ const result = await new Probe().Inner(md, 'Clients', KEY, DENIED_USER);
79
+
80
+ expect(result.Success).toBe(true);
81
+ expect(result.RecordName).toBe('Acme Manufacturing');
82
+ });
83
+
84
+ it('is inert when the entity has field security switched off', async () => {
85
+ // Nearly every entity. A denial set that would otherwise match must not apply.
86
+ const md = providerWith(entityInfo({ fls: false, deniedNames: ['name'] }));
87
+
88
+ const result = await new Probe().Inner(md, 'Clients', KEY, DENIED_USER);
89
+
90
+ expect(result.Success).toBe(true);
91
+ expect(result.RecordName).toBe('Acme Manufacturing');
92
+ });
93
+
94
+ it('honours a designated name field that is not literally called "Name"', async () => {
95
+ const md = providerWith(entityInfo({ fls: true, nameField: 'Title', deniedNames: ['title'] }));
96
+
97
+ const result = await new Probe().Inner(md, 'Clients', KEY, DENIED_USER);
98
+
99
+ expect(result.Success).toBe(false);
100
+ });
101
+
102
+ it('passes the acting user to the provider so row-level security has an identity', async () => {
103
+ const getName = vi.fn().mockResolvedValue('Acme Manufacturing');
104
+ const md = providerWith(entityInfo({ fls: true }), getName);
105
+
106
+ await new Probe().Inner(md, 'Clients', KEY, DENIED_USER);
107
+
108
+ expect(getName).toHaveBeenCalledWith('Clients', expect.anything(), DENIED_USER);
109
+ });
110
+
111
+ it('does not disclose which of "missing" or "restricted" applies', async () => {
112
+ // The denial status must read the same as a genuine not-found, so the query cannot be
113
+ // used to probe for which entities carry restricted name fields.
114
+ const denied = await new Probe().Inner(
115
+ providerWith(entityInfo({ fls: true, deniedNames: ['name'] })), 'Clients', KEY, DENIED_USER);
116
+ expect(denied.Status).not.toMatch(/denied|permission|restricted|field.security/i);
117
+ });
118
+ });
@@ -2769,6 +2769,26 @@ describe('RealtimeClientSessionResolver — app awareness (applicationId / appCo
2769
2769
  const relayArg = executeRelayedToolMock.mock.calls[0][0] as { AllowedAgents?: Array<{ agentId: string }> };
2770
2770
  expect(relayArg.AllowedAgents?.map(a => a.agentId)).toEqual(['skip-1']);
2771
2771
  });
2772
+
2773
+ it('passes the persisted directActions from the session config into ExecuteRelayedTool', async () => {
2774
+ currentProvider = makeProvider(() =>
2775
+ makeSessionEntity({
2776
+ Config_: JSON.stringify({
2777
+ targetAgentID: 'lead-1',
2778
+ directActions: { enabled: true, actionNames: ['SendEmail'] },
2779
+ }),
2780
+ }),
2781
+ );
2782
+ executeRelayedToolMock.mockResolvedValue({ ResultJson: '{"ok":true}', Success: true });
2783
+ const resolver = makeResolver();
2784
+
2785
+ await resolver.ExecuteRealtimeSessionTool(
2786
+ 'session-1', 'call-1', 'SendEmail', '{"To":"user@test.com"}', makeCtx(), makePubSub(),
2787
+ );
2788
+
2789
+ const relayArg = executeRelayedToolMock.mock.calls[0][0] as { DirectActions?: { enabled: boolean; actionNames: string[] } };
2790
+ expect(relayArg.DirectActions).toEqual({ enabled: true, actionNames: ['SendEmail'] });
2791
+ });
2772
2792
  });
2773
2793
 
2774
2794
  describe('RealtimeClientSessionResolver — scoped-anonymous elevation (issue #3371)', () => {
@@ -0,0 +1,191 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import type { Request, Response, NextFunction } from 'express';
3
+ import { MJGlobal, RegisterClass } from '@memberjunction/global';
4
+ import { BaseRealtimeModel, RealtimeProxyRegistry, IRealtimeSession, RealtimeSessionParams } from '@memberjunction/ai';
5
+ import { createRealtimeSdpBrokerRouter, createOpenAILiveBrokerRouter, requireTicket } from '../rest/RealtimeSdpBrokerHandler.js';
6
+
7
+ @RegisterClass(BaseRealtimeModel, 'MockOpenAILiveForBrokerTest', 10)
8
+ class MockOpenAILiveForBrokerTest extends BaseRealtimeModel {
9
+ public static LastSessionConfig: Record<string, unknown> | undefined;
10
+
11
+ public override async StartSession(_params: RealtimeSessionParams): Promise<IRealtimeSession> {
12
+ throw new Error('Not needed in broker test');
13
+ }
14
+
15
+ public override async ExchangeWebRtcSdp(
16
+ offerSdp: string,
17
+ sessionConfig?: Record<string, unknown>
18
+ ): Promise<{ answerSdp: string; sessionId: string; prebillSeconds: number }> {
19
+ MockOpenAILiveForBrokerTest.LastSessionConfig = sessionConfig;
20
+ if (offerSdp === 'trigger_error') {
21
+ throw new Error('Upstream OpenAI internal error details');
22
+ }
23
+ return {
24
+ answerSdp: `mock_answer_for_${offerSdp}`,
25
+ sessionId: 'sess_mock_123',
26
+ prebillSeconds: 15,
27
+ };
28
+ }
29
+ }
30
+
31
+ function makeMockReqRes(query: Record<string, string>, body: unknown): {
32
+ req: Request;
33
+ res: Response;
34
+ getStatus: () => number;
35
+ getJson: () => Record<string, unknown>;
36
+ } {
37
+ let statusCode = 200;
38
+ let jsonBody: Record<string, unknown> = {};
39
+
40
+ const res = {
41
+ locals: {} as Record<string, unknown>,
42
+ status(code: number): Response {
43
+ statusCode = code;
44
+ return res as unknown as Response;
45
+ },
46
+ json(data: Record<string, unknown>): Response {
47
+ jsonBody = data;
48
+ return res as unknown as Response;
49
+ },
50
+ };
51
+
52
+ const req = {
53
+ method: 'POST',
54
+ url: '/',
55
+ query,
56
+ body,
57
+ headers: {},
58
+ _body: true,
59
+ };
60
+
61
+ return {
62
+ req: req as unknown as Request,
63
+ res: res as unknown as Response,
64
+ getStatus: () => statusCode,
65
+ getJson: () => jsonBody,
66
+ };
67
+ }
68
+
69
+ describe('RealtimeSdpBrokerHandler', () => {
70
+ let router: ReturnType<typeof createRealtimeSdpBrokerRouter>;
71
+
72
+ beforeEach(() => {
73
+ router = createRealtimeSdpBrokerRouter();
74
+ });
75
+
76
+ it('createOpenAILiveBrokerRouter is backwards-compatible alias', () => {
77
+ expect(createOpenAILiveBrokerRouter).toBe(createRealtimeSdpBrokerRouter);
78
+ });
79
+
80
+ it('requireTicket middleware rejects missing ticket without calling next()', () => {
81
+ const { req, res, getStatus, getJson } = makeMockReqRes({}, {});
82
+ const next = vi.fn();
83
+ requireTicket(req, res, next as NextFunction);
84
+ expect(getStatus()).toBe(401);
85
+ expect(getJson().error).toContain('missing ticket');
86
+ expect(next).not.toHaveBeenCalled();
87
+ });
88
+
89
+ it('requireTicket middleware rejects invalid ticket without calling next()', () => {
90
+ const { req, res, getStatus, getJson } = makeMockReqRes({ ticket: 'non-existent-ticket' }, {});
91
+ const next = vi.fn();
92
+ requireTicket(req, res, next as NextFunction);
93
+ expect(getStatus()).toBe(401);
94
+ expect(getJson().error).toContain('invalid or expired ticket');
95
+ expect(next).not.toHaveBeenCalled();
96
+ });
97
+
98
+ it('rejects missing ticket with 401', async () => {
99
+ const { req, res, getStatus, getJson } = makeMockReqRes({}, { sdp: 'v=0...' });
100
+ // @ts-expect-error reaching the route handler directly
101
+ await router.handle(req, res, () => {});
102
+ expect(getStatus()).toBe(401);
103
+ expect(getJson().error).toContain('missing ticket');
104
+ });
105
+
106
+ it('rejects invalid or expired ticket with 401', async () => {
107
+ const { req, res, getStatus, getJson } = makeMockReqRes({ ticket: 'non-existent-ticket' }, { sdp: 'v=0...' });
108
+ // @ts-expect-error reaching the route handler directly
109
+ await router.handle(req, res, () => {});
110
+ expect(getStatus()).toBe(401);
111
+ expect(getJson().error).toContain('invalid or expired ticket');
112
+ });
113
+
114
+ it('rejects missing sdp in request body with 400', async () => {
115
+ const ticket = RealtimeProxyRegistry.Instance.Issue({
116
+ UpstreamUrl: 'https://api.openai.com/v1/live/sessions',
117
+ UpstreamAuthHeader: 'test-key',
118
+ DriverClass: 'MockOpenAILiveForBrokerTest',
119
+ TTLSeconds: 60,
120
+ });
121
+
122
+ const { req, res, getStatus, getJson } = makeMockReqRes({ ticket: ticket.ID }, {});
123
+ // @ts-expect-error reaching the route handler directly
124
+ await router.handle(req, res, () => {});
125
+ expect(getStatus()).toBe(400);
126
+ expect(getJson().error).toContain('Missing or invalid sdp');
127
+ });
128
+
129
+ it('exchanges SDP offer for answer with a valid ticket and consumes the ticket', async () => {
130
+ const serverConfig = { model: 'gpt-live-1', authoritative: true };
131
+ const ticket = RealtimeProxyRegistry.Instance.Issue({
132
+ UpstreamUrl: 'https://api.openai.com/v1/live/sessions',
133
+ UpstreamAuthHeader: 'test-key',
134
+ DriverClass: 'MockOpenAILiveForBrokerTest',
135
+ SessionConfig: serverConfig,
136
+ TTLSeconds: 60,
137
+ });
138
+
139
+ // Client attempts to pass its own session config
140
+ const clientAttemptedSession = { model: 'tampered-model' };
141
+ const { req, res, getStatus, getJson } = makeMockReqRes(
142
+ { ticket: ticket.ID },
143
+ { sdp: 'offer_123', session: clientAttemptedSession }
144
+ );
145
+ // @ts-expect-error reaching the route handler directly
146
+ await router.handle(req, res, () => {});
147
+
148
+ expect(getStatus()).toBe(200);
149
+ const json = getJson();
150
+ expect(json.answerSdp).toBe('mock_answer_for_offer_123');
151
+ expect(json.sessionId).toBe('sess_mock_123');
152
+ expect(json.prebillSeconds).toBe(15);
153
+ // Verify server's authoritative session config was passed, not the client's
154
+ expect(MockOpenAILiveForBrokerTest.LastSessionConfig).toEqual(serverConfig);
155
+
156
+ // Ticket is single-use: consuming again should fail
157
+ const reused = makeMockReqRes({ ticket: ticket.ID }, { sdp: 'offer_123' });
158
+ // @ts-expect-error reaching the route handler directly
159
+ await router.handle(reused.req, reused.res, () => {});
160
+ expect(reused.getStatus()).toBe(401);
161
+ });
162
+
163
+ it('rejects ticket without upstream auth header with 500', async () => {
164
+ const ticket = RealtimeProxyRegistry.Instance.Issue({
165
+ UpstreamUrl: 'https://api.openai.com/v1/live/sessions',
166
+ DriverClass: 'MockOpenAILiveForBrokerTest',
167
+ TTLSeconds: 60,
168
+ });
169
+
170
+ const { req, res, getStatus, getJson } = makeMockReqRes({ ticket: ticket.ID }, { sdp: 'offer_123' });
171
+ // @ts-expect-error reaching the route handler directly
172
+ await router.handle(req, res, () => {});
173
+ expect(getStatus()).toBe(500);
174
+ expect(getJson().error).toContain('missing upstream credentials');
175
+ });
176
+
177
+ it('sanitizes 502 error when upstream exchange throws without leaking internal error', async () => {
178
+ const ticket = RealtimeProxyRegistry.Instance.Issue({
179
+ UpstreamUrl: 'https://api.openai.com/v1/live/sessions',
180
+ UpstreamAuthHeader: 'test-key',
181
+ DriverClass: 'MockOpenAILiveForBrokerTest',
182
+ TTLSeconds: 60,
183
+ });
184
+
185
+ const { req, res, getStatus, getJson } = makeMockReqRes({ ticket: ticket.ID }, { sdp: 'trigger_error' });
186
+ // @ts-expect-error reaching the route handler directly
187
+ await router.handle(req, res, () => {});
188
+ expect(getStatus()).toBe(502);
189
+ expect(getJson().error).toBe('Realtime WebRTC handshake failed');
190
+ });
191
+ });
@@ -91,6 +91,14 @@ class Probe extends ResolverBase {
91
91
  public RunDynamic(input: RunDynamicViewInput, provider: DatabaseProviderBase) {
92
92
  return this.RunDynamicViewGeneric(input, provider, fakePayload(), undefined as unknown as PubSubEngine);
93
93
  }
94
+
95
+ /** The whole boundary screen, as the RunView* entry points call it. */
96
+ public ScreenAll(
97
+ clauses: { extraFilter?: string | null; orderBy?: string | null; userSearchString?: string | null; overrideExcludeFilter?: string | null },
98
+ provider: DatabaseProviderBase,
99
+ ) {
100
+ return this.screenClientViewClauses(clauses, provider as unknown as IMetadataProvider);
101
+ }
94
102
  }
95
103
 
96
104
  describe('ResolverBase.findBy — ExtraFilter escaping', () => {
@@ -251,6 +259,61 @@ describe('ResolverBase — GraphQL-boundary ExtraFilter AST screen', () => {
251
259
  });
252
260
  });
253
261
 
262
+ describe('ResolverBase.screenClientViewClauses — UserSearchString is free text, not a clause (#4392)', () => {
263
+ // The base-view AST screen wraps its argument as `SELECT 1 FROM x WHERE (<clause>)` and fails
264
+ // closed when that does not parse. Applied to UserSearchString it rejected everything that
265
+ // wasn't coincidentally valid SQL — which is most of what people type into a search box.
266
+ // UserSearchString never reaches SQL as a fragment: createViewUserSearchSQL builds the
267
+ // predicate and lands the text as an escaped literal, so the screen has nothing to screen.
268
+
269
+ const provider = () => fakeProvider({ params: null });
270
+
271
+ const searchTerms = [
272
+ 'Marcus Chen', // a space — parses as nothing
273
+ "O'Leary", // unterminated literal
274
+ "Marcus O'Leary Chen", // both
275
+ 'Smith, John', // punctuation
276
+ '50% off', // LIKE metacharacter
277
+ 'a_b [c]', // more LIKE metacharacters
278
+ 'select', // a keyword as a term
279
+ 'drop table users', // keywords with spaces
280
+ 'Union Pacific', // a real company name
281
+ "x' OR '1'='1", // an injection attempt — escaped as a literal downstream
282
+ ' spaced out ',
283
+ 'café ☕',
284
+ ];
285
+
286
+ it.each(searchTerms)('lets %j through the boundary screen', (term) => {
287
+ expect(() => new Probe().ScreenAll({ userSearchString: term }, provider())).not.toThrow();
288
+ });
289
+
290
+ it('still screens the three real clause fragments alongside it', () => {
291
+ const p = new Probe();
292
+ const md = provider();
293
+
294
+ expect(() =>
295
+ p.ScreenAll({ userSearchString: 'Marcus Chen', extraFilter: `EXISTS (SELECT 1 FROM __mj.[User])` }, md)
296
+ ).toThrow(/entity base view/);
297
+
298
+ expect(() =>
299
+ p.ScreenAll({ userSearchString: 'Marcus Chen', orderBy: '(SELECT COUNT(*) FROM __mj.APIKey)' }, md)
300
+ ).toThrow(/entity base view/);
301
+
302
+ expect(() =>
303
+ p.ScreenAll({ userSearchString: 'Marcus Chen', overrideExcludeFilter: `EXISTS (SELECT 1 FROM __mj.[User])` }, md)
304
+ ).toThrow(/entity base view/);
305
+ });
306
+
307
+ it('RunDynamicViewGeneric carries a multi-word search term through to RunView', async () => {
308
+ const captured: Captured = { params: null };
309
+ const input = { EntityName: ENTITY_NAME, UserSearchString: "Marcus O'Leary" } as RunDynamicViewInput;
310
+
311
+ await new Probe().RunDynamic(input, fakeProvider(captured));
312
+
313
+ expect(captured.params?.UserSearchString).toBe("Marcus O'Leary");
314
+ });
315
+ });
316
+
254
317
  describe('ResolverBase.RunViewByNameGeneric — view-name escaping', () => {
255
318
  it('escapes the client-supplied view name', async () => {
256
319
  const captured: Captured = { params: null };