@w6w/react 0.5.0 → 0.9.9

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.8 · 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,55 @@ 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` is not on npm today** (`npm view @w6w/ui` → 404, checked 2026-10-06);
168
+ the source is a public GitHub repository (`w6w-io/w6w-ui`). `createW6WUiAdapter`'s
169
+ `W6WApi` return type targets that contract *structurally* — this package needs no
170
+ change whenever `@w6w/ui` becomes reachable another way (a `git+https://`
171
+ dependency on the public repo, or a private registry). `npm i @w6w/ui` does not
172
+ resolve yet.
173
+
174
+ `createW6WUiAdapter` is built on `@w6w/sdk/console` for most members — the SAME
175
+ namespace `packages/studio`'s own facade uses for these routes
176
+ (`packages/ui/src/createW6WApi.ts` is the *other* hand-rolled client for them; this
177
+ package is not a third one) — and on the BASE `client.functions.*`/
178
+ `client.workflows.*` surface for `listFunctions`/`getFunction`/`invokeFunction`/
179
+ `listWorkflows`/`getWorkflow`/`runWorkflow`. **`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`'s `devDependencies` carries a **dev-only, type-only** edge onto
189
+ `@w6w/ui` — `"@w6w/ui": "github:w6w-io/w6w-ui#<sha>"` — that this package never
190
+ ships: `files: ["dist"]` means `dist/` is the only thing npm packs and publishes, so
191
+ no installer of `@w6w/react` ever resolves or needs `@w6w/ui` at all. Its one job is
192
+ `src/__tests__/ui-conformance.test.ts`, which runs at **dev/CI time only** (`npm
193
+ test`, never part of `dist/`): it compiles `src/__tests__/ui-conformance.check.ts`
194
+ against `@w6w/ui`'s real `provider.tsx` and fails, naming the member, the moment
195
+ `createW6WUiAdapter`'s return type stops being assignable to the real `W6WApi` —
196
+ closing the gap a hand-duplicated interface alone cannot (nothing previously caught
197
+ this package's `W6WApi` drifting from `@w6w/ui`'s own).
198
+
199
+ **Why a `github:<owner>/<repo>#<sha>` pin, and not a `link:`/workspace reference to
200
+ a sibling checkout:** `release.yml`'s `react` step checks out **this one repo**
201
+ (`w6w-wrappers`) with no sibling checkout and no submodules — a relative `link:` to `../../ui` resolves only in the
202
+ local devcontainer's incidental directory layout and fails `ENOENT` on every CI run
203
+ and on a fresh clone of `w6w-wrappers` alone. A `github:` spec needs nothing but a
204
+ network fetch of the one pinned commit, which is why it is the only install story
205
+ that survives both environments unchanged. The pin targets `provider.tsx` by
206
+ RELATIVE PATH, not the `@w6w/ui` package barrel — the barrel's own import chain
207
+ reaches `@w6w/expr` (`packages/core`) via a `github:…#path:` subpath npm does not
208
+ honor on a `github:` dependency, which `TS2307`s the moment anything resolves it;
209
+ `provider.tsx`'s own closure (`theme.ts` → `types.ts` → `react`) has no such edge.
210
+
211
+ **Moving the pin:** update the `#<sha>` in `package.json`'s `@w6w/ui` entry to a
212
+ commit on `w6w-io/w6w-ui`'s `main` (`git ls-remote https://github.com/w6w-io/w6w-ui
213
+ main`) and run `npm install` again to refresh `package-lock.json`'s resolved
214
+ entry — no code change is required unless `@w6w/ui`'s `W6WApi` itself changed
215
+ shape, in which case `ui-conformance.test.ts` will say so.
108
216
 
109
217
  ## Hooks catalog
110
218
 
@@ -112,6 +220,11 @@ Every read hook returns `{data, error, loading, refetch}`; every mutation hook
112
220
  returns `{call, data, error, loading}`. None depends on react-query or any other
113
221
  data-fetching library.
114
222
 
223
+ Read hooks asking for the same thing (same hook, same arguments, same provider) while a
224
+ request is in flight share ONE request — two consumers, or React StrictMode's
225
+ mount/unmount/mount, send a single fetch. Nothing is kept once it settles: a hook mounting
226
+ later sends a new request, and `refetch()` starts a fresh one.
227
+
115
228
  | Hook | Wraps |
116
229
  |---|---|
117
230
  | `useMe()` | `client.me()` |
@@ -128,15 +241,52 @@ data-fetching library.
128
241
  | `useDeleteVar()` | `client.vars.delete()` |
129
242
  | `useConnections()` | `client.connections.list()` |
130
243
  | `useWorkflows(options?)` | `client.workflows.list()` |
244
+ | `useWorkflow(id)` | `client.workflows.get()` |
245
+ | `useCreateWorkflow()` | `client.workflows.create()` |
246
+ | `useUpdateWorkflow()` | `client.workflows.update()` |
247
+ | `useArchiveWorkflow()` | `client.workflows.archive()` |
248
+ | `useDeleteWorkflow()` | `client.workflows.delete()` |
131
249
  | `useRunWorkflow()` | `client.workflows.run()`, defaults `wait: true` |
250
+ | `useFunctions()` | `client.functions.list()` |
251
+ | `useFunction(id)` | `client.functions.get()` |
252
+ | `useCreateFunction()` | `client.functions.create()` |
253
+ | `useUpdateFunction()` | `client.functions.update()` |
254
+ | `useDeleteFunction()` | `client.functions.delete()` |
132
255
  | `useRun()` | `client.run()` (the generic URN dispatch) |
133
256
 
257
+ Three things the definition hooks inherit from the SDK rather than invent:
258
+
259
+ - **`create` mints the id.** The server requires one in the body and never
260
+ generates it, so `useCreateWorkflow().call({name, steps})` works and the
261
+ `wf_…` / `fn_…` id comes back in the result.
262
+ - **`update` is a full replacement, not a patch.** This is the one place they
263
+ differ from `useUpdateDocument`/`useUpdateVar`, whose second argument is a
264
+ patch of just the fields to change. Read with `useWorkflow`/`useFunction`,
265
+ spread, change, send the whole thing back — passing only the changed fields
266
+ deletes the rest. `useUpdateWorkflow()`'s third argument carries
267
+ `ifUnmodifiedSince`: hand it the `updatedAt` from `useWorkflow` and a save
268
+ that would clobber someone else's edit is refused with `409 workflow_stale`
269
+ rather than winning silently.
270
+ - **Deleting a workflow is two calls**, `useArchiveWorkflow` then
271
+ `useDeleteWorkflow`. Nothing chains them for you: the archive step is the one
272
+ a user can still change their mind after.
273
+
274
+ No mutation hook refetches a list on success — this package carries no cache to
275
+ invalidate, so pair one with the matching read hook's own `refetch`.
276
+
277
+ `client.functions.run()` has **no** hook, and neither does `endpoints.run()` —
278
+ that predates the definition hooks and is unchanged by them. `useRun()` reaches
279
+ a Function by its `fn_…` id today (the server dispatches it through the same
280
+ service the dedicated invoke route uses); calling one by the `key` a user gave
281
+ it still means `useW6WClient().functions.run("send-email", …)`.
282
+
134
283
  `useRunWorkflow()`'s `call` defaults `wait: true` when the caller's options omit
135
284
  `wait`: `workflows.run` without `wait` (`node/src/workflows.ts`) leaves the caller
136
285
  with only a `runId` and no public polling operation to follow it with —
137
286
  `console.workflows.getRun` exists, but it is the same studio-internal, unstable
138
287
  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.
288
+ explicitly if you want the queued-and-walk-away behaviour, and follow the run
289
+ with `` useW6WClient().request({ method: "GET", path: path`/runs/${runId}` }) ``.
140
290
 
141
291
  `useW6WClient()` returns the underlying `W6WClient` for anything not covered by a
142
292
  hook (e.g. `client.console.*` directly, at your own risk per the caveat above).
@@ -151,22 +301,11 @@ hook (e.g. `client.console.*` directly, at your own risk per the caveat above).
151
301
  fine — every extra field is optional — and there is no runtime data loss (the JSON
152
302
  payload is unfiltered); a caller typing a variable through this adapter's declared
153
303
  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.
304
+ - **Cancellation is per-hook, not exposed to the caller.** Every read hook aborts its
305
+ own superseded or unmounted calls internally (a superseded call, an `identityKey`
306
+ switch, or an unmount all abort the in-flight `AbortSignal` — but a shared request
307
+ aborts only when its LAST subscriber detaches, so StrictMode's remount or a second
308
+ consumer re-attaches instead of aborting it — see `src/hooks.ts`'s
309
+ module header) — but there is no option to pass your OWN `AbortSignal` into a hook
310
+ from outside it. `useW6WClient()` plus the underlying SDK method (which does take a
311
+ `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,6 @@
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
19
  //# 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"}
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,6 @@
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";
19
20
  //# 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"}
@@ -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"}