@promakeai/orm 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/README.md +154 -0
- package/dist/ORM.d.ts +190 -0
- package/dist/adapters/IDataAdapter.d.ts +134 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +1054 -0
- package/dist/schema/defineSchema.d.ts +50 -0
- package/dist/schema/fieldBuilder.d.ts +152 -0
- package/dist/schema/helpers.d.ts +54 -0
- package/dist/schema/index.d.ts +9 -0
- package/dist/schema/schemaHelpers.d.ts +55 -0
- package/dist/schema/validator.d.ts +45 -0
- package/dist/types.d.ts +192 -0
- package/dist/utils/jsonConverter.d.ts +29 -0
- package/dist/utils/populateResolver.d.ts +57 -0
- package/dist/utils/translationQuery.d.ts +50 -0
- package/dist/utils/whereBuilder.d.ts +38 -0
- package/package.json +48 -0
- package/src/ORM.ts +398 -0
- package/src/adapters/IDataAdapter.ts +196 -0
- package/src/index.ts +148 -0
- package/src/schema/defineSchema.ts +164 -0
- package/src/schema/fieldBuilder.ts +244 -0
- package/src/schema/helpers.ts +171 -0
- package/src/schema/index.ts +47 -0
- package/src/schema/schemaHelpers.ts +123 -0
- package/src/schema/validator.ts +189 -0
- package/src/types.ts +243 -0
- package/src/utils/jsonConverter.ts +94 -0
- package/src/utils/populateResolver.ts +322 -0
- package/src/utils/translationQuery.ts +306 -0
- package/src/utils/whereBuilder.ts +154 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Module Exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Main API
|
|
6
|
+
export { defineSchema, f, mergeSchemas, createSchemaUnsafe } from "./defineSchema";
|
|
7
|
+
export type { FieldBuilder } from "./fieldBuilder";
|
|
8
|
+
|
|
9
|
+
// Validation
|
|
10
|
+
export {
|
|
11
|
+
validateSchema,
|
|
12
|
+
assertValidSchema,
|
|
13
|
+
isValidSchema,
|
|
14
|
+
validateTable,
|
|
15
|
+
ValidationErrorCode,
|
|
16
|
+
} from "./validator";
|
|
17
|
+
export type { ValidationError } from "./validator";
|
|
18
|
+
|
|
19
|
+
// String Helpers
|
|
20
|
+
export {
|
|
21
|
+
singularize,
|
|
22
|
+
pluralize,
|
|
23
|
+
toPascalCase,
|
|
24
|
+
toCamelCase,
|
|
25
|
+
toSnakeCase,
|
|
26
|
+
toInterfaceName,
|
|
27
|
+
toDbInterfaceName,
|
|
28
|
+
toPascalCasePlural,
|
|
29
|
+
toTranslationTableName,
|
|
30
|
+
toTranslationFKName,
|
|
31
|
+
} from "./helpers";
|
|
32
|
+
|
|
33
|
+
// Schema Helpers
|
|
34
|
+
export {
|
|
35
|
+
getTranslatableFields,
|
|
36
|
+
getNonTranslatableFields,
|
|
37
|
+
getInsertableFields,
|
|
38
|
+
hasTranslatableFields,
|
|
39
|
+
getPrimaryKeyField,
|
|
40
|
+
getReferenceFields,
|
|
41
|
+
getMainTableFields,
|
|
42
|
+
getTranslationTableFields,
|
|
43
|
+
isRequiredField,
|
|
44
|
+
getRequiredFields,
|
|
45
|
+
getRefTarget,
|
|
46
|
+
getRefTargetFull,
|
|
47
|
+
} from "./schemaHelpers";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Helper Functions
|
|
3
|
+
*
|
|
4
|
+
* Utilities for working with schema definitions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { TableDefinition, FieldDefinition, FieldReference } from "../types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get names of translatable fields in a table
|
|
11
|
+
*/
|
|
12
|
+
export function getTranslatableFields(table: TableDefinition): string[] {
|
|
13
|
+
return Object.entries(table.fields)
|
|
14
|
+
.filter(([_, field]) => field.translatable)
|
|
15
|
+
.map(([name]) => name);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get names of non-translatable fields in a table (excluding primary key)
|
|
20
|
+
*/
|
|
21
|
+
export function getNonTranslatableFields(table: TableDefinition): string[] {
|
|
22
|
+
return Object.entries(table.fields)
|
|
23
|
+
.filter(([_, field]) => !field.translatable && !field.primary)
|
|
24
|
+
.map(([name]) => name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get all non-primary key fields (for INSERT statements)
|
|
29
|
+
*/
|
|
30
|
+
export function getInsertableFields(table: TableDefinition): string[] {
|
|
31
|
+
return Object.entries(table.fields)
|
|
32
|
+
.filter(([_, field]) => !field.primary)
|
|
33
|
+
.map(([name]) => name);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if table has any translatable fields
|
|
38
|
+
*/
|
|
39
|
+
export function hasTranslatableFields(table: TableDefinition): boolean {
|
|
40
|
+
return getTranslatableFields(table).length > 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the primary key field name
|
|
45
|
+
*/
|
|
46
|
+
export function getPrimaryKeyField(table: TableDefinition): string | null {
|
|
47
|
+
const entry = Object.entries(table.fields).find(([_, field]) => field.primary);
|
|
48
|
+
return entry ? entry[0] : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get fields with foreign key references (using ref)
|
|
53
|
+
*/
|
|
54
|
+
export function getReferenceFields(
|
|
55
|
+
table: TableDefinition
|
|
56
|
+
): [string, FieldReference][] {
|
|
57
|
+
return Object.entries(table.fields)
|
|
58
|
+
.filter(([_, field]) => field.ref)
|
|
59
|
+
.map(([name, field]) => {
|
|
60
|
+
const ref = field.ref!;
|
|
61
|
+
// Normalize string to FieldReference
|
|
62
|
+
if (typeof ref === "string") {
|
|
63
|
+
return [name, { table: ref, field: "id" }];
|
|
64
|
+
}
|
|
65
|
+
return [name, { ...ref, field: ref.field || "id" }];
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get fields for main table (excluding translatable)
|
|
71
|
+
*/
|
|
72
|
+
export function getMainTableFields(
|
|
73
|
+
table: TableDefinition
|
|
74
|
+
): [string, FieldDefinition][] {
|
|
75
|
+
return Object.entries(table.fields).filter(([_, field]) => !field.translatable);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get fields for translation table
|
|
80
|
+
*/
|
|
81
|
+
export function getTranslationTableFields(
|
|
82
|
+
table: TableDefinition
|
|
83
|
+
): [string, FieldDefinition][] {
|
|
84
|
+
return Object.entries(table.fields).filter(([_, field]) => field.translatable);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if a field is required (NOT NULL without default)
|
|
89
|
+
*/
|
|
90
|
+
export function isRequiredField(field: FieldDefinition): boolean {
|
|
91
|
+
return !field.nullable && field.default === undefined && !field.primary;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get required fields for input validation
|
|
96
|
+
*/
|
|
97
|
+
export function getRequiredFields(table: TableDefinition): string[] {
|
|
98
|
+
return Object.entries(table.fields)
|
|
99
|
+
.filter(([_, field]) => isRequiredField(field))
|
|
100
|
+
.map(([name]) => name);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get reference target table name from field
|
|
105
|
+
*/
|
|
106
|
+
export function getRefTarget(field: FieldDefinition): string | null {
|
|
107
|
+
if (!field.ref) return null;
|
|
108
|
+
return typeof field.ref === "string" ? field.ref : field.ref.table;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get full reference target (table and field) from field
|
|
113
|
+
* Returns { table, field } object
|
|
114
|
+
*/
|
|
115
|
+
export function getRefTargetFull(
|
|
116
|
+
field: FieldDefinition
|
|
117
|
+
): FieldReference | null {
|
|
118
|
+
if (!field.ref) return null;
|
|
119
|
+
if (typeof field.ref === "string") {
|
|
120
|
+
return { table: field.ref, field: "id" };
|
|
121
|
+
}
|
|
122
|
+
return { table: field.ref.table, field: field.ref.field || "id" };
|
|
123
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates schema definitions for correctness.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SchemaDefinition, TableDefinition } from "../types";
|
|
8
|
+
import { getRefTarget } from "./schemaHelpers";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validation error with context
|
|
12
|
+
*/
|
|
13
|
+
export interface ValidationError {
|
|
14
|
+
table: string;
|
|
15
|
+
field?: string;
|
|
16
|
+
message: string;
|
|
17
|
+
code: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Error codes for validation
|
|
22
|
+
*/
|
|
23
|
+
export const ValidationErrorCode = {
|
|
24
|
+
MISSING_PRIMARY_KEY: "MISSING_PRIMARY_KEY",
|
|
25
|
+
INVALID_REFERENCE: "INVALID_REFERENCE",
|
|
26
|
+
INVALID_TRANSLATABLE_TYPE: "INVALID_TRANSLATABLE_TYPE",
|
|
27
|
+
NO_LANGUAGES: "NO_LANGUAGES",
|
|
28
|
+
DUPLICATE_TABLE: "DUPLICATE_TABLE",
|
|
29
|
+
RESERVED_FIELD_NAME: "RESERVED_FIELD_NAME",
|
|
30
|
+
SELF_REFERENCE_ON_REQUIRED: "SELF_REFERENCE_ON_REQUIRED",
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reserved field names that cannot be used
|
|
35
|
+
*/
|
|
36
|
+
const RESERVED_FIELDS = ["language_code"];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate a schema definition
|
|
40
|
+
*
|
|
41
|
+
* @returns Array of validation errors (empty if valid)
|
|
42
|
+
*/
|
|
43
|
+
export function validateSchema(schema: SchemaDefinition): ValidationError[] {
|
|
44
|
+
const errors: ValidationError[] = [];
|
|
45
|
+
const tableNames = Object.keys(schema.tables);
|
|
46
|
+
|
|
47
|
+
// Check languages
|
|
48
|
+
if (!schema.languages.supported || schema.languages.supported.length === 0) {
|
|
49
|
+
errors.push({
|
|
50
|
+
table: "_schema",
|
|
51
|
+
message: "At least one language must be defined",
|
|
52
|
+
code: ValidationErrorCode.NO_LANGUAGES,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate each table
|
|
57
|
+
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
58
|
+
// Check for primary key
|
|
59
|
+
const hasPrimaryKey = Object.values(table.fields).some(
|
|
60
|
+
(field) => field.primary
|
|
61
|
+
);
|
|
62
|
+
if (!hasPrimaryKey) {
|
|
63
|
+
errors.push({
|
|
64
|
+
table: tableName,
|
|
65
|
+
message: "Table must have a primary key (use f.id())",
|
|
66
|
+
code: ValidationErrorCode.MISSING_PRIMARY_KEY,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate each field
|
|
71
|
+
for (const [fieldName, field] of Object.entries(table.fields)) {
|
|
72
|
+
// Check reserved field names
|
|
73
|
+
if (RESERVED_FIELDS.includes(fieldName)) {
|
|
74
|
+
errors.push({
|
|
75
|
+
table: tableName,
|
|
76
|
+
field: fieldName,
|
|
77
|
+
message: `'${fieldName}' is a reserved field name`,
|
|
78
|
+
code: ValidationErrorCode.RESERVED_FIELD_NAME,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check foreign key references (using ref)
|
|
83
|
+
const refTarget = getRefTarget(field);
|
|
84
|
+
if (refTarget) {
|
|
85
|
+
// Check if referenced table exists
|
|
86
|
+
if (!tableNames.includes(refTarget)) {
|
|
87
|
+
errors.push({
|
|
88
|
+
table: tableName,
|
|
89
|
+
field: fieldName,
|
|
90
|
+
message: `Foreign key references non-existent table: ${refTarget}`,
|
|
91
|
+
code: ValidationErrorCode.INVALID_REFERENCE,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check for required self-reference
|
|
96
|
+
if (refTarget === tableName && !field.nullable) {
|
|
97
|
+
errors.push({
|
|
98
|
+
table: tableName,
|
|
99
|
+
field: fieldName,
|
|
100
|
+
message: `Self-referencing foreign key must be nullable`,
|
|
101
|
+
code: ValidationErrorCode.SELF_REFERENCE_ON_REQUIRED,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check translatable field type
|
|
107
|
+
if (field.translatable && !["string", "text"].includes(field.type)) {
|
|
108
|
+
errors.push({
|
|
109
|
+
table: tableName,
|
|
110
|
+
field: fieldName,
|
|
111
|
+
message: `Only string/text fields can be translatable, got: ${field.type}`,
|
|
112
|
+
code: ValidationErrorCode.INVALID_TRANSLATABLE_TYPE,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return errors;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate schema and throw if invalid
|
|
123
|
+
*/
|
|
124
|
+
export function assertValidSchema(schema: SchemaDefinition): void {
|
|
125
|
+
const errors = validateSchema(schema);
|
|
126
|
+
|
|
127
|
+
if (errors.length > 0) {
|
|
128
|
+
const messages = errors.map((e) =>
|
|
129
|
+
e.field
|
|
130
|
+
? `[${e.table}.${e.field}] ${e.message}`
|
|
131
|
+
: `[${e.table}] ${e.message}`
|
|
132
|
+
);
|
|
133
|
+
throw new Error(`Schema validation failed:\n${messages.join("\n")}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if schema is valid (returns boolean)
|
|
139
|
+
*/
|
|
140
|
+
export function isValidSchema(schema: SchemaDefinition): boolean {
|
|
141
|
+
return validateSchema(schema).length === 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Validate a single table definition
|
|
146
|
+
*/
|
|
147
|
+
export function validateTable(
|
|
148
|
+
tableName: string,
|
|
149
|
+
table: TableDefinition,
|
|
150
|
+
allTableNames: string[]
|
|
151
|
+
): ValidationError[] {
|
|
152
|
+
const errors: ValidationError[] = [];
|
|
153
|
+
|
|
154
|
+
// Check for primary key
|
|
155
|
+
const hasPrimaryKey = Object.values(table.fields).some(
|
|
156
|
+
(field) => field.primary
|
|
157
|
+
);
|
|
158
|
+
if (!hasPrimaryKey) {
|
|
159
|
+
errors.push({
|
|
160
|
+
table: tableName,
|
|
161
|
+
message: "Table must have a primary key",
|
|
162
|
+
code: ValidationErrorCode.MISSING_PRIMARY_KEY,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Validate fields
|
|
167
|
+
for (const [fieldName, field] of Object.entries(table.fields)) {
|
|
168
|
+
const refTarget = getRefTarget(field);
|
|
169
|
+
if (refTarget && !allTableNames.includes(refTarget)) {
|
|
170
|
+
errors.push({
|
|
171
|
+
table: tableName,
|
|
172
|
+
field: fieldName,
|
|
173
|
+
message: `Foreign key references non-existent table: ${refTarget}`,
|
|
174
|
+
code: ValidationErrorCode.INVALID_REFERENCE,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (field.translatable && !["string", "text"].includes(field.type)) {
|
|
179
|
+
errors.push({
|
|
180
|
+
table: tableName,
|
|
181
|
+
field: fieldName,
|
|
182
|
+
message: `Only string/text fields can be translatable`,
|
|
183
|
+
code: ValidationErrorCode.INVALID_TRANSLATABLE_TYPE,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return errors;
|
|
189
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @promakeai/orm Core Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Database-agnostic types that work in browser and Node.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ==================== Field Types ====================
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Supported field types
|
|
11
|
+
*/
|
|
12
|
+
export type FieldType =
|
|
13
|
+
| "id"
|
|
14
|
+
| "string"
|
|
15
|
+
| "text"
|
|
16
|
+
| "int"
|
|
17
|
+
| "decimal"
|
|
18
|
+
| "bool"
|
|
19
|
+
| "timestamp"
|
|
20
|
+
| "json";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Foreign key reference definition (MongoDB-style)
|
|
24
|
+
*/
|
|
25
|
+
export interface FieldReference {
|
|
26
|
+
table: string;
|
|
27
|
+
field?: string; // Default: 'id'
|
|
28
|
+
onDelete?: "CASCADE" | "SET_NULL" | "RESTRICT" | "NO_ACTION";
|
|
29
|
+
onUpdate?: "CASCADE" | "SET_NULL" | "RESTRICT" | "NO_ACTION";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Complete field definition after DSL processing
|
|
34
|
+
*/
|
|
35
|
+
export interface FieldDefinition {
|
|
36
|
+
type: FieldType;
|
|
37
|
+
nullable: boolean;
|
|
38
|
+
unique: boolean;
|
|
39
|
+
primary: boolean;
|
|
40
|
+
default?: unknown;
|
|
41
|
+
translatable: boolean;
|
|
42
|
+
|
|
43
|
+
// Reference (MongoDB-style)
|
|
44
|
+
ref?: string | FieldReference;
|
|
45
|
+
|
|
46
|
+
// Mongoose-style validations & transformations
|
|
47
|
+
required?: boolean;
|
|
48
|
+
trim?: boolean;
|
|
49
|
+
lowercase?: boolean;
|
|
50
|
+
uppercase?: boolean;
|
|
51
|
+
minLength?: number;
|
|
52
|
+
maxLength?: number;
|
|
53
|
+
min?: number;
|
|
54
|
+
max?: number;
|
|
55
|
+
enum?: string[];
|
|
56
|
+
match?: string; // RegExp pattern as string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ==================== Table & Schema Types ====================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Table definition with all fields
|
|
63
|
+
*/
|
|
64
|
+
export interface TableDefinition {
|
|
65
|
+
name: string;
|
|
66
|
+
fields: Record<string, FieldDefinition>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Language configuration
|
|
71
|
+
*/
|
|
72
|
+
export interface LanguageConfig {
|
|
73
|
+
default: string;
|
|
74
|
+
supported: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Complete schema definition
|
|
79
|
+
*/
|
|
80
|
+
export interface SchemaDefinition {
|
|
81
|
+
name?: string;
|
|
82
|
+
languages: LanguageConfig;
|
|
83
|
+
tables: Record<string, TableDefinition>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Interface for FieldBuilder-like objects (for type checking)
|
|
88
|
+
*/
|
|
89
|
+
export interface FieldBuilderLike {
|
|
90
|
+
build(): FieldDefinition;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Raw schema input before processing (from user DSL)
|
|
95
|
+
*/
|
|
96
|
+
export interface SchemaInput {
|
|
97
|
+
name?: string;
|
|
98
|
+
languages: string[] | LanguageConfig;
|
|
99
|
+
tables: Record<string, Record<string, FieldBuilderLike>>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ==================== JSON Schema Types (AI Agent Friendly) ====================
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* JSON Schema Field Definition
|
|
106
|
+
*/
|
|
107
|
+
export interface JSONFieldDefinition {
|
|
108
|
+
type: FieldType;
|
|
109
|
+
translatable?: boolean;
|
|
110
|
+
required?: boolean;
|
|
111
|
+
unique?: boolean;
|
|
112
|
+
primary?: boolean;
|
|
113
|
+
nullable?: boolean;
|
|
114
|
+
default?: unknown;
|
|
115
|
+
|
|
116
|
+
// Numeric fields
|
|
117
|
+
precision?: number;
|
|
118
|
+
scale?: number;
|
|
119
|
+
min?: number;
|
|
120
|
+
max?: number;
|
|
121
|
+
|
|
122
|
+
// String fields
|
|
123
|
+
trim?: boolean;
|
|
124
|
+
lowercase?: boolean;
|
|
125
|
+
uppercase?: boolean;
|
|
126
|
+
minLength?: number;
|
|
127
|
+
maxLength?: number;
|
|
128
|
+
enum?: string[];
|
|
129
|
+
match?: string;
|
|
130
|
+
|
|
131
|
+
// Reference (MongoDB-style)
|
|
132
|
+
ref?:
|
|
133
|
+
| string
|
|
134
|
+
| {
|
|
135
|
+
table: string;
|
|
136
|
+
field?: string;
|
|
137
|
+
onDelete?: "CASCADE" | "SET_NULL" | "RESTRICT" | "NO_ACTION";
|
|
138
|
+
onUpdate?: "CASCADE" | "SET_NULL" | "RESTRICT" | "NO_ACTION";
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* JSON Schema Table Definition
|
|
144
|
+
*/
|
|
145
|
+
export interface JSONTableDefinition {
|
|
146
|
+
[fieldName: string]: JSONFieldDefinition;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Complete JSON Schema Definition
|
|
151
|
+
*/
|
|
152
|
+
export interface JSONSchemaDefinition {
|
|
153
|
+
name?: string;
|
|
154
|
+
languages: string[];
|
|
155
|
+
defaultLanguage?: string;
|
|
156
|
+
tables: {
|
|
157
|
+
[tableName: string]: JSONTableDefinition;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ==================== Query Types ====================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* MongoDB-style WHERE operators
|
|
165
|
+
*/
|
|
166
|
+
export interface WhereCondition {
|
|
167
|
+
$eq?: unknown;
|
|
168
|
+
$ne?: unknown;
|
|
169
|
+
$gt?: number;
|
|
170
|
+
$gte?: number;
|
|
171
|
+
$lt?: number;
|
|
172
|
+
$lte?: number;
|
|
173
|
+
$in?: unknown[];
|
|
174
|
+
$nin?: unknown[];
|
|
175
|
+
$like?: string;
|
|
176
|
+
$notLike?: string;
|
|
177
|
+
$between?: [unknown, unknown];
|
|
178
|
+
$isNull?: boolean;
|
|
179
|
+
$not?: WhereCondition;
|
|
180
|
+
$and?: WhereCondition[];
|
|
181
|
+
$or?: WhereCondition[];
|
|
182
|
+
$nor?: WhereCondition[];
|
|
183
|
+
[key: string]: unknown;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Order by option
|
|
188
|
+
*/
|
|
189
|
+
export interface OrderByOption {
|
|
190
|
+
field: string;
|
|
191
|
+
direction: "ASC" | "DESC";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Populate option for resolving references (MongoDB-style)
|
|
196
|
+
*/
|
|
197
|
+
export interface PopulateNested {
|
|
198
|
+
populate?: PopulateOption;
|
|
199
|
+
where?: Record<string, unknown>;
|
|
200
|
+
limit?: number;
|
|
201
|
+
orderBy?: OrderByOption[];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export type PopulateOption =
|
|
205
|
+
| string // 'userId categoryIds'
|
|
206
|
+
| string[] // ['userId', 'categoryIds']
|
|
207
|
+
| Record<string, boolean | PopulateNested>; // { userId: true, categoryIds: {...} }
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Query options
|
|
211
|
+
*/
|
|
212
|
+
export interface QueryOptions {
|
|
213
|
+
where?: Record<string, unknown>;
|
|
214
|
+
populate?: PopulateOption;
|
|
215
|
+
orderBy?: OrderByOption[];
|
|
216
|
+
limit?: number;
|
|
217
|
+
offset?: number;
|
|
218
|
+
lang?: string;
|
|
219
|
+
fallbackLang?: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Paginated result
|
|
224
|
+
*/
|
|
225
|
+
export interface PaginatedResult<T> {
|
|
226
|
+
data: T[];
|
|
227
|
+
page: number;
|
|
228
|
+
limit: number;
|
|
229
|
+
total: number;
|
|
230
|
+
totalPages: number;
|
|
231
|
+
hasMore: boolean;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ==================== ORM Config ====================
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* ORM Configuration
|
|
238
|
+
*/
|
|
239
|
+
export interface ORMConfig {
|
|
240
|
+
schema?: SchemaDefinition;
|
|
241
|
+
defaultLang?: string;
|
|
242
|
+
fallbackLang?: string;
|
|
243
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema Converter
|
|
3
|
+
*
|
|
4
|
+
* Bidirectional conversion between JSON schema format and internal SchemaDefinition.
|
|
5
|
+
* Enables AI agents to work with JSON while maintaining type-safe internal representation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
JSONSchemaDefinition,
|
|
10
|
+
JSONFieldDefinition,
|
|
11
|
+
SchemaDefinition,
|
|
12
|
+
FieldDefinition,
|
|
13
|
+
TableDefinition,
|
|
14
|
+
LanguageConfig,
|
|
15
|
+
} from "../types";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert JSON schema to internal SchemaDefinition
|
|
19
|
+
*
|
|
20
|
+
* @param json - JSON schema from AI agent or file
|
|
21
|
+
* @returns Internal SchemaDefinition for processing
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const jsonSchema = {
|
|
26
|
+
* name: "products",
|
|
27
|
+
* languages: ["en", "tr"],
|
|
28
|
+
* tables: {
|
|
29
|
+
* products: {
|
|
30
|
+
* id: { type: "id", primary: true },
|
|
31
|
+
* name: { type: "string", translatable: true, required: true }
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* };
|
|
35
|
+
* const schema = parseJSONSchema(jsonSchema);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function parseJSONSchema(json: JSONSchemaDefinition): SchemaDefinition {
|
|
39
|
+
const languages: LanguageConfig = {
|
|
40
|
+
default: json.defaultLanguage || json.languages[0] || "en",
|
|
41
|
+
supported: json.languages || ["en"],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const tables: Record<string, TableDefinition> = {};
|
|
45
|
+
|
|
46
|
+
for (const [tableName, jsonTable] of Object.entries(json.tables)) {
|
|
47
|
+
const fields: Record<string, FieldDefinition> = {};
|
|
48
|
+
|
|
49
|
+
for (const [fieldName, jsonField] of Object.entries(jsonTable)) {
|
|
50
|
+
fields[fieldName] = convertJSONField(jsonField);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
tables[tableName] = {
|
|
54
|
+
name: tableName,
|
|
55
|
+
fields,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
name: json.name,
|
|
61
|
+
languages,
|
|
62
|
+
tables,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Convert single JSON field to internal FieldDefinition
|
|
68
|
+
*/
|
|
69
|
+
function convertJSONField(jsonField: JSONFieldDefinition): FieldDefinition {
|
|
70
|
+
// Convert ref to FieldReference format
|
|
71
|
+
let ref: FieldDefinition["ref"];
|
|
72
|
+
if (jsonField.ref) {
|
|
73
|
+
if (typeof jsonField.ref === "string") {
|
|
74
|
+
ref = jsonField.ref;
|
|
75
|
+
} else {
|
|
76
|
+
ref = {
|
|
77
|
+
table: jsonField.ref.table,
|
|
78
|
+
field: jsonField.ref.field,
|
|
79
|
+
onDelete: jsonField.ref.onDelete,
|
|
80
|
+
onUpdate: jsonField.ref.onUpdate,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
type: jsonField.type,
|
|
87
|
+
nullable: jsonField.nullable ?? !jsonField.required,
|
|
88
|
+
unique: jsonField.unique ?? false,
|
|
89
|
+
primary: jsonField.primary ?? false,
|
|
90
|
+
default: jsonField.default,
|
|
91
|
+
translatable: jsonField.translatable ?? false,
|
|
92
|
+
ref,
|
|
93
|
+
};
|
|
94
|
+
}
|