@permute/sdk 0.4.0 → 0.6.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 +91 -3
- package/dist/client.d.ts +3 -1
- package/dist/client.js +2 -0
- package/dist/deploy.d.ts +18 -0
- package/dist/deploy.js +139 -0
- package/dist/generated/openapi.d.ts +24 -1
- package/dist/generated/runtime-contract.d.ts +1 -1
- package/dist/generated/runtime-contract.js +130 -8
- package/dist/types.d.ts +2 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -442,6 +442,44 @@ Identical contents reuse existing files. Changed contents at the same path are s
|
|
|
442
442
|
|
|
443
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
444
|
|
|
445
|
+
### Deploy a build directory (SDK 0.6.0)
|
|
446
|
+
|
|
447
|
+
The Node/Bun entry `@permute/sdk/deploy` combines validation, upload, draft save,
|
|
448
|
+
and publication for an existing live app. Use SDK 0.6.0 for the grant-based app
|
|
449
|
+
API shown here.
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { deployApp } from '@permute/sdk/deploy';
|
|
453
|
+
|
|
454
|
+
const deployed = await deployApp(client, { appId, directory: './dist' });
|
|
455
|
+
console.log(deployed.url, deployed.revisionId, deployed.changed);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
The helper rejects symbolic links, unsupported paths, missing `index.html`, and
|
|
459
|
+
oversized builds before uploading. It requires the current draft to be published;
|
|
460
|
+
after reviewing an existing draft, pass its exact `expectedDraft` to approve
|
|
461
|
+
replacing it. Omitted `actions` preserve current bindings (an empty array clears them). To change action bindings, supply explicit published revisions:
|
|
462
|
+
|
|
463
|
+
```ts
|
|
464
|
+
await deployApp(client, {
|
|
465
|
+
appId,
|
|
466
|
+
directory: './dist',
|
|
467
|
+
actions: [{ actionId, revisionId: publishedActionRevisionId }],
|
|
468
|
+
});
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
An unchanged build returns `changed: false` without uploading, saving, or publishing
|
|
472
|
+
(files, SPA mode, and any supplied bindings must match). It rechecks refs before
|
|
473
|
+
reporting no change. An approved unpublished draft is still published so settings
|
|
474
|
+
edits are preserved. Changed deployments return `changed: true`.
|
|
475
|
+
|
|
476
|
+
It stops if refs change and never retries mutations. After a failed or lost response,
|
|
477
|
+
inspect app history before retrying: uploads may exist, a draft may be saved, or
|
|
478
|
+
publication may already have completed. `deployment_conflict` requires review;
|
|
479
|
+
`invalid_build` requires fixing the local build. Use the individual calls above
|
|
480
|
+
when saving a draft for later review. The helper does not build, provision, or wait
|
|
481
|
+
for hosting, and must not be imported into browser code.
|
|
482
|
+
|
|
445
483
|
### Inside a hosted app
|
|
446
484
|
|
|
447
485
|
Use the browser entry with the app's existing sign-in session (no API key in frontend code):
|
|
@@ -459,14 +497,27 @@ const request = { requestId: crypto.randomUUID(), input: { name: 'Ada' } };
|
|
|
459
497
|
const receipt = await app.actions.invoke('actn_example', request);
|
|
460
498
|
```
|
|
461
499
|
|
|
462
|
-
The client reads the hosted page's revision-pinned base URL. Save
|
|
500
|
+
The client reads the hosted page's revision-pinned base URL. Save
|
|
463
501
|
`actions: [{ actionId, revisionId }]` with its site manifest, then publish the app revision.
|
|
464
|
-
Queries require
|
|
465
|
-
|
|
502
|
+
Queries require current app and user read permissions. The query request selects its
|
|
503
|
+
sources; source grants take effect without republishing.
|
|
504
|
+
Actions require both app and user invocation permissions and use the pinned published revision.
|
|
466
505
|
Read scope is source-level; client-supplied SQL is not a row-level authorization policy.
|
|
467
506
|
Keep the same request ID and input when retrying an uncertain action response. The client
|
|
468
507
|
does not retry writes automatically. A 401 requires signing in again.
|
|
469
508
|
|
|
509
|
+
For recovery across reloads, retain `{ baseUrl: document.baseURI, actionId, request }`
|
|
510
|
+
before sending (using storage appropriate to the input's sensitivity). After sign-in
|
|
511
|
+
or a newer app release, create `new PermuteAppClient({ baseUrl: pending.baseUrl })`
|
|
512
|
+
and invoke `pending.actionId` with `pending.request` as the same user. This keeps
|
|
513
|
+
the original app/action revision as well as the request ID and input. Never store
|
|
514
|
+
session cookies or API credentials with pending requests.
|
|
515
|
+
|
|
516
|
+
A later 401, 403, or 409 does not prove that an earlier attempt failed to commit.
|
|
517
|
+
Keep an uncertain request until its outcome is reconciled; do not silently clear
|
|
518
|
+
it, change its input, or generate a replacement ID. An edited payload is a new
|
|
519
|
+
operation only after resolving any earlier uncertain write.
|
|
520
|
+
|
|
470
521
|
## Business actions
|
|
471
522
|
|
|
472
523
|
Use `client.actions.create`, `definition`, and `saveDefinition` to author a typed
|
|
@@ -476,6 +527,43 @@ handler with selected database tables or connector accounts. Publish with
|
|
|
476
527
|
requires admin permission. See the [business action guide](https://permute.ai/docs/guides/business-actions)
|
|
477
528
|
for the complete create, save, publish, and invoke workflow.
|
|
478
529
|
|
|
530
|
+
### Expected failures in handlers
|
|
531
|
+
|
|
532
|
+
Use `reject(code, message, details?)` for a deliberate business rejection:
|
|
533
|
+
|
|
534
|
+
```js
|
|
535
|
+
exports.handler = ({ input, db, reject }) => {
|
|
536
|
+
if (!input.name.trim()) reject('validation_failed', 'Enter a name.', { field: 'name' });
|
|
537
|
+
db.insert('customers', input);
|
|
538
|
+
return input;
|
|
539
|
+
};
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
Rejection stops the handler and discards staged database writes. It cannot undo
|
|
543
|
+
connector calls already sent. Messages and details are public to callers; never
|
|
544
|
+
put credentials or private diagnostic data in them. A rejection message permits
|
|
545
|
+
1–1,000 characters, with up to 20 string detail fields (keys 1–100 characters,
|
|
546
|
+
values up to 500). Invalid rejections and unexpected exceptions return a safe
|
|
547
|
+
`execution_failed` response; exception text stays in server logs.
|
|
548
|
+
|
|
549
|
+
Both SDK entries expose `PermuteApiError.code`, `.status`, and `.details`.
|
|
550
|
+
Branch on the code (not message text):
|
|
551
|
+
|
|
552
|
+
| Code | HTTP | Recovery |
|
|
553
|
+
| -------------------------------- | -------- | --------------------------------------------------------------------- |
|
|
554
|
+
| `validation_failed` | 400 | Correct input; submit the changed input as a new request. |
|
|
555
|
+
| `record_conflict` | 409 | Reload current data and resolve the stale edit. |
|
|
556
|
+
| `invalid_transition` | 409 | Choose an allowed transition from the current state. |
|
|
557
|
+
| `permission_denied`, `not_found` | 403, 404 | Check current access or the selected record. |
|
|
558
|
+
| `request_conflict` | 409 | This ID already identifies different input; do not reuse it. |
|
|
559
|
+
| `request_in_progress` | 409 | Wait, then retry the identical request. |
|
|
560
|
+
| `outcome_unknown` | 409 | Retain the request ID; inspect external effects before any new write. |
|
|
561
|
+
| `execution_failed` | 500 | Inspect server logs; retry only with the same request ID and input. |
|
|
562
|
+
|
|
563
|
+
Handlers may reject with the first five codes. Transport/authentication failures
|
|
564
|
+
can still use the API's general error codes. A rejection after an external call
|
|
565
|
+
does not establish that nothing happened; reconcile provider state first.
|
|
566
|
+
|
|
479
567
|
### Connector bindings
|
|
480
568
|
|
|
481
569
|
An action definition can bind multiple connector accounts by name alongside its database
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { PermuteApiError } from './errors.js';
|
|
2
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';
|
|
3
|
+
import type { ConnectorSession, ConnectorSessionCreated, ConnectorSessionCreateRequest, Database, AppDraft, AppDraftList, AppCreateRequest, AppUpdateRequest, AppDeleted, AppUploadsRequest, AppUploads, AppSiteSaveRequest, 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, AnswerJobResponse, AnswerResponse, PermuteClientOptions, PermuteRequestOptions, QueryRequest, QueryResponse, SearchRequest, SearchResponse, SourceDetail, CatalogTable, SourceList, CustomDatasetCreateRequest, DatasetDeleteResponse, DatasetResponse, FileDatasetCreateRequest, FileDatasetResponse, DatasetRows, CustomDatasetResponse, DashboardSource, DashboardSourceUpdateRequest } from './types.js';
|
|
4
4
|
type DatasetWriteInput = {
|
|
5
5
|
tables: Record<string, DatasetRows>;
|
|
6
6
|
mode: 'replace';
|
|
@@ -93,6 +93,8 @@ export declare class PermuteClient {
|
|
|
93
93
|
transact(databaseId: string, body: DatabaseTransactionRequest, options?: PermuteRequestOptions): Promise<DatabaseTransactionReceipt>;
|
|
94
94
|
};
|
|
95
95
|
readonly apps: {
|
|
96
|
+
listGrants(applicationId: string, options?: PermuteRequestOptions): Promise<AppGrants>;
|
|
97
|
+
setGrant(applicationId: string, resourceId: string, body: AppGrantSetRequest, options?: PermuteRequestOptions): Promise<AppGrants>;
|
|
96
98
|
list(options?: PermuteRequestOptions): Promise<AppDraftList>;
|
|
97
99
|
create(body: AppCreateRequest, options?: PermuteRequestOptions): Promise<AppDraft>;
|
|
98
100
|
get(applicationId: string, options?: PermuteRequestOptions): Promise<AppDraft>;
|
package/dist/client.js
CHANGED
|
@@ -335,6 +335,8 @@ export class PermuteClient {
|
|
|
335
335
|
transact: (databaseId, body, options) => this.request('databaseTransactionsCreate', { pathParams: { databaseId }, body }, options),
|
|
336
336
|
};
|
|
337
337
|
this.apps = {
|
|
338
|
+
listGrants: (applicationId, options) => this.request('appsGrantsList', { pathParams: { applicationId } }, options),
|
|
339
|
+
setGrant: (applicationId, resourceId, body, options) => this.request('appsGrantsSet', { pathParams: { applicationId, resourceId }, body }, options),
|
|
338
340
|
prepareUploads: (applicationId, body, options) => this.request('appsUploadsCreate', { pathParams: { applicationId }, body }, options),
|
|
339
341
|
uploadFiles: async (applicationId, files, options) => {
|
|
340
342
|
const contents = await Promise.all(files.map(async (file) => ({
|
package/dist/deploy.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PermuteClient } from './client.js';
|
|
2
|
+
import type { AppSiteSaveRequest, PermuteRequestOptions } from './types.js';
|
|
3
|
+
export interface DeployAppOptions {
|
|
4
|
+
appId: string;
|
|
5
|
+
directory: string;
|
|
6
|
+
/** Defaults to true. */
|
|
7
|
+
spa?: boolean;
|
|
8
|
+
/** Explicitly approve replacing this exact draft. Omit to require a clean published app. */
|
|
9
|
+
expectedDraft?: AppSiteSaveRequest['expectedDraft'];
|
|
10
|
+
/** Omit to preserve bindings; [] removes them. Revisions must already be published. */
|
|
11
|
+
actions?: AppSiteSaveRequest['actions'];
|
|
12
|
+
}
|
|
13
|
+
/** Upload, save, and publish an existing live app. Builds and provisions nothing; never retries mutations. */
|
|
14
|
+
export declare function deployApp(client: PermuteClient, options: DeployAppOptions, requestOptions?: PermuteRequestOptions): Promise<{
|
|
15
|
+
url: string;
|
|
16
|
+
revisionId: string;
|
|
17
|
+
changed: boolean;
|
|
18
|
+
}>;
|
package/dist/deploy.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { lstat, readdir, open } from 'node:fs/promises';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
6
|
+
import { Ajv } from 'ajv';
|
|
7
|
+
import { PermuteApiError } from './errors.js';
|
|
8
|
+
import { publicApiRoutes } from './generated/runtime-contract.js';
|
|
9
|
+
const ajv = new Ajv({ strict: false, allErrors: true, validateFormats: false });
|
|
10
|
+
const validateUploads = ajv.compile(publicApiRoutes.appsUploadsCreate.bodySchema);
|
|
11
|
+
const validateSite = ajv.compile(publicApiRoutes.appsSiteUpdate.bodySchema);
|
|
12
|
+
function conflict(message) {
|
|
13
|
+
throw new PermuteApiError({ status: 409, code: 'deployment_conflict', message });
|
|
14
|
+
}
|
|
15
|
+
/** Node/Bun only. Validate the complete build before making any upload or draft mutation. */
|
|
16
|
+
async function readBuild(directory, body, spa, signal) {
|
|
17
|
+
const entries = [];
|
|
18
|
+
const walk = async (absolutePath, path) => {
|
|
19
|
+
signal?.throwIfAborted();
|
|
20
|
+
const stat = await lstat(absolutePath);
|
|
21
|
+
if (stat.isSymbolicLink())
|
|
22
|
+
throw new Error(`Build contains a symbolic link: ${path || directory}`);
|
|
23
|
+
if (stat.isDirectory()) {
|
|
24
|
+
for (const name of (await readdir(absolutePath)).sort()) {
|
|
25
|
+
await walk(join(absolutePath, name), path ? `${path}/${name}` : name);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (stat.isFile() && path) {
|
|
29
|
+
entries.push({ path, absolutePath, size: stat.size, sha256: '0'.repeat(64) });
|
|
30
|
+
if (!validateUploads({ files: entries.map(({ absolutePath: _, ...file }) => file) })) {
|
|
31
|
+
throw new PermuteApiError({
|
|
32
|
+
status: 0,
|
|
33
|
+
code: 'invalid_build',
|
|
34
|
+
message: 'Build files exceed app limits or contain an unsupported path.',
|
|
35
|
+
details: validateUploads.errors,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else
|
|
40
|
+
throw new Error(`Unsupported build entry: ${path || directory}`);
|
|
41
|
+
};
|
|
42
|
+
await walk(directory, '');
|
|
43
|
+
const manifest = entries.map(({ absolutePath: _, ...file }) => file);
|
|
44
|
+
// JSON Schema cannot express the server's manifest refinements. Check these before uploading.
|
|
45
|
+
if (!manifest.some((file) => file.path === 'index.html') ||
|
|
46
|
+
manifest.reduce((total, file) => total + file.size, 0) > 50 * 1024 * 1024 ||
|
|
47
|
+
new Set(body.actions?.map((action) => action.actionId)).size !== (body.actions?.length ?? 0) ||
|
|
48
|
+
!validateSite({ ...body, site: { files: manifest, spa } })) {
|
|
49
|
+
throw new PermuteApiError({
|
|
50
|
+
status: 0,
|
|
51
|
+
code: 'invalid_build',
|
|
52
|
+
message: 'Invalid app build or bindings. Include index.html, stay within 50 MiB, and use valid app settings.',
|
|
53
|
+
details: validateSite.errors,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const files = [];
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
signal?.throwIfAborted();
|
|
59
|
+
const file = await open(entry.absolutePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
60
|
+
try {
|
|
61
|
+
const stat = await file.stat();
|
|
62
|
+
if (!stat.isFile() || stat.size !== entry.size)
|
|
63
|
+
throw new Error(`Build changed: ${entry.path}`);
|
|
64
|
+
const data = Buffer.alloc(entry.size + 1);
|
|
65
|
+
let length = 0;
|
|
66
|
+
while (length < data.byteLength) {
|
|
67
|
+
signal?.throwIfAborted();
|
|
68
|
+
const { bytesRead } = await file.read(data, length, data.byteLength - length, null);
|
|
69
|
+
if (!bytesRead)
|
|
70
|
+
break;
|
|
71
|
+
length += bytesRead;
|
|
72
|
+
}
|
|
73
|
+
if (length !== entry.size)
|
|
74
|
+
throw new Error(`Build changed: ${entry.path}`);
|
|
75
|
+
files.push({ path: entry.path, data: data.subarray(0, length) });
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await file.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return files;
|
|
82
|
+
}
|
|
83
|
+
/** Upload, save, and publish an existing live app. Builds and provisions nothing; never retries mutations. */
|
|
84
|
+
export async function deployApp(client, options, requestOptions) {
|
|
85
|
+
const { appId, directory, spa = true, ...bindings } = structuredClone(options);
|
|
86
|
+
const app = await client.apps.get(appId, requestOptions);
|
|
87
|
+
if (app.deployment?.status !== 'live')
|
|
88
|
+
throw new Error('App hosting must be live before deploying. Inspect client.apps.get(appId).');
|
|
89
|
+
const history = await client.revisions.list(appId, requestOptions);
|
|
90
|
+
const expected = bindings.expectedDraft ?? app.draft;
|
|
91
|
+
if (history.draft?.revisionId !== expected.revisionId ||
|
|
92
|
+
history.draft.sequence !== expected.sequence ||
|
|
93
|
+
app.draft.revisionId !== expected.revisionId ||
|
|
94
|
+
app.draft.sequence !== expected.sequence) {
|
|
95
|
+
conflict('App draft changed. Read and review it before deploying.');
|
|
96
|
+
}
|
|
97
|
+
if (!bindings.expectedDraft && history.draft.revisionId !== history.published?.revisionId) {
|
|
98
|
+
conflict('App has an unpublished draft. Review it and supply its expectedDraft to replace it.');
|
|
99
|
+
}
|
|
100
|
+
const body = { ...bindings, expectedDraft: expected };
|
|
101
|
+
const files = await readBuild(directory, body, spa, requestOptions?.signal);
|
|
102
|
+
// Only a clean published draft can be skipped: an approved draft may also contain settings edits.
|
|
103
|
+
if (history.published && history.draft.revisionId === history.published.revisionId) {
|
|
104
|
+
const revision = await client.revisions.get(appId, history.published.revisionId, requestOptions);
|
|
105
|
+
const previous = {
|
|
106
|
+
expectedDraft: expected,
|
|
107
|
+
site: revision.value.site,
|
|
108
|
+
actions: revision.value.actions,
|
|
109
|
+
};
|
|
110
|
+
const localManifest = files.map(({ path, data }) => ({
|
|
111
|
+
path,
|
|
112
|
+
size: data.length,
|
|
113
|
+
sha256: createHash('sha256').update(data).digest('hex'),
|
|
114
|
+
}));
|
|
115
|
+
if (validateSite(previous) &&
|
|
116
|
+
previous.site.spa === spa &&
|
|
117
|
+
isDeepStrictEqual(new Map(previous.site.files.map((file) => [file.path, file])), new Map(localManifest.map((file) => [file.path, file]))) &&
|
|
118
|
+
(bindings.actions === undefined ||
|
|
119
|
+
isDeepStrictEqual(new Map((previous.actions ?? []).map((action) => [action.actionId, action.revisionId])), new Map(bindings.actions.map((action) => [action.actionId, action.revisionId]))))) {
|
|
120
|
+
const current = await client.revisions.list(appId, requestOptions);
|
|
121
|
+
if (current.version !== history.version)
|
|
122
|
+
conflict('App changed during inspection. Review the current refs.');
|
|
123
|
+
return { url: app.deployment.url, revisionId: history.published.revisionId, changed: false };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const manifest = await client.apps.uploadFiles(appId, files, requestOptions);
|
|
127
|
+
const saved = await client.apps.saveSite(appId, { ...body, site: { files: manifest, spa } }, requestOptions);
|
|
128
|
+
const ready = await client.revisions.list(appId, requestOptions);
|
|
129
|
+
if (ready.draft?.revisionId !== saved.draft.revisionId ||
|
|
130
|
+
ready.draft.sequence !== saved.draft.sequence ||
|
|
131
|
+
ready.published?.revisionId !== history.published?.revisionId ||
|
|
132
|
+
ready.published?.sequence !== history.published?.sequence) {
|
|
133
|
+
conflict('App changed during deployment. Review the current refs before retrying.');
|
|
134
|
+
}
|
|
135
|
+
const published = await client.revisions.publish(appId, { expectedVersion: ready.version }, requestOptions);
|
|
136
|
+
if (published.published?.revisionId !== saved.draft.revisionId)
|
|
137
|
+
throw new Error('Publication returned an unexpected revision. Inspect app history before retrying.');
|
|
138
|
+
return { url: app.deployment.url, revisionId: published.published.revisionId, changed: true };
|
|
139
|
+
}
|
|
@@ -253,6 +253,30 @@ export interface components {
|
|
|
253
253
|
workspaceId: string;
|
|
254
254
|
}[];
|
|
255
255
|
};
|
|
256
|
+
AppGrantParams: {
|
|
257
|
+
applicationId: string;
|
|
258
|
+
resourceId: string;
|
|
259
|
+
};
|
|
260
|
+
AppGrants: {
|
|
261
|
+
canManage: boolean;
|
|
262
|
+
items: {
|
|
263
|
+
/** Format: date-time */
|
|
264
|
+
expiresAt?: string | null;
|
|
265
|
+
/** @enum {string} */
|
|
266
|
+
level: 'read' | 'write' | 'admin';
|
|
267
|
+
resourceId: string;
|
|
268
|
+
resourceType?: string;
|
|
269
|
+
subResourceId?: string;
|
|
270
|
+
}[];
|
|
271
|
+
};
|
|
272
|
+
AppGrantSetRequest: {
|
|
273
|
+
/** Format: date-time */
|
|
274
|
+
expiresAt?: string | null;
|
|
275
|
+
/** @enum {string|null} */
|
|
276
|
+
level: 'read' | 'write' | 'admin' | null;
|
|
277
|
+
resourceType?: string;
|
|
278
|
+
subResourceId?: string;
|
|
279
|
+
};
|
|
256
280
|
AppIdParams: {
|
|
257
281
|
applicationId: string;
|
|
258
282
|
};
|
|
@@ -273,7 +297,6 @@ export interface components {
|
|
|
273
297
|
}[];
|
|
274
298
|
spa: boolean;
|
|
275
299
|
};
|
|
276
|
-
sourceIds?: string[];
|
|
277
300
|
};
|
|
278
301
|
AppUpdateRequest: {
|
|
279
302
|
description: string;
|
|
@@ -8,7 +8,7 @@ export interface RuntimeRoute {
|
|
|
8
8
|
querySchema: JsonSchema | null;
|
|
9
9
|
responseSchema: JsonSchema;
|
|
10
10
|
}
|
|
11
|
-
export type PublicApiOperationId = 'connectorSessionsCreate' | 'connectorSessionsGet' | 'resourceRevisionsList' | 'resourceRevisionsGet' | 'resourceRevisionsRestore' | 'resourcesPublish' | 'actionsList' | 'actionsCreate' | 'actionsGet' | 'actionsDelete' | 'actionDefinitionGet' | 'actionDefinitionSave' | 'actionsInvoke' | 'workspacesList' | 'workspacesCreate' | 'workspacesGet' | 'workspacesUpdate' | 'workspacesDelete' | 'sourcesList' | 'sourcesGet' | 'dashboardsSourceGet' | 'dashboardsSourceUpdate' | 'datasetsCreate' | 'datasetsGetByKey' | 'datasetsGet' | 'datasetsDelete' | 'datasetsDownload' | 'datasetsWriteTables' | 'searchCreate' | 'answerJobsCreate' | 'answerJobsGet' | 'queriesCreate' | 'operationRunsList' | 'operationsList' | 'operationsGet' | 'operationsUpdate' | 'operationsSourceGet' | 'operationsSourceUpdate' | 'operationsRunsList' | 'operationsRun' | 'operationsRunOutput' | 'dataFlowsCreate' | 'dataFlowsGet' | 'dataFlowsCommand' | 'factsList' | 'factsCreate' | 'factsGet' | 'factsUpdate' | 'factsDelete' | 'skillsList' | 'skillsGet' | 'skillsUpdate' | 'skillsDelete' | 'organizationSkillsList' | 'organizationSkillsGet' | 'organizationSkillsUpdate' | 'organizationSkillsDelete' | 'databasesList' | 'databasesCreate' | 'databasesGet' | 'databasesDelete' | 'databaseDefinitionGet' | 'databaseDefinitionSave' | 'databaseTablesList' | 'databaseTablesCreate' | 'databaseRecordsList' | 'databaseTransactionsCreate' | 'appsList' | 'appsCreate' | 'appsGet' | 'appsUpdate' | 'appsDelete' | 'appsUploadsCreate' | 'appsSiteUpdate' | 'writeSyncsList' | 'writeSyncsCreate' | 'writeSyncsDestinations' | 'writeSyncsGet' | 'writeSyncsUpdate' | 'writeSyncsDelete' | 'writeSyncsPreview' | 'writeSyncsPause' | 'writeSyncsListRuns' | 'writeSyncsGetRun' | 'writeSyncsExecute';
|
|
11
|
+
export type PublicApiOperationId = 'connectorSessionsCreate' | 'connectorSessionsGet' | 'resourceRevisionsList' | 'resourceRevisionsGet' | 'resourceRevisionsRestore' | 'resourcesPublish' | 'actionsList' | 'actionsCreate' | 'actionsGet' | 'actionsDelete' | 'actionDefinitionGet' | 'actionDefinitionSave' | 'actionsInvoke' | 'workspacesList' | 'workspacesCreate' | 'workspacesGet' | 'workspacesUpdate' | 'workspacesDelete' | 'sourcesList' | 'sourcesGet' | 'dashboardsSourceGet' | 'dashboardsSourceUpdate' | 'datasetsCreate' | 'datasetsGetByKey' | 'datasetsGet' | 'datasetsDelete' | 'datasetsDownload' | 'datasetsWriteTables' | 'searchCreate' | 'answerJobsCreate' | 'answerJobsGet' | 'queriesCreate' | 'operationRunsList' | 'operationsList' | 'operationsGet' | 'operationsUpdate' | 'operationsSourceGet' | 'operationsSourceUpdate' | 'operationsRunsList' | 'operationsRun' | 'operationsRunOutput' | 'dataFlowsCreate' | 'dataFlowsGet' | 'dataFlowsCommand' | 'factsList' | 'factsCreate' | 'factsGet' | 'factsUpdate' | 'factsDelete' | 'skillsList' | 'skillsGet' | 'skillsUpdate' | 'skillsDelete' | 'organizationSkillsList' | 'organizationSkillsGet' | 'organizationSkillsUpdate' | 'organizationSkillsDelete' | 'databasesList' | 'databasesCreate' | 'databasesGet' | 'databasesDelete' | 'databaseDefinitionGet' | 'databaseDefinitionSave' | 'databaseTablesList' | 'databaseTablesCreate' | 'databaseRecordsList' | 'databaseTransactionsCreate' | 'appsGrantsList' | 'appsGrantsSet' | 'appsList' | 'appsCreate' | 'appsGet' | 'appsUpdate' | 'appsDelete' | 'appsUploadsCreate' | 'appsSiteUpdate' | 'writeSyncsList' | 'writeSyncsCreate' | 'writeSyncsDestinations' | 'writeSyncsGet' | 'writeSyncsUpdate' | 'writeSyncsDelete' | 'writeSyncsPreview' | 'writeSyncsPause' | 'writeSyncsListRuns' | 'writeSyncsGetRun' | 'writeSyncsExecute';
|
|
12
12
|
export declare const publicApiRoutes: Record<PublicApiOperationId, RuntimeRoute>;
|
|
13
13
|
export declare const Providers: {
|
|
14
14
|
readonly ACUMATICA: "acumatica";
|
|
@@ -6521,6 +6521,136 @@ export const publicApiRoutes = {
|
|
|
6521
6521
|
additionalProperties: false,
|
|
6522
6522
|
},
|
|
6523
6523
|
},
|
|
6524
|
+
appsGrantsList: {
|
|
6525
|
+
operationId: 'appsGrantsList',
|
|
6526
|
+
method: 'GET',
|
|
6527
|
+
path: '/v1/apps/{applicationId}/grants',
|
|
6528
|
+
scope: 'workspace',
|
|
6529
|
+
bodySchema: null,
|
|
6530
|
+
querySchema: null,
|
|
6531
|
+
responseSchema: {
|
|
6532
|
+
type: 'object',
|
|
6533
|
+
properties: {
|
|
6534
|
+
items: {
|
|
6535
|
+
type: 'array',
|
|
6536
|
+
items: {
|
|
6537
|
+
type: 'object',
|
|
6538
|
+
properties: {
|
|
6539
|
+
resourceId: {
|
|
6540
|
+
type: 'string',
|
|
6541
|
+
minLength: 1,
|
|
6542
|
+
maxLength: 128,
|
|
6543
|
+
},
|
|
6544
|
+
resourceType: {
|
|
6545
|
+
type: 'string',
|
|
6546
|
+
minLength: 1,
|
|
6547
|
+
maxLength: 128,
|
|
6548
|
+
},
|
|
6549
|
+
subResourceId: {
|
|
6550
|
+
type: 'string',
|
|
6551
|
+
minLength: 1,
|
|
6552
|
+
maxLength: 128,
|
|
6553
|
+
},
|
|
6554
|
+
expiresAt: {
|
|
6555
|
+
type: 'string',
|
|
6556
|
+
format: 'date-time',
|
|
6557
|
+
nullable: true,
|
|
6558
|
+
},
|
|
6559
|
+
level: {
|
|
6560
|
+
type: 'string',
|
|
6561
|
+
enum: ['read', 'write', 'admin'],
|
|
6562
|
+
},
|
|
6563
|
+
},
|
|
6564
|
+
required: ['resourceId', 'level'],
|
|
6565
|
+
additionalProperties: false,
|
|
6566
|
+
},
|
|
6567
|
+
},
|
|
6568
|
+
canManage: {
|
|
6569
|
+
type: 'boolean',
|
|
6570
|
+
},
|
|
6571
|
+
},
|
|
6572
|
+
required: ['items', 'canManage'],
|
|
6573
|
+
additionalProperties: false,
|
|
6574
|
+
},
|
|
6575
|
+
},
|
|
6576
|
+
appsGrantsSet: {
|
|
6577
|
+
operationId: 'appsGrantsSet',
|
|
6578
|
+
method: 'PUT',
|
|
6579
|
+
path: '/v1/apps/{applicationId}/grants/{resourceId}',
|
|
6580
|
+
scope: 'workspace',
|
|
6581
|
+
bodySchema: {
|
|
6582
|
+
type: 'object',
|
|
6583
|
+
properties: {
|
|
6584
|
+
resourceType: {
|
|
6585
|
+
type: 'string',
|
|
6586
|
+
minLength: 1,
|
|
6587
|
+
maxLength: 128,
|
|
6588
|
+
},
|
|
6589
|
+
subResourceId: {
|
|
6590
|
+
type: 'string',
|
|
6591
|
+
minLength: 1,
|
|
6592
|
+
maxLength: 128,
|
|
6593
|
+
},
|
|
6594
|
+
expiresAt: {
|
|
6595
|
+
type: 'string',
|
|
6596
|
+
format: 'date-time',
|
|
6597
|
+
nullable: true,
|
|
6598
|
+
},
|
|
6599
|
+
level: {
|
|
6600
|
+
type: 'string',
|
|
6601
|
+
enum: ['read', 'write', 'admin', null],
|
|
6602
|
+
nullable: true,
|
|
6603
|
+
},
|
|
6604
|
+
},
|
|
6605
|
+
required: ['level'],
|
|
6606
|
+
additionalProperties: false,
|
|
6607
|
+
},
|
|
6608
|
+
querySchema: null,
|
|
6609
|
+
responseSchema: {
|
|
6610
|
+
type: 'object',
|
|
6611
|
+
properties: {
|
|
6612
|
+
items: {
|
|
6613
|
+
type: 'array',
|
|
6614
|
+
items: {
|
|
6615
|
+
type: 'object',
|
|
6616
|
+
properties: {
|
|
6617
|
+
resourceId: {
|
|
6618
|
+
type: 'string',
|
|
6619
|
+
minLength: 1,
|
|
6620
|
+
maxLength: 128,
|
|
6621
|
+
},
|
|
6622
|
+
resourceType: {
|
|
6623
|
+
type: 'string',
|
|
6624
|
+
minLength: 1,
|
|
6625
|
+
maxLength: 128,
|
|
6626
|
+
},
|
|
6627
|
+
subResourceId: {
|
|
6628
|
+
type: 'string',
|
|
6629
|
+
minLength: 1,
|
|
6630
|
+
maxLength: 128,
|
|
6631
|
+
},
|
|
6632
|
+
expiresAt: {
|
|
6633
|
+
type: 'string',
|
|
6634
|
+
format: 'date-time',
|
|
6635
|
+
nullable: true,
|
|
6636
|
+
},
|
|
6637
|
+
level: {
|
|
6638
|
+
type: 'string',
|
|
6639
|
+
enum: ['read', 'write', 'admin'],
|
|
6640
|
+
},
|
|
6641
|
+
},
|
|
6642
|
+
required: ['resourceId', 'level'],
|
|
6643
|
+
additionalProperties: false,
|
|
6644
|
+
},
|
|
6645
|
+
},
|
|
6646
|
+
canManage: {
|
|
6647
|
+
type: 'boolean',
|
|
6648
|
+
},
|
|
6649
|
+
},
|
|
6650
|
+
required: ['items', 'canManage'],
|
|
6651
|
+
additionalProperties: false,
|
|
6652
|
+
},
|
|
6653
|
+
},
|
|
6524
6654
|
appsList: {
|
|
6525
6655
|
operationId: 'appsList',
|
|
6526
6656
|
method: 'GET',
|
|
@@ -7018,14 +7148,6 @@ export const publicApiRoutes = {
|
|
|
7018
7148
|
},
|
|
7019
7149
|
maxItems: 100,
|
|
7020
7150
|
},
|
|
7021
|
-
sourceIds: {
|
|
7022
|
-
type: 'array',
|
|
7023
|
-
items: {
|
|
7024
|
-
type: 'string',
|
|
7025
|
-
minLength: 1,
|
|
7026
|
-
},
|
|
7027
|
-
maxItems: 100,
|
|
7028
|
-
},
|
|
7029
7151
|
expectedDraft: {
|
|
7030
7152
|
type: 'object',
|
|
7031
7153
|
properties: {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { components } from './generated/openapi.js';
|
|
2
2
|
type PublicApiSchema<Name extends keyof components['schemas']> = components['schemas'][Name];
|
|
3
|
+
export type AppGrants = PublicApiSchema<'AppGrants'>;
|
|
4
|
+
export type AppGrantSetRequest = PublicApiSchema<'AppGrantSetRequest'>;
|
|
3
5
|
export type BusinessAction = PublicApiSchema<'BusinessAction'>;
|
|
4
6
|
export type BusinessActionList = PublicApiSchema<'BusinessActionList'>;
|
|
5
7
|
export type ActionCreateRequest = PublicApiSchema<'ActionCreateRequest'>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@permute/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "TypeScript client for the Permute public API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -33,6 +33,11 @@
|
|
|
33
33
|
"types": "./dist/app.d.ts",
|
|
34
34
|
"import": "./dist/app.js",
|
|
35
35
|
"default": "./dist/app.js"
|
|
36
|
+
},
|
|
37
|
+
"./deploy": {
|
|
38
|
+
"types": "./dist/deploy.d.ts",
|
|
39
|
+
"import": "./dist/deploy.js",
|
|
40
|
+
"default": "./dist/deploy.js"
|
|
36
41
|
}
|
|
37
42
|
},
|
|
38
43
|
"publishConfig": {
|
|
@@ -53,10 +58,10 @@
|
|
|
53
58
|
},
|
|
54
59
|
"devDependencies": {
|
|
55
60
|
"@types/bun": "1.3.13",
|
|
56
|
-
"@types/node": "^24",
|
|
61
|
+
"@types/node": "^24.19.1",
|
|
57
62
|
"openapi-typescript": "^7.13.0",
|
|
58
63
|
"prettier": "3.6.2",
|
|
59
64
|
"typescript": "^5.9.3",
|
|
60
|
-
"vitest": "^4.1.
|
|
65
|
+
"vitest": "^4.1.11"
|
|
61
66
|
}
|
|
62
67
|
}
|