@gridfox/codegen 0.2.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.
Files changed (61) hide show
  1. package/.env.example +3 -0
  2. package/README.md +1152 -0
  3. package/dist/cli/main.d.ts +2 -0
  4. package/dist/cli/main.js +394 -0
  5. package/dist/cli/prompt.d.ts +4 -0
  6. package/dist/cli/prompt.js +89 -0
  7. package/dist/config/loadConfig.d.ts +2 -0
  8. package/dist/config/loadConfig.js +49 -0
  9. package/dist/config/schema.d.ts +21 -0
  10. package/dist/config/schema.js +17 -0
  11. package/dist/emit/formatter.d.ts +1 -0
  12. package/dist/emit/formatter.js +2 -0
  13. package/dist/emit/writer.d.ts +7 -0
  14. package/dist/emit/writer.js +37 -0
  15. package/dist/generate.d.ts +9 -0
  16. package/dist/generate.js +53 -0
  17. package/dist/generators/generateIndexFile.d.ts +2 -0
  18. package/dist/generators/generateIndexFile.js +12 -0
  19. package/dist/generators/generateRegistryFile.d.ts +2 -0
  20. package/dist/generators/generateRegistryFile.js +7 -0
  21. package/dist/generators/generateSdkClientFile.d.ts +2 -0
  22. package/dist/generators/generateSdkClientFile.js +46 -0
  23. package/dist/generators/generateSharedTypes.d.ts +1 -0
  24. package/dist/generators/generateSharedTypes.js +4 -0
  25. package/dist/generators/generateTableModule.d.ts +2 -0
  26. package/dist/generators/generateTableModule.js +49 -0
  27. package/dist/index.d.ts +9 -0
  28. package/dist/index.js +8 -0
  29. package/dist/input/apiTransport.d.ts +6 -0
  30. package/dist/input/apiTransport.js +21 -0
  31. package/dist/input/parseTablesPayload.d.ts +21 -0
  32. package/dist/input/parseTablesPayload.js +71 -0
  33. package/dist/input/readApiInput.d.ts +9 -0
  34. package/dist/input/readApiInput.js +17 -0
  35. package/dist/input/readInput.d.ts +21 -0
  36. package/dist/input/readInput.js +14 -0
  37. package/dist/model/internalTypes.d.ts +60 -0
  38. package/dist/model/internalTypes.js +1 -0
  39. package/dist/model/normalizeTables.d.ts +6 -0
  40. package/dist/model/normalizeTables.js +68 -0
  41. package/dist/model/zodSchemas.d.ts +120 -0
  42. package/dist/model/zodSchemas.js +47 -0
  43. package/dist/naming/fieldAliases.d.ts +1 -0
  44. package/dist/naming/fieldAliases.js +3 -0
  45. package/dist/naming/identifiers.d.ts +1 -0
  46. package/dist/naming/identifiers.js +11 -0
  47. package/dist/naming/reservedWords.d.ts +1 -0
  48. package/dist/naming/reservedWords.js +13 -0
  49. package/dist/naming/tableNames.d.ts +1 -0
  50. package/dist/naming/tableNames.js +3 -0
  51. package/dist/typing/mapFieldType.d.ts +8 -0
  52. package/dist/typing/mapFieldType.js +95 -0
  53. package/dist/typing/writability.d.ts +1 -0
  54. package/dist/typing/writability.js +2 -0
  55. package/dist/utils/sort.d.ts +11 -0
  56. package/dist/utils/sort.js +5 -0
  57. package/dist/validate/crudPlan.d.ts +23 -0
  58. package/dist/validate/crudPlan.js +189 -0
  59. package/dist/validate/renderCrudTest.d.ts +2 -0
  60. package/dist/validate/renderCrudTest.js +180 -0
  61. package/package.json +57 -0
@@ -0,0 +1,120 @@
1
+ import { z } from 'zod';
2
+ export declare const GRIDFOX_FIELD_TYPES: readonly ["text", "email", "textArea", "richText", "user", "number", "money", "percentage", "formula", "autoCounter", "checkbox", "date", "dateTime", "list", "multiSelectList", "parent", "child", "manyToMany", "file", "image"];
3
+ export declare const GridfoxFieldSchema: z.ZodObject<{
4
+ name: z.ZodString;
5
+ type: z.ZodEnum<{
6
+ number: "number";
7
+ text: "text";
8
+ email: "email";
9
+ textArea: "textArea";
10
+ richText: "richText";
11
+ user: "user";
12
+ money: "money";
13
+ percentage: "percentage";
14
+ formula: "formula";
15
+ autoCounter: "autoCounter";
16
+ checkbox: "checkbox";
17
+ date: "date";
18
+ dateTime: "dateTime";
19
+ list: "list";
20
+ multiSelectList: "multiSelectList";
21
+ parent: "parent";
22
+ child: "child";
23
+ manyToMany: "manyToMany";
24
+ file: "file";
25
+ image: "image";
26
+ }>;
27
+ isRequired: z.ZodOptional<z.ZodBoolean>;
28
+ isUnique: z.ZodOptional<z.ZodBoolean>;
29
+ properties: z.ZodOptional<z.ZodObject<{
30
+ items: z.ZodOptional<z.ZodArray<z.ZodObject<{
31
+ value: z.ZodString;
32
+ color: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
+ }, z.core.$strip>>>;
34
+ relatedTableName: z.ZodOptional<z.ZodString>;
35
+ formulaType: z.ZodOptional<z.ZodString>;
36
+ currency: z.ZodOptional<z.ZodString>;
37
+ }, z.core.$catchall<z.ZodUnknown>>>;
38
+ }, z.core.$strip>;
39
+ export declare const GridfoxTableSchema: z.ZodObject<{
40
+ name: z.ZodString;
41
+ singularName: z.ZodString;
42
+ referenceFieldName: z.ZodString;
43
+ fields: z.ZodArray<z.ZodObject<{
44
+ name: z.ZodString;
45
+ type: z.ZodEnum<{
46
+ number: "number";
47
+ text: "text";
48
+ email: "email";
49
+ textArea: "textArea";
50
+ richText: "richText";
51
+ user: "user";
52
+ money: "money";
53
+ percentage: "percentage";
54
+ formula: "formula";
55
+ autoCounter: "autoCounter";
56
+ checkbox: "checkbox";
57
+ date: "date";
58
+ dateTime: "dateTime";
59
+ list: "list";
60
+ multiSelectList: "multiSelectList";
61
+ parent: "parent";
62
+ child: "child";
63
+ manyToMany: "manyToMany";
64
+ file: "file";
65
+ image: "image";
66
+ }>;
67
+ isRequired: z.ZodOptional<z.ZodBoolean>;
68
+ isUnique: z.ZodOptional<z.ZodBoolean>;
69
+ properties: z.ZodOptional<z.ZodObject<{
70
+ items: z.ZodOptional<z.ZodArray<z.ZodObject<{
71
+ value: z.ZodString;
72
+ color: z.ZodOptional<z.ZodNullable<z.ZodString>>;
73
+ }, z.core.$strip>>>;
74
+ relatedTableName: z.ZodOptional<z.ZodString>;
75
+ formulaType: z.ZodOptional<z.ZodString>;
76
+ currency: z.ZodOptional<z.ZodString>;
77
+ }, z.core.$catchall<z.ZodUnknown>>>;
78
+ }, z.core.$strip>>;
79
+ }, z.core.$strip>;
80
+ export declare const GridfoxTablesSchema: z.ZodArray<z.ZodObject<{
81
+ name: z.ZodString;
82
+ singularName: z.ZodString;
83
+ referenceFieldName: z.ZodString;
84
+ fields: z.ZodArray<z.ZodObject<{
85
+ name: z.ZodString;
86
+ type: z.ZodEnum<{
87
+ number: "number";
88
+ text: "text";
89
+ email: "email";
90
+ textArea: "textArea";
91
+ richText: "richText";
92
+ user: "user";
93
+ money: "money";
94
+ percentage: "percentage";
95
+ formula: "formula";
96
+ autoCounter: "autoCounter";
97
+ checkbox: "checkbox";
98
+ date: "date";
99
+ dateTime: "dateTime";
100
+ list: "list";
101
+ multiSelectList: "multiSelectList";
102
+ parent: "parent";
103
+ child: "child";
104
+ manyToMany: "manyToMany";
105
+ file: "file";
106
+ image: "image";
107
+ }>;
108
+ isRequired: z.ZodOptional<z.ZodBoolean>;
109
+ isUnique: z.ZodOptional<z.ZodBoolean>;
110
+ properties: z.ZodOptional<z.ZodObject<{
111
+ items: z.ZodOptional<z.ZodArray<z.ZodObject<{
112
+ value: z.ZodString;
113
+ color: z.ZodOptional<z.ZodNullable<z.ZodString>>;
114
+ }, z.core.$strip>>>;
115
+ relatedTableName: z.ZodOptional<z.ZodString>;
116
+ formulaType: z.ZodOptional<z.ZodString>;
117
+ currency: z.ZodOptional<z.ZodString>;
118
+ }, z.core.$catchall<z.ZodUnknown>>>;
119
+ }, z.core.$strip>>;
120
+ }, z.core.$strip>>;
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ export const GRIDFOX_FIELD_TYPES = [
3
+ 'text',
4
+ 'email',
5
+ 'textArea',
6
+ 'richText',
7
+ 'user',
8
+ 'number',
9
+ 'money',
10
+ 'percentage',
11
+ 'formula',
12
+ 'autoCounter',
13
+ 'checkbox',
14
+ 'date',
15
+ 'dateTime',
16
+ 'list',
17
+ 'multiSelectList',
18
+ 'parent',
19
+ 'child',
20
+ 'manyToMany',
21
+ 'file',
22
+ 'image'
23
+ ];
24
+ const GridfoxFieldTypeSchema = z.enum(GRIDFOX_FIELD_TYPES);
25
+ const FieldPropertiesSchema = z
26
+ .object({
27
+ items: z.array(z.object({ value: z.string(), color: z.string().nullable().optional() })).optional(),
28
+ relatedTableName: z.string().optional(),
29
+ formulaType: z.string().optional(),
30
+ currency: z.string().optional()
31
+ })
32
+ .catchall(z.unknown())
33
+ .optional();
34
+ export const GridfoxFieldSchema = z.object({
35
+ name: z.string().min(1),
36
+ type: GridfoxFieldTypeSchema,
37
+ isRequired: z.boolean().optional(),
38
+ isUnique: z.boolean().optional(),
39
+ properties: FieldPropertiesSchema
40
+ });
41
+ export const GridfoxTableSchema = z.object({
42
+ name: z.string().min(1),
43
+ singularName: z.string().min(1),
44
+ referenceFieldName: z.string().min(1),
45
+ fields: z.array(GridfoxFieldSchema)
46
+ });
47
+ export const GridfoxTablesSchema = z.array(GridfoxTableSchema);
@@ -0,0 +1 @@
1
+ export declare const normalizeFieldAlias: (fieldName: string) => string;
@@ -0,0 +1,3 @@
1
+ import { camelCase } from 'change-case';
2
+ import { safeIdentifier } from './reservedWords.js';
3
+ export const normalizeFieldAlias = (fieldName) => safeIdentifier(camelCase(fieldName));
@@ -0,0 +1 @@
1
+ export declare const ensureIdentifier: (value: string, fallbackPrefix: string) => string;
@@ -0,0 +1,11 @@
1
+ const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2
+ export const ensureIdentifier = (value, fallbackPrefix) => {
3
+ const trimmed = value.trim().replace(/[^A-Za-z0-9_$]/g, '');
4
+ if (!trimmed) {
5
+ return fallbackPrefix;
6
+ }
7
+ if (identifierPattern.test(trimmed)) {
8
+ return trimmed;
9
+ }
10
+ return `${fallbackPrefix}${trimmed}`;
11
+ };
@@ -0,0 +1 @@
1
+ export declare const safeIdentifier: (value: string) => string;
@@ -0,0 +1,13 @@
1
+ const reserved = new Set([
2
+ 'default',
3
+ 'delete',
4
+ 'class',
5
+ 'function',
6
+ 'return',
7
+ 'import',
8
+ 'export',
9
+ 'var',
10
+ 'let',
11
+ 'const'
12
+ ]);
13
+ export const safeIdentifier = (value) => reserved.has(value) ? `${value}Field` : value;
@@ -0,0 +1 @@
1
+ export declare const normalizeTableSymbol: (tableName: string) => string;
@@ -0,0 +1,3 @@
1
+ import { pascalCase } from 'change-case';
2
+ import { ensureIdentifier } from './identifiers.js';
3
+ export const normalizeTableSymbol = (tableName) => ensureIdentifier(pascalCase(tableName), 'Table');
@@ -0,0 +1,8 @@
1
+ import type { RawField } from '../model/internalTypes.js';
2
+ export declare const listUnionType: (values: string[]) => string;
3
+ interface TypeMappingOptions {
4
+ multiSelectMode?: 'union' | 'stringArray';
5
+ }
6
+ export declare const mapReadType: (field: RawField, mappingOptions?: TypeMappingOptions) => string;
7
+ export declare const mapWriteType: (field: RawField, mappingOptions?: TypeMappingOptions) => string;
8
+ export {};
@@ -0,0 +1,95 @@
1
+ const quote = (v) => JSON.stringify(v);
2
+ export const listUnionType = (values) => values.map(quote).join(' | ');
3
+ const numericFormulaTypes = new Set(['number', 'money', 'percentage', 'autocounter']);
4
+ const mapFormulaReadType = (field) => {
5
+ const formulaType = field.properties?.formulaType;
6
+ if (!formulaType) {
7
+ return 'number | string | null';
8
+ }
9
+ if (numericFormulaTypes.has(formulaType.toLowerCase())) {
10
+ return 'number | null';
11
+ }
12
+ return 'string | null';
13
+ };
14
+ export const mapReadType = (field, mappingOptions = {}) => {
15
+ const listOptions = field.properties?.items?.map((item) => item.value) ?? [];
16
+ const multiSelectMode = mappingOptions.multiSelectMode ?? 'union';
17
+ switch (field.type) {
18
+ case 'text':
19
+ case 'email':
20
+ case 'textArea':
21
+ case 'richText':
22
+ case 'user':
23
+ case 'date':
24
+ case 'dateTime':
25
+ return 'string | null';
26
+ case 'number':
27
+ case 'money':
28
+ case 'percentage':
29
+ case 'autoCounter':
30
+ return 'number | null';
31
+ case 'formula':
32
+ return mapFormulaReadType(field);
33
+ case 'checkbox':
34
+ return 'boolean | null';
35
+ case 'list':
36
+ return listOptions.length ? `${listUnionType(listOptions)} | null` : 'string | null';
37
+ case 'multiSelectList':
38
+ if (multiSelectMode === 'stringArray') {
39
+ return 'string[] | null';
40
+ }
41
+ return listOptions.length ? `(${listUnionType(listOptions)})[] | null` : 'string[] | null';
42
+ case 'parent':
43
+ return 'GridfoxLinkedValue | null';
44
+ case 'child':
45
+ case 'manyToMany':
46
+ return 'GridfoxLinkedValue[] | null';
47
+ case 'file':
48
+ return 'GridfoxFileValue[] | null';
49
+ case 'image':
50
+ return 'GridfoxImageValue | null';
51
+ default:
52
+ return 'unknown';
53
+ }
54
+ };
55
+ export const mapWriteType = (field, mappingOptions = {}) => {
56
+ const listOptions = field.properties?.items?.map((item) => item.value) ?? [];
57
+ const multiSelectMode = mappingOptions.multiSelectMode ?? 'union';
58
+ switch (field.type) {
59
+ case 'formula':
60
+ case 'autoCounter':
61
+ case 'child':
62
+ return 'never';
63
+ case 'list':
64
+ return listOptions.length ? listUnionType(listOptions) : 'string';
65
+ case 'multiSelectList':
66
+ if (multiSelectMode === 'stringArray') {
67
+ return 'string[]';
68
+ }
69
+ return listOptions.length ? `(${listUnionType(listOptions)})[]` : 'string[]';
70
+ case 'parent':
71
+ return 'GridfoxLinkedValue';
72
+ case 'manyToMany':
73
+ return 'GridfoxLinkedValue[]';
74
+ case 'file':
75
+ return 'GridfoxFileValue[]';
76
+ case 'image':
77
+ return 'GridfoxImageValue';
78
+ case 'text':
79
+ case 'email':
80
+ case 'textArea':
81
+ case 'richText':
82
+ case 'user':
83
+ case 'date':
84
+ case 'dateTime':
85
+ return 'string';
86
+ case 'number':
87
+ case 'money':
88
+ case 'percentage':
89
+ return 'number';
90
+ case 'checkbox':
91
+ return 'boolean';
92
+ default:
93
+ return 'unknown';
94
+ }
95
+ };
@@ -0,0 +1 @@
1
+ export declare const isWritableFieldType: (kind: string) => boolean;
@@ -0,0 +1,2 @@
1
+ const readonlyKinds = new Set(['autoCounter', 'formula', 'child']);
2
+ export const isWritableFieldType = (kind) => !readonlyKinds.has(kind);
@@ -0,0 +1,11 @@
1
+ export declare const compareText: (left: string, right: string) => number;
2
+ export declare const sortByName: <T extends {
3
+ name: string;
4
+ }>(items: T[]) => T[];
5
+ export declare const sortByFieldName: <T extends {
6
+ fieldName: string;
7
+ }>(items: T[]) => T[];
8
+ export declare const sortByTableName: <T extends {
9
+ tableName: string;
10
+ }>(items: T[]) => T[];
11
+ export declare const sortedKeys: <T>(record: Record<string, T>) => string[];
@@ -0,0 +1,5 @@
1
+ export const compareText = (left, right) => left.localeCompare(right);
2
+ export const sortByName = (items) => [...items].sort((left, right) => compareText(left.name, right.name));
3
+ export const sortByFieldName = (items) => [...items].sort((left, right) => compareText(left.fieldName, right.fieldName));
4
+ export const sortByTableName = (items) => [...items].sort((left, right) => compareText(left.tableName, right.tableName));
5
+ export const sortedKeys = (record) => Object.keys(record).sort(compareText);
@@ -0,0 +1,23 @@
1
+ import type { GridfoxFieldType, RawTable } from '../model/internalTypes.js';
2
+ export type CrudPrimitive = string | number | boolean | null;
3
+ export type CrudArray = CrudPrimitive[];
4
+ export type CrudValue = CrudPrimitive | CrudArray;
5
+ export type CrudPayload = Record<string, CrudValue>;
6
+ export interface CrudPlan {
7
+ tableName: string;
8
+ referenceFieldName: string;
9
+ createPayload: CrudPayload;
10
+ updatePayload: CrudPayload;
11
+ verifyAfterCreate: CrudPayload;
12
+ verifyAfterUpdate: CrudPayload;
13
+ rationale?: string;
14
+ }
15
+ export interface ExecutableCrudPlan extends CrudPlan {
16
+ referenceFieldType: GridfoxFieldType;
17
+ }
18
+ interface BuildHeuristicPlanOptions {
19
+ tableName?: string;
20
+ runId?: string;
21
+ }
22
+ export declare const buildHeuristicPlan: (tables: RawTable[], options?: BuildHeuristicPlanOptions) => ExecutableCrudPlan;
23
+ export {};
@@ -0,0 +1,189 @@
1
+ import { sortByName } from '../utils/sort.js';
2
+ import { isWritableFieldType } from '../typing/writability.js';
3
+ const stringLikeReferenceTypes = new Set(['text', 'email', 'textArea', 'richText', 'user']);
4
+ const scalarWritableTypes = new Set([
5
+ 'text',
6
+ 'email',
7
+ 'textArea',
8
+ 'richText',
9
+ 'user',
10
+ 'number',
11
+ 'money',
12
+ 'percentage',
13
+ 'checkbox',
14
+ 'date',
15
+ 'dateTime',
16
+ 'list',
17
+ 'multiSelectList'
18
+ ]);
19
+ const uniqueValueSafeTypes = new Set([
20
+ 'text',
21
+ 'email',
22
+ 'textArea',
23
+ 'richText',
24
+ 'user',
25
+ 'number',
26
+ 'money',
27
+ 'percentage',
28
+ 'date',
29
+ 'dateTime'
30
+ ]);
31
+ const createReferencePlaceholder = '__GRIDFOX_REFERENCE_VALUE__';
32
+ const hashText = (text) => {
33
+ let out = 0;
34
+ for (let index = 0; index < text.length; index += 1) {
35
+ out = (out * 31 + text.charCodeAt(index)) >>> 0;
36
+ }
37
+ return out;
38
+ };
39
+ const createRunId = () => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
40
+ const makeNumeric = (field, runId, variant) => {
41
+ const base = 100_000 + (hashText(`${field.name}:${runId}`) % 800_000);
42
+ return variant === 'create' ? base : base + 1;
43
+ };
44
+ const twoDigit = (value) => String(value).padStart(2, '0');
45
+ const sampleValueForField = (field, variant, runId) => {
46
+ const tag = variant === 'create' ? 'c' : 'u';
47
+ const suffix = `${tag}-${runId}`;
48
+ switch (field.type) {
49
+ case 'text':
50
+ case 'textArea':
51
+ case 'richText':
52
+ return `real-${field.name.toLowerCase().replace(/\s+/g, '-')}-${suffix}`;
53
+ case 'email':
54
+ return `real-${suffix}@example.com`;
55
+ case 'user':
56
+ return `real-user-${suffix}`;
57
+ case 'number':
58
+ case 'money':
59
+ case 'percentage':
60
+ return makeNumeric(field, runId, variant);
61
+ case 'checkbox':
62
+ return variant === 'create';
63
+ case 'date': {
64
+ const day = 1 + (hashText(`${field.name}:${runId}:day`) % 28);
65
+ const month = variant === 'create' ? '01' : '02';
66
+ return `2026-${month}-${twoDigit(day)}`;
67
+ }
68
+ case 'dateTime': {
69
+ const day = 1 + (hashText(`${field.name}:${runId}:day`) % 28);
70
+ const hour = hashText(`${field.name}:${runId}:hour`) % 24;
71
+ const minute = hashText(`${field.name}:${runId}:minute`) % 60;
72
+ const month = variant === 'create' ? '01' : '02';
73
+ return `2026-${month}-${twoDigit(day)}T${twoDigit(hour)}:${twoDigit(minute)}:00.000Z`;
74
+ }
75
+ case 'list': {
76
+ const options = field.properties?.items?.map((item) => item.value) ?? [];
77
+ if (options.length >= 2) {
78
+ return variant === 'create' ? options[0] : options[1];
79
+ }
80
+ if (options.length === 1) {
81
+ return options[0];
82
+ }
83
+ return variant === 'create' ? 'Option A' : 'Option B';
84
+ }
85
+ case 'multiSelectList': {
86
+ const options = field.properties?.items?.map((item) => item.value) ?? [];
87
+ if (options.length >= 2) {
88
+ return variant === 'create' ? [options[0]] : [options[0], options[1]];
89
+ }
90
+ if (options.length === 1) {
91
+ return [options[0]];
92
+ }
93
+ return variant === 'create' ? ['Option A'] : ['Option B'];
94
+ }
95
+ default:
96
+ return undefined;
97
+ }
98
+ };
99
+ const findField = (table, fieldName) => table.fields.find((field) => field.name === fieldName);
100
+ const supportsHeuristicField = (field) => scalarWritableTypes.has(field.type);
101
+ const supportsUniqueValueGeneration = (field) => uniqueValueSafeTypes.has(field.type);
102
+ export const buildHeuristicPlan = (tables, options = {}) => {
103
+ const runId = options.runId ?? createRunId();
104
+ const candidates = options.tableName === undefined ? sortByName(tables) : sortByName(tables.filter((table) => table.name === options.tableName));
105
+ if (options.tableName && candidates.length === 0) {
106
+ throw new Error(`Table "${options.tableName}" was not found in tables metadata`);
107
+ }
108
+ for (const table of candidates) {
109
+ const referenceField = findField(table, table.referenceFieldName);
110
+ if (!referenceField) {
111
+ continue;
112
+ }
113
+ if (!stringLikeReferenceTypes.has(referenceField.type)) {
114
+ continue;
115
+ }
116
+ const requiredWritableFields = table.fields.filter((field) => field.isRequired === true && isWritableFieldType(field.type));
117
+ if (requiredWritableFields.some((field) => !supportsHeuristicField(field))) {
118
+ continue;
119
+ }
120
+ if (requiredWritableFields.some((field) => field.isUnique === true && !supportsUniqueValueGeneration(field))) {
121
+ continue;
122
+ }
123
+ const writableFields = table.fields.filter((field) => isWritableFieldType(field.type) && supportsHeuristicField(field));
124
+ const updateCandidates = [
125
+ ...writableFields.filter((field) => field.isUnique !== true),
126
+ ...writableFields.filter((field) => field.isUnique === true && supportsUniqueValueGeneration(field))
127
+ ];
128
+ const updateCandidate = updateCandidates.find((field) => {
129
+ if (field.name === table.referenceFieldName) {
130
+ return false;
131
+ }
132
+ const createValue = sampleValueForField(field, 'create', runId);
133
+ const updateValue = sampleValueForField(field, 'update', runId);
134
+ return createValue !== undefined && updateValue !== undefined && createValue !== updateValue;
135
+ });
136
+ if (!updateCandidate) {
137
+ continue;
138
+ }
139
+ const createPayload = {
140
+ [table.referenceFieldName]: createReferencePlaceholder
141
+ };
142
+ for (const field of requiredWritableFields) {
143
+ if (field.name === table.referenceFieldName) {
144
+ continue;
145
+ }
146
+ const sample = sampleValueForField(field, 'create', runId);
147
+ if (sample === undefined) {
148
+ continue;
149
+ }
150
+ createPayload[field.name] = sample;
151
+ }
152
+ if (!(updateCandidate.name in createPayload)) {
153
+ const createSample = sampleValueForField(updateCandidate, 'create', runId);
154
+ if (createSample !== undefined) {
155
+ createPayload[updateCandidate.name] = createSample;
156
+ }
157
+ }
158
+ const updateSample = sampleValueForField(updateCandidate, 'update', runId);
159
+ if (updateSample === undefined) {
160
+ continue;
161
+ }
162
+ const updatePayload = {
163
+ [table.referenceFieldName]: createReferencePlaceholder,
164
+ [updateCandidate.name]: updateSample
165
+ };
166
+ const verifyAfterCreate = {
167
+ [table.referenceFieldName]: createReferencePlaceholder,
168
+ [updateCandidate.name]: createPayload[updateCandidate.name]
169
+ };
170
+ const verifyAfterUpdate = {
171
+ [table.referenceFieldName]: createReferencePlaceholder,
172
+ [updateCandidate.name]: updateSample
173
+ };
174
+ return {
175
+ tableName: table.name,
176
+ referenceFieldName: table.referenceFieldName,
177
+ referenceFieldType: referenceField.type,
178
+ createPayload,
179
+ updatePayload,
180
+ verifyAfterCreate,
181
+ verifyAfterUpdate,
182
+ rationale: 'Heuristic selected first CRUD-safe table'
183
+ };
184
+ }
185
+ if (options.tableName) {
186
+ throw new Error(`Table "${options.tableName}" is not CRUD-safe for heuristic real-project tests`);
187
+ }
188
+ throw new Error('Unable to find a CRUD-safe table for real-project tests');
189
+ };
@@ -0,0 +1,2 @@
1
+ import type { ExecutableCrudPlan } from './crudPlan.js';
2
+ export declare const buildCrudTestSource: (plan: ExecutableCrudPlan) => string;