@arcaelas/dynamite 1.0.15 → 1.0.18

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
@@ -47,7 +47,45 @@
47
47
  "docs": "mkdocs gh-deploy --force",
48
48
  "deploy": "npm version patch && npm publish --access=public",
49
49
  "prepublishOnly": "yarn build",
50
- "postpublish": "find src -type f \\( -name '*.js' -o -name '*.d.ts' -o -name '*.map' \\) -delete"
50
+ "postpublish": "find src -type f \\( -name '*.js' -o -name '*.d.ts' -o -name '*.map' \\) -delete",
51
+ "test": "yarn test:db:start && yarn test:run; yarn test:db:stop",
52
+ "test:run": "node --expose-gc ./node_modules/.bin/jest src/index.test.ts --runInBand --verbose",
53
+ "test:watch": "yarn test:db:start && node --expose-gc ./node_modules/.bin/jest src/index.test.ts --watch --runInBand",
54
+ "test:db:start": "docker run -d -p 8000:8000 --name dynamite-test-db amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb || echo 'DynamoDB ya está corriendo'",
55
+ "test:db:stop": "docker stop dynamite-test-db && docker rm dynamite-test-db || true",
56
+ "test:db:clean": "docker stop dynamite-test-db && docker rm dynamite-test-db; docker run -d -p 8000:8000 --name dynamite-test-db amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb"
51
57
  },
52
- "version": "1.0.15"
58
+ "version": "1.0.18",
59
+ "jest": {
60
+ "preset": "ts-jest",
61
+ "testEnvironment": "node",
62
+ "roots": [
63
+ "<rootDir>/src"
64
+ ],
65
+ "testMatch": [
66
+ "**/*.test.ts"
67
+ ],
68
+ "moduleNameMapper": {
69
+ "^~/(.*)$": "<rootDir>/src/$1",
70
+ "^@type/(.*)$": "<rootDir>/src/@types/$1"
71
+ },
72
+ "transform": {
73
+ "^.+\\.ts$": [
74
+ "ts-jest",
75
+ {
76
+ "tsconfig": {
77
+ "experimentalDecorators": true,
78
+ "emitDecoratorMetadata": true,
79
+ "esModuleInterop": true,
80
+ "target": "ES2022",
81
+ "module": "commonjs"
82
+ }
83
+ }
84
+ ]
85
+ },
86
+ "testTimeout": 60000,
87
+ "maxWorkers": 1,
88
+ "detectOpenHandles": false,
89
+ "forceExit": true
90
+ }
53
91
  }
@@ -0,0 +1,102 @@
1
+ export declare const HasManyBrand: unique symbol;
2
+ export declare const BelongsToBrand: unique symbol;
3
+ export declare const HasOneBrand: unique symbol;
4
+ export declare const NonAttributeBrand: unique symbol;
5
+ export declare const CreationOptionalBrand: unique symbol;
6
+ export type HasMany<T> = T[] & {
7
+ [HasManyBrand]?: true;
8
+ };
9
+ export type BelongsTo<T> = (T | null) & {
10
+ [BelongsToBrand]?: true;
11
+ };
12
+ export type HasOne<T> = (T | null) & {
13
+ [HasOneBrand]?: true;
14
+ };
15
+ export type NonAttribute<T> = T & {
16
+ [NonAttributeBrand]?: true;
17
+ };
18
+ export type CreationOptional<T> = T & {
19
+ [CreationOptionalBrand]?: true;
20
+ };
21
+ export type InferAttributes<T> = {
22
+ [K in keyof T as T[K] extends (...args: any[]) => any ? never : T[K] extends {
23
+ [HasManyBrand]?: true;
24
+ } ? never : T[K] extends {
25
+ [BelongsToBrand]?: true;
26
+ } ? never : T[K] extends {
27
+ [HasOneBrand]?: true;
28
+ } ? never : T[K] extends {
29
+ [NonAttributeBrand]?: true;
30
+ } ? never : K]: T[K];
31
+ };
32
+ export type FilterableAttributes<T> = {
33
+ [K in keyof InferAttributes<T>]: InferAttributes<T>[K];
34
+ };
35
+ export type WhereOptions<T> = {
36
+ where?: Partial<FilterableAttributes<T>>;
37
+ skip?: number;
38
+ limit?: number;
39
+ order?: "ASC" | "DESC";
40
+ attributes?: (keyof FilterableAttributes<T>)[];
41
+ include?: {
42
+ [K in keyof T]?: T[K] extends HasMany<any> | BelongsTo<any> | HasOne<any> ? IncludeRelationOptions | true : never;
43
+ };
44
+ };
45
+ /** Filtros de query sin cláusula where */
46
+ export type QueryFilters<T> = Omit<WhereOptions<T>, "where">;
47
+ /** @deprecated Usa QueryFilters */
48
+ export type WhereOptionsWithoutWhere<T> = QueryFilters<T>;
49
+ export type QueryOperator = "=" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not-in" | "contains" | "begins-with";
50
+ /** Entrada de validador con soporte lazy */
51
+ export interface ValidatorEntry {
52
+ fn: (value: any) => boolean | string;
53
+ lazy?: boolean;
54
+ }
55
+ export interface Column {
56
+ name: string;
57
+ index?: true;
58
+ indexSort?: true;
59
+ primaryKey?: boolean;
60
+ nullable?: boolean;
61
+ unique?: true;
62
+ createdAt?: boolean;
63
+ updatedAt?: boolean;
64
+ softDelete?: boolean;
65
+ lazy_validators?: ((value: any) => boolean | string)[];
66
+ relation?: RelationMetadata;
67
+ }
68
+ export interface RelationMetadata {
69
+ type: "hasMany" | "belongsTo" | "hasOne";
70
+ targetModel: () => any;
71
+ foreignKey: string;
72
+ localKey?: string;
73
+ }
74
+ export interface WrapperEntry {
75
+ name: string;
76
+ columns: Map<string | symbol, Column>;
77
+ relations: Map<string | symbol, RelationMetadata>;
78
+ }
79
+ /** Alias para WrapperEntry (usado internamente con SCHEMA symbol) */
80
+ export type SchemaEntry = WrapperEntry;
81
+ export type IncludeRelationOptions = {
82
+ where?: Record<string, any>;
83
+ attributes?: string[];
84
+ order?: "ASC" | "DESC";
85
+ skip?: number;
86
+ limit?: number;
87
+ include?: Record<string, IncludeRelationOptions | true>;
88
+ };
89
+ /** Opciones de query para métodos where(), first(), last(), etc. */
90
+ export type QueryOptions<T> = {
91
+ order?: "ASC" | "DESC";
92
+ skip?: number;
93
+ limit?: number;
94
+ attributes?: (keyof InferAttributes<T>)[];
95
+ include?: {
96
+ [K in keyof T]?: T[K] extends HasMany<any> | BelongsTo<any> | HasOne<any> ? IncludeRelationOptions | true : never;
97
+ };
98
+ };
99
+ /** @deprecated Usa QueryOptions */
100
+ export type WhereQueryOptions<T> = QueryOptions<T>;
101
+ export type Mutate = (value: any) => any;
102
+ export type Validate = (value: any) => boolean | string;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /*
3
+ @file index.ts
4
+ @descripcion Tipos públicos de Dynamite ORM
5
+ @autor Miguel Alejandro
6
+ @fecha 2025-08-07
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @file client.ts
3
+ * @description Centralized Dynamite client with multi-client support and table sync
4
+ * @autor Miguel Alejandro
5
+ * @fecha 2025-01-27
6
+ */
7
+ import { DynamoDBClient, DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
8
+ /**
9
+ * Configuration for Dynamite client initialization
10
+ */
11
+ export interface DynamiteConfig extends DynamoDBClientConfig {
12
+ tables: Array<new (...args: any[]) => any>;
13
+ }
14
+ /**
15
+ * Centralized Dynamite client for managing DynamoDB connections and table synchronization
16
+ */
17
+ export declare class Dynamite {
18
+ private client;
19
+ private tables;
20
+ private connected;
21
+ private synced;
22
+ /**
23
+ * Initialize Dynamite client with configuration
24
+ * @param config Dynamite client configuration
25
+ */
26
+ constructor(config: DynamiteConfig);
27
+ /**
28
+ * Synchronize all declared tables and their GSIs
29
+ */
30
+ sync(): Promise<void>;
31
+ /**
32
+ * Connect the client for Table operations
33
+ */
34
+ connect(): void;
35
+ /**
36
+ * Get the underlying DynamoDB client
37
+ */
38
+ getClient(): DynamoDBClient;
39
+ /**
40
+ * Check if client is connected and tables are synced
41
+ */
42
+ isReady(): boolean;
43
+ /**
44
+ * Disconnect and cleanup DynamoDB client
45
+ */
46
+ disconnect(): void;
47
+ /**
48
+ * Ejecutar operaciones en una transacción atómica.
49
+ * Si cualquier operación falla, todas se revierten automáticamente.
50
+ *
51
+ * @param callback Función que recibe el contexto de transacción
52
+ * @returns Resultado del callback
53
+ *
54
+ * @example
55
+ * await dynamite.tx(async (tx) => {
56
+ * const user = await User.create({ name: "Juan" }, tx);
57
+ * await Order.create({ user_id: user.id, total: 100 }, tx);
58
+ * });
59
+ */
60
+ tx<R>(callback: (tx: TransactionContext) => Promise<R>): Promise<R>;
61
+ /**
62
+ * Create table with automatic GSI detection and creation
63
+ * @param ctor Table class constructor
64
+ */
65
+ private createTableWithGSI;
66
+ }
67
+ /**
68
+ * Set global client for Table class operations
69
+ * @param client DynamoDB client instance
70
+ */
71
+ export declare const setGlobalClient: (client: DynamoDBClient) => void;
72
+ /**
73
+ * Get global client for Table class operations
74
+ */
75
+ export declare const getGlobalClient: () => DynamoDBClient;
76
+ /**
77
+ * Check if global client is available
78
+ */
79
+ export declare const hasGlobalClient: () => boolean;
80
+ /**
81
+ * Require global client for Table operations (throws if not available)
82
+ */
83
+ export declare const requireClient: () => DynamoDBClient;
84
+ /**
85
+ * Contexto de transacción para agrupar operaciones atómicas.
86
+ * Máximo 25 operaciones por transacción (límite DynamoDB).
87
+ */
88
+ export declare class TransactionContext {
89
+ private operations;
90
+ private client;
91
+ constructor(client: DynamoDBClient);
92
+ /**
93
+ * Agregar operación Put a la transacción
94
+ */
95
+ addPut(table_name: string, item: Record<string, any>): void;
96
+ /**
97
+ * Agregar operación Delete a la transacción
98
+ */
99
+ addDelete(table_name: string, key: Record<string, any>): void;
100
+ /**
101
+ * Confirmar todas las operaciones de la transacción.
102
+ * Si alguna falla, todas se revierten.
103
+ */
104
+ commit(): Promise<void>;
105
+ }
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ /**
3
+ * @file client.ts
4
+ * @description Centralized Dynamite client with multi-client support and table sync
5
+ * @autor Miguel Alejandro
6
+ * @fecha 2025-01-27
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.TransactionContext = exports.requireClient = exports.hasGlobalClient = exports.getGlobalClient = exports.setGlobalClient = exports.Dynamite = void 0;
10
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
11
+ const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
12
+ const decorator_1 = require("./decorator");
13
+ /**
14
+ * Centralized Dynamite client for managing DynamoDB connections and table synchronization
15
+ */
16
+ class Dynamite {
17
+ /**
18
+ * Initialize Dynamite client with configuration
19
+ * @param config Dynamite client configuration
20
+ */
21
+ constructor(config) {
22
+ this.connected = false;
23
+ this.synced = false;
24
+ const { tables, ...clientConfig } = config;
25
+ this.client = new client_dynamodb_1.DynamoDBClient(clientConfig);
26
+ this.tables = tables;
27
+ }
28
+ /**
29
+ * Synchronize all declared tables and their GSIs
30
+ */
31
+ async sync() {
32
+ if (this.synced && this.connected)
33
+ return;
34
+ await Promise.all(this.tables.map((table) => this.createTableWithGSI(table)));
35
+ this.synced = true;
36
+ }
37
+ /**
38
+ * Connect the client for Table operations
39
+ */
40
+ connect() {
41
+ if (this.connected)
42
+ return;
43
+ (0, exports.setGlobalClient)(this.client);
44
+ this.connected = true;
45
+ }
46
+ /**
47
+ * Get the underlying DynamoDB client
48
+ */
49
+ getClient() {
50
+ return this.client;
51
+ }
52
+ /**
53
+ * Check if client is connected and tables are synced
54
+ */
55
+ isReady() {
56
+ return this.connected && this.synced;
57
+ }
58
+ /**
59
+ * Disconnect and cleanup DynamoDB client
60
+ */
61
+ disconnect() {
62
+ try {
63
+ this.client.destroy();
64
+ this.connected = false;
65
+ this.synced = false;
66
+ if (globalClient === this.client)
67
+ globalClient = undefined;
68
+ }
69
+ catch (error) {
70
+ console.warn("Error during Dynamite disconnect:", error);
71
+ }
72
+ }
73
+ /**
74
+ * Ejecutar operaciones en una transacción atómica.
75
+ * Si cualquier operación falla, todas se revierten automáticamente.
76
+ *
77
+ * @param callback Función que recibe el contexto de transacción
78
+ * @returns Resultado del callback
79
+ *
80
+ * @example
81
+ * await dynamite.tx(async (tx) => {
82
+ * const user = await User.create({ name: "Juan" }, tx);
83
+ * await Order.create({ user_id: user.id, total: 100 }, tx);
84
+ * });
85
+ */
86
+ async tx(callback) {
87
+ const ctx = new TransactionContext(this.client);
88
+ const result = await callback(ctx);
89
+ await ctx.commit();
90
+ return result;
91
+ }
92
+ /**
93
+ * Create table with automatic GSI detection and creation
94
+ * @param ctor Table class constructor
95
+ */
96
+ async createTableWithGSI(ctor) {
97
+ const meta = ctor[decorator_1.SCHEMA];
98
+ if (!meta)
99
+ throw new Error(`Class ${ctor.name} not registered. Use decorators.`);
100
+ const cols = Object.values(meta.columns);
101
+ const pk = cols.find((c) => c.store?.index);
102
+ if (!pk)
103
+ throw new Error(`PartitionKey missing in ${ctor.name}`);
104
+ const sk = cols.find((c) => c.store?.indexSort);
105
+ const attr = new Map();
106
+ attr.set(pk.name || 'id', "S");
107
+ if (sk)
108
+ attr.set(sk.name || 'id', "S");
109
+ // Temporalmente deshabilitamos la creación automática de GSI hasta implementar relaciones
110
+ const gsiDefinitions = [];
111
+ const schema = [{ AttributeName: pk.name || 'id', KeyType: "HASH" }];
112
+ if (sk && sk.name !== pk.name)
113
+ schema.push({ AttributeName: sk.name || 'id', KeyType: "RANGE" });
114
+ try {
115
+ await this.client.send(new client_dynamodb_1.CreateTableCommand({
116
+ TableName: meta.name,
117
+ BillingMode: "PAY_PER_REQUEST",
118
+ AttributeDefinitions: [...attr].map(([AttributeName, AttributeType]) => ({
119
+ AttributeName,
120
+ AttributeType,
121
+ })),
122
+ KeySchema: schema,
123
+ ...(gsiDefinitions.length > 0 && {
124
+ GlobalSecondaryIndexes: gsiDefinitions,
125
+ }),
126
+ }));
127
+ }
128
+ catch (error) {
129
+ if (error.name !== "ResourceInUseException")
130
+ throw error;
131
+ }
132
+ }
133
+ }
134
+ exports.Dynamite = Dynamite;
135
+ let globalClient;
136
+ /**
137
+ * Set global client for Table class operations
138
+ * @param client DynamoDB client instance
139
+ */
140
+ const setGlobalClient = (client) => {
141
+ globalClient = client;
142
+ };
143
+ exports.setGlobalClient = setGlobalClient;
144
+ /**
145
+ * Get global client for Table class operations
146
+ */
147
+ const getGlobalClient = () => {
148
+ if (!globalClient)
149
+ throw new Error("No global DynamoDB client set. Call setGlobalClient() first.");
150
+ return globalClient;
151
+ };
152
+ exports.getGlobalClient = getGlobalClient;
153
+ /**
154
+ * Check if global client is available
155
+ */
156
+ const hasGlobalClient = () => {
157
+ return globalClient !== undefined;
158
+ };
159
+ exports.hasGlobalClient = hasGlobalClient;
160
+ /**
161
+ * Require global client for Table operations (throws if not available)
162
+ */
163
+ const requireClient = () => {
164
+ if (!globalClient) {
165
+ throw new Error("DynamoDB client no configurado. Use Dynamite.connect() primero.");
166
+ }
167
+ return globalClient;
168
+ };
169
+ exports.requireClient = requireClient;
170
+ /**
171
+ * Contexto de transacción para agrupar operaciones atómicas.
172
+ * Máximo 25 operaciones por transacción (límite DynamoDB).
173
+ */
174
+ class TransactionContext {
175
+ constructor(client) {
176
+ this.operations = [];
177
+ this.client = client;
178
+ }
179
+ /**
180
+ * Agregar operación Put a la transacción
181
+ */
182
+ addPut(table_name, item) {
183
+ this.operations.push({
184
+ Put: {
185
+ TableName: table_name,
186
+ Item: (0, util_dynamodb_1.marshall)(item, { removeUndefinedValues: true }),
187
+ },
188
+ });
189
+ }
190
+ /**
191
+ * Agregar operación Delete a la transacción
192
+ */
193
+ addDelete(table_name, key) {
194
+ this.operations.push({
195
+ Delete: {
196
+ TableName: table_name,
197
+ Key: (0, util_dynamodb_1.marshall)(key),
198
+ },
199
+ });
200
+ }
201
+ /**
202
+ * Confirmar todas las operaciones de la transacción.
203
+ * Si alguna falla, todas se revierten.
204
+ */
205
+ async commit() {
206
+ if (this.operations.length === 0)
207
+ return;
208
+ if (this.operations.length > 25) {
209
+ throw new Error(`Transacción excede el límite de 25 operaciones (tiene ${this.operations.length})`);
210
+ }
211
+ await this.client.send(new client_dynamodb_1.TransactWriteItemsCommand({
212
+ TransactItems: this.operations,
213
+ }));
214
+ }
215
+ }
216
+ exports.TransactionContext = TransactionContext;
217
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @file decorator.ts
3
+ * @description Sistema de decoradores minimalista con Symbol storage
4
+ * @autor Miguel Alejandro
5
+ * @fecha 2025-01-28
6
+ */
7
+ export declare const SCHEMA: unique symbol;
8
+ export declare const VALUES: unique symbol;
9
+ declare function toSnakePlural(str: string): string;
10
+ /**
11
+ * @description Factory para crear decoradores con soporte de argumentos y composición
12
+ * @param callback Función que recibe (schema, col, params) para configurar la columna
13
+ * @returns Función que acepta argumentos y retorna PropertyDecorator
14
+ * @example
15
+ * ```typescript
16
+ * const Default = decorator((schema, col, params) => {
17
+ * const fallback = params[0];
18
+ * col.get.push((value) => value ?? fallback());
19
+ * });
20
+ * // Uso: @Default(uuid)
21
+ * ```
22
+ */
23
+ export declare function decorator(callback: (schema: any, col: any, params: any[]) => void): (...params: any[]) => (target: any, propertyKey: string | symbol) => void;
24
+ /**
25
+ * @description Factory para decoradores de relación
26
+ * @param relation_type Tipo de relación ("hasMany", "hasOne", "belongsTo")
27
+ * @param RelatedTable Clase de la tabla relacionada
28
+ * @param options Opciones de relación (foreignKey, localKey)
29
+ * @returns PropertyDecorator
30
+ */
31
+ export declare function relationDecorator(relation_type: string, RelatedTable: any, options?: any): (target: any, propertyKey: string | symbol) => void;
32
+ /**
33
+ * @description Función helper para obtener metadatos del esquema
34
+ * @param ctor Constructor de la clase
35
+ * @returns SchemaEntry
36
+ */
37
+ export declare function getSchema(ctor: Function): any;
38
+ /**
39
+ * @description Función helper para verificar que existe esquema
40
+ * @param ctor Constructor de la clase
41
+ * @returns SchemaEntry
42
+ */
43
+ export declare function ensureSchema(ctor: Function): any;
44
+ export { toSnakePlural };
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ /**
3
+ * @file decorator.ts
4
+ * @description Sistema de decoradores minimalista con Symbol storage
5
+ * @autor Miguel Alejandro
6
+ * @fecha 2025-01-28
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.VALUES = exports.SCHEMA = void 0;
10
+ exports.decorator = decorator;
11
+ exports.relationDecorator = relationDecorator;
12
+ exports.getSchema = getSchema;
13
+ exports.ensureSchema = ensureSchema;
14
+ exports.toSnakePlural = toSnakePlural;
15
+ // Symbols para autocontención (exportados desde table.ts pero también aquí compatibilidad)
16
+ exports.SCHEMA = Symbol('dynamite:schema');
17
+ exports.VALUES = Symbol('dynamite:values');
18
+ // Helper simple para snake_case plural
19
+ function toSnakePlural(str) {
20
+ const snake = str
21
+ .replace(/([A-Z])/g, "_$1")
22
+ .toLowerCase()
23
+ .replace(/^_/, "");
24
+ return snake.endsWith("s") ? snake : snake + "s";
25
+ }
26
+ /**
27
+ * @description Factory para crear decoradores con soporte de argumentos y composición
28
+ * @param callback Función que recibe (schema, col, params) para configurar la columna
29
+ * @returns Función que acepta argumentos y retorna PropertyDecorator
30
+ * @example
31
+ * ```typescript
32
+ * const Default = decorator((schema, col, params) => {
33
+ * const fallback = params[0];
34
+ * col.get.push((value) => value ?? fallback());
35
+ * });
36
+ * // Uso: @Default(uuid)
37
+ * ```
38
+ */
39
+ function decorator(callback) {
40
+ return (...params) => {
41
+ return function (target, propertyKey) {
42
+ const table_class = target.constructor;
43
+ const column_name = String(propertyKey);
44
+ // Inicializar SCHEMA si no existe
45
+ if (!table_class[exports.SCHEMA]) {
46
+ table_class[exports.SCHEMA] = {
47
+ name: toSnakePlural(table_class.name),
48
+ primary_key: 'id',
49
+ columns: {}
50
+ };
51
+ }
52
+ // Crear columna si no existe
53
+ if (!table_class[exports.SCHEMA].columns[column_name]) {
54
+ table_class[exports.SCHEMA].columns[column_name] = {
55
+ name: column_name,
56
+ get: [],
57
+ set: [],
58
+ store: {}
59
+ };
60
+ }
61
+ const col = table_class[exports.SCHEMA].columns[column_name];
62
+ // Ejecutar callback con (schema, col, params)
63
+ callback(table_class, col, params);
64
+ };
65
+ };
66
+ }
67
+ /**
68
+ * @description Factory para decoradores de relación
69
+ * @param relation_type Tipo de relación ("hasMany", "hasOne", "belongsTo")
70
+ * @param RelatedTable Clase de la tabla relacionada
71
+ * @param options Opciones de relación (foreignKey, localKey)
72
+ * @returns PropertyDecorator
73
+ */
74
+ function relationDecorator(relation_type, RelatedTable, options = {}) {
75
+ return function (target, propertyKey) {
76
+ const table_class = target.constructor;
77
+ const column_name = String(propertyKey);
78
+ if (!table_class[exports.SCHEMA]) {
79
+ table_class[exports.SCHEMA] = {
80
+ name: toSnakePlural(table_class.name),
81
+ primary_key: 'id',
82
+ columns: {}
83
+ };
84
+ }
85
+ // **Validación: No sobreescribir columnas existentes**
86
+ if (table_class[exports.SCHEMA].columns[column_name]) {
87
+ throw new Error(`Column '${column_name}' already exists. Cannot apply relation decorator.`);
88
+ }
89
+ // Crear columna virtual para relación
90
+ table_class[exports.SCHEMA].columns[column_name] = {
91
+ name: column_name,
92
+ get: [],
93
+ set: [],
94
+ store: {
95
+ relation: {
96
+ type: relation_type,
97
+ target: RelatedTable,
98
+ foreignKey: options.foreignKey,
99
+ localKey: options.localKey,
100
+ nullable: options.nullable
101
+ }
102
+ }
103
+ };
104
+ };
105
+ }
106
+ /**
107
+ * @description Función helper para obtener metadatos del esquema
108
+ * @param ctor Constructor de la clase
109
+ * @returns SchemaEntry
110
+ */
111
+ function getSchema(ctor) {
112
+ const schema = ctor[exports.SCHEMA];
113
+ if (!schema) {
114
+ throw new Error(`Schema not found for ${ctor.name}. Use decorators first.`);
115
+ }
116
+ return schema;
117
+ }
118
+ /**
119
+ * @description Función helper para verificar que existe esquema
120
+ * @param ctor Constructor de la clase
121
+ * @returns SchemaEntry
122
+ */
123
+ function ensureSchema(ctor) {
124
+ if (!ctor[exports.SCHEMA]) {
125
+ ctor[exports.SCHEMA] = {
126
+ name: toSnakePlural(ctor.name),
127
+ primary_key: 'id',
128
+ columns: {}
129
+ };
130
+ }
131
+ return ctor[exports.SCHEMA];
132
+ }
133
+ //# sourceMappingURL=decorator.js.map