Object Storage

Store and serve unstructured files — text documents, images, exports, CAD drawings — from your Cloud Function.

📘

Required modules

Required modules: App Engine - Cloud Functions, App Engine - UI Components, and File Storage. Check your modules at Admin > Licenses. To obtain these modules, contact your IXON account manager or IXON distributor.

The Object Storage is for unstructured data — files, images, CAD drawings, exports, anything binary. For structured records, use the Document Store instead.

Cloud Function Hooks and Authorization

For security reasons, object access is provided via a Cloud Function, and authorization is path-based. The platform intentionally has "no opinion" about who's allowed to do what — the developer makes that choice, and builds the Cloud Function so that it returns a PathResponse (or ListPathResponse) describing the allowed scope. For more information about this topic, check the Authorization patterns section at the end of this article.

The hooks must live in the file ayayot/objectstorage_v1.py in your workspace, and must implement the methods shown in the examples below.

📘

About the ayayot folder

The folder name is required by the Ingress for routing object-storage hooks. Treat it as a fixed convention; don't rename it.

Listing objects

Use this hook to authorize a request to list objects. Listing returns metadata only — you can't fetch a single object by ID directly; you list, then filter.

const objectStorageClient = context.createObjectStorageClient();
const list = await objectStorageClient.getList();
const objects = list.entries;
from ixoncdkingress.function.context import FunctionContext, FunctionResource
from ixoncdkingress.function.objectstorage.types import (
    ResourceType, PathMapping, ListPathResponse,
)

def _request_for(
    context: FunctionContext,
) -> tuple[FunctionResource, ResourceType] | None:
    """Identify the target resource of the request, preferring assets to agents."""

    # A company is required in all cases
    if not context.company:
        return None

    target = context.agent
    type = ResourceType.AGENT

    if context.asset:
        target = context.asset
        type = ResourceType.ASSET

    if not target:
        return None

    return target, type

@FunctionContext.expose
def authorize_list(context: FunctionContext) -> ListPathResponse | None:
    """Allow listing for the active agent or asset."""
    if (target_type := _request_for(context)) is None:
        return None

    target, type = target_type
    return ListPathResponse(
        result="success",
        data=[PathMapping(publicId=target.public_id, type=type, path="")],
    )

Downloading a blob

An object can be downloaded as a Blob via getBlob.

const objectStorageClient = context.createObjectStorageClient();
const list = await objectStorageClient.getList();
const objectMeta = list.objects[0];

// Get the blob
const blob = await objectStorageClient.getBlob(objectMeta);

// Provide the file to the user
context.saveAsFile(blob, objectMeta.tags.name);
from ixoncdkingress.function.context import FunctionContext
from ixoncdkingress.function.objectstorage.types import PathData, PathResponse

@FunctionContext.expose
def authorize_download(context: FunctionContext) -> PathResponse | None:
    """
    Method to validate if and where the caller is allowed
    to download a blob from the object storage.
    """
    return PathResponse(
        result='success',
        data=PathData(path=''),
    )

Downloading multiple objects as a zip

Pass an array of object metadata to receive a zip blob containing multiple objects:

const objectStorageClient = context.createObjectStorageClient();
const list = await objectStorageClient.getList();
const objectMetas = list.objects;

// Get the blob
const zipBlob = await objectStorageClient.getBlob(objectMetas);

// Provide the file to the user
context.saveAsFile(zipBlob, 'object-metas.zip');
from ixoncdkingress.function.context import FunctionContext
from ixoncdkingress.function.objectstorage.types import PathData, PathResponse

@FunctionContext.expose
def authorize_download(context: FunctionContext) -> PathResponse | None:
    """
    Method to validate if and where the caller is allowed
    to download a blob from the object storage.
    """
    return PathResponse(
        result='success',
        data=PathData(path=''),
    )

Creating a folder inside the zip

To create folders inside the zip, the outputDir needs the full path to the folder, and it should be set up per file (full path, e.g. folder-a/folder-1/new-folder):

objectStorageClient = context.createObjectStorageClient();

const list = await objectStorageClient.getList();
const objectMetas = list.objects;

// Get the blob
const zipBlob = await objectStorageClient.getBlob(objectMetas.map((file, index) => ({...file, outputDir: `folder-${index}`}));

// Provide the file to the user
context.saveAsFile(zipBlob, 'object-metas.zip');
from ixoncdkingress.function.context import FunctionContext
from ixoncdkingress.function.objectstorage.types import PathData, PathResponse

"""
Method to validate if and where the caller is allowed
to download a blob from the object storage.
"""
@FunctionContext.expose
def authorize_download(context: FunctionContext) -> PathResponse | None:
    return PathResponse(
        result='success',
        data=PathData(path=''),
    )

Deleting an object

const objectStorageClient = context.createObjectStorageClient();
const list = await objectStorageClient.getList();
const objectMeta = list.objects[0];

await objectStorageClient.delete(objectMeta);
from ixoncdkingress.function.context import FunctionContext
from ixoncdkingress.function.objectstorage.types import PathData, PathResponse

"""
Method to validate if and where the caller is allowed
to delete a blob from the object storage.
"""
@FunctionContext.expose
def authorize_delete(context: FunctionContext) -> PathResponse | None:
    return PathResponse(
        result='success',
        data=PathData(path=''),
    )

Uploading an object

Files are uploaded as a Blob:

const objectStorageClient = context.createObjectStorageClient();
const file = new Blob();

const objectMeta = await objectStorageClient.store(file, {
    tags: { name: filename },
});
from ixoncdkingress.function.context import FunctionContext
from ixoncdkingress.function.objectstorage.types import PathData, PathResponse

"""
Method to validate if and where the caller is allowed
to delete a blob from the object storage.
"""
@FunctionContext.expose
def authorize_upload(context: FunctionContext) -> PathResponse | None:
    return PathResponse(
        result='success',
        data=PathData(path=''),
    )

Authorization patterns

The examples above grant the action to anyone calling the function. To restrict access:

  • Return None to deny the operation entirely.
  • Inspect context.user, context.company, context.agent, context.asset to decide whether the caller is allowed.
  • Return a narrower path to scope the action to a specific subset of objects.

Simulation

While the Cloud Functions handles the path-generation and authorization side of things, the UI Component simulator has a simulated instance of the Object Storage having the following characteristics:

  • It can be used without a Cloud Function, in which case all calls are always allowed;
  • It can be used with a local Cloud Function, where clals are managed by any implemented authorization functions.
  • This simulated storage is in-memory and is not persisted to disk, nor can it be pre-populated on startup.
  • The instance will exist as long as the simulator runs.