@permute/sdk 0.2.0 → 0.4.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
@@ -26,7 +26,7 @@ The server validates source safety and derives the dashboard data contract from
26
26
  the referenced run. Clients cannot supply an authoritative contract.
27
27
 
28
28
  ```sh
29
- npm install @permute/sdk
29
+ bun add @permute/sdk
30
30
  ```
31
31
 
32
32
  ## Author data flows with external agents
@@ -101,6 +101,39 @@ and analyzing governed outputs; it does not expose code editing, draft inspectio
101
101
  execution, or publication. Hosted connectors also restrict access to selected sources. Flow scheduling and standalone operation
102
102
  creation are not part of this surface.
103
103
 
104
+ ## Publish write syncs
105
+
106
+ Use `client.writeSyncs.destinations()` to discover writable destination objects
107
+ and fields, then create a paused sync with `writeSyncs.create({ definition })`.
108
+ For operation sources, use `client.sources.get(operationId)` to inspect tables
109
+ and fields, then read `client.operations.get(operationId)` and use its
110
+ `published.revisionId` in the definition. Publish and run the operation before
111
+ configuring its sync. Definition edits use `writeSyncs.update(id, {
112
+ definition, expectedVersion })`, with the version from the latest read.
113
+
114
+ `writeSyncs.preview(id)` queues a read-only capture, including when the sync is
115
+ enabled. Poll `writeSyncs.getRun(id, runId)` until `previewComplete` or
116
+ `previewError`, then inspect every page with `getRun(id, runId, { page })`
117
+ (pages start at zero) before approving delivery.
118
+
119
+ ```ts
120
+ // After reviewing every page of a completed preview:
121
+ const history = await client.revisions.list(writeSyncId);
122
+ await client.revisions.publish(writeSyncId, {
123
+ expectedVersion: history.version,
124
+ runId,
125
+ });
126
+ ```
127
+
128
+ Publishing approves that preview, starts external delivery now, and schedules
129
+ new captures every 24 hours under the same API key that captured the preview.
130
+ New captures include `definitionRevisionId`, which identifies the exact saved
131
+ definition used by that run; older captures may omit it.
132
+ For an enabled sync, `writeSyncs.execute(id, runId)` sends a selected completed
133
+ preview captured by that same key without refreshing its values or changing the
134
+ schedule. Poll `getRun` for delivery results and inspect per-record outcomes.
135
+ Use `writeSyncs.pause(id)` to stop future writes; requests already sent may finish.
136
+
104
137
  ## Configure
105
138
 
106
139
  ```ts
@@ -151,6 +184,29 @@ const client = new PermuteClient({
151
184
  grant access. The selected ID is available as `client.workspaceId`. Organization
152
185
  APIs such as `client.workspaces.list()` do not send a workspace header.
153
186
 
187
+ ## Connect Customer Sources
188
+
189
+ Create a hosted setup link from your server after authorizing the customer's
190
+ workspace. Use `Providers` for provider IDs (for example, `Providers.HUBSPOT`).
191
+
192
+ ```ts
193
+ import { Providers } from '@permute/sdk';
194
+
195
+ const session = await client.connectorSessions.create({
196
+ provider: Providers.HUBSPOT,
197
+ name: 'CRM',
198
+ });
199
+ // Send session.url to the authorized customer to open in a new tab or popup.
200
+
201
+ // Reconnect an existing source; its provider is inferred.
202
+ const reconnect = await client.connectorSessions.create({
203
+ connectorId: savedConnectorId,
204
+ });
205
+ ```
206
+
207
+ See the [hosted connector setup guide](https://permute.ai/docs/connector-sessions)
208
+ for completion checks and return URLs.
209
+
154
210
  ## Upload Files
155
211
 
156
212
  Use the one-call helper when you have file bytes in process. It creates a file
@@ -202,13 +258,13 @@ data. The SDK accepts JavaScript objects, encodes them as chunked NDJSON, and
202
258
  uploads the result to a signed S3 target; callers do not need temporary files.
203
259
 
204
260
  ```ts
205
- import { CONTEXT, PermuteClient } from '@permute/sdk';
261
+ import { Context, PermuteClient } from '@permute/sdk';
206
262
 
207
263
  const dataset = await client.datasets.create({
208
264
  type: 'custom',
209
265
  key: 'quickbooks',
210
266
  name: 'QuickBooks',
211
- context: [CONTEXT.QuickBooks],
267
+ context: [Context.QUICKBOOKS],
212
268
  });
213
269
 
214
270
  await dataset.replace({
@@ -219,6 +275,10 @@ await dataset.replace({
219
275
  console.log(dataset.sourceId, dataset.version, dataset.data.tables);
220
276
  ```
221
277
 
278
+ Dataset `context` accepts string identifiers, with `Context` providing named
279
+ constants for known contexts (such as `Context.QUICKBOOKS`). It is not restricted
280
+ to the connector provider list. Use `Providers` when selecting a connector.
281
+
222
282
  `replace()` sends the complete dataset snapshot. Tables omitted from the call
223
283
  are removed, while tables with the same name keep their stable identity for
224
284
  reports and dashboards. Retrying an identical replacement is a no-op.
@@ -287,24 +347,51 @@ const evidence = await client.search({
287
347
  console.log(evidence.items);
288
348
  ```
289
349
 
290
- Use SQL only when table names are known and exact rows matter. SQL requests must
291
- include `sourceIds`; call `client.sources.get()` first when table names are not
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.
350
+ Use SQL when table names are known and exact rows matter. SQL requests need
351
+ `sourceIds`. List a source's tables first, then describe the table you plan to
352
+ query (materialized connector fields come from the stored SQL table). Warehouse
353
+ sources use DuckDB SQL and can be queried together. Live remote sources use
354
+ their native SQL dialect and must be queried one at a time.
296
355
 
297
356
  ```ts
298
- const details = await client.sources.get(sourceIds[0]);
357
+ const dataset = sources.items.find((source) => source.type === 'dataset');
358
+ if (!dataset) throw new Error('No dataset is available');
359
+ const tables = await client.sources.listTables(dataset.id);
360
+ const table = await client.sources.describeTable(dataset.id, tables[0].id);
299
361
 
300
362
  const rows = await client.query({
301
- sourceIds: [details.source.id],
302
- sql: `SELECT * FROM "${details.tables[0].name}" LIMIT 10`,
363
+ sourceIds: [dataset.id],
364
+ sql: `SELECT * FROM "${table.id}" LIMIT 10`,
303
365
  });
304
366
 
305
- console.log(rows.rows);
367
+ console.log(table.fields, rows.rows, rows.timings);
306
368
  ```
307
369
 
370
+ `timings` separates source validation, DuckDB startup, source loading, and SQL
371
+ execution. Compare `totalMs` with the caller's elapsed time to find delay
372
+ outside the query action (including transport and response handling).
373
+
374
+ ## Managed databases
375
+
376
+ `client.databases` can create a database, define tables, read current records,
377
+ and submit atomic inserts, updates, or deletes. Use `client.query()` for filtered
378
+ SQL reads. Managed records include `__version`; updates and deletes need that
379
+ token, and a stale version returns 409. Reuse a `requestId` only when retrying
380
+ the same transaction after an uncertain response.
381
+
382
+ Schema definitions use `client.databases.definition(id)` and
383
+ `client.databases.saveDefinition(id, { definition, expectedDraft })`. Save the
384
+ complete table list, then publish the exact draft through `client.revisions`.
385
+ Published schemas only support adding tables; rows are not part of schema history.
386
+
387
+ **Upgrading from 0.3.x:** `createTable` now saves a draft. Publish the database
388
+ before using the new table. Direct `transact` calls require database admin
389
+ permission; use a business action for a delegated write.
390
+
391
+ See the [managed database guide](https://permute.ai/docs/guides/managed-database)
392
+ for a complete example (including provisioning, filtered reads, and conflict
393
+ handling).
394
+
308
395
  ## Error Handling
309
396
 
310
397
  ```ts
@@ -327,3 +414,127 @@ try {
327
414
  ## License
328
415
 
329
416
  Apache-2.0. Copyright 2026 Permute, Inc.
417
+
418
+ ## Hosted apps
419
+
420
+ Create an app with `client.apps.create({ name, key, description })`, then poll `client.apps.get(id)` until its deployment is live. Build your frontend locally with relative asset URLs (for Vite, `bun run build --base=./`).
421
+
422
+ ```typescript
423
+ const app = await client.apps.get(appId);
424
+ const files = await client.apps.uploadFiles(app.id, [
425
+ { path: 'index.html', data: await readFile('dist/index.html') },
426
+ { path: 'assets/main.js', data: await readFile('dist/assets/main.js') },
427
+ ]);
428
+ const saved = await client.apps.saveSite(app.id, {
429
+ site: { files, spa: true },
430
+ expectedDraft: app.draft,
431
+ });
432
+ const state = await client.revisions.list(app.id);
433
+ if (state.draft?.revisionId !== saved.draft.revisionId || state.draft.sequence !== saved.draft.sequence) {
434
+ throw new Error('The draft changed; review before publishing');
435
+ }
436
+ await client.revisions.publish(app.id, { expectedVersion: state.version });
437
+ ```
438
+
439
+ Import `readFile` from `node:fs/promises`. Supply the complete build directory; the filenames above are illustrative. Uploads hash the bytes and use signed, conditional S3 PUTs without forwarding API credentials. Uploading does not create a draft. `saveSite` verifies the uploaded files and saves the complete manifest as a draft revision; `revisions.publish` makes that draft live. The limits are 200 files, 4 MiB per file, and 50 MiB total.
440
+
441
+ Identical contents reuse existing files. Changed contents at the same path are stored separately, preserving earlier versions for rollback. Each save replaces the complete manifest (including unchanged files); omitted paths disappear from the live app when published. Resolve a 409 conflict against the current draft before retrying.
442
+
443
+ Visitors sign in through Permute and need app read permission. HTML receives a revision-specific base URL; use relative URLs for bundled and public assets. Inline scripts, external requests, workers, and server runtimes are not supported. Keep API keys and secrets out of compiled browser files. Restore a historical revision with `client.revisions.restore`, then publish the resulting draft to roll back. See the [complete app guide](https://permute.ai/docs/guides/deploy-app) for creation, directory uploads, updates, and rollback.
444
+
445
+ ### Inside a hosted app
446
+
447
+ Use the browser entry with the app's existing sign-in session (no API key in frontend code):
448
+
449
+ ```ts
450
+ import { PermuteAppClient } from '@permute/sdk/app';
451
+
452
+ const app = new PermuteAppClient();
453
+ const result = await app.query({
454
+ sourceIds: ['dbas_example'],
455
+ sql: 'SELECT * FROM app.contacts',
456
+ limit: 100,
457
+ });
458
+ const request = { requestId: crypto.randomUUID(), input: { name: 'Ada' } };
459
+ const receipt = await app.actions.invoke('actn_example', request);
460
+ ```
461
+
462
+ The client reads the hosted page's revision-pinned base URL. Save the app's `sourceIds` and
463
+ `actions: [{ actionId, revisionId }]` with its site manifest, then publish the app revision.
464
+ Queries require a declared source and the signed-in user's current read permission;
465
+ actions use the pinned published action revision and the user's invocation permission.
466
+ Read scope is source-level; client-supplied SQL is not a row-level authorization policy.
467
+ Keep the same request ID and input when retrying an uncertain action response. The client
468
+ does not retry writes automatically. A 401 requires signing in again.
469
+
470
+ ## Business actions
471
+
472
+ Use `client.actions.create`, `definition`, and `saveDefinition` to author a typed
473
+ handler with selected database tables or connector accounts. Publish with
474
+ `client.revisions.publish`, then call `client.actions.invoke` with a stable
475
+ `requestId` and input. `list` exposes published contracts; reading definitions
476
+ requires admin permission. See the [business action guide](https://permute.ai/docs/guides/business-actions)
477
+ for the complete create, save, publish, and invoke workflow.
478
+
479
+ ### Connector bindings
480
+
481
+ An action definition can bind multiple connector accounts by name alongside its database
482
+ schema bindings. Save and publish require admin permission on each selected resource.
483
+ The action runs with those delegated capabilities; callers need its invocation permission.
484
+
485
+ ```ts
486
+ await client.actions.saveDefinition(actionId, {
487
+ expectedDraft: null, // Use the current draft ref when updating.
488
+ definition: {
489
+ source: handlerSource,
490
+ inputSchema,
491
+ outputSchema,
492
+ invokePermission: 'write',
493
+ sources: databaseBindings,
494
+ connectors: {
495
+ salesCrm: 'conn_aaaaaaaaaaaaaaaaaaaaaaaa',
496
+ partnerCrm: 'conn_bbbbbbbbbbbbbbbbbbbbbbbb',
497
+ },
498
+ },
499
+ });
500
+ ```
501
+
502
+ Both names can refer to HubSpot accounts. Handler code uses the bound name; credentials
503
+ stay in the trusted proxy. The following handler creates a HubSpot deal and stages a local
504
+ record (the action must declare `crm_deals` in its writable database binding):
505
+
506
+ ```js
507
+ exports.handler = async ({ input, connectors, db }) => {
508
+ const deal = await connectors.salesCrm.request({
509
+ stepId: 'create-deal',
510
+ method: 'POST',
511
+ path: '/crm/v3/objects/deals',
512
+ body: {
513
+ properties: {
514
+ dealname: input.name,
515
+ dealstage: input.hubspotStageId,
516
+ pipeline: input.hubspotPipelineId,
517
+ },
518
+ },
519
+ });
520
+ db.insert('crm_deals', { id: input.id, name: input.name, hubspot_id: deal.id });
521
+ return { id: input.id, hubspotId: deal.id };
522
+ };
523
+ ```
524
+
525
+ `db.insert/update/delete` stages the local mutations until the handler finishes and its
526
+ output validates. Existing `{ mutations, output }` handlers remain supported. External
527
+ requests run when awaited and cannot be rolled back if the local commit later fails.
528
+
529
+ HTTP providers expose `request({ stepId, method, path, headers?, body?, service? })` using
530
+ provider-relative paths and current authentication. Supabase and BigQuery expose
531
+ `query(sql, params, { stepId })`; S3 exposes `putObject({ stepId, key, body, contentType? })`
532
+ for text objects in the configured bucket. The connected account's grants apply without
533
+ an action-level endpoint allowlist. The SEC integration has no action transport because
534
+ it uses a shared Permute service key.
535
+
536
+ Reuse the same invocation `requestId` and input after a failed or lost response, and keep
537
+ step IDs stable in code. The first attempt pins the revision; successful external steps
538
+ replay saved responses. Steps with uncertain outcomes block automatic resend and require
539
+ provider-state reconciliation. There is no automatic rollback, reconciliation API, or
540
+ long-running workflow runner. Connector-only actions can use `sources: []`.
package/dist/app.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { ActionRequest, ActionReceipt, QueryRequest, QueryResponse } from './types.js';
2
+ export { PermuteApiError } from './errors.js';
3
+ export type { ActionRequest, ActionReceipt, QueryRequest, QueryResponse } from './types.js';
4
+ export interface PermuteAppClientOptions {
5
+ /** Defaults to the hosted page's revision-pinned document.baseURI. */
6
+ baseUrl?: string;
7
+ fetch?: typeof fetch;
8
+ }
9
+ export interface AppRequestOptions {
10
+ signal?: AbortSignal;
11
+ }
12
+ /** Browser client authenticated by the hosted app's HttpOnly session cookie. */
13
+ export declare class PermuteAppClient {
14
+ private readonly baseUrl;
15
+ private readonly fetcher;
16
+ constructor(options?: PermuteAppClientOptions);
17
+ readonly actions: {
18
+ /** Keep requestId and input unchanged when retrying an uncertain result. */
19
+ invoke: (actionId: string, request: ActionRequest, options?: AppRequestOptions) => Promise<ActionReceipt>;
20
+ };
21
+ query(request: QueryRequest, options?: AppRequestOptions): Promise<QueryResponse>;
22
+ private request;
23
+ }
package/dist/app.js ADDED
@@ -0,0 +1,65 @@
1
+ import { PermuteApiError } from './errors.js';
2
+ export { PermuteApiError } from './errors.js';
3
+ /** Browser client authenticated by the hosted app's HttpOnly session cookie. */
4
+ export class PermuteAppClient {
5
+ baseUrl;
6
+ fetcher;
7
+ constructor(options = {}) {
8
+ const base = options.baseUrl ?? (typeof document !== 'undefined' ? document.baseURI : undefined);
9
+ if (!base)
10
+ throw new Error('A hosted app revision URL is required.');
11
+ const url = new URL(base);
12
+ const revision = /^\/__revisions\/(rvsn_[a-z0-9]+)\//.exec(url.pathname);
13
+ if (!revision || !['https:', 'http:'].includes(url.protocol) || url.username || url.password)
14
+ throw new Error('Use a revision-pinned hosted app URL.');
15
+ if (typeof location !== 'undefined' && url.origin !== location.origin)
16
+ throw new Error('App requests must use the current origin.');
17
+ this.baseUrl = `${url.origin}${revision[0]}`;
18
+ this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
19
+ }
20
+ actions = {
21
+ /** Keep requestId and input unchanged when retrying an uncertain result. */
22
+ invoke: (actionId, request, options) => this.request(`__actions/${encodeURIComponent(actionId)}/invoke`, request, options),
23
+ };
24
+ query(request, options) {
25
+ return this.request('__queries', request, options);
26
+ }
27
+ async request(path, body, options) {
28
+ const response = await this.fetcher(`${this.baseUrl}${path}`, {
29
+ method: 'POST',
30
+ credentials: 'same-origin',
31
+ mode: 'same-origin',
32
+ redirect: 'error',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify(body),
35
+ signal: options?.signal,
36
+ });
37
+ const text = await response.text();
38
+ let payload;
39
+ try {
40
+ payload = JSON.parse(text);
41
+ }
42
+ catch {
43
+ /* Gateways can return plain-text errors. */
44
+ }
45
+ if (!response.ok) {
46
+ const error = payload?.error;
47
+ throw new PermuteApiError({
48
+ status: response.status,
49
+ code: error?.code ?? (response.status === 401 ? 'unauthenticated' : 'request_failed'),
50
+ message: (typeof error === 'string' ? error : error?.message) ??
51
+ (response.headers.get('content-type')?.startsWith('text/plain') ? text : null) ??
52
+ `App request failed (${response.status}).`,
53
+ details: error?.details,
54
+ requestId: error?.requestId,
55
+ });
56
+ }
57
+ if (!payload || typeof payload !== 'object' || !Object.hasOwn(payload, 'data'))
58
+ throw new PermuteApiError({
59
+ status: response.status,
60
+ code: 'invalid_response',
61
+ message: 'Invalid app response.',
62
+ });
63
+ return payload.data;
64
+ }
65
+ }
package/dist/client.d.ts CHANGED
@@ -1,17 +1,6 @@
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
- export declare class PermuteApiError extends Error {
3
- readonly status: number;
4
- readonly code: string;
5
- readonly details?: unknown;
6
- readonly requestId?: string;
7
- constructor(input: {
8
- status: number;
9
- code: string;
10
- message: string;
11
- details?: unknown;
12
- requestId?: string;
13
- });
14
- }
1
+ export { PermuteApiError } from './errors.js';
2
+ import type { BusinessAction, BusinessActionList, ActionCreateRequest, ActionDefinition, ActionDefinitionSaveRequest, ActionDefinitionSaved, ActionRequest, ActionReceipt } from './types.js';
3
+ import type { ConnectorSession, ConnectorSessionCreated, ConnectorSessionCreateRequest, Database, AppDraft, AppDraftList, AppCreateRequest, AppUpdateRequest, AppDeleted, AppUploadsRequest, AppUploads, AppSiteSaveRequest, AppUploadFile, DatabaseList, DatabaseCreateRequest, DatabaseDefinition, DatabaseDefinitionSaveRequest, DatabaseDefinitionSaved, DatabaseTables, DatabaseTableCreateRequest, DatabaseTableCreated, DatabaseRecords, DatabaseRecordsQuery, DatabaseTransactionRequest, DatabaseTransactionReceipt, WriteSync, WriteSyncCreateRequest, WriteSyncUpdateRequest, WriteSyncDetail, WriteSyncList, WriteSyncDeleted, WriteSyncDestinations, WriteSyncRun, WriteSyncRunList, WriteSyncRunQuery, WriteSyncExecution, ResourceVersionState, ResourceRevisionList, ResourceRevision, ResourceVersionRequest, ResourcePublishRequest, 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, CatalogTable, SourceList, CustomDatasetCreateRequest, DatasetDeleteResponse, DatasetResponse, FileDatasetCreateRequest, FileDatasetResponse, DatasetRows, CustomDatasetResponse, DashboardSource, DashboardSourceUpdateRequest } from './types.js';
15
4
  type DatasetWriteInput = {
16
5
  tables: Record<string, DatasetRows>;
17
6
  mode: 'replace';
@@ -71,7 +60,7 @@ export declare class PermuteClient {
71
60
  list(resourceId: string, options?: PermuteRequestOptions): Promise<ResourceRevisionList>;
72
61
  get(resourceId: string, revisionId: string, options?: PermuteRequestOptions): Promise<ResourceRevision>;
73
62
  restore(resourceId: string, revisionId: string, request: ResourceVersionRequest, options?: PermuteRequestOptions): Promise<ResourceVersionState>;
74
- publish(resourceId: string, request: ResourceVersionRequest, options?: PermuteRequestOptions): Promise<ResourceVersionState>;
63
+ publish(resourceId: string, request: ResourcePublishRequest, options?: PermuteRequestOptions): Promise<ResourceVersionState>;
75
64
  };
76
65
  readonly facts: {
77
66
  list(options?: PermuteRequestOptions): Promise<FactList>;
@@ -82,16 +71,54 @@ export declare class PermuteClient {
82
71
  };
83
72
  readonly skills: SkillsClient;
84
73
  readonly organizationSkills: SkillsClient;
74
+ readonly actions: {
75
+ list(options?: PermuteRequestOptions): Promise<BusinessActionList>;
76
+ create(body: ActionCreateRequest, options?: PermuteRequestOptions): Promise<BusinessAction>;
77
+ get(actionId: string, options?: PermuteRequestOptions): Promise<BusinessAction>;
78
+ delete(actionId: string, options?: PermuteRequestOptions): Promise<BusinessAction>;
79
+ definition(actionId: string, options?: PermuteRequestOptions): Promise<ActionDefinition>;
80
+ saveDefinition(actionId: string, body: ActionDefinitionSaveRequest, options?: PermuteRequestOptions): Promise<ActionDefinitionSaved>;
81
+ invoke(actionId: string, body: ActionRequest, options?: PermuteRequestOptions): Promise<ActionReceipt>;
82
+ };
85
83
  readonly databases: {
86
84
  list(options?: PermuteRequestOptions): Promise<DatabaseList>;
87
85
  create(body: DatabaseCreateRequest, options?: PermuteRequestOptions): Promise<Database>;
88
86
  get(databaseId: string, options?: PermuteRequestOptions): Promise<Database>;
89
87
  delete(databaseId: string, options?: PermuteRequestOptions): Promise<Database>;
88
+ definition(databaseId: string, options?: PermuteRequestOptions): Promise<DatabaseDefinition>;
89
+ saveDefinition(databaseId: string, body: DatabaseDefinitionSaveRequest, options?: PermuteRequestOptions): Promise<DatabaseDefinitionSaved>;
90
90
  tables(databaseId: string, options?: PermuteRequestOptions): Promise<DatabaseTables>;
91
91
  createTable(databaseId: string, body: DatabaseTableCreateRequest, options?: PermuteRequestOptions): Promise<DatabaseTableCreated>;
92
92
  records(databaseId: string, table: string, query?: Partial<DatabaseRecordsQuery>, options?: PermuteRequestOptions): Promise<DatabaseRecords>;
93
93
  transact(databaseId: string, body: DatabaseTransactionRequest, options?: PermuteRequestOptions): Promise<DatabaseTransactionReceipt>;
94
94
  };
95
+ readonly apps: {
96
+ list(options?: PermuteRequestOptions): Promise<AppDraftList>;
97
+ create(body: AppCreateRequest, options?: PermuteRequestOptions): Promise<AppDraft>;
98
+ get(applicationId: string, options?: PermuteRequestOptions): Promise<AppDraft>;
99
+ update(applicationId: string, body: AppUpdateRequest, options?: PermuteRequestOptions): Promise<AppDraft>;
100
+ delete(applicationId: string, options?: PermuteRequestOptions): Promise<AppDeleted>;
101
+ prepareUploads(applicationId: string, body: AppUploadsRequest, options?: PermuteRequestOptions): Promise<AppUploads>;
102
+ uploadFiles(applicationId: string, files: AppUploadFile[], options?: PermuteRequestOptions): Promise<AppUploadsRequest['files']>;
103
+ saveSite(applicationId: string, body: AppSiteSaveRequest, options?: PermuteRequestOptions): Promise<AppDraft>;
104
+ };
105
+ readonly connectorSessions: {
106
+ create(request: ConnectorSessionCreateRequest, options?: PermuteRequestOptions): Promise<ConnectorSessionCreated>;
107
+ get(sessionId: string, options?: PermuteRequestOptions): Promise<ConnectorSession>;
108
+ };
109
+ readonly writeSyncs: {
110
+ list(options?: PermuteRequestOptions): Promise<WriteSyncList>;
111
+ create(request: WriteSyncCreateRequest, options?: PermuteRequestOptions): Promise<WriteSync>;
112
+ get(writeSyncId: string, options?: PermuteRequestOptions): Promise<WriteSyncDetail>;
113
+ update(writeSyncId: string, request: WriteSyncUpdateRequest, options?: PermuteRequestOptions): Promise<WriteSync>;
114
+ delete(writeSyncId: string, options?: PermuteRequestOptions): Promise<WriteSyncDeleted>;
115
+ destinations(options?: PermuteRequestOptions): Promise<WriteSyncDestinations>;
116
+ preview(writeSyncId: string, options?: PermuteRequestOptions): Promise<WriteSyncRun>;
117
+ pause(writeSyncId: string, options?: PermuteRequestOptions): Promise<WriteSync>;
118
+ listRuns(writeSyncId: string, options?: PermuteRequestOptions): Promise<WriteSyncRunList>;
119
+ getRun(writeSyncId: string, runId: string, query?: WriteSyncRunQuery, options?: PermuteRequestOptions): Promise<WriteSyncRun>;
120
+ execute(writeSyncId: string, runId: string, options?: PermuteRequestOptions): Promise<WriteSyncExecution>;
121
+ };
95
122
  readonly workspaces: {
96
123
  list: (options?: PermuteRequestOptions) => Promise<WorkspaceList>;
97
124
  create: (request: WorkspaceCreateRequest, options?: PermuteRequestOptions) => Promise<Workspace>;
@@ -102,6 +129,8 @@ export declare class PermuteClient {
102
129
  readonly sources: {
103
130
  list: (options?: PermuteRequestOptions) => Promise<SourceList>;
104
131
  get: (sourceId: string, options?: PermuteRequestOptions) => Promise<SourceDetail>;
132
+ listTables: (sourceId: string, options?: PermuteRequestOptions) => Promise<CatalogTable[]>;
133
+ describeTable: (sourceId: string, tableId: string, options?: PermuteRequestOptions) => Promise<CatalogTable>;
105
134
  };
106
135
  readonly operations: {
107
136
  list: (query?: OperationListQuery, options?: PermuteRequestOptions) => Promise<OperationList>;
@@ -152,4 +181,3 @@ export declare class PermuteClient {
152
181
  private validateRequestBody;
153
182
  private validateRequestQuery;
154
183
  }
155
- export {};
package/dist/client.js CHANGED
@@ -1,22 +1,10 @@
1
+ import { PermuteApiError } from './errors.js';
2
+ export { PermuteApiError } from './errors.js';
1
3
  import { Ajv } from 'ajv';
2
4
  import { createHash } from 'node:crypto';
3
5
  import { publicApiRoutes } from './generated/runtime-contract.js';
4
6
  const DEFAULT_BASE_URL = 'https://api.permute.ai';
5
7
  const DEFAULT_POLL_TIMEOUT_MS = 20 * 60 * 1000;
6
- export class PermuteApiError extends Error {
7
- status;
8
- code;
9
- details;
10
- requestId;
11
- constructor(input) {
12
- super(input.message);
13
- this.name = 'PermuteApiError';
14
- this.status = input.status;
15
- this.code = input.code;
16
- this.details = input.details;
17
- this.requestId = input.requestId;
18
- }
19
- }
20
8
  function routeFor(operationId) {
21
9
  const route = publicApiRoutes[operationId];
22
10
  if (!route) {
@@ -266,7 +254,11 @@ export class PermuteClient {
266
254
  facts;
267
255
  skills;
268
256
  organizationSkills;
257
+ actions;
269
258
  databases;
259
+ apps;
260
+ connectorSessions;
261
+ writeSyncs;
270
262
  workspaces;
271
263
  sources;
272
264
  operations;
@@ -321,16 +313,88 @@ export class PermuteClient {
321
313
  });
322
314
  this.skills = skillsClient('skills');
323
315
  this.organizationSkills = skillsClient('organizationSkills');
316
+ this.actions = {
317
+ list: (options) => this.request('actionsList', {}, options),
318
+ create: (body, options) => this.request('actionsCreate', { body }, options),
319
+ get: (actionId, options) => this.request('actionsGet', { pathParams: { actionId } }, options),
320
+ delete: (actionId, options) => this.request('actionsDelete', { pathParams: { actionId } }, options),
321
+ definition: (actionId, options) => this.request('actionDefinitionGet', { pathParams: { actionId } }, options),
322
+ saveDefinition: (actionId, body, options) => this.request('actionDefinitionSave', { pathParams: { actionId }, body }, options),
323
+ invoke: (actionId, body, options) => this.request('actionsInvoke', { pathParams: { actionId }, body }, options),
324
+ };
324
325
  this.databases = {
325
326
  list: (options) => this.request('databasesList', {}, options),
326
327
  create: (body, options) => this.request('databasesCreate', { body }, options),
327
328
  get: (databaseId, options) => this.request('databasesGet', { pathParams: { databaseId } }, options),
328
329
  delete: (databaseId, options) => this.request('databasesDelete', { pathParams: { databaseId } }, options),
330
+ definition: (databaseId, options) => this.request('databaseDefinitionGet', { pathParams: { databaseId } }, options),
331
+ saveDefinition: (databaseId, body, options) => this.request('databaseDefinitionSave', { pathParams: { databaseId }, body }, options),
329
332
  tables: (databaseId, options) => this.request('databaseTablesList', { pathParams: { databaseId } }, options),
330
333
  createTable: (databaseId, body, options) => this.request('databaseTablesCreate', { pathParams: { databaseId }, body }, options),
331
334
  records: (databaseId, table, query = {}, options) => this.request('databaseRecordsList', { pathParams: { databaseId, table }, query }, options),
332
335
  transact: (databaseId, body, options) => this.request('databaseTransactionsCreate', { pathParams: { databaseId }, body }, options),
333
336
  };
337
+ this.apps = {
338
+ prepareUploads: (applicationId, body, options) => this.request('appsUploadsCreate', { pathParams: { applicationId }, body }, options),
339
+ uploadFiles: async (applicationId, files, options) => {
340
+ const contents = await Promise.all(files.map(async (file) => ({
341
+ path: file.path,
342
+ bytes: typeof file.data === 'string'
343
+ ? Buffer.from(file.data)
344
+ : file.data instanceof Blob
345
+ ? Buffer.from(await file.data.arrayBuffer())
346
+ : Buffer.from(file.data),
347
+ })));
348
+ const manifest = contents.map(({ path, bytes }) => ({
349
+ path,
350
+ sha256: createHash('sha256').update(bytes).digest('hex'),
351
+ size: bytes.length,
352
+ }));
353
+ const { uploads } = await this.apps.prepareUploads(applicationId, { files: manifest }, options);
354
+ for (const target of uploads) {
355
+ const file = contents.find((item) => item.path === target.path);
356
+ if (!file)
357
+ throw new Error('Unexpected app upload path');
358
+ const uploaded = await this.fetchImpl(target.url, {
359
+ method: 'PUT',
360
+ headers: target.headers,
361
+ body: new Blob([new Uint8Array(file.bytes)]),
362
+ signal: options?.signal,
363
+ });
364
+ // A retry or another identical file can win the conditional PUT. Save verifies the stored checksum.
365
+ if (!uploaded.ok && uploaded.status !== 412)
366
+ throw new PermuteApiError({
367
+ status: uploaded.status,
368
+ code: 'upload_failed',
369
+ message: `App upload failed for ${target.path}`,
370
+ });
371
+ }
372
+ return manifest;
373
+ },
374
+ saveSite: (applicationId, body, options) => this.request('appsSiteUpdate', { pathParams: { applicationId }, body }, options),
375
+ list: (options) => this.request('appsList', {}, options),
376
+ create: (body, options) => this.request('appsCreate', { body }, options),
377
+ get: (applicationId, options) => this.request('appsGet', { pathParams: { applicationId } }, options),
378
+ update: (applicationId, body, options) => this.request('appsUpdate', { pathParams: { applicationId }, body }, options),
379
+ delete: (applicationId, options) => this.request('appsDelete', { pathParams: { applicationId } }, options),
380
+ };
381
+ this.connectorSessions = {
382
+ create: (body, options) => this.request('connectorSessionsCreate', { body }, options),
383
+ get: (sessionId, options) => this.request('connectorSessionsGet', { pathParams: { sessionId } }, options),
384
+ };
385
+ this.writeSyncs = {
386
+ list: (options) => this.request('writeSyncsList', {}, options),
387
+ create: (body, options) => this.request('writeSyncsCreate', { body }, options),
388
+ get: (writeSyncId, options) => this.request('writeSyncsGet', { pathParams: { writeSyncId } }, options),
389
+ update: (writeSyncId, body, options) => this.request('writeSyncsUpdate', { pathParams: { writeSyncId }, body }, options),
390
+ delete: (writeSyncId, options) => this.request('writeSyncsDelete', { pathParams: { writeSyncId } }, options),
391
+ destinations: (options) => this.request('writeSyncsDestinations', {}, options),
392
+ preview: (writeSyncId, options) => this.request('writeSyncsPreview', { pathParams: { writeSyncId } }, options),
393
+ pause: (writeSyncId, options) => this.request('writeSyncsPause', { pathParams: { writeSyncId } }, options),
394
+ listRuns: (writeSyncId, options) => this.request('writeSyncsListRuns', { pathParams: { writeSyncId } }, options),
395
+ getRun: (writeSyncId, runId, query = {}, options) => this.request('writeSyncsGetRun', { pathParams: { writeSyncId, runId }, query }, options),
396
+ execute: (writeSyncId, runId, options) => this.request('writeSyncsExecute', { pathParams: { writeSyncId, runId } }, options),
397
+ };
334
398
  this.workspaces = {
335
399
  list: (requestOptions) => this.request('workspacesList', {}, requestOptions),
336
400
  create: (request, requestOptions) => this.request('workspacesCreate', { body: request }, requestOptions),
@@ -341,6 +405,14 @@ export class PermuteClient {
341
405
  this.sources = {
342
406
  list: (requestOptions) => this.request('sourcesList', {}, requestOptions),
343
407
  get: (sourceId, requestOptions) => this.request('sourcesGet', { pathParams: { sourceId } }, requestOptions),
408
+ listTables: async (sourceId, requestOptions) => (await this.request('sourcesGet', {
409
+ pathParams: { sourceId },
410
+ query: { includeFields: 'false' },
411
+ }, requestOptions)).tables,
412
+ describeTable: async (sourceId, tableId, requestOptions) => (await this.request('sourcesGet', {
413
+ pathParams: { sourceId },
414
+ query: { tableId },
415
+ }, requestOptions)).tables[0],
344
416
  };
345
417
  this.operations = {
346
418
  list: (query = {}, options) => this.request('operationsList', { query }, options),
@@ -0,0 +1,13 @@
1
+ export declare class PermuteApiError extends Error {
2
+ readonly status: number;
3
+ readonly code: string;
4
+ readonly details?: unknown;
5
+ readonly requestId?: string;
6
+ constructor(input: {
7
+ status: number;
8
+ code: string;
9
+ message: string;
10
+ details?: unknown;
11
+ requestId?: string;
12
+ });
13
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,14 @@
1
+ export class PermuteApiError extends Error {
2
+ status;
3
+ code;
4
+ details;
5
+ requestId;
6
+ constructor(input) {
7
+ super(input.message);
8
+ this.name = 'PermuteApiError';
9
+ this.status = input.status;
10
+ this.code = input.code;
11
+ this.details = input.details;
12
+ this.requestId = input.requestId;
13
+ }
14
+ }