feat: Functions — JS/TS execution with run log

core.functions + core.function_runs: project-scoped code executed by a
runtime binary on the backend host (bun or deno per function). Payload
arrives on stdin + PRIMORA_PAYLOAD env; stdout/stderr captured per run
(capped), with status/exit code/duration. Invoke returns 502 + the
recorded run on failure so errors stay observable.

Dashboard gains a Functions page: code editor, JSON payload invoke,
live result, run history. Runner config via FUNCTIONS_RUNTIME /
FUNCTIONS_TIMEOUT_SECONDS / FUNCTIONS_MAX_OUTPUT_BYTES.

Verified live: bun ran code, stdin payload delivered, error path returns
502 with captured stderr; timeout kills at deadline.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-24 19:46:32 +02:00
1 parent 3aca5bd1e0
commit 32587c8063
37 files changed
+2157 -6

No files matched your search

@@ -37,6 +37,7 @@ export type { CreatedApiKey } from './models/CreatedApiKey';
export { CreateDBConnectionRequest } from './models/CreateDBConnectionRequest';
export type { CreateDeployMarkerRequest } from './models/CreateDeployMarkerRequest';
export type { CreateDocumentRequest } from './models/CreateDocumentRequest';
export { CreateFunctionRequest } from './models/CreateFunctionRequest';
export { CreateInboundHookRequest } from './models/CreateInboundHookRequest';
export { CreateIntegrationRequest } from './models/CreateIntegrationRequest';
export { CreateInvitationRequest } from './models/CreateInvitationRequest';
@@ -71,6 +72,11 @@ export { EmailLogEntry } from './models/EmailLogEntry';
export type { EmailLogListResponse } from './models/EmailLogListResponse';
export type { ErrorEnvelope } from './models/ErrorEnvelope';
export type { EventSeriesBucket } from './models/EventSeriesBucket';
export { Function } from './models/Function';
export type { FunctionDetailResponse } from './models/FunctionDetailResponse';
export type { FunctionListResponse } from './models/FunctionListResponse';
export { FunctionRun } from './models/FunctionRun';
export type { FunctionRunListResponse } from './models/FunctionRunListResponse';
export type { HealthStatus } from './models/HealthStatus';
export { InboundHook } from './models/InboundHook';
export type { InboundHookListResponse } from './models/InboundHookListResponse';
@@ -88,6 +94,7 @@ export type { IntegrationListResponse } from './models/IntegrationListResponse';
export type { IntegrationTestResult } from './models/IntegrationTestResult';
export type { Invitation } from './models/Invitation';
export type { InvitationID } from './models/InvitationID';
export type { InvokeFunctionRequest } from './models/InvokeFunctionRequest';
export type { JobID } from './models/JobID';
export type { MeResponse } from './models/MeResponse';
export type { MetricPoint } from './models/MetricPoint';
@@ -132,6 +139,7 @@ export type { UpdateBucketObjectRequest } from './models/UpdateBucketObjectReque
export { UpdateBucketRequest } from './models/UpdateBucketRequest';
export type { UpdateCollectionRequest } from './models/UpdateCollectionRequest';
export type { UpdateDocumentRequest } from './models/UpdateDocumentRequest';
export type { UpdateFunctionRequest } from './models/UpdateFunctionRequest';
export type { UpdateInstanceSettingRequest } from './models/UpdateInstanceSettingRequest';
export { UpdateOrganizationMemberRoleRequest } from './models/UpdateOrganizationMemberRoleRequest';
export type { UpdateOrganizationRequest } from './models/UpdateOrganizationRequest';
@@ -179,6 +187,7 @@ export { $CreatedApiKey } from './schemas/$CreatedApiKey';
export { $CreateDBConnectionRequest } from './schemas/$CreateDBConnectionRequest';
export { $CreateDeployMarkerRequest } from './schemas/$CreateDeployMarkerRequest';
export { $CreateDocumentRequest } from './schemas/$CreateDocumentRequest';
export { $CreateFunctionRequest } from './schemas/$CreateFunctionRequest';
export { $CreateInboundHookRequest } from './schemas/$CreateInboundHookRequest';
export { $CreateIntegrationRequest } from './schemas/$CreateIntegrationRequest';
export { $CreateInvitationRequest } from './schemas/$CreateInvitationRequest';
@@ -213,6 +222,11 @@ export { $EmailLogEntry } from './schemas/$EmailLogEntry';
export { $EmailLogListResponse } from './schemas/$EmailLogListResponse';
export { $ErrorEnvelope } from './schemas/$ErrorEnvelope';
export { $EventSeriesBucket } from './schemas/$EventSeriesBucket';
export { $Function } from './schemas/$Function';
export { $FunctionDetailResponse } from './schemas/$FunctionDetailResponse';
export { $FunctionListResponse } from './schemas/$FunctionListResponse';
export { $FunctionRun } from './schemas/$FunctionRun';
export { $FunctionRunListResponse } from './schemas/$FunctionRunListResponse';
export { $HealthStatus } from './schemas/$HealthStatus';
export { $InboundHook } from './schemas/$InboundHook';
export { $InboundHookListResponse } from './schemas/$InboundHookListResponse';
@@ -230,6 +244,7 @@ export { $IntegrationListResponse } from './schemas/$IntegrationListResponse';
export { $IntegrationTestResult } from './schemas/$IntegrationTestResult';
export { $Invitation } from './schemas/$Invitation';
export { $InvitationID } from './schemas/$InvitationID';
export { $InvokeFunctionRequest } from './schemas/$InvokeFunctionRequest';
export { $JobID } from './schemas/$JobID';
export { $MeResponse } from './schemas/$MeResponse';
export { $MetricPoint } from './schemas/$MetricPoint';
@@ -274,6 +289,7 @@ export { $UpdateBucketObjectRequest } from './schemas/$UpdateBucketObjectRequest
export { $UpdateBucketRequest } from './schemas/$UpdateBucketRequest';
export { $UpdateCollectionRequest } from './schemas/$UpdateCollectionRequest';
export { $UpdateDocumentRequest } from './schemas/$UpdateDocumentRequest';
export { $UpdateFunctionRequest } from './schemas/$UpdateFunctionRequest';
export { $UpdateInstanceSettingRequest } from './schemas/$UpdateInstanceSettingRequest';
export { $UpdateOrganizationMemberRoleRequest } from './schemas/$UpdateOrganizationMemberRoleRequest';
export { $UpdateOrganizationRequest } from './schemas/$UpdateOrganizationRequest';
@@ -296,6 +312,7 @@ export { AutomationService } from './services/AutomationService';
export { CollectionsService } from './services/CollectionsService';
export { DatabasesService } from './services/DatabasesService';
export { EmailService } from './services/EmailService';
export { FunctionsService } from './services/FunctionsService';
export { HealthService } from './services/HealthService';
export { InboundService } from './services/InboundService';
export { InstanceService } from './services/InstanceService';
@@ -0,0 +1,16 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type CreateFunctionRequest = {
name: string;
code: string;
runtime?: CreateFunctionRequest.runtime;
};
export namespace CreateFunctionRequest {
export enum runtime {
BUN = 'bun',
DENO = 'deno',
}
}
@@ -0,0 +1,19 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type Function = {
id: string;
name: string;
runtime: Function.runtime;
enabled: boolean;
created_at: string;
updated_at: string;
};
export namespace Function {
export enum runtime {
BUN = 'bun',
DENO = 'deno',
}
}
@@ -0,0 +1,10 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Function } from './Function';
export type FunctionDetailResponse = {
function: Function;
code: string;
};
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { Function } from './Function';
export type FunctionListResponse = {
items: Array<Function>;
};
@@ -0,0 +1,27 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type FunctionRun = {
id: string;
trigger: FunctionRun.trigger;
status: FunctionRun.status;
exit_code?: number | null;
stdout: string;
stderr: string;
duration_ms: number;
created_at: string;
};
export namespace FunctionRun {
export enum trigger {
MANUAL = 'manual',
SCHEDULE = 'schedule',
HOOK = 'hook',
}
export enum status {
SUCCESS = 'success',
ERROR = 'error',
TIMEOUT = 'timeout',
}
}
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { FunctionRun } from './FunctionRun';
export type FunctionRunListResponse = {
items: Array<FunctionRun>;
};
@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type InvokeFunctionRequest = {
payload?: any;
};
@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UpdateFunctionRequest = {
code?: string;
enabled?: boolean;
};
@@ -0,0 +1,19 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CreateFunctionRequest = {
properties: {
name: {
type: 'string',
isRequired: true,
},
code: {
type: 'string',
isRequired: true,
},
runtime: {
type: 'Enum',
},
},
} as const;
@@ -0,0 +1,35 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $Function = {
properties: {
id: {
type: 'string',
isRequired: true,
format: 'uuid',
},
name: {
type: 'string',
isRequired: true,
},
runtime: {
type: 'Enum',
isRequired: true,
},
enabled: {
type: 'boolean',
isRequired: true,
},
created_at: {
type: 'string',
isRequired: true,
format: 'date-time',
},
updated_at: {
type: 'string',
isRequired: true,
format: 'date-time',
},
},
} as const;
@@ -0,0 +1,16 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $FunctionDetailResponse = {
properties: {
function: {
type: 'Function',
isRequired: true,
},
code: {
type: 'string',
isRequired: true,
},
},
} as const;
@@ -0,0 +1,15 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $FunctionListResponse = {
properties: {
items: {
type: 'array',
contains: {
type: 'Function',
},
isRequired: true,
},
},
} as const;
@@ -0,0 +1,42 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $FunctionRun = {
properties: {
id: {
type: 'string',
isRequired: true,
format: 'uuid',
},
trigger: {
type: 'Enum',
isRequired: true,
},
status: {
type: 'Enum',
isRequired: true,
},
exit_code: {
type: 'number',
isNullable: true,
},
stdout: {
type: 'string',
isRequired: true,
},
stderr: {
type: 'string',
isRequired: true,
},
duration_ms: {
type: 'number',
isRequired: true,
},
created_at: {
type: 'string',
isRequired: true,
format: 'date-time',
},
},
} as const;
@@ -0,0 +1,15 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $FunctionRunListResponse = {
properties: {
items: {
type: 'array',
contains: {
type: 'FunctionRun',
},
isRequired: true,
},
},
} as const;
@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $InvokeFunctionRequest = {
properties: {
payload: {
properties: {
},
},
},
} as const;
@@ -0,0 +1,14 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $UpdateFunctionRequest = {
properties: {
code: {
type: 'string',
},
enabled: {
type: 'boolean',
},
},
} as const;
@@ -0,0 +1,197 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CreateFunctionRequest } from '../models/CreateFunctionRequest';
import type { Function } from '../models/Function';
import type { FunctionDetailResponse } from '../models/FunctionDetailResponse';
import type { FunctionListResponse } from '../models/FunctionListResponse';
import type { FunctionRun } from '../models/FunctionRun';
import type { FunctionRunListResponse } from '../models/FunctionRunListResponse';
import type { InvokeFunctionRequest } from '../models/InvokeFunctionRequest';
import type { UpdateFunctionRequest } from '../models/UpdateFunctionRequest';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class FunctionsService {
/**
* List functions in a project.
* @returns FunctionListResponse Functions
* @throws ApiError
*/
public static listFunctions({
projectId,
}: {
projectId: string,
}): CancelablePromise<FunctionListResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/projects/{projectID}/functions',
path: {
'projectID': projectId,
},
errors: {
401: `Error response`,
},
});
}
/**
* Create a function (name, JS/TS code, runtime bun|deno).
* @returns Function Created function
* @throws ApiError
*/
public static createFunction({
projectId,
requestBody,
}: {
projectId: string,
requestBody: CreateFunctionRequest,
}): CancelablePromise<Function> {
return __request(OpenAPI, {
method: 'POST',
url: '/projects/{projectID}/functions',
path: {
'projectID': projectId,
},
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Error response`,
},
});
}
/**
* Function metadata plus its source code.
* @returns FunctionDetailResponse Function with code
* @throws ApiError
*/
public static getFunction({
projectId,
functionId,
}: {
projectId: string,
functionId: string,
}): CancelablePromise<FunctionDetailResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/projects/{projectID}/functions/{functionID}',
path: {
'projectID': projectId,
'functionID': functionId,
},
errors: {
404: `Error response`,
},
});
}
/**
* Update function code and/or enabled state.
* @returns Function Updated function
* @throws ApiError
*/
public static updateFunction({
projectId,
functionId,
requestBody,
}: {
projectId: string,
functionId: string,
requestBody: UpdateFunctionRequest,
}): CancelablePromise<Function> {
return __request(OpenAPI, {
method: 'PUT',
url: '/projects/{projectID}/functions/{functionID}',
path: {
'projectID': projectId,
'functionID': functionId,
},
body: requestBody,
mediaType: 'application/json',
errors: {
404: `Error response`,
},
});
}
/**
* Delete a function and its run history.
* @returns void
* @throws ApiError
*/
public static deleteFunction({
projectId,
functionId,
}: {
projectId: string,
functionId: string,
}): CancelablePromise<void> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/projects/{projectID}/functions/{functionID}',
path: {
'projectID': projectId,
'functionID': functionId,
},
errors: {
404: `Error response`,
},
});
}
/**
* Execute the function with a JSON payload on stdin; returns the recorded run. 502 when the run fails or times out.
* @returns FunctionRun Recorded run (status success)
* @throws ApiError
*/
public static invokeFunction({
projectId,
functionId,
requestBody,
}: {
projectId: string,
functionId: string,
requestBody?: InvokeFunctionRequest,
}): CancelablePromise<FunctionRun> {
return __request(OpenAPI, {
method: 'POST',
url: '/projects/{projectID}/functions/{functionID}/invoke',
path: {
'projectID': projectId,
'functionID': functionId,
},
body: requestBody,
mediaType: 'application/json',
errors: {
404: `Error response`,
502: `Recorded run (status error|timeout)`,
},
});
}
/**
* Recent runs for a function.
* @returns FunctionRunListResponse Function runs
* @throws ApiError
*/
public static listFunctionRuns({
projectId,
functionId,
limit = 50,
}: {
projectId: string,
functionId: string,
limit?: number,
}): CancelablePromise<FunctionRunListResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/projects/{projectID}/functions/{functionID}/runs',
path: {
'projectID': projectId,
'functionID': functionId,
},
query: {
'limit': limit,
},
errors: {
404: `Error response`,
},
});
}
}