@stapel/workspaces-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/MODULE.md +54 -0
- package/README.md +73 -0
- package/dist/api/extensions.d.ts +8 -0
- package/dist/api/extensions.d.ts.map +1 -0
- package/dist/api/extensions.js +8 -0
- package/dist/api/extensions.js.map +1 -0
- package/dist/api/types.d.ts +51 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/workspacesApi.d.ts +43 -0
- package/dist/api/workspacesApi.d.ts.map +1 -0
- package/dist/api/workspacesApi.js +31 -0
- package/dist/api/workspacesApi.js.map +1 -0
- package/dist/flows/errors.d.ts +12 -0
- package/dist/flows/errors.d.ts.map +1 -0
- package/dist/flows/errors.js +13 -0
- package/dist/flows/errors.js.map +1 -0
- package/dist/flows/generated/flows.gen.d.ts +19 -0
- package/dist/flows/generated/flows.gen.d.ts.map +1 -0
- package/dist/flows/generated/flows.gen.js +23 -0
- package/dist/flows/generated/flows.gen.js.map +1 -0
- package/dist/headless/AcceptInvitation.d.ts +39 -0
- package/dist/headless/AcceptInvitation.d.ts.map +1 -0
- package/dist/headless/AcceptInvitation.js +27 -0
- package/dist/headless/AcceptInvitation.js.map +1 -0
- package/dist/headless/Members.d.ts +47 -0
- package/dist/headless/Members.d.ts.map +1 -0
- package/dist/headless/Members.js +50 -0
- package/dist/headless/Members.js.map +1 -0
- package/dist/headless/WorkspaceList.d.ts +38 -0
- package/dist/headless/WorkspaceList.d.ts.map +1 -0
- package/dist/headless/WorkspaceList.js +33 -0
- package/dist/headless/WorkspaceList.js.map +1 -0
- package/dist/headless/WorkspacesProvider.d.ts +18 -0
- package/dist/headless/WorkspacesProvider.d.ts.map +1 -0
- package/dist/headless/WorkspacesProvider.js +17 -0
- package/dist/headless/WorkspacesProvider.js.map +1 -0
- package/dist/i18n/errorsMap.d.ts +10 -0
- package/dist/i18n/errorsMap.d.ts.map +1 -0
- package/dist/i18n/errorsMap.js +20 -0
- package/dist/i18n/errorsMap.js.map +1 -0
- package/dist/i18n/generated/errors.gen.d.ts +347 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.gen.js +177 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts +9 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.js +61 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
- package/dist/i18n/keys.d.ts +55 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +76 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +28 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +49 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/model/context.d.ts +14 -0
- package/dist/model/context.d.ts.map +1 -0
- package/dist/model/context.js +21 -0
- package/dist/model/context.js.map +1 -0
- package/dist/model/mutations.d.ts +55 -0
- package/dist/model/mutations.d.ts.map +1 -0
- package/dist/model/mutations.js +138 -0
- package/dist/model/mutations.js.map +1 -0
- package/dist/model/queries.d.ts +21 -0
- package/dist/model/queries.d.ts.map +1 -0
- package/dist/model/queries.js +41 -0
- package/dist/model/queries.js.map +1 -0
- package/dist/model/queryKeys.d.ts +7 -0
- package/dist/model/queryKeys.d.ts.map +1 -0
- package/dist/model/queryKeys.js +15 -0
- package/dist/model/queryKeys.js.map +1 -0
- package/dist/model/runtime.d.ts +26 -0
- package/dist/model/runtime.d.ts.map +1 -0
- package/dist/model/runtime.js +18 -0
- package/dist/model/runtime.js.map +1 -0
- package/llms.txt +95 -0
- package/manifest.json +705 -0
- package/package.json +85 -0
- package/src/analytics/generated/events.json +7 -0
- package/src/api/extensions.ts +7 -0
- package/src/api/types.ts +58 -0
- package/src/api/workspacesApi.ts +139 -0
- package/src/flows/errors.ts +16 -0
- package/src/flows/generated/flows.gen.ts +38 -0
- package/src/flows/generated/flows.json +1 -0
- package/src/headless/AcceptInvitation.tsx +53 -0
- package/src/headless/Members.tsx +89 -0
- package/src/headless/WorkspaceList.tsx +58 -0
- package/src/headless/WorkspacesProvider.tsx +25 -0
- package/src/i18n/errorsMap.ts +31 -0
- package/src/i18n/generated/errors.gen.ts +208 -0
- package/src/i18n/generated/errors.json +394 -0
- package/src/i18n/generated/errors.ru.gen.ts +67 -0
- package/src/i18n/keys.ts +83 -0
- package/src/i18n/ru.ts +53 -0
- package/src/index.ts +113 -0
- package/src/model/context.tsx +29 -0
- package/src/model/mutations.ts +196 -0
- package/src/model/queries.ts +51 -0
- package/src/model/queryKeys.ts +20 -0
- package/src/model/runtime.ts +46 -0
- package/tsconfig.json +26 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @stapel/workspaces-react
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0786d55: Russian locale as an opt-in `@stapel/workspaces-react/i18n/ru` subpath
|
|
8
|
+
(i18n-shipping wave 2, following the auth-react etalon — wave 1).
|
|
9
|
+
|
|
10
|
+
- `errors.ru.gen.ts` — generated per-locale error bundle, auto-discovered by
|
|
11
|
+
the shared `gen-errors.mjs` driver from stapel-workspaces's
|
|
12
|
+
`translations/errors.ru.json` catalog. `pnpm gen:errors:check` remains the
|
|
13
|
+
drift gate; existing en outputs are byte-identical.
|
|
14
|
+
- `@stapel/workspaces-react/i18n/ru` — `workspacesI18nBundleRu` (generated
|
|
15
|
+
backend ru + hand-written ru UI copy) and `registerWorkspacesI18nRu(engine)`,
|
|
16
|
+
which registers the en floor UNDER the ru texts so a missing key degrades
|
|
17
|
+
to English, never to a raw key. Host bundles registered after the pair's
|
|
18
|
+
win (merge-priority convention, now documented on `registerWorkspacesI18n`).
|
|
19
|
+
- Tree-shake purity is gated twice: the main-entry size-limit budget is
|
|
20
|
+
unchanged (the ru locale is not in its graph; the ru subpath is its own
|
|
21
|
+
chunk with its own budget) and `test/i18nRu.test.ts` walks the compiled
|
|
22
|
+
`dist/index.js` module graph asserting the ru modules never appear.
|
|
23
|
+
|
|
24
|
+
- 1af230c: New headless React flow pair for **stapel-workspaces** — the fourth pipeline pair
|
|
25
|
+
(final of the first wave) after notifications, profiles, and billing (scaffolded
|
|
26
|
+
by `stapel-new-react-lib`, tools 0.8.2). Business + state only, zero visual
|
|
27
|
+
opinion, built on `@stapel/core`'s StapelClient.
|
|
28
|
+
|
|
29
|
+
- **API surface (`workspacesApi`)** — ten typed operations over the signed-in
|
|
30
|
+
workspaces endpoints: `listWorkspaces` / `createWorkspace` / `getWorkspace` /
|
|
31
|
+
`updateWorkspace` / `deleteWorkspace` / `listMembers` / `inviteMembers` /
|
|
32
|
+
`updateMemberRole` / `removeMember` / `acceptInvitation`. Wire types alias the
|
|
33
|
+
generated `@stapel/core` schema (two documented corrections: `WorkspaceRole`
|
|
34
|
+
and `WorkspaceKind` narrow the backend's bare `role` / `type` strings to their
|
|
35
|
+
`TextChoices`). The service-to-service `GET /internal/{ws}/members/{user}` and
|
|
36
|
+
`POST /internal/users/{user}/personal` are intentionally excluded —
|
|
37
|
+
machine-to-machine surfaces, not part of the signed-in UI.
|
|
38
|
+
- **Model hooks** — read hooks `useWorkspaces` / `useWorkspace` / `useMembers`
|
|
39
|
+
and write hooks `useCreateWorkspace` / `useUpdateWorkspace` /
|
|
40
|
+
`useDeleteWorkspace` / `useInviteMembers` / `useUpdateMemberRole` /
|
|
41
|
+
`useRemoveMember` / `useAcceptInvitation`, all under the namespaced
|
|
42
|
+
`workspacesQueryKeys`. Membership and ownership are server truth (roles gate
|
|
43
|
+
access cross-service via the membership cache), so no mutation is optimistic
|
|
44
|
+
(frontend-core-architecture §2.6).
|
|
45
|
+
- **Headless components** — `WorkspaceList` (the caller's workspaces + create),
|
|
46
|
+
`Members` (roster + invite / role-change / removal for one workspace), and
|
|
47
|
+
`AcceptInvitation` (join by email-link token), plus the `WorkspacesProvider`
|
|
48
|
+
root. Each ships a demo (completeness gate green) and msw happy-path tests,
|
|
49
|
+
including a negative case that surfaces a localizable
|
|
50
|
+
`error.400.invitation_expired`.
|
|
51
|
+
- **i18n** — an English `workspaces.*` key bundle spread over the generated
|
|
52
|
+
backend error fallbacks, so a `StapelApiError.code` never renders as a raw key.
|
|
53
|
+
|
|
54
|
+
## 0.0.0
|
|
55
|
+
|
|
56
|
+
- Scaffolded by `stapel-new-react-lib` from the auth-react etalon
|
|
57
|
+
(frontend-standard §9, frontend-core-architecture §4 checklist). Layers
|
|
58
|
+
api → model → flows → headless → i18n; drift-gated generated surfaces
|
|
59
|
+
(flows registry, backend error map, manifest + llms.txt) via the shared
|
|
60
|
+
monorepo `gen:*` drivers.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stapel contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/MODULE.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @stapel/workspaces-react — module guide
|
|
2
|
+
|
|
3
|
+
Headless React flow pair for **stapel-workspaces**. This is the human companion to the
|
|
4
|
+
generated `llms.txt` (agent context) and `manifest.json` (machine catalog).
|
|
5
|
+
|
|
6
|
+
## Layers
|
|
7
|
+
|
|
8
|
+
- **api/** — `createWorkspacesApi(client)`; types are aliases over the generated
|
|
9
|
+
`components["schemas"]` from `@stapel/core` (never parallel hand-written
|
|
10
|
+
bodies). Named typed operations arrive with gen-api v2 (`core-typed-ops`);
|
|
11
|
+
hand-authored, un-generatable surface lives in `api/extensions.ts`.
|
|
12
|
+
- **model/** — `workspacesQueryKeys` (single key factory, `["workspaces"]`
|
|
13
|
+
namespace), `createWorkspacesRuntime`, React context/hooks. Declare the
|
|
14
|
+
persist/optimistic policy here as you add read hooks and mutations.
|
|
15
|
+
- **flows/** — `createFlowMachine`-based machines (primitive imported from
|
|
16
|
+
`@stapel/core`), bound to the generated `WORKSPACES_FLOWS` registry. Scaffold
|
|
17
|
+
new machines from flows.json; keep them under `gen:flows:check`.
|
|
18
|
+
- **headless/** — render-prop components; `<WorkspacesProvider>` wires the
|
|
19
|
+
runtime into context. shadcn-copyable (frontend-standard §7).
|
|
20
|
+
- **i18n/** — `WORKSPACES_I18N_KEYS` + en bundle; the generated backend error
|
|
21
|
+
bundle is merged in so every `error.*` code has a fallback. Override any key
|
|
22
|
+
via core's `loadLocale`, or statically by registering a host bundle AFTER
|
|
23
|
+
the pair's (registration order = override priority, later wins per key —
|
|
24
|
+
i18n-shipping.md §3). Layering inside one locale: generated en floor → pair
|
|
25
|
+
polish/UI copy → pair locale bundle (`@stapel/workspaces-react/i18n/ru`:
|
|
26
|
+
generated from the backend's `translations/errors.ru.json` catalog +
|
|
27
|
+
hand-written ru UI copy; it registers the en floor UNDER ru so a missing key
|
|
28
|
+
degrades to English, never a raw key) → host bundle last. The ru subpath is
|
|
29
|
+
opt-in and stays out of the main entry (size-limit budget + module-graph
|
|
30
|
+
test in `test/i18nRu.test.ts`).
|
|
31
|
+
- **analytics/** — `generated/events.json`, the typed-event registry projected
|
|
32
|
+
from `defineEvent` call sites + flow funnels (`pnpm gen:events`). Read by the
|
|
33
|
+
analytics lint and embedded into `manifest.json`; nothing to hand-edit.
|
|
34
|
+
- **demo/** — first-class demos (`defineDemo`, `@stapel/showcase`): `_harness.tsx`
|
|
35
|
+
wires a mock runtime + i18n + query client; each `<Name>.demo.tsx` is compiled,
|
|
36
|
+
product-linted, smoke-rendered, and projected to a Ladle story (`pnpm gen:demos`).
|
|
37
|
+
The completeness gate requires ≥1 demo per exported headless component; the
|
|
38
|
+
starter `Workspaces.demo.tsx` covers `WorkspacesProvider`. Demos never ship.
|
|
39
|
+
|
|
40
|
+
## Extension seams (frontend-standard §7)
|
|
41
|
+
|
|
42
|
+
- Client is injected via `<WorkspacesProvider>` / core's `StapelConfigProvider`
|
|
43
|
+
(per-module override) — pairs never hard-import a client.
|
|
44
|
+
- Flow deps are injected through `create<X>Flow(deps)` factories.
|
|
45
|
+
- The headless layer is fully replaceable (copy-and-own).
|
|
46
|
+
|
|
47
|
+
## TODO after scaffold
|
|
48
|
+
|
|
49
|
+
1. `pnpm install && pnpm gen` — materialize the generated surfaces.
|
|
50
|
+
2. Alias the stapel-workspaces schemas you use in `api/types.ts`.
|
|
51
|
+
3. Add read hooks + mutations in `model/` and a persist/optimistic policy.
|
|
52
|
+
4. Once stapel-workspaces annotates `@flow_step`, scaffold flow machines from
|
|
53
|
+
flows.json and put them under `gen:flows:check`.
|
|
54
|
+
5. Fill `MODULE.md`'s machine table and link the SA-doc flows.
|
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @stapel/workspaces-react
|
|
2
|
+
|
|
3
|
+
Headless React flow pair for stapel-workspaces: typed API client, TanStack Query hooks, flow machines, headless components, and i18n keys. Zero visual opinion.
|
|
4
|
+
|
|
5
|
+
Headless React flow pair for **stapel-workspaces** (frontend-standard §2). Business +
|
|
6
|
+
state only, zero visual opinion — any design layers on top. Built on
|
|
7
|
+
`@stapel/core` (typed client + `StapelApiError` envelope, token refresh,
|
|
8
|
+
verification-403 interception, i18n engine, analytics facade, TanStack Query).
|
|
9
|
+
|
|
10
|
+
Scaffolded by `stapel-new-react-lib`. See `MODULE.md` for the layer map, machine
|
|
11
|
+
table, extension seams, and persist policy.
|
|
12
|
+
|
|
13
|
+
## Layers
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/
|
|
17
|
+
api/ typed client — thin adapter over @stapel/core `components`
|
|
18
|
+
model/ query keys, runtime wiring, context/hooks
|
|
19
|
+
flows/ createFlowMachine flow machines (+ generated registry)
|
|
20
|
+
headless/ renderless components (WorkspacesProvider, flow render-props)
|
|
21
|
+
i18n/ translation keys + generated backend error map
|
|
22
|
+
analytics/ generated typed-event registry (events.json)
|
|
23
|
+
demo/ first-class demos (compiled, product-linted, smoke-rendered)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Generated surfaces (drift-gated)
|
|
27
|
+
|
|
28
|
+
| Surface | Path | Gate |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Flow registry | `src/flows/generated/` | `pnpm gen:flows:check` |
|
|
31
|
+
| Backend error map + en bundle | `src/i18n/generated/` | `pnpm gen:errors:check` |
|
|
32
|
+
| Typed-event registry | `src/analytics/generated/events.json` | `pnpm gen:events:check` |
|
|
33
|
+
| Demos → Ladle stories | `demo/generated/` | `pnpm gen:demos:check` |
|
|
34
|
+
| `manifest.json` + `llms.txt` | package root | `pnpm gen:manifest:check` |
|
|
35
|
+
|
|
36
|
+
These drift gates run at the **monorepo root** (`pnpm gen` / `pnpm gen:check`) —
|
|
37
|
+
the etalon's env-parametrized `scripts/gen-*.mjs` drivers are shared, not forked.
|
|
38
|
+
`stapel-new-react-lib` wired this pair into the root `gen`/`gen:check` aggregates
|
|
39
|
+
at scaffold time (one env-parametrized invocation per driver). The typed
|
|
40
|
+
`schema.ts` is core-owned (`pnpm gen:api`); design tokens are tokens-owned
|
|
41
|
+
(`pnpm gen:tokens`).
|
|
42
|
+
|
|
43
|
+
### Russian locale (opt-in subpath)
|
|
44
|
+
|
|
45
|
+
The `ru` bundle ships as a separate subpath so it never bloats the main entry
|
|
46
|
+
(size-limit gated — the locale stays out of hosts that don't register it):
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { registerWorkspacesI18nRu } from "@stapel/workspaces-react/i18n/ru";
|
|
50
|
+
|
|
51
|
+
registerWorkspacesI18n(i18n); // en floor + polish
|
|
52
|
+
registerWorkspacesI18nRu(i18n); // ru locale (generated from the backend catalog)
|
|
53
|
+
await i18n.setLocale("ru"); // live switch; a missing key degrades to English
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Backend error texts are generated from stapel-workspaces's
|
|
57
|
+
`translations/errors.ru.json` catalog (`pnpm gen:errors`, drift-gated); the
|
|
58
|
+
pair's UI keys carry hand-written ru copy. Register your own bundle AFTER the
|
|
59
|
+
pair's to override any key — registration order is override priority.
|
|
60
|
+
|
|
61
|
+
## Guardrails
|
|
62
|
+
|
|
63
|
+
Linted by the shared `@stapel/eslint-plugin` flat config (no raw colours, no raw
|
|
64
|
+
token imports, no raw fetch, i18n-key existence, typed analytics, headless-only)
|
|
65
|
+
and the shared **stylelint** preset — `pnpm lint` per package plus `pnpm lint:css`
|
|
66
|
+
at the root (colours only ever `var(--stapel-*)`). Demos are first-class code:
|
|
67
|
+
compiled by `tsconfig.demo.json`, linted with the product ruleset, and
|
|
68
|
+
smoke-rendered by `test/demos.test.tsx` — but never shipped (excluded from the
|
|
69
|
+
`files` allowlist; proven by `test/prodBundlePurity.test.ts`).
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-authored API surface the codegen does not (yet) cover — browser-redirect
|
|
3
|
+
* URL builders, open-redirect guards, narrow domain type-guards, header
|
|
4
|
+
* conventions. Everything that CAN be derived from schema.json belongs in the
|
|
5
|
+
* generated operations (`api/workspacesApi.ts`), not here.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../src/api/extensions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-authored API surface the codegen does not (yet) cover — browser-redirect
|
|
3
|
+
* URL builders, open-redirect guards, narrow domain type-guards, header
|
|
4
|
+
* conventions. Everything that CAN be derived from schema.json belongs in the
|
|
5
|
+
* generated operations (`api/workspacesApi.ts`), not here.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=extensions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../src/api/extensions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for the stapel-workspaces HTTP contract — **derived from the generated
|
|
3
|
+
* OpenAPI surface** (frontend-standard §2/§3), never hand-maintained. The
|
|
4
|
+
* single source of truth is `components["schemas"]` from `@stapel/core`
|
|
5
|
+
* (`packages/core/src/generated/schema.ts`, produced by `pnpm gen:api` from the
|
|
6
|
+
* unified all-modules OpenAPI). Alias the schemas this pair uses under local
|
|
7
|
+
* names here; do NOT write parallel response bodies. Where drf-spectacular +
|
|
8
|
+
* openapi-typescript under-describe the runtime, apply a small documented
|
|
9
|
+
* correction (see auth-react `api/types.ts` for the three canonical patterns).
|
|
10
|
+
*/
|
|
11
|
+
import type { components } from "@stapel/core";
|
|
12
|
+
/** The generated schema table — the one source of truth for wire shapes. */
|
|
13
|
+
export type Schemas = components["schemas"];
|
|
14
|
+
/** One workspace — identity, storage, my role, member count (GET/POST/PATCH). */
|
|
15
|
+
export type Workspace = Schemas["WorkspaceResponse"];
|
|
16
|
+
/** GET / 200 body — the caller's workspaces (memberships). */
|
|
17
|
+
export type WorkspaceList = Schemas["WorkspaceListResponse"];
|
|
18
|
+
/** POST / request body — create a workspace (slug auto-generated when omitted). */
|
|
19
|
+
export type WorkspaceCreate = Schemas["WorkspaceCreateRequest"];
|
|
20
|
+
/** PATCH /{id} request body — a partial name / slug / settings update. */
|
|
21
|
+
export type WorkspaceUpdate = Schemas["PatchedWorkspaceUpdateRequest"];
|
|
22
|
+
/** One workspace member (GET members, PATCH member role, POST accept). */
|
|
23
|
+
export type Member = Schemas["MemberResponse"];
|
|
24
|
+
/** GET /{id}/members 200 body — a workspace's members. */
|
|
25
|
+
export type MemberList = Schemas["MemberListResponse"];
|
|
26
|
+
/** POST /{id}/members/invite request body — one or more emails + a role. */
|
|
27
|
+
export type MemberInvite = Schemas["MemberInviteRequest"];
|
|
28
|
+
/** POST /{id}/members/invite 201 body — the created invitations. */
|
|
29
|
+
export type MemberInviteResult = Schemas["MemberInviteResponse"];
|
|
30
|
+
/** PATCH /{id}/members/{userId} request body — the new role. */
|
|
31
|
+
export type MemberRoleUpdate = Schemas["PatchedMemberUpdateRequest"];
|
|
32
|
+
/** One pending/accepted invitation (a row of {@link MemberInviteResult}). */
|
|
33
|
+
export type Invitation = Schemas["InvitationResponse"];
|
|
34
|
+
/** POST /invitations/accept request body — the token from the email link. */
|
|
35
|
+
export type InvitationAccept = Schemas["InvitationAcceptRequest"];
|
|
36
|
+
/**
|
|
37
|
+
* A workspace membership role, ordered least→most privileged. The generated
|
|
38
|
+
* schema types `MemberResponse.role` / the invite `role` as a bare `string`,
|
|
39
|
+
* but the backend (`models.Role`, a Django `TextChoices`) constrains it to
|
|
40
|
+
* exactly these values. Narrowing here gives call sites a checked union — a
|
|
41
|
+
* documented correction of the same kind auth-react/billing-react apply.
|
|
42
|
+
*/
|
|
43
|
+
export type WorkspaceRole = "owner" | "admin" | "member" | "viewer";
|
|
44
|
+
/**
|
|
45
|
+
* A workspace category. The generated schema types `WorkspaceResponse.type` /
|
|
46
|
+
* the create `type` as a bare `string`; the backend (`models.WorkspaceType`)
|
|
47
|
+
* constrains it to exactly these two. Narrowed for the same reason as
|
|
48
|
+
* {@link WorkspaceRole}.
|
|
49
|
+
*/
|
|
50
|
+
export type WorkspaceKind = "personal" | "work";
|
|
51
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,4EAA4E;AAC5E,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;AAI5C,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACrD,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAC7D,mFAAmF;AACnF,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;AAChE,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,+BAA+B,CAAC,CAAC;AACvE,0EAA0E;AAC1E,MAAM,MAAM,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAC/C,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;AACvD,4EAA4E;AAC5E,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAC1D,oEAAoE;AACpE,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AACjE,gEAAgE;AAChE,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;AACrE,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;AACvD,6EAA6E;AAC7E,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAIlE;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { StapelClient } from "@stapel/core";
|
|
2
|
+
import type { Member, MemberInvite, MemberInviteResult, MemberList, MemberRoleUpdate, InvitationAccept, Workspace, WorkspaceCreate, WorkspaceList, WorkspaceUpdate } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The pair's typed operation surface — one method per stapel-workspaces endpoint
|
|
5
|
+
* a signed-in browser client may call, bound to the injected {@link StapelClient}
|
|
6
|
+
* (the per-module override seam of frontend-standard §7.2). Paths are relative
|
|
7
|
+
* to the runtime's `baseUrl` (e.g. `/workspaces/api`).
|
|
8
|
+
*
|
|
9
|
+
* The service-to-service `GET /internal/{ws}/members/{user}` (membership check
|
|
10
|
+
* from another backend) and `POST /internal/users/{user}/personal` (get-or-create
|
|
11
|
+
* a personal workspace on first login) are intentionally absent — they are
|
|
12
|
+
* machine-to-machine surfaces (`IsServiceRequest | IsStaffUser`), not part of the
|
|
13
|
+
* signed-in UI this pair drives.
|
|
14
|
+
*
|
|
15
|
+
* These operations will be GENERATED from schema.json operationIds by gen-api
|
|
16
|
+
* v2 (task `core-typed-ops`); until then they are hand-authored here (the ONE
|
|
17
|
+
* legal home of path strings — `stapel/no-string-paths` §2.3 carve-out).
|
|
18
|
+
*/
|
|
19
|
+
export interface WorkspacesApi {
|
|
20
|
+
readonly client: StapelClient;
|
|
21
|
+
/** The caller's workspaces (accepted memberships, newest-accessed first). */
|
|
22
|
+
listWorkspaces(): Promise<WorkspaceList>;
|
|
23
|
+
/** Create a workspace and seed the caller as owner. */
|
|
24
|
+
createWorkspace(body: WorkspaceCreate): Promise<Workspace>;
|
|
25
|
+
/** A single workspace by id (touches last-accessed). */
|
|
26
|
+
getWorkspace(workspaceId: string): Promise<Workspace>;
|
|
27
|
+
/** Partially update a workspace's name / slug / settings (admin+). */
|
|
28
|
+
updateWorkspace(workspaceId: string, patch: WorkspaceUpdate): Promise<Workspace>;
|
|
29
|
+
/** Soft-delete a workspace (owner only). */
|
|
30
|
+
deleteWorkspace(workspaceId: string): Promise<void>;
|
|
31
|
+
/** A workspace's members (viewer+). */
|
|
32
|
+
listMembers(workspaceId: string): Promise<MemberList>;
|
|
33
|
+
/** Invite one or more emails to a workspace at a role (admin+). */
|
|
34
|
+
inviteMembers(workspaceId: string, body: MemberInvite): Promise<MemberInviteResult>;
|
|
35
|
+
/** Change a member's role (admin+; owner grants require owner). */
|
|
36
|
+
updateMemberRole(workspaceId: string, userId: string, patch: MemberRoleUpdate): Promise<Member>;
|
|
37
|
+
/** Remove a member from a workspace (admin+; the last owner is protected). */
|
|
38
|
+
removeMember(workspaceId: string, userId: string): Promise<void>;
|
|
39
|
+
/** Accept an invitation by its token — returns the caller's new membership. */
|
|
40
|
+
acceptInvitation(body: InvitationAccept): Promise<Member>;
|
|
41
|
+
}
|
|
42
|
+
export declare function createWorkspacesApi(client: StapelClient): WorkspacesApi;
|
|
43
|
+
//# sourceMappingURL=workspacesApi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspacesApi.d.ts","sourceRoot":"","sources":["../../src/api/workspacesApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAwB,MAAM,cAAc,CAAC;AACvE,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAqBpB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAE9B,6EAA6E;IAC7E,cAAc,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IACzC,uDAAuD;IACvD,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3D,wDAAwD;IACxD,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACtD,sEAAsE;IACtE,eAAe,CACb,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,SAAS,CAAC,CAAC;IACtB,4CAA4C;IAC5C,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpD,uCAAuC;IACvC,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtD,mEAAmE;IACnE,aAAa,CACX,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC/B,mEAAmE;IACnE,gBAAgB,CACd,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,8EAA8E;IAC9E,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,+EAA+E;IAC/E,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAoDvE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSRF rule for cookie-authenticated browser clients (mirrors auth-react): the
|
|
3
|
+
* simplest SPA rule is to always send `X-Requested-With: XMLHttpRequest` on
|
|
4
|
+
* mutating requests. Header-token clients ignore it; it is harmless there, so
|
|
5
|
+
* every mutation carries it.
|
|
6
|
+
*/
|
|
7
|
+
const CSRF_HEADERS = {
|
|
8
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
9
|
+
};
|
|
10
|
+
function mutating(options) {
|
|
11
|
+
return {
|
|
12
|
+
...options,
|
|
13
|
+
headers: { ...CSRF_HEADERS, ...options?.headers },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function createWorkspacesApi(client) {
|
|
17
|
+
return {
|
|
18
|
+
client,
|
|
19
|
+
listWorkspaces: () => client.get("/"),
|
|
20
|
+
createWorkspace: (body) => client.post("/", body, mutating()),
|
|
21
|
+
getWorkspace: (workspaceId) => client.get(`/${encodeURIComponent(workspaceId)}`),
|
|
22
|
+
updateWorkspace: (workspaceId, patch) => client.patch(`/${encodeURIComponent(workspaceId)}`, patch, mutating()),
|
|
23
|
+
deleteWorkspace: (workspaceId) => client.delete(`/${encodeURIComponent(workspaceId)}`, mutating()),
|
|
24
|
+
listMembers: (workspaceId) => client.get(`/${encodeURIComponent(workspaceId)}/members`),
|
|
25
|
+
inviteMembers: (workspaceId, body) => client.post(`/${encodeURIComponent(workspaceId)}/members/invite`, body, mutating()),
|
|
26
|
+
updateMemberRole: (workspaceId, userId, patch) => client.patch(`/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(userId)}`, patch, mutating()),
|
|
27
|
+
removeMember: (workspaceId, userId) => client.delete(`/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(userId)}`, mutating()),
|
|
28
|
+
acceptInvitation: (body) => client.post("/invitations/accept", body, mutating()),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=workspacesApi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspacesApi.js","sourceRoot":"","sources":["../../src/api/workspacesApi.ts"],"names":[],"mappings":"AAcA;;;;;GAKG;AACH,MAAM,YAAY,GAA2B;IAC3C,kBAAkB,EAAE,gBAAgB;CACrC,CAAC;AAEF,SAAS,QAAQ,CACf,OAAuD;IAEvD,OAAO;QACL,GAAG,OAAO;QACV,OAAO,EAAE,EAAE,GAAG,YAAY,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE;KAClD,CAAC;AACJ,CAAC;AAuDD,MAAM,UAAU,mBAAmB,CAAC,MAAoB;IACtD,OAAO;QACL,MAAM;QAEN,cAAc,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;QAErC,eAAe,EAAE,CAAC,IAAI,EAAE,EAAE,CACxB,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAA8B,EAAE,QAAQ,EAAE,CAAC;QAE9D,YAAY,EAAE,CAAC,WAAW,EAAE,EAAE,CAC5B,MAAM,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;QAEnD,eAAe,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,CACtC,MAAM,CAAC,KAAK,CACV,IAAI,kBAAkB,CAAC,WAAW,CAAC,EAAE,EACrC,KAA+B,EAC/B,QAAQ,EAAE,CACX;QAEH,eAAe,EAAE,CAAC,WAAW,EAAE,EAAE,CAC/B,MAAM,CAAC,MAAM,CAAC,IAAI,kBAAkB,CAAC,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC;QAElE,WAAW,EAAE,CAAC,WAAW,EAAE,EAAE,CAC3B,MAAM,CAAC,GAAG,CAAC,IAAI,kBAAkB,CAAC,WAAW,CAAC,UAAU,CAAC;QAE3D,aAAa,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,CACnC,MAAM,CAAC,IAAI,CACT,IAAI,kBAAkB,CAAC,WAAW,CAAC,iBAAiB,EACpD,IAA2B,EAC3B,QAAQ,EAAE,CACX;QAEH,gBAAgB,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAC/C,MAAM,CAAC,KAAK,CACV,IAAI,kBAAkB,CAAC,WAAW,CAAC,YAAY,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAC3E,KAAgC,EAChC,QAAQ,EAAE,CACX;QAEH,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CACpC,MAAM,CAAC,MAAM,CACX,IAAI,kBAAkB,CAAC,WAAW,CAAC,YAAY,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAC3E,QAAQ,EAAE,CACX;QAEH,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CACzB,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,IAA+B,EAC/B,QAAQ,EAAE,CACX;KACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FlowError } from "@stapel/core";
|
|
2
|
+
export type { FlowError } from "@stapel/core";
|
|
3
|
+
export { isErrorCode } from "@stapel/core";
|
|
4
|
+
/**
|
|
5
|
+
* Fold any thrown value into a {@link FlowError} using this pair's own
|
|
6
|
+
* module-scoped fallback key (`workspaces.error.unknown`, an en string in
|
|
7
|
+
* {@link workspacesI18nBundleEn}) so a non-`StapelApiError` fault still renders
|
|
8
|
+
* real copy rather than a raw key. The primitive lives in `@stapel/core`
|
|
9
|
+
* (frontend-core-architecture §4b); this wrapper only pins the fallback.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toFlowError(error: unknown): FlowError;
|
|
12
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/flows/errors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAErD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { toFlowError as coreToFlowError } from "@stapel/core";
|
|
2
|
+
export { isErrorCode } from "@stapel/core";
|
|
3
|
+
/**
|
|
4
|
+
* Fold any thrown value into a {@link FlowError} using this pair's own
|
|
5
|
+
* module-scoped fallback key (`workspaces.error.unknown`, an en string in
|
|
6
|
+
* {@link workspacesI18nBundleEn}) so a non-`StapelApiError` fault still renders
|
|
7
|
+
* real copy rather than a raw key. The primitive lives in `@stapel/core`
|
|
8
|
+
* (frontend-core-architecture §4b); this wrapper only pins the fallback.
|
|
9
|
+
*/
|
|
10
|
+
export function toFlowError(error) {
|
|
11
|
+
return coreToFlowError(error, "workspaces.error.unknown");
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/flows/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,cAAc,CAAC;AAI9D,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,eAAe,CAAC,KAAK,EAAE,0BAA0B,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical workspaces flow registry — the machine-facing projection of the
|
|
3
|
+
* flow source (docs/flow-system.md §5). Each entry carries a flow's canonical
|
|
4
|
+
* id (`flow.<id>.<step>` analytics funnel = this flow), its i18n keys, and its
|
|
5
|
+
* ordered step→endpoint contract. Flow machines bind their `id` to
|
|
6
|
+
* `WORKSPACES_FLOWS[...].id` so a backend flow rename/re-step surfaces as a drift
|
|
7
|
+
* here (`pnpm gen:flows:check`) rather than a silent analytics/contract skew.
|
|
8
|
+
*/
|
|
9
|
+
export declare const WORKSPACES_FLOWS: {};
|
|
10
|
+
/** Canonical flow ids present in flows.json (a subset grows as the backend annotates more). */
|
|
11
|
+
export type WorkspacesFlowId = keyof typeof WORKSPACES_FLOWS;
|
|
12
|
+
export type WorkspacesFlowSpec = (typeof WORKSPACES_FLOWS)[WorkspacesFlowId];
|
|
13
|
+
export interface FlowEndpoint {
|
|
14
|
+
readonly method: string;
|
|
15
|
+
readonly path: string;
|
|
16
|
+
}
|
|
17
|
+
/** All HTTP endpoints a flow touches, in step order (for the contract test / MSW). */
|
|
18
|
+
export declare function flowEndpoints(id: WorkspacesFlowId): readonly FlowEndpoint[];
|
|
19
|
+
//# sourceMappingURL=flows.gen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flows.gen.d.ts","sourceRoot":"","sources":["../../../src/flows/generated/flows.gen.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,IAEnB,CAAC;AAEX,+FAA+F;AAC/F,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,gBAAgB,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,gBAAgB,CAAC,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,EAAE,EAAE,gBAAgB,GAAG,SAAS,YAAY,EAAE,CAU3E"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/gen-flows.mjs — do not edit by hand.
|
|
2
|
+
// Source: unified Stapel flows.json (stapel-example-monolith `make codegen`).
|
|
3
|
+
// Regenerate: pnpm gen:flows · Drift gate: pnpm gen:flows:check
|
|
4
|
+
/**
|
|
5
|
+
* The canonical workspaces flow registry — the machine-facing projection of the
|
|
6
|
+
* flow source (docs/flow-system.md §5). Each entry carries a flow's canonical
|
|
7
|
+
* id (`flow.<id>.<step>` analytics funnel = this flow), its i18n keys, and its
|
|
8
|
+
* ordered step→endpoint contract. Flow machines bind their `id` to
|
|
9
|
+
* `WORKSPACES_FLOWS[...].id` so a backend flow rename/re-step surfaces as a drift
|
|
10
|
+
* here (`pnpm gen:flows:check`) rather than a silent analytics/contract skew.
|
|
11
|
+
*/
|
|
12
|
+
export const WORKSPACES_FLOWS = {};
|
|
13
|
+
/** All HTTP endpoints a flow touches, in step order (for the contract test / MSW). */
|
|
14
|
+
export function flowEndpoints(id) {
|
|
15
|
+
// Guard the empty-registry case: a module with no annotated flows makes
|
|
16
|
+
// `WorkspacesFlowId` = `never` and `WORKSPACES_FLOWS` = `{}`, so a direct
|
|
17
|
+
// `WORKSPACES_FLOWS[id].steps` does not type-check. Widening to an optional spec
|
|
18
|
+
// keeps this body valid for the zero-flow scaffold AND correct once the
|
|
19
|
+
// backend annotates `@flow_step` and the registry fills in.
|
|
20
|
+
const spec = WORKSPACES_FLOWS[id];
|
|
21
|
+
return spec ? spec.steps.flatMap((s) => s.endpoints) : [];
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=flows.gen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flows.gen.js","sourceRoot":"","sources":["../../../src/flows/generated/flows.gen.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,8EAA8E;AAC9E,oEAAoE;AAEpE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAEtB,CAAC;AAYX,sFAAsF;AACtF,MAAM,UAAU,aAAa,CAAC,EAAoB;IAChD,wEAAwE;IACxE,0EAA0E;IAC1E,iFAAiF;IACjF,wEAAwE;IACxE,4DAA4D;IAC5D,MAAM,IAAI,GAAG,gBAAgB,CAAC,EAAE,CAEnB,CAAC;IACd,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { StapelApiError } from "@stapel/core";
|
|
3
|
+
import type { Member } from "../api/types.js";
|
|
4
|
+
/** Render-prop bag for {@link AcceptInvitation}. */
|
|
5
|
+
export interface AcceptInvitationBag {
|
|
6
|
+
/** Accept the invitation identified by `token`. */
|
|
7
|
+
accept(token: string): void;
|
|
8
|
+
/** An accept call is in flight. */
|
|
9
|
+
readonly isAccepting: boolean;
|
|
10
|
+
/** The accept succeeded — the caller's new membership. */
|
|
11
|
+
readonly isAccepted: boolean;
|
|
12
|
+
/** The membership created on success, else null. */
|
|
13
|
+
readonly member: Member | null;
|
|
14
|
+
/** The accept failed. */
|
|
15
|
+
readonly isError: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The error, when `isError`. A dead token surfaces a localizable
|
|
18
|
+
* `StapelApiError` — `error.400.invitation_expired` /
|
|
19
|
+
* `error.400.invitation_revoked` / `error.400.invitation_already_used` /
|
|
20
|
+
* `error.404.invitation_not_found` — each carrying its backend remediation.
|
|
21
|
+
*/
|
|
22
|
+
readonly error: StapelApiError | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Headless invitation-accept control — a renderless "join by token" action.
|
|
26
|
+
* Wires {@link useAcceptInvitation} and hands an {@link AcceptInvitationBag} to
|
|
27
|
+
* `children`; bring your own accept button / status. Zero visual opinion
|
|
28
|
+
* (frontend-standard §2).
|
|
29
|
+
*
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <AcceptInvitation>
|
|
32
|
+
* {({ accept, isAccepted, error }) => ( ... )}
|
|
33
|
+
* </AcceptInvitation>
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function AcceptInvitation(props: {
|
|
37
|
+
children: (bag: AcceptInvitationBag) => ReactNode;
|
|
38
|
+
}): ReactNode;
|
|
39
|
+
//# sourceMappingURL=AcceptInvitation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AcceptInvitation.d.ts","sourceRoot":"","sources":["../../src/headless/AcceptInvitation.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAG9C,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mCAAmC;IACnC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,yBAAyB;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;CACvC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,SAAS,CAAC;CACnD,GAAG,SAAS,CAYZ"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useAcceptInvitation } from "../model/mutations.js";
|
|
2
|
+
/**
|
|
3
|
+
* Headless invitation-accept control — a renderless "join by token" action.
|
|
4
|
+
* Wires {@link useAcceptInvitation} and hands an {@link AcceptInvitationBag} to
|
|
5
|
+
* `children`; bring your own accept button / status. Zero visual opinion
|
|
6
|
+
* (frontend-standard §2).
|
|
7
|
+
*
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <AcceptInvitation>
|
|
10
|
+
* {({ accept, isAccepted, error }) => ( ... )}
|
|
11
|
+
* </AcceptInvitation>
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function AcceptInvitation(props) {
|
|
15
|
+
const mutation = useAcceptInvitation();
|
|
16
|
+
return props.children({
|
|
17
|
+
accept: (token) => {
|
|
18
|
+
mutation.mutate({ token });
|
|
19
|
+
},
|
|
20
|
+
isAccepting: mutation.isPending,
|
|
21
|
+
isAccepted: mutation.isSuccess,
|
|
22
|
+
member: mutation.data ?? null,
|
|
23
|
+
isError: mutation.isError,
|
|
24
|
+
error: mutation.error ?? null,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=AcceptInvitation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AcceptInvitation.js","sourceRoot":"","sources":["../../src/headless/AcceptInvitation.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAuB5D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAEhC;IACC,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IACvC,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAChB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7B,CAAC;QACD,WAAW,EAAE,QAAQ,CAAC,SAAS;QAC/B,UAAU,EAAE,QAAQ,CAAC,SAAS;QAC9B,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAI,IAAI;QAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,KAAK,EAAE,QAAQ,CAAC,KAAK,IAAI,IAAI;KAC9B,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { StapelApiError } from "@stapel/core";
|
|
3
|
+
import type { Member, MemberInvite } from "../api/types.js";
|
|
4
|
+
import type { MemberRoleChange } from "../model/mutations.js";
|
|
5
|
+
/** Render-prop bag for {@link Members}. */
|
|
6
|
+
export interface MembersBag {
|
|
7
|
+
/** The workspace's members once loaded (empty before load). */
|
|
8
|
+
readonly members: readonly Member[];
|
|
9
|
+
/** The initial member-list load is in flight. */
|
|
10
|
+
readonly isLoading: boolean;
|
|
11
|
+
/** The read, an invite, a role change, or a removal failed. */
|
|
12
|
+
readonly isError: boolean;
|
|
13
|
+
/** The error, when `isError` (a localizable `StapelApiError`), else null. */
|
|
14
|
+
readonly error: StapelApiError | null;
|
|
15
|
+
/** Invite one or more emails at a role. */
|
|
16
|
+
invite(body: MemberInvite): void;
|
|
17
|
+
/** An invite call is in flight. */
|
|
18
|
+
readonly isInviting: boolean;
|
|
19
|
+
/** Change a member's role. */
|
|
20
|
+
updateRole(change: MemberRoleChange): void;
|
|
21
|
+
/** A role-change call is in flight. */
|
|
22
|
+
readonly isUpdatingRole: boolean;
|
|
23
|
+
/** Remove a member by `userId`. */
|
|
24
|
+
remove(userId: string): void;
|
|
25
|
+
/** A removal call is in flight. */
|
|
26
|
+
readonly isRemoving: boolean;
|
|
27
|
+
/** Refetch the member list from the server. */
|
|
28
|
+
refetch(): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Headless member roster — a renderless view + invite / role-change / removal
|
|
32
|
+
* controls over one workspace's members. Wires {@link useMembers},
|
|
33
|
+
* {@link useInviteMembers}, {@link useUpdateMemberRole}, and
|
|
34
|
+
* {@link useRemoveMember} and hands a {@link MembersBag} to `children`; bring your
|
|
35
|
+
* own roster / invite form / role menu. Zero visual opinion (frontend-standard §2).
|
|
36
|
+
*
|
|
37
|
+
* ```tsx
|
|
38
|
+
* <Members workspaceId={id}>
|
|
39
|
+
* {({ members, invite, updateRole, remove }) => ( ... )}
|
|
40
|
+
* </Members>
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function Members(props: {
|
|
44
|
+
workspaceId: string;
|
|
45
|
+
children: (bag: MembersBag) => ReactNode;
|
|
46
|
+
}): ReactNode;
|
|
47
|
+
//# sourceMappingURL=Members.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Members.d.ts","sourceRoot":"","sources":["../../src/headless/Members.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAO5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,2CAA2C;IAC3C,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IACjC,mCAAmC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,8BAA8B;IAC9B,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3C,uCAAuC;IACvC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,mCAAmC;IACnC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mCAAmC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,+CAA+C;IAC/C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,SAAS,CAAC;CAC1C,GAAG,SAAS,CAmCZ"}
|