@stonyx/orm 0.3.2-beta.65 → 0.3.2-beta.66
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/config/environment.js +8 -0
- package/dist/commands.js +34 -0
- package/dist/dynamodb/connection.d.ts +28 -0
- package/dist/dynamodb/connection.js +28 -0
- package/dist/dynamodb/dynamodb-db.d.ts +131 -0
- package/dist/dynamodb/dynamodb-db.js +556 -0
- package/dist/dynamodb/operation-builder.d.ts +76 -0
- package/dist/dynamodb/operation-builder.js +109 -0
- package/dist/dynamodb/type-map.d.ts +31 -0
- package/dist/dynamodb/type-map.js +48 -0
- package/dist/main.js +6 -0
- package/dist/types/orm-types.d.ts +6 -0
- package/package.json +9 -1
- package/src/commands.ts +43 -0
- package/src/dynamodb/connection.ts +49 -0
- package/src/dynamodb/dynamodb-db.ts +768 -0
- package/src/dynamodb/operation-builder.ts +188 -0
- package/src/dynamodb/type-map.ts +54 -0
- package/src/main.ts +5 -0
- package/src/types/orm-types.ts +7 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DynamoDB operation parameter builders.
|
|
3
|
+
*
|
|
4
|
+
* Each function returns a plain-object "params" bag that can be passed
|
|
5
|
+
* directly to the corresponding DocumentClient command
|
|
6
|
+
* (PutCommand, GetCommand, UpdateCommand, DeleteCommand, ScanCommand, QueryCommand).
|
|
7
|
+
*
|
|
8
|
+
* All functions are pure — no SDK imports here; the caller wraps params
|
|
9
|
+
* in the appropriate Command class.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* PutItem — optionally with a condition expression.
|
|
13
|
+
*
|
|
14
|
+
* Pass conditionExpression = 'attribute_not_exists(id)' to enforce uniqueness.
|
|
15
|
+
*/
|
|
16
|
+
export function buildPutItem(tableName, item, conditionExpression) {
|
|
17
|
+
const params = { TableName: tableName, Item: item };
|
|
18
|
+
if (conditionExpression)
|
|
19
|
+
params.ConditionExpression = conditionExpression;
|
|
20
|
+
return params;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* GetItem by primary key.
|
|
24
|
+
*/
|
|
25
|
+
export function buildGetItem(tableName, key) {
|
|
26
|
+
return { TableName: tableName, Key: key };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* UpdateItem with a SET expression built from the `updates` object.
|
|
30
|
+
* Only the supplied attributes are updated (diff-based call site).
|
|
31
|
+
*/
|
|
32
|
+
export function buildUpdateItem(tableName, key, updates) {
|
|
33
|
+
const names = {};
|
|
34
|
+
const values = {};
|
|
35
|
+
const setClauses = [];
|
|
36
|
+
for (const [attr, val] of Object.entries(updates)) {
|
|
37
|
+
const nameAlias = `#${attr}`;
|
|
38
|
+
const valAlias = `:${attr}`;
|
|
39
|
+
names[nameAlias] = attr;
|
|
40
|
+
values[valAlias] = val;
|
|
41
|
+
setClauses.push(`${nameAlias} = ${valAlias}`);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
TableName: tableName,
|
|
45
|
+
Key: key,
|
|
46
|
+
UpdateExpression: `SET ${setClauses.join(', ')}`,
|
|
47
|
+
ExpressionAttributeNames: names,
|
|
48
|
+
ExpressionAttributeValues: values,
|
|
49
|
+
ReturnValues: 'NONE',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* DeleteItem by primary key.
|
|
54
|
+
*/
|
|
55
|
+
export function buildDeleteItem(tableName, key) {
|
|
56
|
+
return { TableName: tableName, Key: key };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* ScanCommand params.
|
|
60
|
+
* If conditions are supplied they are rendered as a FilterExpression using AND.
|
|
61
|
+
*/
|
|
62
|
+
export function buildScan(tableName, conditions, exclusiveStartKey) {
|
|
63
|
+
const params = { TableName: tableName };
|
|
64
|
+
if (exclusiveStartKey)
|
|
65
|
+
params.ExclusiveStartKey = exclusiveStartKey;
|
|
66
|
+
if (conditions && Object.keys(conditions).length > 0) {
|
|
67
|
+
const names = {};
|
|
68
|
+
const values = {};
|
|
69
|
+
const clauses = [];
|
|
70
|
+
for (const [attr, val] of Object.entries(conditions)) {
|
|
71
|
+
const nameAlias = `#${attr}`;
|
|
72
|
+
const valAlias = `:${attr}`;
|
|
73
|
+
names[nameAlias] = attr;
|
|
74
|
+
values[valAlias] = val;
|
|
75
|
+
clauses.push(`${nameAlias} = ${valAlias}`);
|
|
76
|
+
}
|
|
77
|
+
params.FilterExpression = clauses.join(' AND ');
|
|
78
|
+
params.ExpressionAttributeNames = names;
|
|
79
|
+
params.ExpressionAttributeValues = values;
|
|
80
|
+
}
|
|
81
|
+
return params;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* QueryCommand params for a GSI.
|
|
85
|
+
* keyConditions must be in the form { attrName: value } and will be rendered
|
|
86
|
+
* as equality expressions joined by AND.
|
|
87
|
+
*/
|
|
88
|
+
export function buildQuery(tableName, indexName, keyConditions, exclusiveStartKey) {
|
|
89
|
+
const names = {};
|
|
90
|
+
const values = {};
|
|
91
|
+
const clauses = [];
|
|
92
|
+
for (const [attr, val] of Object.entries(keyConditions)) {
|
|
93
|
+
const nameAlias = `#${attr}`;
|
|
94
|
+
const valAlias = `:${attr}`;
|
|
95
|
+
names[nameAlias] = attr;
|
|
96
|
+
values[valAlias] = val;
|
|
97
|
+
clauses.push(`${nameAlias} = ${valAlias}`);
|
|
98
|
+
}
|
|
99
|
+
const params = {
|
|
100
|
+
TableName: tableName,
|
|
101
|
+
IndexName: indexName,
|
|
102
|
+
KeyConditionExpression: clauses.join(' AND '),
|
|
103
|
+
ExpressionAttributeNames: names,
|
|
104
|
+
ExpressionAttributeValues: values,
|
|
105
|
+
};
|
|
106
|
+
if (exclusiveStartKey)
|
|
107
|
+
params.ExclusiveStartKey = exclusiveStartKey;
|
|
108
|
+
return params;
|
|
109
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps ORM attribute types to DynamoDB scalar attribute types.
|
|
3
|
+
* DynamoDB DocumentClient auto-marshalls JS objects, so most values
|
|
4
|
+
* are sent as their native JS types. This map is used by the
|
|
5
|
+
* schema-introspector and startup provisioner for table/GSI creation.
|
|
6
|
+
*/
|
|
7
|
+
export type DynamoScalarType = 'S' | 'N' | 'BOOL';
|
|
8
|
+
/**
|
|
9
|
+
* DynamoDB attribute-type string for a given ORM attr type.
|
|
10
|
+
* - string → S
|
|
11
|
+
* - number / float → N (stored as Number; DocumentClient handles it)
|
|
12
|
+
* - boolean → BOOL
|
|
13
|
+
* - date → S (ISO-8601 string — enables range queries)
|
|
14
|
+
* - timestamp → N (milliseconds since epoch)
|
|
15
|
+
* - passthrough/trim/etc → S (safe default)
|
|
16
|
+
*
|
|
17
|
+
* For key schema declarations only `S` and `N` are valid; BOOL
|
|
18
|
+
* is legal for attributes but never for a PK/SK.
|
|
19
|
+
*/
|
|
20
|
+
declare const typeMap: Record<string, DynamoScalarType>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the DynamoDB attribute type for a given ORM type string.
|
|
23
|
+
* Defaults to 'S' for any unknown/custom type.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getDynamoType(attrType: string): DynamoScalarType;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the DynamoDB key type ('S' | 'N') for use in KeySchema.
|
|
28
|
+
* BOOL cannot be a key attribute; anything that maps to BOOL falls back to 'S'.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getDynamoKeyType(attrType: string): 'S' | 'N';
|
|
31
|
+
export default typeMap;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps ORM attribute types to DynamoDB scalar attribute types.
|
|
3
|
+
* DynamoDB DocumentClient auto-marshalls JS objects, so most values
|
|
4
|
+
* are sent as their native JS types. This map is used by the
|
|
5
|
+
* schema-introspector and startup provisioner for table/GSI creation.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* DynamoDB attribute-type string for a given ORM attr type.
|
|
9
|
+
* - string → S
|
|
10
|
+
* - number / float → N (stored as Number; DocumentClient handles it)
|
|
11
|
+
* - boolean → BOOL
|
|
12
|
+
* - date → S (ISO-8601 string — enables range queries)
|
|
13
|
+
* - timestamp → N (milliseconds since epoch)
|
|
14
|
+
* - passthrough/trim/etc → S (safe default)
|
|
15
|
+
*
|
|
16
|
+
* For key schema declarations only `S` and `N` are valid; BOOL
|
|
17
|
+
* is legal for attributes but never for a PK/SK.
|
|
18
|
+
*/
|
|
19
|
+
const typeMap = {
|
|
20
|
+
string: 'S',
|
|
21
|
+
number: 'N',
|
|
22
|
+
float: 'N',
|
|
23
|
+
boolean: 'BOOL',
|
|
24
|
+
date: 'S',
|
|
25
|
+
timestamp: 'N',
|
|
26
|
+
passthrough: 'S',
|
|
27
|
+
trim: 'S',
|
|
28
|
+
uppercase: 'S',
|
|
29
|
+
ceil: 'N',
|
|
30
|
+
floor: 'N',
|
|
31
|
+
round: 'N',
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Returns the DynamoDB attribute type for a given ORM type string.
|
|
35
|
+
* Defaults to 'S' for any unknown/custom type.
|
|
36
|
+
*/
|
|
37
|
+
export function getDynamoType(attrType) {
|
|
38
|
+
return typeMap[attrType] ?? 'S';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Returns the DynamoDB key type ('S' | 'N') for use in KeySchema.
|
|
42
|
+
* BOOL cannot be a key attribute; anything that maps to BOOL falls back to 'S'.
|
|
43
|
+
*/
|
|
44
|
+
export function getDynamoKeyType(attrType) {
|
|
45
|
+
const t = getDynamoType(attrType);
|
|
46
|
+
return t === 'N' ? 'N' : 'S';
|
|
47
|
+
}
|
|
48
|
+
export default typeMap;
|
package/dist/main.js
CHANGED
|
@@ -119,6 +119,12 @@ export default class Orm {
|
|
|
119
119
|
this.db = this.sqlDb;
|
|
120
120
|
promises.push(this.sqlDb.init());
|
|
121
121
|
}
|
|
122
|
+
else if (config.orm.dynamodb) {
|
|
123
|
+
const { default: DynamoDBDB } = await import('./dynamodb/dynamodb-db.js');
|
|
124
|
+
this.sqlDb = new DynamoDBDB();
|
|
125
|
+
this.db = this.sqlDb;
|
|
126
|
+
promises.push(this.sqlDb.init());
|
|
127
|
+
}
|
|
122
128
|
else if (this.options.dbType !== 'none') {
|
|
123
129
|
const db = new DB();
|
|
124
130
|
this.db = db;
|
|
@@ -42,6 +42,11 @@ export interface OrmRestServerConfig {
|
|
|
42
42
|
route: string;
|
|
43
43
|
metaRoute: boolean;
|
|
44
44
|
}
|
|
45
|
+
export interface OrmDynamoDBConfig {
|
|
46
|
+
region?: string;
|
|
47
|
+
endpoint?: string;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
45
50
|
export interface OrmSection {
|
|
46
51
|
db: OrmDbConfig;
|
|
47
52
|
paths: OrmPaths;
|
|
@@ -49,6 +54,7 @@ export interface OrmSection {
|
|
|
49
54
|
mysql?: OrmMysqlConfig;
|
|
50
55
|
postgres?: OrmPostgresConfig;
|
|
51
56
|
timescale?: OrmPostgresConfig;
|
|
57
|
+
dynamodb?: OrmDynamoDBConfig;
|
|
52
58
|
logColor?: string;
|
|
53
59
|
logMethod?: string;
|
|
54
60
|
[key: string]: unknown;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"stonyx-async",
|
|
5
5
|
"stonyx-module"
|
|
6
6
|
],
|
|
7
|
-
"version": "0.3.2-beta.
|
|
7
|
+
"version": "0.3.2-beta.66",
|
|
8
8
|
"description": "",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"type": "module",
|
|
@@ -66,11 +66,19 @@
|
|
|
66
66
|
"stonyx": "0.2.3-beta.66"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
+
"@aws-sdk/client-dynamodb": "^3.0.0",
|
|
70
|
+
"@aws-sdk/lib-dynamodb": "^3.0.0",
|
|
69
71
|
"@stonyx/rest-server": ">=0.2.1-beta.14",
|
|
70
72
|
"mysql2": "^3.0.0",
|
|
71
73
|
"pg": "^8.0.0"
|
|
72
74
|
},
|
|
73
75
|
"peerDependenciesMeta": {
|
|
76
|
+
"@aws-sdk/client-dynamodb": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"@aws-sdk/lib-dynamodb": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
74
82
|
"mysql2": {
|
|
75
83
|
"optional": true
|
|
76
84
|
},
|
package/src/commands.ts
CHANGED
|
@@ -28,6 +28,13 @@ const commands: Record<string, Command> = {
|
|
|
28
28
|
description: 'Generate a MySQL migration from current model schemas',
|
|
29
29
|
bootstrap: true,
|
|
30
30
|
run: async (args) => {
|
|
31
|
+
const config = (await import('stonyx/config')).default;
|
|
32
|
+
|
|
33
|
+
if (config.orm.dynamodb) {
|
|
34
|
+
console.log('DynamoDB does not use file-based migrations. Use db:sync to provision tables.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
const description = args?.join(' ') || 'migration';
|
|
32
39
|
const { generateMigration } = await import('./mysql/migration-generator.js');
|
|
33
40
|
const result = await generateMigration(description);
|
|
@@ -39,6 +46,25 @@ const commands: Record<string, Command> = {
|
|
|
39
46
|
}
|
|
40
47
|
}
|
|
41
48
|
},
|
|
49
|
+
'db:sync': {
|
|
50
|
+
description: 'Provision DynamoDB tables and GSIs from current model schemas',
|
|
51
|
+
bootstrap: true,
|
|
52
|
+
run: async () => {
|
|
53
|
+
const config = (await import('stonyx/config')).default;
|
|
54
|
+
|
|
55
|
+
if (!config.orm.dynamodb) {
|
|
56
|
+
console.error('DynamoDB is not configured. Set DYNAMODB_REGION (and optionally DYNAMODB_ENDPOINT) to enable DynamoDB mode.');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { default: DynamoDBDB } = await import('./dynamodb/dynamodb-db.js');
|
|
61
|
+
const db = new DynamoDBDB();
|
|
62
|
+
await db.init();
|
|
63
|
+
await db.startup();
|
|
64
|
+
await db.shutdown();
|
|
65
|
+
console.log('DynamoDB tables synced successfully.');
|
|
66
|
+
}
|
|
67
|
+
},
|
|
42
68
|
'db:migrate': {
|
|
43
69
|
description: 'Apply pending MySQL migrations',
|
|
44
70
|
bootstrap: true,
|
|
@@ -46,6 +72,11 @@ const commands: Record<string, Command> = {
|
|
|
46
72
|
const config = (await import('stonyx/config')).default;
|
|
47
73
|
const mysqlConfig = config.orm.mysql;
|
|
48
74
|
|
|
75
|
+
if (config.orm.dynamodb) {
|
|
76
|
+
console.log('DynamoDB does not use file-based migrations. Use db:sync to provision tables.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
49
80
|
if (!mysqlConfig) {
|
|
50
81
|
console.error('MySQL is not configured. Set MYSQL_HOST to enable MySQL mode.');
|
|
51
82
|
process.exit(1);
|
|
@@ -92,6 +123,12 @@ const commands: Record<string, Command> = {
|
|
|
92
123
|
bootstrap: true,
|
|
93
124
|
run: async () => {
|
|
94
125
|
const config = (await import('stonyx/config')).default;
|
|
126
|
+
|
|
127
|
+
if (config.orm.dynamodb) {
|
|
128
|
+
console.log('DynamoDB does not support migration rollback. Manage table changes via the AWS console or db:sync.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
95
132
|
const mysqlConfig = config.orm.mysql;
|
|
96
133
|
|
|
97
134
|
if (!mysqlConfig) {
|
|
@@ -138,6 +175,12 @@ const commands: Record<string, Command> = {
|
|
|
138
175
|
bootstrap: true,
|
|
139
176
|
run: async () => {
|
|
140
177
|
const config = (await import('stonyx/config')).default;
|
|
178
|
+
|
|
179
|
+
if (config.orm.dynamodb) {
|
|
180
|
+
console.log('DynamoDB does not use file-based migrations. Use db:sync to provision tables.');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
141
184
|
const mysqlConfig = config.orm.mysql;
|
|
142
185
|
|
|
143
186
|
if (!mysqlConfig) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DynamoDB connection factory.
|
|
3
|
+
*
|
|
4
|
+
* Dynamically imports @aws-sdk/client-dynamodb and @aws-sdk/lib-dynamodb
|
|
5
|
+
* so these are optional peerDependencies (matching the pg/mysql2 pattern).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface DynamoDBConfig {
|
|
9
|
+
region?: string;
|
|
10
|
+
endpoint?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Type aliases — declared loose so we don't need to import the real SDK types
|
|
15
|
+
// at compile time (they're optional peer deps).
|
|
16
|
+
export type DocumentClient = {
|
|
17
|
+
send(command: unknown): Promise<unknown>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type DocumentClientConstructor = new (options: { client: unknown }) => DocumentClient;
|
|
21
|
+
export type DynamoDBClientConstructor = new (options: unknown) => unknown;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a DynamoDBDocumentClient from the given config.
|
|
25
|
+
* Uses dynamic import so @aws-sdk/* are optional peer deps.
|
|
26
|
+
*/
|
|
27
|
+
export async function createDocumentClient(dbConfig: DynamoDBConfig): Promise<DocumentClient> {
|
|
28
|
+
const { DynamoDB } = await import('@aws-sdk/client-dynamodb' as string) as {
|
|
29
|
+
DynamoDB: DynamoDBClientConstructor;
|
|
30
|
+
};
|
|
31
|
+
const { DynamoDBDocument } = await import('@aws-sdk/lib-dynamodb' as string) as {
|
|
32
|
+
DynamoDBDocument: DocumentClientConstructor;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const clientOptions: Record<string, unknown> = {};
|
|
36
|
+
if (dbConfig.region) clientOptions.region = dbConfig.region;
|
|
37
|
+
if (dbConfig.endpoint) clientOptions.endpoint = dbConfig.endpoint;
|
|
38
|
+
|
|
39
|
+
const rawClient = new DynamoDB(clientOptions);
|
|
40
|
+
return new DynamoDBDocument({ client: rawClient });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Nullify the document client reference (DynamoDB connections are HTTP-based
|
|
45
|
+
* and stateless — no explicit pool close needed, but we clear the reference).
|
|
46
|
+
*/
|
|
47
|
+
export function destroyDocumentClient(_client: DocumentClient | null): null {
|
|
48
|
+
return null;
|
|
49
|
+
}
|