@w6w/react 0.6.0 → 0.9.12

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
@@ -2,11 +2,13 @@
2
2
 
3
3
  React bindings for [`@w6w/sdk`](../node) — a `<W6WProvider>` that holds one memoized
4
4
  `W6WClient` with per-request token freshness, a small hook set over the SDK's public
5
- surface (`me`, `documents`, `vars`, `connections`, `workflows`, `run`), and
5
+ surface (`me`, `documents`, `vars`, `connections`, `workflows`, `functions`, `run`), and
6
6
  `createW6WUiAdapter`, a structural bridge from a `W6WClient` to
7
7
  [`@w6w/ui`](https://github.com/w6w-io/w6w-ui)'s `W6WApi` contract.
8
8
 
9
- License: MIT · Version: 0.3.0
9
+ License: MIT · Version: 0.9.12 · Guides: [docs.w6w.io/clients/react](https://docs.w6w.io/clients/react/)
10
+ (with a [hooks reference](https://docs.w6w.io/clients/react/hooks/) and a
11
+ [Next.js walkthrough](https://docs.w6w.io/clients/react/nextjs/))
10
12
 
11
13
  This lane implements no endpoint — it composes `@w6w/sdk`, which is already
12
14
  conformant against [`endpoints.json`](../endpoints.json). There is nothing here to
@@ -18,6 +20,10 @@ re-verify against the wire contract; verify `@w6w/sdk` instead.
18
20
  npm install @w6w/react react
19
21
  ```
20
22
 
23
+ `@w6w/sdk` comes in as a dependency. Add it to your own `package.json` too
24
+ (`npm install @w6w/sdk`) if you import from it directly — `ApiError` for an
25
+ `instanceof` check, or `exchangeToken` from `@w6w/sdk/server` on your backend.
26
+
21
27
  `react-dom` is not a dependency of this package — `<W6WProvider>`'s only JSX is a
22
28
  context wrapper (`<Ctx.Provider>`), never a DOM element of its own. Your app already
23
29
  depends on `react-dom` to mount itself; nothing extra is needed here.
@@ -38,11 +44,16 @@ function App() {
38
44
  }
39
45
  ```
40
46
 
41
- `token` accepts a literal string, or a supplier function called fresh on every
42
- request — pass a supplier when your token rotates (e.g. a short-lived JWT read from
43
- an auth SDK) and the client will pick up the new value on the very next call, with no
44
- teardown/rebuild of the underlying `W6WClient`. See `src/W6WProvider.tsx`'s module
45
- header for the exact mechanism (the C-4 shim).
47
+ `token` accepts a literal string, or a supplier — sync or **async** — called per
48
+ request (requests in flight at the same time share ONE supplier call; nothing is cached once
49
+ it settles, so memoize an expensive supplier yourself): `() => getCurrentJwt()` or `async () => await getCurrentJwt()` both
50
+ work, and either is awaited before the bearer is attached. Pass a supplier when your
51
+ token rotates (e.g. a short-lived JWT read from an auth SDK, or minted on demand by
52
+ your own backend) and the client picks up the new value on the very next call, with no
53
+ teardown/rebuild of the underlying `W6WClient`. A nullish or blank result (from either
54
+ form) never reaches the wire: every read hook below reports `loading: true` instead of
55
+ sending a request with no credential. See `src/W6WProvider.tsx`'s module header for the
56
+ exact mechanism.
46
57
 
47
58
  Then, anywhere under the provider:
48
59
 
@@ -61,6 +72,70 @@ function DocList() {
61
72
  }
62
73
  ```
63
74
 
75
+ ## Embedding for enterprise tenants
76
+
77
+ A host juggling more than one signed-in identity — or one that does not have a
78
+ token/identity yet at mount time — needs more than the Quick start's one-shot
79
+ `token` prop. `<W6WProvider>` takes five more props for exactly this:
80
+
81
+ - **`ready`** (default `true`) — set it `false` until you actually have a
82
+ token/identity to embed with. While `false`, every read hook reports
83
+ `loading: true` and none of them calls the SDK at all; flipping it back to
84
+ `true` fetches.
85
+ - **`identityKey`** — a string identifying WHICH signed-in identity this
86
+ client speaks for, e.g. `` `${subject}:${account}` ``. Changing it rebuilds
87
+ the underlying `W6WClient` and resets every read hook's state — the
88
+ supported way to switch accounts without a stale read from the previous
89
+ identity ever rendering under the new one.
90
+ - **`refreshOnUnauthorized`** + **`onUnauthorized`** — opt in to a single,
91
+ one-shot recovery when a request 401s with `{code: "unauthorized"}`: your
92
+ `token` supplier is called once more with `{forceRefresh: true}`, and a
93
+ usable result retries the same request once. `onUnauthorized` receives the
94
+ terminal error — after a failed retry, or immediately when recovery is off
95
+ or not possible — at most once per call.
96
+ - **`headers`** — default headers sent with every request. This is how a
97
+ tenant on the custom-authorizer integration path (no OIDC on your side)
98
+ attaches the tenant header your w6w account's authorizer webhook expects
99
+ (e.g. `headers={{ "X-W6W-Tenant": tenantId }}`) alongside the bearer token.
100
+ Compared by content, not identity — a new-but-equal object on every render
101
+ never rebuilds the client.
102
+
103
+ ```tsx
104
+ "use client";
105
+
106
+ import { W6WProvider } from "@w6w/react";
107
+
108
+ export default function W6WLayout({ children }: { children: React.ReactNode }) {
109
+ return (
110
+ <W6WProvider
111
+ baseUrl="https://api.example.com"
112
+ ready={true}
113
+ identityKey={`${session.subject}:${session.account}`}
114
+ headers={{ "X-W6W-Tenant": session.tenantId }}
115
+ refreshOnUnauthorized
116
+ onUnauthorized={() => redirectToLogin()}
117
+ token={async () => {
118
+ // Your own backend route, not w6w's — it holds whatever credential
119
+ // mints a w6w token for this signed-in user (a tenant exchange, a
120
+ // cached short-lived token, …). This is a Next.js App Router layout,
121
+ // so it is a Client Component ("use client" above) even though the
122
+ // token-minting route itself runs on your server.
123
+ const res = await fetch("/api/w6w-token");
124
+ if (!res.ok) return null;
125
+ const { token } = await res.json();
126
+ return token;
127
+ }}
128
+ >
129
+ {children}
130
+ </W6WProvider>
131
+ );
132
+ }
133
+ ```
134
+
135
+ The [Next.js walkthrough](https://docs.w6w.io/clients/react/nextjs/) builds the
136
+ `/api/w6w-token` route this example fetches from, with `exchangeToken` and a
137
+ cached, refreshable token supplier.
138
+
64
139
  ## Using this package with `@w6w/ui`
65
140
 
66
141
  `@w6w/ui`'s components (e.g. `AppPicker`, `ActionTestForm`, the flow editor) take a
@@ -68,6 +143,7 @@ function DocList() {
68
143
  `@w6w/react` client with `createW6WUiAdapter`:
69
144
 
70
145
  ```tsx
146
+ import { useMemo } from "react";
71
147
  import { createW6WUiAdapter, useW6WClient, W6WProvider } from "@w6w/react";
72
148
  import { W6WUIProvider, AppPicker } from "@w6w/ui";
73
149
 
@@ -88,23 +164,86 @@ function App() {
88
164
  }
89
165
  ```
90
166
 
91
- **`@w6w/ui` is not on npm today.** The `@w6w` scope holds only `@w6w/sdk` and
92
- `@w6w/cli` at the time of writing (`npm view @w6w/ui` → 404); the source itself IS a
93
- public GitHub repository (`w6w-io/w6w-ui`). `createW6WUiAdapter`'s `W6WApi` return
94
- type targets that contract *structurally* — this package names `@w6w/ui` nowhere in
95
- its own manifest and needs no change whenever `@w6w/ui` becomes reachable another
96
- way (this monorepo, a `git+https://` dependency on the public repo, or a private
97
- registry). This is an honest statement of today's install story, not a promise that
98
- `npm i @w6w/ui` resolves.
99
-
100
- `createW6WUiAdapter` is built entirely on `@w6w/sdk/console` — the SAME namespace
101
- `packages/studio`'s own facade uses for these routes (`packages/ui/src/createW6WApi.ts`
102
- is the *other* hand-rolled client for them; this package is not a third one).
103
- **`client.console.*` is documented "Studio-internal… unstable" and is deliberately
104
- excluded from `endpoints.json`'s conformance runner** (`node/src/client.ts:114-116`).
105
- That means a `console.*` signature change ships with no lockstep protection for this
106
- bridge beyond `@w6w/sdk`'s own version — pin your `@w6w/sdk` version alongside
107
- `@w6w/react`'s, and re-test the bridge on an upgrade rather than assuming it.
167
+ `@w6w/ui@0.1.0` is available on npm. Install it with `npm i @w6w/ui`.
168
+ `createW6WUiAdapter` targets its `W6WApi` contract structurally and has no runtime
169
+ dependency on the UI package.
170
+
171
+ `createW6WUiAdapter` is built on `@w6w/sdk/console` for most members — the SAME
172
+ namespace `packages/studio`'s own facade uses for these routes
173
+ (`packages/ui/src/createW6WApi.ts` is the *other* hand-rolled client for them; this
174
+ package is not a third one) — and on the BASE `client.functions.*`/
175
+ `client.workflows.*` surface for `listFunctions`/`getFunction`/`invokeFunction`/
176
+ `listWorkflows`/`getWorkflow`/`runWorkflow`. `listAppsByIds` and `listConnections` go through
177
+ the public `client.apps.byIds` and `client.connections.list`. **The adapter is the supported
178
+ seam:** hand `createW6WUiAdapter(client)` to the UI as it is, and do not reach for the
179
+ console-backed internals or patch members onto it yourself. **`client.console.*` is documented
180
+ "Studio-internal… unstable" and is deliberately excluded from `endpoints.json`'s
181
+ conformance runner** (`node/src/client.ts:114-116`). That means a `console.*`
182
+ signature change ships with no lockstep protection for this bridge beyond
183
+ `@w6w/sdk`'s own version — pin your `@w6w/sdk` version alongside `@w6w/react`'s, and
184
+ re-test the bridge on an upgrade rather than assuming it.
185
+
186
+ ### Keeping `createW6WUiAdapter` in sync with the real `@w6w/ui`
187
+
188
+ `package.json` pins `@w6w/ui` to npm version `0.1.0` as a dev dependency.
189
+ Consumers of `@w6w/react` do not install that dependency automatically; it is used
190
+ only by the conformance checks. The release workflow can install it in a clean
191
+ checkout without sibling repositories.
192
+
193
+ `src/__tests__/ui-conformance.check.ts` imports `W6WApi` and `FlowWorkflow` from
194
+ the UI package's public exports. It checks the adapter's return type and the
195
+ workflow converter in both directions. The runtime suite reads the installed
196
+ `dist/provider.d.ts` declaration to enumerate the real interface members.
197
+
198
+ To update the pin, change the npm version in `package.json`, refresh
199
+ `package-lock.json`, and run `npm test` and `npm run build`. These checks report
200
+ any incompatible UI contract changes.
201
+
202
+ **Runtime conformance.** Assignability is checked at compile time only, so it cannot
203
+ see a member that is typed but missing or mis-wired at run time.
204
+ `src/__tests__/adapter-conformance.test.ts` closes that gap. It reads the member names
205
+ (21 required, 8 optional) from the installed `@w6w/ui` `provider.d.ts` declaration — not from
206
+ this package's hand-copied interface — and asserts each is a function on
207
+ `createW6WUiAdapter(client)`. It then drives every member with sentinel arguments
208
+ through a stub transport and asserts the exact wire request produced (forwarding), and
209
+ asserts that a 4xx JSON error body reaches the caller as `err.body`, deep-equal to
210
+ what the server sent. Nothing in it imports `@w6w/ui` at run time; the pinned UI is
211
+ read as text.
212
+
213
+ `toFlowWorkflow` does not validate `steps[].uses`, although the returned type declares it
214
+ required; a step without `uses` converts, so guard before handing it to code that reads
215
+ `step.uses.app`.
216
+
217
+ ### Editing workflows with a typed shape
218
+
219
+ `@w6w/sdk` keeps a workflow definition opaque (`WorkflowDefinition`, a
220
+ `Record<string, unknown>`) on purpose, so a newer server's extra fields never break a
221
+ client. The visual editor wants a typed shape, so this package exports a validating
222
+ converter instead of a cast:
223
+
224
+ ```ts
225
+ import { fromFlowWorkflow, toFlowWorkflow, WorkflowShapeError } from "@w6w/react";
226
+
227
+ const { workflow } = await client.workflows.get(id);
228
+ try {
229
+ const flow = toFlowWorkflow(workflow); // FlowWorkflowShape
230
+ // ... hand `flow` to the editor, receive an edited copy back ...
231
+ await client.workflows.update(id, fromFlowWorkflow(edited));
232
+ } catch (err) {
233
+ if (err instanceof WorkflowShapeError) console.error(err.issues); // [{ path, message }, ...]
234
+ }
235
+ ```
236
+
237
+ `toFlowWorkflow` requires string `manifestVersion`/`id`/`name` and a `steps` array of
238
+ objects with a string `id` (plus `edges` an array, `settings` an object, `variables`
239
+ an array, when present); anything else throws `WorkflowShapeError` whose `issues` lists
240
+ every violation. It does not inspect the inside of `steps[].uses` or any other nested
241
+ field. Unknown keys (`retry`, `onError`, `ports`, a host's `x-*` extension) are copied
242
+ through in both directions, so `fromFlowWorkflow(toFlowWorkflow(def))` deep-equals
243
+ `def`. `FlowWorkflowShape` is a structural mirror of `@w6w/ui`'s `FlowWorkflow`, with no
244
+ runtime `@w6w/ui` dependency; `ui-conformance.check.ts` pins it both ways (ui's
245
+ `FlowWorkflow` is accepted by `fromFlowWorkflow`, and the mirror is assignable to ui's
246
+ `FlowWorkflow`).
108
247
 
109
248
  ## Hooks catalog
110
249
 
@@ -112,6 +251,11 @@ Every read hook returns `{data, error, loading, refetch}`; every mutation hook
112
251
  returns `{call, data, error, loading}`. None depends on react-query or any other
113
252
  data-fetching library.
114
253
 
254
+ Read hooks asking for the same thing (same hook, same arguments, same provider) while a
255
+ request is in flight share ONE request — two consumers, or React StrictMode's
256
+ mount/unmount/mount, send a single fetch. Nothing is kept once it settles: a hook mounting
257
+ later sends a new request, and `refetch()` starts a fresh one.
258
+
115
259
  | Hook | Wraps |
116
260
  |---|---|
117
261
  | `useMe()` | `client.me()` |
@@ -128,15 +272,52 @@ data-fetching library.
128
272
  | `useDeleteVar()` | `client.vars.delete()` |
129
273
  | `useConnections()` | `client.connections.list()` |
130
274
  | `useWorkflows(options?)` | `client.workflows.list()` |
275
+ | `useWorkflow(id)` | `client.workflows.get()` |
276
+ | `useCreateWorkflow()` | `client.workflows.create()` |
277
+ | `useUpdateWorkflow()` | `client.workflows.update()` |
278
+ | `useArchiveWorkflow()` | `client.workflows.archive()` |
279
+ | `useDeleteWorkflow()` | `client.workflows.delete()` |
131
280
  | `useRunWorkflow()` | `client.workflows.run()`, defaults `wait: true` |
281
+ | `useFunctions()` | `client.functions.list()` |
282
+ | `useFunction(id)` | `client.functions.get()` |
283
+ | `useCreateFunction()` | `client.functions.create()` |
284
+ | `useUpdateFunction()` | `client.functions.update()` |
285
+ | `useDeleteFunction()` | `client.functions.delete()` |
132
286
  | `useRun()` | `client.run()` (the generic URN dispatch) |
133
287
 
288
+ Three things the definition hooks inherit from the SDK rather than invent:
289
+
290
+ - **`create` mints the id.** The server requires one in the body and never
291
+ generates it, so `useCreateWorkflow().call({name, steps})` works and the
292
+ `wf_…` / `fn_…` id comes back in the result.
293
+ - **`update` is a full replacement, not a patch.** This is the one place they
294
+ differ from `useUpdateDocument`/`useUpdateVar`, whose second argument is a
295
+ patch of just the fields to change. Read with `useWorkflow`/`useFunction`,
296
+ spread, change, send the whole thing back — passing only the changed fields
297
+ deletes the rest. `useUpdateWorkflow()`'s third argument carries
298
+ `ifUnmodifiedSince`: hand it the `updatedAt` from `useWorkflow` and a save
299
+ that would clobber someone else's edit is refused with `409 workflow_stale`
300
+ rather than winning silently.
301
+ - **Deleting a workflow is two calls**, `useArchiveWorkflow` then
302
+ `useDeleteWorkflow`. Nothing chains them for you: the archive step is the one
303
+ a user can still change their mind after.
304
+
305
+ No mutation hook refetches a list on success — this package carries no cache to
306
+ invalidate, so pair one with the matching read hook's own `refetch`.
307
+
308
+ `client.functions.run()` has **no** hook, and neither does `endpoints.run()` —
309
+ that predates the definition hooks and is unchanged by them. `useRun()` reaches
310
+ a Function by its `fn_…` id today (the server dispatches it through the same
311
+ service the dedicated invoke route uses); calling one by the `key` a user gave
312
+ it still means `useW6WClient().functions.run("send-email", …)`.
313
+
134
314
  `useRunWorkflow()`'s `call` defaults `wait: true` when the caller's options omit
135
315
  `wait`: `workflows.run` without `wait` (`node/src/workflows.ts`) leaves the caller
136
316
  with only a `runId` and no public polling operation to follow it with —
137
317
  `console.workflows.getRun` exists, but it is the same studio-internal, unstable
138
318
  surface described above, so this hook does not reach for it. Pass `wait: false`
139
- explicitly if you genuinely want the queued-and-walk-away behaviour.
319
+ explicitly if you want the queued-and-walk-away behaviour, and follow the run
320
+ with `` useW6WClient().request({ method: "GET", path: path`/runs/${runId}` }) ``.
140
321
 
141
322
  `useW6WClient()` returns the underlying `W6WClient` for anything not covered by a
142
323
  hook (e.g. `client.console.*` directly, at your own risk per the caveat above).
@@ -151,22 +332,11 @@ hook (e.g. `client.console.*` directly, at your own risk per the caveat above).
151
332
  fine — every extra field is optional — and there is no runtime data loss (the JSON
152
333
  payload is unfiltered); a caller typing a variable through this adapter's declared
153
334
  return type just gets no autocomplete for those extra fields.
154
- - **`ActionTestForm.tsx:149,176`'s error handling cannot be satisfied from this
155
- package.** `@w6w/ui`'s `ActionTestForm` does a NOMINAL `instanceof ApiError` check
156
- against its OWN `ApiError` class (`packages/ui/src/createW6WApi.ts`), not a
157
- duck-typed one. No error object this adapter throws can ever satisfy that check
158
- without importing `@w6w/ui`'s class directly, which this package's MIT/C-1
159
- boundary forbids. Concretely: on a failed `invokeAction` used together with
160
- `@w6w/ui`'s `ActionTestForm`, the 401/403 permission-hint messaging falls back to a
161
- generic message, and the `ApiCallsPanel` egress log renders empty instead of
162
- showing the outbound calls the action made — even though this adapter's thrown
163
- error genuinely carries that data on `.body`/`.raw`. This adapter still aliases
164
- `@w6w/sdk`'s `ApiError.raw` onto `.body` (`err.body === err.raw`) and sets
165
- `err.name = "ApiError"`, so it is already correct for any DUCK-TYPING consumer, and
166
- needs no further change the moment `@w6w/ui` switches its own check to one. The fix
167
- belongs to `@w6w/ui`, not this package, and is filed there:
168
- `.ai/projects/backlog/26-08-13-01-ui-error-nominal-check.md`.
169
- - **No `AbortSignal`/cancellation support in the hook set.** `@w6w/sdk`'s transport
170
- (`node/src/http.ts`) has nothing to hook an abort into. A read hook only IGNORES a
171
- result that resolves after its component unmounts (a mounted-ref guard); it cannot
172
- cancel the in-flight request itself.
335
+ - **Cancellation is per-hook, not exposed to the caller.** Every read hook aborts its
336
+ own superseded or unmounted calls internally (a superseded call, an `identityKey`
337
+ switch, or an unmount all abort the in-flight `AbortSignal` — but a shared request
338
+ aborts only when its LAST subscriber detaches, so StrictMode's remount or a second
339
+ consumer re-attaches instead of aborting it — see `src/hooks.ts`'s
340
+ module header) — but there is no option to pass your OWN `AbortSignal` into a hook
341
+ from outside it. `useW6WClient()` plus the underlying SDK method (which does take a
342
+ `signal`) is the escape hatch if you need that.
package/dist/mod.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * `@w6w/react` — the package barrel.
3
3
  *
4
- * A thin React binding over `@w6w/sdk`: `<W6WProvider>` (the C-4
5
- * token-supplier shim), a small hook set, and `createW6WUiAdapter` (the
4
+ * A thin React binding over `@w6w/sdk`: `<W6WProvider>` (with its native token
5
+ * supplier), a small hook set, and `createW6WUiAdapter` (the
6
6
  * `@w6w/ui` `W6WApi` structural bridge, C-1/C-2). See `README.md` for the
7
7
  * install + wrap-your-root example, the hooks catalog, and this lane's
8
8
  * documented limitations.
@@ -14,6 +14,7 @@
14
14
  */
15
15
  export { VERSION } from "./src/version.ts";
16
16
  export { type TokenSource, useW6WClient, W6WProvider, type W6WProviderProps, } from "./src/W6WProvider.tsx";
17
- export { type MutationResult, type ReadResult, useConnections, useCreateDocument, useCreateVar, useDeleteDocument, useDeleteVar, useDocument, useDocumentByKey, useDocuments, useMe, useRun, useRunWorkflow, useUpdateDocument, useUpdateVar, useVar, useVars, useWorkflows, } from "./src/hooks.ts";
17
+ export { type MutationResult, type ReadResult, useArchiveWorkflow, useConnections, useCreateDocument, useCreateFunction, useCreateVar, useCreateWorkflow, useDeleteDocument, useDeleteFunction, useDeleteVar, useDeleteWorkflow, useDocument, useDocumentByKey, useDocuments, useFunction, useFunctions, useMe, useRun, useRunWorkflow, useUpdateDocument, useUpdateFunction, useUpdateVar, useUpdateWorkflow, useVar, useVars, useWorkflow, useWorkflows, } from "./src/hooks.ts";
18
18
  export { createW6WUiAdapter, type W6WApi } from "./src/adapter.ts";
19
+ export { type FlowEdgeShape, type FlowStepShape, type FlowWorkflowShape, fromFlowWorkflow, toFlowWorkflow, WorkflowShapeError, type WorkflowShapeIssue, } from "./src/flow-workflow.ts";
19
20
  //# sourceMappingURL=mod.d.ts.map
package/dist/mod.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,OAAO,EACL,KAAK,WAAW,EAChB,YAAY,EACZ,WAAW,EACX,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,KAAK,EACL,MAAM,EACN,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,OAAO,EACP,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../mod.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,OAAO,EACL,KAAK,WAAW,EAChB,YAAY,EACZ,WAAW,EACX,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,KAAK,EACL,MAAM,EACN,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,MAAM,EACN,OAAO,EACP,WAAW,EACX,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAC"}
package/dist/mod.js CHANGED
@@ -1,8 +1,9 @@
1
+ "use client";
1
2
  /**
2
3
  * `@w6w/react` — the package barrel.
3
4
  *
4
- * A thin React binding over `@w6w/sdk`: `<W6WProvider>` (the C-4
5
- * token-supplier shim), a small hook set, and `createW6WUiAdapter` (the
5
+ * A thin React binding over `@w6w/sdk`: `<W6WProvider>` (with its native token
6
+ * supplier), a small hook set, and `createW6WUiAdapter` (the
6
7
  * `@w6w/ui` `W6WApi` structural bridge, C-1/C-2). See `README.md` for the
7
8
  * install + wrap-your-root example, the hooks catalog, and this lane's
8
9
  * documented limitations.
@@ -14,6 +15,7 @@
14
15
  */
15
16
  export { VERSION } from "./src/version.js";
16
17
  export { useW6WClient, W6WProvider, } from "./src/W6WProvider.js";
17
- export { useConnections, useCreateDocument, useCreateVar, useDeleteDocument, useDeleteVar, useDocument, useDocumentByKey, useDocuments, useMe, useRun, useRunWorkflow, useUpdateDocument, useUpdateVar, useVar, useVars, useWorkflows, } from "./src/hooks.js";
18
+ export { useArchiveWorkflow, useConnections, useCreateDocument, useCreateFunction, useCreateVar, useCreateWorkflow, useDeleteDocument, useDeleteFunction, useDeleteVar, useDeleteWorkflow, useDocument, useDocumentByKey, useDocuments, useFunction, useFunctions, useMe, useRun, useRunWorkflow, useUpdateDocument, useUpdateFunction, useUpdateVar, useUpdateWorkflow, useVar, useVars, useWorkflow, useWorkflows, } from "./src/hooks.js";
18
19
  export { createW6WUiAdapter } from "./src/adapter.js";
20
+ export { fromFlowWorkflow, toFlowWorkflow, WorkflowShapeError, } from "./src/flow-workflow.js";
19
21
  //# sourceMappingURL=mod.js.map
package/dist/mod.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.js","sourceRoot":"","sources":["../mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,OAAO,EAEL,YAAY,EACZ,WAAW,GAEZ,MAAM,sBAAuB,CAAC;AAE/B,OAAO,EAGL,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,KAAK,EACL,MAAM,EACN,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,OAAO,EACP,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAe,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"mod.js","sourceRoot":"","sources":["../mod.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,OAAO,EAEL,YAAY,EACZ,WAAW,GAEZ,MAAM,sBAAuB,CAAC;AAE/B,OAAO,EAGL,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,KAAK,EACL,MAAM,EACN,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,MAAM,EACN,OAAO,EACP,WAAW,EACX,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAe,MAAM,kBAAkB,CAAC;AAEnE,OAAO,EAIL,gBAAgB,EAChB,cAAc,EACd,kBAAkB,GAEnB,MAAM,wBAAwB,CAAC"}
@@ -1,62 +1,104 @@
1
1
  /**
2
- * `<W6WProvider>` — one `W6WClient` per `baseUrl`, with per-request token
3
- * freshness (C-4).
2
+ * `<W6WProvider>` — one `W6WClient` per `{baseUrl, project, identityKey,
3
+ * refreshOnUnauthorized, headers}` combination, with the token resolved per
4
+ * request (concurrent requests share one supplier call).
4
5
  *
5
- * `@w6w/sdk`'s `W6WClientOptions.token` (`node/src/config.ts:41-63`) is
6
- * resolved ONCE, at construction, into `readonly` instance state
7
- * (`node/src/client.ts:59`) — there is no supplier form. A naive
8
- * `new W6WClient({ token })` therefore captures whatever `token` was at mount
9
- * time and never sees a later rotation.
6
+ * `@w6w/sdk`'s `W6WClientOptions.token` (`node/src/config.ts:41-63`) accepts
7
+ * either a plain string or a {@linkcode TokenProvider} function — the
8
+ * function form is called (and its result awaited) per request — requests in
9
+ * flight at the same time share ONE call, nothing is cached once it settles,
10
+ * and it is never resolved once at construction. This provider hands the
11
+ * SDK a single STABLE function that reads the CURRENT `token` prop through a
12
+ * ref: a string prop is returned as-is, a function prop is called with
13
+ * whatever `ctx` the SDK forwards (including `{forceRefresh: true}` on its
14
+ * own one-shot 401 recovery retry, `node/src/http.ts`). `fetch` and
15
+ * `onUnauthorized` are the same shape — stable wrappers over "latest" refs —
16
+ * so neither a token rotation, a new `fetch` identity, nor a new
17
+ * `onUnauthorized` closure identity ever rebuilds the client. A nullish or
18
+ * blank token (sync or async) never reaches the wire: the SDK's own
19
+ * `requireToken` raises a local {@linkcode ConfigError} before any `fetch`
20
+ * call, which is the floor `hooks.ts`'s `useAsync` reports as `loading`, not
21
+ * `error`.
10
22
  *
11
- * The fix lives at the `fetch`-injection seam, not the `token` seam.
12
- * `node/src/http.ts`'s `request()` builds `headers = new Headers(options.headers)`
13
- * and then unconditionally `headers.set("authorization", ...)` BEFORE calling
14
- * the injected `fetchImpl(url, {method, headers, body})` — `Headers.set`
15
- * REPLACES, so a custom `fetch` passed to `new W6WClient({ fetch })` can read
16
- * the CURRENT token and overwrite `authorization` on the `Headers` object it
17
- * receives, immediately before delegating to the real transport. This
18
- * provider therefore constructs its `W6WClient` ONCE, with a non-blank
19
- * placeholder token (any non-blank string satisfies `requireToken`,
20
- * `config.ts:229-237` — it is never actually sent, since the injected `fetch`
21
- * always overwrites it first) and an injected `fetch` that resolves the
22
- * current `token` prop (calling it, if it is a supplier) on every call.
23
+ * **What DOES rebuild the client** (the `useMemo` below): `baseUrl`,
24
+ * `project`, `identityKey`, `refreshOnUnauthorized`, and a canonical STRING of
25
+ * `headers`' sorted entries — never the `headers` object's own identity (an
26
+ * inline literal would rebuild the client on every render) and never
27
+ * `token`. `identityKey` (e.g. `${subject}:${account}`) is the one supported
28
+ * way to force a rebuild on purpose, for a host juggling more than one
29
+ * signed-in identity: every hook built on `useAsync` (`hooks.ts`) resets its
30
+ * state to `{data: undefined, error: undefined, loading: true}` when the
31
+ * client it reads through changes, aborting whatever it had in flight first —
32
+ * a stale account's data is never shown under a new one.
23
33
  *
24
- * **Do not key the outer `useMemo` on `token`.** `packages/studio/src/api/apiClient.tsx`
25
- * rebuilds its client whenever the token STRING changes — the right policy for
26
- * studio's own state-variable token, and the wrong one here: a supplier
27
- * function has a stable identity across renders, so keying on the supplier
28
- * would never rebuild AND never read a fresh value, while keying on the
29
- * token's resolved value would tear the client down and rebuild it on every
30
- * rotation — exactly what C-4 says not to do ("without tearing down"). The
31
- * memo below depends on `[baseUrl, project]` only; `token` and `fetch` are
32
- * read through "latest" refs, updated every render but never a dependency, so
33
- * a rotating token is read fresh per request without ever rebuilding the
34
- * client.
34
+ * `ready` (default `true`) gates every READ hook, never a mutation: while
35
+ * `false`, a read hook reports `loading: true` and never calls the SDK at
36
+ * all. Flipping it to `true` (or changing `identityKey`) fetches.
35
37
  *
36
38
  * @module
37
39
  */
38
- import { type FetchLike, W6WClient } from "@w6w/sdk";
40
+ import { type ApiError, type FetchLike, type TokenProvider, W6WClient } from "@w6w/sdk";
39
41
  import { type ReactNode } from "react";
40
- /** A static bearer token, or a supplier read fresh on every request. */
41
- export type TokenSource = string | (() => string | null | undefined);
42
+ /** A static bearer token, or a supplier called per request (concurrent requests share one call). */
43
+ export type TokenSource = string | TokenProvider;
42
44
  export interface W6WProviderProps {
43
45
  /** The w6w server's origin, e.g. `"https://api.example.com"`. */
44
46
  baseUrl: string;
45
- /** A literal bearer token, or a supplier called fresh on every request. */
47
+ /** A literal bearer token, or a supplier called per request (concurrent requests share one call). */
46
48
  token: TokenSource;
47
49
  /**
48
- * The real transport this provider's injected `fetch` delegates to, after
49
- * overwriting the `authorization` header. Defaults to `globalThis.fetch`.
50
- * Mainly for tests — a caller wanting to record or stub outbound requests.
50
+ * The real transport this provider's stable `fetch` wrapper delegates to.
51
+ * Defaults to `globalThis.fetch`. Mainly for tests — a caller wanting to
52
+ * record or stub outbound requests.
51
53
  */
52
54
  fetch?: FetchLike;
53
55
  /** Default project id for the project-scoped operations (`documents.*`, `workflows.list`). */
54
56
  project?: string;
57
+ /**
58
+ * Default headers sent with every request — e.g. `{"X-W6W-Tenant": "t1"}`
59
+ * for a Path 3 tenant binding. Compared by CONTENT (sorted entries), not by
60
+ * object identity: a new-but-equal `headers` object on every render never
61
+ * rebuilds the client.
62
+ */
63
+ headers?: Record<string, string>;
64
+ /**
65
+ * Gates every read hook. `false` (default `true`) means "I don't have a
66
+ * token/identity yet": no read hook calls the SDK, and every one reports
67
+ * `loading: true` until this flips — or forever, if it never does.
68
+ */
69
+ ready?: boolean;
70
+ /**
71
+ * Identifies WHICH signed-in identity this client speaks for. Changing it
72
+ * (e.g. `${subject}:${account}`) rebuilds the `W6WClient` and resets every
73
+ * read hook's state — the one supported way to switch accounts without a
74
+ * stale read from the previous identity ever rendering under the new one.
75
+ */
76
+ identityKey?: string;
77
+ /**
78
+ * Opt in to a single, one-shot recovery when a request 401s with
79
+ * `{code: "unauthorized"}`: the `token` supplier is called once more with
80
+ * `{forceRefresh: true}`, and a usable result retries the same request
81
+ * once. See `@w6w/sdk`'s `W6WClientOptions.refreshOnUnauthorized`.
82
+ */
83
+ refreshOnUnauthorized?: boolean;
84
+ /**
85
+ * Called with the terminal `401`/`unauthorized` error — after a failed
86
+ * recovery retry, or immediately when recovery is off or not possible — at
87
+ * most once per call. Always the LATEST `onUnauthorized` prop, read through
88
+ * a ref like `token`/`fetch`.
89
+ */
90
+ onUnauthorized?: (error: ApiError) => void;
55
91
  children: ReactNode;
56
92
  }
93
+ interface W6WContextValue {
94
+ client: W6WClient;
95
+ /** Mirrors the `ready` prop. Read by `hooks.ts`'s `useAsync`, never by `useW6WClient`. */
96
+ ready: boolean;
97
+ }
57
98
  /**
58
- * Provides one memoized `W6WClient` to every component under it. See this
59
- * module's header for the C-4 token-freshness mechanism.
99
+ * Provides one memoized `W6WClient` (plus `ready`) to every component under
100
+ * it. See this module's header for the token-freshness mechanism and the
101
+ * memoization policy.
60
102
  */
61
103
  export declare function W6WProvider(props: W6WProviderProps): ReactNode;
62
104
  /**
@@ -65,4 +107,12 @@ export declare function W6WProvider(props: W6WProviderProps): ReactNode;
65
107
  * `useW6WApi` shape — read-only reference, transcribed here, never imported).
66
108
  */
67
109
  export declare function useW6WClient(): W6WClient;
110
+ /**
111
+ * Internal: the context value including `ready`, consumed only by the read
112
+ * hooks in `hooks.ts` (via a relative import — never re-exported from
113
+ * `mod.ts`). `useW6WClient`'s `W6WClient` return type is the public surface;
114
+ * this is the one extra bit every read hook needs to gate on.
115
+ */
116
+ export declare function useW6WProviderContext(): W6WContextValue;
117
+ export {};
68
118
  //# sourceMappingURL=W6WProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"W6WProvider.d.ts","sourceRoot":"","sources":["../../src/W6WProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,KAAK,SAAS,EAA8C,MAAM,OAAO,CAAC;AAEnF,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAUrE,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,KAAK,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAID;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,SAAS,CA8B9D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,SAAS,CASxC"}
1
+ {"version":3,"file":"W6WProvider.d.ts","sourceRoot":"","sources":["../../src/W6WProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxF,OAAO,EAAE,KAAK,SAAS,EAA8C,MAAM,OAAO,CAAC;AAEnF,oGAAoG;AACpG,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,aAAa,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,qGAAqG;IACrG,KAAK,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC3C,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,SAAS,CAAC;IAClB,0FAA0F;IAC1F,KAAK,EAAE,OAAO,CAAC;CAChB;AAeD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAqE9D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,SAAS,CASxC;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CASvD"}