@entifix/react-integration 0.1.0-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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/context/adapters-context/adapters-context.d.ts +4 -0
- package/dist/context/adapters-context/adapters-context.d.ts.map +1 -0
- package/dist/context/adapters-context/adapters-context.js +19 -0
- package/dist/context/adapters-context/adapters-context.js.map +1 -0
- package/dist/context/adapters-context/index.d.ts +2 -0
- package/dist/context/adapters-context/index.d.ts.map +1 -0
- package/dist/context/adapters-context/index.js +3 -0
- package/dist/context/adapters-context/index.js.map +1 -0
- package/dist/hooks/use-data-loading/index.d.ts +3 -0
- package/dist/hooks/use-data-loading/index.d.ts.map +1 -0
- package/dist/hooks/use-data-loading/index.js +4 -0
- package/dist/hooks/use-data-loading/index.js.map +1 -0
- package/dist/hooks/use-data-loading/use-data-loading.d.ts +26 -0
- package/dist/hooks/use-data-loading/use-data-loading.d.ts.map +1 -0
- package/dist/hooks/use-data-loading/use-data-loading.js +167 -0
- package/dist/hooks/use-data-loading/use-data-loading.js.map +1 -0
- package/dist/hooks/use-data-loading/use-data-loading.types.d.ts +53 -0
- package/dist/hooks/use-data-loading/use-data-loading.types.d.ts.map +1 -0
- package/dist/hooks/use-data-loading/use-data-loading.types.js +3 -0
- package/dist/hooks/use-data-loading/use-data-loading.types.js.map +1 -0
- package/dist/hooks/use-entity-form/index.d.ts +4 -0
- package/dist/hooks/use-entity-form/index.d.ts.map +1 -0
- package/dist/hooks/use-entity-form/index.js +5 -0
- package/dist/hooks/use-entity-form/index.js.map +1 -0
- package/dist/hooks/use-entity-form/use-entity-form.d.ts +31 -0
- package/dist/hooks/use-entity-form/use-entity-form.d.ts.map +1 -0
- package/dist/hooks/use-entity-form/use-entity-form.helpers.d.ts +136 -0
- package/dist/hooks/use-entity-form/use-entity-form.helpers.d.ts.map +1 -0
- package/dist/hooks/use-entity-form/use-entity-form.helpers.js +337 -0
- package/dist/hooks/use-entity-form/use-entity-form.helpers.js.map +1 -0
- package/dist/hooks/use-entity-form/use-entity-form.js +209 -0
- package/dist/hooks/use-entity-form/use-entity-form.js.map +1 -0
- package/dist/hooks/use-entity-form/use-entity-form.types.d.ts +173 -0
- package/dist/hooks/use-entity-form/use-entity-form.types.d.ts.map +1 -0
- package/dist/hooks/use-entity-form/use-entity-form.types.js +3 -0
- package/dist/hooks/use-entity-form/use-entity-form.types.js.map +1 -0
- package/dist/hooks/use-entity-link-resolver/index.d.ts +2 -0
- package/dist/hooks/use-entity-link-resolver/index.d.ts.map +1 -0
- package/dist/hooks/use-entity-link-resolver/index.js +3 -0
- package/dist/hooks/use-entity-link-resolver/index.js.map +1 -0
- package/dist/hooks/use-entity-link-resolver/use-entity-link-resolver.d.ts +17 -0
- package/dist/hooks/use-entity-link-resolver/use-entity-link-resolver.d.ts.map +1 -0
- package/dist/hooks/use-entity-link-resolver/use-entity-link-resolver.js +26 -0
- package/dist/hooks/use-entity-link-resolver/use-entity-link-resolver.js.map +1 -0
- package/dist/hooks/use-entity-link-source/index.d.ts +3 -0
- package/dist/hooks/use-entity-link-source/index.d.ts.map +1 -0
- package/dist/hooks/use-entity-link-source/index.js +4 -0
- package/dist/hooks/use-entity-link-source/index.js.map +1 -0
- package/dist/hooks/use-entity-link-source/use-entity-link-source.d.ts +25 -0
- package/dist/hooks/use-entity-link-source/use-entity-link-source.d.ts.map +1 -0
- package/dist/hooks/use-entity-link-source/use-entity-link-source.js +169 -0
- package/dist/hooks/use-entity-link-source/use-entity-link-source.js.map +1 -0
- package/dist/hooks/use-entity-link-source/use-entity-link-source.types.d.ts +49 -0
- package/dist/hooks/use-entity-link-source/use-entity-link-source.types.d.ts.map +1 -0
- package/dist/hooks/use-entity-link-source/use-entity-link-source.types.js +3 -0
- package/dist/hooks/use-entity-link-source/use-entity-link-source.types.js.map +1 -0
- package/dist/hooks/use-entity-mutation/index.d.ts +3 -0
- package/dist/hooks/use-entity-mutation/index.d.ts.map +1 -0
- package/dist/hooks/use-entity-mutation/index.js +4 -0
- package/dist/hooks/use-entity-mutation/index.js.map +1 -0
- package/dist/hooks/use-entity-mutation/use-entity-mutation.d.ts +22 -0
- package/dist/hooks/use-entity-mutation/use-entity-mutation.d.ts.map +1 -0
- package/dist/hooks/use-entity-mutation/use-entity-mutation.js +88 -0
- package/dist/hooks/use-entity-mutation/use-entity-mutation.js.map +1 -0
- package/dist/hooks/use-entity-mutation/use-entity-mutation.types.d.ts +19 -0
- package/dist/hooks/use-entity-mutation/use-entity-mutation.types.d.ts.map +1 -0
- package/dist/hooks/use-entity-mutation/use-entity-mutation.types.js +3 -0
- package/dist/hooks/use-entity-mutation/use-entity-mutation.types.js.map +1 -0
- package/dist/hooks/use-entity-record/index.d.ts +3 -0
- package/dist/hooks/use-entity-record/index.d.ts.map +1 -0
- package/dist/hooks/use-entity-record/index.js +4 -0
- package/dist/hooks/use-entity-record/index.js.map +1 -0
- package/dist/hooks/use-entity-record/use-entity-record.d.ts +21 -0
- package/dist/hooks/use-entity-record/use-entity-record.d.ts.map +1 -0
- package/dist/hooks/use-entity-record/use-entity-record.js +93 -0
- package/dist/hooks/use-entity-record/use-entity-record.js.map +1 -0
- package/dist/hooks/use-entity-record/use-entity-record.types.d.ts +22 -0
- package/dist/hooks/use-entity-record/use-entity-record.types.d.ts.map +1 -0
- package/dist/hooks/use-entity-record/use-entity-record.types.js +3 -0
- package/dist/hooks/use-entity-record/use-entity-record.types.js.map +1 -0
- package/dist/hooks/use-entity-use-cases/index.d.ts +2 -0
- package/dist/hooks/use-entity-use-cases/index.d.ts.map +1 -0
- package/dist/hooks/use-entity-use-cases/index.js +3 -0
- package/dist/hooks/use-entity-use-cases/index.js.map +1 -0
- package/dist/hooks/use-entity-use-cases/use-entity-use-cases.d.ts +25 -0
- package/dist/hooks/use-entity-use-cases/use-entity-use-cases.d.ts.map +1 -0
- package/dist/hooks/use-entity-use-cases/use-entity-use-cases.js +47 -0
- package/dist/hooks/use-entity-use-cases/use-entity-use-cases.js.map +1 -0
- package/dist/hooks/use-wizard/index.d.ts +3 -0
- package/dist/hooks/use-wizard/index.d.ts.map +1 -0
- package/dist/hooks/use-wizard/index.js +4 -0
- package/dist/hooks/use-wizard/index.js.map +1 -0
- package/dist/hooks/use-wizard/use-wizard.d.ts +19 -0
- package/dist/hooks/use-wizard/use-wizard.d.ts.map +1 -0
- package/dist/hooks/use-wizard/use-wizard.js +136 -0
- package/dist/hooks/use-wizard/use-wizard.js.map +1 -0
- package/dist/hooks/use-wizard/use-wizard.types.d.ts +87 -0
- package/dist/hooks/use-wizard/use-wizard.types.d.ts.map +1 -0
- package/dist/hooks/use-wizard/use-wizard.types.js +3 -0
- package/dist/hooks/use-wizard/use-wizard.types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/query/combine-filtering.d.ts +11 -0
- package/dist/query/combine-filtering.d.ts.map +1 -0
- package/dist/query/combine-filtering.js +18 -0
- package/dist/query/combine-filtering.js.map +1 -0
- package/dist/query/entity-query-key.d.ts +28 -0
- package/dist/query/entity-query-key.d.ts.map +1 -0
- package/dist/query/entity-query-key.js +28 -0
- package/dist/query/entity-query-key.js.map +1 -0
- package/dist/query/query-provider.d.ts +21 -0
- package/dist/query/query-provider.d.ts.map +1 -0
- package/dist/query/query-provider.js +35 -0
- package/dist/query/query-provider.js.map +1 -0
- package/dist/reactive/event-source-channel.d.ts +22 -0
- package/dist/reactive/event-source-channel.d.ts.map +1 -0
- package/dist/reactive/event-source-channel.js +93 -0
- package/dist/reactive/event-source-channel.js.map +1 -0
- package/dist/reactive/reactive-channel.d.ts +75 -0
- package/dist/reactive/reactive-channel.d.ts.map +1 -0
- package/dist/reactive/reactive-channel.js +55 -0
- package/dist/reactive/reactive-channel.js.map +1 -0
- package/dist/reactive/use-reactive-invalidation.d.ts +10 -0
- package/dist/reactive/use-reactive-invalidation.d.ts.map +1 -0
- package/dist/reactive/use-reactive-invalidation.js +25 -0
- package/dist/reactive/use-reactive-invalidation.js.map +1 -0
- package/dist/reactive/use-transaction-settlement.d.ts +41 -0
- package/dist/reactive/use-transaction-settlement.d.ts.map +1 -0
- package/dist/reactive/use-transaction-settlement.js +123 -0
- package/dist/reactive/use-transaction-settlement.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EntifixError, type Entity, type EntityId } from '@entifix/core';
|
|
2
|
+
import type { UseEntityMutationOptions } from './use-entity-mutation.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Runs the write use-cases from a component — the mutating counterpart to
|
|
5
|
+
* {@link useDataLoading}.
|
|
6
|
+
*
|
|
7
|
+
* Inputs are provided as tags at call time ({@link EntityTag} for a save,
|
|
8
|
+
* {@link EntityIdTag} for a delete), so the use-cases stay unaware of both React
|
|
9
|
+
* and the transport behind `ctx`.
|
|
10
|
+
*
|
|
11
|
+
* `save` resolves to the entity the *repository* returned rather than the one
|
|
12
|
+
* passed in. That distinction matters: a create round-trips through the store's
|
|
13
|
+
* id generation, so only the returned entity is addressable.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useEntityMutation<TEntity extends Entity, TContext>({ saveUc, deleteUc, ctx, }: UseEntityMutationOptions<TEntity, TContext>): {
|
|
16
|
+
save: (entity: TEntity) => Promise<TEntity | undefined>;
|
|
17
|
+
remove: (id: EntityId) => Promise<boolean>;
|
|
18
|
+
isSaving: boolean;
|
|
19
|
+
isDeleting: boolean;
|
|
20
|
+
error: EntifixError | undefined;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=use-entity-mutation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-entity-mutation.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-mutation/use-entity-mutation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzE,OAAO,KAAK,EAEV,wBAAwB,EAEzB,MAAM,gCAAgC,CAAC;AAexC;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,MAAM,EAAE,QAAQ,EAAE,EAClE,MAAM,EACN,QAAQ,EACR,GAAG,GACJ,EAAE,wBAAwB,CAAC,OAAO,EAAE,QAAQ,CAAC;mBAI3B,OAAO,KAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;iBAiB1C,QAAQ,KAAG,OAAO,CAAC,OAAO,CAAC;;;;EAiBzC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { EntityIdTag, EntityTag } from '@entifix/business';
|
|
3
|
+
import { Context, Effect } from 'effect';
|
|
4
|
+
import { useCallback, useReducer } from 'react';
|
|
5
|
+
const initialState = {
|
|
6
|
+
isSaving: false,
|
|
7
|
+
isDeleting: false,
|
|
8
|
+
error: undefined
|
|
9
|
+
};
|
|
10
|
+
function reducer(state, action) {
|
|
11
|
+
return _extends({}, state, action.set);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Runs the write use-cases from a component — the mutating counterpart to
|
|
15
|
+
* {@link useDataLoading}.
|
|
16
|
+
*
|
|
17
|
+
* Inputs are provided as tags at call time ({@link EntityTag} for a save,
|
|
18
|
+
* {@link EntityIdTag} for a delete), so the use-cases stay unaware of both React
|
|
19
|
+
* and the transport behind `ctx`.
|
|
20
|
+
*
|
|
21
|
+
* `save` resolves to the entity the *repository* returned rather than the one
|
|
22
|
+
* passed in. That distinction matters: a create round-trips through the store's
|
|
23
|
+
* id generation, so only the returned entity is addressable.
|
|
24
|
+
*/ export function useEntityMutation({ saveUc, deleteUc, ctx }) {
|
|
25
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
26
|
+
const save = useCallback(async (entity)=>{
|
|
27
|
+
dispatch({
|
|
28
|
+
set: {
|
|
29
|
+
isSaving: true,
|
|
30
|
+
error: undefined
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const saved = await Effect.runPromise(Effect.provide(saveUc, ctx.pipe(Context.add(EntityTag, entity))));
|
|
35
|
+
dispatch({
|
|
36
|
+
set: {
|
|
37
|
+
isSaving: false
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return saved;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
dispatch({
|
|
43
|
+
set: {
|
|
44
|
+
isSaving: false,
|
|
45
|
+
error: error
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}, [
|
|
51
|
+
saveUc,
|
|
52
|
+
ctx
|
|
53
|
+
]);
|
|
54
|
+
const remove = useCallback(async (id)=>{
|
|
55
|
+
dispatch({
|
|
56
|
+
set: {
|
|
57
|
+
isDeleting: true,
|
|
58
|
+
error: undefined
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
await Effect.runPromise(Effect.provide(deleteUc, ctx.pipe(Context.add(EntityIdTag, id))));
|
|
63
|
+
dispatch({
|
|
64
|
+
set: {
|
|
65
|
+
isDeleting: false
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return true;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
dispatch({
|
|
71
|
+
set: {
|
|
72
|
+
isDeleting: false,
|
|
73
|
+
error: error
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}, [
|
|
79
|
+
deleteUc,
|
|
80
|
+
ctx
|
|
81
|
+
]);
|
|
82
|
+
return _extends({}, state, {
|
|
83
|
+
save,
|
|
84
|
+
remove
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=use-entity-mutation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/use-entity-mutation/use-entity-mutation.ts"],"sourcesContent":["import { EntityIdTag, EntityTag } from '@entifix/business';\nimport { EntifixError, type Entity, type EntityId } from '@entifix/core';\nimport { Context, Effect } from 'effect';\nimport { useCallback, useReducer } from 'react';\n\nimport type {\n UseEntityMutationAction,\n UseEntityMutationOptions,\n UseEntityMutationState,\n} from './use-entity-mutation.types.js';\n\nconst initialState: UseEntityMutationState = {\n isSaving: false,\n isDeleting: false,\n error: undefined,\n};\n\nfunction reducer(\n state: UseEntityMutationState,\n action: UseEntityMutationAction,\n): UseEntityMutationState {\n return { ...state, ...action.set };\n}\n\n/**\n * Runs the write use-cases from a component — the mutating counterpart to\n * {@link useDataLoading}.\n *\n * Inputs are provided as tags at call time ({@link EntityTag} for a save,\n * {@link EntityIdTag} for a delete), so the use-cases stay unaware of both React\n * and the transport behind `ctx`.\n *\n * `save` resolves to the entity the *repository* returned rather than the one\n * passed in. That distinction matters: a create round-trips through the store's\n * id generation, so only the returned entity is addressable.\n */\nexport function useEntityMutation<TEntity extends Entity, TContext>({\n saveUc,\n deleteUc,\n ctx,\n}: UseEntityMutationOptions<TEntity, TContext>) {\n const [state, dispatch] = useReducer(reducer, initialState);\n\n const save = useCallback(\n async (entity: TEntity): Promise<TEntity | undefined> => {\n dispatch({ set: { isSaving: true, error: undefined } });\n try {\n const saved = await Effect.runPromise(\n Effect.provide(saveUc, ctx.pipe(Context.add(EntityTag, entity))),\n );\n dispatch({ set: { isSaving: false } });\n return saved;\n } catch (error) {\n dispatch({ set: { isSaving: false, error: error as EntifixError } });\n return undefined;\n }\n },\n [saveUc, ctx],\n );\n\n const remove = useCallback(\n async (id: EntityId): Promise<boolean> => {\n dispatch({ set: { isDeleting: true, error: undefined } });\n try {\n await Effect.runPromise(\n Effect.provide(deleteUc, ctx.pipe(Context.add(EntityIdTag, id))),\n );\n dispatch({ set: { isDeleting: false } });\n return true;\n } catch (error) {\n dispatch({ set: { isDeleting: false, error: error as EntifixError } });\n return false;\n }\n },\n [deleteUc, ctx],\n );\n\n return { ...state, save, remove };\n}\n"],"names":["EntityIdTag","EntityTag","Context","Effect","useCallback","useReducer","initialState","isSaving","isDeleting","error","undefined","reducer","state","action","set","useEntityMutation","saveUc","deleteUc","ctx","dispatch","save","entity","saved","runPromise","provide","pipe","add","remove","id"],"mappings":";AAAA,SAASA,WAAW,EAAEC,SAAS,QAAQ,oBAAoB;AAE3D,SAASC,OAAO,EAAEC,MAAM,QAAQ,SAAS;AACzC,SAASC,WAAW,EAAEC,UAAU,QAAQ,QAAQ;AAQhD,MAAMC,eAAuC;IAC3CC,UAAU;IACVC,YAAY;IACZC,OAAOC;AACT;AAEA,SAASC,QACPC,KAA6B,EAC7BC,MAA+B;IAE/B,OAAO,aAAKD,OAAUC,OAAOC,GAAG;AAClC;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,kBAAoD,EAClEC,MAAM,EACNC,QAAQ,EACRC,GAAG,EACyC;IAC5C,MAAM,CAACN,OAAOO,SAAS,GAAGd,WAAWM,SAASL;IAE9C,MAAMc,OAAOhB,YACX,OAAOiB;QACLF,SAAS;YAAEL,KAAK;gBAAEP,UAAU;gBAAME,OAAOC;YAAU;QAAE;QACrD,IAAI;YACF,MAAMY,QAAQ,MAAMnB,OAAOoB,UAAU,CACnCpB,OAAOqB,OAAO,CAACR,QAAQE,IAAIO,IAAI,CAACvB,QAAQwB,GAAG,CAACzB,WAAWoB;YAEzDF,SAAS;gBAAEL,KAAK;oBAAEP,UAAU;gBAAM;YAAE;YACpC,OAAOe;QACT,EAAE,OAAOb,OAAO;YACdU,SAAS;gBAAEL,KAAK;oBAAEP,UAAU;oBAAOE,OAAOA;gBAAsB;YAAE;YAClE,OAAOC;QACT;IACF,GACA;QAACM;QAAQE;KAAI;IAGf,MAAMS,SAASvB,YACb,OAAOwB;QACLT,SAAS;YAAEL,KAAK;gBAAEN,YAAY;gBAAMC,OAAOC;YAAU;QAAE;QACvD,IAAI;YACF,MAAMP,OAAOoB,UAAU,CACrBpB,OAAOqB,OAAO,CAACP,UAAUC,IAAIO,IAAI,CAACvB,QAAQwB,GAAG,CAAC1B,aAAa4B;YAE7DT,SAAS;gBAAEL,KAAK;oBAAEN,YAAY;gBAAM;YAAE;YACtC,OAAO;QACT,EAAE,OAAOC,OAAO;YACdU,SAAS;gBAAEL,KAAK;oBAAEN,YAAY;oBAAOC,OAAOA;gBAAsB;YAAE;YACpE,OAAO;QACT;IACF,GACA;QAACQ;QAAUC;KAAI;IAGjB,OAAO,aAAKN;QAAOQ;QAAMO;;AAC3B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { EntityIdTag, EntityTag } from '@entifix/business';
|
|
2
|
+
import { EntifixError, Entity } from '@entifix/core';
|
|
3
|
+
import { Context, Effect } from 'effect';
|
|
4
|
+
export interface UseEntityMutationState {
|
|
5
|
+
isSaving: boolean;
|
|
6
|
+
isDeleting: boolean;
|
|
7
|
+
error: EntifixError | undefined;
|
|
8
|
+
}
|
|
9
|
+
export type UseEntityMutationAction = {
|
|
10
|
+
set: Partial<UseEntityMutationState>;
|
|
11
|
+
};
|
|
12
|
+
export interface UseEntityMutationOptions<TEntity extends Entity, TContext> {
|
|
13
|
+
/** A save use-case; the hook supplies {@link EntityTag}. */
|
|
14
|
+
saveUc: Effect.Effect<TEntity, EntifixError, EntityTag | TContext>;
|
|
15
|
+
/** A delete use-case; the hook supplies {@link EntityIdTag}. */
|
|
16
|
+
deleteUc: Effect.Effect<void, EntifixError, EntityIdTag | TContext>;
|
|
17
|
+
ctx: Context.Context<TContext>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=use-entity-mutation.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-entity-mutation.types.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-mutation/use-entity-mutation.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEzC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,GAAG,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,WAAW,wBAAwB,CAAC,OAAO,SAAS,MAAM,EAAE,QAAQ;IACxE,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,GAAG,QAAQ,CAAC,CAAC;IACnE,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC;IACpE,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/use-entity-mutation/use-entity-mutation.types.ts"],"sourcesContent":["import { EntityIdTag, EntityTag } from '@entifix/business';\nimport { EntifixError, Entity } from '@entifix/core';\nimport { Context, Effect } from 'effect';\n\nexport interface UseEntityMutationState {\n isSaving: boolean;\n isDeleting: boolean;\n error: EntifixError | undefined;\n}\n\nexport type UseEntityMutationAction = {\n set: Partial<UseEntityMutationState>;\n};\n\nexport interface UseEntityMutationOptions<TEntity extends Entity, TContext> {\n /** A save use-case; the hook supplies {@link EntityTag}. */\n saveUc: Effect.Effect<TEntity, EntifixError, EntityTag | TContext>;\n /** A delete use-case; the hook supplies {@link EntityIdTag}. */\n deleteUc: Effect.Effect<void, EntifixError, EntityIdTag | TContext>;\n ctx: Context.Context<TContext>;\n}\n"],"names":[],"mappings":"AAcA,WAMC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-record/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/use-entity-record/index.ts"],"sourcesContent":["export * from './use-entity-record.js';\nexport * from './use-entity-record.types.js';\n"],"names":[],"mappings":"AAAA,cAAc,yBAAyB;AACvC,cAAc,+BAA+B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EntifixError, type Entity } from '@entifix/core';
|
|
2
|
+
import type { UseEntityRecordOptions } from './use-entity-record.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Loads a single entity by id — the read half of an entity form.
|
|
5
|
+
*
|
|
6
|
+
* The id is provided through {@link EntityIdTag} at call time, mirroring how
|
|
7
|
+
* {@link useDataLoading} provides the load request: the use-case stays
|
|
8
|
+
* environment-agnostic and the hook is the only place that knows it is running
|
|
9
|
+
* in React.
|
|
10
|
+
*
|
|
11
|
+
* When `id` is `undefined` the hook does not fetch. That is the create case —
|
|
12
|
+
* there is no record yet — and it is a deliberate no-op rather than an error.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useEntityRecord<TEntity extends Entity, TContext>({ uc, ctx, id, }: UseEntityRecordOptions<TEntity, TContext>): {
|
|
15
|
+
reload: () => void;
|
|
16
|
+
setEntity: (entity: TEntity | undefined) => void;
|
|
17
|
+
entity: TEntity | undefined;
|
|
18
|
+
isLoading: boolean;
|
|
19
|
+
error: EntifixError | undefined;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=use-entity-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-entity-record.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-record/use-entity-record.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAI1D,OAAO,KAAK,EAEV,sBAAsB,EAEvB,MAAM,8BAA8B,CAAC;AAmBtC;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,EAAE,QAAQ,EAAE,EAChE,EAAE,EACF,GAAG,EACH,EAAE,GACH,EAAE,sBAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC;;wBA8D/B,OAAO,GAAG,SAAS;;;;EAK/B"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { EntityIdTag } from '@entifix/business';
|
|
3
|
+
import { Context, Effect } from 'effect';
|
|
4
|
+
import { useCallback, useEffect, useReducer, useRef, useState } from 'react';
|
|
5
|
+
function getInitialState() {
|
|
6
|
+
return {
|
|
7
|
+
entity: undefined,
|
|
8
|
+
isLoading: false,
|
|
9
|
+
error: undefined
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function reducer(state, action) {
|
|
13
|
+
return _extends({}, state, action.set);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Loads a single entity by id — the read half of an entity form.
|
|
17
|
+
*
|
|
18
|
+
* The id is provided through {@link EntityIdTag} at call time, mirroring how
|
|
19
|
+
* {@link useDataLoading} provides the load request: the use-case stays
|
|
20
|
+
* environment-agnostic and the hook is the only place that knows it is running
|
|
21
|
+
* in React.
|
|
22
|
+
*
|
|
23
|
+
* When `id` is `undefined` the hook does not fetch. That is the create case —
|
|
24
|
+
* there is no record yet — and it is a deliberate no-op rather than an error.
|
|
25
|
+
*/ export function useEntityRecord({ uc, ctx, id }) {
|
|
26
|
+
const [state, dispatch] = useReducer(reducer, getInitialState());
|
|
27
|
+
// Bumped to re-run the fetch on demand.
|
|
28
|
+
const [reloadToken, setReloadToken] = useState(0);
|
|
29
|
+
// Callers build `uc`/`ctx` inline, so they are fresh objects on every render.
|
|
30
|
+
// Holding the latest in refs keeps them out of the fetch effect's
|
|
31
|
+
// dependencies: keying the fetch on their identity would re-run it on every
|
|
32
|
+
// render, and its dispatch would render again — an unbreakable loop. The
|
|
33
|
+
// fetch is keyed on `id`/`reloadToken`, which is what actually selects a
|
|
34
|
+
// record.
|
|
35
|
+
const ucRef = useRef(uc);
|
|
36
|
+
const ctxRef = useRef(ctx);
|
|
37
|
+
// Refs may not be written during render; this runs before the fetch effect
|
|
38
|
+
// below (effects fire in declaration order), so it always sees the latest.
|
|
39
|
+
useEffect(()=>{
|
|
40
|
+
ucRef.current = uc;
|
|
41
|
+
ctxRef.current = ctx;
|
|
42
|
+
});
|
|
43
|
+
useEffect(()=>{
|
|
44
|
+
if (id == null) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Guards against a slow response for a previous id landing after a newer one.
|
|
48
|
+
let active = true;
|
|
49
|
+
dispatch({
|
|
50
|
+
set: {
|
|
51
|
+
isLoading: true,
|
|
52
|
+
error: undefined
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
Effect.runPromise(Effect.provide(ucRef.current, ctxRef.current.pipe(Context.add(EntityIdTag, id)))).then((entity)=>{
|
|
56
|
+
if (active) {
|
|
57
|
+
dispatch({
|
|
58
|
+
set: {
|
|
59
|
+
entity,
|
|
60
|
+
isLoading: false
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}).catch((error)=>{
|
|
65
|
+
if (active) {
|
|
66
|
+
dispatch({
|
|
67
|
+
set: {
|
|
68
|
+
error: error,
|
|
69
|
+
isLoading: false
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return ()=>{
|
|
75
|
+
active = false;
|
|
76
|
+
};
|
|
77
|
+
}, [
|
|
78
|
+
id,
|
|
79
|
+
reloadToken
|
|
80
|
+
]);
|
|
81
|
+
const reload = useCallback(()=>setReloadToken((token)=>token + 1), []);
|
|
82
|
+
/** Replaces the held record, e.g. with the entity a save returned. */ const setEntity = useCallback((entity)=>dispatch({
|
|
83
|
+
set: {
|
|
84
|
+
entity
|
|
85
|
+
}
|
|
86
|
+
}), []);
|
|
87
|
+
return _extends({}, state, {
|
|
88
|
+
reload,
|
|
89
|
+
setEntity
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=use-entity-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/use-entity-record/use-entity-record.ts"],"sourcesContent":["import { EntityIdTag } from '@entifix/business';\nimport { EntifixError, type Entity } from '@entifix/core';\nimport { Context, Effect } from 'effect';\nimport { useCallback, useEffect, useReducer, useRef, useState } from 'react';\n\nimport type {\n UseEntityRecordAction,\n UseEntityRecordOptions,\n UseEntityRecordState,\n} from './use-entity-record.types.js';\n\nfunction getInitialState<\n TEntity extends Entity,\n>(): UseEntityRecordState<TEntity> {\n return {\n entity: undefined,\n isLoading: false,\n error: undefined,\n };\n}\n\nfunction reducer<TEntity extends Entity>(\n state: UseEntityRecordState<TEntity>,\n action: UseEntityRecordAction<TEntity>,\n): UseEntityRecordState<TEntity> {\n return { ...state, ...action.set };\n}\n\n/**\n * Loads a single entity by id — the read half of an entity form.\n *\n * The id is provided through {@link EntityIdTag} at call time, mirroring how\n * {@link useDataLoading} provides the load request: the use-case stays\n * environment-agnostic and the hook is the only place that knows it is running\n * in React.\n *\n * When `id` is `undefined` the hook does not fetch. That is the create case —\n * there is no record yet — and it is a deliberate no-op rather than an error.\n */\nexport function useEntityRecord<TEntity extends Entity, TContext>({\n uc,\n ctx,\n id,\n}: UseEntityRecordOptions<TEntity, TContext>) {\n const [state, dispatch] = useReducer(\n reducer as (\n state: UseEntityRecordState<TEntity>,\n action: UseEntityRecordAction<TEntity>,\n ) => UseEntityRecordState<TEntity>,\n getInitialState<TEntity>(),\n );\n // Bumped to re-run the fetch on demand.\n const [reloadToken, setReloadToken] = useState(0);\n\n // Callers build `uc`/`ctx` inline, so they are fresh objects on every render.\n // Holding the latest in refs keeps them out of the fetch effect's\n // dependencies: keying the fetch on their identity would re-run it on every\n // render, and its dispatch would render again — an unbreakable loop. The\n // fetch is keyed on `id`/`reloadToken`, which is what actually selects a\n // record.\n const ucRef = useRef(uc);\n const ctxRef = useRef(ctx);\n\n // Refs may not be written during render; this runs before the fetch effect\n // below (effects fire in declaration order), so it always sees the latest.\n useEffect(() => {\n ucRef.current = uc;\n ctxRef.current = ctx;\n });\n\n useEffect(() => {\n if (id == null) {\n return;\n }\n\n // Guards against a slow response for a previous id landing after a newer one.\n let active = true;\n dispatch({ set: { isLoading: true, error: undefined } });\n\n Effect.runPromise(\n Effect.provide(\n ucRef.current,\n ctxRef.current.pipe(Context.add(EntityIdTag, id)),\n ),\n )\n .then(entity => {\n if (active) {\n dispatch({ set: { entity, isLoading: false } });\n }\n })\n .catch((error: unknown) => {\n if (active) {\n dispatch({ set: { error: error as EntifixError, isLoading: false } });\n }\n });\n\n return () => {\n active = false;\n };\n }, [id, reloadToken]);\n\n const reload = useCallback(() => setReloadToken(token => token + 1), []);\n\n /** Replaces the held record, e.g. with the entity a save returned. */\n const setEntity = useCallback(\n (entity: TEntity | undefined) => dispatch({ set: { entity } }),\n [],\n );\n\n return { ...state, reload, setEntity };\n}\n"],"names":["EntityIdTag","Context","Effect","useCallback","useEffect","useReducer","useRef","useState","getInitialState","entity","undefined","isLoading","error","reducer","state","action","set","useEntityRecord","uc","ctx","id","dispatch","reloadToken","setReloadToken","ucRef","ctxRef","current","active","runPromise","provide","pipe","add","then","catch","reload","token","setEntity"],"mappings":";AAAA,SAASA,WAAW,QAAQ,oBAAoB;AAEhD,SAASC,OAAO,EAAEC,MAAM,QAAQ,SAAS;AACzC,SAASC,WAAW,EAAEC,SAAS,EAAEC,UAAU,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAQ;AAQ7E,SAASC;IAGP,OAAO;QACLC,QAAQC;QACRC,WAAW;QACXC,OAAOF;IACT;AACF;AAEA,SAASG,QACPC,KAAoC,EACpCC,MAAsC;IAEtC,OAAO,aAAKD,OAAUC,OAAOC,GAAG;AAClC;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,gBAAkD,EAChEC,EAAE,EACFC,GAAG,EACHC,EAAE,EACwC;IAC1C,MAAM,CAACN,OAAOO,SAAS,GAAGhB,WACxBQ,SAIAL;IAEF,wCAAwC;IACxC,MAAM,CAACc,aAAaC,eAAe,GAAGhB,SAAS;IAE/C,8EAA8E;IAC9E,kEAAkE;IAClE,4EAA4E;IAC5E,yEAAyE;IACzE,yEAAyE;IACzE,UAAU;IACV,MAAMiB,QAAQlB,OAAOY;IACrB,MAAMO,SAASnB,OAAOa;IAEtB,2EAA2E;IAC3E,2EAA2E;IAC3Ef,UAAU;QACRoB,MAAME,OAAO,GAAGR;QAChBO,OAAOC,OAAO,GAAGP;IACnB;IAEAf,UAAU;QACR,IAAIgB,MAAM,MAAM;YACd;QACF;QAEA,8EAA8E;QAC9E,IAAIO,SAAS;QACbN,SAAS;YAAEL,KAAK;gBAAEL,WAAW;gBAAMC,OAAOF;YAAU;QAAE;QAEtDR,OAAO0B,UAAU,CACf1B,OAAO2B,OAAO,CACZL,MAAME,OAAO,EACbD,OAAOC,OAAO,CAACI,IAAI,CAAC7B,QAAQ8B,GAAG,CAAC/B,aAAaoB,OAG9CY,IAAI,CAACvB,CAAAA;YACJ,IAAIkB,QAAQ;gBACVN,SAAS;oBAAEL,KAAK;wBAAEP;wBAAQE,WAAW;oBAAM;gBAAE;YAC/C;QACF,GACCsB,KAAK,CAAC,CAACrB;YACN,IAAIe,QAAQ;gBACVN,SAAS;oBAAEL,KAAK;wBAAEJ,OAAOA;wBAAuBD,WAAW;oBAAM;gBAAE;YACrE;QACF;QAEF,OAAO;YACLgB,SAAS;QACX;IACF,GAAG;QAACP;QAAIE;KAAY;IAEpB,MAAMY,SAAS/B,YAAY,IAAMoB,eAAeY,CAAAA,QAASA,QAAQ,IAAI,EAAE;IAEvE,oEAAoE,GACpE,MAAMC,YAAYjC,YAChB,CAACM,SAAgCY,SAAS;YAAEL,KAAK;gBAAEP;YAAO;QAAE,IAC5D,EAAE;IAGJ,OAAO,aAAKK;QAAOoB;QAAQE;;AAC7B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EntityIdTag } from '@entifix/business';
|
|
2
|
+
import { EntifixError, Entity, EntityId } from '@entifix/core';
|
|
3
|
+
import { Context, Effect } from 'effect';
|
|
4
|
+
export interface UseEntityRecordState<TEntity extends Entity> {
|
|
5
|
+
entity: TEntity | undefined;
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
error: EntifixError | undefined;
|
|
8
|
+
}
|
|
9
|
+
export type UseEntityRecordAction<TEntity extends Entity> = {
|
|
10
|
+
set: Partial<UseEntityRecordState<TEntity>>;
|
|
11
|
+
};
|
|
12
|
+
export interface UseEntityRecordOptions<TEntity extends Entity, TContext> {
|
|
13
|
+
/** A get use-case; the hook supplies {@link EntityIdTag}. */
|
|
14
|
+
uc: Effect.Effect<TEntity, EntifixError, EntityIdTag | TContext>;
|
|
15
|
+
ctx: Context.Context<TContext>;
|
|
16
|
+
/**
|
|
17
|
+
* The record to load. `undefined` means "nothing to load yet" — the create
|
|
18
|
+
* case — and the hook stays idle rather than fetching.
|
|
19
|
+
*/
|
|
20
|
+
id: EntityId;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=use-entity-record.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-entity-record.types.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-record/use-entity-record.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEzC,MAAM,WAAW,oBAAoB,CAAC,OAAO,SAAS,MAAM;IAC1D,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,MAAM,qBAAqB,CAAC,OAAO,SAAS,MAAM,IAAI;IAC1D,GAAG,EAAE,OAAO,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC;CAC7C,CAAC;AAEF,MAAM,WAAW,sBAAsB,CAAC,OAAO,SAAS,MAAM,EAAE,QAAQ;IACtE,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC;IACjE,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B;;;OAGG;IACH,EAAE,EAAE,QAAQ,CAAC;CACd"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/use-entity-record/use-entity-record.types.ts"],"sourcesContent":["import { EntityIdTag } from '@entifix/business';\nimport { EntifixError, Entity, EntityId } from '@entifix/core';\nimport { Context, Effect } from 'effect';\n\nexport interface UseEntityRecordState<TEntity extends Entity> {\n entity: TEntity | undefined;\n isLoading: boolean;\n error: EntifixError | undefined;\n}\n\nexport type UseEntityRecordAction<TEntity extends Entity> = {\n set: Partial<UseEntityRecordState<TEntity>>;\n};\n\nexport interface UseEntityRecordOptions<TEntity extends Entity, TContext> {\n /** A get use-case; the hook supplies {@link EntityIdTag}. */\n uc: Effect.Effect<TEntity, EntifixError, EntityIdTag | TContext>;\n ctx: Context.Context<TContext>;\n /**\n * The record to load. `undefined` means \"nothing to load yet\" — the create\n * case — and the hook stays idle rather than fetching.\n */\n id: EntityId;\n}\n"],"names":[],"mappings":"AAcA,WASC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-use-cases/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/use-entity-use-cases/index.ts"],"sourcesContent":["export * from './use-entity-use-cases.js';\n"],"names":[],"mappings":"AAAA,cAAc,4BAA4B"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Entity, type EntityConstructor, type EntityMetadataDocument, type EntityMetadataSource } from '@entifix/core';
|
|
2
|
+
export interface UseEntityUseCasesResult {
|
|
3
|
+
/** Undefined until the document arrives; a surface renders its defaults meanwhile. */
|
|
4
|
+
metadata?: EntityMetadataDocument;
|
|
5
|
+
isLoading: boolean;
|
|
6
|
+
error?: unknown;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reads what this caller may do with an entity, from the service.
|
|
10
|
+
*
|
|
11
|
+
* The source is a parameter rather than resolved from context because the two
|
|
12
|
+
* consumers reach their service differently — one through the config-driven REST
|
|
13
|
+
* adapters, one through a shell's own same-origin routes — and a hook that knew
|
|
14
|
+
* about both would have to know about neither's layer.
|
|
15
|
+
*
|
|
16
|
+
* The query key sits under `'entity-metadata'`, deliberately **outside** the
|
|
17
|
+
* `['entity', …]` namespace: `useReactiveInvalidation` invalidates
|
|
18
|
+
* `['entity', <key>]` wholesale whenever a row changes, and affordances do not
|
|
19
|
+
* change when a record does. Keeping it out is what stops every save refetching
|
|
20
|
+
* this. `staleTime: Infinity` for the same reason — the document changes with
|
|
21
|
+
* the deployment and the caller's grants, neither of which moves inside a
|
|
22
|
+
* session.
|
|
23
|
+
*/
|
|
24
|
+
export declare function useEntityUseCases<TEntity extends Entity>(entityConstructor: EntityConstructor<TEntity>, source: EntityMetadataSource | undefined): UseEntityUseCasesResult;
|
|
25
|
+
//# sourceMappingURL=use-entity-use-cases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-entity-use-cases.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-entity-use-cases/use-entity-use-cases.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EAE1B,MAAM,eAAe,CAAC;AAGvB,MAAM,WAAW,uBAAuB;IACtC,sFAAsF;IACtF,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,MAAM,EACtD,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAC7C,MAAM,EAAE,oBAAoB,GAAG,SAAS,GACvC,uBAAuB,CAyBzB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { envelopeEntityName } from '@entifix/core';
|
|
2
|
+
import { skipToken, useQuery } from '@tanstack/react-query';
|
|
3
|
+
/**
|
|
4
|
+
* Reads what this caller may do with an entity, from the service.
|
|
5
|
+
*
|
|
6
|
+
* The source is a parameter rather than resolved from context because the two
|
|
7
|
+
* consumers reach their service differently — one through the config-driven REST
|
|
8
|
+
* adapters, one through a shell's own same-origin routes — and a hook that knew
|
|
9
|
+
* about both would have to know about neither's layer.
|
|
10
|
+
*
|
|
11
|
+
* The query key sits under `'entity-metadata'`, deliberately **outside** the
|
|
12
|
+
* `['entity', …]` namespace: `useReactiveInvalidation` invalidates
|
|
13
|
+
* `['entity', <key>]` wholesale whenever a row changes, and affordances do not
|
|
14
|
+
* change when a record does. Keeping it out is what stops every save refetching
|
|
15
|
+
* this. `staleTime: Infinity` for the same reason — the document changes with
|
|
16
|
+
* the deployment and the caller's grants, neither of which moves inside a
|
|
17
|
+
* session.
|
|
18
|
+
*/ export function useEntityUseCases(entityConstructor, source) {
|
|
19
|
+
var _query_error;
|
|
20
|
+
// An **optional** source, so a caller with none to offer — a generated page
|
|
21
|
+
// whose service exposes no `$metadata` route — needs no placeholder to hand
|
|
22
|
+
// over. A hook cannot be called conditionally, so the query is always created
|
|
23
|
+
// and simply disabled; fetching-and-failing instead would have TanStack Query
|
|
24
|
+
// *retry* a rejecting promise on a loop for a capability nobody asked for.
|
|
25
|
+
const enabled = source !== undefined;
|
|
26
|
+
const query = useQuery({
|
|
27
|
+
queryKey: [
|
|
28
|
+
'entity-metadata',
|
|
29
|
+
envelopeEntityName(entityConstructor)
|
|
30
|
+
],
|
|
31
|
+
// `skipToken` rather than `enabled`, because it is what lets the closure
|
|
32
|
+
// keep the narrowed `source` — the `enabled` form needs a non-null
|
|
33
|
+
// assertion in a `queryFn` that provably never runs.
|
|
34
|
+
queryFn: source ? ()=>source.fetchMetadata(entityConstructor) : skipToken,
|
|
35
|
+
staleTime: Infinity
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
metadata: query.data,
|
|
39
|
+
// A disabled query sits in `pending` forever, so `isLoading` would be
|
|
40
|
+
// permanently true and every action surface would render a skeleton that
|
|
41
|
+
// never resolves. Nothing is in flight when nothing was asked for.
|
|
42
|
+
isLoading: enabled && query.isLoading,
|
|
43
|
+
error: (_query_error = query.error) != null ? _query_error : undefined
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=use-entity-use-cases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/use-entity-use-cases/use-entity-use-cases.ts"],"sourcesContent":["import {\n type Entity,\n type EntityConstructor,\n type EntityMetadataDocument,\n type EntityMetadataSource,\n envelopeEntityName,\n} from '@entifix/core';\nimport { skipToken, useQuery } from '@tanstack/react-query';\n\nexport interface UseEntityUseCasesResult {\n /** Undefined until the document arrives; a surface renders its defaults meanwhile. */\n metadata?: EntityMetadataDocument;\n isLoading: boolean;\n error?: unknown;\n}\n\n/**\n * Reads what this caller may do with an entity, from the service.\n *\n * The source is a parameter rather than resolved from context because the two\n * consumers reach their service differently — one through the config-driven REST\n * adapters, one through a shell's own same-origin routes — and a hook that knew\n * about both would have to know about neither's layer.\n *\n * The query key sits under `'entity-metadata'`, deliberately **outside** the\n * `['entity', …]` namespace: `useReactiveInvalidation` invalidates\n * `['entity', <key>]` wholesale whenever a row changes, and affordances do not\n * change when a record does. Keeping it out is what stops every save refetching\n * this. `staleTime: Infinity` for the same reason — the document changes with\n * the deployment and the caller's grants, neither of which moves inside a\n * session.\n */\nexport function useEntityUseCases<TEntity extends Entity>(\n entityConstructor: EntityConstructor<TEntity>,\n source: EntityMetadataSource | undefined,\n): UseEntityUseCasesResult {\n // An **optional** source, so a caller with none to offer — a generated page\n // whose service exposes no `$metadata` route — needs no placeholder to hand\n // over. A hook cannot be called conditionally, so the query is always created\n // and simply disabled; fetching-and-failing instead would have TanStack Query\n // *retry* a rejecting promise on a loop for a capability nobody asked for.\n const enabled = source !== undefined;\n\n const query = useQuery({\n queryKey: ['entity-metadata', envelopeEntityName(entityConstructor)],\n // `skipToken` rather than `enabled`, because it is what lets the closure\n // keep the narrowed `source` — the `enabled` form needs a non-null\n // assertion in a `queryFn` that provably never runs.\n queryFn: source ? () => source.fetchMetadata(entityConstructor) : skipToken,\n staleTime: Infinity,\n });\n\n return {\n metadata: query.data,\n // A disabled query sits in `pending` forever, so `isLoading` would be\n // permanently true and every action surface would render a skeleton that\n // never resolves. Nothing is in flight when nothing was asked for.\n isLoading: enabled && query.isLoading,\n error: query.error ?? undefined,\n };\n}\n"],"names":["envelopeEntityName","skipToken","useQuery","useEntityUseCases","entityConstructor","source","query","enabled","undefined","queryKey","queryFn","fetchMetadata","staleTime","Infinity","metadata","data","isLoading","error"],"mappings":"AAAA,SAKEA,kBAAkB,QACb,gBAAgB;AACvB,SAASC,SAAS,EAAEC,QAAQ,QAAQ,wBAAwB;AAS5D;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,kBACdC,iBAA6C,EAC7CC,MAAwC;QAwB/BC;IAtBT,4EAA4E;IAC5E,4EAA4E;IAC5E,8EAA8E;IAC9E,8EAA8E;IAC9E,2EAA2E;IAC3E,MAAMC,UAAUF,WAAWG;IAE3B,MAAMF,QAAQJ,SAAS;QACrBO,UAAU;YAAC;YAAmBT,mBAAmBI;SAAmB;QACpE,yEAAyE;QACzE,mEAAmE;QACnE,qDAAqD;QACrDM,SAASL,SAAS,IAAMA,OAAOM,aAAa,CAACP,qBAAqBH;QAClEW,WAAWC;IACb;IAEA,OAAO;QACLC,UAAUR,MAAMS,IAAI;QACpB,sEAAsE;QACtE,yEAAyE;QACzE,mEAAmE;QACnEC,WAAWT,WAAWD,MAAMU,SAAS;QACrCC,KAAK,GAAEX,eAAAA,MAAMW,KAAK,YAAXX,eAAeE;IACxB;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-wizard/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/use-wizard/index.ts"],"sourcesContent":["export * from './use-wizard.js';\nexport * from './use-wizard.types.js';\n"],"names":[],"mappings":"AAAA,cAAc,kBAAkB;AAChC,cAAc,wBAAwB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { UseWizardOptions, UseWizardResult } from './use-wizard.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Drives one wizard: where the operator is, what they have answered, and the
|
|
4
|
+
* per-step draft each form step edits.
|
|
5
|
+
*
|
|
6
|
+
* **The draft lives here, above the forms.** A form step calls `useEntityForm`
|
|
7
|
+
* itself — React's hook count must stay fixed, so N form steps cannot be N hook
|
|
8
|
+
* calls in one component — and it unmounts when it is not the active step. What
|
|
9
|
+
* makes Back safe is therefore not keeping the step mounted but keeping its
|
|
10
|
+
* values out of it: {@link UseWizardResult.draftStoreFor} hands the step an
|
|
11
|
+
* `EntityDraftStore` viewing this state, and the step is free to come and go
|
|
12
|
+
* ([ADR 0045](../../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)).
|
|
13
|
+
*
|
|
14
|
+
* It holds no router and performs no submit. `onStepChange` reports where the
|
|
15
|
+
* operator went so a shell can write the URL, and finishing is the host's — this
|
|
16
|
+
* hook does not know a transaction exists.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useWizard({ definition, draft, errored, onStepChange, }: UseWizardOptions): UseWizardResult;
|
|
19
|
+
//# sourceMappingURL=use-wizard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-wizard.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-wizard/use-wizard.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE/E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EACxB,UAAU,EACV,KAAK,EACL,OAAO,EACP,YAAY,GACb,EAAE,gBAAgB,GAAG,eAAe,CAoIpC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
3
|
+
import { advanceWizard, canFinish as canFinishWizard, emptyWizardState, entryStep, goBackWizard, goToWizardStep, stepStatuses, withStepValue } from '@entifix/core';
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
/**
|
|
6
|
+
* Drives one wizard: where the operator is, what they have answered, and the
|
|
7
|
+
* per-step draft each form step edits.
|
|
8
|
+
*
|
|
9
|
+
* **The draft lives here, above the forms.** A form step calls `useEntityForm`
|
|
10
|
+
* itself — React's hook count must stay fixed, so N form steps cannot be N hook
|
|
11
|
+
* calls in one component — and it unmounts when it is not the active step. What
|
|
12
|
+
* makes Back safe is therefore not keeping the step mounted but keeping its
|
|
13
|
+
* values out of it: {@link UseWizardResult.draftStoreFor} hands the step an
|
|
14
|
+
* `EntityDraftStore` viewing this state, and the step is free to come and go
|
|
15
|
+
* ([ADR 0045](../../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)).
|
|
16
|
+
*
|
|
17
|
+
* It holds no router and performs no submit. `onStepChange` reports where the
|
|
18
|
+
* operator went so a shell can write the URL, and finishing is the host's — this
|
|
19
|
+
* hook does not know a transaction exists.
|
|
20
|
+
*/ export function useWizard({ definition, draft, errored, onStepChange }) {
|
|
21
|
+
const entry = useMemo(()=>entryStep(definition), [
|
|
22
|
+
definition
|
|
23
|
+
]);
|
|
24
|
+
const [state, setState] = useState(()=>emptyWizardState(entry.id));
|
|
25
|
+
const [isResumed, setResumed] = useState(false);
|
|
26
|
+
/**
|
|
27
|
+
* The state as of the last commit, mirrored so a handler can read it without
|
|
28
|
+
* a stale closure **and without doing its work inside a `setState` updater** —
|
|
29
|
+
* an updater runs twice under StrictMode, which would fire `onStepChange`
|
|
30
|
+
* and the persistence write twice per advance.
|
|
31
|
+
*/ const stateRef = useRef(state);
|
|
32
|
+
const touched = useRef(false);
|
|
33
|
+
const adopted = useRef(false);
|
|
34
|
+
const save = draft == null ? void 0 : draft.save;
|
|
35
|
+
const persisted = draft == null ? void 0 : draft.state;
|
|
36
|
+
const commit = useCallback((updated)=>{
|
|
37
|
+
const current = stateRef.current;
|
|
38
|
+
if (updated === current) return;
|
|
39
|
+
touched.current = true;
|
|
40
|
+
stateRef.current = updated;
|
|
41
|
+
setState(updated);
|
|
42
|
+
save == null ? void 0 : save(updated);
|
|
43
|
+
if (updated.activeStep !== current.activeStep) {
|
|
44
|
+
// Resuming is a fact about arriving. Once the operator moves, the recap
|
|
45
|
+
// has been read or ignored, and a banner that never leaves stops being
|
|
46
|
+
// read at all.
|
|
47
|
+
setResumed(false);
|
|
48
|
+
onStepChange == null ? void 0 : onStepChange(updated.activeStep);
|
|
49
|
+
}
|
|
50
|
+
}, [
|
|
51
|
+
save,
|
|
52
|
+
onStepChange
|
|
53
|
+
]);
|
|
54
|
+
const apply = useCallback((next)=>commit(next(stateRef.current)), [
|
|
55
|
+
commit
|
|
56
|
+
]);
|
|
57
|
+
/**
|
|
58
|
+
* A persisted wizard is adopted **once, and only while untouched**.
|
|
59
|
+
*
|
|
60
|
+
* The store hydrates from an effect (`skipHydration`, ADR 0032), so it arrives
|
|
61
|
+
* after the first render and cannot be a `useState` initializer. Adopting it
|
|
62
|
+
* more than once would let a later hydration overwrite what the operator has
|
|
63
|
+
* since typed, and adopting it after a first edit would do the same on the
|
|
64
|
+
* very first keystroke.
|
|
65
|
+
*/ useEffect(()=>{
|
|
66
|
+
if (adopted.current || touched.current || persisted === undefined) return;
|
|
67
|
+
adopted.current = true;
|
|
68
|
+
stateRef.current = persisted;
|
|
69
|
+
setState(persisted);
|
|
70
|
+
// Only a wizard that was genuinely mid-flow is a resume; one persisted on
|
|
71
|
+
// its first step has nothing to recap.
|
|
72
|
+
setResumed(persisted.history.length > 0);
|
|
73
|
+
}, [
|
|
74
|
+
persisted
|
|
75
|
+
]);
|
|
76
|
+
/**
|
|
77
|
+
* One draft store per step id, minted once and kept forever.
|
|
78
|
+
*
|
|
79
|
+
* `useEntityForm` writes from an effect keyed on `save`, so a new identity per
|
|
80
|
+
* render would make every render a write. A `Map` in a ref rather than a memo
|
|
81
|
+
* because the set of steps a caller asks for grows as the flow is walked, and
|
|
82
|
+
* a memo keyed on that set would rebuild every entry whenever one was added.
|
|
83
|
+
*/ const stores = useRef(new Map());
|
|
84
|
+
const draftStoreFor = useCallback((stepId)=>{
|
|
85
|
+
let store = stores.current.get(stepId);
|
|
86
|
+
if (store === undefined) {
|
|
87
|
+
store = {
|
|
88
|
+
save: (values)=>apply((current)=>withStepValue(current, stepId, {
|
|
89
|
+
kind: 'form',
|
|
90
|
+
values
|
|
91
|
+
})),
|
|
92
|
+
clear: ()=>apply((current)=>withStepValue(current, stepId, {
|
|
93
|
+
kind: 'none'
|
|
94
|
+
}))
|
|
95
|
+
};
|
|
96
|
+
stores.current.set(stepId, store);
|
|
97
|
+
}
|
|
98
|
+
const value = state.steps[stepId];
|
|
99
|
+
return _extends({}, (value == null ? void 0 : value.kind) === 'form' ? {
|
|
100
|
+
draft: value.values
|
|
101
|
+
} : {}, {
|
|
102
|
+
save: store.save,
|
|
103
|
+
clear: store.clear
|
|
104
|
+
});
|
|
105
|
+
}, [
|
|
106
|
+
apply,
|
|
107
|
+
state
|
|
108
|
+
]);
|
|
109
|
+
return {
|
|
110
|
+
state,
|
|
111
|
+
activeStep: state.activeStep,
|
|
112
|
+
steps: stepStatuses(definition, state, errored),
|
|
113
|
+
canFinish: canFinishWizard(definition, state),
|
|
114
|
+
isResumed,
|
|
115
|
+
draftStoreFor,
|
|
116
|
+
setStepValue: useCallback((stepId, value)=>apply((current)=>withStepValue(current, stepId, value)), [
|
|
117
|
+
apply
|
|
118
|
+
]),
|
|
119
|
+
next: useCallback(()=>apply((current)=>advanceWizard(definition, current)), [
|
|
120
|
+
apply,
|
|
121
|
+
definition
|
|
122
|
+
]),
|
|
123
|
+
previous: useCallback(()=>apply(goBackWizard), [
|
|
124
|
+
apply
|
|
125
|
+
]),
|
|
126
|
+
goTo: useCallback((stepId)=>apply((current)=>goToWizardStep(current, stepId)), [
|
|
127
|
+
apply
|
|
128
|
+
]),
|
|
129
|
+
reset: useCallback(()=>apply(()=>emptyWizardState(entry.id)), [
|
|
130
|
+
apply,
|
|
131
|
+
entry
|
|
132
|
+
])
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=use-wizard.js.map
|