diff --git a/README.md b/README.md index 38505ac4..923b2ff5 100644 --- a/README.md +++ b/README.md @@ -69,17 +69,22 @@ execution = client.durable.start( ) print(execution.id, execution.status) -execution = client.durable.get( +owner_client = VolcanoClient( + api_url="https://api.volcano.dev", + anon_key="your-anon-key", + access_token="your-platform-user-token", +) +execution = owner_client.durable.get( "00000000-0000-4000-8000-000000000001", "order-pipeline", execution.id ) print(execution.is_terminal, execution.result) -executions = client.durable.list( +executions = owner_client.durable.list( "00000000-0000-4000-8000-000000000001", "order-pipeline", status="running" ) print(executions.total, executions.has_more) -client.durable.stop( +owner_client.durable.stop( "00000000-0000-4000-8000-000000000001", "order-pipeline", execution.id ) @@ -239,18 +244,18 @@ A function's own response, HTTP 403, or a network failure never triggers this re Anonymous and service keys do not refresh. `durable.start()` begins an execution of a deployed durable function and returns -a handle rather than a result: an execution can run for hours, so its result is -read back with `durable.get()`. It takes the credential `functions.invoke()` +a handle rather than a result: an execution can run for up to 366 days, so its +result is read back with `durable.get()`. It takes the credential `functions.invoke()` takes, and is the only durable operation an application credential may perform. An `execution_name` makes the start idempotent — starting again under the same name returns the execution that already exists rather than beginning a second one, and is charged once. `durable.get()`, `durable.list()` and `durable.stop()` are owner-scoped and need -the project's own platform token, because an execution is addressed by its id -alone and an anonymous key is held by everyone who loads the page. Neither an -auth-user session from `sign_in()` nor a service key is accepted -- the routes -take a user token, and anything else is answered 401. Poll them from a backend. +the project owner's platform user token, because an execution is addressed by +its id alone and an anonymous key is held by everyone who loads the page. +Auth-user sessions from `sign_in()`, anonymous keys, service keys, and project +access tokens are not accepted. Poll them from a trusted backend. `get()` carries `result` once the execution has succeeded and `error` when it failed; `result_expired` separates a result the platform has discarded from a function that returned nothing. `is_terminal` reports whether the execution has @@ -331,6 +336,20 @@ nothing while it waits. Running the handler anywhere durable execution does not exist raises `DurableRuntimeMissingError` rather than an import error from an unfamiliar package. +Run the same handler through the local durable engine: + +```bash +volcano start +volcano durable deploy --all +volcano durable start order-pipeline --input '{"order_id":"order-9"}' +``` + +Local waits resolve immediately by default while preserving checkpoint and replay +behavior. Set `LOCAL_DURABLE_REAL_TIME=true` before `volcano start` when wait +timing must match the deployed function. Local executions persist across +`volcano stop` and `volcano start`. Volcano does not expose externally completed +callbacks; use `ctx.wait_until` to poll application state instead. + `logs.search()` returns an immutable page of retained runtime or deployment log events. Pass `next_cursor` back as `cursor` to continue a search. `logs.activity()` returns immutable time buckets using the same resource selector and query syntax. diff --git a/docs/README.md b/docs/README.md index f1342922..89f179e2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -130,6 +130,6 @@ See [Realtime](./realtime.md) for broadcasts, presence, database changes, and sh See [Authentication](./authentication.md) for account, session, email, and OAuth workflows. -See [Functions](./functions.md) for invocation identity, response values, and error handling. +See [Functions](./functions.md) for standard invocation, durable execution, local durable development, and error handling. See [Versions and compatibility](./versions.md) for runtime support, upgrades, and restoring a tested dependency set. diff --git a/docs/functions.md b/docs/functions.md index 67b37652..42eb71ad 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -63,3 +63,59 @@ A function's own HTTP 404 does not trigger another invocation. Network failures do not establish whether a function ran; do not blindly retry operations with side effects. Invalid invocation arguments and malformed successful resolution responses can raise `ValueError` or `TypeError`. + +## Start and follow a durable execution + +A durable execution can run for up to 366 days. Starting one returns a handle instead of waiting for its result: + +```python +handle = client.durable.start( + "charge-order", + {"order_id": "order-9"}, + execution_name="order-9", +) + +owner_client = VolcanoClient( + anon_key=os.environ["VOLCANO_ANON_KEY"], + api_url=os.environ.get("VOLCANO_API_URL", "https://api.volcano.dev"), + access_token=os.environ["VOLCANO_PLATFORM_TOKEN"], +) +execution = owner_client.durable.get(project_id, "charge-order", handle.id) +page = owner_client.durable.list(project_id, "charge-order", status="running") +owner_client.durable.stop(project_id, "charge-order", handle.id) +``` + +`start()` accepts the same active session, service key, or anonymous key as `functions.invoke()`. It is the only durable operation available to application credentials. An execution name makes a start idempotent. + +`get()`, `list()`, and `stop()` are owner-scoped. Call them from a trusted backend with the project owner's platform user token. Auth-user sessions, anonymous keys, service keys, and project access tokens are not accepted. `stop()` returns after the stop request is accepted, so poll `get()` until `is_terminal` is true. + +## Write a durable function + +Use `volcano_sdk.durable_authoring` in a durable function running on `python3.13` or `python3.14`: + +```python +from volcano_sdk.durable_authoring import durable + + +@durable +def handler(event, ctx): + charge = ctx.step("charge", lambda scope: charge_card(event["order_id"])) + ctx.wait("settle", "30s") + return {"charge_id": charge["id"]} +``` + +Volcano records each context operation. Resumed executions replay recorded results instead of repeating completed work. Keep changing decisions inside `ctx.step()`. Use `ctx.wait_until()` to poll application state. Volcano does not expose externally completed callbacks. + +## Run durable functions locally + +Deploy and start the same handler through the local durable engine: + +```bash +volcano start +volcano durable deploy --all +volcano durable start charge-order --input '{"order_id":"order-9"}' +``` + +Local waits resolve immediately by default while preserving checkpoint and replay behavior. Set `LOCAL_DURABLE_REAL_TIME=true` before `volcano start` when wait timing must match the deployed function. Local executions persist across `volcano stop` and `volcano start`. + +Running a decorated handler directly in a Python process still needs the optional test runtime: `python -m pip install 'volcano-sdk-python[durable]'`.