@permute/sdk 0.8.1 → 0.10.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 +144 -20
- package/dist/auth/oauth.d.ts +4 -0
- package/dist/auth/oauth.js +5 -1
- package/dist/auth/request.d.ts +24 -0
- package/dist/auth/request.js +15 -0
- package/dist/cli.js +13 -1
- package/dist/client.d.ts +35 -4
- package/dist/client.js +106 -23
- package/dist/generated/openapi.d.ts +501 -5
- package/dist/generated/runtime-contract.d.ts +3 -1
- package/dist/generated/runtime-contract.js +87 -19
- package/dist/types.d.ts +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,18 @@ bunx @permute/sdk auth logout
|
|
|
20
20
|
|
|
21
21
|
Choose an organization, optionally limit the connection to one workspace, then select resources and permissions to approve in Permute. The CLI stores renewable credentials in your operating system’s credential store (no plaintext fallback). Active logins have no fixed maximum lifetime and expire after 30 days without renewal; company sign-in rules can require earlier authentication. Logging out revokes access before removing the local credential. Signing in again revokes the previous stored CLI session.
|
|
22
22
|
|
|
23
|
+
To request a particular scope, add `--organization ID`, `--workspace ID`, or `--access-file PATH` to login. The JSON file uses the same resource/permission selections as API-key creation:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"organizationId": "org_...",
|
|
28
|
+
"workspaceId": "wksp_...",
|
|
29
|
+
"permissions": [{ "resourceId": "dbas_...", "resourceType": "database", "level": "read" }]
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
An SDK login can supply the same object as `new OAuthLogin({ requestedAccess })` before calling `beginDevice()` or `beginBrowser(...)`. The browser prefills the request and permits only narrower selections. When available in the same browser, reauthentication recalls settings from a prior session. Each login still requires confirmation, and current resource permissions apply (saved selections create no resource grants). The browser stores only a non-secret session ID; it does not create another grant or change existing sessions. Logout and Connections disconnect individual logins.
|
|
34
|
+
|
|
23
35
|
The SDK reuses credentials in memory between requests and reloads the OS store before renewal or when another process changes the saved login. The local auth directory holds only a lock and a non-secret change marker; tokens stay out of ordinary files. Current server permissions and revocation still apply to every request.
|
|
24
36
|
|
|
25
37
|
The CLI prints the issuer and `permute-cli` entry name to stderr before accessing the OS store. macOS controls its own permission dialog, which may identify the Node or Bun process. SDK applications stay silent by default and can supply `onCredentialStoreAccess: (issuer) => { /* show a notice */ }` to `OAuthLogin`; the callback runs once before the default OS store is accessed and receives no credentials.
|
|
@@ -130,8 +142,9 @@ Data Flow and operation authoring are REST/SDK only. MCP does not expose their c
|
|
|
130
142
|
editing, draft inspection, execution, or publication. Hosted MCP access uses
|
|
131
143
|
selected sources or a hosted app's current grants and published action bindings,
|
|
132
144
|
intersected with the user's permissions. App actions require confirmation in
|
|
133
|
-
Permute; see the [MCP guide](https://permute.ai/docs/mcp).
|
|
134
|
-
standalone operation creation
|
|
145
|
+
Permute; see the [MCP guide](https://permute.ai/docs/mcp). MCP does not expose flow
|
|
146
|
+
scheduling or standalone operation creation; schedule operations and flows with
|
|
147
|
+
`client.operations.update`.
|
|
135
148
|
|
|
136
149
|
## Publish write syncs
|
|
137
150
|
|
|
@@ -325,10 +338,8 @@ const dataset = await client.datasets.create({
|
|
|
325
338
|
context: [Context.QUICKBOOKS],
|
|
326
339
|
});
|
|
327
340
|
|
|
328
|
-
await dataset.
|
|
329
|
-
|
|
330
|
-
customers: [{ id: 10 }],
|
|
331
|
-
});
|
|
341
|
+
await dataset.replaceTable('orders', [{ id: 1 }, { id: 2 }]);
|
|
342
|
+
await dataset.replaceTable('customers', [{ id: 10 }]);
|
|
332
343
|
|
|
333
344
|
console.log(dataset.sourceId, dataset.version, dataset.data.tables);
|
|
334
345
|
```
|
|
@@ -337,15 +348,30 @@ Dataset `context` accepts string identifiers, with `Context` providing named
|
|
|
337
348
|
constants for known contexts (such as `Context.QUICKBOOKS`). It is not restricted
|
|
338
349
|
to the connector provider list. Use `Providers` when selecting a connector.
|
|
339
350
|
|
|
340
|
-
|
|
341
|
-
are removed, while tables with the same name keep their stable identity for
|
|
342
|
-
reports and dashboards. Retrying an identical replacement is a no-op.
|
|
351
|
+
### Add or replace one table
|
|
343
352
|
|
|
344
|
-
Use `replaceTable()`
|
|
345
|
-
of
|
|
353
|
+
Use `replaceTable()` to add a new named table or replace the complete contents
|
|
354
|
+
of an existing table. Existing rows not supplied are removed; other tables stay
|
|
355
|
+
in the dataset. Await each write before starting the next write to that dataset:
|
|
346
356
|
|
|
347
357
|
```ts
|
|
348
358
|
await dataset.replaceTable('orders', allOrders);
|
|
359
|
+
await dataset.replaceTable('customers', allCustomers);
|
|
360
|
+
// Both tables now belong to the same dataset.
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Replace the complete snapshot or append new rows
|
|
364
|
+
|
|
365
|
+
`replace()` sends the complete dataset snapshot. Tables omitted from the call
|
|
366
|
+
are removed, while tables with the same name keep their stable identity for
|
|
367
|
+
reports and dashboards. Retrying an identical replacement is a no-op:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
await dataset.replace({
|
|
371
|
+
orders: [{ id: 1 }, { id: 2 }],
|
|
372
|
+
customers: [{ id: 10 }],
|
|
373
|
+
});
|
|
374
|
+
// Only orders and customers remain in this dataset.
|
|
349
375
|
```
|
|
350
376
|
|
|
351
377
|
Use `appendTable()` only when every supplied row is new and previously written
|
|
@@ -363,14 +389,68 @@ and use a new value for a new append. It prevents a retried request from being
|
|
|
363
389
|
applied twice; reusing it with different rows fails. It does not remove duplicate
|
|
364
390
|
rows sent in different batches.
|
|
365
391
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
392
|
+
### Upload a local CSV
|
|
393
|
+
|
|
394
|
+
With SDK 0.9.0 or later, pass a disk-backed Blob to the same `replaceTable()` method.
|
|
395
|
+
Use Node.js 24 or later with `openAsBlob`; the SDK hashes the file in small blocks,
|
|
396
|
+
uploads it to S3, and waits for processing without loading the whole file into
|
|
397
|
+
memory:
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
import { openAsBlob } from 'node:fs';
|
|
401
|
+
|
|
402
|
+
await dataset.replaceTable('customers', await openAsBlob('./customers.csv'), {
|
|
403
|
+
format: 'csv',
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Supply comma-separated UTF-8 CSV with unique, nonempty column names in its first row.
|
|
408
|
+
Permute derives the row count and infers column types; an optional `rowCount`
|
|
409
|
+
checks an expected count. Keep the local file unchanged until its upload finishes.
|
|
410
|
+
Header-only CSV can create an empty table.
|
|
411
|
+
|
|
412
|
+
### Upload a gzipped CSV
|
|
413
|
+
|
|
414
|
+
Pass an existing CSV.gz file with `compression: 'gzip'`. The SDK sends the file
|
|
415
|
+
as-is (it does not gzip a plain CSV when you set the option). Unpack ZIP archives
|
|
416
|
+
before importing individual table files:
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
await dataset.replaceTable('order_history', await openAsBlob('./order_history.csv.gz'), {
|
|
420
|
+
format: 'csv',
|
|
421
|
+
compression: 'gzip',
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Import a large export
|
|
426
|
+
|
|
427
|
+
The row-array overload still serializes objects as NDJSON and holds both the
|
|
428
|
+
rows and encoded upload in memory. Use the Blob overload for large CSV exports.
|
|
429
|
+
|
|
430
|
+
Each uploaded file can be at most 5 GiB (5,368,709,120 bytes), with at most 10 GiB
|
|
431
|
+
(10,737,418,240 bytes) after decompression. The upload limit measures compressed
|
|
432
|
+
bytes when using gzip. Raw REST NDJSON writes also accept `compression: 'gzip'`;
|
|
433
|
+
`sizeBytes` and `contentHash` describe the exact uploaded bytes. Each complete
|
|
434
|
+
table replacement can contain at most 10,000,000 rows. A full `replace()` accepts
|
|
435
|
+
up to 1,000 tables within that same upload limit. The byte limit is per write,
|
|
436
|
+
so separate table writes can accumulate several GB in one dataset. Table-by-table
|
|
437
|
+
imports become visible as each write completes; they are not an atomic snapshot
|
|
438
|
+
of the whole export. New empty NDJSON tables require an existing schema.
|
|
439
|
+
|
|
440
|
+
For CSV exports with hundreds of tables, await each `replaceTable()` call while
|
|
441
|
+
reusing the same dataset key. Use a different key for each separate source export.
|
|
442
|
+
The guide includes [folder imports](https://permute.ai/docs/guides/custom-dataset#folder-of-tables),
|
|
443
|
+
[multiple datasets](https://permute.ai/docs/guides/custom-dataset#multiple-datasets), and
|
|
444
|
+
[signed REST uploads](https://permute.ai/docs/guides/custom-dataset#csv-export-to-s3).
|
|
445
|
+
Validate imported column names, counts, and types before loading the full export.
|
|
446
|
+
These limits apply to custom table writes; `type: 'file'` creates a separate file
|
|
447
|
+
dataset with its own upload limits.
|
|
448
|
+
|
|
449
|
+
`appendTable()` accepts up to 10,000 rows per batch. Repeated small appends are
|
|
450
|
+
inefficient for a large snapshot; upload its complete CSV with `replaceTable()`.
|
|
451
|
+
Durable SDK operations stop polling after 20 minutes by default; pass
|
|
452
|
+
`pollTimeoutMs` to change the client wait. A polling timeout does not prove the
|
|
453
|
+
server write failed; inspect its status before retrying.
|
|
374
454
|
|
|
375
455
|
## Recommended Flow
|
|
376
456
|
|
|
@@ -814,7 +894,8 @@ account’s access even when the caller has no connector access. Permute enforce
|
|
|
814
894
|
declared execution capabilities and validates input/output schemas; the handler
|
|
815
895
|
defines the operation exposed to callers.
|
|
816
896
|
|
|
817
|
-
Handlers receive trusted `caller: { id, type }` (`type` is `user` or
|
|
897
|
+
Handlers receive trusted `caller: { id, type }` (`type` is `user`, `apiKey`, or
|
|
898
|
+
`system`; a scheduled trigger run passes `{ type: 'system', id: '<triggerId>' }`).
|
|
818
899
|
An action can use that identity and server-side business data to check or deny a
|
|
819
900
|
request when its rules warrant it (such as an ownership rule or allowed state transition).
|
|
820
901
|
Apply those checks before the affected reads or writes. They supplement the
|
|
@@ -929,6 +1010,49 @@ replay saved responses. Steps with uncertain outcomes block automatic resend and
|
|
|
929
1010
|
provider-state reconciliation. There is no automatic rollback, reconciliation API, or
|
|
930
1011
|
long-running workflow runner. Connector-only actions can use `sources: []`.
|
|
931
1012
|
|
|
1013
|
+
### Schedule triggers
|
|
1014
|
+
|
|
1015
|
+
A trigger invokes a published action automatically. Schedule triggers use the same UTC
|
|
1016
|
+
schedule format as operations and a fixed input which must match the published input contract.
|
|
1017
|
+
Schedule values are whole numbers: `hourly { minute }`, `daily { hour, minute }`,
|
|
1018
|
+
`weekly { hour, minute, dayOfWeek }` (0 is Sunday), and `monthly { hour, minute, dayOfMonth }`.
|
|
1019
|
+
Use `dayOfMonth` 1–28 to run in every month.
|
|
1020
|
+
|
|
1021
|
+
```ts
|
|
1022
|
+
const trigger = await client.actionTriggers.create('actn_example', {
|
|
1023
|
+
type: 'schedule',
|
|
1024
|
+
config: { schedule: { type: 'daily', hour: 9, minute: 0 }, input: { region: 'EMEA' } },
|
|
1025
|
+
});
|
|
1026
|
+
await client.actionTriggers.update('actn_example', trigger.id, { isEnabled: false });
|
|
1027
|
+
const { items } = await client.actionTriggers.runs('actn_example', trigger.id);
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
Trigger routes require API key authentication. A trigger has no owner and runs as the workspace
|
|
1031
|
+
system identity, so its runs do not recheck anyone's access. Instead, creating, editing, or enabling a
|
|
1032
|
+
trigger requires invocation permission and authority to delegate every capability which the action
|
|
1033
|
+
declares (SDK permissions, connectors, and sources), and publishing an action with an enabled trigger
|
|
1034
|
+
requires the publisher to be able to invoke the new revision. Every reader of the action can see its
|
|
1035
|
+
triggers, and invokers can change them. Scheduled runs pass `caller: { type: 'system', id: '<triggerId>' }`.
|
|
1036
|
+
Resources which a scheduled run creates through the SDK belong to the action owner (the user who
|
|
1037
|
+
created the action, or the user who created its API key); scheduled runs cannot create or delete
|
|
1038
|
+
hosted resources (managed databases and apps) or create connector sessions. Each run uses the action's current published revision and the
|
|
1039
|
+
request ID `<triggerId>_<slot epoch ms>`, so a retried slot never runs twice. Runs start within about
|
|
1040
|
+
five minutes of their slot. Missed slots run once, as the newest missed slot, and the skipped
|
|
1041
|
+
slots appear as a `businessAction.trigger.skipped` audit event. A deleted or
|
|
1042
|
+
unpublished action, or input which no longer matches the contract, disables the trigger and
|
|
1043
|
+
sets `disabledReason`; enable it again with `update` after fixing the cause. A slot which cannot
|
|
1044
|
+
start for 24 hours, for example because of a persistent platform error, also disables the trigger.
|
|
1045
|
+
Publishing a new action revision does not check trigger input; an incompatible trigger turns
|
|
1046
|
+
off at its next run. A disable records no run, so read `isEnabled` and `disabledReason`; `nextRunAt`
|
|
1047
|
+
is `null` while the trigger is off. Updating `config` replaces the whole config and repeats the
|
|
1048
|
+
access and input checks, like enabling. Pausing with `isEnabled: false` keeps `disabledReason` `null`.
|
|
1049
|
+
`runs` returns the newest 50 runs with status, revision, and failure, but not their output; to keep a
|
|
1050
|
+
run's results, write them inside the handler. Saved receipts belong to their caller, so invoking with a
|
|
1051
|
+
run's request ID runs the action again.
|
|
1052
|
+
Failures inside the action do not disable the trigger and are not retried; the next slot runs as
|
|
1053
|
+
scheduled. They appear in `runs`, as `lastRun` on the trigger, and as a
|
|
1054
|
+
`businessAction.trigger.failed` audit event.
|
|
1055
|
+
|
|
932
1056
|
## Hosted app visitor access (next release)
|
|
933
1057
|
|
|
934
1058
|
App admins can read `client.apps.access(appId)` and update access with
|
package/dist/auth/oauth.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type RequestedAccess } from './request.js';
|
|
2
|
+
export type { RequestedAccess } from './request.js';
|
|
1
3
|
import { PermuteClient } from '../client.js';
|
|
2
4
|
import { type CredentialStore, type LoginCredentials } from './store.js';
|
|
3
5
|
export { SystemCredentialStore, type CredentialStore, type LoginCredentials } from './store.js';
|
|
@@ -6,6 +8,7 @@ export declare const DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_cod
|
|
|
6
8
|
export interface OAuthOptions {
|
|
7
9
|
issuer?: string;
|
|
8
10
|
apiUrl?: string;
|
|
11
|
+
requestedAccess?: RequestedAccess;
|
|
9
12
|
store?: CredentialStore;
|
|
10
13
|
fetch?: typeof fetch;
|
|
11
14
|
/** Called once before the default OS credential store is accessed; no tokens are passed. */
|
|
@@ -16,6 +19,7 @@ export declare class OAuthLogin {
|
|
|
16
19
|
readonly apiUrl: string;
|
|
17
20
|
private readonly store;
|
|
18
21
|
private readonly fetchImpl;
|
|
22
|
+
private readonly requested;
|
|
19
23
|
constructor(options?: OAuthOptions);
|
|
20
24
|
private endpoint;
|
|
21
25
|
private post;
|
package/dist/auth/oauth.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { authorizationDetails } from './request.js';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { createHash, randomBytes } from 'node:crypto';
|
|
3
4
|
import { setTimeout } from 'node:timers/promises';
|
|
@@ -27,11 +28,13 @@ export class OAuthLogin {
|
|
|
27
28
|
apiUrl;
|
|
28
29
|
store;
|
|
29
30
|
fetchImpl;
|
|
31
|
+
requested;
|
|
30
32
|
constructor(options = {}) {
|
|
31
33
|
this.issuer = this.endpoint(options.issuer ?? DEFAULT_ISSUER);
|
|
32
34
|
this.apiUrl = this.endpoint(options.apiUrl ?? 'https://api.permute.ai');
|
|
33
35
|
this.store = options.store ?? new SystemCredentialStore(this.issuer, options.onCredentialStoreAccess);
|
|
34
36
|
this.fetchImpl = options.fetch ?? fetch;
|
|
37
|
+
this.requested = authorizationDetails(options.requestedAccess);
|
|
35
38
|
}
|
|
36
39
|
endpoint(value) {
|
|
37
40
|
const url = new URL(value);
|
|
@@ -90,7 +93,7 @@ export class OAuthLogin {
|
|
|
90
93
|
return credentials;
|
|
91
94
|
}
|
|
92
95
|
async beginDevice() {
|
|
93
|
-
const parsed = DeviceSchema.safeParse(await this.post('/cli/device/authorization', { client_id: CLIENT_ID, scope: 'workspace' }));
|
|
96
|
+
const parsed = DeviceSchema.safeParse(await this.post('/cli/device/authorization', { client_id: CLIENT_ID, scope: 'workspace', ...this.requested }));
|
|
94
97
|
if (!parsed.success)
|
|
95
98
|
throw new Error('Invalid device login response');
|
|
96
99
|
const result = parsed.data;
|
|
@@ -139,6 +142,7 @@ export class OAuthLogin {
|
|
|
139
142
|
url.search = new URLSearchParams({
|
|
140
143
|
client_id: CLIENT_ID,
|
|
141
144
|
scope: 'workspace',
|
|
145
|
+
...this.requested,
|
|
142
146
|
response_type: 'code',
|
|
143
147
|
redirect_uri: redirectURI,
|
|
144
148
|
state,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { components } from '../generated/openapi.js';
|
|
3
|
+
export type RequestedAccess = components['schemas']['OAuthRequestedAccess'];
|
|
4
|
+
export declare const RequestedAccessSchema: z.ZodCustom<{
|
|
5
|
+
organizationId?: string;
|
|
6
|
+
permissions?: {
|
|
7
|
+
level: 'read' | 'write' | 'admin';
|
|
8
|
+
operations?: ('application.read_metadata' | 'application.update_metadata' | 'application.read_implementation' | 'application.update_draft' | 'application.publish' | 'application.manage_access' | 'application.manage_hosting' | 'application.delete' | 'business_action.read_metadata' | 'business_action.invoke' | 'business_action.read_implementation' | 'business_action.update_draft' | 'business_action.publish' | 'business_action.manage_access' | 'business_action.delegate_access' | 'business_action.delete')[];
|
|
9
|
+
resourceId: string;
|
|
10
|
+
resourceType: string;
|
|
11
|
+
}[];
|
|
12
|
+
workspaceId?: string;
|
|
13
|
+
}, {
|
|
14
|
+
organizationId?: string;
|
|
15
|
+
permissions?: {
|
|
16
|
+
level: 'read' | 'write' | 'admin';
|
|
17
|
+
operations?: ('application.read_metadata' | 'application.update_metadata' | 'application.read_implementation' | 'application.update_draft' | 'application.publish' | 'application.manage_access' | 'application.manage_hosting' | 'application.delete' | 'business_action.read_metadata' | 'business_action.invoke' | 'business_action.read_implementation' | 'business_action.update_draft' | 'business_action.publish' | 'business_action.manage_access' | 'business_action.delegate_access' | 'business_action.delete')[];
|
|
18
|
+
resourceId: string;
|
|
19
|
+
resourceType: string;
|
|
20
|
+
}[];
|
|
21
|
+
workspaceId?: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** RFC 9396 carries structured permissions without inventing a second operation catalog. */
|
|
24
|
+
export declare function authorizationDetails(request: RequestedAccess | undefined): Record<string, string>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Ajv } from 'ajv';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { authorizationRequestSchema } from '../generated/runtime-contract.js';
|
|
4
|
+
const validate = new Ajv({ strict: false }).compile(authorizationRequestSchema);
|
|
5
|
+
export const RequestedAccessSchema = z.custom((value) => validate(value), 'Invalid requested access');
|
|
6
|
+
/** RFC 9396 carries structured permissions without inventing a second operation catalog. */
|
|
7
|
+
export function authorizationDetails(request) {
|
|
8
|
+
if (!request)
|
|
9
|
+
return {};
|
|
10
|
+
return {
|
|
11
|
+
authorization_details: JSON.stringify([
|
|
12
|
+
{ type: 'https://permute.ai/permissions', ...RequestedAccessSchema.parse(request) },
|
|
13
|
+
]),
|
|
14
|
+
};
|
|
15
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { RequestedAccessSchema } from './auth/request.js';
|
|
2
4
|
import { createServer } from 'node:http';
|
|
3
5
|
import { spawn } from 'node:child_process';
|
|
4
6
|
import { parseArgs } from 'node:util';
|
|
@@ -75,6 +77,9 @@ export async function runCli(args = process.argv.slice(2)) {
|
|
|
75
77
|
allowPositionals: true,
|
|
76
78
|
options: {
|
|
77
79
|
issuer: { type: 'string' },
|
|
80
|
+
'access-file': { type: 'string' },
|
|
81
|
+
organization: { type: 'string' },
|
|
82
|
+
workspace: { type: 'string' },
|
|
78
83
|
'api-url': { type: 'string' },
|
|
79
84
|
'device-code': { type: 'boolean' },
|
|
80
85
|
'no-browser': { type: 'boolean' },
|
|
@@ -83,14 +88,21 @@ export async function runCli(args = process.argv.slice(2)) {
|
|
|
83
88
|
});
|
|
84
89
|
const [area, command] = positionals;
|
|
85
90
|
if (values.help || area !== 'auth' || !['login', 'logout', 'status'].includes(command ?? '')) {
|
|
86
|
-
console.log('permute auth login [--device-code] [--no-browser]\npermute auth status\npermute auth logout\n\nUse --issuer and --api-url together for a development environment.');
|
|
91
|
+
console.log('permute auth login [--device-code] [--no-browser] [--organization ID] [--workspace ID] [--access-file PATH]\npermute auth status\npermute auth logout\n\nUse --issuer and --api-url together for a development environment.');
|
|
87
92
|
if (!values.help)
|
|
88
93
|
process.exitCode = 1;
|
|
89
94
|
return;
|
|
90
95
|
}
|
|
91
96
|
if (!!values.issuer !== !!values['api-url'])
|
|
92
97
|
throw new Error('Provide both --issuer and --api-url for another environment.');
|
|
98
|
+
const requestedAccess = {
|
|
99
|
+
...(values['access-file'] &&
|
|
100
|
+
RequestedAccessSchema.parse(JSON.parse(await readFile(values['access-file'], 'utf8')))),
|
|
101
|
+
...(values.organization && { organizationId: values.organization }),
|
|
102
|
+
...(values.workspace && { workspaceId: values.workspace }),
|
|
103
|
+
};
|
|
93
104
|
const login = new OAuthLogin({
|
|
105
|
+
...(Object.keys(requestedAccess).length > 0 && { requestedAccess }),
|
|
94
106
|
issuer: values.issuer,
|
|
95
107
|
apiUrl: values['api-url'],
|
|
96
108
|
onCredentialStoreAccess: (issuer) => {
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { EmailSender, EmailSendRequest, EmailReceipt, PermissionGrant, PermissionGrantInput, PermissionGrantUpdate, PermissionGrantQuery, PermissionGrantList, PermissionGrantRevoked, PermissionInspectionInput, PermissionInspection, AccessRequest, AccessRequestInput, AccessRequestQuery, AccessRequestList, AccessRequestDecision } from './types.js';
|
|
1
|
+
import type { EmailSender, EmailSendRequest, EmailReceipt, PermissionGrant, PermissionGrantInput, PermissionGrantUpdate, PermissionGrantQuery, PermissionGrantList, PermissionGrantRevoked, PermissionInspectionInput, PermissionInspection, AccessRequest, AccessRequestInput, AccessRequestQuery, AccessRequestList, AccessRequestDecision, MemberList, OrganizationMemberInvite, WorkspaceMemberInvite, OrganizationMemberRoleUpdate, WorkspaceMemberRoleUpdate, MemberInvitation, MemberInvitationDelivery, MemberRoleUpdated, MemberRemoved, Organization, OrganizationList } from './types.js';
|
|
2
2
|
export { PermuteApiError } from './errors.js';
|
|
3
|
-
import type { BusinessAction, BusinessActionDetail, BusinessActionList, ActionCreateRequest, ActionDefinition, ActionDefinitionSaveRequest, ActionDefinitionSaved, ActionRequest, ActionReceipt, ActionPublicationRequest, ActionPublicationRequestInput, ActionPublicationRequestList, ActionPublicationDecision } from './types.js';
|
|
4
|
-
import type { ConnectorSession, ConnectorSessionCreated, ConnectorSessionCreateRequest, Database, AppPublicationRequest, AppPublicationRequestInput, AppPublicationRequestList, AppPublicationDecision, AppDraft, AppAccess, AppAddressSettings, AppAddressSetRequest, AppAccessSetRequest, AppDraftList, AppCreateRequest, AppUpdateRequest, AppDeleted, AppUploadsRequest, AppUploads, AppSiteSaveRequest, AppSourceFileQuery, AppSourceFileContent, AppGrants, AppGrantSetRequest, 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, AnswerResponse, PermuteClientOptions, PermuteRequestOptions, QueryRequest, SearchRequest, SourceDetail, CatalogTable, SourceList, CustomDatasetCreateRequest, DatasetDeleteResponse, DatasetResponse, FileDatasetCreateRequest, FileDatasetMetadataCreateRequest, FileDatasetResponse, DatasetRows, CustomDatasetResponse, DashboardSource, DashboardSourceUpdateRequest } from './types.js';
|
|
3
|
+
import type { BusinessAction, BusinessActionDetail, BusinessActionList, ActionCreateRequest, ActionDefinition, ActionDefinitionSaveRequest, ActionDefinitionSaved, ActionRequest, ActionReceipt, ActionTrigger, ActionTriggerList, ActionTriggerCreateRequest, ActionTriggerUpdateRequest, ActionTriggerRunList, ActionPublicationRequest, ActionPublicationRequestInput, ActionPublicationRequestList, ActionPublicationDecision } from './types.js';
|
|
4
|
+
import type { ConnectorSession, ConnectorSessionCreated, ConnectorSessionCreateRequest, Database, AppPublicationRequest, AppPublicationRequestInput, AppPublicationRequestList, AppPublicationDecision, AppDraft, AppAccess, AppAddressSettings, AppAddressSetRequest, AppAccessSetRequest, AppDraftList, AppCreateRequest, AppUpdateRequest, AppDeleted, AppUploadsRequest, AppUploads, AppSiteSaveRequest, AppSourceFileQuery, AppSourceFileContent, AppGrants, AppGrantSetRequest, 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, AnswerResponse, PermuteClientOptions, PermuteRequestOptions, QueryRequest, SearchRequest, SourceDetail, CatalogTable, SourceList, CustomDatasetCreateRequest, DatasetDeleteResponse, DatasetResponse, FileDatasetCreateRequest, FileDatasetMetadataCreateRequest, FileDatasetResponse, DatasetRows, DatasetCsvUploadOptions, CustomDatasetResponse, DashboardSource, DashboardSourceUpdateRequest } from './types.js';
|
|
5
5
|
type DatasetWriteInput = {
|
|
6
6
|
tables: Record<string, DatasetRows>;
|
|
7
7
|
mode: 'replace';
|
|
@@ -14,7 +14,11 @@ type DatasetWriteInput = {
|
|
|
14
14
|
rows: DatasetRows;
|
|
15
15
|
batchId: string;
|
|
16
16
|
mode: 'appendTable';
|
|
17
|
-
}
|
|
17
|
+
} | ({
|
|
18
|
+
tableName: string;
|
|
19
|
+
file: Blob;
|
|
20
|
+
mode: 'replaceTable';
|
|
21
|
+
} & DatasetCsvUploadOptions);
|
|
18
22
|
type DatasetWriter = (id: string, request: DatasetWriteInput, options?: PermuteRequestOptions) => Promise<CustomDatasetResponse>;
|
|
19
23
|
type DatasetReader = (id: string, options?: PermuteRequestOptions) => Promise<DatasetResponse>;
|
|
20
24
|
export declare class PermuteDataset {
|
|
@@ -33,6 +37,7 @@ export declare class PermuteDataset {
|
|
|
33
37
|
get version(): number;
|
|
34
38
|
replace(tables: Record<string, DatasetRows>, options?: PermuteRequestOptions): Promise<this>;
|
|
35
39
|
replaceTable(tableName: string, rows: DatasetRows, options?: PermuteRequestOptions): Promise<this>;
|
|
40
|
+
replaceTable(tableName: string, file: Blob, options: DatasetCsvUploadOptions & PermuteRequestOptions): Promise<this>;
|
|
36
41
|
appendTable(tableName: string, rows: DatasetRows, options: {
|
|
37
42
|
batchId: string;
|
|
38
43
|
} & PermuteRequestOptions): Promise<this>;
|
|
@@ -57,6 +62,24 @@ export interface SkillsClient {
|
|
|
57
62
|
delete(path: string, options?: PermuteRequestOptions): Promise<KnowledgeWriteResponse>;
|
|
58
63
|
}
|
|
59
64
|
export declare class PermuteClient {
|
|
65
|
+
readonly organizations: {
|
|
66
|
+
list(options?: PermuteRequestOptions): Promise<OrganizationList>;
|
|
67
|
+
get(organizationId: string, options?: PermuteRequestOptions): Promise<Organization>;
|
|
68
|
+
};
|
|
69
|
+
readonly organizationMembers: {
|
|
70
|
+
list(organizationId: string, options?: PermuteRequestOptions): Promise<MemberList>;
|
|
71
|
+
invite(organizationId: string, body: OrganizationMemberInvite, options?: PermuteRequestOptions): Promise<MemberInvitation>;
|
|
72
|
+
resendInvitation(organizationId: string, userId: string, options?: PermuteRequestOptions): Promise<MemberInvitationDelivery>;
|
|
73
|
+
updateRole(organizationId: string, userId: string, body: OrganizationMemberRoleUpdate, options?: PermuteRequestOptions): Promise<MemberRoleUpdated>;
|
|
74
|
+
remove(organizationId: string, userId: string, options?: PermuteRequestOptions): Promise<MemberRemoved>;
|
|
75
|
+
};
|
|
76
|
+
readonly workspaceMembers: {
|
|
77
|
+
list(workspaceId: string, options?: PermuteRequestOptions): Promise<MemberList>;
|
|
78
|
+
invite(workspaceId: string, body: WorkspaceMemberInvite, options?: PermuteRequestOptions): Promise<MemberInvitation>;
|
|
79
|
+
resendInvitation(workspaceId: string, userId: string, options?: PermuteRequestOptions): Promise<MemberInvitationDelivery>;
|
|
80
|
+
updateRole(workspaceId: string, userId: string, body: WorkspaceMemberRoleUpdate, options?: PermuteRequestOptions): Promise<MemberRoleUpdated>;
|
|
81
|
+
remove(workspaceId: string, userId: string, options?: PermuteRequestOptions): Promise<MemberRemoved>;
|
|
82
|
+
};
|
|
60
83
|
readonly permissions: {
|
|
61
84
|
update(permissionId: string, body: PermissionGrantUpdate, options?: PermuteRequestOptions): Promise<PermissionGrant>;
|
|
62
85
|
list(query?: PermissionGrantQuery, options?: PermuteRequestOptions): Promise<PermissionGrantList>;
|
|
@@ -97,6 +120,14 @@ export declare class PermuteClient {
|
|
|
97
120
|
saveDefinition(actionId: string, body: ActionDefinitionSaveRequest, options?: PermuteRequestOptions): Promise<ActionDefinitionSaved>;
|
|
98
121
|
invoke(actionId: string, body: ActionRequest, options?: PermuteRequestOptions): Promise<ActionReceipt>;
|
|
99
122
|
};
|
|
123
|
+
readonly actionTriggers: {
|
|
124
|
+
list(actionId: string, options?: PermuteRequestOptions): Promise<ActionTriggerList>;
|
|
125
|
+
create(actionId: string, body: ActionTriggerCreateRequest, options?: PermuteRequestOptions): Promise<ActionTrigger>;
|
|
126
|
+
get(actionId: string, triggerId: string, options?: PermuteRequestOptions): Promise<ActionTrigger>;
|
|
127
|
+
update(actionId: string, triggerId: string, body: ActionTriggerUpdateRequest, options?: PermuteRequestOptions): Promise<ActionTrigger>;
|
|
128
|
+
delete(actionId: string, triggerId: string, options?: PermuteRequestOptions): Promise<ActionTrigger>;
|
|
129
|
+
runs(actionId: string, triggerId: string, options?: PermuteRequestOptions): Promise<ActionTriggerRunList>;
|
|
130
|
+
};
|
|
100
131
|
readonly emails: {
|
|
101
132
|
get(options?: PermuteRequestOptions): Promise<EmailSender>;
|
|
102
133
|
/** Only callable from Business Actions through platform.withStep(stepId). */
|
package/dist/client.js
CHANGED
|
@@ -93,12 +93,45 @@ function inferUploadSize(data) {
|
|
|
93
93
|
return undefined;
|
|
94
94
|
}
|
|
95
95
|
const DATASET_WRITE_CHUNK_ROWS = 500;
|
|
96
|
+
const DATASET_WRITE_CHUNK_BYTES = 16 * 1024 * 1024;
|
|
97
|
+
async function hashDatasetFile(file, signal) {
|
|
98
|
+
const hash = createHash('sha256');
|
|
99
|
+
const reader = file.stream().getReader();
|
|
100
|
+
try {
|
|
101
|
+
while (true) {
|
|
102
|
+
if (signal?.aborted) {
|
|
103
|
+
await reader.cancel();
|
|
104
|
+
throw new PermuteApiError({ status: 0, code: 'request_aborted', message: 'Request was aborted' });
|
|
105
|
+
}
|
|
106
|
+
const { done, value } = await reader.read();
|
|
107
|
+
if (done)
|
|
108
|
+
return hash.digest('hex');
|
|
109
|
+
hash.update(value);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
reader.releaseLock();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
96
116
|
function encodeDatasetWrite(request) {
|
|
97
117
|
const entries = request.mode === 'replace' ? Object.entries(request.tables) : [[request.tableName, request.rows]];
|
|
98
118
|
const parts = [];
|
|
99
119
|
const hash = createHash('sha256');
|
|
100
120
|
const addChunk = (tableName, rows) => {
|
|
101
121
|
const line = `${JSON.stringify({ tableName, rows })}\n`;
|
|
122
|
+
if (new TextEncoder().encode(line).byteLength > DATASET_WRITE_CHUNK_BYTES) {
|
|
123
|
+
if (rows.length <= 1) {
|
|
124
|
+
throw new PermuteApiError({
|
|
125
|
+
status: 0,
|
|
126
|
+
code: 'dataset_row_too_large',
|
|
127
|
+
message: 'A dataset row exceeds the 16 MiB upload chunk limit',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const middle = Math.floor(rows.length / 2);
|
|
131
|
+
addChunk(tableName, rows.slice(0, middle));
|
|
132
|
+
addChunk(tableName, rows.slice(middle));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
102
135
|
parts.push(line);
|
|
103
136
|
hash.update(line);
|
|
104
137
|
};
|
|
@@ -113,7 +146,6 @@ function encodeDatasetWrite(request) {
|
|
|
113
146
|
return {
|
|
114
147
|
data,
|
|
115
148
|
contentHash: hash.digest('hex'),
|
|
116
|
-
tables: entries.map(([name, rows]) => ({ name, rowCount: rows.length })),
|
|
117
149
|
};
|
|
118
150
|
}
|
|
119
151
|
function pollingDeadline(options) {
|
|
@@ -208,8 +240,20 @@ export class PermuteDataset {
|
|
|
208
240
|
this.snapshot = await this.writeTables(this.id, { tables, mode: 'replace' }, options);
|
|
209
241
|
return this;
|
|
210
242
|
}
|
|
211
|
-
async replaceTable(tableName,
|
|
212
|
-
|
|
243
|
+
async replaceTable(tableName, data, options) {
|
|
244
|
+
if (data instanceof Blob) {
|
|
245
|
+
if (!options || !('format' in options)) {
|
|
246
|
+
throw new PermuteApiError({
|
|
247
|
+
status: 0,
|
|
248
|
+
code: 'invalid_upload_format',
|
|
249
|
+
message: 'CSV file uploads require format: csv',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const { format, compression, rowCount, ...requestOptions } = options;
|
|
253
|
+
this.snapshot = await this.writeTables(this.id, { tableName, file: data, mode: 'replaceTable', format, compression, rowCount }, requestOptions);
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
this.snapshot = await this.writeTables(this.id, { tableName, rows: data, mode: 'replaceTable' }, options);
|
|
213
257
|
return this;
|
|
214
258
|
}
|
|
215
259
|
async appendTable(tableName, rows, options) {
|
|
@@ -251,6 +295,9 @@ export class PermuteFileDataset {
|
|
|
251
295
|
}
|
|
252
296
|
}
|
|
253
297
|
export class PermuteClient {
|
|
298
|
+
organizations;
|
|
299
|
+
organizationMembers;
|
|
300
|
+
workspaceMembers;
|
|
254
301
|
permissions;
|
|
255
302
|
accessRequests;
|
|
256
303
|
revisions;
|
|
@@ -258,6 +305,7 @@ export class PermuteClient {
|
|
|
258
305
|
skills;
|
|
259
306
|
organizationSkills;
|
|
260
307
|
actions;
|
|
308
|
+
actionTriggers;
|
|
261
309
|
emails;
|
|
262
310
|
databases;
|
|
263
311
|
apps;
|
|
@@ -305,6 +353,24 @@ export class PermuteClient {
|
|
|
305
353
|
delete: this.bindMethod('permissionsDelete', (request) => (permissionId, options) => request({ pathParams: { permissionId } }, options)),
|
|
306
354
|
inspect: this.bindMethod('permissionsInspect', (request) => (body, options) => request({ body }, options)),
|
|
307
355
|
};
|
|
356
|
+
this.organizations = {
|
|
357
|
+
list: this.bindMethod('organizationsList', (apiRequest) => (options) => apiRequest({}, options)),
|
|
358
|
+
get: this.bindMethod('organizationsGet', (apiRequest) => (organizationId, options) => apiRequest({ pathParams: { organizationId } }, options)),
|
|
359
|
+
};
|
|
360
|
+
this.organizationMembers = {
|
|
361
|
+
list: this.bindMethod('organizationMembersList', (apiRequest) => (organizationId, options) => apiRequest({ pathParams: { organizationId } }, options)),
|
|
362
|
+
invite: this.bindMethod('organizationMembersInvite', (request) => (organizationId, body, options) => request({ pathParams: { organizationId }, body }, options)),
|
|
363
|
+
resendInvitation: this.bindMethod('organizationMembersResendInvitation', (request) => (organizationId, userId, options) => request({ pathParams: { organizationId, userId } }, options)),
|
|
364
|
+
updateRole: this.bindMethod('organizationMembersUpdateRole', (request) => (organizationId, userId, body, options) => request({ pathParams: { organizationId, userId }, body }, options)),
|
|
365
|
+
remove: this.bindMethod('organizationMembersRemove', (request) => (organizationId, userId, options) => request({ pathParams: { organizationId, userId } }, options)),
|
|
366
|
+
};
|
|
367
|
+
this.workspaceMembers = {
|
|
368
|
+
list: this.bindMethod('workspaceMembersList', (apiRequest) => (workspaceId, options) => apiRequest({ pathParams: { workspaceId } }, options)),
|
|
369
|
+
invite: this.bindMethod('workspaceMembersInvite', (request) => (workspaceId, body, options) => request({ pathParams: { workspaceId }, body }, options)),
|
|
370
|
+
resendInvitation: this.bindMethod('workspaceMembersResendInvitation', (request) => (workspaceId, userId, options) => request({ pathParams: { workspaceId, userId } }, options)),
|
|
371
|
+
updateRole: this.bindMethod('workspaceMembersUpdateRole', (request) => (workspaceId, userId, body, options) => request({ pathParams: { workspaceId, userId }, body }, options)),
|
|
372
|
+
remove: this.bindMethod('workspaceMembersRemove', (request) => (workspaceId, userId, options) => request({ pathParams: { workspaceId, userId } }, options)),
|
|
373
|
+
};
|
|
308
374
|
this.accessRequests = {
|
|
309
375
|
create: this.bindMethod('accessRequestsCreate', (request) => (body, options) => request({ body }, options)),
|
|
310
376
|
list: this.bindMethod('accessRequestsList', (request) => (query, options) => request({ query }, options)),
|
|
@@ -344,6 +410,14 @@ export class PermuteClient {
|
|
|
344
410
|
saveDefinition: this.bindMethod('actionDefinitionSave', (apiRequest) => (actionId, body, options) => apiRequest({ pathParams: { actionId }, body }, options)),
|
|
345
411
|
invoke: this.bindMethod('actionsInvoke', (apiRequest) => (actionId, body, options) => apiRequest({ pathParams: { actionId }, body }, options)),
|
|
346
412
|
};
|
|
413
|
+
this.actionTriggers = {
|
|
414
|
+
list: this.bindMethod('actionTriggersList', (apiRequest) => (actionId, options) => apiRequest({ pathParams: { actionId } }, options)),
|
|
415
|
+
create: this.bindMethod('actionTriggersCreate', (apiRequest) => (actionId, body, options) => apiRequest({ pathParams: { actionId }, body }, options)),
|
|
416
|
+
get: this.bindMethod('actionTriggersGet', (apiRequest) => (actionId, triggerId, options) => apiRequest({ pathParams: { actionId, triggerId } }, options)),
|
|
417
|
+
update: this.bindMethod('actionTriggersUpdate', (apiRequest) => (actionId, triggerId, body, options) => apiRequest({ pathParams: { actionId, triggerId }, body }, options)),
|
|
418
|
+
delete: this.bindMethod('actionTriggersDelete', (apiRequest) => (actionId, triggerId, options) => apiRequest({ pathParams: { actionId, triggerId } }, options)),
|
|
419
|
+
runs: this.bindMethod('actionTriggerRunsList', (apiRequest) => (actionId, triggerId, options) => apiRequest({ pathParams: { actionId, triggerId } }, options)),
|
|
420
|
+
};
|
|
347
421
|
this.emails = {
|
|
348
422
|
get: this.bindMethod('emailsGet', (request) => (options) => request({}, options)),
|
|
349
423
|
send: this.bindMethod('emailsSend', (request) => (body, options) => request({ body }, options)),
|
|
@@ -475,30 +549,39 @@ export class PermuteClient {
|
|
|
475
549
|
};
|
|
476
550
|
const readDataset = this.bindMethod('datasetsGet', (apiRequest) => (datasetId, requestOptions) => apiRequest({ pathParams: { datasetId } }, requestOptions));
|
|
477
551
|
const writeDatasetTables = async (datasetId, request, requestOptions) => {
|
|
478
|
-
const encoded =
|
|
479
|
-
|
|
552
|
+
const encoded = 'file' in request
|
|
553
|
+
? { data: request.file, contentHash: await hashDatasetFile(request.file, requestOptions?.signal) }
|
|
554
|
+
: encodeDatasetWrite(request);
|
|
555
|
+
const uploadMetadata = { sizeBytes: encoded.data.size, contentHash: encoded.contentHash };
|
|
556
|
+
const body = 'file' in request
|
|
480
557
|
? {
|
|
481
|
-
mode:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
558
|
+
mode: 'replaceTable',
|
|
559
|
+
tableName: request.tableName,
|
|
560
|
+
format: request.format,
|
|
561
|
+
compression: request.compression,
|
|
562
|
+
rowCount: request.rowCount,
|
|
563
|
+
...uploadMetadata,
|
|
485
564
|
}
|
|
486
|
-
: request.mode === '
|
|
565
|
+
: request.mode === 'replace'
|
|
487
566
|
? {
|
|
488
567
|
mode: request.mode,
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
sizeBytes: encoded.data.size,
|
|
492
|
-
contentHash: encoded.contentHash,
|
|
568
|
+
tables: Object.entries(request.tables).map(([name, rows]) => ({ name, rowCount: rows.length })),
|
|
569
|
+
...uploadMetadata,
|
|
493
570
|
}
|
|
494
|
-
:
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
571
|
+
: request.mode === 'replaceTable'
|
|
572
|
+
? {
|
|
573
|
+
mode: request.mode,
|
|
574
|
+
tableName: request.tableName,
|
|
575
|
+
rowCount: request.rows.length,
|
|
576
|
+
...uploadMetadata,
|
|
577
|
+
}
|
|
578
|
+
: {
|
|
579
|
+
mode: request.mode,
|
|
580
|
+
tableName: request.tableName,
|
|
581
|
+
rowCount: request.rows.length,
|
|
582
|
+
...uploadMetadata,
|
|
583
|
+
batchId: request.batchId,
|
|
584
|
+
};
|
|
502
585
|
const initialized = await this.request('datasetsWriteTables', { pathParams: { datasetId }, body }, requestOptions);
|
|
503
586
|
if (initialized.write.status === 'completed')
|
|
504
587
|
return initialized.dataset;
|
|
@@ -662,7 +745,7 @@ export class PermuteClient {
|
|
|
662
745
|
...options?.headers,
|
|
663
746
|
};
|
|
664
747
|
const workspaceId = options?.workspaceId ?? this.workspaceId;
|
|
665
|
-
if (route.scope === 'workspace' && workspaceId) {
|
|
748
|
+
if (route.scope === 'workspace' && workspaceId && !input.pathParams?.workspaceId) {
|
|
666
749
|
headers['X-Workspace-Id'] = workspaceId;
|
|
667
750
|
}
|
|
668
751
|
if (body !== undefined) {
|