@shuffl/sdk 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,7 @@ const preview = await shuffl.programs.preview({
33
33
 
34
34
  `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
35
 
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. Company settings and billing are not in the API: a team key never gains company scope. 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.
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 client and key, 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
37
 
38
38
  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
39
 
@@ -60,4 +60,23 @@ Management writes return `{receipt, result}`. A duplicate request returns the sa
60
60
 
61
61
  Credentials with `credentials:write` can delegate only their own scopes, with bounded expiry and inherited revocation. Account sign-in, switching to another team, provider consent and payment confirmation remain explicit browser/account flows.
62
62
 
63
+ ## Company settings
64
+
65
+ 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. `createShufflCompany` takes it and calls the REST operations under `/api/v1/company`:
66
+
67
+ ```ts
68
+ import { createShufflCompany } from '@shuffl/sdk';
69
+
70
+ const company = createShufflCompany({
71
+ baseUrl: process.env.SHUFFL_BASE_URL!,
72
+ token: process.env.SHUFFL_COMPANY_KEY!,
73
+ });
74
+
75
+ const { permissions } = await company.current(); // Any company key.
76
+ const page = await company.members.list({ limit: 100 });
77
+ await company.members.remove({ userId: page.items[0]!.userId });
78
+ ```
79
+
80
+ `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 `createShuffl` methods, and a workspace key can't call these. See [company keys](https://shuffl-rebuild.vercel.app/docs/api-permissions#company-keys).
81
+
63
82
  Full guide: [Use the SDK and HTTP API](https://shuffl-rebuild.vercel.app/docs/sdk).
package/dist/index.d.ts CHANGED
@@ -1,12 +1,23 @@
1
- import type { ContractRouterClient } from '@orpc/contract';
1
+ import { type ContractRouterClient } from '@orpc/contract';
2
2
  import type { ShufflContract } from '@shuffl/api-contracts';
3
+ import { type ShufflCompanyContract } from '@shuffl/api-contracts/company';
3
4
  export { ORPCError } from '@orpc/client';
4
- export type { Permission, PermissionDefinition, Scope, } from '@shuffl/api-contracts';
5
+ export type { CompanyPermission, Permission, PermissionDefinition, Scope, } from '@shuffl/api-contracts';
5
6
  export type ShufflClient = ContractRouterClient<ShufflContract>;
7
+ /** The company API (#1692), for a company key (shuffl_company_). */
8
+ export type ShufflCompanyClient = ContractRouterClient<ShufflCompanyContract>;
6
9
  export interface ShufflOptions {
7
10
  token: string | (() => string | Promise<string>);
8
11
  /** Shuffl application origin, without /api/v1/rpc. */
9
12
  baseUrl: string;
10
13
  fetch?: typeof globalThis.fetch;
11
14
  }
15
+ /** A client for workspace operations, with a workspace key (shuffl_api_). */
12
16
  export declare function createShuffl(options: ShufflOptions): ShufflClient;
17
+ /**
18
+ * A client for the company API, with a company key (shuffl_company_) from
19
+ * Company settings → API keys. It calls the REST operations under
20
+ * /api/v1/company: `company.members.list({ limit: 100 })`. A company key
21
+ * never reaches workspace operations, and a workspace key never reaches these.
22
+ */
23
+ export declare function createShufflCompany(options: ShufflOptions): ShufflCompanyClient;
package/dist/index.js CHANGED
@@ -1,23 +1,95 @@
1
- import { createORPCClient } from '@orpc/client';
1
+ import { createORPCClient, ORPCError } from '@orpc/client';
2
2
  import { RPCLink } from '@orpc/client/fetch';
3
+ import { isContractProcedure } from '@orpc/contract';
4
+ import { companyContract, } from '@shuffl/api-contracts/company';
3
5
  export { ORPCError } from '@orpc/client';
4
- export function createShuffl(options) {
5
- const base = new URL(options.baseUrl);
6
+ function origin(baseUrl) {
7
+ const base = new URL(baseUrl);
6
8
  if (base.username ||
7
9
  base.password ||
8
10
  (base.protocol !== 'https:' &&
9
11
  !(base.protocol === 'http:' &&
10
12
  ['localhost', '127.0.0.1', '[::1]'].includes(base.hostname))))
11
13
  throw new Error('Use HTTPS, or HTTP on localhost for development.');
14
+ return base;
15
+ }
16
+ const bearer = async (token) => `Bearer ${typeof token === 'function' ? await token() : token}`;
17
+ /** A client for workspace operations, with a workspace key (shuffl_api_). */
18
+ export function createShuffl(options) {
19
+ const base = origin(options.baseUrl);
12
20
  return createORPCClient(new RPCLink({
13
21
  // Writes are not automatically retried. Callers own stable request keys.
14
22
  fetch: async (request, init) => (options.fetch ?? globalThis.fetch)(request, {
15
23
  ...init,
16
24
  redirect: 'error',
17
25
  }),
18
- headers: async () => ({
19
- authorization: `Bearer ${typeof options.token === 'function' ? await options.token() : options.token}`,
20
- }),
26
+ headers: async () => ({ authorization: await bearer(options.token) }),
21
27
  url: new URL('/api/v1/rpc', base).toString(),
22
28
  }));
23
29
  }
30
+ const statusCodes = {
31
+ 400: 'BAD_REQUEST',
32
+ 401: 'UNAUTHORIZED',
33
+ 403: 'FORBIDDEN',
34
+ 404: 'NOT_FOUND',
35
+ 405: 'METHOD_NOT_SUPPORTED',
36
+ 409: 'CONFLICT',
37
+ 429: 'TOO_MANY_REQUESTS',
38
+ };
39
+ /**
40
+ * A client for the company API, with a company key (shuffl_company_) from
41
+ * Company settings → API keys. It calls the REST operations under
42
+ * /api/v1/company: `company.members.list({ limit: 100 })`. A company key
43
+ * never reaches workspace operations, and a workspace key never reaches these.
44
+ */
45
+ export function createShufflCompany(options) {
46
+ const base = origin(options.baseUrl);
47
+ const procedure = ({ method, path }) => async (input, call) => {
48
+ const url = new URL(`/api/v1${path}`, base);
49
+ const read = method === 'GET';
50
+ if (read)
51
+ for (const [key, value] of Object.entries(input ?? {}))
52
+ if (value !== undefined)
53
+ url.searchParams.set(key, String(value));
54
+ const response = await (options.fetch ?? globalThis.fetch)(url, {
55
+ body: read ? undefined : JSON.stringify(input ?? {}),
56
+ headers: {
57
+ accept: 'application/json',
58
+ authorization: await bearer(options.token),
59
+ ...(read ? {} : { 'content-type': 'application/json' }),
60
+ },
61
+ method,
62
+ redirect: 'error',
63
+ signal: call?.signal,
64
+ });
65
+ const text = await response.text();
66
+ let body;
67
+ try {
68
+ body = text ? JSON.parse(text) : null;
69
+ }
70
+ catch {
71
+ body = undefined;
72
+ }
73
+ if (!response.ok) {
74
+ const error = (body ?? {});
75
+ throw new ORPCError(typeof error.code === 'string'
76
+ ? error.code
77
+ : (statusCodes[response.status] ?? 'INTERNAL_SERVER_ERROR'), {
78
+ message: typeof error.message === 'string'
79
+ ? error.message
80
+ : `Shuffl answered ${response.status}.`,
81
+ status: response.status,
82
+ });
83
+ }
84
+ if (body === undefined)
85
+ throw new ORPCError('INTERNAL_SERVER_ERROR', {
86
+ message: 'Shuffl returned a response that is not JSON.',
87
+ status: response.status,
88
+ });
89
+ return body;
90
+ };
91
+ const build = (node) => isContractProcedure(node)
92
+ ? procedure(node['~orpc'].route)
93
+ : Object.fromEntries(Object.entries(node).map(([key, value]) => [key, build(value)]));
94
+ return build(companyContract);
95
+ }
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.3.0"
5
+ "@shuffl/api-contracts": "0.4.0"
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.3.0"
37
+ "version": "0.4.0"
38
38
  }