@rytass/bpm-core-client 0.1.1 → 0.1.3

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +113 -3
  3. package/eslint.config.mjs +19 -0
  4. package/jest.config.cts +10 -0
  5. package/package.json +4 -5
  6. package/project.json +66 -0
  7. package/src/lib/api-auth-client.ts +169 -0
  8. package/src/lib/form/form-api.spec.ts +81 -0
  9. package/src/lib/form/form-api.ts +388 -0
  10. package/src/lib/form/form-rendering.ts +580 -0
  11. package/src/lib/graphql-client.spec.ts +65 -0
  12. package/src/lib/graphql-client.ts +114 -0
  13. package/src/lib/member-api.ts +129 -0
  14. package/src/lib/organization/organization-api.spec.ts +90 -0
  15. package/src/lib/organization/organization-api.ts +710 -0
  16. package/src/lib/template/template-api.spec.ts +88 -0
  17. package/src/lib/template/template-api.ts +772 -0
  18. package/src/lib/workflow/workflow-api.spec.ts +132 -0
  19. package/src/lib/workflow/workflow-api.ts +1749 -0
  20. package/tsconfig.json +27 -0
  21. package/tsconfig.lib.json +16 -0
  22. package/tsconfig.spec.json +16 -0
  23. package/LICENSE +0 -21
  24. package/src/index.js +0 -7
  25. package/src/index.js.map +0 -1
  26. package/src/lib/api-auth-client.d.ts +0 -73
  27. package/src/lib/api-auth-client.js +0 -121
  28. package/src/lib/api-auth-client.js.map +0 -1
  29. package/src/lib/form/form-api.d.ts +0 -48
  30. package/src/lib/form/form-api.js +0 -228
  31. package/src/lib/form/form-api.js.map +0 -1
  32. package/src/lib/form/form-rendering.d.ts +0 -52
  33. package/src/lib/form/form-rendering.js +0 -341
  34. package/src/lib/form/form-rendering.js.map +0 -1
  35. package/src/lib/form/index.js +0 -6
  36. package/src/lib/form/index.js.map +0 -1
  37. package/src/lib/graphql-client.d.ts +0 -45
  38. package/src/lib/graphql-client.js +0 -92
  39. package/src/lib/graphql-client.js.map +0 -1
  40. package/src/lib/member-api.d.ts +0 -49
  41. package/src/lib/member-api.js +0 -72
  42. package/src/lib/member-api.js.map +0 -1
  43. package/src/lib/organization/index.js +0 -5
  44. package/src/lib/organization/index.js.map +0 -1
  45. package/src/lib/organization/organization-api.d.ts +0 -168
  46. package/src/lib/organization/organization-api.js +0 -394
  47. package/src/lib/organization/organization-api.js.map +0 -1
  48. package/src/lib/template/index.js +0 -5
  49. package/src/lib/template/index.js.map +0 -1
  50. package/src/lib/template/template-api.d.ts +0 -150
  51. package/src/lib/template/template-api.js +0 -407
  52. package/src/lib/template/template-api.js.map +0 -1
  53. package/src/lib/workflow/index.js +0 -5
  54. package/src/lib/workflow/index.js.map +0 -1
  55. package/src/lib/workflow/workflow-api.d.ts +0 -376
  56. package/src/lib/workflow/workflow-api.js +0 -867
  57. package/src/lib/workflow/workflow-api.js.map +0 -1
  58. /package/src/{index.d.ts → index.ts} +0 -0
  59. /package/src/lib/form/{index.d.ts → index.ts} +0 -0
  60. /package/src/lib/organization/{index.d.ts → index.ts} +0 -0
  61. /package/src/lib/template/{index.d.ts → index.ts} +0 -0
  62. /package/src/lib/workflow/{index.d.ts → index.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  Releases are managed by [`nx release`](https://nx.dev/recipes/nx-release) with
9
9
  Conventional Commits — see `nx.json` for the release config.
10
10
 
11
+ ## 0.1.2 — 2026-05-27
12
+
13
+ No source changes. Bumped in lockstep with
14
+ `@rytass/bpm-core-nestjs-module@0.1.2` (see its CHANGELOG for the
15
+ migration class-name fix).
16
+
11
17
  ## 0.1.1 — 2026-05-27
12
18
 
13
19
  No source changes. Version bumped in lockstep with
package/README.md CHANGED
@@ -18,7 +18,7 @@ functions directly from any framework (Next.js / Vite / Remix / plain Node).
18
18
 
19
19
  ## Package Status
20
20
 
21
- Current version: `0.0.1`
21
+ Current version: `0.1.2`
22
22
 
23
23
  The package intentionally stays framework-agnostic so it can be reused from
24
24
  Next.js Server Components, Server Actions, plain React, or non-React
@@ -42,6 +42,113 @@ your `tsconfig.json` so the package's `exports` field is honored. Classic
42
42
  shipped with this package, but is on TypeScript's long-term deprecation
43
43
  path — new projects should opt in to modern resolution.
44
44
 
45
+ ## Runtime Compatibility
46
+
47
+ > **TL;DR — the transport is isomorphic (always `fetch`), but session is
48
+ > browser-first.** Out of the box every function works in a browser tab.
49
+ > Server Components, Server Actions, Node CLIs, workers, and Edge Functions
50
+ > can also call the same functions, but the caller is responsible for
51
+ > resolving the endpoint to an absolute URL and for forwarding any
52
+ > session cookie.
53
+
54
+ ### What is identical across runtimes
55
+
56
+ | Concern | Behavior |
57
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | Transport | Always `globalThis.fetch`. No `axios`, no `node-fetch`, no `cross-fetch`, no Apollo Link. |
59
+ | Serialization | `Content-Type: application/json`; `POST { query, variables }`. |
60
+ | Error model | Throws `Error` on non-2xx HTTP, on `payload.errors[]`, or on missing `data`. |
61
+ | Type guarantees | Every function returns a typed `Promise<T>` — return shape is identical regardless of where the call runs. |
62
+
63
+ ### What differs across runtimes
64
+
65
+ | Concern | Browser | Node SSR / Server Component / CLI |
66
+ | ---------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
67
+ | Default endpoint | Same-origin `/graphql` resolves to `https://your-host/graphql`. | Same-origin `/graphql` **throws** `TypeError: Failed to parse URL`. Set `NEXT_PUBLIC_API_URL` to an absolute URL. |
68
+ | Session cookie | `credentials: 'include'` automatically sends the HttpOnly cookie issued by `/auth/login`.| `fetch` has no cookie jar; the host must forward the incoming request's `Cookie` header manually. |
69
+ | `loginApi()` storage | Cookie lands in browser jar, persists across navigations. | Returned `Set-Cookie` header is dropped — caller must read and reuse it explicitly. |
70
+ | `window.location` | Used to detect `localhost` for the dev fallback endpoint. | `typeof window === 'undefined'` → endpoint resolver returns `/graphql` (relative), which is invalid in Node unless overridden. |
71
+
72
+ ### Server Component / Server Action recipe
73
+
74
+ Next.js App Router server runtime needs two things: an absolute endpoint and
75
+ a way to relay the inbound `Cookie` header. The recommended pattern is to
76
+ set `NEXT_PUBLIC_API_URL` at build time and pass cookies via the underlying
77
+ `fetch` headers using a wrapper such as Next's `cookies()`:
78
+
79
+ ```ts
80
+ // app/inbox/page.tsx — Server Component
81
+ import { cookies } from 'next/headers';
82
+
83
+ async function fetchInbox(): Promise<unknown> {
84
+ const cookieHeader = (await cookies()).toString();
85
+ const response = await fetch(`${process.env.BPM_API_URL}/graphql`, {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/json',
89
+ Cookie: cookieHeader,
90
+ },
91
+ body: JSON.stringify({
92
+ query: 'query Ping { memberCount }',
93
+ }),
94
+ });
95
+ return response.json();
96
+ }
97
+ ```
98
+
99
+ > For SSR cases that need to call **many** typed functions, the cleanest
100
+ > long-term option is to add a `configureBPMClient({ fetch, endpoint })`
101
+ > hook so the typed wrappers can be reused. That is not yet shipped — if
102
+ > you need it, file an issue. Today, Server Component callers either talk
103
+ > to the host directly (as above) or proxy through a Route Handler that
104
+ > runs inside the consumer's browser session.
105
+
106
+ ### Node CLI recipe
107
+
108
+ CLIs that drive BPM from outside a browser need both an absolute endpoint
109
+ and explicit session management. The auth REST client (`loginApi`,
110
+ `logoutApi`, `readApiCurrentMember`) returns typed bodies but does not
111
+ persist cookies in Node — capture them yourself:
112
+
113
+ ```ts
114
+ // scripts/dump-running-instances.ts
115
+ import { listApprovalInstancesPage } from '@rytass/bpm-core-client/workflow';
116
+
117
+ process.env.NEXT_PUBLIC_API_URL ??= 'https://bpm.your-host.example/graphql';
118
+
119
+ // 1. Log in and capture the session cookie manually.
120
+ const loginResponse = await fetch(
121
+ 'https://bpm.your-host.example/auth/login',
122
+ {
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({ identifier: 'svc-bot', password: '...' }),
126
+ },
127
+ );
128
+ const cookieJar = loginResponse.headers.get('set-cookie') ?? '';
129
+
130
+ // 2. Hand the cookie to subsequent fetches via a small fetch override.
131
+ const originalFetch = globalThis.fetch;
132
+ globalThis.fetch = ((input, init = {}) =>
133
+ originalFetch(input, {
134
+ ...init,
135
+ headers: { ...(init.headers ?? {}), Cookie: cookieJar },
136
+ })) as typeof fetch;
137
+
138
+ // 3. Call typed BPM functions as usual.
139
+ const { instances } = await listApprovalInstancesPage({
140
+ view: 'ALL', state: 'RUNNING', page: 1, pageSize: 1000,
141
+ searchText: '', templateId: null,
142
+ });
143
+ console.log(instances.length, 'running instances');
144
+ ```
145
+
146
+ ### Edge / Workers
147
+
148
+ Cloudflare Workers, Vercel Edge Functions, and similar V8-isolate runtimes
149
+ ship `fetch` natively and follow the **Node SSR** column above — no
150
+ default endpoint, no cookie jar. Treat them the same as Server Components.
151
+
45
152
  ## Endpoint Resolution
46
153
 
47
154
  By default the client targets the same origin (no `/api` prefix) — see
@@ -161,8 +268,11 @@ export function useMembers(memberIds: readonly string[]) {
161
268
  }
162
269
  ```
163
270
 
164
- For Next.js App Router, call the same functions from Server Components or
165
- Server Actions — they only need standard `fetch`.
271
+ For Next.js App Router Client Components the typed wrappers work
272
+ out of the box (same-origin endpoint, browser cookie jar). For Server
273
+ Components, Server Actions, Route Handlers, Edge runtimes, or plain Node
274
+ CLIs, see **[Runtime Compatibility](#runtime-compatibility)** — those
275
+ runtimes need an absolute endpoint and manual cookie forwarding.
166
276
 
167
277
  ## Local Development
168
278
 
@@ -0,0 +1,19 @@
1
+ import baseConfig from '../../eslint.config.mjs';
2
+
3
+ export default [
4
+ ...baseConfig,
5
+ {
6
+ files: ['**/*.json'],
7
+ rules: {
8
+ '@nx/dependency-checks': [
9
+ 'error',
10
+ {
11
+ ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}'],
12
+ },
13
+ ],
14
+ },
15
+ languageOptions: {
16
+ parser: await import('jsonc-eslint-parser'),
17
+ },
18
+ },
19
+ ];
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ displayName: 'bpm-core-client',
3
+ preset: '../../jest.preset.js',
4
+ testEnvironment: 'node',
5
+ transform: {
6
+ '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
7
+ },
8
+ moduleFileExtensions: ['ts', 'js', 'html'],
9
+ coverageDirectory: '../../coverage/libs/bpm-core-client',
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rytass/bpm-core-client",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Framework-agnostic TypeScript client for the Rytass BPM approval workflow GraphQL surface: fetch-based GraphQL transport, REST auth client, pre-baked typed operations for organization, member, form, template, and workflow.",
5
5
  "keywords": [
6
6
  "bpm",
@@ -73,10 +73,9 @@
73
73
  }
74
74
  },
75
75
  "peerDependencies": {
76
- "@rytass/bpm-core-shared": "^0.1.0"
76
+ "@rytass/bpm-core-shared": "^0.1.3"
77
77
  },
78
78
  "dependencies": {
79
79
  "tslib": "^2.3.0"
80
- },
81
- "type": "commonjs"
82
- }
80
+ }
81
+ }
package/project.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "bpm-core-client",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/bpm-core-client/src",
5
+ "projectType": "library",
6
+ "tags": [],
7
+ "targets": {
8
+ "build": {
9
+ "executor": "@nx/js:tsc",
10
+ "outputs": ["{options.outputPath}"],
11
+ "options": {
12
+ "outputPath": "dist/libs/bpm-core-client",
13
+ "main": "libs/bpm-core-client/src/index.ts",
14
+ "tsConfig": "libs/bpm-core-client/tsconfig.lib.json",
15
+ "generatePackageJson": true,
16
+ "assets": [
17
+ "libs/bpm-core-client/*.md",
18
+ { "input": ".", "glob": "LICENSE", "output": "." }
19
+ ]
20
+ }
21
+ },
22
+ "test": {
23
+ "executor": "@nx/jest:jest",
24
+ "outputs": ["{workspaceRoot}/coverage/libs/bpm-core-client"],
25
+ "options": {
26
+ "jestConfig": "libs/bpm-core-client/jest.config.cts",
27
+ "passWithNoTests": true
28
+ }
29
+ },
30
+ "typecheck": {
31
+ "executor": "nx:run-commands",
32
+ "options": {
33
+ "command": "tsc --noEmit -p libs/bpm-core-client/tsconfig.lib.json"
34
+ }
35
+ },
36
+ "finalize-dist": {
37
+ "executor": "nx:run-commands",
38
+ "outputs": ["{workspaceRoot}/dist/libs/bpm-core-client"],
39
+ "dependsOn": ["build"],
40
+ "options": {
41
+ "command": "node tools/publish/finalize-dist-package.mjs dist/libs/bpm-core-client"
42
+ }
43
+ },
44
+ "pkg-quality": {
45
+ "executor": "nx:run-commands",
46
+ "dependsOn": ["finalize-dist"],
47
+ "options": {
48
+ "commands": [
49
+ "pnpm exec publint dist/libs/bpm-core-client"
50
+ ],
51
+ "parallel": false
52
+ }
53
+ },
54
+ "pkg-quality-attw": {
55
+ "executor": "nx:run-commands",
56
+ "dependsOn": ["finalize-dist"],
57
+ "options": {
58
+ "command": "pnpm exec attw --pack dist/libs/bpm-core-client"
59
+ }
60
+ },
61
+ "prepublish-check": {
62
+ "executor": "nx:noop",
63
+ "dependsOn": ["typecheck", "lint", "test", "finalize-dist", "pkg-quality"]
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,169 @@
1
+ import { readGraphQlEndpoint } from './graphql-client';
2
+
3
+ /**
4
+ * Authenticated BPM member returned by {@link loginApi} and
5
+ * {@link readApiCurrentMember}.
6
+ *
7
+ * Mirrors the shape that BPM-compatible wrapper hosts emit from their
8
+ * `POST /auth/login` and `GET /auth/me` endpoints. Roles and permissions
9
+ * are used by BPM's `BPMAdminGuard` / `BPMDesignerGuard`.
10
+ */
11
+ export interface ApiMember {
12
+ readonly email: string;
13
+ readonly expiresAt: string;
14
+ readonly memberId: string;
15
+ readonly name: string;
16
+ readonly permissions: readonly string[];
17
+ readonly roles: readonly string[];
18
+ }
19
+
20
+ /**
21
+ * Reduced member shape returned by {@link listApiTestMembers}. Excludes
22
+ * `permissions` and `expiresAt` because the test-members endpoint is meant
23
+ * for picker UIs in dev / demo, not for session establishment.
24
+ */
25
+ export interface ApiPublicMember {
26
+ readonly email: string;
27
+ readonly memberId: string;
28
+ readonly name: string;
29
+ readonly roles: readonly string[];
30
+ }
31
+
32
+ /**
33
+ * Lists seeded test members exposed by the host's `/auth/test-members`
34
+ * endpoint. Hosts targeting production typically disable this endpoint or
35
+ * gate it behind a feature flag.
36
+ */
37
+ export async function listApiTestMembers(): Promise<
38
+ readonly ApiPublicMember[]
39
+ > {
40
+ return requestApi<readonly ApiPublicMember[]>('/auth/test-members');
41
+ }
42
+
43
+ /**
44
+ * Performs a login against the host's `POST /auth/login` endpoint. On
45
+ * success the host issues an HTTP-only session cookie that subsequent
46
+ * {@link requestGraphQl} calls automatically carry, because
47
+ * {@link requestApi} uses `credentials: 'include'`.
48
+ *
49
+ * Throws on HTTP errors with a message extracted from the response body
50
+ * when available.
51
+ *
52
+ * @param input.identifier - Member id, email, or any other identifier the
53
+ * host understands.
54
+ * @param input.password - Cleartext credential — TLS is the consumer's
55
+ * responsibility.
56
+ */
57
+ export async function loginApi(input: {
58
+ readonly identifier: string;
59
+ readonly password: string;
60
+ }): Promise<ApiMember> {
61
+ return requestApi<ApiMember>('/auth/login', {
62
+ body: JSON.stringify(input),
63
+ headers: { 'Content-Type': 'application/json' },
64
+ method: 'POST',
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Calls `POST /auth/logout` on the host. The host clears the session
70
+ * cookie. Safe to call when not logged in (host should return 200).
71
+ */
72
+ export async function logoutApi(): Promise<void> {
73
+ await requestApi<{ readonly ok: true }>('/auth/logout', {
74
+ method: 'POST',
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Reads the currently authenticated member from `GET /auth/me`.
80
+ *
81
+ * Returns `null` when the host replies `401`, which is the conventional
82
+ * way to signal "no active session". Any other HTTP failure throws.
83
+ */
84
+ export async function readApiCurrentMember(): Promise<ApiMember | null> {
85
+ try {
86
+ return await requestApi<ApiMember>('/auth/me');
87
+ } catch (error: unknown) {
88
+ if (error instanceof ApiRequestError && error.status === 401) {
89
+ return null;
90
+ }
91
+
92
+ throw error;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Resolves the base URL used by the REST `/auth/*` calls.
98
+ *
99
+ * Resolution order:
100
+ * 1. `process.env.NEXT_PUBLIC_API_AUTH_URL` if non-empty.
101
+ * 2. Derived from {@link readGraphQlEndpoint}'s origin — the host is
102
+ * assumed to expose both `/graphql` and `/auth/*` at the same origin.
103
+ */
104
+ export function readApiBaseUrl(): string {
105
+ const explicitApiBaseUrl = process.env.NEXT_PUBLIC_API_AUTH_URL?.trim();
106
+
107
+ if (explicitApiBaseUrl) {
108
+ return explicitApiBaseUrl.replace(/\/$/, '');
109
+ }
110
+
111
+ const graphQlEndpoint = readGraphQlEndpoint();
112
+
113
+ return resolveApiBaseUrlFromGraphQlEndpoint(graphQlEndpoint);
114
+ }
115
+
116
+ export function resolveApiBaseUrlFromGraphQlEndpoint(
117
+ graphQlEndpoint: string,
118
+ ): string {
119
+ if (!isAbsoluteUrl(graphQlEndpoint)) {
120
+ return '';
121
+ }
122
+
123
+ const endpoint = new URL(graphQlEndpoint);
124
+ endpoint.pathname = '/';
125
+ endpoint.search = '';
126
+ endpoint.hash = '';
127
+ return endpoint.toString().replace(/\/$/, '');
128
+ }
129
+
130
+ async function requestApi<TData>(
131
+ path: string,
132
+ init?: RequestInit,
133
+ ): Promise<TData> {
134
+ const response = await fetch(`${readApiBaseUrl()}${path}`, {
135
+ credentials: 'include',
136
+ ...init,
137
+ });
138
+
139
+ if (!response.ok) {
140
+ throw new ApiRequestError(response.status, await readError(response));
141
+ }
142
+
143
+ return (await response.json()) as TData;
144
+ }
145
+
146
+ async function readError(response: Response): Promise<string> {
147
+ try {
148
+ const payload = (await response.json()) as { readonly message?: unknown };
149
+
150
+ return typeof payload.message === 'string'
151
+ ? payload.message
152
+ : `BPM API request failed with HTTP ${response.status}`;
153
+ } catch {
154
+ return `BPM API request failed with HTTP ${response.status}`;
155
+ }
156
+ }
157
+
158
+ class ApiRequestError extends Error {
159
+ constructor(
160
+ readonly status: number,
161
+ message: string,
162
+ ) {
163
+ super(message);
164
+ }
165
+ }
166
+
167
+ function isAbsoluteUrl(value: string): boolean {
168
+ return value.startsWith('http://') || value.startsWith('https://');
169
+ }
@@ -0,0 +1,81 @@
1
+ import { createFormDefinition, listFormDefinitions } from './form-api';
2
+
3
+ interface CapturedRequest {
4
+ readonly query: string;
5
+ readonly variables?: Readonly<Record<string, unknown>>;
6
+ }
7
+
8
+ function installFetchMock<TData>(payload: TData): {
9
+ readonly capture: () => CapturedRequest;
10
+ readonly restore: () => void;
11
+ } {
12
+ const originalFetch = global.fetch;
13
+ const fetchMock = jest.fn<
14
+ Promise<Response>,
15
+ [RequestInfo | URL, RequestInit?]
16
+ >();
17
+ fetchMock.mockImplementation(
18
+ async () =>
19
+ new Response(JSON.stringify({ data: payload }), {
20
+ headers: { 'Content-Type': 'application/json' },
21
+ status: 200,
22
+ }),
23
+ );
24
+ (global as { fetch: typeof fetch }).fetch =
25
+ fetchMock as unknown as typeof fetch;
26
+
27
+ return {
28
+ capture: (): CapturedRequest => {
29
+ expect(fetchMock).toHaveBeenCalledTimes(1);
30
+ const init = fetchMock.mock.calls[0]?.[1];
31
+ return JSON.parse(String(init?.body ?? '{}')) as CapturedRequest;
32
+ },
33
+ restore: (): void => {
34
+ (global as { fetch: typeof fetch }).fetch = originalFetch;
35
+ },
36
+ };
37
+ }
38
+
39
+ describe('@rytass/bpm-core-client/form', () => {
40
+ describe('listFormDefinitions', () => {
41
+ it('issues the FormDefinitions query and returns the array', async (): Promise<void> => {
42
+ const harness = installFetchMock({ formDefinitions: [] });
43
+ try {
44
+ const records = await listFormDefinitions();
45
+ const request = harness.capture();
46
+
47
+ expect(request.query).toContain('query FormDefinitions');
48
+ expect(request.variables).toBeUndefined();
49
+ expect(records).toEqual([]);
50
+ } finally {
51
+ harness.restore();
52
+ }
53
+ });
54
+ });
55
+
56
+ describe('createFormDefinition', () => {
57
+ it('wraps the name in a CreateFormDefinitionInput and returns the new id', async (): Promise<void> => {
58
+ const harness = installFetchMock({
59
+ createFormDefinition: { id: 'form-1' },
60
+ });
61
+ try {
62
+ const id = await createFormDefinition('Vacation request');
63
+ const request = harness.capture();
64
+
65
+ expect(request.query).toContain('mutation CreateFormDefinition');
66
+ expect(request.variables).toEqual({
67
+ input: {
68
+ createdByMemberId: null,
69
+ description: null,
70
+ name: 'Vacation request',
71
+ schemaJson: null,
72
+ uiSchemaJson: null,
73
+ },
74
+ });
75
+ expect(id).toBe('form-1');
76
+ } finally {
77
+ harness.restore();
78
+ }
79
+ });
80
+ });
81
+ });