@permute/sdk 0.1.1 → 0.2.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.
package/README.md CHANGED
@@ -4,10 +4,103 @@ TypeScript client for the Permute public API.
4
4
 
5
5
  The SDK supports Node.js 20 and newer and includes TypeScript declarations.
6
6
 
7
+ See the [changelog](https://permute.ai/docs/changelog) for release notes and upgrade guidance.
8
+
9
+ ## Dashboard source authoring
10
+
11
+ API-key clients with dashboard write access can read and replace the complete
12
+ TSX source for an existing dashboard. Pass the returned dashboard revision and
13
+ data run back on the write so Permute can reject stale edits:
14
+
15
+ ```ts
16
+ const dashboard = await client.dashboards.getSource('oper_...');
17
+
18
+ await client.dashboards.updateSource(dashboard.dashboardId, {
19
+ source: updatedSource,
20
+ expectedRevision: dashboard.revision,
21
+ expectedDataRunId: dashboard.dataRunId,
22
+ });
23
+ ```
24
+
25
+ The server validates source safety and derives the dashboard data contract from
26
+ the referenced run. Clients cannot supply an authoritative contract.
27
+
7
28
  ```sh
8
29
  npm install @permute/sdk
9
30
  ```
10
31
 
32
+ ## Author data flows with external agents
33
+
34
+ For a runnable example from sample input through publication, see
35
+ [Build and publish a data flow](https://permute.ai/docs/guides/data-flow).
36
+
37
+ Use `operations.list({ kind: 'dataFlow' })` to discover flows, `dataFlows.get(id)`
38
+ to read their edges and operation IDs, and `operations.list({ dataFlowId })` to
39
+ inspect the member operations. Drafts are included (they do not need published output).
40
+ Fetch each operation’s code with `operations.getSource(id)` as needed.
41
+
42
+ ```typescript
43
+ const flow = await client.dataFlows.create({
44
+ name: 'Revenue',
45
+ description: 'Revenue by customer',
46
+ dataSourceIds: ['data_...'],
47
+ });
48
+
49
+ await client.dataFlows.command(flow.operation.id, {
50
+ command: {
51
+ type: 'addTransform',
52
+ expectedRevision: flow.revision,
53
+ input: {
54
+ name: 'Revenue by customer',
55
+ instruction: 'Sum revenue by customer using the agreed revenue definition.',
56
+ inputIds: ['data_...'],
57
+ },
58
+ },
59
+ });
60
+ const updatedFlow = await client.dataFlows.get(flow.operation.id);
61
+ const operationId = updatedFlow.operationIds[0];
62
+ const current = await client.operations.getSource(operationId);
63
+ await client.operations.updateSource(operationId, {
64
+ source: handlerSource,
65
+ expectedRevision: current.revision,
66
+ expectedDataFlowRevision: current.dataFlowRevision,
67
+ expectedUpdatedAt: current.updatedAt,
68
+ });
69
+ const started = await client.operations.run(flow.operation.id, { version: 'draft' });
70
+
71
+ // Poll until the selected workflow's run completes or fails.
72
+ const runs = await client.operations.listRuns(operationId);
73
+ const completed = runs.items.find((run) => run.workflowId === started.workflowId && run.status === 'completed');
74
+ if (completed) {
75
+ const preview = await client.operations.getRunOutput(operationId, completed.id);
76
+ console.log(preview.schemas, preview.tables); // At most 1,000 rows per table.
77
+ }
78
+ ```
79
+
80
+ `handlerSource` is JavaScript using `module.exports = { handler }`. The async
81
+ handler receives `event.query(sql)` for local tables, `event.remoteQuery(connectorId, sql)`
82
+ for live SQL connectors, and `event.currentDate` for relative dates. Imports,
83
+ `require()`, and external package calls are unavailable. Inspect source tables
84
+ before writing SQL. Return arrays of flat row objects or an object of named tables;
85
+ Data Flow operations produce one table. Parent outputs retain their table names;
86
+ inspect a completed parent run with `getRunOutput` before writing dependent SQL.
87
+
88
+ Source updates replace the complete handler and save a draft without starting
89
+ Permute’s coding agent, running code, or publishing. Partial source patches are
90
+ not supported. Read with `getSource` before editing and pass back its `revision`,
91
+ `dataFlowRevision`, and `updatedAt` (as shown above). On a 409 conflict, fetch again
92
+ and reconcile your edits before retrying; do not attach fresh tokens to an old
93
+ replacement without checking the changes. Graph commands use `get`’s numeric flow revision. They support
94
+ `updateFlow`, `addTransform`, `updateNode`, `deleteNode`, `connectNodes`,
95
+ `disconnectNodes`, `publish`, and `deleteFlow`. Fetch the flow after every command.
96
+ Publishing requires a successful run of the current flow version. Deleting a flow
97
+ requires admin access; its owned operations are deleted with it.
98
+
99
+ Data Flow and operation authoring are REST/SDK only. MCP serves end users reading
100
+ and analyzing governed outputs; it does not expose code editing, draft inspection,
101
+ execution, or publication. Hosted connectors also restrict access to selected sources. Flow scheduling and standalone operation
102
+ creation are not part of this surface.
103
+
11
104
  ## Configure
12
105
 
13
106
  ```ts
@@ -80,6 +173,26 @@ Blobs. REST callers provide `sizeBytes`, then send a raw `PUT` to the returned
80
173
  `client.datasets.get(result.id)` for processing status; there is no completion
81
174
  API call.
82
175
 
176
+ CSV, TSV, JSON, NDJSON, and Parquet files can be up to 1 GiB (1,073,741,824
177
+ bytes). Other supported file types can be up to 100 MiB (104,857,600 bytes).
178
+ Permute selects the limit from the submitted `contentType` and rejects an
179
+ oversized file before issuing its signed upload target.
180
+
181
+ Request a signed URL for the original uploaded file, or delete one file or custom
182
+ dataset when the API key has dataset admin access:
183
+
184
+ ```ts
185
+ const downloadUrl = await client.datasets.download(uploaded.id);
186
+ const response = await fetch(downloadUrl);
187
+ const bytes = await response.arrayBuffer();
188
+ console.log(response.headers.get('content-type'), bytes.byteLength);
189
+
190
+ await client.datasets.delete(uploaded.id);
191
+ ```
192
+
193
+ For large files, consume `response.body` as a stream instead of buffering the full
194
+ response. Signed download URLs expire after 15 minutes.
195
+
83
196
  ## Manage Custom Datasets
84
197
 
85
198
  Create a custom dataset by a stable workspace key when one logical source
@@ -176,7 +289,10 @@ console.log(evidence.items);
176
289
 
177
290
  Use SQL only when table names are known and exact rows matter. SQL requests must
178
291
  include `sourceIds`; call `client.sources.get()` first when table names are not
179
- known.
292
+ known, and use the returned table names exactly. Warehouse-backed sources use
293
+ DuckDB SQL and can be queried together. Live remote database connectors use
294
+ their native dialect—GoogleSQL for BigQuery and PostgreSQL for Supabase—and
295
+ must be queried one at a time without other sources.
180
296
 
181
297
  ```ts
182
298
  const details = await client.sources.get(sourceIds[0]);
@@ -201,6 +317,7 @@ try {
201
317
  });
202
318
  } catch (error) {
203
319
  if (error instanceof PermuteApiError) {
320
+ // Invalid SQL returns status 400 with an actionable message when available.
204
321
  console.error(error.status, error.code, error.message, error.requestId);
205
322
  }
206
323
  throw error;
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Workspace, WorkspaceCreateRequest, WorkspaceDeleteResponse, WorkspaceList, WorkspaceUpdateRequest, AnswerRequest, AnswerJobResponse, AnswerResponse, PermuteClientOptions, PermuteRequestOptions, QueryRequest, QueryResponse, SearchRequest, SearchResponse, SourceDetail, SourceList, CustomDatasetCreateRequest, DatasetResponse, FileDatasetCreateRequest, FileDatasetResponse, DatasetRows, CustomDatasetResponse } from './types.js';
1
+ import type { Database, DatabaseList, DatabaseCreateRequest, DatabaseTables, DatabaseTableCreateRequest, DatabaseTableCreated, DatabaseRecords, DatabaseRecordsQuery, DatabaseTransactionRequest, DatabaseTransactionReceipt, ResourceVersionState, ResourceRevisionList, ResourceRevision, ResourceVersionRequest, Fact, FactList, FactWriteRequest, SkillList, SkillContent, SkillWriteRequest, KnowledgeWriteResponse, OperationRunGroup, OperationRunQuery, Operation, OperationList, OperationListQuery, OperationSource, OperationSourceUpdateRequest, OperationUpdateRequest, OperationRunRequest, OperationRunStarted, OperationRunList, OperationRunOutput, DataFlow, DataFlowCreateRequest, DataFlowCommandRequest, DataFlowCommandResponse, Workspace, WorkspaceCreateRequest, WorkspaceDeleteResponse, WorkspaceList, WorkspaceUpdateRequest, AnswerRequest, AnswerJobResponse, AnswerResponse, PermuteClientOptions, PermuteRequestOptions, QueryRequest, QueryResponse, SearchRequest, SearchResponse, SourceDetail, SourceList, CustomDatasetCreateRequest, DatasetDeleteResponse, DatasetResponse, FileDatasetCreateRequest, FileDatasetResponse, DatasetRows, CustomDatasetResponse, DashboardSource, DashboardSourceUpdateRequest } from './types.js';
2
2
  export declare class PermuteApiError extends Error {
3
3
  readonly status: number;
4
4
  readonly code: string;
@@ -60,7 +60,38 @@ export declare class PermuteFileDataset {
60
60
  get status(): FileDatasetResponse['status'];
61
61
  refresh(options?: PermuteRequestOptions): Promise<this>;
62
62
  }
63
+ export interface SkillsClient {
64
+ list(options?: PermuteRequestOptions): Promise<SkillList>;
65
+ get(path: string, options?: PermuteRequestOptions): Promise<SkillContent>;
66
+ update(path: string, request: SkillWriteRequest, options?: PermuteRequestOptions): Promise<KnowledgeWriteResponse>;
67
+ delete(path: string, options?: PermuteRequestOptions): Promise<KnowledgeWriteResponse>;
68
+ }
63
69
  export declare class PermuteClient {
70
+ readonly revisions: {
71
+ list(resourceId: string, options?: PermuteRequestOptions): Promise<ResourceRevisionList>;
72
+ get(resourceId: string, revisionId: string, options?: PermuteRequestOptions): Promise<ResourceRevision>;
73
+ restore(resourceId: string, revisionId: string, request: ResourceVersionRequest, options?: PermuteRequestOptions): Promise<ResourceVersionState>;
74
+ publish(resourceId: string, request: ResourceVersionRequest, options?: PermuteRequestOptions): Promise<ResourceVersionState>;
75
+ };
76
+ readonly facts: {
77
+ list(options?: PermuteRequestOptions): Promise<FactList>;
78
+ get(factId: string, options?: PermuteRequestOptions): Promise<Fact>;
79
+ create(request: FactWriteRequest, options?: PermuteRequestOptions): Promise<Fact>;
80
+ update(factId: string, request: FactWriteRequest, options?: PermuteRequestOptions): Promise<Fact>;
81
+ delete(factId: string, options?: PermuteRequestOptions): Promise<KnowledgeWriteResponse>;
82
+ };
83
+ readonly skills: SkillsClient;
84
+ readonly organizationSkills: SkillsClient;
85
+ readonly databases: {
86
+ list(options?: PermuteRequestOptions): Promise<DatabaseList>;
87
+ create(body: DatabaseCreateRequest, options?: PermuteRequestOptions): Promise<Database>;
88
+ get(databaseId: string, options?: PermuteRequestOptions): Promise<Database>;
89
+ delete(databaseId: string, options?: PermuteRequestOptions): Promise<Database>;
90
+ tables(databaseId: string, options?: PermuteRequestOptions): Promise<DatabaseTables>;
91
+ createTable(databaseId: string, body: DatabaseTableCreateRequest, options?: PermuteRequestOptions): Promise<DatabaseTableCreated>;
92
+ records(databaseId: string, table: string, query?: Partial<DatabaseRecordsQuery>, options?: PermuteRequestOptions): Promise<DatabaseRecords>;
93
+ transact(databaseId: string, body: DatabaseTransactionRequest, options?: PermuteRequestOptions): Promise<DatabaseTransactionReceipt>;
94
+ };
64
95
  readonly workspaces: {
65
96
  list: (options?: PermuteRequestOptions) => Promise<WorkspaceList>;
66
97
  create: (request: WorkspaceCreateRequest, options?: PermuteRequestOptions) => Promise<Workspace>;
@@ -72,6 +103,28 @@ export declare class PermuteClient {
72
103
  list: (options?: PermuteRequestOptions) => Promise<SourceList>;
73
104
  get: (sourceId: string, options?: PermuteRequestOptions) => Promise<SourceDetail>;
74
105
  };
106
+ readonly operations: {
107
+ list: (query?: OperationListQuery, options?: PermuteRequestOptions) => Promise<OperationList>;
108
+ get: (operationId: string, options?: PermuteRequestOptions) => Promise<Operation>;
109
+ update: (operationId: string, request: OperationUpdateRequest, options?: PermuteRequestOptions) => Promise<Operation>;
110
+ getSource: (operationId: string, options?: PermuteRequestOptions) => Promise<OperationSource>;
111
+ updateSource: (operationId: string, request: OperationSourceUpdateRequest, options?: PermuteRequestOptions) => Promise<OperationSource>;
112
+ getRunOutput: (operationId: string, runId: string, options?: PermuteRequestOptions) => Promise<OperationRunOutput>;
113
+ listRuns: {
114
+ (operationId: string, options?: PermuteRequestOptions): Promise<OperationRunList>;
115
+ (query: OperationRunQuery, options?: PermuteRequestOptions): Promise<OperationRunGroup>;
116
+ };
117
+ run: (operationId: string, request: OperationRunRequest, options?: PermuteRequestOptions) => Promise<OperationRunStarted>;
118
+ };
119
+ readonly dataFlows: {
120
+ create: (request: DataFlowCreateRequest, options?: PermuteRequestOptions) => Promise<DataFlow>;
121
+ get: (dataFlowId: string, options?: PermuteRequestOptions) => Promise<DataFlow>;
122
+ command: (dataFlowId: string, request: DataFlowCommandRequest, options?: PermuteRequestOptions) => Promise<DataFlowCommandResponse>;
123
+ };
124
+ readonly dashboards: {
125
+ getSource: (dashboardId: string, options?: PermuteRequestOptions) => Promise<DashboardSource>;
126
+ updateSource: (dashboardId: string, request: DashboardSourceUpdateRequest, options?: PermuteRequestOptions) => Promise<DashboardSource>;
127
+ };
75
128
  readonly datasets: {
76
129
  create: {
77
130
  (request: CustomDatasetCreateRequest, options?: PermuteRequestOptions): Promise<PermuteDataset>;
@@ -79,6 +132,8 @@ export declare class PermuteClient {
79
132
  };
80
133
  getByKey: (key: string, options?: PermuteRequestOptions) => Promise<PermuteDataset | PermuteFileDataset>;
81
134
  get: (datasetId: string, options?: PermuteRequestOptions) => Promise<PermuteDataset | PermuteFileDataset>;
135
+ download: (datasetId: string, options?: PermuteRequestOptions) => Promise<string>;
136
+ delete: (datasetId: string, options?: PermuteRequestOptions) => Promise<DatasetDeleteResponse>;
82
137
  };
83
138
  private readonly apiKey;
84
139
  private readonly baseUrl;
package/dist/client.js CHANGED
@@ -262,8 +262,16 @@ export class PermuteFileDataset {
262
262
  }
263
263
  }
264
264
  export class PermuteClient {
265
+ revisions;
266
+ facts;
267
+ skills;
268
+ organizationSkills;
269
+ databases;
265
270
  workspaces;
266
271
  sources;
272
+ operations;
273
+ dataFlows;
274
+ dashboards;
267
275
  datasets;
268
276
  apiKey;
269
277
  baseUrl;
@@ -292,6 +300,37 @@ export class PermuteClient {
292
300
  this.workspaceId = options.workspaceId;
293
301
  this.defaultHeaders = options.headers ?? {};
294
302
  this.fetchImpl = fetchImpl.bind(globalThis);
303
+ this.revisions = {
304
+ list: (resourceId, options) => this.request('resourceRevisionsList', { pathParams: { resourceId } }, options),
305
+ get: (resourceId, revisionId, options) => this.request('resourceRevisionsGet', { pathParams: { resourceId, revisionId } }, options),
306
+ restore: (resourceId, revisionId, body, options) => this.request('resourceRevisionsRestore', { pathParams: { resourceId, revisionId }, body }, options),
307
+ publish: (resourceId, body, options) => this.request('resourcesPublish', { pathParams: { resourceId }, body }, options),
308
+ };
309
+ this.facts = {
310
+ list: (options) => this.request('factsList', {}, options),
311
+ get: (factId, options) => this.request('factsGet', { pathParams: { factId } }, options),
312
+ create: (body, options) => this.request('factsCreate', { body }, options),
313
+ update: (factId, body, options) => this.request('factsUpdate', { pathParams: { factId }, body }, options),
314
+ delete: (factId, options) => this.request('factsDelete', { pathParams: { factId } }, options),
315
+ };
316
+ const skillsClient = (prefix) => ({
317
+ list: (options) => this.request(`${prefix}List`, {}, options),
318
+ get: (path, options) => this.request(`${prefix}Get`, { pathParams: { path } }, options),
319
+ update: (path, body, options) => this.request(`${prefix}Update`, { pathParams: { path }, body }, options),
320
+ delete: (path, options) => this.request(`${prefix}Delete`, { pathParams: { path } }, options),
321
+ });
322
+ this.skills = skillsClient('skills');
323
+ this.organizationSkills = skillsClient('organizationSkills');
324
+ this.databases = {
325
+ list: (options) => this.request('databasesList', {}, options),
326
+ create: (body, options) => this.request('databasesCreate', { body }, options),
327
+ get: (databaseId, options) => this.request('databasesGet', { pathParams: { databaseId } }, options),
328
+ delete: (databaseId, options) => this.request('databasesDelete', { pathParams: { databaseId } }, options),
329
+ tables: (databaseId, options) => this.request('databaseTablesList', { pathParams: { databaseId } }, options),
330
+ createTable: (databaseId, body, options) => this.request('databaseTablesCreate', { pathParams: { databaseId }, body }, options),
331
+ records: (databaseId, table, query = {}, options) => this.request('databaseRecordsList', { pathParams: { databaseId, table }, query }, options),
332
+ transact: (databaseId, body, options) => this.request('databaseTransactionsCreate', { pathParams: { databaseId }, body }, options),
333
+ };
295
334
  this.workspaces = {
296
335
  list: (requestOptions) => this.request('workspacesList', {}, requestOptions),
297
336
  create: (request, requestOptions) => this.request('workspacesCreate', { body: request }, requestOptions),
@@ -303,6 +342,27 @@ export class PermuteClient {
303
342
  list: (requestOptions) => this.request('sourcesList', {}, requestOptions),
304
343
  get: (sourceId, requestOptions) => this.request('sourcesGet', { pathParams: { sourceId } }, requestOptions),
305
344
  };
345
+ this.operations = {
346
+ list: (query = {}, options) => this.request('operationsList', { query }, options),
347
+ get: (operationId, options) => this.request('operationsGet', { pathParams: { operationId } }, options),
348
+ update: (operationId, body, options) => this.request('operationsUpdate', { pathParams: { operationId }, body }, options),
349
+ getSource: (operationId, options) => this.request('operationsSourceGet', { pathParams: { operationId } }, options),
350
+ updateSource: (operationId, body, options) => this.request('operationsSourceUpdate', { pathParams: { operationId }, body }, options),
351
+ getRunOutput: (operationId, runId, options) => this.request('operationsRunOutput', { pathParams: { operationId, runId } }, options),
352
+ listRuns: ((input, options) => typeof input === 'string'
353
+ ? this.request('operationsRunsList', { pathParams: { operationId: input } }, options)
354
+ : this.request('operationRunsList', { query: input }, options)),
355
+ run: (operationId, body, options) => this.request('operationsRun', { pathParams: { operationId }, body }, options),
356
+ };
357
+ this.dataFlows = {
358
+ create: (body, options) => this.request('dataFlowsCreate', { body }, options),
359
+ get: (dataFlowId, options) => this.request('dataFlowsGet', { pathParams: { dataFlowId } }, options),
360
+ command: (dataFlowId, body, options) => this.request('dataFlowsCommand', { pathParams: { dataFlowId }, body }, options),
361
+ };
362
+ this.dashboards = {
363
+ getSource: (dashboardId, requestOptions) => this.request('dashboardsSourceGet', { pathParams: { dashboardId } }, requestOptions),
364
+ updateSource: (dashboardId, request, requestOptions) => this.request('dashboardsSourceUpdate', { pathParams: { dashboardId }, body: request }, requestOptions),
365
+ };
306
366
  const readDataset = (datasetId, requestOptions) => this.request('datasetsGet', { pathParams: { datasetId } }, requestOptions);
307
367
  const writeDatasetTables = async (datasetId, request, requestOptions) => {
308
368
  const encoded = encodeDatasetWrite(request);
@@ -428,6 +488,11 @@ export class PermuteClient {
428
488
  const dataset = await readDataset(datasetId, requestOptions);
429
489
  return wrapDataset(dataset);
430
490
  },
491
+ download: async (datasetId, requestOptions) => {
492
+ const { url } = await this.request('datasetsDownload', { pathParams: { datasetId } }, requestOptions);
493
+ return url;
494
+ },
495
+ delete: (datasetId, requestOptions) => this.request('datasetsDelete', { pathParams: { datasetId } }, requestOptions),
431
496
  };
432
497
  }
433
498
  async search(request, options) {
@@ -494,7 +559,7 @@ export class PermuteClient {
494
559
  }
495
560
  const url = new URL(`${this.baseUrl.replace(/\/+$/, '')}${buildPath(route, input.pathParams)}`);
496
561
  for (const [key, value] of Object.entries(query))
497
- url.searchParams.set(key, value);
562
+ url.searchParams.set(key, String(value));
498
563
  const response = await this.fetchImpl(url.toString(), {
499
564
  method: route.method,
500
565
  headers,
@@ -548,6 +613,6 @@ export class PermuteClient {
548
613
  if (!queryValidator(requestQuery)) {
549
614
  throw validationError(operationId, 'request', queryValidator.errors);
550
615
  }
551
- return requestQuery;
616
+ return Object.fromEntries(Object.entries(requestQuery).map(([key, value]) => [key, String(value)]));
552
617
  }
553
618
  }