@uniweb/api 0.4.1 → 0.5.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/README.md +3 -2
- package/package.json +2 -2
- package/src/client.js +11 -1
- package/src/mock/store.js +15 -2
- package/src/wire.js +16 -7
package/README.md
CHANGED
|
@@ -53,8 +53,9 @@ function Account() {
|
|
|
53
53
|
|
|
54
54
|
`viewer` is flat — `viewer.uuid`, `viewer.username`, `viewer.handle`, and `viewer.roles`,
|
|
55
55
|
a list of `{ role, scope_unit_id }`. A member holds no roles (`[]`); the site's operator
|
|
56
|
-
holds `system_admin`. ⚠️ `viewer.
|
|
57
|
-
|
|
56
|
+
holds `system_admin`. ⚠️ `viewer.workspace` — the site service's home org,
|
|
57
|
+
`{ handle: 'home', … }` — is the same for every member, so it cannot tell an operator
|
|
58
|
+
from a member. To decide whether to show an edit control for
|
|
58
59
|
a particular record, read that record: `entity.can_edit` is the backend's own answer.
|
|
59
60
|
|
|
60
61
|
**Signing up** — `useSignUp().signUp({ username, email, password })` answers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The client for a Uniweb site's own backend — session, records, entities — imported by a foundation the way it imports @uniweb/kit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"node": ">=20.19"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@uniweb/core": "^0.29.
|
|
37
|
+
"@uniweb/core": "^0.29.8"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": "^19.0.0"
|
package/src/client.js
CHANGED
|
@@ -93,6 +93,16 @@ const LOADING = Object.freeze({ status: 'loading', viewer: null, error: null })
|
|
|
93
93
|
* from `website.config` on each call, so the editor's `Website.rebuild()`
|
|
94
94
|
* needs no hook here.
|
|
95
95
|
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* `/auth/me`'s `workspace` — `{ unit_uuid, handle }`, both null when none — as the
|
|
99
|
+
* viewer carries it: `{ unitUuid, handle }`, or `null` for none (`VIEWER`).
|
|
100
|
+
*/
|
|
101
|
+
function viewerWorkspace(ws) {
|
|
102
|
+
if (!ws || typeof ws !== 'object' || (ws.unit_uuid == null && ws.handle == null)) return null
|
|
103
|
+
return Object.freeze({ unitUuid: ws.unit_uuid ?? null, handle: ws.handle ?? null })
|
|
104
|
+
}
|
|
105
|
+
|
|
96
106
|
export class ApiClient {
|
|
97
107
|
/**
|
|
98
108
|
* @param {object} uniweb - the page's `Uniweb` singleton
|
|
@@ -307,7 +317,7 @@ export class ApiClient {
|
|
|
307
317
|
const viewer = Object.freeze({
|
|
308
318
|
...account,
|
|
309
319
|
roles: Array.isArray(me?.roles) ? me.roles : [],
|
|
310
|
-
|
|
320
|
+
workspace: viewerWorkspace(me?.workspace),
|
|
311
321
|
})
|
|
312
322
|
if (this._session.viewer && this._session.viewer.uuid !== viewer.uuid) this.forgetViewer()
|
|
313
323
|
return this.setSession({ status: 'authenticated', viewer, error: null })
|
package/src/mock/store.js
CHANGED
|
@@ -32,9 +32,20 @@ import { checkItemWrite, OUTCOME } from './schema-shape.js'
|
|
|
32
32
|
* same read), nested sections, second factors, reference fields.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
/** The unit every account of a site acts in —
|
|
35
|
+
/** The unit every account of a site acts in — an entity's `unit_id`. */
|
|
36
36
|
export const SITE_UNIT = 1
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The service's home org — `workspace` on `/auth/me`. A site's service is founded with
|
|
40
|
+
* one, handle `home`, and enrolls every account in it, the owner included, so a request
|
|
41
|
+
* that names no workspace lands there. Its own value: it is minted in the service's
|
|
42
|
+
* database, and has nothing to do with the workspace the site was authored in.
|
|
43
|
+
*/
|
|
44
|
+
export const HOME_ORG = Object.freeze({
|
|
45
|
+
unit_uuid: '01926d5e-0000-7000-8000-0000000000a0',
|
|
46
|
+
handle: 'home',
|
|
47
|
+
})
|
|
48
|
+
|
|
38
49
|
/** The gap between neighbouring items' order numbers, as the backend spaces them. */
|
|
39
50
|
const GAP = 1_000_000
|
|
40
51
|
|
|
@@ -169,7 +180,9 @@ export class MockStore {
|
|
|
169
180
|
return {
|
|
170
181
|
account: this.identity(account),
|
|
171
182
|
roles: account.operator ? [{ role: 'system_admin', scope_unit_id: null }] : [],
|
|
172
|
-
|
|
183
|
+
// A request that names no workspace — as this package's never do — lands in the
|
|
184
|
+
// home org, which every account belongs to. Both null would mean a non-member.
|
|
185
|
+
workspace: { ...HOME_ORG },
|
|
173
186
|
}
|
|
174
187
|
}
|
|
175
188
|
|
package/src/wire.js
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
* | a `409` was rebased onto "the first op with an item id" | only the item the stale `409` names (`item_id`, since 2026-09-18); an older backend names none, and a batch is then not rebased — never by guesswork |
|
|
39
39
|
* | a sign-up answered with the account | it answers `202 { status: 'verification_required', email }`; the account cannot sign in until verified (`403 Email Not Verified`) |
|
|
40
40
|
* | `acting_unit_id` was a membership signal | every signed-in member of a site acts in the same unit — it cannot tell an operator from a member; `roles` can |
|
|
41
|
+
* | the viewer carried `actingUnitId` | it carries `workspace` — where the request landed: on a site's service, its home org (2026-09-23; `acting_unit_id` leaves the wire) |
|
|
41
42
|
*
|
|
42
43
|
* @module @uniweb/api/wire
|
|
43
44
|
*/
|
|
@@ -113,7 +114,7 @@ export const AUTH = {
|
|
|
113
114
|
* | reset request | `{ email }` | `202 { status: 'reset_requested' }` whether or not the address is known |
|
|
114
115
|
* | reset confirm | `{ token, new_password, code? }` | `200 { reset: true }`; a bad token is `400 Validation`; `code` is required when a second factor is enrolled |
|
|
115
116
|
* | logout | none | `204`; with no session to end, `401` |
|
|
116
|
-
* | me | none | `200 { account: { uuid, username, handle }, roles,
|
|
117
|
+
* | me | none | `200 { account: { uuid, username, handle }, roles, workspace: { unit_uuid, handle } }` · `401` |
|
|
117
118
|
*/
|
|
118
119
|
export const AUTH_BODY = {
|
|
119
120
|
login: ['username', 'password'],
|
|
@@ -136,16 +137,24 @@ export const TOTP = {
|
|
|
136
137
|
* `roles` is a list of `{ role, scope_unit_id }` — `role` one of `system_admin`,
|
|
137
138
|
* `unit_admin`, `content_editor`, `user`. An ordinary member holds none: `[]`.
|
|
138
139
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
140
|
+
* `workspace` is `{ unit_uuid, handle }` — **the workspace the request works in**. This
|
|
141
|
+
* package's requests name none, and on a site's `api` service such a request lands in
|
|
142
|
+
* the service's **home org**, `handle: 'home'`, in which every account is enrolled.
|
|
143
|
+
* Both `null` there means an account that is not a member — a service account, a
|
|
144
|
+
* revoked membership. (On a backend with no home org, naming none is personal: both
|
|
145
|
+
* `null`.) The viewer carries it as `{ unitUuid, handle }`, or `null` when both are
|
|
146
|
+
* null. *(It replaced `acting_unit_id` on 2026-09-23. MEASURED by backend at the
|
|
147
|
+
* function the route serializes, 2026-09-23.)*
|
|
148
|
+
*
|
|
149
|
+
* ⛔ **Neither is an operator signal.** Every member of a site's service works in the
|
|
150
|
+
* same home org, so it reads the same for the operator and for someone who signed up a
|
|
151
|
+
* minute ago. The operator holds `system_admin`. Better than either: ask about the
|
|
152
|
+
* thing — a single-entity read carries `can_edit`, the write gate's own answer.
|
|
144
153
|
*/
|
|
145
154
|
export const VIEWER = {
|
|
146
155
|
account: 'account',
|
|
147
156
|
roles: 'roles',
|
|
148
|
-
|
|
157
|
+
workspace: 'workspace',
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
/**
|