@rytass/bpm-core-client 0.1.2 → 0.1.4

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 (2) hide show
  1. package/README.md +113 -3
  2. package/package.json +2 -2
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rytass/bpm-core-client",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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,7 +73,7 @@
73
73
  }
74
74
  },
75
75
  "peerDependencies": {
76
- "@rytass/bpm-core-shared": "^0.1.0"
76
+ "@rytass/bpm-core-shared": "^0.1.4"
77
77
  },
78
78
  "dependencies": {
79
79
  "tslib": "^2.3.0"