Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,35 @@ Details: [docs/hub/triggers.md](./docs/hub/triggers.md),
[docs/hub/sms.md](./docs/hub/sms.md), [docs/hub/push.md](./docs/hub/push.md),
[docs/hub/account.md](./docs/hub/account.md).

## Cloud functions

Norbix does not host code: a trigger, a scheduler task or a manual call runs a
function in **your** cloud (AWS Lambda, Azure Functions, Google Cloud Functions)
through a Code integration, and the function receives one JSON envelope — the
event, the data, a token to call Norbix back as a service user, and your
config. `parse_function_event` reads it; `client_options()` turns it into a
client for that user:

```python
from norbix_python import Norbix, parse_function_event


def handler(raw, _context):
event = parse_function_event(raw) # raises on an unknown shape / specVersion
if event.event == "database.record.updated":
change = event.as_database_record_change() # .old / .new as dicts, None when absent
norbix = Norbix(**event.client_options()) # acts as the run-as user, 15 minutes
...
return {"ok": True}
```

From your backend: `norbix.hub.code.get_code_functions(integrationId=...)`,
`get_code_function_qualifiers(...)`, `invoke_code_function(functionRef=..., payload=..., invocationType="Sync")`,
`get_code_function_event_sample(source="database")` and
`get_code_function_runs(triggerId=...)`. Details and the typed views
(`as_user_change`, `as_file_change`, `as_payment`, `as_scheduled_task`, `payload()`):
[docs/hub/code.md](./docs/hub/code.md).

## Errors

```python
Expand Down
1 change: 1 addition & 0 deletions docs/hub/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
| --- | ---: |
| [`account`](./account.md) | 61 |
| [`ai`](./ai.md) | 14 |
| [`code`](./code.md) | 33 |
| [`database`](./database.md) | 67 |
| [`echo`](./echo.md) | 1 |
| [`email`](./email.md) | 1 |
Expand Down
118 changes: 118 additions & 0 deletions docs/hub/code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# HUB · Code

Access with `norbix.hub.code` (and the same methods, awaited, on
`AsyncNorbix`). The Code module holds the credentials of your own cloud
(AWS Lambda, Azure Functions, Google Cloud Functions) as **Code
integrations**, lists the functions it may call, invokes them by hand, and
is what a trigger's `Code` action and a scheduler `CodeFunctionalCall` task
point at. Norbix does not host code: the function runs in your cloud and
receives one event envelope — see [Cloud functions](#cloud-functions).

Keyword arguments other than the path ids, `timeout` and `bearer_token` are
the request: the query string for `GET` / `DELETE`, the JSON body otherwise.
Integration ids look like `int_…`.

## Module and integrations

| Method | Verb | Path | Scope | Parameters go in |
| --- | --- | --- | --- | --- |
| `enable_code` | `PUT` | `/{version}/code/enable` | `project` | — |
| `disable_code` | `PUT` | `/{version}/code/disable` | `project` | — |
| `get_code_integrations` | `GET` | `/{version}/code/integrations` | `project` | query |
| `get_code_integration` | `GET` | `/{version}/code/integrations/{id}` | `project` | path: `id` |
| `save_code_integration` | `POST` | `/{version}/code/integrations` | `project` | JSON body |
| `test_code_integration` | `POST` | `/{version}/code/integrations/test` | `project` | JSON body |
| `confirm_code_integration_human_delivery` | `POST` | `/{version}/code/integrations/confirm-human-delivery` | `project` | JSON body |
| `set_code_integration_as_default` | `PUT` | `/{version}/code/integrations/{Id}/default` | `project` | path: `id` |
| `enable_code_integration` | `PUT` | `/{version}/code/integrations/{Id}/enable` | `project` | path: `id` |
| `disable_code_integration` | `PUT` | `/{version}/code/integrations/{Id}/disable` | `project` | path: `id` |
| `delete_code_integration` | `DELETE` | `/{version}/code/integrations/{Id}` | `project` | path: `id` |

The 17 `*_marketplace_*` methods of the module are from an earlier
generation of the gateway contract (the `…/bindings` routes); the marketplace
is documented when its own campaign step lands.

## Cloud functions

The five routes of the gateway contract `Code.Functions.md` (§3.3, §3.4,
§5.1). Every method, its verb, path, query and body are asserted in
`tests/hub/test_code.py`, for the sync and the async client.

| Method | Verb | Path | Scope | Parameters go in |
| --- | --- | --- | --- | --- |
| `get_code_functions` | `GET` | `/{version}/code/functions` | `project` | query: `integrationId` (required), `prefix`, `tag` (`key` or `key=value`), `pageSize`, `startingAfter`, `endingBefore` |
| `get_code_function_qualifiers` | `GET` | `/{version}/code/functions/qualifiers` | `project` | query: `integrationId`, `functionId` |
| `invoke_code_function` | `POST` | `/{version}/code/functions/invoke` | `project` | JSON body: `functionRef` `{integrationId, functionId, qualifier?}`, `payload?`, `invocationType` (`Sync` / `Async`), `runAsUserId?`, `config?` |
| `get_code_function_event_sample` | `GET` | `/{version}/code/functions/event-sample` | `project` | query: `source` (`database` · `membership` · `files` · `payments` · `ai` · `scheduler` · `manual`), `event?`, `schemaId?` |
| `get_code_function_runs` | `GET` | `/{version}/code/functions/runs` | `project` | query: exactly one of `triggerId` / `taskId`, `limit` (1–50, default 20) |

```python
integration_id = "int_6rBACnEaxQ0x1jTiHtAuWV"
functions = norbix.hub.code.get_code_functions(integrationId=integration_id, prefix="orders-")
qualifiers = norbix.hub.code.get_code_function_qualifiers(integrationId=integration_id, functionId="orders-on-update")
result = norbix.hub.code.invoke_code_function(
functionRef={"integrationId": integration_id, "functionId": "orders-on-update", "qualifier": "prod"},
payload={"orderId": "6ac54a59212c29912eaddd81", "dryRun": True},
invocationType="Sync",
)
sample = norbix.hub.code.get_code_function_event_sample(source="database", event="database.record.updated")
runs = norbix.hub.code.get_code_function_runs(triggerId="trg_…", limit=5)
```

`get_code_functions` is a live listing from the provider (no Norbix-side
state), paged in memory on the function id. An Azure integration cannot list
(`CM-ERRORS-CODE-010`) — type the function name instead. `invoke_code_function`
answers `{statusCode, output, durationMs, requestId?, logs?}`; `output` is the
function's body as JSON when it parses, else the raw text. `runs` is the last
20 (up to 50) runs of one trigger or task, newest first:
`{firedAt, event, source, succeeded, statusCode?, durationMs?, error?, requestId?}`.

### The event envelope — `parse_function_event`

Inside the function, the SDK reads the envelope Norbix sent
(`CodeFunctionEventDto`, one shape for every source) and gives you a client
that acts as the run-as user:

```python
import json

from norbix_python import Norbix, parse_function_event


def handler(raw, _context): # AWS Lambda; Azure / Google pass the body the same way
event = parse_function_event(raw) # str, bytes or the decoded dict; raises on specVersion != "1"
if event.event != "database.record.updated":
return {"ok": True}

change = event.as_database_record_change() # .old / .new as dicts, None when absent
old, new = change.old or {}, change.new or {}
if old.get("status") != new.get("status") and new.get("status") == "processing":
norbix = Norbix(**event.client_options()) # project, env, region, hub / api urls, the run-as token
norbix.api.database.insert_one(
"order_events",
document=json.dumps({"orderId": new["_id"], "status": "processing", "eventId": event.event_id}),
)
return {"ok": True}
```

| On the event | What it is |
| --- | --- |
| `event`, `event_id`, `id`, `source`, `created_on`, `spec_version` | the envelope header; `event` is the handler key (`database.record.updated`, `scheduler.task.fired`, `manual.invoke`, …), `source` one of `database` · `membership` · `files` · `payments` · `ai` · `scheduler` · `manual` |
| `context` | `project_id`, `account_id`, `environment`, `region`, `hub_url`, `api_url`, `echo_url`, `trigger_id` / `task_id`, `correlation_id`, `caller` (`user_id`, `email`, `kind` — data, never authority), `run_as` (`user_id`, `access_token` valid 15 minutes, `expires_at`) |
| `as_database_record_change()` | `collection`, `schema_id`, `schema_version`, `record_id`, `old`, `new`, `truncated`, `old_ref` / `new_ref` (over 200 KB the documents are left out and the refs say where to fetch them) |
| `as_user_change()` | `user_id`, `old`, `new` |
| `as_file_change()` | `old` (delete), `new` (upload), `path` |
| `as_payment()` | `integration_id`, `provider`, `provider_event_id`, `event_name`, `status`, `amount_minor`, `currency`, `external_ids`, `occurred_on`, `received_on` |
| `as_scheduled_task()` | `task_id`, `name`, `fired_at` |
| `as_ai_usage()` | `payload`, `target_user_id` |
| `payload()` | the caller's JSON on a manual invoke, or a scheduler task's static payload; `None` when there is none |
| `config` | the developer's JSON bound at fire time; `None` when the action has none |
| `client_options()` | the `Norbix` / `AsyncNorbix` keyword arguments: `project_id`, `account_id`, `env`, `region`, `bearer_token`, `base_url_hub`, `base_url_api` |
| `raw` | the decoded envelope, for anything else |

A view of a slot the event does not carry (a file event asked for a record
change) raises `NorbixFunctionEventError`, as does a payload that is not an
object or not `specVersion` `"1"`. Timestamps stay ISO-8601 strings. Every
sample source, the refusals and the client built from an event are asserted
in `tests/test_functions.py`. A sample envelope for a local run comes from
`get_code_function_event_sample`.
Loading
Loading