@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
package/src/index.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @promakeai/orm - Database-Agnostic ORM
|
|
3
|
+
*
|
|
4
|
+
* Works in browser and Node.js with any adapter implementing IDataAdapter.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { ORM, defineSchema, f } from '@promakeai/orm';
|
|
9
|
+
*
|
|
10
|
+
* // Define schema
|
|
11
|
+
* const schema = defineSchema({
|
|
12
|
+
* languages: ['en', 'tr'],
|
|
13
|
+
* tables: {
|
|
14
|
+
* products: {
|
|
15
|
+
* id: f.id(),
|
|
16
|
+
* name: f.string().translatable().required(),
|
|
17
|
+
* price: f.decimal().required(),
|
|
18
|
+
* categoryId: f.int().ref('categories'),
|
|
19
|
+
* },
|
|
20
|
+
* categories: {
|
|
21
|
+
* id: f.id(),
|
|
22
|
+
* name: f.string().translatable().required(),
|
|
23
|
+
* },
|
|
24
|
+
* },
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Use with adapter
|
|
28
|
+
* const db = new ORM(adapter, { schema });
|
|
29
|
+
* const products = await db.list('products', {
|
|
30
|
+
* where: { price: { $gt: 100 } },
|
|
31
|
+
* populate: ['categoryId'],
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// Main ORM Class
|
|
37
|
+
export { ORM } from "./ORM";
|
|
38
|
+
|
|
39
|
+
// Adapter Interface
|
|
40
|
+
export type { IDataAdapter } from "./adapters/IDataAdapter";
|
|
41
|
+
|
|
42
|
+
// Schema API
|
|
43
|
+
export {
|
|
44
|
+
defineSchema,
|
|
45
|
+
f,
|
|
46
|
+
mergeSchemas,
|
|
47
|
+
createSchemaUnsafe,
|
|
48
|
+
} from "./schema";
|
|
49
|
+
export type { FieldBuilder } from "./schema";
|
|
50
|
+
|
|
51
|
+
// Schema Validation
|
|
52
|
+
export {
|
|
53
|
+
validateSchema,
|
|
54
|
+
assertValidSchema,
|
|
55
|
+
isValidSchema,
|
|
56
|
+
validateTable,
|
|
57
|
+
ValidationErrorCode,
|
|
58
|
+
} from "./schema";
|
|
59
|
+
export type { ValidationError } from "./schema";
|
|
60
|
+
|
|
61
|
+
// String Helpers
|
|
62
|
+
export {
|
|
63
|
+
singularize,
|
|
64
|
+
pluralize,
|
|
65
|
+
toPascalCase,
|
|
66
|
+
toCamelCase,
|
|
67
|
+
toSnakeCase,
|
|
68
|
+
toInterfaceName,
|
|
69
|
+
toDbInterfaceName,
|
|
70
|
+
toPascalCasePlural,
|
|
71
|
+
toTranslationTableName,
|
|
72
|
+
toTranslationFKName,
|
|
73
|
+
} from "./schema";
|
|
74
|
+
|
|
75
|
+
// Schema Helpers
|
|
76
|
+
export {
|
|
77
|
+
getTranslatableFields,
|
|
78
|
+
getNonTranslatableFields,
|
|
79
|
+
getInsertableFields,
|
|
80
|
+
hasTranslatableFields,
|
|
81
|
+
getPrimaryKeyField,
|
|
82
|
+
getReferenceFields,
|
|
83
|
+
getMainTableFields,
|
|
84
|
+
getTranslationTableFields,
|
|
85
|
+
isRequiredField,
|
|
86
|
+
getRequiredFields,
|
|
87
|
+
getRefTarget,
|
|
88
|
+
getRefTargetFull,
|
|
89
|
+
} from "./schema";
|
|
90
|
+
|
|
91
|
+
// Query Builder
|
|
92
|
+
export { buildWhereClause } from "./utils/whereBuilder";
|
|
93
|
+
export type { WhereResult } from "./utils/whereBuilder";
|
|
94
|
+
|
|
95
|
+
// Translation Query Builder
|
|
96
|
+
export {
|
|
97
|
+
buildTranslationQuery,
|
|
98
|
+
buildTranslationQueryById,
|
|
99
|
+
buildTranslationInsert,
|
|
100
|
+
buildTranslationUpsert,
|
|
101
|
+
extractTranslatableData,
|
|
102
|
+
} from "./utils/translationQuery";
|
|
103
|
+
export type {
|
|
104
|
+
TranslationQueryOptions,
|
|
105
|
+
TranslationQueryResult,
|
|
106
|
+
} from "./utils/translationQuery";
|
|
107
|
+
|
|
108
|
+
// Populate Resolver
|
|
109
|
+
export {
|
|
110
|
+
resolvePopulate,
|
|
111
|
+
getPopulatableFields,
|
|
112
|
+
validatePopulate,
|
|
113
|
+
} from "./utils/populateResolver";
|
|
114
|
+
export type { PopulateAdapter } from "./utils/populateResolver";
|
|
115
|
+
|
|
116
|
+
// JSON Schema Converter
|
|
117
|
+
export { parseJSONSchema } from "./utils/jsonConverter";
|
|
118
|
+
|
|
119
|
+
// Types
|
|
120
|
+
export type {
|
|
121
|
+
// Field types
|
|
122
|
+
FieldType,
|
|
123
|
+
FieldDefinition,
|
|
124
|
+
FieldReference,
|
|
125
|
+
FieldBuilderLike,
|
|
126
|
+
|
|
127
|
+
// Schema types
|
|
128
|
+
TableDefinition,
|
|
129
|
+
LanguageConfig,
|
|
130
|
+
SchemaDefinition,
|
|
131
|
+
SchemaInput,
|
|
132
|
+
|
|
133
|
+
// JSON Schema types (AI-friendly)
|
|
134
|
+
JSONFieldDefinition,
|
|
135
|
+
JSONTableDefinition,
|
|
136
|
+
JSONSchemaDefinition,
|
|
137
|
+
|
|
138
|
+
// Query types
|
|
139
|
+
WhereCondition,
|
|
140
|
+
OrderByOption,
|
|
141
|
+
PopulateOption,
|
|
142
|
+
PopulateNested,
|
|
143
|
+
QueryOptions,
|
|
144
|
+
PaginatedResult,
|
|
145
|
+
|
|
146
|
+
// Config
|
|
147
|
+
ORMConfig,
|
|
148
|
+
} from "./types";
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Definition API
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for defining database schemas.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { defineSchema, f } from '@promakeai/orm';
|
|
9
|
+
*
|
|
10
|
+
* export default defineSchema({
|
|
11
|
+
* languages: ['en', 'tr', 'de'],
|
|
12
|
+
* tables: {
|
|
13
|
+
* products: {
|
|
14
|
+
* id: f.id(),
|
|
15
|
+
* price: f.decimal().required(),
|
|
16
|
+
* name: f.string().translatable().required(),
|
|
17
|
+
* category_id: f.int().ref('categories'),
|
|
18
|
+
* },
|
|
19
|
+
* categories: {
|
|
20
|
+
* id: f.id(),
|
|
21
|
+
* slug: f.string().required().unique(),
|
|
22
|
+
* name: f.string().translatable().required(),
|
|
23
|
+
* },
|
|
24
|
+
* },
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type {
|
|
30
|
+
SchemaDefinition,
|
|
31
|
+
SchemaInput,
|
|
32
|
+
TableDefinition,
|
|
33
|
+
LanguageConfig,
|
|
34
|
+
FieldBuilderLike,
|
|
35
|
+
} from "../types";
|
|
36
|
+
import { assertValidSchema } from "./validator";
|
|
37
|
+
|
|
38
|
+
// Re-export f for convenience
|
|
39
|
+
export { f } from "./fieldBuilder";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Define a database schema with type-safe DSL
|
|
43
|
+
*
|
|
44
|
+
* @param input - Schema definition with languages and tables
|
|
45
|
+
* @returns Validated SchemaDefinition
|
|
46
|
+
* @throws Error if schema is invalid
|
|
47
|
+
*/
|
|
48
|
+
export function defineSchema(input: SchemaInput): SchemaDefinition {
|
|
49
|
+
// 1. Normalize languages
|
|
50
|
+
const languages = normalizeLanguages(input.languages);
|
|
51
|
+
|
|
52
|
+
// 2. Build each table's fields from DSL
|
|
53
|
+
const tables: Record<string, TableDefinition> = {};
|
|
54
|
+
|
|
55
|
+
for (const [tableName, fields] of Object.entries(input.tables)) {
|
|
56
|
+
tables[tableName] = {
|
|
57
|
+
name: tableName,
|
|
58
|
+
fields: Object.fromEntries(
|
|
59
|
+
Object.entries(fields).map(([fieldName, builder]) => [
|
|
60
|
+
fieldName,
|
|
61
|
+
(builder as FieldBuilderLike).build(),
|
|
62
|
+
])
|
|
63
|
+
),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3. Create schema definition
|
|
68
|
+
const schema: SchemaDefinition = {
|
|
69
|
+
name: input.name,
|
|
70
|
+
languages,
|
|
71
|
+
tables,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// 4. Validate
|
|
75
|
+
assertValidSchema(schema);
|
|
76
|
+
|
|
77
|
+
return schema;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Normalize language input to LanguageConfig
|
|
82
|
+
*/
|
|
83
|
+
function normalizeLanguages(input: string[] | LanguageConfig): LanguageConfig {
|
|
84
|
+
if (Array.isArray(input)) {
|
|
85
|
+
if (input.length === 0) {
|
|
86
|
+
throw new Error("At least one language must be defined");
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
default: input[0],
|
|
90
|
+
supported: input,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return input;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create schema without validation (for testing/internal use)
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
export function createSchemaUnsafe(input: SchemaInput): SchemaDefinition {
|
|
101
|
+
const languages = normalizeLanguages(input.languages);
|
|
102
|
+
|
|
103
|
+
const tables: Record<string, TableDefinition> = {};
|
|
104
|
+
for (const [tableName, fields] of Object.entries(input.tables)) {
|
|
105
|
+
tables[tableName] = {
|
|
106
|
+
name: tableName,
|
|
107
|
+
fields: Object.fromEntries(
|
|
108
|
+
Object.entries(fields).map(([fieldName, builder]) => [
|
|
109
|
+
fieldName,
|
|
110
|
+
(builder as FieldBuilderLike).build(),
|
|
111
|
+
])
|
|
112
|
+
),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
name: input.name,
|
|
118
|
+
languages,
|
|
119
|
+
tables,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Merge multiple schemas into one
|
|
125
|
+
*
|
|
126
|
+
* @param schemas - Array of schema definitions to merge
|
|
127
|
+
* @returns Merged schema definition
|
|
128
|
+
* @throws Error if table names conflict
|
|
129
|
+
*/
|
|
130
|
+
export function mergeSchemas(schemas: SchemaDefinition[]): SchemaDefinition {
|
|
131
|
+
if (schemas.length === 0) {
|
|
132
|
+
throw new Error("At least one schema is required");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Use first schema's default language
|
|
136
|
+
const defaultLang = schemas[0].languages.default;
|
|
137
|
+
|
|
138
|
+
// Collect all languages (unique)
|
|
139
|
+
const allLangs = new Set<string>();
|
|
140
|
+
for (const schema of schemas) {
|
|
141
|
+
for (const lang of schema.languages.supported) {
|
|
142
|
+
allLangs.add(lang);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Merge tables
|
|
147
|
+
const tables: Record<string, TableDefinition> = {};
|
|
148
|
+
for (const schema of schemas) {
|
|
149
|
+
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
150
|
+
if (tables[tableName]) {
|
|
151
|
+
throw new Error(`Duplicate table name across schemas: ${tableName}`);
|
|
152
|
+
}
|
|
153
|
+
tables[tableName] = table;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
languages: {
|
|
159
|
+
default: defaultLang,
|
|
160
|
+
supported: Array.from(allLangs),
|
|
161
|
+
},
|
|
162
|
+
tables,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field Builder DSL
|
|
3
|
+
*
|
|
4
|
+
* Type-safe fluent API for field definitions.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { f } from '@promakeai/orm';
|
|
9
|
+
*
|
|
10
|
+
* const schema = defineSchema({
|
|
11
|
+
* tables: {
|
|
12
|
+
* products: {
|
|
13
|
+
* id: f.id(),
|
|
14
|
+
* price: f.decimal().required().min(0),
|
|
15
|
+
* name: f.string().translatable().required().trim(),
|
|
16
|
+
* email: f.string().lowercase().trim().unique(),
|
|
17
|
+
* categoryId: f.int().ref('categories'),
|
|
18
|
+
* brandId: f.int().ref({ table: 'brands', onDelete: 'SET_NULL' }),
|
|
19
|
+
* },
|
|
20
|
+
* },
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { FieldDefinition, FieldType, FieldReference } from "../types";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Fluent builder for field definitions
|
|
29
|
+
*/
|
|
30
|
+
export class FieldBuilder {
|
|
31
|
+
protected definition: FieldDefinition;
|
|
32
|
+
|
|
33
|
+
constructor(type: FieldType) {
|
|
34
|
+
this.definition = {
|
|
35
|
+
type,
|
|
36
|
+
nullable: true, // Default nullable
|
|
37
|
+
unique: false,
|
|
38
|
+
primary: false,
|
|
39
|
+
translatable: false,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Mark field as NOT NULL
|
|
45
|
+
*/
|
|
46
|
+
required(): this {
|
|
47
|
+
this.definition.nullable = false;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Mark field as nullable (default)
|
|
53
|
+
*/
|
|
54
|
+
nullable(): this {
|
|
55
|
+
this.definition.nullable = true;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Mark field as UNIQUE
|
|
61
|
+
*/
|
|
62
|
+
unique(): this {
|
|
63
|
+
this.definition.unique = true;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set default value
|
|
69
|
+
*/
|
|
70
|
+
default(value: unknown): this {
|
|
71
|
+
this.definition.default = value;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Mark field as translatable (stored in translation table)
|
|
77
|
+
* Only valid for string/text fields
|
|
78
|
+
*/
|
|
79
|
+
translatable(): this {
|
|
80
|
+
this.definition.translatable = true;
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Add reference to another table (MongoDB-style)
|
|
86
|
+
* @param tableOrOptions - Table name or options object
|
|
87
|
+
*/
|
|
88
|
+
ref(
|
|
89
|
+
tableOrOptions:
|
|
90
|
+
| string
|
|
91
|
+
| {
|
|
92
|
+
table: string;
|
|
93
|
+
field?: string;
|
|
94
|
+
onDelete?: "CASCADE" | "SET_NULL" | "RESTRICT" | "NO_ACTION";
|
|
95
|
+
onUpdate?: "CASCADE" | "SET_NULL" | "RESTRICT" | "NO_ACTION";
|
|
96
|
+
}
|
|
97
|
+
): this {
|
|
98
|
+
this.definition.ref = tableOrOptions;
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Trim whitespace from string values
|
|
104
|
+
*/
|
|
105
|
+
trim(): this {
|
|
106
|
+
this.definition.trim = true;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Convert string to lowercase
|
|
112
|
+
*/
|
|
113
|
+
lowercase(): this {
|
|
114
|
+
this.definition.lowercase = true;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Convert string to uppercase
|
|
120
|
+
*/
|
|
121
|
+
uppercase(): this {
|
|
122
|
+
this.definition.uppercase = true;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Minimum string length
|
|
128
|
+
*/
|
|
129
|
+
minLength(value: number): this {
|
|
130
|
+
this.definition.minLength = value;
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Maximum string length
|
|
136
|
+
*/
|
|
137
|
+
maxLength(value: number): this {
|
|
138
|
+
this.definition.maxLength = value;
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Minimum numeric value
|
|
144
|
+
*/
|
|
145
|
+
min(value: number): this {
|
|
146
|
+
this.definition.min = value;
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Maximum numeric value
|
|
152
|
+
*/
|
|
153
|
+
max(value: number): this {
|
|
154
|
+
this.definition.max = value;
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Enum values (allowed values)
|
|
160
|
+
*/
|
|
161
|
+
enum(values: string[]): this {
|
|
162
|
+
this.definition.enum = values;
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* RegExp pattern to match
|
|
168
|
+
*/
|
|
169
|
+
match(pattern: string): this {
|
|
170
|
+
this.definition.match = pattern;
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Build the final field definition
|
|
176
|
+
* @internal
|
|
177
|
+
*/
|
|
178
|
+
build(): FieldDefinition {
|
|
179
|
+
return { ...this.definition };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Factory object for creating field builders
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* f.id() // Primary key
|
|
189
|
+
* f.string().required().trim() // NOT NULL trimmed string
|
|
190
|
+
* f.int().default(0).min(0) // Non-negative integer
|
|
191
|
+
* f.string().unique().lowercase() // Unique lowercase string
|
|
192
|
+
* f.string().translatable().required() // Translatable NOT NULL
|
|
193
|
+
* f.int().ref('users') // Foreign key to users
|
|
194
|
+
* f.int().ref({ table: 'users', onDelete: 'CASCADE' }) // With constraint
|
|
195
|
+
* f.json().ref('categories') // Array of category IDs
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
export const f = {
|
|
199
|
+
/**
|
|
200
|
+
* Primary key field (INTEGER PRIMARY KEY AUTOINCREMENT)
|
|
201
|
+
*/
|
|
202
|
+
id: (): FieldBuilder => {
|
|
203
|
+
const builder = new FieldBuilder("id");
|
|
204
|
+
builder["definition"].primary = true;
|
|
205
|
+
builder["definition"].nullable = false;
|
|
206
|
+
return builder;
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* String field (VARCHAR/TEXT)
|
|
211
|
+
*/
|
|
212
|
+
string: (): FieldBuilder => new FieldBuilder("string"),
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Text field (TEXT/LONGTEXT)
|
|
216
|
+
*/
|
|
217
|
+
text: (): FieldBuilder => new FieldBuilder("text"),
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Integer field (INTEGER)
|
|
221
|
+
*/
|
|
222
|
+
int: (): FieldBuilder => new FieldBuilder("int"),
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Decimal field (REAL/DECIMAL)
|
|
226
|
+
*/
|
|
227
|
+
decimal: (): FieldBuilder => new FieldBuilder("decimal"),
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Boolean field (INTEGER 0/1 in SQLite)
|
|
231
|
+
*/
|
|
232
|
+
bool: (): FieldBuilder => new FieldBuilder("bool"),
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Timestamp field (TEXT in ISO format)
|
|
236
|
+
*/
|
|
237
|
+
timestamp: (): FieldBuilder => new FieldBuilder("timestamp"),
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* JSON field (TEXT with JSON serialization)
|
|
241
|
+
*/
|
|
242
|
+
json: (): FieldBuilder => new FieldBuilder("json"),
|
|
243
|
+
};
|
|
244
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Helper Functions
|
|
3
|
+
*
|
|
4
|
+
* Utilities for transforming table/field names.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert plural word to singular
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* singularize('products') // 'product'
|
|
13
|
+
* singularize('categories') // 'category'
|
|
14
|
+
* singularize('users') // 'user'
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function singularize(word: string): string {
|
|
18
|
+
const irregulars: Record<string, string> = {
|
|
19
|
+
people: "person",
|
|
20
|
+
children: "child",
|
|
21
|
+
men: "man",
|
|
22
|
+
women: "woman",
|
|
23
|
+
teeth: "tooth",
|
|
24
|
+
feet: "foot",
|
|
25
|
+
mice: "mouse",
|
|
26
|
+
geese: "goose",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (irregulars[word]) {
|
|
30
|
+
return irregulars[word];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// -ies → -y (categories → category)
|
|
34
|
+
if (word.endsWith("ies")) {
|
|
35
|
+
return word.slice(0, -3) + "y";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// -ses, -xes, -zes, -ches, -shes → remove 'es'
|
|
39
|
+
if (
|
|
40
|
+
word.endsWith("ses") ||
|
|
41
|
+
word.endsWith("xes") ||
|
|
42
|
+
word.endsWith("zes") ||
|
|
43
|
+
word.endsWith("ches") ||
|
|
44
|
+
word.endsWith("shes")
|
|
45
|
+
) {
|
|
46
|
+
return word.slice(0, -2);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// -es → remove 'es' for words ending in -o
|
|
50
|
+
if (word.endsWith("oes")) {
|
|
51
|
+
return word.slice(0, -2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// -s → remove 's'
|
|
55
|
+
if (word.endsWith("s") && !word.endsWith("ss")) {
|
|
56
|
+
return word.slice(0, -1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return word;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Convert string to PascalCase
|
|
64
|
+
*/
|
|
65
|
+
export function toPascalCase(str: string): string {
|
|
66
|
+
return str
|
|
67
|
+
.split(/[_\-\s]+/)
|
|
68
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
69
|
+
.join("");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Convert string to camelCase
|
|
74
|
+
*/
|
|
75
|
+
export function toCamelCase(str: string): string {
|
|
76
|
+
const pascal = toPascalCase(str);
|
|
77
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Convert string to snake_case
|
|
82
|
+
*/
|
|
83
|
+
export function toSnakeCase(str: string): string {
|
|
84
|
+
return str
|
|
85
|
+
.replace(/([A-Z])/g, "_$1")
|
|
86
|
+
.replace(/[-\s]+/g, "_")
|
|
87
|
+
.toLowerCase()
|
|
88
|
+
.replace(/^_/, "");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get singular PascalCase name for interface
|
|
93
|
+
*/
|
|
94
|
+
export function toInterfaceName(tableName: string): string {
|
|
95
|
+
return toPascalCase(singularize(tableName));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get Db-prefixed interface name
|
|
100
|
+
* @example toDbInterfaceName('products') // 'DbProduct'
|
|
101
|
+
*/
|
|
102
|
+
export function toDbInterfaceName(tableName: string): string {
|
|
103
|
+
return "Db" + toInterfaceName(tableName);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Pluralize a word (basic implementation)
|
|
108
|
+
*/
|
|
109
|
+
export function pluralize(word: string): string {
|
|
110
|
+
const irregulars: Record<string, string> = {
|
|
111
|
+
person: "people",
|
|
112
|
+
child: "children",
|
|
113
|
+
man: "men",
|
|
114
|
+
woman: "women",
|
|
115
|
+
tooth: "teeth",
|
|
116
|
+
foot: "feet",
|
|
117
|
+
mouse: "mice",
|
|
118
|
+
goose: "geese",
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (irregulars[word]) {
|
|
122
|
+
return irregulars[word];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// -y → -ies (category → categories)
|
|
126
|
+
if (word.endsWith("y") && !/[aeiou]y$/.test(word)) {
|
|
127
|
+
return word.slice(0, -1) + "ies";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// -s, -x, -z, -ch, -sh → add 'es'
|
|
131
|
+
if (
|
|
132
|
+
word.endsWith("s") ||
|
|
133
|
+
word.endsWith("x") ||
|
|
134
|
+
word.endsWith("z") ||
|
|
135
|
+
word.endsWith("ch") ||
|
|
136
|
+
word.endsWith("sh")
|
|
137
|
+
) {
|
|
138
|
+
return word + "es";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// -o → add 'es' for common words
|
|
142
|
+
if (word.endsWith("o") && /[^aeiou]o$/.test(word)) {
|
|
143
|
+
return word + "es";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Default: add 's'
|
|
147
|
+
return word + "s";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get PascalCase plural form
|
|
152
|
+
* @example toPascalCasePlural('products') // 'Products'
|
|
153
|
+
*/
|
|
154
|
+
export function toPascalCasePlural(tableName: string): string {
|
|
155
|
+
// Table names are usually already plural, just convert to PascalCase
|
|
156
|
+
return toPascalCase(tableName);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get translation table name
|
|
161
|
+
*/
|
|
162
|
+
export function toTranslationTableName(tableName: string): string {
|
|
163
|
+
return `${tableName}_translations`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get foreign key column name for translation table
|
|
168
|
+
*/
|
|
169
|
+
export function toTranslationFKName(tableName: string): string {
|
|
170
|
+
return `${singularize(tableName)}_id`;
|
|
171
|
+
}
|