@centrali-io/centrali-sdk 5.5.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +164 -14
  2. package/dist/index.d.ts +1807 -878
  3. package/dist/index.js +9153 -4076
  4. package/index.ts +61 -7152
  5. package/package.json +10 -3
  6. package/query-types.ts +83 -2
  7. package/scripts/smoke-types.ts +145 -5
  8. package/src/client.ts +1507 -0
  9. package/src/internal/auth.ts +35 -0
  10. package/src/internal/deprecation.ts +11 -0
  11. package/src/internal/error.ts +90 -0
  12. package/src/internal/paths.ts +456 -0
  13. package/src/internal/queryGuard.ts +21 -0
  14. package/src/managers/allowedDomains.ts +90 -0
  15. package/src/managers/anomalyInsights.ts +215 -0
  16. package/src/managers/auditLog.ts +105 -0
  17. package/src/managers/collections.ts +197 -0
  18. package/src/managers/files.ts +182 -0
  19. package/src/managers/functionRuns.ts +229 -0
  20. package/src/managers/functions.ts +171 -0
  21. package/src/managers/orchestrationRuns.ts +122 -0
  22. package/src/managers/orchestrations.ts +297 -0
  23. package/src/managers/query.ts +199 -0
  24. package/src/managers/records.ts +186 -0
  25. package/src/managers/smartQueries.ts +374 -0
  26. package/src/managers/structures.ts +205 -0
  27. package/src/managers/triggers.ts +349 -0
  28. package/src/managers/validation.ts +303 -0
  29. package/src/managers/webhookSubscriptions.ts +206 -0
  30. package/src/realtime/manager.ts +292 -0
  31. package/src/types/allowedDomains.ts +29 -0
  32. package/src/types/auth.ts +83 -0
  33. package/src/types/common.ts +57 -0
  34. package/src/types/compute.ts +145 -0
  35. package/src/types/insights.ts +113 -0
  36. package/src/types/orchestrations.ts +460 -0
  37. package/src/types/realtime.ts +403 -0
  38. package/src/types/records.ts +261 -0
  39. package/src/types/search.ts +44 -0
  40. package/src/types/smartQueries.ts +303 -0
  41. package/src/types/structures.ts +203 -0
  42. package/src/types/triggers.ts +122 -0
  43. package/src/types/validation.ts +167 -0
  44. package/src/types/webhooks.ts +114 -0
  45. package/src/urls.ts +33 -0
  46. package/dist/query-types.d.ts +0 -187
  47. package/dist/query-types.js +0 -137
  48. package/dist/scripts/smoke-types.d.ts +0 -12
  49. package/dist/scripts/smoke-types.js +0 -102
@@ -0,0 +1,182 @@
1
+ // =====================================================
2
+ // Files Manager (CEN-1218 / Phase 3)
3
+ // =====================================================
4
+
5
+ import type { Method } from 'axios';
6
+ import type { ApiResponse } from '../types/common';
7
+ import type { QueryDefinition, QueryResult } from '../../query-types';
8
+ import { getFilesCanonicalApiPath } from '../internal/paths';
9
+
10
+
11
+ /**
12
+ * Canonical shape returned by `client.files.query()`. Mirrors the
13
+ * `filesquery` executor's canonical projection in the storage service —
14
+ * camelCase keys, snake_case never leaks. The truly internal blob-routing
15
+ * attributes (`storageAddress`, `uniqueName`) are intentionally not on
16
+ * the canonical surface. `renderId` IS exposed so callers can pass it to
17
+ * {@link CentraliCli.getFileRenderUrl} / {@link CentraliCli.getFileDownloadUrl}
18
+ * after discovering files via query.
19
+ *
20
+ * Optional fields are absent when unset on the row OR when not requested
21
+ * via `select.fields`.
22
+ */
23
+ export interface FileMetadata {
24
+ /** UUID. Always present. */
25
+ id: string;
26
+ /** Original filename. */
27
+ name?: string;
28
+ /** Absolute storage path (e.g. `/root/shared/logo.png`). */
29
+ path?: string;
30
+ /** Parent folder display path. */
31
+ location?: string;
32
+ /** Workspace slug — pinned by the executor. */
33
+ workspaceSlug?: string;
34
+ /**
35
+ * Render token. Pass to {@link CentraliCli.getFileRenderUrl} to display
36
+ * a file inline (e.g. images), or {@link CentraliCli.getFileDownloadUrl}
37
+ * for an attachment-style download link.
38
+ */
39
+ renderId?: string;
40
+ /** MIME type (e.g. `image/png`). */
41
+ contentType?: string;
42
+ /** High-level file kind (image, video, document, …). */
43
+ fileType?: string;
44
+ /** Byte size. */
45
+ size?: number;
46
+ /** Parent folder UUID; null/absent for files at the root. */
47
+ folderId?: string | null;
48
+ /** User ID of the uploader. */
49
+ createdBy?: string;
50
+ /** ISO timestamp. */
51
+ createdAt?: string;
52
+ /** ISO timestamp. */
53
+ updatedAt?: string;
54
+ /** Whether the file is publicly accessible without auth. */
55
+ isPublic?: boolean;
56
+ /** Structure slug when this file is attached to a record (otherwise null). */
57
+ recordSlug?: string | null;
58
+ /** Authorization context: `general`, `record`, `avatar`, `support`, … */
59
+ fileContext?: string;
60
+ /** Soft-delete timestamp (null when active). */
61
+ deletedAt?: string | null;
62
+ /** Tags array; supports `hasAny` / `hasAll` operators in queries. */
63
+ tags?: string[];
64
+ /** Media duration in seconds (audio/video only). */
65
+ duration?: number | null;
66
+ /** Pixel width (image/video). */
67
+ width?: number | null;
68
+ /** Pixel height (image/video). */
69
+ height?: number | null;
70
+ /** Primary codec (e.g. `opus`, `h264`). */
71
+ codec?: string | null;
72
+ /** Bitrate in bits per second. */
73
+ bitrate?: number | null;
74
+ }
75
+
76
+ /**
77
+ * FilesManager exposes the canonical files query surface (CEN-1218,
78
+ * Phase 3 of the Query Foundation). Mirrors `client.orchestrationRuns`
79
+ * and `client.functionRuns`: a thin wrapper over
80
+ * `POST /files/query` and `/query/test`.
81
+ *
82
+ * Existing helpers (`client.uploadFile`, `client.getFileRenderUrl`,
83
+ * `client.createFolder`, …) stay where they are — those are workflow
84
+ * helpers, not query primitives.
85
+ *
86
+ * Access via `client.files`.
87
+ */
88
+ export class FilesManager {
89
+ private requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>;
90
+ private workspaceId: string;
91
+
92
+ constructor(
93
+ workspaceId: string,
94
+ requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>
95
+ ) {
96
+ this.workspaceId = workspaceId;
97
+ this.requestFn = requestFn;
98
+ }
99
+
100
+ /**
101
+ * Run a canonical query against `POST /storage/ws/<ws>/api/v1/files/query`.
102
+ *
103
+ * The body **is** a `QueryDefinition`. `resource` is forced to
104
+ * `"files"` — the storage executor rejects anything else. Returns the
105
+ * canonical `{ data, meta }` envelope.
106
+ *
107
+ * Queryable fields (canonical camelCase): `id`, `name`, `path`,
108
+ * `location`, `renderId`, `contentType`, `fileType`, `size`,
109
+ * `folderId`, `createdBy`, `createdAt`, `updatedAt`, `isPublic`,
110
+ * `recordSlug`, `fileContext`, `deletedAt`, `tags`, `duration`,
111
+ * `width`, `height`, `codec`, `bitrate`. Internal blob-routing
112
+ * columns (`storageAddress`, `uniqueName`) are intentionally not
113
+ * exposed.
114
+ *
115
+ * `tags` is a varchar array column — `hasAny` (overlap) and `hasAll`
116
+ * (contains) work; range operators don't.
117
+ *
118
+ * Authorization: a `where` that pins `id.eq` (in a flat AND-of-scalar-eq
119
+ * shape) routes through file-context-aware ABAC server-side, including
120
+ * record-backed files (`records:retrieve`) and path-keyed user / avatar
121
+ * / support policies. Non-scoped queries use `files:list`.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * // Recent images uploaded in the last 7 days
126
+ * const recent = await client.files.query({
127
+ * resource: 'files',
128
+ * where: {
129
+ * and: [
130
+ * { fileType: { eq: 'image' } },
131
+ * { createdAt: { gte: '2026-04-24T00:00:00Z' } },
132
+ * ],
133
+ * },
134
+ * sort: [{ field: 'createdAt', direction: 'desc' }],
135
+ * page: { limit: 50 },
136
+ * });
137
+ *
138
+ * // Files in a folder, narrowed projection for a list UI
139
+ * const items = await client.files.query({
140
+ * resource: 'files',
141
+ * where: { folderId: { eq: 'folder-uuid' } },
142
+ * select: { fields: ['name', 'contentType', 'size', 'createdAt'] },
143
+ * page: { limit: 100 },
144
+ * });
145
+ *
146
+ * // Files tagged "draft" or "review"
147
+ * const drafts = await client.files.query({
148
+ * resource: 'files',
149
+ * where: { tags: { hasAny: ['draft', 'review'] } },
150
+ * });
151
+ * ```
152
+ */
153
+ public async query<T = FileMetadata>(
154
+ definition: Omit<QueryDefinition, 'resource'> & { resource?: string }
155
+ ): Promise<QueryResult<T>> {
156
+ const path = getFilesCanonicalApiPath(this.workspaceId, 'query');
157
+ const body: QueryDefinition = { ...definition, resource: 'files' };
158
+ const resp = await this.requestFn<T[]>('POST', path, body);
159
+ return resp as unknown as QueryResult<T>;
160
+ }
161
+
162
+ /**
163
+ * Authoring-time dry-run against `POST /files/query/test`.
164
+ *
165
+ * Same input shape as {@link FilesManager.query | query}. Returns a
166
+ * plan summary on success or structured `QueryError`s on failure,
167
+ * without executing the query. Use from query builders to surface
168
+ * precise errors before saving / running.
169
+ *
170
+ * The `/query/test` response is `{ ok, plan }` (NOT a `{ data }`
171
+ * envelope), so we return `resp.data` to unwrap the request layer's
172
+ * coercion — same convention as `client.orchestrationRuns.test`.
173
+ */
174
+ public async test(
175
+ definition: Omit<QueryDefinition, 'resource'> & { resource?: string }
176
+ ): Promise<{ ok: true; plan: { executor: string; resource: string; mode: string; hasUnreadableField: boolean } }> {
177
+ const path = getFilesCanonicalApiPath(this.workspaceId, 'query/test');
178
+ const body: QueryDefinition = { ...definition, resource: 'files' };
179
+ const resp = await this.requestFn<{ ok: true; plan: { executor: string; resource: string; mode: string; hasUnreadableField: boolean } }>('POST', path, body);
180
+ return resp.data;
181
+ }
182
+ }
@@ -0,0 +1,229 @@
1
+ // =====================================================
2
+ // Function Runs Manager
3
+ // =====================================================
4
+
5
+ import type { Method } from 'axios';
6
+ import type { ApiResponse, PaginatedResponse } from '../types/common';
7
+ import type {
8
+ FunctionRun,
9
+ ListFunctionRunsOptions,
10
+ ComputeJobStatusResponse,
11
+ } from '../types/compute';
12
+ import type { QueryDefinition, QueryResult } from '../../query-types';
13
+ import {
14
+ getFunctionRunsApiPath,
15
+ getFunctionRunsByTriggerApiPath,
16
+ getFunctionRunsByFunctionApiPath,
17
+ getComputeJobStatusApiPath,
18
+ } from '../internal/paths';
19
+
20
+ /**
21
+ * Manager for querying function execution runs.
22
+ *
23
+ * Provides read access to function run history — useful for checking
24
+ * whether jobs completed, inspecting outputs, and monitoring trigger activity.
25
+ *
26
+ * - {@link FunctionRunsManager.query | query} — canonical `POST /function-runs/query`
27
+ * (CEN-1216). Use for nested boolean trees, `select` projection, paging.
28
+ * - {@link FunctionRunsManager.test | test} — authoring dry-run against
29
+ * `/function-runs/query/test`. Validates and plans without executing.
30
+ * - {@link FunctionRunsManager.listByTrigger | listByTrigger} /
31
+ * {@link FunctionRunsManager.listByFunction | listByFunction} — legacy GET
32
+ * surfaces, retained for back-compat. Prefer `query()` for new code.
33
+ *
34
+ * Access via `client.functionRuns` (canonical, CEN-1227). The legacy
35
+ * `client.runs` accessor remains as a deprecated alias.
36
+ */
37
+ export class FunctionRunsManager {
38
+ private requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>;
39
+ private workspaceId: string;
40
+
41
+ constructor(
42
+ workspaceId: string,
43
+ requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>
44
+ ) {
45
+ this.workspaceId = workspaceId;
46
+ this.requestFn = requestFn;
47
+ }
48
+
49
+ /**
50
+ * Get a function run by ID.
51
+ *
52
+ * @param runId - The function run UUID
53
+ * @returns The function run details
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * const run = await client.functionRuns.get('run-uuid');
58
+ * console.log('Status:', run.data.status);
59
+ * console.log('Duration:', run.data.endedAt ? Date.parse(run.data.endedAt) - Date.parse(run.data.startedAt) : 'still running');
60
+ * ```
61
+ */
62
+ public get(runId: string): Promise<ApiResponse<FunctionRun>> {
63
+ const path = getFunctionRunsApiPath(this.workspaceId, runId);
64
+ return this.requestFn<FunctionRun>('GET', path);
65
+ }
66
+
67
+ /**
68
+ * List runs for a specific trigger.
69
+ *
70
+ * @param triggerId - The trigger UUID
71
+ * @param options - Optional pagination and status filter
72
+ * @returns Paginated list of function runs
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * // List recent runs for a trigger
77
+ * const runs = await client.functionRuns.listByTrigger('trigger-uuid');
78
+ *
79
+ * // Filter to only failed runs
80
+ * const failed = await client.functionRuns.listByTrigger('trigger-uuid', {
81
+ * status: 'failure'
82
+ * });
83
+ * ```
84
+ */
85
+ public listByTrigger(triggerId: string, options?: ListFunctionRunsOptions): Promise<ApiResponse<PaginatedResponse<FunctionRun>>> {
86
+ const path = getFunctionRunsByTriggerApiPath(this.workspaceId, triggerId);
87
+ const queryParams: Record<string, any> = {};
88
+ if (options?.page) queryParams.page = options.page;
89
+ if (options?.limit) queryParams.limit = options.limit;
90
+ if (options?.status) queryParams.status = options.status;
91
+ return this.requestFn<PaginatedResponse<FunctionRun>>('GET', path, null, queryParams);
92
+ }
93
+
94
+ /**
95
+ * List runs for a specific compute function.
96
+ *
97
+ * @param functionId - The compute function UUID
98
+ * @param options - Optional pagination and status filter
99
+ * @returns Paginated list of function runs
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * // List recent runs for a function
104
+ * const runs = await client.functionRuns.listByFunction('function-uuid');
105
+ *
106
+ * // Only completed runs, page 2
107
+ * const completed = await client.functionRuns.listByFunction('function-uuid', {
108
+ * status: 'completed',
109
+ * page: 2
110
+ * });
111
+ * ```
112
+ */
113
+ public listByFunction(functionId: string, options?: ListFunctionRunsOptions): Promise<ApiResponse<PaginatedResponse<FunctionRun>>> {
114
+ const path = getFunctionRunsByFunctionApiPath(this.workspaceId, functionId);
115
+ const queryParams: Record<string, any> = {};
116
+ if (options?.page) queryParams.page = options.page;
117
+ if (options?.limit) queryParams.limit = options.limit;
118
+ if (options?.status) queryParams.status = options.status;
119
+ return this.requestFn<PaginatedResponse<FunctionRun>>('GET', path, null, queryParams);
120
+ }
121
+
122
+ /**
123
+ * Get the status of a compute job by job ID.
124
+ *
125
+ * This is the primary way to poll for the result of an async trigger
126
+ * invocation. The job ID is returned by `client.triggers.invoke()`.
127
+ *
128
+ * @param jobId - The job ID returned by invoke
129
+ * @returns Job status including returnValue (on success) or failedReason (on failure)
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * // Invoke a trigger and poll for the result
134
+ * const { data: jobId } = await client.triggers.invoke('trigger-uuid');
135
+ *
136
+ * // Poll until complete
137
+ * let job;
138
+ * do {
139
+ * await new Promise(r => setTimeout(r, 1000));
140
+ * job = await client.functionRuns.getJobStatus(jobId);
141
+ * } while (job.data.status === 'queued' || job.data.status === 'running');
142
+ *
143
+ * if (job.data.status === 'completed') {
144
+ * console.log('Result:', job.data.returnValue);
145
+ * } else {
146
+ * console.error('Failed:', job.data.failedReason);
147
+ * }
148
+ * ```
149
+ */
150
+ public getJobStatus(jobId: string): Promise<ApiResponse<ComputeJobStatusResponse>> {
151
+ const path = getComputeJobStatusApiPath(this.workspaceId, jobId);
152
+ return this.requestFn<ComputeJobStatusResponse>('GET', path);
153
+ }
154
+
155
+ /**
156
+ * Run a canonical query against `POST /workspace/<ws>/api/v1/function-runs/query`.
157
+ *
158
+ * Phase 3 of the query foundation (CEN-1216). The body **is** a
159
+ * `QueryDefinition`. `resource` is forced to `"function-runs"` — the
160
+ * data-service executor rejects anything else. Returns the canonical
161
+ * `{ data, meta }` envelope.
162
+ *
163
+ * Queryable fields are the fixed-schema columns: `id`, `functionId`,
164
+ * `triggerId`, `status`, `startedAt`, `endedAt`, `executionSource`,
165
+ * `errorCode`, `errorMessage`, etc. The `runData` and `executionContext`
166
+ * JSONB columns are intentionally not queryable — fetch a specific run
167
+ * via `client.functionRuns.get(runId)` if you need its payload.
168
+ *
169
+ * The `where` clause that pins `functionId.eq` or `triggerId.eq` (in a
170
+ * pure AND-of-scalar-eq shape) uses `function_runs:retrieve` server-side;
171
+ * everything else uses `function_runs:list`. Retrieve-only roles can
172
+ * still query a specific function or trigger's run history this way.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * // Recent failures for a function
177
+ * const failures = await client.functionRuns.query({
178
+ * resource: 'function-runs',
179
+ * where: {
180
+ * and: [
181
+ * { functionId: { eq: 'fn-123' } },
182
+ * { status: { eq: 'failure' } },
183
+ * ],
184
+ * },
185
+ * sort: [{ field: 'startedAt', direction: 'desc' }],
186
+ * page: { limit: 50 },
187
+ * });
188
+ *
189
+ * // Workspace-wide run activity in a date window with field projection
190
+ * const activity = await client.functionRuns.query({
191
+ * resource: 'function-runs',
192
+ * where: {
193
+ * and: [
194
+ * { startedAt: { gte: '2026-04-01' } },
195
+ * { startedAt: { lt: '2026-05-01' } },
196
+ * ],
197
+ * },
198
+ * sort: [{ field: 'startedAt', direction: 'desc' }],
199
+ * page: { limit: 100 },
200
+ * select: { fields: ['id', 'functionId', 'status', 'startedAt', 'endedAt', 'errorCode'] },
201
+ * });
202
+ * ```
203
+ */
204
+ public async query<T = FunctionRun>(
205
+ definition: Omit<QueryDefinition, 'resource'> & { resource?: string }
206
+ ): Promise<QueryResult<T>> {
207
+ const path = `data/workspace/${this.workspaceId}/api/v1/function-runs/query`;
208
+ const body: QueryDefinition = { ...definition, resource: 'function-runs' };
209
+ const resp = await this.requestFn<T[]>('POST', path, body);
210
+ return resp as unknown as QueryResult<T>;
211
+ }
212
+
213
+ /**
214
+ * Authoring-time dry-run against `POST /function-runs/query/test`.
215
+ *
216
+ * Same input shape as {@link FunctionRunsManager.query | query}. Returns
217
+ * a plan summary on success or structured `QueryError`s on failure,
218
+ * without executing the query. Use from query builders to surface precise
219
+ * errors before saving / running.
220
+ */
221
+ public async test(
222
+ definition: Omit<QueryDefinition, 'resource'> & { resource?: string }
223
+ ): Promise<{ ok: true; plan: { executor: string; resource: string; mode: string; hasUnreadableField: boolean } }> {
224
+ const path = `data/workspace/${this.workspaceId}/api/v1/function-runs/query/test`;
225
+ const body: QueryDefinition = { ...definition, resource: 'function-runs' };
226
+ const resp = await this.requestFn<{ ok: true; plan: { executor: string; resource: string; mode: string; hasUnreadableField: boolean } }>('POST', path, body);
227
+ return resp.data;
228
+ }
229
+ }
@@ -0,0 +1,171 @@
1
+ // =====================================================
2
+ // Compute Functions Manager (Configuration-as-Code)
3
+ // =====================================================
4
+
5
+ import type { Method } from 'axios';
6
+ import type { ApiResponse } from '../types/common';
7
+ import type {
8
+ ComputeFunction,
9
+ CreateComputeFunctionInput,
10
+ UpdateComputeFunctionInput,
11
+ ListComputeFunctionsOptions,
12
+ TestComputeFunctionInput,
13
+ TestComputeFunctionResult,
14
+ } from '../types/compute';
15
+ import {
16
+ getComputeFunctionsApiPath,
17
+ getComputeFunctionTestApiPath,
18
+ } from '../internal/paths';
19
+
20
+ // =====================================================
21
+ // Compute Functions Manager (Configuration-as-Code)
22
+ // =====================================================
23
+
24
+ /**
25
+ * ComputeFunctionsManager provides methods for managing compute functions.
26
+ * Compute functions are JavaScript code blocks that can be executed on triggers,
27
+ * schedules, or on-demand.
28
+ * Access via `client.functions`.
29
+ *
30
+ * Usage:
31
+ * ```ts
32
+ * // List all compute functions
33
+ * const fns = await client.functions.list();
34
+ *
35
+ * // Create a new function
36
+ * const fn = await client.functions.create({
37
+ * name: 'Process Order',
38
+ * code: 'async function run() { return { processed: true }; }'
39
+ * });
40
+ *
41
+ * // Test execute code without saving
42
+ * const result = await client.functions.testExecute({
43
+ * code: 'async function run() { return executionParams; }',
44
+ * params: { orderId: '123' }
45
+ * });
46
+ * ```
47
+ */
48
+ export class ComputeFunctionsManager {
49
+ private requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>;
50
+ private workspaceId: string;
51
+
52
+ constructor(
53
+ workspaceId: string,
54
+ requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>
55
+ ) {
56
+ this.workspaceId = workspaceId;
57
+ this.requestFn = requestFn;
58
+ }
59
+
60
+ /**
61
+ * List all compute functions in the workspace.
62
+ *
63
+ * @param options - Optional list parameters (pagination, search)
64
+ * @returns List of compute functions
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const fns = await client.functions.list();
69
+ * const searched = await client.functions.list({ search: 'order', limit: 10 });
70
+ * ```
71
+ */
72
+ public list(options?: ListComputeFunctionsOptions): Promise<ApiResponse<ComputeFunction[]>> {
73
+ const path = getComputeFunctionsApiPath(this.workspaceId);
74
+ return this.requestFn<ComputeFunction[]>('GET', path, null, options);
75
+ }
76
+
77
+ /**
78
+ * Get a compute function by ID.
79
+ *
80
+ * @param functionId - The compute function UUID
81
+ * @returns The compute function details
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const fn = await client.functions.get('function-uuid');
86
+ * console.log('Function name:', fn.data.name);
87
+ * ```
88
+ */
89
+ public get(functionId: string): Promise<ApiResponse<ComputeFunction>> {
90
+ const path = getComputeFunctionsApiPath(this.workspaceId, functionId);
91
+ return this.requestFn<ComputeFunction>('GET', path);
92
+ }
93
+
94
+ /**
95
+ * Create a new compute function.
96
+ *
97
+ * @param input - The function definition
98
+ * @returns The created compute function
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const fn = await client.functions.create({
103
+ * name: 'Process Order',
104
+ * code: 'async function run() { return { processed: true }; }',
105
+ * description: 'Processes incoming orders',
106
+ * timeoutMs: 60000
107
+ * });
108
+ * ```
109
+ */
110
+ public create(input: CreateComputeFunctionInput): Promise<ApiResponse<ComputeFunction>> {
111
+ const path = getComputeFunctionsApiPath(this.workspaceId);
112
+ return this.requestFn<ComputeFunction>('POST', path, input);
113
+ }
114
+
115
+ /**
116
+ * Update an existing compute function.
117
+ *
118
+ * @param functionId - The compute function UUID
119
+ * @param input - The fields to update
120
+ * @returns The updated compute function
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * const updated = await client.functions.update('function-uuid', {
125
+ * code: 'async function run() { return { v2: true }; }',
126
+ * timeoutMs: 120000
127
+ * });
128
+ * ```
129
+ */
130
+ public update(functionId: string, input: UpdateComputeFunctionInput): Promise<ApiResponse<ComputeFunction>> {
131
+ const path = getComputeFunctionsApiPath(this.workspaceId, functionId);
132
+ return this.requestFn<ComputeFunction>('PUT', path, input);
133
+ }
134
+
135
+ /**
136
+ * Delete a compute function.
137
+ *
138
+ * @param functionId - The compute function UUID
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * await client.functions.delete('function-uuid');
143
+ * ```
144
+ */
145
+ public delete(functionId: string): Promise<ApiResponse<void>> {
146
+ const path = getComputeFunctionsApiPath(this.workspaceId, functionId);
147
+ return this.requestFn<void>('DELETE', path);
148
+ }
149
+
150
+ /**
151
+ * Test execute code without saving it as a function.
152
+ * Useful for validating function code before creating/updating.
153
+ *
154
+ * @param input - The code to test and optional input data
155
+ * @returns Test execution result including output, duration, and logs
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * const result = await client.functions.testExecute({
160
+ * code: 'async function run() { return { sum: executionParams.a + executionParams.b }; }',
161
+ * params: { a: 1, b: 2 }
162
+ * });
163
+ * console.log('Output:', result.data.output); // { sum: 3 }
164
+ * console.log('Duration:', result.data.duration_ms, 'ms');
165
+ * ```
166
+ */
167
+ public testExecute(input: TestComputeFunctionInput): Promise<ApiResponse<TestComputeFunctionResult>> {
168
+ const path = getComputeFunctionTestApiPath(this.workspaceId);
169
+ return this.requestFn<TestComputeFunctionResult>('POST', path, input);
170
+ }
171
+ }