@fougere/react 0.5.0-alpha.1 → 0.7.0-alpha.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/dist/index.d.ts +1 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -14
- package/dist/index.js.map +1 -1
- package/dist/transport.d.ts +1 -5
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +1 -5
- package/dist/transport.js.map +1 -1
- package/dist/useCurrentUser.d.ts +1 -11
- package/dist/useCurrentUser.d.ts.map +1 -1
- package/dist/useCurrentUser.js +1 -11
- package/dist/useCurrentUser.js.map +1 -1
- package/dist/useFormFor.d.ts +2 -4
- package/dist/useFormFor.d.ts.map +1 -1
- package/dist/useFormFor.js +9 -28
- package/dist/useFormFor.js.map +1 -1
- package/dist/useFougereData.d.ts.map +1 -1
- package/dist/useFougereData.js +2 -9
- package/dist/useFougereData.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +1 -14
- package/src/transport.ts +1 -5
- package/src/useCurrentUser.ts +1 -11
- package/src/useFormFor.ts +9 -28
- package/src/useFougereData.ts +2 -9
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@fougere/react` — the four primitives, in React, for any host that renders it.
|
|
3
|
-
*
|
|
4
|
-
* It was extracted from `@fougere/next` once a second React host was on the table,
|
|
5
|
-
* and the extraction was cheap for a measurable reason: the hooks never imported
|
|
6
|
-
* Next. They reach `@fougere/app/client` and `react`, and talk to the server over
|
|
7
|
-
* `fetch` on same-origin paths — which is all a React app has in common with
|
|
8
|
-
* another React app.
|
|
9
|
-
*
|
|
10
|
-
* The Vue side has no counterpart on purpose. Nuxt's composables lean on
|
|
11
|
-
* `useAsyncData`, `useRequestFetch`, `useState` and `refreshNuxtData` — Nuxt's data
|
|
12
|
-
* layer, not Vue's — so a `@fougere/vue` would have to rebuild what Nuxt gives for
|
|
13
|
-
* free, and there is no second Vue host asking for it.
|
|
14
|
-
*/
|
|
1
|
+
/** `@fougere/react` — the four primitives, in React, for any host that renders it. */
|
|
15
2
|
export { useQuery, useCommand } from './useFougereData.js';
|
|
16
3
|
export { useFormFor, type FormOptions } from './useFormFor.js';
|
|
17
4
|
export { useCurrentUser, FougereSession } from './useCurrentUser.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,sFAAsF;AACtF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAExD,YAAY,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,SAAS,EACT,WAAW,GACZ,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
/**
|
|
3
|
-
* `@fougere/react` — the four primitives, in React, for any host that renders it.
|
|
4
|
-
*
|
|
5
|
-
* It was extracted from `@fougere/next` once a second React host was on the table,
|
|
6
|
-
* and the extraction was cheap for a measurable reason: the hooks never imported
|
|
7
|
-
* Next. They reach `@fougere/app/client` and `react`, and talk to the server over
|
|
8
|
-
* `fetch` on same-origin paths — which is all a React app has in common with
|
|
9
|
-
* another React app.
|
|
10
|
-
*
|
|
11
|
-
* The Vue side has no counterpart on purpose. Nuxt's composables lean on
|
|
12
|
-
* `useAsyncData`, `useRequestFetch`, `useState` and `refreshNuxtData` — Nuxt's data
|
|
13
|
-
* layer, not Vue's — so a `@fougere/vue` would have to rebuild what Nuxt gives for
|
|
14
|
-
* free, and there is no second Vue host asking for it.
|
|
15
|
-
*/
|
|
2
|
+
/** `@fougere/react` — the four primitives, in React, for any host that renders it. */
|
|
16
3
|
export { useQuery, useCommand } from './useFougereData.js';
|
|
17
4
|
export { useFormFor } from './useFormFor.js';
|
|
18
5
|
export { useCurrentUser, FougereSession } from './useCurrentUser.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb,sFAAsF;AACtF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAoB,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Kept as a re-export: the fetcher and the revalidation bus started here, and moving
|
|
3
|
-
* them into `@fougere/app/client` is what a second non-Nuxt client revealed — none
|
|
4
|
-
* of it was React. This file is the seam that used to hide that.
|
|
5
|
-
*/
|
|
1
|
+
/** Kept as a re-export. */
|
|
6
2
|
export { fetcher, onRefetch, revalidate, CALL_ENDPOINT } from '@fougere/app/client';
|
|
7
3
|
//# sourceMappingURL=transport.d.ts.map
|
package/dist/transport.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AACA
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AACA,2BAA2B;AAC3B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/transport.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
/**
|
|
3
|
-
* Kept as a re-export: the fetcher and the revalidation bus started here, and moving
|
|
4
|
-
* them into `@fougere/app/client` is what a second non-Nuxt client revealed — none
|
|
5
|
-
* of it was React. This file is the seam that used to hide that.
|
|
6
|
-
*/
|
|
2
|
+
/** Kept as a re-export. */
|
|
7
3
|
export { fetcher, onRefetch, revalidate, CALL_ENDPOINT } from '@fougere/app/client';
|
|
8
4
|
//# sourceMappingURL=transport.js.map
|
package/dist/transport.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb,2BAA2B;AAC3B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/useCurrentUser.d.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The session: resolved once server-side, handed down through the provider, read
|
|
3
|
-
* here as state. `refresh()` re-reads it after a client-side auth change (login,
|
|
4
|
-
* logout) — no page reload, no hand-rolled /api/me.
|
|
5
|
-
*
|
|
6
|
-
* `session` is the whole view (extensible context); `user` is its everyday
|
|
7
|
-
* projection. Nuxt hydrates through its own `useState` payload; React has no
|
|
8
|
-
* ambient equivalent, so a layout that already resolved the session passes it to
|
|
9
|
-
* `<FougereSession>` and the page arrives knowing its user. Without the provider
|
|
10
|
-
* the hook fetches once, which is correct and one round-trip slower.
|
|
11
|
-
*/
|
|
1
|
+
/** The session. */
|
|
12
2
|
import { type ReactNode } from 'react';
|
|
13
3
|
import type { SessionView } from '@fougere/app/client';
|
|
14
4
|
export declare function FougereSession({ value, children }: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCurrentUser.d.ts","sourceRoot":"","sources":["../src/useCurrentUser.ts"],"names":[],"mappings":"AACA
|
|
1
|
+
{"version":3,"file":"useCurrentUser.d.ts","sourceRoot":"","sources":["../src/useCurrentUser.ts"],"names":[],"mappings":"AACA,mBAAmB;AACnB,OAAO,EAA8E,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIvD,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,+FAE9F;AAED,wBAAgB,cAAc,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;UAe1B,KAAK,GAAG,IAAI;;mBAXR,OAAO,CAAC,IAAI,CAAC;EAepD"}
|
package/dist/useCurrentUser.js
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
/**
|
|
3
|
-
* The session: resolved once server-side, handed down through the provider, read
|
|
4
|
-
* here as state. `refresh()` re-reads it after a client-side auth change (login,
|
|
5
|
-
* logout) — no page reload, no hand-rolled /api/me.
|
|
6
|
-
*
|
|
7
|
-
* `session` is the whole view (extensible context); `user` is its everyday
|
|
8
|
-
* projection. Nuxt hydrates through its own `useState` payload; React has no
|
|
9
|
-
* ambient equivalent, so a layout that already resolved the session passes it to
|
|
10
|
-
* `<FougereSession>` and the page arrives knowing its user. Without the provider
|
|
11
|
-
* the hook fetches once, which is correct and one round-trip slower.
|
|
12
|
-
*/
|
|
2
|
+
/** The session. */
|
|
13
3
|
import { createContext, createElement, useCallback, useContext, useEffect, useState } from 'react';
|
|
14
4
|
const SessionContext = createContext(null);
|
|
15
5
|
export function FougereSession({ value, children }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCurrentUser.js","sourceRoot":"","sources":["../src/useCurrentUser.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb
|
|
1
|
+
{"version":3,"file":"useCurrentUser.js","sourceRoot":"","sources":["../src/useCurrentUser.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb,mBAAmB;AACnB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAGnH,MAAM,cAAc,GAAG,aAAa,CAAqB,IAAI,CAAC,CAAC;AAE/D,MAAM,UAAU,cAAc,CAAC,EAAE,KAAK,EAAE,QAAQ,EAA+C;IAC7F,OAAO,aAAa,CAAC,cAAc,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,QAAQ,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IAC5C,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAc,QAAQ,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAEhF,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACpD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAClD,UAAU,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAgB,CAAC,CAAC;IACrD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,QAAQ;YAAE,KAAK,OAAO,EAAE,CAAC;IAChC,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAExB,OAAO;QACL,OAAO;QACP,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAiB;QAC5C,QAAQ,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI;QAC9B,OAAO;KACR,CAAC;AACJ,CAAC"}
|
package/dist/useFormFor.d.ts
CHANGED
|
@@ -10,10 +10,8 @@ export interface FormOptions {
|
|
|
10
10
|
export declare function useFormFor<T = Record<string, unknown>>(entity: FormEntity, options?: FormOptions): {
|
|
11
11
|
fields: FormField[];
|
|
12
12
|
/**
|
|
13
|
-
* The same fields, keyed by name — a form that lays its inputs out by hand binds
|
|
14
|
-
*
|
|
15
|
-
* own. Without it, a page retypes `type="email"` next to a card that already says
|
|
16
|
-
* `format: 'email'`, and the browser enforces the page rather than the declaration.
|
|
13
|
+
* The same fields, keyed by name — a form that lays its inputs out by hand binds one at a time
|
|
14
|
+
* (`v-bind="fieldsByName.email.attrs"`), and still states no rule of its own.
|
|
17
15
|
*/
|
|
18
16
|
fieldsByName: Record<string, FormField>;
|
|
19
17
|
values: Record<string, unknown>;
|
package/dist/useFormFor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFormFor.d.ts","sourceRoot":"","sources":["../src/useFormFor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useFormFor.d.ts","sourceRoot":"","sources":["../src/useFormFor.ts"],"names":[],"mappings":"AAIA,OAAO,EAML,KAAK,UAAU,EACf,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,wBAAgB,UAAU,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,WAAgB;;IA+CjG;;;OAGG;;;qBApC+B,MAAM,SAAS,OAAO;;kBAYrB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;;IA+BpD,+EAA+E;;;EAIlF"}
|
package/dist/useFormFor.js
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
/**
|
|
3
|
-
* The form contract — state, validation, submission, error mapping. Never a
|
|
4
|
-
* widget: the page owns the rendering, this owns the mechanics.
|
|
5
|
-
*
|
|
6
|
-
* The cycle composes the other primitives: fields from the io axes, local
|
|
7
|
-
* pre-judgment with the same rules the handler enforces (one declaration, both
|
|
8
|
-
* sides), submission through the command (so the entity link revalidates mounted
|
|
9
|
-
* queries), and per-field errors in the same `{ path, message }` shape whoever
|
|
10
|
-
* judged.
|
|
11
|
-
*
|
|
12
|
-
* One visible difference from the Vue composable, and it is React's, not ours:
|
|
13
|
-
* Vue hands back a reactive object a template mutates in place, React state is
|
|
14
|
-
* replaced rather than mutated, so `setValue` is part of the contract here.
|
|
15
|
-
*/
|
|
2
|
+
/** The form contract — state, validation, submission, error mapping. */
|
|
16
3
|
import { useCallback, useMemo, useState } from 'react';
|
|
17
4
|
import { validationErrorsOf } from '@fougere/core/contract';
|
|
18
5
|
import { entityKeyOf, errorsByField, formFieldsOf, payloadOf, } from '@fougere/app/client';
|
|
@@ -30,22 +17,18 @@ export function useFormFor(entity, options = {}) {
|
|
|
30
17
|
const setValue = useCallback((name, value) => {
|
|
31
18
|
setValues((current) => ({ ...current, [name]: value }));
|
|
32
19
|
}, []);
|
|
33
|
-
/** Local pre-
|
|
34
|
-
const
|
|
20
|
+
/** Local pre-verdict — same rules as the handler, saves a lost round-trip. */
|
|
21
|
+
const validator = useCallback(() => {
|
|
35
22
|
const result = entity.validate(payloadOf(values));
|
|
36
23
|
setErrors(result.success ? {} : errorsByField(result.errors));
|
|
37
24
|
return result.success;
|
|
38
25
|
}, [entity, values]);
|
|
39
|
-
/**
|
|
40
|
-
* Judge locally, then send through the command. Returns the created/updated value,
|
|
41
|
-
* or null when a judge (either side) rejected — the errors land per field either
|
|
42
|
-
* way, the form never knows who judged.
|
|
43
|
-
*/
|
|
26
|
+
/** Validate locally, then send through the command. */
|
|
44
27
|
const submit = useCallback(async () => {
|
|
45
|
-
if (!
|
|
28
|
+
if (!validator())
|
|
46
29
|
return null;
|
|
47
30
|
try {
|
|
48
|
-
return await command.execute({ params: options.params,
|
|
31
|
+
return await command.execute({ params: options.params, input: payloadOf(values) });
|
|
49
32
|
}
|
|
50
33
|
catch (err) {
|
|
51
34
|
const refusals = validationErrorsOf(err);
|
|
@@ -55,15 +38,13 @@ export function useFormFor(entity, options = {}) {
|
|
|
55
38
|
}
|
|
56
39
|
throw err;
|
|
57
40
|
}
|
|
58
|
-
}, [
|
|
41
|
+
}, [validator, command, options.params, values]);
|
|
59
42
|
const fieldsByName = useMemo(() => Object.fromEntries(fields.map((field) => [field.name, field])), [fields]);
|
|
60
43
|
return {
|
|
61
44
|
fields,
|
|
62
45
|
/**
|
|
63
|
-
* The same fields, keyed by name — a form that lays its inputs out by hand binds
|
|
64
|
-
*
|
|
65
|
-
* own. Without it, a page retypes `type="email"` next to a card that already says
|
|
66
|
-
* `format: 'email'`, and the browser enforces the page rather than the declaration.
|
|
46
|
+
* The same fields, keyed by name — a form that lays its inputs out by hand binds one at a time
|
|
47
|
+
* (`v-bind="fieldsByName.email.attrs"`), and still states no rule of its own.
|
|
67
48
|
*/
|
|
68
49
|
fieldsByName,
|
|
69
50
|
values,
|
package/dist/useFormFor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFormFor.js","sourceRoot":"","sources":["../src/useFormFor.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb
|
|
1
|
+
{"version":3,"file":"useFormFor.js","sourceRoot":"","sources":["../src/useFormFor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFougereData.d.ts","sourceRoot":"","sources":["../src/useFougereData.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useFougereData.d.ts","sourceRoot":"","sources":["../src/useFougereData.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAWL,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAG7B,wBAAgB,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClD,MAAM,EAAE,WAAW,EACnB,EAAE,EAAE,MAAM,EACV,KAAK,CAAC,EAAE,SAAS,EACjB,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;;;mBAYQ,OAAO,CAAC,IAAI,CAAC;EAiCpD;AAED,wBAAgB,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM;sBAMpD,SAAS,KAAG,OAAO,CAAC,CAAC,CAAC;;;EAqBxC"}
|
package/dist/useFougereData.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
/**
|
|
3
|
-
* The couple — useQuery (reads) and useCommand (writes), the two dual gestures of
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* What the gestures ARE lives in `@fougere/app/client` and is shared with Vue:
|
|
7
|
-
* designation is class + verb, the cache key derives from designation plus input,
|
|
8
|
-
* and a successful command on an entity revalidates every mounted query on that
|
|
9
|
-
* entity. Nothing of that is restated here — this file is `useState` for the
|
|
10
|
-
* values, `useEffect` for the mount, and the bus in `transport.ts` where Nuxt
|
|
11
|
-
* calls `refreshNuxtData`.
|
|
3
|
+
* The couple — useQuery (reads) and useCommand (writes), the two dual gestures of a page talking
|
|
4
|
+
* to a Frond, in Vue.
|
|
12
5
|
*/
|
|
13
6
|
import { useCallback, useEffect, useState } from 'react';
|
|
14
7
|
import { asFougereError, callOf, entityKeyOf, invocationOf, itemsOf, mountedKeys, pageOf, queryKeyOf, sendCall, trackQuery, } from '@fougere/app/client';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFougereData.js","sourceRoot":"","sources":["../src/useFougereData.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb
|
|
1
|
+
{"version":3,"file":"useFougereData.js","sourceRoot":"","sources":["../src/useFougereData.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb;;;GAGG;AACH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,EACL,cAAc,EACd,MAAM,EACN,WAAW,EACX,YAAY,EACZ,OAAO,EACP,WAAW,EACX,MAAM,EACN,UAAU,EACV,QAAQ,EACR,UAAU,GAGX,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEhE,MAAM,UAAU,QAAQ,CACtB,MAAmB,EACnB,EAAU,EACV,KAAiB,EACjB,IAA8B;IAE9B,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,kFAAkF;IAClF,+EAA+E;IAC/E,MAAM,GAAG,GAAG,UAAU,CAAC,SAAS,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,IAAI,EAAE,SAAS,KAAK,KAAK,CAAC;IAE5C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAW,IAAI,CAAC,CAAC;IACjD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAsB,IAAI,CAAC,CAAC;IAE9D,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACpD,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,OAAO,CAAC,CAAC,MAAM,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAM,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;QAC/C,CAAC;gBAAS,CAAC;YACT,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;QACD,oDAAoD;QACpD,uDAAuD;IACzD,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAEV,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;QACjD,IAAI,SAAS;YAAE,KAAK,OAAO,EAAE,CAAC;QAC9B,OAAO,GAAG,EAAE;YACV,OAAO,EAAE,CAAC;YACV,GAAG,EAAE,CAAC;QACR,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAEzC,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,OAAO,CAAI,IAAI,CAAC;QACvB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK;QACzB,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO;QAC7B,OAAO;QACP,KAAK;QACL,OAAO;KACR,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAc,MAAmB,EAAE,EAAU;IACrE,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAsB,IAAI,CAAC,CAAC;IAE9D,MAAM,OAAO,GAAG,WAAW,CACzB,KAAK,EAAE,KAAiB,EAAc,EAAE;QACtC,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAM,CAAC;YACvF,2EAA2E;YAC3E,UAAU,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;YACnC,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;YACnD,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClB,MAAM,OAAO,CAAC;QAChB,CAAC;gBAAS,CAAC;YACT,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;QACD,uDAAuD;IACzD,CAAC,EACD,CAAC,SAAS,EAAE,EAAE,CAAC,CAChB,CAAC;IAEF,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACrC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-alpha.0",
|
|
4
4
|
"description": "Fougere's four primitives as React hooks — host-independent.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"src"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@fougere/app": "0.
|
|
34
|
-
"@fougere/core": "0.
|
|
33
|
+
"@fougere/app": "0.7.0-alpha.0",
|
|
34
|
+
"@fougere/core": "0.7.0-alpha.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/react": "^19.2.2",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@testing-library/react": "^16.3.0",
|
|
41
41
|
"jsdom": "^27.0.0",
|
|
42
42
|
"react-dom": "19.2.8",
|
|
43
|
-
"@fougere/schema": "0.
|
|
43
|
+
"@fougere/schema": "0.7.0-alpha.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"react": "^18.3.0 || ^19.0.0"
|
package/src/index.ts
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
/**
|
|
3
|
-
* `@fougere/react` — the four primitives, in React, for any host that renders it.
|
|
4
|
-
*
|
|
5
|
-
* It was extracted from `@fougere/next` once a second React host was on the table,
|
|
6
|
-
* and the extraction was cheap for a measurable reason: the hooks never imported
|
|
7
|
-
* Next. They reach `@fougere/app/client` and `react`, and talk to the server over
|
|
8
|
-
* `fetch` on same-origin paths — which is all a React app has in common with
|
|
9
|
-
* another React app.
|
|
10
|
-
*
|
|
11
|
-
* The Vue side has no counterpart on purpose. Nuxt's composables lean on
|
|
12
|
-
* `useAsyncData`, `useRequestFetch`, `useState` and `refreshNuxtData` — Nuxt's data
|
|
13
|
-
* layer, not Vue's — so a `@fougere/vue` would have to rebuild what Nuxt gives for
|
|
14
|
-
* free, and there is no second Vue host asking for it.
|
|
15
|
-
*/
|
|
2
|
+
/** `@fougere/react` — the four primitives, in React, for any host that renders it. */
|
|
16
3
|
export { useQuery, useCommand } from './useFougereData.js';
|
|
17
4
|
export { useFormFor, type FormOptions } from './useFormFor.js';
|
|
18
5
|
export { useCurrentUser, FougereSession } from './useCurrentUser.js';
|
package/src/transport.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
/**
|
|
3
|
-
* Kept as a re-export: the fetcher and the revalidation bus started here, and moving
|
|
4
|
-
* them into `@fougere/app/client` is what a second non-Nuxt client revealed — none
|
|
5
|
-
* of it was React. This file is the seam that used to hide that.
|
|
6
|
-
*/
|
|
2
|
+
/** Kept as a re-export. */
|
|
7
3
|
export { fetcher, onRefetch, revalidate, CALL_ENDPOINT } from '@fougere/app/client';
|
package/src/useCurrentUser.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
/**
|
|
3
|
-
* The session: resolved once server-side, handed down through the provider, read
|
|
4
|
-
* here as state. `refresh()` re-reads it after a client-side auth change (login,
|
|
5
|
-
* logout) — no page reload, no hand-rolled /api/me.
|
|
6
|
-
*
|
|
7
|
-
* `session` is the whole view (extensible context); `user` is its everyday
|
|
8
|
-
* projection. Nuxt hydrates through its own `useState` payload; React has no
|
|
9
|
-
* ambient equivalent, so a layout that already resolved the session passes it to
|
|
10
|
-
* `<FougereSession>` and the page arrives knowing its user. Without the provider
|
|
11
|
-
* the hook fetches once, which is correct and one round-trip slower.
|
|
12
|
-
*/
|
|
2
|
+
/** The session. */
|
|
13
3
|
import { createContext, createElement, useCallback, useContext, useEffect, useState, type ReactNode } from 'react';
|
|
14
4
|
import type { SessionView } from '@fougere/app/client';
|
|
15
5
|
|
package/src/useFormFor.ts
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
/**
|
|
3
|
-
* The form contract — state, validation, submission, error mapping. Never a
|
|
4
|
-
* widget: the page owns the rendering, this owns the mechanics.
|
|
5
|
-
*
|
|
6
|
-
* The cycle composes the other primitives: fields from the io axes, local
|
|
7
|
-
* pre-judgment with the same rules the handler enforces (one declaration, both
|
|
8
|
-
* sides), submission through the command (so the entity link revalidates mounted
|
|
9
|
-
* queries), and per-field errors in the same `{ path, message }` shape whoever
|
|
10
|
-
* judged.
|
|
11
|
-
*
|
|
12
|
-
* One visible difference from the Vue composable, and it is React's, not ours:
|
|
13
|
-
* Vue hands back a reactive object a template mutates in place, React state is
|
|
14
|
-
* replaced rather than mutated, so `setValue` is part of the contract here.
|
|
15
|
-
*/
|
|
2
|
+
/** The form contract — state, validation, submission, error mapping. */
|
|
16
3
|
import { useCallback, useMemo, useState } from 'react';
|
|
17
4
|
import { validationErrorsOf } from '@fougere/core/contract';
|
|
18
5
|
import {
|
|
@@ -53,22 +40,18 @@ export function useFormFor<T = Record<string, unknown>>(entity: FormEntity, opti
|
|
|
53
40
|
setValues((current) => ({ ...current, [name]: value }));
|
|
54
41
|
}, []);
|
|
55
42
|
|
|
56
|
-
/** Local pre-
|
|
57
|
-
const
|
|
43
|
+
/** Local pre-verdict — same rules as the handler, saves a lost round-trip. */
|
|
44
|
+
const validator = useCallback((): boolean => {
|
|
58
45
|
const result = entity.validate(payloadOf(values));
|
|
59
46
|
setErrors(result.success ? {} : errorsByField(result.errors));
|
|
60
47
|
return result.success;
|
|
61
48
|
}, [entity, values]);
|
|
62
49
|
|
|
63
|
-
/**
|
|
64
|
-
* Judge locally, then send through the command. Returns the created/updated value,
|
|
65
|
-
* or null when a judge (either side) rejected — the errors land per field either
|
|
66
|
-
* way, the form never knows who judged.
|
|
67
|
-
*/
|
|
50
|
+
/** Validate locally, then send through the command. */
|
|
68
51
|
const submit = useCallback(async (): Promise<T | null> => {
|
|
69
|
-
if (!
|
|
52
|
+
if (!validator()) return null;
|
|
70
53
|
try {
|
|
71
|
-
return await command.execute({ params: options.params,
|
|
54
|
+
return await command.execute({ params: options.params, input: payloadOf(values) });
|
|
72
55
|
} catch (err) {
|
|
73
56
|
const refusals = validationErrorsOf(err);
|
|
74
57
|
if (refusals) {
|
|
@@ -77,7 +60,7 @@ export function useFormFor<T = Record<string, unknown>>(entity: FormEntity, opti
|
|
|
77
60
|
}
|
|
78
61
|
throw err;
|
|
79
62
|
}
|
|
80
|
-
}, [
|
|
63
|
+
}, [validator, command, options.params, values]);
|
|
81
64
|
|
|
82
65
|
const fieldsByName = useMemo(
|
|
83
66
|
() => Object.fromEntries(fields.map((field) => [field.name, field])) as Record<string, FormField>,
|
|
@@ -87,10 +70,8 @@ export function useFormFor<T = Record<string, unknown>>(entity: FormEntity, opti
|
|
|
87
70
|
return {
|
|
88
71
|
fields,
|
|
89
72
|
/**
|
|
90
|
-
* The same fields, keyed by name — a form that lays its inputs out by hand binds
|
|
91
|
-
*
|
|
92
|
-
* own. Without it, a page retypes `type="email"` next to a card that already says
|
|
93
|
-
* `format: 'email'`, and the browser enforces the page rather than the declaration.
|
|
73
|
+
* The same fields, keyed by name — a form that lays its inputs out by hand binds one at a time
|
|
74
|
+
* (`v-bind="fieldsByName.email.attrs"`), and still states no rule of its own.
|
|
94
75
|
*/
|
|
95
76
|
fieldsByName,
|
|
96
77
|
values,
|
package/src/useFougereData.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
/**
|
|
3
|
-
* The couple — useQuery (reads) and useCommand (writes), the two dual gestures of
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* What the gestures ARE lives in `@fougere/app/client` and is shared with Vue:
|
|
7
|
-
* designation is class + verb, the cache key derives from designation plus input,
|
|
8
|
-
* and a successful command on an entity revalidates every mounted query on that
|
|
9
|
-
* entity. Nothing of that is restated here — this file is `useState` for the
|
|
10
|
-
* values, `useEffect` for the mount, and the bus in `transport.ts` where Nuxt
|
|
11
|
-
* calls `refreshNuxtData`.
|
|
3
|
+
* The couple — useQuery (reads) and useCommand (writes), the two dual gestures of a page talking
|
|
4
|
+
* to a Frond, in Vue.
|
|
12
5
|
*/
|
|
13
6
|
import { useCallback, useEffect, useState } from 'react';
|
|
14
7
|
import type { FougereError } from '@fougere/core/contract';
|