@fougere/react 0.9.3-alpha.0 → 0.11.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 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/useFormFor.d.ts +1 -1
- package/dist/useFormFor.d.ts.map +1 -1
- package/dist/useFormFor.js +5 -3
- package/dist/useFormFor.js.map +1 -1
- package/dist/useFougereData.d.ts +8 -8
- package/dist/useFougereData.d.ts.map +1 -1
- package/dist/useFougereData.js +7 -7
- package/dist/useFougereData.js.map +1 -1
- package/package.json +6 -5
- package/src/index.ts +2 -0
- package/src/useFormFor.ts +5 -3
- package/src/useFougereData.ts +30 -17
- package/template/index.html +5 -0
- package/template/package.json +25 -0
- package/template/src/App.tsx +21 -0
- package/template/src/main.tsx +4 -0
- package/template/tsconfig.json +21 -0
- package/template/vite.config.ts +14 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** `@fougere/react` — the four primitives, in React, for any host that renders it. */
|
|
2
2
|
export { useQuery, useCommand } from './useFougereData.js';
|
|
3
|
+
export { facade, facadeOf } from '@fougere/app/client';
|
|
3
4
|
export { useFormFor, type FormOptions } from './useFormFor.js';
|
|
4
5
|
export { useCurrentUser, FougereSession } from './useCurrentUser.js';
|
|
5
6
|
export { fetcher, CALL_ENDPOINT } from './transport.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,sFAAsF;AACtF,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;
|
|
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;AAE3D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACvD,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,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
/** `@fougere/react` — the four primitives, in React, for any host that renders it. */
|
|
3
3
|
export { useQuery, useCommand } from './useFougereData.js';
|
|
4
|
+
// The facade a page names, and the one an entity class names for a form.
|
|
5
|
+
export { facade, facadeOf } from '@fougere/app/client';
|
|
4
6
|
export { useFormFor } from './useFormFor.js';
|
|
5
7
|
export { useCurrentUser, FougereSession } from './useCurrentUser.js';
|
|
6
8
|
export { fetcher, CALL_ENDPOINT } from './transport.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,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"}
|
|
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,yEAAyE;AACzE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACvD,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/useFormFor.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare function useFormFor<T = Record<string, unknown>>(entity: FormEnti
|
|
|
20
20
|
submit: () => Promise<T | null>;
|
|
21
21
|
loading: boolean;
|
|
22
22
|
/** Non-validation failure of the last submit (unreachable host, conflict…). */
|
|
23
|
-
error: import("@fougere/core/contract").FougereError | null;
|
|
23
|
+
error: import("@fougere/core/contract").FougereError<import("@fougere/core/contract").ErrorCode> | null;
|
|
24
24
|
valid: boolean;
|
|
25
25
|
};
|
|
26
26
|
//# sourceMappingURL=useFormFor.d.ts.map
|
package/dist/useFormFor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;;
|
|
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;;IAiDjG;;;OAGG;;;qBApC+B,MAAM,SAAS,OAAO;;kBAYrB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;;IA+BpD,+EAA+E;;;EAIlF"}
|
package/dist/useFormFor.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/** The form contract — state, validation, submission, error mapping. */
|
|
3
3
|
import { useCallback, useMemo, useState } from 'react';
|
|
4
4
|
import { validationErrorsOf } from '@fougere/core/contract';
|
|
5
|
-
import { entityKeyOf, errorsByField, formFieldsOf, payloadOf, } from '@fougere/app/client';
|
|
5
|
+
import { entityKeyOf, errorsByField, facadeOf, formFieldsOf, payloadOf, } from '@fougere/app/client';
|
|
6
6
|
import { useCommand } from './useFougereData.js';
|
|
7
7
|
export function useFormFor(entity, options = {}) {
|
|
8
8
|
const entityKey = entityKeyOf(entity);
|
|
@@ -13,7 +13,9 @@ export function useFormFor(entity, options = {}) {
|
|
|
13
13
|
// schema's own literal, shown rather than guessed by the page.
|
|
14
14
|
const [values, setValues] = useState(() => Object.fromEntries(fields.map((field) => [field.name, options.initial?.[field.name] ?? field.default])));
|
|
15
15
|
const [errors, setErrors] = useState({});
|
|
16
|
-
|
|
16
|
+
// A form is designated by its ENTITY — it is a set of fields — so the facade it submits to is
|
|
17
|
+
// an address with no handler type behind it, and `T` stays the caller's to state.
|
|
18
|
+
const command = useCommand(facadeOf(entity), options.op ?? 'create');
|
|
17
19
|
const setValue = useCallback((name, value) => {
|
|
18
20
|
setValues((current) => ({ ...current, [name]: value }));
|
|
19
21
|
}, []);
|
|
@@ -28,7 +30,7 @@ export function useFormFor(entity, options = {}) {
|
|
|
28
30
|
if (!validator())
|
|
29
31
|
return null;
|
|
30
32
|
try {
|
|
31
|
-
return await command.execute({ params: options.params, input: payloadOf(values) });
|
|
33
|
+
return (await command.execute({ params: options.params, input: payloadOf(values) }));
|
|
32
34
|
}
|
|
33
35
|
catch (err) {
|
|
34
36
|
const refusals = validationErrorsOf(err);
|
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,wEAAwE;AACxE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EACL,WAAW,EACX,aAAa,EACb,YAAY,EACZ,SAAS,
|
|
1
|
+
{"version":3,"file":"useFormFor.js","sourceRoot":"","sources":["../src/useFormFor.ts"],"names":[],"mappings":"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"}
|
package/dist/useFougereData.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import type { FougereError } from '@fougere/core/contract';
|
|
2
|
-
import { type CallInput, type
|
|
3
|
-
export declare function useQuery<
|
|
2
|
+
import { type CallInput, type Answer, type Refused, type FacadeName, type Rows } from '@fougere/app/client';
|
|
3
|
+
export declare function useQuery<Handler, Address extends string, Op extends keyof Handler & string>(facade: FacadeName<Handler, Address>, op: Op, input?: CallInput, opts?: {
|
|
4
4
|
immediate?: boolean;
|
|
5
5
|
}): {
|
|
6
|
-
data:
|
|
7
|
-
items:
|
|
6
|
+
data: Answer<Handler, Op> | null;
|
|
7
|
+
items: Rows<Answer<Handler, Op>>[];
|
|
8
8
|
total: number | undefined;
|
|
9
9
|
hasMore: boolean | undefined;
|
|
10
10
|
loading: boolean;
|
|
11
|
-
error: FougereError | null;
|
|
11
|
+
error: FougereError<Refused<Address, Op>> | null;
|
|
12
12
|
refresh: () => Promise<void>;
|
|
13
13
|
};
|
|
14
|
-
export declare function useCommand<
|
|
15
|
-
execute: (input?: CallInput) => Promise<
|
|
14
|
+
export declare function useCommand<Handler, Address extends string, Op extends keyof Handler & string>(facade: FacadeName<Handler, Address>, op: Op): {
|
|
15
|
+
execute: (input?: CallInput) => Promise<Answer<Handler, Op>>;
|
|
16
16
|
loading: boolean;
|
|
17
|
-
error: FougereError | null;
|
|
17
|
+
error: FougereError<Refused<Address, Op>> | null;
|
|
18
18
|
};
|
|
19
19
|
//# sourceMappingURL=useFougereData.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,MAAM,EACX,KAAK,OAAO,EACZ,KAAK,UAAU,EACf,KAAK,IAAI,EACV,MAAM,qBAAqB,CAAC;AAG7B,wBAAgB,QAAQ,CACtB,OAAO,EACP,OAAO,SAAS,MAAM,EACtB,EAAE,SAAS,MAAM,OAAO,GAAG,MAAM,EAEjC,MAAM,EAAE,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,EACpC,EAAE,EAAE,EAAE,EACN,KAAK,CAAC,EAAE,SAAS,EACjB,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;;;mBAaQ,OAAO,CAAC,IAAI,CAAC;EAiCpD;AAED,wBAAgB,UAAU,CACxB,OAAO,EACP,OAAO,SAAS,MAAM,EACtB,EAAE,SAAS,MAAM,OAAO,GAAG,MAAM,EACjC,MAAM,EAAE,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE;sBAO3B,SAAS,KAAG,OAAO,qBAAU;;;EAqB/C"}
|
package/dist/useFougereData.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* to a Frond, in Vue.
|
|
5
5
|
*/
|
|
6
6
|
import { useCallback, useEffect, useState } from 'react';
|
|
7
|
-
import { asFougereError, callOf,
|
|
7
|
+
import { asFougereError, callOf, addressOf, invocationOf, itemsOf, mountedKeys, pageOf, queryKeyOf, sendCall, trackQuery, } from '@fougere/app/client';
|
|
8
8
|
import { fetcher, onRefetch, revalidate } from './transport.js';
|
|
9
|
-
export function useQuery(
|
|
10
|
-
const entityKey =
|
|
9
|
+
export function useQuery(facade, op, input, opts) {
|
|
10
|
+
const entityKey = addressOf(facade);
|
|
11
11
|
// The key IS the dependency: an input literal is a new object on every render, so
|
|
12
12
|
// depending on it directly would refetch forever. Its serialization is stable.
|
|
13
13
|
const key = queryKeyOf(entityKey, op, input);
|
|
@@ -19,7 +19,7 @@ export function useQuery(entity, op, input, opts) {
|
|
|
19
19
|
setLoading(true);
|
|
20
20
|
setError(null);
|
|
21
21
|
try {
|
|
22
|
-
setData((await sendCall(fetcher, callOf(
|
|
22
|
+
setData((await sendCall(fetcher, callOf(facade, op), invocationOf(input))));
|
|
23
23
|
}
|
|
24
24
|
catch (err) {
|
|
25
25
|
setError(asFougereError(err, entityKey, op));
|
|
@@ -50,15 +50,15 @@ export function useQuery(entity, op, input, opts) {
|
|
|
50
50
|
refresh,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
export function useCommand(
|
|
54
|
-
const entityKey =
|
|
53
|
+
export function useCommand(facade, op) {
|
|
54
|
+
const entityKey = addressOf(facade);
|
|
55
55
|
const [loading, setLoading] = useState(false);
|
|
56
56
|
const [error, setError] = useState(null);
|
|
57
57
|
const execute = useCallback(async (input) => {
|
|
58
58
|
setLoading(true);
|
|
59
59
|
setError(null);
|
|
60
60
|
try {
|
|
61
|
-
const result = (await sendCall(fetcher, callOf(
|
|
61
|
+
const result = (await sendCall(fetcher, callOf(facade, op), invocationOf(input)));
|
|
62
62
|
// The link: same entity designated on both sides → revalidate its queries.
|
|
63
63
|
revalidate(mountedKeys(entityKey));
|
|
64
64
|
return result;
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,SAAS,EACT,YAAY,EACZ,OAAO,EACP,WAAW,EACX,MAAM,EACN,UAAU,EACV,QAAQ,EACR,UAAU,GAMX,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEhE,MAAM,UAAU,QAAQ,CAKtB,MAAoC,EACpC,EAAM,EACN,KAAiB,EACjB,IAA8B;IAG9B,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACpC,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,CAAkB,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAA4C,IAAI,CAAC,CAAC;IAEpF,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,CAAa,CAAC,CAAC;QAC1F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAuC,CAAC,CAAC;QACrF,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,CAAiB,IAAI,CAAC;QACpC,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,CAIxB,MAAoC,EAAE,EAAM;IAE5C,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAA4C,IAAI,CAAC,CAAC;IAEpF,MAAM,OAAO,GAAG,WAAW,CACzB,KAAK,EAAE,KAAiB,EAAqB,EAAE;QAC7C,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,CAAa,CAAC;YAC9F,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,CAAuC,CAAC;YACzF,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.11.0-alpha.0",
|
|
4
4
|
"description": "Fougere's four primitives as React hooks — host-independent.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -27,11 +27,12 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
|
-
"src"
|
|
30
|
+
"src",
|
|
31
|
+
"template"
|
|
31
32
|
],
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@fougere/core": "0.
|
|
34
|
-
"@fougere/app": "0.
|
|
34
|
+
"@fougere/core": "0.11.0-alpha.0",
|
|
35
|
+
"@fougere/app": "0.11.0-alpha.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/react": "^19.2.2",
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"@testing-library/react": "^16.3.0",
|
|
41
42
|
"jsdom": "^27.0.0",
|
|
42
43
|
"react-dom": "19.2.8",
|
|
43
|
-
"@fougere/schema": "0.
|
|
44
|
+
"@fougere/schema": "0.11.0-alpha.0"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
47
|
"react": "^18.3.0 || ^19.0.0"
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
/** `@fougere/react` — the four primitives, in React, for any host that renders it. */
|
|
3
3
|
export { useQuery, useCommand } from './useFougereData.js';
|
|
4
|
+
// The facade a page names, and the one an entity class names for a form.
|
|
5
|
+
export { facade, facadeOf } from '@fougere/app/client';
|
|
4
6
|
export { useFormFor, type FormOptions } from './useFormFor.js';
|
|
5
7
|
export { useCurrentUser, FougereSession } from './useCurrentUser.js';
|
|
6
8
|
export { fetcher, CALL_ENDPOINT } from './transport.js';
|
package/src/useFormFor.ts
CHANGED
|
@@ -5,9 +5,9 @@ import { validationErrorsOf } from '@fougere/core/contract';
|
|
|
5
5
|
import {
|
|
6
6
|
entityKeyOf,
|
|
7
7
|
errorsByField,
|
|
8
|
+
facadeOf,
|
|
8
9
|
formFieldsOf,
|
|
9
10
|
payloadOf,
|
|
10
|
-
type EntityClass,
|
|
11
11
|
type FormEntity,
|
|
12
12
|
type FormField,
|
|
13
13
|
} from '@fougere/app/client';
|
|
@@ -34,7 +34,9 @@ export function useFormFor<T = Record<string, unknown>>(entity: FormEntity, opti
|
|
|
34
34
|
Object.fromEntries(fields.map((field) => [field.name, options.initial?.[field.name] ?? field.default])),
|
|
35
35
|
);
|
|
36
36
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
37
|
-
|
|
37
|
+
// A form is designated by its ENTITY — it is a set of fields — so the facade it submits to is
|
|
38
|
+
// an address with no handler type behind it, and `T` stays the caller's to state.
|
|
39
|
+
const command = useCommand(facadeOf(entity), options.op ?? 'create');
|
|
38
40
|
|
|
39
41
|
const setValue = useCallback((name: string, value: unknown) => {
|
|
40
42
|
setValues((current) => ({ ...current, [name]: value }));
|
|
@@ -51,7 +53,7 @@ export function useFormFor<T = Record<string, unknown>>(entity: FormEntity, opti
|
|
|
51
53
|
const submit = useCallback(async (): Promise<T | null> => {
|
|
52
54
|
if (!validator()) return null;
|
|
53
55
|
try {
|
|
54
|
-
return await command.execute({ params: options.params, input: payloadOf(values) });
|
|
56
|
+
return (await command.execute({ params: options.params, input: payloadOf(values) })) as T;
|
|
55
57
|
} catch (err) {
|
|
56
58
|
const refusals = validationErrorsOf(err);
|
|
57
59
|
if (refusals) {
|
package/src/useFougereData.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { FougereError } from '@fougere/core/contract';
|
|
|
8
8
|
import {
|
|
9
9
|
asFougereError,
|
|
10
10
|
callOf,
|
|
11
|
-
|
|
11
|
+
addressOf,
|
|
12
12
|
invocationOf,
|
|
13
13
|
itemsOf,
|
|
14
14
|
mountedKeys,
|
|
@@ -17,33 +17,41 @@ import {
|
|
|
17
17
|
sendCall,
|
|
18
18
|
trackQuery,
|
|
19
19
|
type CallInput,
|
|
20
|
-
type
|
|
20
|
+
type Answer,
|
|
21
|
+
type Refused,
|
|
22
|
+
type FacadeName,
|
|
23
|
+
type Rows,
|
|
21
24
|
} from '@fougere/app/client';
|
|
22
25
|
import { fetcher, onRefetch, revalidate } from './transport.js';
|
|
23
26
|
|
|
24
|
-
export function useQuery<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
export function useQuery<
|
|
28
|
+
Handler,
|
|
29
|
+
Address extends string,
|
|
30
|
+
Op extends keyof Handler & string,
|
|
31
|
+
>(
|
|
32
|
+
facade: FacadeName<Handler, Address>,
|
|
33
|
+
op: Op,
|
|
27
34
|
input?: CallInput,
|
|
28
35
|
opts?: { immediate?: boolean },
|
|
29
36
|
) {
|
|
30
|
-
|
|
37
|
+
type Answered = Answer<Handler, Op>;
|
|
38
|
+
const entityKey = addressOf(facade);
|
|
31
39
|
// The key IS the dependency: an input literal is a new object on every render, so
|
|
32
40
|
// depending on it directly would refetch forever. Its serialization is stable.
|
|
33
41
|
const key = queryKeyOf(entityKey, op, input);
|
|
34
42
|
const immediate = opts?.immediate !== false;
|
|
35
43
|
|
|
36
|
-
const [data, setData] = useState<
|
|
44
|
+
const [data, setData] = useState<Answered | null>(null);
|
|
37
45
|
const [loading, setLoading] = useState(immediate);
|
|
38
|
-
const [error, setError] = useState<FougereError | null>(null);
|
|
46
|
+
const [error, setError] = useState<FougereError<Refused<Address, Op>> | null>(null);
|
|
39
47
|
|
|
40
48
|
const refresh = useCallback(async (): Promise<void> => {
|
|
41
49
|
setLoading(true);
|
|
42
50
|
setError(null);
|
|
43
51
|
try {
|
|
44
|
-
setData((await sendCall(fetcher, callOf(
|
|
52
|
+
setData((await sendCall(fetcher, callOf(facade, op), invocationOf(input))) as Answered);
|
|
45
53
|
} catch (err) {
|
|
46
|
-
setError(asFougereError(err, entityKey, op));
|
|
54
|
+
setError(asFougereError(err, entityKey, op) as FougereError<Refused<Address, Op>>);
|
|
47
55
|
} finally {
|
|
48
56
|
setLoading(false);
|
|
49
57
|
}
|
|
@@ -63,7 +71,7 @@ export function useQuery<T = Record<string, unknown>>(
|
|
|
63
71
|
|
|
64
72
|
return {
|
|
65
73
|
data,
|
|
66
|
-
items: itemsOf<
|
|
74
|
+
items: itemsOf<Rows<Answered>>(data),
|
|
67
75
|
total: pageOf(data).total,
|
|
68
76
|
hasMore: pageOf(data).hasMore,
|
|
69
77
|
loading,
|
|
@@ -72,22 +80,27 @@ export function useQuery<T = Record<string, unknown>>(
|
|
|
72
80
|
};
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
export function useCommand<
|
|
76
|
-
|
|
83
|
+
export function useCommand<
|
|
84
|
+
Handler,
|
|
85
|
+
Address extends string,
|
|
86
|
+
Op extends keyof Handler & string,
|
|
87
|
+
>(facade: FacadeName<Handler, Address>, op: Op) {
|
|
88
|
+
type Answered = Answer<Handler, Op>;
|
|
89
|
+
const entityKey = addressOf(facade);
|
|
77
90
|
const [loading, setLoading] = useState(false);
|
|
78
|
-
const [error, setError] = useState<FougereError | null>(null);
|
|
91
|
+
const [error, setError] = useState<FougereError<Refused<Address, Op>> | null>(null);
|
|
79
92
|
|
|
80
93
|
const execute = useCallback(
|
|
81
|
-
async (input?: CallInput): Promise<
|
|
94
|
+
async (input?: CallInput): Promise<Answered> => {
|
|
82
95
|
setLoading(true);
|
|
83
96
|
setError(null);
|
|
84
97
|
try {
|
|
85
|
-
const result = (await sendCall(fetcher, callOf(
|
|
98
|
+
const result = (await sendCall(fetcher, callOf(facade, op), invocationOf(input))) as Answered;
|
|
86
99
|
// The link: same entity designated on both sides → revalidate its queries.
|
|
87
100
|
revalidate(mountedKeys(entityKey));
|
|
88
101
|
return result;
|
|
89
102
|
} catch (err) {
|
|
90
|
-
const failure = asFougereError(err, entityKey, op)
|
|
103
|
+
const failure = asFougereError(err, entityKey, op) as FougereError<Refused<Address, Op>>;
|
|
91
104
|
setError(failure);
|
|
92
105
|
throw failure;
|
|
93
106
|
} finally {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fougere-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "vite build"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@fougere/core": "latest",
|
|
11
|
+
"@fougere/react": "latest",
|
|
12
|
+
"@fougere/schema": "latest",
|
|
13
|
+
"@fougere/defaults": "latest",
|
|
14
|
+
"@fougere/container": "latest",
|
|
15
|
+
"better-sqlite3": "^13.0.3",
|
|
16
|
+
"kysely": "^0.28.17",
|
|
17
|
+
"react": "^19.0.0",
|
|
18
|
+
"react-dom": "^19.0.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@fougere/vite": "latest",
|
|
22
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
23
|
+
"vite": "^8.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useQuery } from '@fougere/react';
|
|
2
|
+
// Written by the scan: one export per address, carrying the handler that answers there.
|
|
3
|
+
// Replace `post` with a facade your own frond serves.
|
|
4
|
+
import { post } from '@fronds/facade';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One read. `items` is typed by what the handler's `list` answers, and `error.code` by what
|
|
8
|
+
* it can refuse — neither is written here, and neither can drift from the handler.
|
|
9
|
+
*/
|
|
10
|
+
export default function App() {
|
|
11
|
+
const { items, loading, error } = useQuery(post, 'list');
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<main style={{ maxWidth: 720, margin: '3rem auto', fontFamily: 'system-ui' }}>
|
|
15
|
+
<h1>Fougere</h1>
|
|
16
|
+
{loading && <p>Loading…</p>}
|
|
17
|
+
{error && <p style={{ color: '#b00' }}>{error.message}</p>}
|
|
18
|
+
{items.map((row) => <pre key={String(row.id)}>{JSON.stringify(row, null, 2)}</pre>)}
|
|
19
|
+
</main>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"types": [
|
|
10
|
+
"node"
|
|
11
|
+
],
|
|
12
|
+
"paths": {
|
|
13
|
+
"@fronds/facade": [
|
|
14
|
+
"./.fougere/facade.generated.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"src"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { fougere } from '@fougere/vite';
|
|
3
|
+
import react from '@vitejs/plugin-react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `fougere()` states the two things any Vite-built host must know: the packages a boot loads
|
|
7
|
+
* at runtime, and a minifier that keeps class names — designation reads a class's `name`, and
|
|
8
|
+
* a renamed one designates an entity nobody hosts. It also writes `@fronds/facade`.
|
|
9
|
+
*/
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
// Fronds are shared at the workspace root, two levels up from apps/<name>.
|
|
12
|
+
root: '.',
|
|
13
|
+
plugins: [fougere(), react()],
|
|
14
|
+
});
|