How do I make API requests from a Plugin?


A Plugin never installs or imports anything. Navigator hands it the already-authenticated libraries through pluginParams, so a request from inside a Plugin is the same call you would write in a web app, minus the setup.

Two params matter here. BModels is the whole bruce-models namespace, which is where the per-record utilities live. getters is an ApiGetters instance already pointed at the current account, environment and session, and it is how you reach a raw API instance for anything the library has no utility for.

Using the library namespaces

Prefer a namespace over a hand-rolled request. Every namespace resolves the API instance itself, caches consistently with the rest of Navigator, and returns typed records, so a Plugin reading an Entity sees exactly what the Info View sees.

Note that every namespace function takes an optional api. Omitting it is correct inside Navigator: the library falls back to the same instance the app is already using. Pass one only when you deliberately want a different account or environment.


Entity
Entity list
Entity Type
async function Run(params) {
    const { BModels, visualRegister } = params.pluginParams;
    const { Entity, EntityType } = BModels;

    const selectedIds = visualRegister.GetSelected();
    if (!selectedIds.length) {
        return () => {};
    }

    // One Entity, with its Type expanded in the same request.
    const { entity } = await Entity.Get({
        entityId: selectedIds[0],
        expandEntityType: true,
        expandAttachments: true
    });

    // Bruce holds the system attributes, Data holds the schema attributes.
    console.log("ID", entity.Bruce.ID);
    console.log("Type", entity.Bruce["EntityType.ID"]);

    // Reads a nested attribute without hand-walking the object.
    const status = Entity.GetValue({
        entity: entity,
        path: "Data.Condition.Status"
    });
    console.log("Status", status);

    // The display name Navigator itself would show.
    const { entityType } = await EntityType.Get({
        entityTypeId: entity.Bruce["EntityType.ID"]
    });
    console.log("Name", Entity.CalculateName({
        entity: entity,
        type: entityType,
        defaultToId: true
    }));

    // Writing back. Only the attributes you supply are changed
    // because override is false.
    Entity.SetValue({
        entity: entity,
        path: "Data.Condition.Status",
        value: "INSPECTED"
    });
    await Entity.Update({
        entity: entity,
        override: false
    });

    // Tells Navigator the record moved, so the scene and panels reread it.
    params.pluginParams.refreshData({
        entityIds: [entity.Bruce.ID]
    });

    return () => {};
}

Custom requests

Not every endpoint has a namespace utility, and a new endpoint always lands in the API before it lands in the library. For those, take the API instance directly and call the verb yourself. This is the same instance every namespace uses underneath, so the account header, session header, base URL and cache are already correct.

getters.GetBruceApi() returns the Bruce API for the current account. Paths are appended to the API's base URL and take no leading slash. The base URL already carries the version root, so most paths are bare, for example entitytypes or entity/{id}. Some newer endpoints are addressed under an explicit v3/ prefix; the Swagger reference is the place to confirm which.


Raw requests
Non-library requests
Other APIs
async function Run(params) {
    const { getters } = params.pluginParams;

    // The Bruce API for the account Navigator is currently viewing.
    const api = getters.GetBruceApi();

    // GET. The second argument is the request params object, where
    // urlParams becomes the query string.
    const types = await api.GET("entitytypes", {
        urlParams: {
            "expandSettings": "yes"
        }
    });
    console.log(types);

    // A path that has no namespace utility yet.
    const links = await api.GET("entity/SOME_ENTITY_ID/links");
    console.log(links);

    // POST with a JSON body.
    await api.POST("entity/SOME_ENTITY_ID", {
        Data: {
            Note: "Written by a Plugin"
        }
    });

    // PUT and DELETE take the same shape.
    // await api.PUT("some.endpoint", body);
    // await api.DELETE("some.endpoint");

    return () => {};
}

Caching

Namespace reads are cached, which is what keeps Navigator responsive when several surfaces ask for the same record. A Plugin that has just written a record, or that is polling something that changes underneath it, needs to say so.

const { BModels, getters } = params.pluginParams;

// Bypasses the cache for this one read.
const { entity } = await BModels.Entity.Get({
    entityId: "SOME_ENTITY_ID",
    req: {
        noCache: true
    }
});

// Drops a record from the cache, so every later reader refetches it.
const api = getters.GetBruceApi();
api.Cache.Remove(BModels.Entity.GetCacheKey({
    entityId: "SOME_ENTITY_ID"
}));

Namespaces that write, such as Entity.Update, already clear what they invalidate. Clear the cache yourself only after a raw POST that the library does not know changed anything.