@pipelex/sdk 0.60.0 → 0.62.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 +14 -1
- package/dist/cli/script.d.ts +1 -1
- package/dist/client.d.ts +119 -40
- package/dist/client.js +288 -44
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/product-models.d.ts +248 -23
- package/dist/product-models.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +3 -2
- package/docs/errors.md +1 -1
- package/docs/files.md +1 -0
- package/docs/method-copy.md +9 -5
- package/docs/method-trees.md +1 -1
- package/docs/organization-branding.md +21 -7
- package/docs/public-methods.md +97 -0
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# An organization's branding
|
|
2
2
|
|
|
3
|
-
An organization may carry a brand: up to two logos, a square one and a wide one,
|
|
3
|
+
An organization may carry a brand: up to two logos, a square one and a wide one, two colour sets of three colours, the light set and an optional dark one, an app style and a typeface. The webapp shows it on the organization's menu and on the apps of its methods, and an app scaffolded from the method-app template reads it with its API key to wear the same brand. A personal workspace never has one. Beside the brand, the branding says whether the organization holds the white-label entitlement, which Pipelex staff grant. The client reaches it on two routes of the hosted API, and the types are in `src/product-models.ts`.
|
|
4
4
|
|
|
5
5
|
## The methods
|
|
6
6
|
|
|
7
7
|
| Method | Route | Who may call it | Answer |
|
|
8
8
|
|---|---|---|---|
|
|
9
9
|
| `getBranding()` | `GET /v1/branding` | Every member of the active organization, `runner` included, by web session or API key | The active organization's `OrganizationBranding`. |
|
|
10
|
-
| `updateOrganizationBranding(orgId, input)` | `PATCH /v1/organizations/{org_id}/branding` |
|
|
10
|
+
| `updateOrganizationBranding(orgId, input)` | `PATCH /v1/organizations/{org_id}/branding` | A caller with the `admin` or the `member` role in the active organization, never a `runner`, by web session | The `OrganizationBranding` as the write left it. |
|
|
11
11
|
|
|
12
12
|
Both answer the same shape, so a client holds one type whichever it called. `getBranding` takes no organization id: it reads the caller's active organization.
|
|
13
13
|
|
|
@@ -21,9 +21,18 @@ Both answer the same shape, so a client holds one type whichever it called. `get
|
|
|
21
21
|
| `dark_colors` | `BrandingColors` | The dark colour set, used in dark mode only. |
|
|
22
22
|
| `square_logo` | `LogoDescriptor \| null` | The square logo, for the organization menu and small headers. |
|
|
23
23
|
| `wide_logo` | `LogoDescriptor \| null` | The wide logo, for an app's header. |
|
|
24
|
+
| `style` | `string \| null` | The id of the app style the team chose for its method apps, `null` when not set. |
|
|
25
|
+
| `typeface` | `string \| null` | The id of the typeface the team sets its apps' text in, `null` when not set. |
|
|
26
|
+
| `white_label` | `boolean` | Whether the organization holds the white-label entitlement. |
|
|
24
27
|
|
|
25
28
|
A `BrandingColors` always carries its three keys, each `#rrggbb` in lower case or `null` when not set: `action` is the button colour, `accent` the accent colour and `header` the header band's colour. A dark colour that is `null` falls back to the light colour of the same role, and a light one that is `null` to Pipelex's own palette for that theme. The `@pipelex/mthds-form/brand` entry turns a colour set into the roles a page paints with (`resolveDarkSeeds`, then `deriveBrandRoles` once per theme).
|
|
26
29
|
|
|
30
|
+
**The style and the typeface.** `style` is the id of the app style the team chose, such as `editorial`: how its method apps' headings, panels, corners and spacing look. `typeface` is the id of the typeface their text is set in, such as `manrope`. Both themes share them. The platform holds each to a shape, one to 32 lower-case letters, digits and hyphens (`^[a-z0-9-]{1,32}$`), and never to a list: the list of styles and typefaces lives in the form kernel's `@pipelex/mthds-form/brand` entry, so a client reads an id it does not know as unset, and a style added there needs no new SDK. Neither counts towards whether an app is branded, which stays a logo or a colour, since a branded app trades Pipelex's strip for the team's band and a style alone would leave that band carrying nothing of the team's; a client applies the style whenever one is set.
|
|
31
|
+
|
|
32
|
+
**The white-label entitlement.** `white_label` is `true` when the organization holds the white-label option: its method apps show no Pipelex mark anywhere a runner looks, whether or not the team set a brand. It is `false` for every other organization, a personal workspace included. It is not part of the brand the team sets: Pipelex staff grant and withdraw it, no branding write changes it, and `OrganizationBrandingUpdate` has no such key.
|
|
33
|
+
|
|
34
|
+
**A branding written before these fields existed** answers `style` and `typeface` `null` and `white_label` `false`. The client reads an answer that leaves any of the three out, as a platform that predates them sends, or that sends a value of another type, the same way: `style` and `typeface` as `null` and `white_label` as `false`, so each is what the type declares, never `undefined`.
|
|
35
|
+
|
|
27
36
|
A `LogoDescriptor` is what the platform recorded when it accepted the logo:
|
|
28
37
|
|
|
29
38
|
| Field | Meaning |
|
|
@@ -39,17 +48,20 @@ Every key of an `OrganizationBrandingUpdate` is optional: a value sets it, `null
|
|
|
39
48
|
|
|
40
49
|
```ts
|
|
41
50
|
// Set the button and accent colours, keep the header colour, clear the whole dark set,
|
|
42
|
-
// attach a square logo
|
|
51
|
+
// attach a square logo, remove the wide one, choose a style and clear the typeface.
|
|
43
52
|
const branding = await client.updateOrganizationBranding(orgId, {
|
|
44
53
|
colors: { action: "#14213d", accent: "#fca311" },
|
|
45
54
|
dark_colors: null,
|
|
46
55
|
square_logo: "pipelex-storage://<org_id>/assets/<file>.png",
|
|
47
56
|
wide_logo: null,
|
|
57
|
+
style: "editorial",
|
|
58
|
+
typeface: null,
|
|
48
59
|
});
|
|
49
60
|
```
|
|
50
61
|
|
|
51
62
|
- **Colours** (`BrandingColorsUpdate`): each of `action`, `accent` and `header` takes `#rrggbb` in either case, stored in lower case. `colors: null` or `dark_colors: null` clears all three colours of that set.
|
|
52
63
|
- **Logos**: `square_logo` and `wide_logo` take one of three values. The `pipelex-storage://` URI of a file the organization uploaded on its own through `requestUploadGrant` or `upload` sets a new logo: the platform reads and measures it, then writes its own copy under `branding/`, and the answer's `uri` names that copy, never the upload, which the platform leaves as it is. The `uri` the branding answers for that slot, sent back unchanged, leaves the slot out of the write: nothing is read, and the slot keeps whatever logo the branding names when the write lands, so a save that resends the logo it read never puts back one a concurrent save replaced. `null` removes the logo. Any other URI is refused as `logo_not_an_upload`: a run's or a method's file, a `branding/` copy the slot no longer holds, and the other slot's logo too, so a logo cannot move between slots by its URI; upload it again for the other slot instead. Once the write has landed, the platform erases the `branding/` copy it replaced or cleared.
|
|
64
|
+
- **Style and typeface**: `style` and `typeface` each take an id of one to 32 lower-case letters, digits and hyphens, stored as it is whether or not such a style or typeface exists, or `null` to clear it. Writing one leaves the colours, the logos and the other as they are. There is no `white_label` key: the platform refuses a body naming it as it refuses any key it does not know.
|
|
53
65
|
- **The rules a logo meets**: at most 1 MiB; a PNG, JPEG, WebP or SVG image by its bytes, whatever type the upload declared, an SVG in UTF-8 or, behind its byte-order mark, UTF-16; a still image, so never an animated PNG (an APNG) or an animated WebP; a raster of at most 4096 × 4096 pixels that decodes whole; a square logo's longer side at most 10% longer than its shorter, and a raster one at least 128 px on each side; a wide logo at least one and a half times as wide as it is tall, and a raster one at least 64 px tall. An SVG is measured from its root element, by its `width` and `height` when both are in absolute units, else by its `viewBox`, and each side must lie between 0.001 and 1,000,000, in pixels for a `width` or `height` and in the SVG's own units for a `viewBox`. It must declare no XML entities or external references and carry no active content: no `script` or `foreignObject` element, no element in the XHTML namespace, no `on…` event handler attribute, no `set` or `animate` that rewrites a link, no `xml-stylesheet` processing instruction, and no link but a reference inside the file (`#…`) or an embedded PNG, JPEG, GIF or WebP image (`data:image/…`), whether an `href`, an `xlink:href`, or a CSS `url()` or `src()`. Wherever a browser reads CSS, which is a `<style>` element's text and any attribute in no namespace or the SVG one, such as `style`, `fill` or `filter`, it must hold no `@import`, no `image()` or `image-set()`, and no escape (a backslash, which can spell a link out of sight). A `<style>` element itself is allowed, so the class-based styles a design tool exports pass, and so does a reference inside the file such as `fill:url(#grad)`; an attribute in another namespace, such as Inkscape's `inkscape:export-filename`, and a `data-*` one, such as the layer names Illustrator writes in `data-name`, are never read as CSS.
|
|
54
66
|
|
|
55
67
|
The write sends no `Idempotency-Key`, as `renameOrganization` sends none. A later write that should keep a logo leaves its key out, or sends back the `uri` the branding answered.
|
|
@@ -60,15 +72,17 @@ A refused write changes nothing, whichever part of it was refused, and throws an
|
|
|
60
72
|
|
|
61
73
|
| Status | `code` | When |
|
|
62
74
|
|---|---|---|
|
|
63
|
-
| `403` | `auth.forbidden` | The caller is
|
|
75
|
+
| `403` | `auth.forbidden` | The caller is a runner, or holds no recognised role in the organization. |
|
|
64
76
|
| `403` | `pipelex.org.mismatch` | `orgId` is not the caller's active organization, or a logo's URI is a file of another organization. |
|
|
65
77
|
| `404` | `pipelex.org.not_found` | The organization does not exist. |
|
|
66
78
|
| `409` | `pipelex.org.not_manageable` | The organization is a personal workspace, which has no branding. |
|
|
67
|
-
| `422` | `request.invalid` | A colour is not `#rrggbb`, the body holds a key the route does not take, or a logo was refused. |
|
|
79
|
+
| `422` | `request.invalid` | A colour is not `#rrggbb`, a style or a typeface is not an id of the shape `^[a-z0-9-]{1,32}$`, the body holds a key the route does not take, `white_label` included, or a logo was refused. |
|
|
68
80
|
| `422` | `pipelex.storage.uri_invalid` | A logo's URI is not a storage URI. |
|
|
69
81
|
| `503` | `runtime.unavailable` | The platform's storage failed while reading or copying a logo, or its database failed or could not be reached, so the write may have landed or not. Its `retry` is `yes`: the same `input` sent again is safe, and `getBranding` reads what the branding holds. |
|
|
70
82
|
|
|
71
|
-
**A
|
|
83
|
+
**A refused field is named by its `location.field`**, so the settings screen shows each sentence under the control it concerns rather than in a toast: a colour by its dotted key, such as `colors.action` or `dark_colors.header`, a style or a typeface by `style` or `typeface`, and a key the route does not take by its own name. When the write refuses one such field alone, the problem itself names it, in `location.field` and in `params.field`, and carries no `errors`; when it refuses several, it names the first and carries one item per refused field in `errors`, each with its own `location.field`.
|
|
84
|
+
|
|
85
|
+
**A refusal about a logo names it in its problem's `errors`**, one problem item per refused logo. Each item's `location.field` is the slot, `square_logo` or `wide_logo`; its `detail` is the sentence to show. A logo the platform read and refused carries one item per broken rule, both slots answered in one refusal, each item's `code` one `LogoRefusalCode` names:
|
|
72
86
|
|
|
73
87
|
| `code` | Example `detail` |
|
|
74
88
|
|---|---|
|
|
@@ -97,4 +111,4 @@ try {
|
|
|
97
111
|
}
|
|
98
112
|
```
|
|
99
113
|
|
|
100
|
-
`getBranding` answers `
|
|
114
|
+
`getBranding` answers `403` `pipelex.org.required` when the caller has no active organization and `404` when its organization's row is missing. Either method throws the `ApiResponseError` of an answer it cannot read for a `2xx` that is not a JSON object, an empty one included.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Public methods and the template catalog
|
|
2
|
+
|
|
3
|
+
A stored method is `internal` by default: only the members of the organization that holds it see it. An admin of the platform can make one `public`, and every organization then reads, runs and copies its published versions, while every write stays the owner's. Users see the capability through the template catalog: the public methods of the organization the deployment names as its templates organization, which every organization reads the same. No route of this client makes a method public or writes the catalog's order; both are the platform's admin surface. The types are in `src/product-models.ts`. This page is the twin of the Python SDK's `docs/public-methods.md`.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { PipelexApiClient } from "@pipelex/sdk";
|
|
7
|
+
|
|
8
|
+
const client = new PipelexApiClient();
|
|
9
|
+
const page = await client.listMethodTemplates({ ordering: "default", category: "finance", limit: 8 });
|
|
10
|
+
for (const tab of page.categories) console.log(tab.slug, tab.label); // every category with a template
|
|
11
|
+
for (const template of page.items) {
|
|
12
|
+
// The thumbnail whose ratio is closest to a 2:1 card.
|
|
13
|
+
const card = template.thumbnails.reduce<(typeof template.thumbnails)[number] | undefined>(
|
|
14
|
+
(best, thumbnail) =>
|
|
15
|
+
best === undefined ||
|
|
16
|
+
Math.abs(thumbnail.width / thumbnail.height - 2) < Math.abs(best.width / best.height - 2)
|
|
17
|
+
? thumbnail
|
|
18
|
+
: best,
|
|
19
|
+
undefined,
|
|
20
|
+
);
|
|
21
|
+
console.log(template.name, template.description, card?.url);
|
|
22
|
+
}
|
|
23
|
+
// From a web session, a template copies into a team the caller may change methods in, from its
|
|
24
|
+
// latest published version; an API key, which belongs to one organization, cannot copy.
|
|
25
|
+
const first = page.items[0];
|
|
26
|
+
if (first !== undefined) {
|
|
27
|
+
await client.copyMethod(first.method_id, { destination_org_id: "org_team" });
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## The catalog
|
|
32
|
+
|
|
33
|
+
| Method | Route | Answer |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `listMethodTemplates({ ordering?, category?, limit?, cursor? })` | `GET /v1/method-templates` | A `MethodTemplatePage`: `{ items, nextCursor, ordering, categories }`. |
|
|
36
|
+
| `iterateMethodTemplates({ ordering?, category?, limit? })` | the same, following the cursor | Every `MethodTemplate`, in the order served. |
|
|
37
|
+
|
|
38
|
+
**The order is the platform's.** An ordering is a named list an admin writes, such as `default` or one arm of a test. The templates it lists lead, in its order, and every other template follows by name, so a template appears as soon as it is public. `ordering` asks for one by name; an absent, unknown or malformed name serves `default`, and the page's `ordering` names the one served, `null` when there is none, the templates then listed by name. A client never sorts the cards: it keeps the order served.
|
|
39
|
+
|
|
40
|
+
**A category filters and keeps the order.** `category` takes a slug of the catalog's list, such as `human-resources`, and keeps that category's templates in the same order; a slug no template shows keeps none. Whatever the filter, the page's `categories` names, in the catalog's order, every category at least one template shows, each a `MethodCategory` with its `slug` and its English `label`, so a tab bar drawn from it never shows an empty tab. A client translating the labels translates by slug and falls back on the label.
|
|
41
|
+
|
|
42
|
+
**A card is the latest published version.** Each `MethodTemplate` carries `method_id`, `name`, `description` (`null` when it has none), `latest_version`, `categories` (slugs), `organization_name`, the display name of the organization holding it, and `thumbnails`, each a `MethodTemplateThumbnail`: the image's `path`, `media_type`, `width`, `height` and `sha256`, with a `url` signed by storage and its `expires_at`, about fifteen minutes after the read. The ratio is never declared: draw the thumbnail whose `width` and `height` come closest to your frame. The link is a bearer credential until it expires, so it stays out of logs and stored records, and an answer the SDK cannot read is thrown with its links redacted.
|
|
43
|
+
|
|
44
|
+
**Paging.** A page holds 50 templates by default and at most 200; a `limit` outside 1 to 200 is a `422` `request.invalid`. `nextCursor` resumes after the last template the page served, wherever the catalog now places it, so a template added or removed elsewhere between two pages neither repeats nor skips the ones around that point. The cursor holds neither the ordering nor the category: pass both again with it, as `iterateMethodTemplates` does. A cursor of another listing is a `422` `request.invalid` and one that does not decode a `422` `request.malformed`.
|
|
45
|
+
|
|
46
|
+
**Refusals.** `500` `runtime.misconfigured` when the templates organization the deployment names has no record or a link cannot be signed, and `503` `runtime.unavailable`, safe to retry. A deployment that names no templates organization serves an empty catalog.
|
|
47
|
+
|
|
48
|
+
## Reading another organization's public method
|
|
49
|
+
|
|
50
|
+
A template is a public method, so its `method_id` opens on the method routes of every organization:
|
|
51
|
+
|
|
52
|
+
| Method | From another organization |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `getMethod(id)` | A `PublicMethodDetail`: the latest published version, never the draft |
|
|
55
|
+
| `listMethodVersions(id)`, `getMethodVersion(id, n)` | The published versions, each without `published_by` |
|
|
56
|
+
| `start`, `execute`, `startAndWaitForResult` with `method_id` | The bare id or `mt_…@<n>` runs, belongs to and is billed to the caller's organization; `mt_…@draft` is `404` |
|
|
57
|
+
| `copyMethod(id, …)` | A copy of the latest published version (below) |
|
|
58
|
+
| `writeDraft`, `renameMethod`, `publishMethod`, `deleteMethod`, the examples | `404` `pipelex.catalog.not_found`: every write is the owner's |
|
|
59
|
+
|
|
60
|
+
**`getMethod` answers the owner's shape or the public one**, typed `MethodDetail | PublicMethodDetail`. The caller's own method reads whole, as before. Another organization's public method reads in a narrower shape: `method_id`, `org_id` (the organization holding it), `name`, `created_at`, `visibility`, and the latest published version's `description`, `categories`, `latest_version` and `latest_published`, with `files`, that version's tree, linked in the holding organization, which `writeMethodTree` writes as any listing. It carries nothing of the draft or of the owner's people: no `updated_at`, `draft_digest`, `input_data`, `pipe_output`, `created_by_user_id`, `copied_from` or `deletion_state`. The draft's token `updated_at` is what tells them apart, so narrow before reading the draft. An answer without `updated_at` that still carries a member only the owner's read has is a damaged owner read, and the SDK throws it as an answer it cannot read rather than pass it off as a public method:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const method = await client.getMethod(methodId);
|
|
64
|
+
if ("updated_at" in method) {
|
|
65
|
+
await client.writeDraft(method.method_id, { contents, expected_updated_at: method.updated_at });
|
|
66
|
+
} else {
|
|
67
|
+
// Another organization's public method: read it, run it, copy it.
|
|
68
|
+
console.log(`version ${method.latest_version} of ${method.org_id}'s method`);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**A version read leaves out its publisher.** `MethodVersionSummary.published_by` is optional for that reason: the publisher's user id no more crosses organizations than the method's `created_by_user_id` does.
|
|
73
|
+
|
|
74
|
+
**What reads as not found.** An unknown id, another organization's internal method, and a public one made internal again or being erased all answer `404` `pipelex.catalog.not_found`, alike. A method made internal is closed at once, even before the platform's index has caught up.
|
|
75
|
+
|
|
76
|
+
## Copying a public method
|
|
77
|
+
|
|
78
|
+
`copyMethod` of another organization's public method copies from its **latest published version**, never its draft: that version's tree, description and categories, and the method's saved examples, but not the owner's form inputs. The copy's `copied_from` names the `version` it took, its `draft_digest` `null`. Leave `expected_draft_updated_at` out: the caller never saw that method's draft, and the field sent with it is refused with a `422` `request.invalid` naming it. The rest of the copy, its verdict and its refusals, are [method-copy.md](./method-copy.md)'s.
|
|
79
|
+
|
|
80
|
+
## Visibility, categories and thumbnails on a method
|
|
81
|
+
|
|
82
|
+
- **`visibility`**, on every method read (`MethodData`, `MethodDetail`, `PublicMethodDetail`): `internal` or `public`, read open, so a value a later platform adds arrives as its raw string. Absent from a platform that predates it, which means `internal`. The list rows (`MethodSummary`) do not carry it.
|
|
83
|
+
- **`categories`**, on a method and on each version summary: the slugs the manifest declares under `[package]` that are on the catalog's list, in their declared order. The platform ignores a value it does not know, so a typo reads as no category.
|
|
84
|
+
- **`thumbnails`**, on each version summary: the valid images directly under the tree's `thumbnails/` folder, each a `MethodThumbnail` (`path`, `media_type`, `width`, `height`, `sha256`), measured by the publish. A PNG, JPEG or WebP of at least 800 by 400 pixels is valid; an invalid image is simply not recorded.
|
|
85
|
+
- **`description`**: the manifest's `description`, else the primary bundle's top-level `description`, else its main pipe's, a blank one counting as none at each step.
|
|
86
|
+
|
|
87
|
+
## Publishing a public method
|
|
88
|
+
|
|
89
|
+
A public method's publish is held to more than any draft's, so that a template's card never loses its picture and every organization can run what it reads. On top of `invalid` and `not_runnable`, its `refused` verdict may carry:
|
|
90
|
+
|
|
91
|
+
| `reason` | When |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `storage_reference` | A `.mthds` file of the draft names a `pipelex-storage://` file, which other organizations cannot read. |
|
|
94
|
+
| `thumbnail_missing` | The draft's tree holds no file directly under `thumbnails/`, or none was read within the publish's time; a retry may read them in time. |
|
|
95
|
+
| `thumbnail_invalid` | No file directly under `thumbnails/` is a valid thumbnail. |
|
|
96
|
+
|
|
97
|
+
Each is a verdict, a `200` with a `message` for a person and the runner's valid `validation`, never a thrown error. `MethodPublishRefused.reason` is read open, typed `MethodPublishRefusalReason | (string & {})`: a reason a later platform adds arrives as its raw string, still a verdict, so a client shows its `message` when it does not handle the reason. An internal method's publish never meets the reasons of this table. A method made public while its publish ran is a `503` with nothing written, safe to retry.
|