@svadmin/core 0.59.0 → 0.60.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/core",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "author": "zuohuadong",
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.ts CHANGED
@@ -304,7 +304,7 @@ export {
304
304
  generateComponentCode,
305
305
  generateResourceBundle,
306
306
  } from './inferencer';
307
- export type { InferResult } from './inferencer';
307
+ export type { InferResult, InferReview, InferReviewWarning, InferReviewWarningCode } from './inferencer';
308
308
  export { createWebSocketLiveProvider } from './live-websocket';
309
309
  export type { WebSocketLiveProviderOptions } from './live-websocket';
310
310
  export { createSSELiveProvider } from './live-sse';
package/src/inferencer.ts CHANGED
@@ -84,6 +84,7 @@ export interface InferResult {
84
84
  resource: ResourceDefinition;
85
85
  code: string;
86
86
  typeboxCode: string;
87
+ review: InferReview;
87
88
  componentCode: {
88
89
  list: string;
89
90
  create: string;
@@ -92,6 +93,26 @@ export interface InferResult {
92
93
  };
93
94
  }
94
95
 
96
+ export type InferReviewWarningCode =
97
+ | 'empty-sample'
98
+ | 'nullable-field'
99
+ | 'mixed-types'
100
+ | 'heuristic-relation'
101
+ | 'heuristic-select';
102
+
103
+ export interface InferReviewWarning {
104
+ code: InferReviewWarningCode;
105
+ field?: string;
106
+ message: string;
107
+ }
108
+
109
+ export interface InferReview {
110
+ needsReview: boolean;
111
+ confidence: 'high' | 'medium' | 'low';
112
+ sampleSize: number;
113
+ warnings: InferReviewWarning[];
114
+ }
115
+
95
116
  /**
96
117
  * Analyze a sample data array and produce a ResourceDefinition.
97
118
  * @param resourceName - Name of the resource (e.g. "posts")
@@ -116,6 +137,15 @@ export function inferResource(
116
137
  resource: emptyResource,
117
138
  code: `// No data available to infer fields for "${resourceName}".`,
118
139
  typeboxCode: generateTypeBoxSchemaCode(emptyResource),
140
+ review: {
141
+ needsReview: true,
142
+ confidence: 'low',
143
+ sampleSize: 0,
144
+ warnings: [{
145
+ code: 'empty-sample',
146
+ message: `No sample records were available for "${resourceName}".`,
147
+ }],
148
+ },
119
149
  componentCode: {
120
150
  list: generateListPageCode(emptyResource),
121
151
  create: generateCreatePageCode(emptyResource),
@@ -133,6 +163,7 @@ export function inferResource(
133
163
 
134
164
  // For each key, infer from all non-null values
135
165
  const fields: FieldDefinition[] = [];
166
+ const warnings: InferReviewWarning[] = [];
136
167
  for (const key of keySet) {
137
168
  // Collect non-null values
138
169
  const values = sampleData
@@ -141,6 +172,13 @@ export function inferResource(
141
172
 
142
173
  const sampleValue = values[0];
143
174
  let inferredType = inferFieldType(key, sampleValue);
175
+ if (values.length === 0) {
176
+ warnings.push({
177
+ code: 'nullable-field',
178
+ field: key,
179
+ message: `Field "${key}" contains only null or undefined values in the sample.`,
180
+ });
181
+ }
144
182
 
145
183
  // Cross-validate: if most values for this key are of a different type, use majority
146
184
  const typeCounts = new Map<InferredType, number>();
@@ -155,13 +193,34 @@ export function inferResource(
155
193
  inferredType = t;
156
194
  }
157
195
  }
196
+ if (typeCounts.size > 1) {
197
+ warnings.push({
198
+ code: 'mixed-types',
199
+ field: key,
200
+ message: `Field "${key}" contains multiple inferred types; the majority type was selected.`,
201
+ });
202
+ }
158
203
 
159
204
  // Check for relation
160
205
  const relatedResource = isLikelyRelation(key);
206
+ if (relatedResource) {
207
+ warnings.push({
208
+ code: 'heuristic-relation',
209
+ field: key,
210
+ message: `Relation "${key}" was inferred from its field name.`,
211
+ });
212
+ }
161
213
 
162
214
  // Check if it looks like a select (few unique string values)
163
215
  const uniqueStrings = new Set(values.filter(v => typeof v === 'string') as string[]);
164
216
  const isSelect = inferredType === 'text' && uniqueStrings.size > 1 && uniqueStrings.size <= 10 && values.length >= 5;
217
+ if (isSelect) {
218
+ warnings.push({
219
+ code: 'heuristic-select',
220
+ field: key,
221
+ message: `Select options for "${key}" were inferred from repeated sample values.`,
222
+ });
223
+ }
165
224
 
166
225
  const field: FieldDefinition = {
167
226
  key,
@@ -213,7 +272,23 @@ export function inferResource(
213
272
  show: generateShowPageCode(resource),
214
273
  };
215
274
 
216
- return { fields, resource, code, typeboxCode, componentCode };
275
+ return {
276
+ fields,
277
+ resource,
278
+ code,
279
+ typeboxCode,
280
+ review: {
281
+ needsReview: warnings.length > 0,
282
+ confidence: warnings.some((warning) => warning.code === 'mixed-types' || warning.code === 'nullable-field')
283
+ ? 'low'
284
+ : warnings.length > 0
285
+ ? 'medium'
286
+ : 'high',
287
+ sampleSize: sampleData.length,
288
+ warnings,
289
+ },
290
+ componentCode,
291
+ };
217
292
  }
218
293
 
219
294
  // ─── Code Generation ─────────────────────────────────────────
@@ -462,6 +537,12 @@ export function generateResourceBundle(resource: ResourceDefinition): InferResul
462
537
  resource,
463
538
  code: generateResourceCode(resource),
464
539
  typeboxCode: generateTypeBoxSchemaCode(resource),
540
+ review: {
541
+ needsReview: false,
542
+ confidence: 'high',
543
+ sampleSize: 0,
544
+ warnings: [],
545
+ },
465
546
  componentCode: {
466
547
  list: generateListPageCode(resource),
467
548
  create: generateCreatePageCode(resource),