@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.
Files changed (111) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +21 -0
  3. package/MODULE.md +54 -0
  4. package/README.md +73 -0
  5. package/dist/api/extensions.d.ts +8 -0
  6. package/dist/api/extensions.d.ts.map +1 -0
  7. package/dist/api/extensions.js +8 -0
  8. package/dist/api/extensions.js.map +1 -0
  9. package/dist/api/types.d.ts +51 -0
  10. package/dist/api/types.d.ts.map +1 -0
  11. package/dist/api/types.js +2 -0
  12. package/dist/api/types.js.map +1 -0
  13. package/dist/api/workspacesApi.d.ts +43 -0
  14. package/dist/api/workspacesApi.d.ts.map +1 -0
  15. package/dist/api/workspacesApi.js +31 -0
  16. package/dist/api/workspacesApi.js.map +1 -0
  17. package/dist/flows/errors.d.ts +12 -0
  18. package/dist/flows/errors.d.ts.map +1 -0
  19. package/dist/flows/errors.js +13 -0
  20. package/dist/flows/errors.js.map +1 -0
  21. package/dist/flows/generated/flows.gen.d.ts +19 -0
  22. package/dist/flows/generated/flows.gen.d.ts.map +1 -0
  23. package/dist/flows/generated/flows.gen.js +23 -0
  24. package/dist/flows/generated/flows.gen.js.map +1 -0
  25. package/dist/headless/AcceptInvitation.d.ts +39 -0
  26. package/dist/headless/AcceptInvitation.d.ts.map +1 -0
  27. package/dist/headless/AcceptInvitation.js +27 -0
  28. package/dist/headless/AcceptInvitation.js.map +1 -0
  29. package/dist/headless/Members.d.ts +47 -0
  30. package/dist/headless/Members.d.ts.map +1 -0
  31. package/dist/headless/Members.js +50 -0
  32. package/dist/headless/Members.js.map +1 -0
  33. package/dist/headless/WorkspaceList.d.ts +38 -0
  34. package/dist/headless/WorkspaceList.d.ts.map +1 -0
  35. package/dist/headless/WorkspaceList.js +33 -0
  36. package/dist/headless/WorkspaceList.js.map +1 -0
  37. package/dist/headless/WorkspacesProvider.d.ts +18 -0
  38. package/dist/headless/WorkspacesProvider.d.ts.map +1 -0
  39. package/dist/headless/WorkspacesProvider.js +17 -0
  40. package/dist/headless/WorkspacesProvider.js.map +1 -0
  41. package/dist/i18n/errorsMap.d.ts +10 -0
  42. package/dist/i18n/errorsMap.d.ts.map +1 -0
  43. package/dist/i18n/errorsMap.js +20 -0
  44. package/dist/i18n/errorsMap.js.map +1 -0
  45. package/dist/i18n/generated/errors.gen.d.ts +347 -0
  46. package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
  47. package/dist/i18n/generated/errors.gen.js +177 -0
  48. package/dist/i18n/generated/errors.gen.js.map +1 -0
  49. package/dist/i18n/generated/errors.ru.gen.d.ts +9 -0
  50. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
  51. package/dist/i18n/generated/errors.ru.gen.js +61 -0
  52. package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
  53. package/dist/i18n/keys.d.ts +55 -0
  54. package/dist/i18n/keys.d.ts.map +1 -0
  55. package/dist/i18n/keys.js +76 -0
  56. package/dist/i18n/keys.js.map +1 -0
  57. package/dist/i18n/ru.d.ts +28 -0
  58. package/dist/i18n/ru.d.ts.map +1 -0
  59. package/dist/i18n/ru.js +49 -0
  60. package/dist/i18n/ru.js.map +1 -0
  61. package/dist/index.d.ts +38 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +36 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/model/context.d.ts +14 -0
  66. package/dist/model/context.d.ts.map +1 -0
  67. package/dist/model/context.js +21 -0
  68. package/dist/model/context.js.map +1 -0
  69. package/dist/model/mutations.d.ts +55 -0
  70. package/dist/model/mutations.d.ts.map +1 -0
  71. package/dist/model/mutations.js +138 -0
  72. package/dist/model/mutations.js.map +1 -0
  73. package/dist/model/queries.d.ts +21 -0
  74. package/dist/model/queries.d.ts.map +1 -0
  75. package/dist/model/queries.js +41 -0
  76. package/dist/model/queries.js.map +1 -0
  77. package/dist/model/queryKeys.d.ts +7 -0
  78. package/dist/model/queryKeys.d.ts.map +1 -0
  79. package/dist/model/queryKeys.js +15 -0
  80. package/dist/model/queryKeys.js.map +1 -0
  81. package/dist/model/runtime.d.ts +26 -0
  82. package/dist/model/runtime.d.ts.map +1 -0
  83. package/dist/model/runtime.js +18 -0
  84. package/dist/model/runtime.js.map +1 -0
  85. package/llms.txt +95 -0
  86. package/manifest.json +705 -0
  87. package/package.json +85 -0
  88. package/src/analytics/generated/events.json +7 -0
  89. package/src/api/extensions.ts +7 -0
  90. package/src/api/types.ts +58 -0
  91. package/src/api/workspacesApi.ts +139 -0
  92. package/src/flows/errors.ts +16 -0
  93. package/src/flows/generated/flows.gen.ts +38 -0
  94. package/src/flows/generated/flows.json +1 -0
  95. package/src/headless/AcceptInvitation.tsx +53 -0
  96. package/src/headless/Members.tsx +89 -0
  97. package/src/headless/WorkspaceList.tsx +58 -0
  98. package/src/headless/WorkspacesProvider.tsx +25 -0
  99. package/src/i18n/errorsMap.ts +31 -0
  100. package/src/i18n/generated/errors.gen.ts +208 -0
  101. package/src/i18n/generated/errors.json +394 -0
  102. package/src/i18n/generated/errors.ru.gen.ts +67 -0
  103. package/src/i18n/keys.ts +83 -0
  104. package/src/i18n/ru.ts +53 -0
  105. package/src/index.ts +113 -0
  106. package/src/model/context.tsx +29 -0
  107. package/src/model/mutations.ts +196 -0
  108. package/src/model/queries.ts +51 -0
  109. package/src/model/queryKeys.ts +20 -0
  110. package/src/model/runtime.ts +46 -0
  111. package/tsconfig.json +26 -0
@@ -0,0 +1,14 @@
1
+ import type { Context } from "react";
2
+ import type { Analytics } from "@stapel/core";
3
+ import type { WorkspacesApi } from "../api/workspacesApi.js";
4
+ import type { WorkspacesRuntime } from "./runtime.js";
5
+ /**
6
+ * The wired WorkspacesRuntime shared through React context by
7
+ * `<WorkspacesProvider>`. Hooks in `model/` and `headless/` read the singletons
8
+ * from here.
9
+ */
10
+ export declare const WorkspacesRuntimeContext: Context<WorkspacesRuntime | null>;
11
+ export declare function useWorkspacesRuntime(): WorkspacesRuntime;
12
+ export declare function useWorkspacesApi(): WorkspacesApi;
13
+ export declare function useWorkspacesAnalytics(): Analytics | null;
14
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/model/context.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AACrC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,OAAO,CAAC,iBAAiB,GAAG,IAAI,CACxB,CAAC;AAEhD,wBAAgB,oBAAoB,IAAI,iBAAiB,CAMxD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD;AAED,wBAAgB,sBAAsB,IAAI,SAAS,GAAG,IAAI,CAEzD"}
@@ -0,0 +1,21 @@
1
+ import { createContext, useContext } from "react";
2
+ /**
3
+ * The wired WorkspacesRuntime shared through React context by
4
+ * `<WorkspacesProvider>`. Hooks in `model/` and `headless/` read the singletons
5
+ * from here.
6
+ */
7
+ export const WorkspacesRuntimeContext = createContext(null);
8
+ export function useWorkspacesRuntime() {
9
+ const runtime = useContext(WorkspacesRuntimeContext);
10
+ if (runtime === null) {
11
+ throw new Error("Workspaces hooks must be used within a <WorkspacesProvider>");
12
+ }
13
+ return runtime;
14
+ }
15
+ export function useWorkspacesApi() {
16
+ return useWorkspacesRuntime().api;
17
+ }
18
+ export function useWorkspacesAnalytics() {
19
+ return useWorkspacesRuntime().analytics;
20
+ }
21
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/model/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAMlD;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,aAAa,CAA2B,IAAI,CAAC,CAAC;AAEhD,MAAM,UAAU,oBAAoB;IAClC,MAAM,OAAO,GAAG,UAAU,CAAC,wBAAwB,CAAC,CAAC;IACrD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,oBAAoB,EAAE,CAAC,GAAG,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,oBAAoB,EAAE,CAAC,SAAS,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { UseMutationResult } from "@tanstack/react-query";
2
+ import type { StapelApiError } from "@stapel/core";
3
+ import type { Member, MemberInvite, MemberInviteResult, InvitationAccept, Workspace, WorkspaceCreate, WorkspaceRole, WorkspaceUpdate } from "../api/types.js";
4
+ /**
5
+ * Write hooks (frontend-standard §2 — "мутации с инвалидацией"). Each mutation
6
+ * invalidates exactly the server state it can move. Membership and ownership are
7
+ * server truth (roles gate access in other services via the membership cache),
8
+ * so NONE of these are optimistic (frontend-core-architecture §2.6: optimistic
9
+ * is for local-echo UX, never for flows with server truth).
10
+ *
11
+ * Options are built as typed `UseMutationOptions` objects (not call-site
12
+ * generics) so `void` stays in type-reference position, which
13
+ * `no-invalid-void-type` permits. Hooks that act on one workspace take its id as
14
+ * a parameter (the mutation variable then carries only the payload).
15
+ */
16
+ /** Create a workspace (owner-seeded) — refreshes the workspace list. */
17
+ export declare function useCreateWorkspace(): UseMutationResult<Workspace, StapelApiError, WorkspaceCreate>;
18
+ /**
19
+ * Partially update a workspace — writes the fresh row into its detail cache and
20
+ * refreshes the list (name / slug may have changed there too).
21
+ */
22
+ export declare function useUpdateWorkspace(workspaceId: string): UseMutationResult<Workspace, StapelApiError, WorkspaceUpdate>;
23
+ /** Soft-delete a workspace (owner only) — drops its detail cache + refreshes the list. */
24
+ export declare function useDeleteWorkspace(): UseMutationResult<void, StapelApiError, string>;
25
+ /**
26
+ * Invite one or more emails to a workspace (admin+). Server truth — no optimism.
27
+ * A bad role or missing email surfaces as a localizable `StapelApiError`
28
+ * (`error.400.invalid_role`). Refreshes the member list (pending invites show
29
+ * there).
30
+ */
31
+ export declare function useInviteMembers(workspaceId: string): UseMutationResult<MemberInviteResult, StapelApiError, MemberInvite>;
32
+ /** The variable for {@link useUpdateMemberRole}: which member, which new role. */
33
+ export interface MemberRoleChange {
34
+ readonly userId: string;
35
+ readonly role: WorkspaceRole;
36
+ }
37
+ /**
38
+ * Change a member's role (admin+; the backend guards owner grants and the
39
+ * last-owner invariant, surfacing `error.403.last_owner_cannot_be_removed`).
40
+ * Refreshes the member list.
41
+ */
42
+ export declare function useUpdateMemberRole(workspaceId: string): UseMutationResult<Member, StapelApiError, MemberRoleChange>;
43
+ /**
44
+ * Remove a member (admin+; the last owner is protected by the backend). The
45
+ * variable is the member's `userId`. Refreshes the member list.
46
+ */
47
+ export declare function useRemoveMember(workspaceId: string): UseMutationResult<void, StapelApiError, string>;
48
+ /**
49
+ * Accept an invitation by its token — the caller joins the workspace. A dead
50
+ * token surfaces a localizable `StapelApiError` (`error.400.invitation_expired`
51
+ * / `error.400.invitation_revoked` / `error.400.invitation_already_used`).
52
+ * Refreshes the workspace list (the new membership appears there).
53
+ */
54
+ export declare function useAcceptInvitation(): UseMutationResult<Member, StapelApiError, InvitationAccept>;
55
+ //# sourceMappingURL=mutations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../../src/model/mutations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,aAAa,EACb,eAAe,EAChB,MAAM,iBAAiB,CAAC;AAIzB;;;;;;;;;;;GAWG;AAEH,wEAAwE;AACxE,wBAAgB,kBAAkB,IAAI,iBAAiB,CACrD,SAAS,EACT,cAAc,EACd,eAAe,CAChB,CAaA;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,GAClB,iBAAiB,CAAC,SAAS,EAAE,cAAc,EAAE,eAAe,CAAC,CAiB/D;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,IAAI,iBAAiB,CACrD,IAAI,EACJ,cAAc,EACd,MAAM,CACP,CAeA;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAClB,iBAAiB,CAAC,kBAAkB,EAAE,cAAc,EAAE,YAAY,CAAC,CAgBrE;AAED,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,GAClB,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAa7D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,GAClB,iBAAiB,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,CAYjD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,iBAAiB,CACtD,MAAM,EACN,cAAc,EACd,gBAAgB,CACjB,CAYA"}
@@ -0,0 +1,138 @@
1
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import { useWorkspacesApi } from "./context.js";
3
+ import { workspacesQueryKeys } from "./queryKeys.js";
4
+ /**
5
+ * Write hooks (frontend-standard §2 — "мутации с инвалидацией"). Each mutation
6
+ * invalidates exactly the server state it can move. Membership and ownership are
7
+ * server truth (roles gate access in other services via the membership cache),
8
+ * so NONE of these are optimistic (frontend-core-architecture §2.6: optimistic
9
+ * is for local-echo UX, never for flows with server truth).
10
+ *
11
+ * Options are built as typed `UseMutationOptions` objects (not call-site
12
+ * generics) so `void` stays in type-reference position, which
13
+ * `no-invalid-void-type` permits. Hooks that act on one workspace take its id as
14
+ * a parameter (the mutation variable then carries only the payload).
15
+ */
16
+ /** Create a workspace (owner-seeded) — refreshes the workspace list. */
17
+ export function useCreateWorkspace() {
18
+ const api = useWorkspacesApi();
19
+ const queryClient = useQueryClient();
20
+ const options = {
21
+ mutationFn: (body) => api.createWorkspace(body),
22
+ onSuccess: () => {
23
+ void queryClient.invalidateQueries({
24
+ queryKey: workspacesQueryKeys.list(),
25
+ });
26
+ },
27
+ };
28
+ return useMutation(options);
29
+ }
30
+ /**
31
+ * Partially update a workspace — writes the fresh row into its detail cache and
32
+ * refreshes the list (name / slug may have changed there too).
33
+ */
34
+ export function useUpdateWorkspace(workspaceId) {
35
+ const api = useWorkspacesApi();
36
+ const queryClient = useQueryClient();
37
+ const options = {
38
+ mutationFn: (patch) => api.updateWorkspace(workspaceId, patch),
39
+ onSuccess: (updated) => {
40
+ queryClient.setQueryData(workspacesQueryKeys.detail(workspaceId), updated);
41
+ void queryClient.invalidateQueries({
42
+ queryKey: workspacesQueryKeys.list(),
43
+ });
44
+ },
45
+ };
46
+ return useMutation(options);
47
+ }
48
+ /** Soft-delete a workspace (owner only) — drops its detail cache + refreshes the list. */
49
+ export function useDeleteWorkspace() {
50
+ const api = useWorkspacesApi();
51
+ const queryClient = useQueryClient();
52
+ const options = {
53
+ mutationFn: (workspaceId) => api.deleteWorkspace(workspaceId),
54
+ onSuccess: (_result, workspaceId) => {
55
+ queryClient.removeQueries({
56
+ queryKey: workspacesQueryKeys.detail(workspaceId),
57
+ });
58
+ void queryClient.invalidateQueries({
59
+ queryKey: workspacesQueryKeys.list(),
60
+ });
61
+ },
62
+ };
63
+ return useMutation(options);
64
+ }
65
+ /**
66
+ * Invite one or more emails to a workspace (admin+). Server truth — no optimism.
67
+ * A bad role or missing email surfaces as a localizable `StapelApiError`
68
+ * (`error.400.invalid_role`). Refreshes the member list (pending invites show
69
+ * there).
70
+ */
71
+ export function useInviteMembers(workspaceId) {
72
+ const api = useWorkspacesApi();
73
+ const queryClient = useQueryClient();
74
+ const options = {
75
+ mutationFn: (body) => api.inviteMembers(workspaceId, body),
76
+ onSuccess: () => {
77
+ void queryClient.invalidateQueries({
78
+ queryKey: workspacesQueryKeys.members(workspaceId),
79
+ });
80
+ },
81
+ };
82
+ return useMutation(options);
83
+ }
84
+ /**
85
+ * Change a member's role (admin+; the backend guards owner grants and the
86
+ * last-owner invariant, surfacing `error.403.last_owner_cannot_be_removed`).
87
+ * Refreshes the member list.
88
+ */
89
+ export function useUpdateMemberRole(workspaceId) {
90
+ const api = useWorkspacesApi();
91
+ const queryClient = useQueryClient();
92
+ const options = {
93
+ mutationFn: ({ userId, role }) => api.updateMemberRole(workspaceId, userId, { role }),
94
+ onSuccess: () => {
95
+ void queryClient.invalidateQueries({
96
+ queryKey: workspacesQueryKeys.members(workspaceId),
97
+ });
98
+ },
99
+ };
100
+ return useMutation(options);
101
+ }
102
+ /**
103
+ * Remove a member (admin+; the last owner is protected by the backend). The
104
+ * variable is the member's `userId`. Refreshes the member list.
105
+ */
106
+ export function useRemoveMember(workspaceId) {
107
+ const api = useWorkspacesApi();
108
+ const queryClient = useQueryClient();
109
+ const options = {
110
+ mutationFn: (userId) => api.removeMember(workspaceId, userId),
111
+ onSuccess: () => {
112
+ void queryClient.invalidateQueries({
113
+ queryKey: workspacesQueryKeys.members(workspaceId),
114
+ });
115
+ },
116
+ };
117
+ return useMutation(options);
118
+ }
119
+ /**
120
+ * Accept an invitation by its token — the caller joins the workspace. A dead
121
+ * token surfaces a localizable `StapelApiError` (`error.400.invitation_expired`
122
+ * / `error.400.invitation_revoked` / `error.400.invitation_already_used`).
123
+ * Refreshes the workspace list (the new membership appears there).
124
+ */
125
+ export function useAcceptInvitation() {
126
+ const api = useWorkspacesApi();
127
+ const queryClient = useQueryClient();
128
+ const options = {
129
+ mutationFn: (body) => api.acceptInvitation(body),
130
+ onSuccess: () => {
131
+ void queryClient.invalidateQueries({
132
+ queryKey: workspacesQueryKeys.list(),
133
+ });
134
+ },
135
+ };
136
+ return useMutation(options);
137
+ }
138
+ //# sourceMappingURL=mutations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutations.js","sourceRoot":"","sources":["../../src/model/mutations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { UseQueryResult } from "@tanstack/react-query";
2
+ import type { StapelApiError } from "@stapel/core";
3
+ import type { MemberList, Workspace, WorkspaceList } from "../api/types.js";
4
+ /**
5
+ * Read hooks over the workspaces API. Staleness follows core's query defaults;
6
+ * override per call site via a page that needs fresher data. Keys are
7
+ * namespaced (see `workspacesQueryKeys`).
8
+ */
9
+ /** The caller's workspaces — their accepted memberships (GET /). */
10
+ export declare function useWorkspaces(): UseQueryResult<WorkspaceList, StapelApiError>;
11
+ /**
12
+ * A single workspace by id (GET /{id}). Disabled until a `workspaceId` is
13
+ * given, so a detail hook can mount before a selection exists.
14
+ */
15
+ export declare function useWorkspace(workspaceId: string | null): UseQueryResult<Workspace, StapelApiError>;
16
+ /**
17
+ * A workspace's members (GET /{id}/members). Disabled until a `workspaceId` is
18
+ * given.
19
+ */
20
+ export declare function useMembers(workspaceId: string | null): UseQueryResult<MemberList, StapelApiError>;
21
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAI5E;;;;GAIG;AAEH,oEAAoE;AACpE,wBAAgB,aAAa,IAAI,cAAc,CAAC,aAAa,EAAE,cAAc,CAAC,CAM7E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,CAO3C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,cAAc,CAAC,UAAU,EAAE,cAAc,CAAC,CAO5C"}
@@ -0,0 +1,41 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { useWorkspacesApi } from "./context.js";
3
+ import { workspacesQueryKeys } from "./queryKeys.js";
4
+ /**
5
+ * Read hooks over the workspaces API. Staleness follows core's query defaults;
6
+ * override per call site via a page that needs fresher data. Keys are
7
+ * namespaced (see `workspacesQueryKeys`).
8
+ */
9
+ /** The caller's workspaces — their accepted memberships (GET /). */
10
+ export function useWorkspaces() {
11
+ const api = useWorkspacesApi();
12
+ return useQuery({
13
+ queryKey: workspacesQueryKeys.list(),
14
+ queryFn: () => api.listWorkspaces(),
15
+ });
16
+ }
17
+ /**
18
+ * A single workspace by id (GET /{id}). Disabled until a `workspaceId` is
19
+ * given, so a detail hook can mount before a selection exists.
20
+ */
21
+ export function useWorkspace(workspaceId) {
22
+ const api = useWorkspacesApi();
23
+ return useQuery({
24
+ queryKey: workspacesQueryKeys.detail(workspaceId ?? ""),
25
+ queryFn: () => api.getWorkspace(workspaceId),
26
+ enabled: workspaceId !== null && workspaceId !== "",
27
+ });
28
+ }
29
+ /**
30
+ * A workspace's members (GET /{id}/members). Disabled until a `workspaceId` is
31
+ * given.
32
+ */
33
+ export function useMembers(workspaceId) {
34
+ const api = useWorkspacesApi();
35
+ return useQuery({
36
+ queryKey: workspacesQueryKeys.members(workspaceId ?? ""),
37
+ queryFn: () => api.listMembers(workspaceId),
38
+ enabled: workspaceId !== null && workspaceId !== "",
39
+ });
40
+ }
41
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;;GAIG;AAEH,oEAAoE;AACpE,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,mBAAmB,CAAC,IAAI,EAAE;QACpC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,cAAc,EAAE;KACpC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,WAA0B;IAE1B,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,mBAAmB,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;QACvD,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,WAAqB,CAAC;QACtD,OAAO,EAAE,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,EAAE;KACpD,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,WAA0B;IAE1B,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC/B,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;QACxD,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,WAAqB,CAAC;QACrD,OAAO,EAAE,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,EAAE;KACpD,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare const workspacesQueryKeys: {
2
+ readonly all: readonly ["workspaces"];
3
+ list(): readonly ["workspaces", "list"];
4
+ detail(workspaceId: string): readonly ["workspaces", "detail", string];
5
+ members(workspaceId: string): readonly ["workspaces", "members", string];
6
+ };
7
+ //# sourceMappingURL=queryKeys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryKeys.d.ts","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,mBAAmB,EAAE;IAChC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,YAAY,CAAC,CAAC;IACtC,IAAI,IAAI,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACvE,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC,YAAY,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;CAM1E,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Namespaced TanStack Query keys (frontend-standard §2 — "ключи неймспейснуты").
3
+ * Everything under the `"workspaces"` root so a host can invalidate the whole
4
+ * module or match a single resource. Persist scope is per-user via core's query
5
+ * runtime (`setPersistUser`). Explicit tuple return types satisfy
6
+ * `--isolatedDeclarations`. One entry per read-operation.
7
+ */
8
+ const ROOT = "workspaces";
9
+ export const workspacesQueryKeys = {
10
+ all: [ROOT],
11
+ list: () => [ROOT, "list"],
12
+ detail: (workspaceId) => [ROOT, "detail", workspaceId],
13
+ members: (workspaceId) => [ROOT, "members", workspaceId],
14
+ };
15
+ //# sourceMappingURL=queryKeys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,IAAI,GAAG,YAAqB,CAAC;AAEnC,MAAM,CAAC,MAAM,mBAAmB,GAK5B;IACF,GAAG,EAAE,CAAC,IAAI,CAAC;IACX,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,CAAC;IACtD,OAAO,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,CAAC;CACzD,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { Analytics, StapelClient } from "@stapel/core";
2
+ import type { WorkspacesApi } from "../api/workspacesApi.js";
3
+ /**
4
+ * The wired workspaces runtime — builds a {@link StapelClient} and the pair's
5
+ * API over it. The returned `client` is what the host injects into core's
6
+ * `StapelConfigProvider` (as the default or the `"workspaces"` module client),
7
+ * preserving the client-injection fork seam (frontend-standard §7.2). Auth
8
+ * token/refresh and the verification-403 seam are supplied by the host's auth
9
+ * runtime on the shared client — this pair does not re-implement them.
10
+ */
11
+ export interface WorkspacesRuntime {
12
+ readonly client: StapelClient;
13
+ readonly api: WorkspacesApi;
14
+ readonly analytics: Analytics | null;
15
+ }
16
+ export interface CreateWorkspacesRuntimeOptions {
17
+ /** e.g. `/workspaces/api/` or `https://app.example.com/workspaces/api/`. */
18
+ readonly baseUrl: string;
19
+ readonly fetch?: typeof globalThis.fetch;
20
+ readonly credentials?: RequestCredentials;
21
+ readonly analytics?: Analytics | null;
22
+ /** Extra headers merged into every request (e.g. a tenant id). */
23
+ readonly defaultHeaders?: Record<string, string>;
24
+ }
25
+ export declare function createWorkspacesRuntime(options: CreateWorkspacesRuntimeOptions): WorkspacesRuntime;
26
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,8BAA8B;IAC7C,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACtC,kEAAkE;IAClE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClD;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,8BAA8B,GACtC,iBAAiB,CAcnB"}
@@ -0,0 +1,18 @@
1
+ import { createStapelClient } from "@stapel/core";
2
+ import { createWorkspacesApi } from "../api/workspacesApi.js";
3
+ export function createWorkspacesRuntime(options) {
4
+ const analytics = options.analytics ?? null;
5
+ const client = createStapelClient({
6
+ baseUrl: options.baseUrl,
7
+ ...(options.fetch !== undefined ? { fetch: options.fetch } : {}),
8
+ ...(options.credentials !== undefined
9
+ ? { credentials: options.credentials }
10
+ : {}),
11
+ ...(options.defaultHeaders !== undefined
12
+ ? { defaultHeaders: options.defaultHeaders }
13
+ : {}),
14
+ });
15
+ const api = createWorkspacesApi(client);
16
+ return { client, api, analytics };
17
+ }
18
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AA2B9D,MAAM,UAAU,uBAAuB,CACrC,OAAuC;IAEvC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;IAC5C,MAAM,MAAM,GAAG,kBAAkB,CAAC;QAChC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS;YACnC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE;YACtC,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,OAAO,CAAC,cAAc,KAAK,SAAS;YACtC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE;YAC5C,CAAC,CAAC,EAAE,CAAC;KACR,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;AACpC,CAAC"}
package/llms.txt ADDED
@@ -0,0 +1,95 @@
1
+ # @stapel/workspaces-react 0.1.0
2
+
3
+ Headless React flow pair for stapel-workspaces (contract >=0.3 <0.4) — business + state, zero visual opinion.
4
+ Built on @stapel/core: typed client + StapelApiError envelope, auth token refresh,
5
+ verification-403 interception, i18n engine, analytics facade, TanStack Query layer.
6
+
7
+ ## The one right way (do this, the rest is a review/lint smell)
8
+ - No raw fetch/axios. The client is injected via <WorkspacesProvider>/StapelConfigProvider;
9
+ every hook and flow already carries auth, refresh, and the error envelope.
10
+ - Render errors, never try/catch them: a flow's state carries FlowError{code,params};
11
+ render `t(code, params)` and branch on `explainWorkspacesError(code)` remediation.
12
+ - Server state = the use* hooks (query layer); keys come only from workspacesQueryKeys.
13
+ - User strings = i18n keys (registerWorkspacesI18n); never string literals.
14
+ - Sign-in UI = a headless flow component; copy it (shadcn-style) to restyle.
15
+
16
+ ## Layers
17
+ api (typed client) · model (hooks, session) · flows (machines) · headless · i18n
18
+
19
+ ## Documented flows (flows.json — canonical id, steps, endpoints)
20
+
21
+ ## Operations (typed; use the named op, never a path string)
22
+ Request/response schema names are in manifest.json + the generated types.
23
+ - workspaces_api_create: POST /workspaces/api/
24
+ - workspaces_api_destroy: DELETE /workspaces/api/{workspace_id}
25
+ - workspaces_api_internal_members_retrieve: GET /workspaces/api/internal/{workspace_id}/members/{user_id}
26
+ - workspaces_api_internal_users_personal_create: POST /workspaces/api/internal/users/{user_id}/personal
27
+ - workspaces_api_invitations_accept_create: POST /workspaces/api/invitations/accept
28
+ - workspaces_api_members_destroy: DELETE /workspaces/api/{workspace_id}/members/{user_id}
29
+ - workspaces_api_members_invite_create: POST /workspaces/api/{workspace_id}/members/invite
30
+ - workspaces_api_members_partial_update: PATCH /workspaces/api/{workspace_id}/members/{user_id}
31
+ - workspaces_api_members_retrieve: GET /workspaces/api/{workspace_id}/members
32
+ - workspaces_api_partial_update: PATCH /workspaces/api/{workspace_id}
33
+ - workspaces_api_retrieve: GET /workspaces/api/
34
+ - workspaces_api_retrieve_2: GET /workspaces/api/{workspace_id}
35
+
36
+ ## Query hooks (server state; keys come only from the key factory)
37
+ - useAcceptInvitation (mutation) → acceptInvitation
38
+ - useCreateWorkspace (mutation) → createWorkspace
39
+ - useDeleteWorkspace (mutation) → deleteWorkspace
40
+ - useInviteMembers (mutation) → inviteMembers
41
+ - useMembers (query) → listMembers
42
+ - useRemoveMember (mutation) → removeMember
43
+ - useUpdateMemberRole (mutation) → updateMemberRole
44
+ - useUpdateWorkspace (mutation) → updateWorkspace
45
+ - useWorkspace (query) → getWorkspace
46
+ - useWorkspaces (query) → listWorkspaces
47
+
48
+ ## Errors (render t(code, params); UX from remediation)
49
+ 52 keys (full catalog: manifest.json §errors). By remediation: fix_input 24 · retry 12 · contact_support 5 · verify 5 · wait_and_retry 5 · reauthenticate 1.
50
+ Param-bearing keys (interpolation slots matter):
51
+ - error.400.field.blank [400] → fix_input {field}
52
+ - error.400.field.does_not_exist [400] → fix_input {field}
53
+ - error.400.field.invalid [400] → fix_input {field}
54
+ - error.400.field.invalid_choice [400] → fix_input {field}
55
+ - error.400.field.max_length [400] → fix_input {field,max_length}
56
+ - error.400.field.max_value [400] → fix_input {field,max_value}
57
+ - error.400.field.min_length [400] → fix_input {field,min_length}
58
+ - error.400.field.min_value [400] → fix_input {field,min_value}
59
+ - error.400.field.null [400] → fix_input {field}
60
+ - error.400.field.required [400] → fix_input {field}
61
+ - error.400.field.unique [400] → fix_input {field}
62
+ - error.429.rate_limit [429] → wait_and_retry {retry_after_minutes}
63
+
64
+ ## Analytics events (typed; defineEvent → events.json, drift-gated)
65
+ - (no app defineEvent() in this pair — its analytic events are the
66
+ auto-instrumented flow funnels below)
67
+
68
+ ## Flow funnels (auto-instrumented: flow.<id>.<step> {phase})
69
+
70
+ ```tsx
71
+ // Typed event + tracked() click (the one right way; §3.1).
72
+ const planSelected = defineEvent({
73
+ name: "pricing.plan.selected",
74
+ description: "User picked a plan",
75
+ props: { plan: prop.oneOf(["free", "pro", "team"], "Plan code") },
76
+ });
77
+ const { tracked } = useTracked();
78
+ <Button onClick={tracked(planSelected, { plan }, startCheckout)} />
79
+ // A click that STEPS a flow machine is already instrumented — mark it
80
+ // data-analytics="flow" instead; tracked() on top double-counts (§3.2).
81
+ ```
82
+
83
+ ## Demos (defineDemo → manifest.demos; compiled, linted, rendered examples)
84
+ - workspaces.accept → <AcceptInvitation> [default] demo/AcceptInvitation.demo.tsx
85
+ - workspaces.list → <WorkspaceList> [default] demo/WorkspaceList.demo.tsx
86
+ - workspaces.members → <Members> [default] demo/Members.demo.tsx
87
+ - workspaces.provider → <WorkspacesProvider> [default] demo/Workspaces.demo.tsx
88
+ Each source file is the canonical usage snippet (open the default variant).
89
+
90
+ ## Snippets
91
+ ```tsx
92
+ // Error rendering + remediation branch (one pattern for every pair).
93
+ const r = explainWorkspacesError(err.code); // 'wait_and_retry' | 'verify' | ...
94
+ return <Alert action={r}>{t(err.code, err.params)}</Alert>;
95
+ ```