@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 +40 -2
- package/src/@types/index.d.ts +102 -0
- package/src/@types/index.js +9 -0
- package/src/core/client.d.ts +105 -0
- package/src/core/client.js +217 -0
- package/src/core/decorator.d.ts +44 -0
- package/src/core/decorator.js +133 -0
- package/src/core/method.d.ts +73 -0
- package/src/core/method.js +140 -0
- package/src/core/table.d.ts +56 -0
- package/src/core/table.js +659 -0
- package/src/decorators/indexes.d.ts +38 -0
- package/src/decorators/indexes.js +67 -0
- package/src/decorators/relations.d.ts +55 -0
- package/src/decorators/relations.js +84 -0
- package/src/decorators/timestamps.d.ts +54 -0
- package/src/decorators/timestamps.js +67 -0
- package/src/decorators/transforms.d.ts +86 -0
- package/src/decorators/transforms.js +154 -0
- package/src/index.d.ts +15 -0
- package/src/index.js +50 -0
- package/src/index.test.d.ts +13 -0
- package/src/index.test.js +1992 -0
- package/src/utils/relations.d.ts +39 -0
- package/src/utils/relations.js +141 -0
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.
|
|
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,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
|