@specverse/engines 4.1.21 → 4.1.23
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/inference/ui-contracts/index.d.ts +38 -0
- package/dist/inference/ui-contracts/index.d.ts.map +1 -0
- package/dist/inference/ui-contracts/index.js +212 -0
- package/dist/inference/ui-contracts/index.js.map +1 -0
- package/dist/inference/ui-contracts/rules/_shared.d.ts +32 -0
- package/dist/inference/ui-contracts/rules/_shared.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/_shared.js +103 -0
- package/dist/inference/ui-contracts/rules/_shared.js.map +1 -0
- package/dist/inference/ui-contracts/rules/action-buttons-present.d.ts +21 -0
- package/dist/inference/ui-contracts/rules/action-buttons-present.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/action-buttons-present.js +62 -0
- package/dist/inference/ui-contracts/rules/action-buttons-present.js.map +1 -0
- package/dist/inference/ui-contracts/rules/create-reflects-in-list.d.ts +22 -0
- package/dist/inference/ui-contracts/rules/create-reflects-in-list.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/create-reflects-in-list.js +48 -0
- package/dist/inference/ui-contracts/rules/create-reflects-in-list.js.map +1 -0
- package/dist/inference/ui-contracts/rules/delete-reflects-in-list.d.ts +22 -0
- package/dist/inference/ui-contracts/rules/delete-reflects-in-list.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/delete-reflects-in-list.js +50 -0
- package/dist/inference/ui-contracts/rules/delete-reflects-in-list.js.map +1 -0
- package/dist/inference/ui-contracts/rules/detail-view-renders.d.ts +24 -0
- package/dist/inference/ui-contracts/rules/detail-view-renders.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/detail-view-renders.js +34 -0
- package/dist/inference/ui-contracts/rules/detail-view-renders.js.map +1 -0
- package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.d.ts +21 -0
- package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.js +53 -0
- package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.js.map +1 -0
- package/dist/inference/ui-contracts/rules/form-shows-required-indicators.d.ts +15 -0
- package/dist/inference/ui-contracts/rules/form-shows-required-indicators.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/form-shows-required-indicators.js +38 -0
- package/dist/inference/ui-contracts/rules/form-shows-required-indicators.js.map +1 -0
- package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.d.ts +17 -0
- package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.js +39 -0
- package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.js.map +1 -0
- package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts +25 -0
- package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js +66 -0
- package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js.map +1 -0
- package/dist/inference/ui-contracts/rules/list-shows-business-columns.d.ts +17 -0
- package/dist/inference/ui-contracts/rules/list-shows-business-columns.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/list-shows-business-columns.js +39 -0
- package/dist/inference/ui-contracts/rules/list-shows-business-columns.js.map +1 -0
- package/dist/inference/ui-contracts/rules/list-view-renders.d.ts +19 -0
- package/dist/inference/ui-contracts/rules/list-view-renders.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/list-view-renders.js +29 -0
- package/dist/inference/ui-contracts/rules/list-view-renders.js.map +1 -0
- package/dist/inference/ui-contracts/rules/nav-has-model-entries.d.ts +20 -0
- package/dist/inference/ui-contracts/rules/nav-has-model-entries.d.ts.map +1 -0
- package/dist/inference/ui-contracts/rules/nav-has-model-entries.js +29 -0
- package/dist/inference/ui-contracts/rules/nav-has-model-entries.js.map +1 -0
- package/dist/inference/ui-contracts/test-case-types.d.ts +126 -0
- package/dist/inference/ui-contracts/test-case-types.d.ts.map +1 -0
- package/dist/inference/ui-contracts/test-case-types.js +14 -0
- package/dist/inference/ui-contracts/test-case-types.js.map +1 -0
- package/dist/inference/ui-contracts/translator.d.ts +17 -0
- package/dist/inference/ui-contracts/translator.d.ts.map +1 -0
- package/dist/inference/ui-contracts/translator.js +127 -0
- package/dist/inference/ui-contracts/translator.js.map +1 -0
- package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +37 -2
- package/dist/libs/instance-factories/applications/templates/react/runtime-app-tsx-generator.js +41 -4
- package/dist/libs/instance-factories/applications/templates/react/use-api-hooks-generator.js +27 -7
- package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +10 -0
- package/dist/libs/instance-factories/views/templates/react/components-generator.js +34 -23
- package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +51 -81
- package/dist/realize/index.d.ts.map +1 -1
- package/dist/realize/index.js +204 -0
- package/dist/realize/index.js.map +1 -1
- package/libs/instance-factories/applications/templates/react/api-client-generator.ts +37 -2
- package/libs/instance-factories/applications/templates/react/runtime-app-tsx-generator.ts +41 -4
- package/libs/instance-factories/applications/templates/react/use-api-hooks-generator.ts +27 -7
- package/libs/instance-factories/scaffolding/templates/generic/package-json-generator.ts +13 -0
- package/libs/instance-factories/views/templates/react/components-generator.ts +34 -23
- package/libs/instance-factories/views/templates/react/hooks-generator.ts +72 -88
- package/package.json +1 -1
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* React Hooks Generator
|
|
2
|
+
* React Hooks Generator — per-model convenience hook.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Emits `use${Model}()` which is a thin delegator over the canonical
|
|
5
|
+
* hooks in `hooks/useApi.ts`. Historically this generator produced
|
|
6
|
+
* its own `useQuery(...)` + `useMutation(...)` blocks with private
|
|
7
|
+
* query keys (`['poll', id]`, `['polls', filters]`), which meant:
|
|
8
|
+
*
|
|
9
|
+
* 1. WebSocket events invalidating `['entities', modelName]` never
|
|
10
|
+
* reached views that consumed this hook.
|
|
11
|
+
* 2. The conformance check `no-bare-usequery-in-views` lit up for
|
|
12
|
+
* every per-model hook file.
|
|
13
|
+
*
|
|
14
|
+
* v2: delegate everything to `useEntitiesQuery` and
|
|
15
|
+
* `useExecuteOperationMutation` so there's exactly one cache key
|
|
16
|
+
* per model and one mutation pipeline across the whole frontend.
|
|
17
|
+
*
|
|
18
|
+
* The public API of `use${Model}()` is preserved for callers that
|
|
19
|
+
* depend on it: { [model], [models], isLoading, error, refetch,
|
|
20
|
+
* create, update, delete, isCreating, isUpdating, isDeleting }.
|
|
5
21
|
*/
|
|
6
22
|
|
|
7
23
|
import type { TemplateContext } from '@specverse/types';
|
|
8
24
|
|
|
9
|
-
/**
|
|
10
|
-
* Generate React custom hook for a model
|
|
11
|
-
*/
|
|
12
25
|
export default function generateReactHook(context: TemplateContext): string {
|
|
13
|
-
const { model
|
|
26
|
+
const { model } = context;
|
|
14
27
|
|
|
15
28
|
if (!model) {
|
|
16
29
|
throw new Error('Model is required in template context');
|
|
@@ -19,104 +32,75 @@ export default function generateReactHook(context: TemplateContext): string {
|
|
|
19
32
|
const modelName = model.name;
|
|
20
33
|
const hookName = `use${modelName}`;
|
|
21
34
|
const controllerName = `${modelName}Controller`;
|
|
35
|
+
const singular = modelName.charAt(0).toLowerCase() + modelName.slice(1);
|
|
36
|
+
const plural = singular + 's';
|
|
22
37
|
|
|
23
38
|
return `/**
|
|
24
39
|
* ${hookName}
|
|
25
|
-
*
|
|
40
|
+
*
|
|
41
|
+
* Thin delegator over the canonical useApi hooks. All state flows
|
|
42
|
+
* through \`['entities', '${modelName}']\` so WebSocket invalidation
|
|
43
|
+
* reaches every view consuming this hook.
|
|
26
44
|
*/
|
|
27
45
|
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
46
|
+
import { useMemo } from 'react';
|
|
47
|
+
import {
|
|
48
|
+
useEntitiesQuery,
|
|
49
|
+
useExecuteOperationMutation,
|
|
50
|
+
} from './useApi';
|
|
30
51
|
import type { ${modelName} } from '../types/${modelName}';
|
|
31
52
|
|
|
32
|
-
interface
|
|
53
|
+
interface ${capitalize(hookName)}Options {
|
|
33
54
|
id?: string;
|
|
34
55
|
list?: boolean;
|
|
35
56
|
filters?: Record<string, any>;
|
|
36
57
|
}
|
|
37
58
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
queryKey: ['${modelName.toLowerCase()}s', filters],
|
|
58
|
-
queryFn: async () => {
|
|
59
|
-
return await executeOperation('${controllerName}', 'list', filters || {});
|
|
60
|
-
},
|
|
61
|
-
enabled: list,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const isLoading = list ? listLoading : singleLoading;
|
|
65
|
-
const error = list ? listError : singleError;
|
|
66
|
-
|
|
67
|
-
// Create mutation
|
|
68
|
-
const createMutation = useMutation({
|
|
69
|
-
mutationFn: async (data: Partial<${modelName}>) => {
|
|
70
|
-
return await executeOperation('${controllerName}', 'create', data);
|
|
71
|
-
},
|
|
72
|
-
onSuccess: () => {
|
|
73
|
-
queryClient.invalidateQueries({ queryKey: ['${modelName.toLowerCase()}s'] });
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Update mutation
|
|
78
|
-
const updateMutation = useMutation({
|
|
79
|
-
mutationFn: async ({ id, data }: { id: string; data: Partial<${modelName}> }) => {
|
|
80
|
-
return await executeOperation('${controllerName}', 'update', { id, ...data });
|
|
81
|
-
},
|
|
82
|
-
onSuccess: (_, variables) => {
|
|
83
|
-
queryClient.invalidateQueries({ queryKey: ['${modelName.toLowerCase()}', variables.id] });
|
|
84
|
-
queryClient.invalidateQueries({ queryKey: ['${modelName.toLowerCase()}s'] });
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Delete mutation
|
|
89
|
-
const deleteMutation = useMutation({
|
|
90
|
-
mutationFn: async (id: string) => {
|
|
91
|
-
return await executeOperation('${controllerName}', 'delete', { id });
|
|
92
|
-
},
|
|
93
|
-
onSuccess: () => {
|
|
94
|
-
queryClient.invalidateQueries({ queryKey: ['${modelName.toLowerCase()}s'] });
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Refetch functions
|
|
99
|
-
const refetch = () => {
|
|
100
|
-
if (list) {
|
|
101
|
-
queryClient.invalidateQueries({ queryKey: ['${modelName.toLowerCase()}s'] });
|
|
102
|
-
} else if (id) {
|
|
103
|
-
queryClient.invalidateQueries({ queryKey: ['${modelName.toLowerCase()}', id] });
|
|
59
|
+
export function ${hookName}(opts: ${capitalize(hookName)}Options = {}) {
|
|
60
|
+
const { id, filters: _filters } = opts;
|
|
61
|
+
|
|
62
|
+
// Canonical list query — one cache key per model. The list flag
|
|
63
|
+
// is preserved for backwards compatibility but the query always
|
|
64
|
+
// runs; React Query dedupes across consumers automatically.
|
|
65
|
+
const query = useEntitiesQuery('${controllerName}', '${modelName}');
|
|
66
|
+
const ${plural} = (query.data as ${modelName}[] | undefined) || [];
|
|
67
|
+
|
|
68
|
+
// Single-entity lookup is client-side against the canonical list
|
|
69
|
+
// so both the detail view and the list view share one source of
|
|
70
|
+
// truth. If the caller needs server-side hydration for a single
|
|
71
|
+
// entity, they should call useEntitiesQuery directly with a
|
|
72
|
+
// filtered controller endpoint.
|
|
73
|
+
const ${singular} = useMemo<${modelName} | null>(() => {
|
|
74
|
+
if (!id) return null;
|
|
75
|
+
for (const entity of ${plural}) {
|
|
76
|
+
const entityId = (entity as any)?.id || (entity as any)?.data?.id;
|
|
77
|
+
if (entityId === id) return entity;
|
|
104
78
|
}
|
|
105
|
-
|
|
79
|
+
return null;
|
|
80
|
+
}, [id, ${plural}]);
|
|
81
|
+
|
|
82
|
+
const mutation = useExecuteOperationMutation();
|
|
106
83
|
|
|
107
84
|
return {
|
|
108
|
-
${
|
|
109
|
-
${
|
|
110
|
-
isLoading,
|
|
111
|
-
error,
|
|
112
|
-
refetch,
|
|
113
|
-
create:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
85
|
+
${singular},
|
|
86
|
+
${plural},
|
|
87
|
+
isLoading: query.isLoading,
|
|
88
|
+
error: query.error,
|
|
89
|
+
refetch: () => { /* handled by useEntitiesQuery refetchInterval + WS invalidation */ },
|
|
90
|
+
create: (data: Partial<${modelName}>) =>
|
|
91
|
+
mutation.mutateAsync({ controllerName: '${controllerName}', operationName: 'create', data: data as Record<string, unknown> }),
|
|
92
|
+
update: (args: { id: string; data: Partial<${modelName}> }) =>
|
|
93
|
+
mutation.mutateAsync({ controllerName: '${controllerName}', operationName: 'update', entityId: args.id, data: args.data as Record<string, unknown> }),
|
|
94
|
+
delete: (entityId: string) =>
|
|
95
|
+
mutation.mutateAsync({ controllerName: '${controllerName}', operationName: 'delete', entityId, data: {} }),
|
|
96
|
+
isCreating: mutation.isPending,
|
|
97
|
+
isUpdating: mutation.isPending,
|
|
98
|
+
isDeleting: mutation.isPending,
|
|
119
99
|
};
|
|
120
100
|
}
|
|
121
101
|
`;
|
|
122
102
|
}
|
|
103
|
+
|
|
104
|
+
function capitalize(s: string): string {
|
|
105
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
106
|
+
}
|