@permute/sdk 0.1.2 → 0.3.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
@@ -58,6 +151,29 @@ const client = new PermuteClient({
58
151
  grant access. The selected ID is available as `client.workspaceId`. Organization
59
152
  APIs such as `client.workspaces.list()` do not send a workspace header.
60
153
 
154
+ ## Connect Customer Sources
155
+
156
+ Create a hosted setup link from your server after authorizing the customer's
157
+ workspace. Use `Providers` for provider IDs (for example, `Providers.HUBSPOT`).
158
+
159
+ ```ts
160
+ import { Providers } from '@permute/sdk';
161
+
162
+ const session = await client.connectorSessions.create({
163
+ provider: Providers.HUBSPOT,
164
+ name: 'CRM',
165
+ });
166
+ // Send session.url to the authorized customer to open in a new tab or popup.
167
+
168
+ // Reconnect an existing source; its provider is inferred.
169
+ const reconnect = await client.connectorSessions.create({
170
+ connectorId: savedConnectorId,
171
+ });
172
+ ```
173
+
174
+ See the [hosted connector setup guide](https://permute.ai/docs/connector-sessions)
175
+ for completion checks and return URLs.
176
+
61
177
  ## Upload Files
62
178
 
63
179
  Use the one-call helper when you have file bytes in process. It creates a file
@@ -109,13 +225,13 @@ data. The SDK accepts JavaScript objects, encodes them as chunked NDJSON, and
109
225
  uploads the result to a signed S3 target; callers do not need temporary files.
110
226
 
111
227
  ```ts
112
- import { CONTEXT, PermuteClient } from '@permute/sdk';
228
+ import { Context, PermuteClient } from '@permute/sdk';
113
229
 
114
230
  const dataset = await client.datasets.create({
115
231
  type: 'custom',
116
232
  key: 'quickbooks',
117
233
  name: 'QuickBooks',
118
- context: [CONTEXT.QuickBooks],
234
+ context: [Context.QUICKBOOKS],
119
235
  });
120
236
 
121
237
  await dataset.replace({
@@ -126,6 +242,10 @@ await dataset.replace({
126
242
  console.log(dataset.sourceId, dataset.version, dataset.data.tables);
127
243
  ```
128
244
 
245
+ Dataset `context` accepts string identifiers, with `Context` providing named
246
+ constants for known contexts (such as `Context.QUICKBOOKS`). It is not restricted
247
+ to the connector provider list. Use `Providers` when selecting a connector.
248
+
129
249
  `replace()` sends the complete dataset snapshot. Tables omitted from the call
130
250
  are removed, while tables with the same name keep their stable identity for
131
251
  reports and dashboards. Retrying an identical replacement is a no-op.
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, DatasetDeleteResponse, DatasetResponse, FileDatasetCreateRequest, FileDatasetResponse, DatasetRows, CustomDatasetResponse } from './types.js';
1
+ import type { ConnectorSession, ConnectorSessionCreated, ConnectorSessionCreateRequest, 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,42 @@ 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
+ };
95
+ readonly connectorSessions: {
96
+ create(request: ConnectorSessionCreateRequest, options?: PermuteRequestOptions): Promise<ConnectorSessionCreated>;
97
+ get(sessionId: string, options?: PermuteRequestOptions): Promise<ConnectorSession>;
98
+ };
64
99
  readonly workspaces: {
65
100
  list: (options?: PermuteRequestOptions) => Promise<WorkspaceList>;
66
101
  create: (request: WorkspaceCreateRequest, options?: PermuteRequestOptions) => Promise<Workspace>;
@@ -72,6 +107,28 @@ export declare class PermuteClient {
72
107
  list: (options?: PermuteRequestOptions) => Promise<SourceList>;
73
108
  get: (sourceId: string, options?: PermuteRequestOptions) => Promise<SourceDetail>;
74
109
  };
110
+ readonly operations: {
111
+ list: (query?: OperationListQuery, options?: PermuteRequestOptions) => Promise<OperationList>;
112
+ get: (operationId: string, options?: PermuteRequestOptions) => Promise<Operation>;
113
+ update: (operationId: string, request: OperationUpdateRequest, options?: PermuteRequestOptions) => Promise<Operation>;
114
+ getSource: (operationId: string, options?: PermuteRequestOptions) => Promise<OperationSource>;
115
+ updateSource: (operationId: string, request: OperationSourceUpdateRequest, options?: PermuteRequestOptions) => Promise<OperationSource>;
116
+ getRunOutput: (operationId: string, runId: string, options?: PermuteRequestOptions) => Promise<OperationRunOutput>;
117
+ listRuns: {
118
+ (operationId: string, options?: PermuteRequestOptions): Promise<OperationRunList>;
119
+ (query: OperationRunQuery, options?: PermuteRequestOptions): Promise<OperationRunGroup>;
120
+ };
121
+ run: (operationId: string, request: OperationRunRequest, options?: PermuteRequestOptions) => Promise<OperationRunStarted>;
122
+ };
123
+ readonly dataFlows: {
124
+ create: (request: DataFlowCreateRequest, options?: PermuteRequestOptions) => Promise<DataFlow>;
125
+ get: (dataFlowId: string, options?: PermuteRequestOptions) => Promise<DataFlow>;
126
+ command: (dataFlowId: string, request: DataFlowCommandRequest, options?: PermuteRequestOptions) => Promise<DataFlowCommandResponse>;
127
+ };
128
+ readonly dashboards: {
129
+ getSource: (dashboardId: string, options?: PermuteRequestOptions) => Promise<DashboardSource>;
130
+ updateSource: (dashboardId: string, request: DashboardSourceUpdateRequest, options?: PermuteRequestOptions) => Promise<DashboardSource>;
131
+ };
75
132
  readonly datasets: {
76
133
  create: {
77
134
  (request: CustomDatasetCreateRequest, options?: PermuteRequestOptions): Promise<PermuteDataset>;
package/dist/client.js CHANGED
@@ -262,8 +262,17 @@ export class PermuteFileDataset {
262
262
  }
263
263
  }
264
264
  export class PermuteClient {
265
+ revisions;
266
+ facts;
267
+ skills;
268
+ organizationSkills;
269
+ databases;
270
+ connectorSessions;
265
271
  workspaces;
266
272
  sources;
273
+ operations;
274
+ dataFlows;
275
+ dashboards;
267
276
  datasets;
268
277
  apiKey;
269
278
  baseUrl;
@@ -292,6 +301,41 @@ export class PermuteClient {
292
301
  this.workspaceId = options.workspaceId;
293
302
  this.defaultHeaders = options.headers ?? {};
294
303
  this.fetchImpl = fetchImpl.bind(globalThis);
304
+ this.revisions = {
305
+ list: (resourceId, options) => this.request('resourceRevisionsList', { pathParams: { resourceId } }, options),
306
+ get: (resourceId, revisionId, options) => this.request('resourceRevisionsGet', { pathParams: { resourceId, revisionId } }, options),
307
+ restore: (resourceId, revisionId, body, options) => this.request('resourceRevisionsRestore', { pathParams: { resourceId, revisionId }, body }, options),
308
+ publish: (resourceId, body, options) => this.request('resourcesPublish', { pathParams: { resourceId }, body }, options),
309
+ };
310
+ this.facts = {
311
+ list: (options) => this.request('factsList', {}, options),
312
+ get: (factId, options) => this.request('factsGet', { pathParams: { factId } }, options),
313
+ create: (body, options) => this.request('factsCreate', { body }, options),
314
+ update: (factId, body, options) => this.request('factsUpdate', { pathParams: { factId }, body }, options),
315
+ delete: (factId, options) => this.request('factsDelete', { pathParams: { factId } }, options),
316
+ };
317
+ const skillsClient = (prefix) => ({
318
+ list: (options) => this.request(`${prefix}List`, {}, options),
319
+ get: (path, options) => this.request(`${prefix}Get`, { pathParams: { path } }, options),
320
+ update: (path, body, options) => this.request(`${prefix}Update`, { pathParams: { path }, body }, options),
321
+ delete: (path, options) => this.request(`${prefix}Delete`, { pathParams: { path } }, options),
322
+ });
323
+ this.skills = skillsClient('skills');
324
+ this.organizationSkills = skillsClient('organizationSkills');
325
+ this.databases = {
326
+ list: (options) => this.request('databasesList', {}, options),
327
+ create: (body, options) => this.request('databasesCreate', { body }, options),
328
+ get: (databaseId, options) => this.request('databasesGet', { pathParams: { databaseId } }, options),
329
+ delete: (databaseId, options) => this.request('databasesDelete', { pathParams: { databaseId } }, options),
330
+ tables: (databaseId, options) => this.request('databaseTablesList', { pathParams: { databaseId } }, options),
331
+ createTable: (databaseId, body, options) => this.request('databaseTablesCreate', { pathParams: { databaseId }, body }, options),
332
+ records: (databaseId, table, query = {}, options) => this.request('databaseRecordsList', { pathParams: { databaseId, table }, query }, options),
333
+ transact: (databaseId, body, options) => this.request('databaseTransactionsCreate', { pathParams: { databaseId }, body }, options),
334
+ };
335
+ this.connectorSessions = {
336
+ create: (body, options) => this.request('connectorSessionsCreate', { body }, options),
337
+ get: (sessionId, options) => this.request('connectorSessionsGet', { pathParams: { sessionId } }, options),
338
+ };
295
339
  this.workspaces = {
296
340
  list: (requestOptions) => this.request('workspacesList', {}, requestOptions),
297
341
  create: (request, requestOptions) => this.request('workspacesCreate', { body: request }, requestOptions),
@@ -303,6 +347,27 @@ export class PermuteClient {
303
347
  list: (requestOptions) => this.request('sourcesList', {}, requestOptions),
304
348
  get: (sourceId, requestOptions) => this.request('sourcesGet', { pathParams: { sourceId } }, requestOptions),
305
349
  };
350
+ this.operations = {
351
+ list: (query = {}, options) => this.request('operationsList', { query }, options),
352
+ get: (operationId, options) => this.request('operationsGet', { pathParams: { operationId } }, options),
353
+ update: (operationId, body, options) => this.request('operationsUpdate', { pathParams: { operationId }, body }, options),
354
+ getSource: (operationId, options) => this.request('operationsSourceGet', { pathParams: { operationId } }, options),
355
+ updateSource: (operationId, body, options) => this.request('operationsSourceUpdate', { pathParams: { operationId }, body }, options),
356
+ getRunOutput: (operationId, runId, options) => this.request('operationsRunOutput', { pathParams: { operationId, runId } }, options),
357
+ listRuns: ((input, options) => typeof input === 'string'
358
+ ? this.request('operationsRunsList', { pathParams: { operationId: input } }, options)
359
+ : this.request('operationRunsList', { query: input }, options)),
360
+ run: (operationId, body, options) => this.request('operationsRun', { pathParams: { operationId }, body }, options),
361
+ };
362
+ this.dataFlows = {
363
+ create: (body, options) => this.request('dataFlowsCreate', { body }, options),
364
+ get: (dataFlowId, options) => this.request('dataFlowsGet', { pathParams: { dataFlowId } }, options),
365
+ command: (dataFlowId, body, options) => this.request('dataFlowsCommand', { pathParams: { dataFlowId }, body }, options),
366
+ };
367
+ this.dashboards = {
368
+ getSource: (dashboardId, requestOptions) => this.request('dashboardsSourceGet', { pathParams: { dashboardId } }, requestOptions),
369
+ updateSource: (dashboardId, request, requestOptions) => this.request('dashboardsSourceUpdate', { pathParams: { dashboardId }, body: request }, requestOptions),
370
+ };
306
371
  const readDataset = (datasetId, requestOptions) => this.request('datasetsGet', { pathParams: { datasetId } }, requestOptions);
307
372
  const writeDatasetTables = async (datasetId, request, requestOptions) => {
308
373
  const encoded = encodeDatasetWrite(request);
@@ -499,7 +564,7 @@ export class PermuteClient {
499
564
  }
500
565
  const url = new URL(`${this.baseUrl.replace(/\/+$/, '')}${buildPath(route, input.pathParams)}`);
501
566
  for (const [key, value] of Object.entries(query))
502
- url.searchParams.set(key, value);
567
+ url.searchParams.set(key, String(value));
503
568
  const response = await this.fetchImpl(url.toString(), {
504
569
  method: route.method,
505
570
  headers,
@@ -553,6 +618,6 @@ export class PermuteClient {
553
618
  if (!queryValidator(requestQuery)) {
554
619
  throw validationError(operationId, 'request', queryValidator.errors);
555
620
  }
556
- return requestQuery;
621
+ return Object.fromEntries(Object.entries(requestQuery).map(([key, value]) => [key, String(value)]));
557
622
  }
558
623
  }