@arcaelas/dynamite 1.0.17 → 1.0.19

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 (67) hide show
  1. package/package.json +40 -2
  2. package/src/@types/index.d.ts +116 -75
  3. package/src/core/client.d.ts +36 -0
  4. package/src/core/client.js +80 -27
  5. package/src/core/decorator.d.ts +44 -0
  6. package/src/core/decorator.js +133 -0
  7. package/src/core/method.d.ts +73 -0
  8. package/src/core/method.js +140 -0
  9. package/src/core/table.d.ts +44 -86
  10. package/src/core/table.js +510 -310
  11. package/src/decorators/indexes.d.ts +38 -0
  12. package/src/decorators/indexes.js +67 -0
  13. package/src/decorators/relations.d.ts +55 -0
  14. package/src/decorators/relations.js +84 -0
  15. package/src/decorators/timestamps.d.ts +54 -0
  16. package/src/decorators/timestamps.js +67 -0
  17. package/src/decorators/transforms.d.ts +86 -0
  18. package/src/decorators/transforms.js +154 -0
  19. package/src/index.d.ts +10 -16
  20. package/src/index.js +50 -32
  21. package/src/index.test.d.ts +13 -0
  22. package/src/index.test.js +1992 -0
  23. package/src/utils/relations.d.ts +34 -12
  24. package/src/utils/relations.js +109 -133
  25. package/src/@types/index.js +0 -9
  26. package/src/core/wrapper.d.ts +0 -17
  27. package/src/core/wrapper.js +0 -46
  28. package/src/decorators/belongs_to.d.ts +0 -1
  29. package/src/decorators/belongs_to.js +0 -24
  30. package/src/decorators/created_at.d.ts +0 -1
  31. package/src/decorators/created_at.js +0 -11
  32. package/src/decorators/default.d.ts +0 -1
  33. package/src/decorators/default.js +0 -47
  34. package/src/decorators/has_many.d.ts +0 -1
  35. package/src/decorators/has_many.js +0 -24
  36. package/src/decorators/index.d.ts +0 -11
  37. package/src/decorators/index.js +0 -36
  38. package/src/decorators/index_sort.d.ts +0 -12
  39. package/src/decorators/index_sort.js +0 -43
  40. package/src/decorators/mutate.d.ts +0 -2
  41. package/src/decorators/mutate.js +0 -51
  42. package/src/decorators/name.d.ts +0 -1
  43. package/src/decorators/name.js +0 -28
  44. package/src/decorators/not_null.d.ts +0 -1
  45. package/src/decorators/not_null.js +0 -13
  46. package/src/decorators/primary_key.d.ts +0 -6
  47. package/src/decorators/primary_key.js +0 -30
  48. package/src/decorators/updated_at.d.ts +0 -12
  49. package/src/decorators/updated_at.js +0 -26
  50. package/src/decorators/validate.d.ts +0 -1
  51. package/src/decorators/validate.js +0 -53
  52. package/src/utils/batch-relations.d.ts +0 -14
  53. package/src/utils/batch-relations.js +0 -131
  54. package/src/utils/circular-detector.d.ts +0 -82
  55. package/src/utils/circular-detector.js +0 -212
  56. package/src/utils/memory-manager.d.ts +0 -42
  57. package/src/utils/memory-manager.js +0 -107
  58. package/src/utils/naming.d.ts +0 -8
  59. package/src/utils/naming.js +0 -18
  60. package/src/utils/projection.d.ts +0 -12
  61. package/src/utils/projection.js +0 -51
  62. package/src/utils/security-validator.d.ts +0 -49
  63. package/src/utils/security-validator.js +0 -163
  64. package/src/utils/throttle-manager.d.ts +0 -78
  65. package/src/utils/throttle-manager.js +0 -201
  66. package/src/utils/transaction-manager.d.ts +0 -88
  67. package/src/utils/transaction-manager.js +0 -300
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.17"
58
+ "version": "1.0.19",
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
  }
@@ -1,96 +1,137 @@
1
+ /*
2
+ @file index.ts
3
+ @descripcion Tipos públicos de Dynamite ORM
4
+ @autor Miguel Alejandro
5
+ @fecha 2025-08-07
6
+ */
7
+
8
+ // Brands para tipos especiales
1
9
  export declare const HasManyBrand: unique symbol;
2
10
  export declare const BelongsToBrand: unique symbol;
11
+ export declare const HasOneBrand: unique symbol;
3
12
  export declare const NonAttributeBrand: unique symbol;
4
13
  export declare const CreationOptionalBrand: unique symbol;
5
- export type HasMany<T> = T[] & {
6
- [HasManyBrand]?: true;
7
- };
8
- export type BelongsTo<T> = (T | null) & {
9
- [BelongsToBrand]?: true;
10
- };
11
- export type NonAttribute<T> = T & {
12
- [NonAttributeBrand]?: true;
13
- };
14
- export type CreationOptional<T> = T & {
15
- [CreationOptionalBrand]?: true;
16
- };
14
+
15
+ // Relaciones y atributos especiales
16
+ export type HasMany<T> = T[] & { [HasManyBrand]?: true };
17
+ export type BelongsTo<T> = (T | null) & { [BelongsToBrand]?: true };
18
+ export type HasOne<T> = (T | null) & { [HasOneBrand]?: true };
19
+ export type NonAttribute<T> = T & { [NonAttributeBrand]?: true };
20
+ export type CreationOptional<T> = T & { [CreationOptionalBrand]?: true };
21
+
22
+ // Atributos inferidos (excluye relaciones, non-attributes y funciones)
17
23
  export type InferAttributes<T> = {
18
- [K in keyof T as T[K] extends (...args: any[]) => any ? never : T[K] extends {
19
- [HasManyBrand]?: true;
20
- } ? never : T[K] extends {
21
- [BelongsToBrand]?: true;
22
- } ? never : T[K] extends {
23
- [NonAttributeBrand]?: true;
24
- } ? never : K]: T[K];
24
+ [K in keyof T as T[K] extends (...args: any[]) => any
25
+ ? never
26
+ : T[K] extends { [HasManyBrand]?: true }
27
+ ? never
28
+ : T[K] extends { [BelongsToBrand]?: true }
29
+ ? never
30
+ : T[K] extends { [HasOneBrand]?: true }
31
+ ? never
32
+ : T[K] extends { [NonAttributeBrand]?: true }
33
+ ? never
34
+ : K]: T[K];
25
35
  };
36
+
26
37
  export type FilterableAttributes<T> = {
27
- [K in keyof InferAttributes<T>]: InferAttributes<T>[K];
28
- };
29
- type SelectResult<T, A extends keyof T> = Pick<T, A>;
30
- type ResolveIncludeType<T, K extends keyof T> = T[K] extends HasMany<infer U> ? U[] : T[K] extends BelongsTo<infer U> ? U | null : never;
31
- export type IncludeOptions = {
32
- where?: Record<string, any>;
33
- attributes?: string[];
34
- limit?: number;
35
- skip?: number;
36
- order?: "ASC" | "DESC";
38
+ [K in keyof InferAttributes<T>]: InferAttributes<T>[K];
37
39
  };
38
- export type QueryResult<T, A extends keyof T = keyof T, I extends Record<string, any> = {}> = SelectResult<T & {
39
- [K in keyof I]: K extends keyof T ? ResolveIncludeType<T, K> : never;
40
- }, A>;
40
+
41
41
  export type WhereOptions<T> = {
42
- where?: Partial<FilterableAttributes<T>>;
43
- skip?: number;
44
- limit?: number;
45
- order?: "ASC" | "DESC";
46
- attributes?: (keyof FilterableAttributes<T>)[];
47
- include?: {
48
- [K in keyof T]?: T[K] extends HasMany<any> | BelongsTo<any> ? IncludeOptions | {} : never;
49
- };
42
+ where?: Partial<FilterableAttributes<T>>;
43
+ skip?: number;
44
+ limit?: number;
45
+ order?: "ASC" | "DESC";
46
+ attributes?: (keyof FilterableAttributes<T>)[];
47
+ include?: {
48
+ [K in keyof T]?: T[K] extends HasMany<any> | BelongsTo<any> | HasOne<any>
49
+ ? IncludeRelationOptions | true
50
+ : never;
51
+ };
50
52
  };
51
- export type WhereOptionsWithoutWhere<T> = Omit<WhereOptions<T>, "where">;
52
- export type QueryOperator = "=" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not-in" | "contains" | "begins-with";
53
+
54
+ /** Filtros de query sin cláusula where */
55
+ export type QueryFilters<T> = Omit<WhereOptions<T>, "where">;
56
+
57
+ /** @deprecated Usa QueryFilters */
58
+ export type WhereOptionsWithoutWhere<T> = QueryFilters<T>;
59
+
60
+ export type QueryOperator =
61
+ | "="
62
+ | "!="
63
+ | "<"
64
+ | "<="
65
+ | ">"
66
+ | ">="
67
+ | "in"
68
+ | "not-in"
69
+ | "contains"
70
+ | "begins-with";
71
+
72
+ /** Entrada de validador con soporte lazy */
73
+ export interface ValidatorEntry {
74
+ fn: (value: any) => boolean | string;
75
+ lazy?: boolean;
76
+ }
77
+
78
+ // Tipos para core/wrapper
53
79
  export interface Column {
54
- name: string;
55
- default?: any | (() => any);
56
- mutate?: ((value: any) => any)[];
57
- validate?: ((value: any) => boolean | string)[];
58
- index?: true;
59
- indexSort?: true;
60
- primaryKey?: boolean;
61
- nullable?: boolean;
62
- unique?: true;
63
- createdAt?: boolean;
64
- updatedAt?: boolean;
80
+ name: string;
81
+ index?: true;
82
+ indexSort?: true;
83
+ primaryKey?: boolean;
84
+ nullable?: boolean;
85
+ unique?: true;
86
+ createdAt?: boolean;
87
+ updatedAt?: boolean;
88
+ softDelete?: boolean;
89
+ lazy_validators?: ((value: any) => boolean | string)[];
90
+ relation?: RelationMetadata;
65
91
  }
92
+
66
93
  export interface RelationMetadata {
67
- type: "hasMany" | "belongsTo";
68
- targetModel: () => any;
69
- foreignKey: string;
70
- localKey?: string;
94
+ type: "hasMany" | "belongsTo" | "hasOne";
95
+ targetModel: () => any;
96
+ foreignKey: string;
97
+ localKey?: string;
71
98
  }
99
+
72
100
  export interface WrapperEntry {
73
- name: string;
74
- columns: Map<string | symbol, Column>;
75
- relations: Map<string | symbol, RelationMetadata>;
101
+ name: string;
102
+ columns: Map<string | symbol, Column>;
103
+ relations: Map<string | symbol, RelationMetadata>;
76
104
  }
105
+
106
+ /** Alias para WrapperEntry (usado internamente con SCHEMA symbol) */
107
+ export type SchemaEntry = WrapperEntry;
108
+
109
+ // Tipos internos del where de Table
77
110
  export type IncludeRelationOptions = {
78
- where?: Record<string, any>;
79
- attributes?: string[];
80
- order?: "ASC" | "DESC";
81
- skip?: number;
82
- limit?: number;
83
- include?: Record<string, IncludeRelationOptions | true>;
111
+ where?: Record<string, any>;
112
+ attributes?: string[];
113
+ order?: "ASC" | "DESC";
114
+ skip?: number;
115
+ limit?: number;
116
+ include?: Record<string, IncludeRelationOptions | true>;
84
117
  };
85
- export type WhereQueryOptions<T> = {
86
- order?: "ASC" | "DESC";
87
- skip?: number;
88
- limit?: number;
89
- attributes?: (keyof InferAttributes<T>)[];
90
- include?: {
91
- [K in keyof T]?: T[K] extends HasMany<any> | BelongsTo<any> ? IncludeRelationOptions | true : never;
92
- };
118
+
119
+ /** Opciones de query para métodos where(), first(), last(), etc. */
120
+ export type QueryOptions<T> = {
121
+ order?: "ASC" | "DESC";
122
+ skip?: number;
123
+ limit?: number;
124
+ attributes?: (keyof InferAttributes<T>)[];
125
+ include?: {
126
+ [K in keyof T]?: T[K] extends HasMany<any> | BelongsTo<any> | HasOne<any>
127
+ ? IncludeRelationOptions | true
128
+ : never;
129
+ };
93
130
  };
131
+
132
+ /** @deprecated Usa QueryOptions */
133
+ export type WhereQueryOptions<T> = QueryOptions<T>;
134
+
135
+ // Tipos utilitarios para decoradores
94
136
  export type Mutate = (value: any) => any;
95
137
  export type Validate = (value: any) => boolean | string;
96
- export {};
@@ -44,6 +44,20 @@ export declare class Dynamite {
44
44
  * Disconnect and cleanup DynamoDB client
45
45
  */
46
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>;
47
61
  /**
48
62
  * Create table with automatic GSI detection and creation
49
63
  * @param ctor Table class constructor
@@ -67,3 +81,25 @@ export declare const hasGlobalClient: () => boolean;
67
81
  * Require global client for Table operations (throws if not available)
68
82
  */
69
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
+ }
@@ -5,13 +5,11 @@
5
5
  * @autor Miguel Alejandro
6
6
  * @fecha 2025-01-27
7
7
  */
8
- var __importDefault = (this && this.__importDefault) || function (mod) {
9
- return (mod && mod.__esModule) ? mod : { "default": mod };
10
- };
11
8
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.requireClient = exports.hasGlobalClient = exports.getGlobalClient = exports.setGlobalClient = exports.Dynamite = void 0;
9
+ exports.TransactionContext = exports.requireClient = exports.hasGlobalClient = exports.getGlobalClient = exports.setGlobalClient = exports.Dynamite = void 0;
13
10
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
14
- const wrapper_1 = __importDefault(require("./wrapper"));
11
+ const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
12
+ const decorator_1 = require("./decorator");
15
13
  /**
16
14
  * Centralized Dynamite client for managing DynamoDB connections and table synchronization
17
15
  */
@@ -72,39 +70,47 @@ class Dynamite {
72
70
  console.warn("Error during Dynamite disconnect:", error);
73
71
  }
74
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
+ }
75
92
  /**
76
93
  * Create table with automatic GSI detection and creation
77
94
  * @param ctor Table class constructor
78
95
  */
79
96
  async createTableWithGSI(ctor) {
80
- const meta = wrapper_1.default.get(ctor);
97
+ const meta = ctor[decorator_1.SCHEMA];
81
98
  if (!meta)
82
- throw new Error(`Class ${ctor.name} not registered in wrapper`);
83
- const cols = [...meta.columns.values()];
84
- const pk = cols.find((c) => c.index);
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);
85
102
  if (!pk)
86
103
  throw new Error(`PartitionKey missing in ${ctor.name}`);
87
- const sk = cols.find((c) => c.indexSort);
104
+ const sk = cols.find((c) => c.store?.indexSort);
88
105
  const attr = new Map();
89
- attr.set(pk.name, "S");
106
+ attr.set(pk.name || 'id', "S");
90
107
  if (sk)
91
- attr.set(sk.name, "S");
92
- let gsiIndex = 1;
93
- const gsiDefinitions = [...meta.relations.values()]
94
- .filter((relation) => relation.type === "hasMany")
95
- .map((relation) => {
96
- const { foreignKey } = relation;
97
- if (!attr.has(foreignKey))
98
- attr.set(foreignKey, "S");
99
- return {
100
- IndexName: `GSI${gsiIndex++}_${foreignKey}`,
101
- KeySchema: [{ AttributeName: foreignKey, KeyType: "HASH" }],
102
- Projection: { ProjectionType: "ALL" },
103
- };
104
- });
105
- const schema = [{ AttributeName: pk.name, KeyType: "HASH" }];
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" }];
106
112
  if (sk && sk.name !== pk.name)
107
- schema.push({ AttributeName: sk.name, KeyType: "RANGE" });
113
+ schema.push({ AttributeName: sk.name || 'id', KeyType: "RANGE" });
108
114
  try {
109
115
  await this.client.send(new client_dynamodb_1.CreateTableCommand({
110
116
  TableName: meta.name,
@@ -161,4 +167,51 @@ const requireClient = () => {
161
167
  return globalClient;
162
168
  };
163
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;
164
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