@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.
Files changed (76) hide show
  1. package/dist/inference/ui-contracts/index.d.ts +38 -0
  2. package/dist/inference/ui-contracts/index.d.ts.map +1 -0
  3. package/dist/inference/ui-contracts/index.js +212 -0
  4. package/dist/inference/ui-contracts/index.js.map +1 -0
  5. package/dist/inference/ui-contracts/rules/_shared.d.ts +32 -0
  6. package/dist/inference/ui-contracts/rules/_shared.d.ts.map +1 -0
  7. package/dist/inference/ui-contracts/rules/_shared.js +103 -0
  8. package/dist/inference/ui-contracts/rules/_shared.js.map +1 -0
  9. package/dist/inference/ui-contracts/rules/action-buttons-present.d.ts +21 -0
  10. package/dist/inference/ui-contracts/rules/action-buttons-present.d.ts.map +1 -0
  11. package/dist/inference/ui-contracts/rules/action-buttons-present.js +62 -0
  12. package/dist/inference/ui-contracts/rules/action-buttons-present.js.map +1 -0
  13. package/dist/inference/ui-contracts/rules/create-reflects-in-list.d.ts +22 -0
  14. package/dist/inference/ui-contracts/rules/create-reflects-in-list.d.ts.map +1 -0
  15. package/dist/inference/ui-contracts/rules/create-reflects-in-list.js +48 -0
  16. package/dist/inference/ui-contracts/rules/create-reflects-in-list.js.map +1 -0
  17. package/dist/inference/ui-contracts/rules/delete-reflects-in-list.d.ts +22 -0
  18. package/dist/inference/ui-contracts/rules/delete-reflects-in-list.d.ts.map +1 -0
  19. package/dist/inference/ui-contracts/rules/delete-reflects-in-list.js +50 -0
  20. package/dist/inference/ui-contracts/rules/delete-reflects-in-list.js.map +1 -0
  21. package/dist/inference/ui-contracts/rules/detail-view-renders.d.ts +24 -0
  22. package/dist/inference/ui-contracts/rules/detail-view-renders.d.ts.map +1 -0
  23. package/dist/inference/ui-contracts/rules/detail-view-renders.js +34 -0
  24. package/dist/inference/ui-contracts/rules/detail-view-renders.js.map +1 -0
  25. package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.d.ts +21 -0
  26. package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.d.ts.map +1 -0
  27. package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.js +53 -0
  28. package/dist/inference/ui-contracts/rules/evolve-reflects-in-list.js.map +1 -0
  29. package/dist/inference/ui-contracts/rules/form-shows-required-indicators.d.ts +15 -0
  30. package/dist/inference/ui-contracts/rules/form-shows-required-indicators.d.ts.map +1 -0
  31. package/dist/inference/ui-contracts/rules/form-shows-required-indicators.js +38 -0
  32. package/dist/inference/ui-contracts/rules/form-shows-required-indicators.js.map +1 -0
  33. package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.d.ts +17 -0
  34. package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.d.ts.map +1 -0
  35. package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.js +39 -0
  36. package/dist/inference/ui-contracts/rules/hasmany-shows-children-in-detail.js.map +1 -0
  37. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts +25 -0
  38. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts.map +1 -0
  39. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js +66 -0
  40. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js.map +1 -0
  41. package/dist/inference/ui-contracts/rules/list-shows-business-columns.d.ts +17 -0
  42. package/dist/inference/ui-contracts/rules/list-shows-business-columns.d.ts.map +1 -0
  43. package/dist/inference/ui-contracts/rules/list-shows-business-columns.js +39 -0
  44. package/dist/inference/ui-contracts/rules/list-shows-business-columns.js.map +1 -0
  45. package/dist/inference/ui-contracts/rules/list-view-renders.d.ts +19 -0
  46. package/dist/inference/ui-contracts/rules/list-view-renders.d.ts.map +1 -0
  47. package/dist/inference/ui-contracts/rules/list-view-renders.js +29 -0
  48. package/dist/inference/ui-contracts/rules/list-view-renders.js.map +1 -0
  49. package/dist/inference/ui-contracts/rules/nav-has-model-entries.d.ts +20 -0
  50. package/dist/inference/ui-contracts/rules/nav-has-model-entries.d.ts.map +1 -0
  51. package/dist/inference/ui-contracts/rules/nav-has-model-entries.js +29 -0
  52. package/dist/inference/ui-contracts/rules/nav-has-model-entries.js.map +1 -0
  53. package/dist/inference/ui-contracts/test-case-types.d.ts +126 -0
  54. package/dist/inference/ui-contracts/test-case-types.d.ts.map +1 -0
  55. package/dist/inference/ui-contracts/test-case-types.js +14 -0
  56. package/dist/inference/ui-contracts/test-case-types.js.map +1 -0
  57. package/dist/inference/ui-contracts/translator.d.ts +17 -0
  58. package/dist/inference/ui-contracts/translator.d.ts.map +1 -0
  59. package/dist/inference/ui-contracts/translator.js +127 -0
  60. package/dist/inference/ui-contracts/translator.js.map +1 -0
  61. package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +37 -2
  62. package/dist/libs/instance-factories/applications/templates/react/runtime-app-tsx-generator.js +41 -4
  63. package/dist/libs/instance-factories/applications/templates/react/use-api-hooks-generator.js +27 -7
  64. package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +10 -0
  65. package/dist/libs/instance-factories/views/templates/react/components-generator.js +34 -23
  66. package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +51 -81
  67. package/dist/realize/index.d.ts.map +1 -1
  68. package/dist/realize/index.js +204 -0
  69. package/dist/realize/index.js.map +1 -1
  70. package/libs/instance-factories/applications/templates/react/api-client-generator.ts +37 -2
  71. package/libs/instance-factories/applications/templates/react/runtime-app-tsx-generator.ts +41 -4
  72. package/libs/instance-factories/applications/templates/react/use-api-hooks-generator.ts +27 -7
  73. package/libs/instance-factories/scaffolding/templates/generic/package-json-generator.ts +13 -0
  74. package/libs/instance-factories/views/templates/react/components-generator.ts +34 -23
  75. package/libs/instance-factories/views/templates/react/hooks-generator.ts +72 -88
  76. 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
- * Generates React custom hooks for data fetching and mutations
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, controller, spec } = context;
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
- * Custom React hook for ${modelName} data fetching and mutations
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 { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
29
- import { executeOperation } from '../lib/apiClient';
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 Use${modelName}Options {
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
- * ${hookName} - Fetch and mutate ${modelName} data
40
- */
41
- export function ${hookName}(options: Use${modelName}Options = {}) {
42
- const queryClient = useQueryClient();
43
- const { id, list, filters } = options;
44
-
45
- // Fetch single ${modelName}
46
- const { data: ${modelName.toLowerCase()}, isLoading: singleLoading, error: singleError } = useQuery({
47
- queryKey: ['${modelName.toLowerCase()}', id],
48
- queryFn: async () => {
49
- if (!id) return null;
50
- return await executeOperation('${controllerName}', 'retrieve', { id });
51
- },
52
- enabled: !!id && !list,
53
- });
54
-
55
- // Fetch list of ${modelName}s
56
- const { data: ${modelName.toLowerCase()}s, isLoading: listLoading, error: listError } = useQuery({
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
- ${modelName.toLowerCase()},
109
- ${modelName.toLowerCase()}s,
110
- isLoading,
111
- error,
112
- refetch,
113
- create: createMutation.mutateAsync,
114
- update: updateMutation.mutateAsync,
115
- delete: deleteMutation.mutateAsync,
116
- isCreating: createMutation.isPending,
117
- isUpdating: updateMutation.isPending,
118
- isDeleting: deleteMutation.isPending,
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specverse/engines",
3
- "version": "4.1.21",
3
+ "version": "4.1.23",
4
4
  "description": "SpecVerse toolchain — parser, inference, realize, generators, AI, registry",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",