I'm intrigued to hear about expression indices - sounds really cool.

Without those, another option is just to have a separate class/table. Suppose the key to the AR array is the address type (Home, Office, etc.); then you could have:

Class Sample.Person1 Extends (%Persistent, %Populate)
{

Property Name As %String;

Relationship Addresses As Sample.PersonAddress [ Cardinality = children, Inverse = Person ];

}

Class Sample.PersonAddress Extends (%Persistent, %Populate)
{

Relationship Person As Sample.Person1 [ Cardinality = parent, Inverse = Addresses ];

Property Type As %String;

Property Address As Sample.Address;

}

Sample.PersonAddress then can have whatever plain old normal indices you want (except bitmap indices - if you want those, make it one-to-many instead of parent/child).

Generally: any time you add an array property - especially an array of objects - it's worth stepping back and thinking about whether it should just be its own full-blown class/table.

@Richard Schilke , you should be able to share a session by specifying the same CSP session cookie path for your REST web application and the web application(s) through which your Zen pages are accessed. Alternatively, you could assign the web applications the same GroupById in their web application configuration.

You likely also need to configure your REST handler class (your subclass of AppS.REST.Handler) to use CSP sessions (from your earlier description, I assumed you had). This is done by overriding the UseSession class parameter and setting it to 1 (instead of the default 0).

To reference header data in the UserInfo classmethod, you should just be able to use %request (an instance of %CSP.Request) and %response (an instance of %CSP.Response) as appropriate for request/response headers.

@Richard Schilke - great!

We have support for filtering/sorting on the collection endpoints already, though perhaps not fully documented. Pagination is a challenge from a REST standpoint but I'd love to add support for it (perhaps in conjunction with "advanced search") at some point. I'm certainly open to ideas on the implementation there. :)

Users are the best, because if you don't have them, it's all just pointlessly academic. ;)

@Richard Schilke - on further review, it's an issue with the Action map. See my response in https://github.com/intersystems/apps-rest/issues/7 (and thank you for filing the issue!). I'll still create a new release soon to pick up the projection bug you found.

Regarding headers - you can reference %request anywhere in the REST model classes, it just breaks abstraction a bit. (And for the sake of unit testing, it would be good to behave reasonably if %request happens not to be defined, unless your planning on using Forgery or equivalent.)

Regarding sessions - yes, you can share a session with a Zen application via a common session cookie path or using GroupById. You can reference this as needed as well, though I'd recommend wrapping any %session (or even %request) dependencies in the user context object that gets passed to CheckPermissions().

On IRIS there's $zu(209,code) - e.g.:

USER>w $zu(209,3)
<3> The system cannot find the path specified.

In IRIS 2020.1+ you don't need the $zu:

USER>w $System.Util.GetOSErrorText(3)
<3> The system cannot find the path specified. 

AFAIK there's no Caché/Ensemble equivalent. (Or maybe this is in newer Caché/Ensemble versions.)

Thanks for posting - I'm taking a look now. This issue is starting to ring a bell; I think this looks like a bug we fixed in another branch internally to my team. (I've had reconciling the GitHub branch and our internal branch on my list for some time - I'll try to at least get this fix in, soon.)

Re: customizing mappings of relationship/object properties, see https://docs.intersystems.com/healthconnectlatest/csp/docbook/Doc.View.cls?KEY=GJSON_adaptor#GJSON_adaptor_xdata_define - this is doable in %JSON.Adaptor mapping XData blocks via the Mapping attribute for an object-valued property included in the mapping.

What's the MEDIATYPE parameter in Lookups.Terms (the model class)? The Accept header should be set to that.

Also, you shouldn't need to set Content-Type on a GET, because you're not supplying any content in the request. (It's possible that it's throwing things off.)

If you can reproduce a simple case independent of your code (that you'd be comfortable to share), feel free to file a GitHub issue and I'll try to knock it out soon.

@Richard Schilke, I'm glad to hear that you're planning on using this, and we're grateful for your feedback.

Quick fix should just be: Do ##class(AppS.REST.ResourceMap).ModelClassDelete("Data.DocHead")

Background: metadata on REST resources and actions is kept in the AppS.REST.ResourceMap and AppS.REST.ActionMap classes. These are maintained by projections and it seems there's an edge case where data isn't getting cleaned up properly. I've created a GitHub issue as a reminder to find and address the root cause: https://github.com/intersystems/apps-rest/issues/5

I've had a few times where I've needed to do a targeted restore based on a journal (e.g., restoring a week of work an intern accidentally reverted; this would work for class definition changes if you could find the right window). Just to add to what Dmitriy and Erik have said, assuming your case is eligible, here's a code sample using the %SYS.Journal classes (modified from one of the times I had to do this):

Class DC.Demo.JrnFix
{

/// Intended to be run from terminal. Find the right values to put in the variables at the top first.
/// Also, use at your own risk.
ClassMethod Run()
{
    // Path to journal file (find this based on timestamps)
    Set file = "/path/to/journal/file"
    
    // Path to database containing data that was killed
    // (assuming killed during transaction so individual nodes are journalled as ZKILL)
    Set dbJrn = "/path/to/database/directory/"
    
    // First problem offset/address (find a real value for this via management portal or further
    // %SYS.Journal scripting - e.g., output from below with full range of addresses used)
    Set addStart = 0
    
    // Last problem offset/address (find a real value for this via management portal or further
    // %SYS.Journal scripting - e.g., output from below with full range of addresses used)
    Set addEnd = 1000000000
    
    // Global that you're looking to restore - as much of the global reference as is possible
    Set global = "MyApp.DataD"
    
    Set jrn = ##class(%SYS.Journal.File).%OpenId(file)
    
    #dim rec As %SYS.Journal.SetKillRecord
    
    TSTART
    Set rec = jrn.GetRecordAt(addEnd)
    Do {
        If ((rec.%IsA("%SYS.Journal.SetKillRecord"))&&(rec.DatabaseName=dbJrn)) {
            If (rec.GlobalNode [ global) {
                w rec.Address,!
                Set @rec.GlobalNode = rec.OldValue
            } Else {
                // Keep track of other globals we see (optional)
                Set skippedList($p(rec.GlobalNode,"(")) = ""
            }
        }
        Set rec = rec.Prev
    } While (rec.Address > addStart)
    ZWrite skippedList
    Break //At this point, examine things, TCOMMIT, and quit if things look good.
    TROLLBACK
}

}

A good approach is adding application and/or matching roles for the web application (in the web application's security configuration).

An application role is granted to users of the web application while in that context only. A matching role provides additional privileges to users holding a particular specified role.

A lazy approach would be adding %All as an application role, but that likely exposes too much. This is better than giving UnknownUser %All, for sure, but it's best to provide more granular roles than %All (in this case and more generally) - say, a role that provides Read access on the namespace's default routine DB and R/W on the namespace's default global/data DB.