@uniweb/api 0.2.12 → 0.3.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 +2 -2
- package/package.json +2 -2
- package/src/client.js +21 -3
- package/src/index.js +1 -1
- package/src/mock/index.js +5 -2
- package/src/mock/store.js +23 -1
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ Ask before you draw. This is a synchronous read of the site's own configuration,
|
|
|
17
17
|
a probe — there is nothing to await.
|
|
18
18
|
|
|
19
19
|
```jsx
|
|
20
|
-
import {
|
|
20
|
+
import { isApiEnabled } from '@uniweb/kit'
|
|
21
21
|
|
|
22
|
-
if (!
|
|
22
|
+
if (!isApiEnabled()) return <StaticVersion />
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
⛔ **And when the answer is no, draw nothing** — not a disabled button, and not an
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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.24.
|
|
37
|
+
"@uniweb/core": "^0.24.3"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": "^19.0.0"
|
package/src/client.js
CHANGED
|
@@ -44,16 +44,34 @@ export function resolveBase(website) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Does this site have
|
|
47
|
+
* Does this site have an app backend — the `api` service?
|
|
48
48
|
*
|
|
49
49
|
* The question to ask before drawing a sign-in affordance or any control only a
|
|
50
50
|
* backend can answer. False means: draw nothing, or the static alternative the
|
|
51
51
|
* site already carries.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
53
|
+
* ⭐ **The website argument is OPTIONAL and defaults to the active one**, so this
|
|
54
|
+
* is the same call as `@uniweb/kit`'s `isApiEnabled()` — one predicate per
|
|
55
|
+
* service, no arguments, the website resolved for you. Pass one explicitly only
|
|
56
|
+
* when you already hold it, or are working outside a render.
|
|
57
|
+
*
|
|
58
|
+
* ⛔ **THE DEFAULT IS NOT A CONVENIENCE, IT CLOSES A TRAP.** For one commit this
|
|
59
|
+
* took a REQUIRED website while every doc and template showed the no-argument
|
|
60
|
+
* spelling — so `import { isApiEnabled } from '@uniweb/api'` followed by
|
|
61
|
+
* `isApiEnabled()` resolved `undefined`, returned `false` forever, and drew no
|
|
62
|
+
* sign-in UI on a site that had a backend. Silent, and indistinguishable from a
|
|
63
|
+
* site with no backend: exactly the invisible absence this predicate exists to
|
|
64
|
+
* prevent. Caught in review the same day; pinned by `tests/enabled.test.js`.
|
|
65
|
+
*
|
|
66
|
+
* ⛔ **Renamed from `isEnabled` (2026-09-10).** The old name said nothing about
|
|
67
|
+
* its subject and collided with three unrelated `isEnabled`s in the framework —
|
|
68
|
+
* including the field `useSearch()` returns, which made `uniweb doctor` read a
|
|
69
|
+
* search control as gated when it was gated on this instead.
|
|
70
|
+
*
|
|
71
|
+
* @param {object} [website] - defaults to the active website
|
|
54
72
|
* @returns {boolean}
|
|
55
73
|
*/
|
|
56
|
-
export function
|
|
74
|
+
export function isApiEnabled(website = getUniweb()?.activeWebsite ?? null) {
|
|
57
75
|
return resolveBase(website) !== null
|
|
58
76
|
}
|
|
59
77
|
|
package/src/index.js
CHANGED
package/src/mock/index.js
CHANGED
|
@@ -38,11 +38,14 @@ import { DEFAULT_SEED } from './seed.js'
|
|
|
38
38
|
*
|
|
39
39
|
* @param {object} [options]
|
|
40
40
|
* @param {object} [options.seed] - accounts, schemas and entities to start from
|
|
41
|
+
* @param {string} [options.signedInAs] - a seeded `username` to start signed in as, for a
|
|
42
|
+
* demo whose subject is the signed-in view. Development-only by construction, since
|
|
43
|
+
* this whole module is; throws if the username is not seeded.
|
|
41
44
|
* @param {string} [options.prefix] - the path the API is mounted under (default `/api`)
|
|
42
45
|
* @returns {{ fetch: (request: Request) => Promise<Response>, store: MockStore }}
|
|
43
46
|
*/
|
|
44
|
-
export function createMockBackend({ seed = DEFAULT_SEED, prefix = '/api' } = {}) {
|
|
45
|
-
const store = new MockStore(seed)
|
|
47
|
+
export function createMockBackend({ seed = DEFAULT_SEED, prefix = '/api', signedInAs = null } = {}) {
|
|
48
|
+
const store = new MockStore(seed, { signedInAs })
|
|
46
49
|
|
|
47
50
|
const json = (status, body) =>
|
|
48
51
|
new Response(body === undefined ? null : JSON.stringify(body), {
|
package/src/mock/store.js
CHANGED
|
@@ -33,8 +33,10 @@ export class MockStore {
|
|
|
33
33
|
* @param {object[]} [seed.accounts] - `{ username, password, handle, roles?, units? }`
|
|
34
34
|
* @param {object} [seed.schemas] - `{ '@/session': { creatable_by?, append_only? } }`
|
|
35
35
|
* @param {object[]} [seed.entities] - `{ uuid?, model, data?, items? }`
|
|
36
|
+
* @param {object} [options]
|
|
37
|
+
* @param {string} [options.signedInAs] - start with this account already signed in.
|
|
36
38
|
*/
|
|
37
|
-
constructor(seed = {}) {
|
|
39
|
+
constructor(seed = {}, { signedInAs = null } = {}) {
|
|
38
40
|
this.accounts = (seed.accounts || []).map((a) => ({
|
|
39
41
|
uuid: a.uuid || nextId('acct'),
|
|
40
42
|
username: a.username,
|
|
@@ -52,6 +54,26 @@ export class MockStore {
|
|
|
52
54
|
/** The one session. A mock serves one developer, so one is the honest number. */
|
|
53
55
|
this.session = null
|
|
54
56
|
this.resets = new Map()
|
|
57
|
+
|
|
58
|
+
// ⭐ `signedInAs` — start already signed in, for a demo whose whole point is the
|
|
59
|
+
// signed-in view. Without it a visitor must type credentials before seeing
|
|
60
|
+
// anything, which for a lived-in demo is the experience itself.
|
|
61
|
+
//
|
|
62
|
+
// ⛔ THROWS on an unknown username rather than leaving the session null. A typo
|
|
63
|
+
// here produces "why am I not logged in?" — a question with no visible cause, in
|
|
64
|
+
// the one place where the answer is a string three lines away. The mock is
|
|
65
|
+
// development-only, so failing at construction costs nothing and a silent
|
|
66
|
+
// anonymous session costs an afternoon.
|
|
67
|
+
if (signedInAs) {
|
|
68
|
+
const account = this.accounts.find((a) => a.username === signedInAs)
|
|
69
|
+
if (!account) {
|
|
70
|
+
const known = this.accounts.map((a) => a.username).join(', ') || '(none)'
|
|
71
|
+
throw new Error(
|
|
72
|
+
`[uniweb/api mock] signedInAs: '${signedInAs}' is not a seeded account. Seeded: ${known}`
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
this.session = { account, at: now() }
|
|
76
|
+
}
|
|
55
77
|
}
|
|
56
78
|
|
|
57
79
|
seedEntity({ uuid, model, data = {}, items = [], owner = null }) {
|