@shuffl/sdk 0.5.12 → 0.6.1
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 +15 -17
- package/dist/index.d.ts +24 -11
- package/dist/index.js +60 -14
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,12 +13,9 @@ Create an API key in **Settings → API keys**. The key is bound to one team and
|
|
|
13
13
|
Choose exact endpoint permissions such as `users.list` and `groups.save`, grouped by resource and Read/Write. Discover them with `shuffl.permissions.list()`. A write grant does not imply reads or sibling operations. See [endpoint permissions and delegation](https://shuffl-rebuild.vercel.app/docs/api-permissions).
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import {
|
|
16
|
+
import { Shuffl } from '@shuffl/sdk';
|
|
17
17
|
|
|
18
|
-
const shuffl =
|
|
19
|
-
baseUrl: process.env.SHUFFL_BASE_URL!, // Application origin, without /api/v1/rpc
|
|
20
|
-
token: process.env.SHUFFL_TOKEN!,
|
|
21
|
-
});
|
|
18
|
+
const shuffl = new Shuffl({ apiKey: process.env.SHUFFL_TOKEN! });
|
|
22
19
|
|
|
23
20
|
const page = await shuffl.users.list({ limit: 25 });
|
|
24
21
|
const next = page.nextCursor
|
|
@@ -31,18 +28,22 @@ const preview = await shuffl.programs.preview({
|
|
|
31
28
|
});
|
|
32
29
|
```
|
|
33
30
|
|
|
31
|
+
`new Shuffl` takes a workspace key or a company key and reads its prefix to tell them apart. Pass `baseUrl` (the application origin, without `/api/v1`) only to reach a server other than production, such as `http://localhost:3000` in development.
|
|
32
|
+
|
|
34
33
|
`users` means people in the team directory, including people who have no web login. IDs are Shuffl person IDs, not Slack IDs or authentication account IDs.
|
|
35
34
|
|
|
36
|
-
The client includes people and their profiles, teams and departments, access, identity links, programs and runs, Knowledge, invitations, People sync, onboarding and first 90 days check-ins, celebrations, Pulse, Praise, badges, communities, reminders, Home, the HR digest, HR requests, support, reports, exports, credentials and assistant conversations. A team key never gains company scope; company settings have their own
|
|
35
|
+
The client includes people and their profiles, teams and departments, access, identity links, programs and runs, Knowledge, invitations, People sync, onboarding and first 90 days check-ins, celebrations, Pulse, Praise, badges, communities, reminders, Home, the HR digest, HR requests, support, reports, exports, credentials and assistant conversations. A team key never gains company scope; company settings have their own key and live under `shuffl.company`, below. See the [permission reference](https://shuffl-rebuild.vercel.app/docs/api-permissions) for every method. Input and output types come directly from the portable public oRPC contract.
|
|
37
36
|
|
|
38
37
|
Program creation requires a stable `requestKey`. Repeating the same creation request returns the existing program; using that key with different settings returns `CONFLICT`. Programs start paused unless `draft: false` is explicitly supplied. Updates require `expectedRevision`. Run requests require a preview and request key and return a durable run ID; poll `runs.get` for status. The application scheduler owns execution and delivery.
|
|
39
38
|
|
|
40
39
|
Dates in the public API are ISO strings. Directory lists use an opaque cursor and a limit of 1–100; other methods retain their documented pagination shapes. The SDK does not automatically retry writes. Supply a token getter if credentials rotate:
|
|
41
40
|
|
|
42
41
|
```ts
|
|
43
|
-
|
|
42
|
+
new Shuffl({ apiKey: async () => credentialStore.read() });
|
|
44
43
|
```
|
|
45
44
|
|
|
45
|
+
Every request carries an `x-shuffl-client` header naming the SDK release (`shuffl-sdk/0.6.1`; the CLI adds `shuffl-cli/0.6.1`), so Shuffl can count requests by release. Pass `client: 'billing-sync/1.2.0'` to put your app's name in front; Shuffl never stores that part.
|
|
46
|
+
|
|
46
47
|
Use `ORPCError.code` for errors (`UNAUTHORIZED`, `FORBIDDEN`, `BAD_REQUEST`, `NOT_FOUND`, `CONFLICT`, and others). Calls accept oRPC's second options argument, including an abort signal. HTTPS is required except on loopback; redirects are refused to avoid forwarding credentials to another origin. The public API currently allows same-origin browser calls and server/CLI clients; cross-origin browser CORS is not enabled.
|
|
47
48
|
|
|
48
49
|
## Webhooks
|
|
@@ -89,21 +90,18 @@ Credentials with `credentials:write` can delegate only their own scopes, with bo
|
|
|
89
90
|
|
|
90
91
|
## Company settings
|
|
91
92
|
|
|
92
|
-
A company key (`shuffl_company_`), created by a company owner or administrator in **Company settings → API keys**, manages one company's settings and never reaches a workspace.
|
|
93
|
+
A company key (`shuffl_company_`), created by a company owner or administrator in **Company settings → API keys**, manages one company's settings and never reaches a workspace. Pass it to the same client and call `shuffl.company`, which runs the REST operations under `/api/v1/company`:
|
|
93
94
|
|
|
94
95
|
```ts
|
|
95
|
-
import {
|
|
96
|
+
import { Shuffl } from '@shuffl/sdk';
|
|
96
97
|
|
|
97
|
-
const
|
|
98
|
-
baseUrl: process.env.SHUFFL_BASE_URL!,
|
|
99
|
-
token: process.env.SHUFFL_COMPANY_KEY!,
|
|
100
|
-
});
|
|
98
|
+
const shuffl = new Shuffl({ apiKey: process.env.SHUFFL_COMPANY_KEY! });
|
|
101
99
|
|
|
102
|
-
const { permissions } = await company.current(); // Any company key.
|
|
103
|
-
const page = await company.members.list({ limit: 100 });
|
|
104
|
-
await company.members.remove({ userId: page.items[0]!.userId });
|
|
100
|
+
const { permissions } = await shuffl.company.current(); // Any company key.
|
|
101
|
+
const page = await shuffl.company.members.list({ limit: 100 });
|
|
102
|
+
await shuffl.company.members.remove({ userId: page.items[0]!.userId });
|
|
105
103
|
```
|
|
106
104
|
|
|
107
|
-
`company.current()` returns the key's company and grants. Every other method needs its grant: `company.details`, `company.workspaces.list`, `company.members.list`, `company.emailDomains.list`, `company.slack.list`, `company.rename`, `company.workspaces.setDefault` and `company.members.remove`. `members.list` pages with `limit` and `cursor` like `users.list`. Company writes run once when called and take no `requestKey`. A company key can't call
|
|
105
|
+
`shuffl.company.current()` returns the key's company and grants. Every other method needs its grant: `company.details`, `company.workspaces.list`, `company.members.list`, `company.emailDomains.list`, `company.slack.list`, `company.rename`, `company.workspaces.setDefault` and `company.members.remove`. `members.list` pages with `limit` and `cursor` like `users.list`. Company writes run once when called and take no `requestKey`. A company key can't call the workspace methods, and a workspace key can't call `shuffl.company`; the SDK refuses the wrong kind with `UNAUTHORIZED` before sending a request. See [company keys](https://shuffl-rebuild.vercel.app/docs/api-permissions#company-keys).
|
|
108
106
|
|
|
109
107
|
Full guide: [Use the SDK and HTTP API](https://shuffl-rebuild.vercel.app/docs/sdk).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ContractRouterClient } from '@orpc/contract';
|
|
2
|
-
import type
|
|
2
|
+
import { type ShufflContract } from '@shuffl/api-contracts';
|
|
3
3
|
import { type ShufflCompanyContract } from '@shuffl/api-contracts/company';
|
|
4
4
|
export { ORPCError } from '@orpc/client';
|
|
5
5
|
export type { CompanyPermission, Permission, PermissionDefinition, Scope, } from '@shuffl/api-contracts';
|
|
@@ -8,17 +8,30 @@ export type ShufflClient = ContractRouterClient<ShufflContract>;
|
|
|
8
8
|
/** The company API (#1692), for a company key (shuffl_company_). */
|
|
9
9
|
export type ShufflCompanyClient = ContractRouterClient<ShufflCompanyContract>;
|
|
10
10
|
export interface ShufflOptions {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/**
|
|
12
|
+
* A workspace key (shuffl_api_) or a company key (shuffl_company_), or a
|
|
13
|
+
* function that returns the current one when keys rotate.
|
|
14
|
+
*/
|
|
15
|
+
apiKey: string | (() => string | Promise<string>);
|
|
16
|
+
/** Shuffl application origin, without /api/v1. Defaults to production. */
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Your app's name and version, such as "hris-sync/2.1". Sent ahead of the
|
|
20
|
+
* SDK's own in the x-shuffl-client header; Shuffl doesn't store it.
|
|
21
|
+
*/
|
|
22
|
+
client?: string;
|
|
14
23
|
fetch?: typeof globalThis.fetch;
|
|
15
24
|
}
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
export interface Shuffl extends ShufflClient {
|
|
26
|
+
}
|
|
18
27
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
28
|
+
* The Shuffl API client. A workspace key (shuffl_api_) calls the workspace
|
|
29
|
+
* operations at the top level: `shuffl.users.list({ limit: 25 })`. A company
|
|
30
|
+
* key (shuffl_company_) from Company settings → API keys calls the company
|
|
31
|
+
* operations under `shuffl.company`: `shuffl.company.members.list()`. Calling
|
|
32
|
+
* the other kind's methods fails before any request is sent.
|
|
23
33
|
*/
|
|
24
|
-
export declare
|
|
34
|
+
export declare class Shuffl {
|
|
35
|
+
readonly company: ShufflCompanyClient;
|
|
36
|
+
constructor(options: ShufflOptions);
|
|
37
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { createORPCClient, ORPCError } from '@orpc/client';
|
|
2
2
|
import { RPCLink } from '@orpc/client/fetch';
|
|
3
3
|
import { isContractProcedure } from '@orpc/contract';
|
|
4
|
+
import { contract } from '@shuffl/api-contracts';
|
|
4
5
|
import { companyContract, } from '@shuffl/api-contracts/company';
|
|
5
6
|
export { ORPCError } from '@orpc/client';
|
|
6
7
|
// #643: check and type the webhooks Shuffl sends.
|
|
7
8
|
export { isWebhookEventType, signWebhook, verifyWebhook, WebhookVerificationError, webhookApiVersion, webhookEventCatalog, webhookEventGroups, webhookEventTypes, } from '@shuffl/api-contracts/webhooks';
|
|
9
|
+
import { sdkVersion } from './version.js';
|
|
10
|
+
// Which SDK release (and, for the CLI, which CLI release) made a request,
|
|
11
|
+
// for Shuffl's own usage counts. One header line, no spaces inside a part.
|
|
12
|
+
function clientHeader(client) {
|
|
13
|
+
const own = `shuffl-sdk/${sdkVersion}`;
|
|
14
|
+
const name = client?.trim();
|
|
15
|
+
if (!name)
|
|
16
|
+
return own;
|
|
17
|
+
if (!/^[\x21-\x7e]+(?: [\x21-\x7e]+)*$/.test(name) || name.length > 128)
|
|
18
|
+
throw new Error('client is a short name/version, such as billing-sync/1.2.0.');
|
|
19
|
+
return `${name} ${own}`;
|
|
20
|
+
}
|
|
21
|
+
const defaultBaseUrl = 'https://shuffl-rebuild.vercel.app';
|
|
22
|
+
const companyKeyPrefix = 'shuffl_company_';
|
|
8
23
|
function origin(baseUrl) {
|
|
9
24
|
const base = new URL(baseUrl);
|
|
10
25
|
if (base.username ||
|
|
@@ -15,17 +30,30 @@ function origin(baseUrl) {
|
|
|
15
30
|
throw new Error('Use HTTPS, or HTTP on localhost for development.');
|
|
16
31
|
return base;
|
|
17
32
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
33
|
+
// The key's prefix picks the API it can call, so the wrong kind fails here
|
|
34
|
+
// with a clear message instead of an UNAUTHORIZED from the server.
|
|
35
|
+
const bearer = async (apiKey, kind) => {
|
|
36
|
+
const key = typeof apiKey === 'function' ? await apiKey() : apiKey;
|
|
37
|
+
if (key.startsWith(companyKeyPrefix) !== (kind === 'company'))
|
|
38
|
+
throw new ORPCError('UNAUTHORIZED', {
|
|
39
|
+
message: kind === 'company'
|
|
40
|
+
? 'shuffl.company methods need a company key (shuffl_company_) from Company settings → API keys.'
|
|
41
|
+
: 'A company key (shuffl_company_) only reaches shuffl.company methods. Use a workspace key (shuffl_api_) from Settings → API keys.',
|
|
42
|
+
status: 401,
|
|
43
|
+
});
|
|
44
|
+
return `Bearer ${key}`;
|
|
45
|
+
};
|
|
46
|
+
function workspaceClient(base, options, client) {
|
|
22
47
|
return createORPCClient(new RPCLink({
|
|
23
48
|
// Writes are not automatically retried. Callers own stable request keys.
|
|
24
49
|
fetch: async (request, init) => (options.fetch ?? globalThis.fetch)(request, {
|
|
25
50
|
...init,
|
|
26
51
|
redirect: 'error',
|
|
27
52
|
}),
|
|
28
|
-
headers: async () => ({
|
|
53
|
+
headers: async () => ({
|
|
54
|
+
authorization: await bearer(options.apiKey, 'workspace'),
|
|
55
|
+
'x-shuffl-client': client,
|
|
56
|
+
}),
|
|
29
57
|
url: new URL('/api/v1/rpc', base).toString(),
|
|
30
58
|
}));
|
|
31
59
|
}
|
|
@@ -38,14 +66,9 @@ const statusCodes = {
|
|
|
38
66
|
409: 'CONFLICT',
|
|
39
67
|
429: 'TOO_MANY_REQUESTS',
|
|
40
68
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* /api/v1/company: `company.members.list({ limit: 100 })`. A company key
|
|
45
|
-
* never reaches workspace operations, and a workspace key never reaches these.
|
|
46
|
-
*/
|
|
47
|
-
export function createShufflCompany(options) {
|
|
48
|
-
const base = origin(options.baseUrl);
|
|
69
|
+
// The company API (#1692) is REST under /api/v1/company, so its client is
|
|
70
|
+
// built from the contract's routes rather than the RPC link.
|
|
71
|
+
function companyClient(base, options, client) {
|
|
49
72
|
const procedure = ({ method, path }) => async (input, call) => {
|
|
50
73
|
const url = new URL(`/api/v1${path}`, base);
|
|
51
74
|
const read = method === 'GET';
|
|
@@ -57,7 +80,8 @@ export function createShufflCompany(options) {
|
|
|
57
80
|
body: read ? undefined : JSON.stringify(input ?? {}),
|
|
58
81
|
headers: {
|
|
59
82
|
accept: 'application/json',
|
|
60
|
-
authorization: await bearer(options.
|
|
83
|
+
authorization: await bearer(options.apiKey, 'company'),
|
|
84
|
+
'x-shuffl-client': client,
|
|
61
85
|
...(read ? {} : { 'content-type': 'application/json' }),
|
|
62
86
|
},
|
|
63
87
|
method,
|
|
@@ -95,3 +119,25 @@ export function createShufflCompany(options) {
|
|
|
95
119
|
: Object.fromEntries(Object.entries(node).map(([key, value]) => [key, build(value)]));
|
|
96
120
|
return build(companyContract);
|
|
97
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* The Shuffl API client. A workspace key (shuffl_api_) calls the workspace
|
|
124
|
+
* operations at the top level: `shuffl.users.list({ limit: 25 })`. A company
|
|
125
|
+
* key (shuffl_company_) from Company settings → API keys calls the company
|
|
126
|
+
* operations under `shuffl.company`: `shuffl.company.members.list()`. Calling
|
|
127
|
+
* the other kind's methods fails before any request is sent.
|
|
128
|
+
*/
|
|
129
|
+
// biome-ignore lint/suspicious/noUnsafeDeclarationMerging: the workspace operations are assigned in the constructor.
|
|
130
|
+
export class Shuffl {
|
|
131
|
+
company;
|
|
132
|
+
constructor(options) {
|
|
133
|
+
const base = origin(options.baseUrl ?? defaultBaseUrl);
|
|
134
|
+
const client = clientHeader(options.client);
|
|
135
|
+
const workspace = workspaceClient(base, options, client);
|
|
136
|
+
for (const key of Object.keys(contract))
|
|
137
|
+
Object.defineProperty(this, key, {
|
|
138
|
+
enumerable: true,
|
|
139
|
+
value: workspace[key],
|
|
140
|
+
});
|
|
141
|
+
this.company = companyClient(base, options, client);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sdkVersion = "0.6.1";
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@orpc/client": "1.15.3",
|
|
4
4
|
"@orpc/contract": "1.15.3",
|
|
5
|
-
"@shuffl/api-contracts": "0.
|
|
5
|
+
"@shuffl/api-contracts": "0.6.1"
|
|
6
6
|
},
|
|
7
7
|
"description": "TypeScript client for the Shuffl API: people, teams, knowledge, programs and management commands.",
|
|
8
8
|
"engines": {
|
|
@@ -34,5 +34,5 @@
|
|
|
34
34
|
"sideEffects": false,
|
|
35
35
|
"type": "module",
|
|
36
36
|
"types": "./dist/index.d.ts",
|
|
37
|
-
"version": "0.
|
|
37
|
+
"version": "0.6.1"
|
|
38
38
|
}
|