rajt 0.0.15 → 0.0.17
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 +4 -4
- package/src/dynamodb/client.ts +5 -1
- package/src/dynamodb/compact.ts +149 -0
- package/src/dynamodb/decorators.ts +5 -16
- package/src/dynamodb/model.ts +163 -39
- package/src/dynamodb/query-builder.ts +1 -8
- package/src/dynamodb/schema.ts +25 -11
- package/src/dynamodb/types.ts +32 -0
- package/src/types.ts +0 -4
- package/src/utils/lenght.ts +32 -0
- package/src/utils/resolve.ts +0 -38
package/package.json
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rajt",
|
|
3
3
|
"description": "A serverless bundler layer, fully typed for AWS Lambda (Node.js and LLRT) and Cloudflare Workers.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.17",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts",
|
|
9
9
|
"./dynamodb": "./src/dynamodb/index.ts",
|
|
10
|
+
"./dynamodb/types": "./src/dynamodb/types.ts",
|
|
10
11
|
"./http": "./src/http.ts",
|
|
11
12
|
"./types": "./src/types.ts"
|
|
12
13
|
},
|
|
13
|
-
"files": [
|
|
14
|
-
"src"
|
|
15
|
-
],
|
|
14
|
+
"files": ["src"],
|
|
16
15
|
"scripts": {
|
|
17
16
|
"dev": "tsx watch src/dev.ts",
|
|
18
17
|
"local": "bun run --silent build && bun run --silent sam:local",
|
|
@@ -54,6 +53,7 @@
|
|
|
54
53
|
"registry": "https://registry.npmjs.org"
|
|
55
54
|
},
|
|
56
55
|
"author": "Zunq <open-source@zunq.com>",
|
|
56
|
+
"license": "MIT",
|
|
57
57
|
"homepage": "https://zunq.dev",
|
|
58
58
|
"repository": "git://github.com/attla/rajt",
|
|
59
59
|
"bugs": "https://github.com/attla/rajt/issues",
|
package/src/dynamodb/client.ts
CHANGED
|
@@ -3,7 +3,11 @@ import dynamodbLite from '@aws-lite/dynamodb'
|
|
|
3
3
|
import AbstractModel from './model'
|
|
4
4
|
|
|
5
5
|
export const aws = await awsLite({
|
|
6
|
-
plugins: [dynamodbLite]
|
|
6
|
+
plugins: [dynamodbLite],
|
|
7
|
+
// https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-lib-dynamodb/#configuration
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
awsjsonMarshall: {convertClassInstanceToMap: true},
|
|
10
|
+
awsjsonUnmarshall: {convertClassInstanceToMap: true}
|
|
7
11
|
})
|
|
8
12
|
|
|
9
13
|
export class Dynamodb {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { SchemaStructure } from './types'
|
|
2
|
+
import getLength from '../utils/lenght'
|
|
3
|
+
|
|
4
|
+
export default class Compact {
|
|
5
|
+
private static typeMap: Record<string, string> = {
|
|
6
|
+
// Null
|
|
7
|
+
'null,': 'N,',
|
|
8
|
+
',null': ',N',
|
|
9
|
+
'null]': 'N]',
|
|
10
|
+
// True
|
|
11
|
+
'true,': 'T,',
|
|
12
|
+
',true': ',T',
|
|
13
|
+
'true]': 'T]',
|
|
14
|
+
// False
|
|
15
|
+
'false,': 'F,',
|
|
16
|
+
',false': ',F',
|
|
17
|
+
'false]': 'F]',
|
|
18
|
+
// Array
|
|
19
|
+
'[],': 'A,',
|
|
20
|
+
',[]': ',A',
|
|
21
|
+
'[]]': 'A]',
|
|
22
|
+
// Object
|
|
23
|
+
'{},': 'O,',
|
|
24
|
+
',{}': ',O',
|
|
25
|
+
'{}]': 'O]'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static encode(obj: any, schema: SchemaStructure): string {
|
|
29
|
+
const seen: any[] = []
|
|
30
|
+
return this.replaceTypes(
|
|
31
|
+
JSON.stringify(this.zip(obj, schema, seen)).replace(/(,|\[)"(\^\d+)"(\]|,|$)/g, '$1$2$3')
|
|
32
|
+
.replace(/"/g, '~TDQ~')
|
|
33
|
+
.replace(/'/g, '"')
|
|
34
|
+
.replace(/~TDQ~/g, "'")
|
|
35
|
+
.replace(/\\'/g, "^'"),
|
|
36
|
+
this.typeMap
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static smartDecode<T = any>(val: any, schema: SchemaStructure): T {
|
|
41
|
+
if (!val) return val as T
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(val))
|
|
44
|
+
return val.map((i: { v: string }) => this.decode<T>(i?.V, schema)).filter(Boolean) as T
|
|
45
|
+
|
|
46
|
+
return val?.V ? this.decode<T>(val.V, schema) : val
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static decode<T = any>(val: string, schema: SchemaStructure): T {
|
|
50
|
+
if (!val) return val as T
|
|
51
|
+
|
|
52
|
+
val = this.replaceTypes(val, this.reverseMap(this.typeMap))
|
|
53
|
+
.replace(/"/g, '~TSQ~')
|
|
54
|
+
.replace(/'/g, '"')
|
|
55
|
+
.replace(/~TSQ~/g, "'")
|
|
56
|
+
.replace(/\^"/g, '\\"')
|
|
57
|
+
.replace(/(,|\[)(\^\d+)(\]|,|$)/g, '$1"$2"$3')
|
|
58
|
+
|
|
59
|
+
return this.withSchema(this.unzip(JSON.parse(val)), schema) as T
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static zip(obj: any, schema: SchemaStructure, seen: any[]): any[] {
|
|
63
|
+
return schema.map(key => {
|
|
64
|
+
if (typeof key === 'string')
|
|
65
|
+
return this.memo(obj[key], seen)
|
|
66
|
+
|
|
67
|
+
const mainKey = Object.keys(key)[0]
|
|
68
|
+
const subKeys = key[mainKey]
|
|
69
|
+
const val = obj[mainKey]
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(val))
|
|
72
|
+
return val.map(item => this.zip(item, subKeys, seen))
|
|
73
|
+
|
|
74
|
+
return this.zip(val, subKeys, seen)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static unzip(array: any[], seen: any[] = [], deep = false): any[] {
|
|
79
|
+
return array.map(item => {
|
|
80
|
+
const length = getLength(item)
|
|
81
|
+
|
|
82
|
+
if ([null, true, false].includes(item) || typeof item !== 'object' && length < 2)
|
|
83
|
+
return item
|
|
84
|
+
|
|
85
|
+
if (Array.isArray(item))
|
|
86
|
+
return this.unzip(item, seen, true)
|
|
87
|
+
|
|
88
|
+
if (typeof item === 'string' && item.startsWith('^')) {
|
|
89
|
+
const pos = parseInt(item.slice(1), 10)
|
|
90
|
+
const val = seen[pos]
|
|
91
|
+
return deep || (val && !`${val}`.startsWith('^')) ? val : item
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
seen.push(item)
|
|
95
|
+
return item
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static withSchema(value: any[], keys: any[]): any {
|
|
100
|
+
if (!value || !Array.isArray(value))
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
return Object.fromEntries(
|
|
104
|
+
keys.map((key, index) => this.entry(key, value[index])).filter(Boolean)
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static entry(key: any, value: any): any {
|
|
109
|
+
if (!key) return undefined
|
|
110
|
+
|
|
111
|
+
if (typeof key === 'string')
|
|
112
|
+
return [key, value]
|
|
113
|
+
|
|
114
|
+
const mainKey = Object.keys(key)[0]
|
|
115
|
+
const subKeys = key[mainKey]
|
|
116
|
+
|
|
117
|
+
if (Array.isArray(value)) {
|
|
118
|
+
if (value.length === 0)
|
|
119
|
+
return [mainKey, []]
|
|
120
|
+
|
|
121
|
+
return Array.isArray(value[0])
|
|
122
|
+
? [mainKey, value.map(v => this.withSchema(v, subKeys))]
|
|
123
|
+
: [mainKey, this.withSchema(value, subKeys)]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return [mainKey, value]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static memo(val: any, seen: any[]): any {
|
|
130
|
+
const length = getLength(val)
|
|
131
|
+
// TODO: may be incompatible with empty objects or arrays
|
|
132
|
+
if (typeof val !== 'object' && length < 2) return val
|
|
133
|
+
|
|
134
|
+
const index = seen.indexOf(val)
|
|
135
|
+
if (index !== -1)
|
|
136
|
+
return `^${index}`
|
|
137
|
+
|
|
138
|
+
seen.push(val)
|
|
139
|
+
return val
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static replaceTypes(str: string, map: Record<string, string>) {
|
|
143
|
+
return Object.entries(map).reduce((s, [from, to]) => s.replaceAll(from, to), str)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static reverseMap(map: Record<string, string>): Record<string, string> {
|
|
147
|
+
return Object.fromEntries(Object.entries(map).map(([k, v]) => [v, k]))
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
import plur from 'plur'
|
|
2
|
-
|
|
3
|
-
export type ModelMetadata = {
|
|
4
|
-
table: string,
|
|
5
|
-
keys?: Record<'PK' | 'SK', string>,
|
|
6
|
-
zip: boolean,
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type ModelOpts = string | {
|
|
10
|
-
table?: string,
|
|
11
|
-
partitionKey?: string,
|
|
12
|
-
sortKey?: string,
|
|
13
|
-
zip?: boolean,
|
|
14
|
-
}
|
|
2
|
+
import type { ModelMetadata, ModelOpts } from './types'
|
|
15
3
|
|
|
16
4
|
export function getModelMetadata(target: Function | any): ModelMetadata {
|
|
17
5
|
if (!target?.m)
|
|
@@ -22,6 +10,7 @@ export function getModelMetadata(target: Function | any): ModelMetadata {
|
|
|
22
10
|
table: target.m[0],
|
|
23
11
|
// @ts-ignore
|
|
24
12
|
keys: typeKeys !== 'undefined' ? (typeKeys === 'string' ? { PK: target.m[1] } : { PK: target.m[1][0], SK: target.m[1][1] }) : undefined,
|
|
13
|
+
defaultSK: target?.defaultSK || undefined,
|
|
25
14
|
zip: target.m[2] || false,
|
|
26
15
|
fields: target.m[3] || [],
|
|
27
16
|
}
|
|
@@ -37,19 +26,19 @@ function _table(target: Function | any, opt?: ModelOpts) {
|
|
|
37
26
|
function _zip(target: Function | any) {
|
|
38
27
|
if (!target?.m) target.m = []
|
|
39
28
|
target.m[2] = true
|
|
40
|
-
target.m[3] = Object.keys(new target)
|
|
29
|
+
target.m[3] = target?.schema || Object.keys(new target)
|
|
41
30
|
}
|
|
42
31
|
|
|
43
32
|
function _key(target: Function | any, pk: string, sk?: string) {
|
|
44
33
|
if (!target?.m) target.m = []
|
|
45
|
-
target.m[1] = pk && sk? [pk, sk] : [pk]
|
|
34
|
+
target.m[1] = pk && sk ? [pk, sk] : [pk]
|
|
46
35
|
}
|
|
47
36
|
|
|
48
37
|
function _model(target: any, opt?: ModelOpts) {
|
|
49
38
|
_table(target, opt)
|
|
50
39
|
const notStr = typeof opt !== 'string'
|
|
51
40
|
|
|
52
|
-
if (!opt || notStr
|
|
41
|
+
if (!opt || !notStr || (typeof opt?.zip === undefined || opt?.zip))
|
|
53
42
|
_zip(target)
|
|
54
43
|
|
|
55
44
|
const pk = opt && notStr ? opt?.partitionKey : undefined
|
package/src/dynamodb/model.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { AwsLiteDynamoDB } from '@aws-lite/dynamodb-types'
|
|
2
|
+
import type { ModelMetadata, Keys, Model, Filter } from './types'
|
|
2
3
|
import { getModelMetadata } from './decorators'
|
|
3
|
-
import type { ModelMetadata } from './decorators'
|
|
4
4
|
import QueryBuilder from './query-builder'
|
|
5
|
+
import Compact from './compact'
|
|
6
|
+
import getLength from '../utils/lenght'
|
|
5
7
|
|
|
6
8
|
export default class AbstractModel<T extends object> {
|
|
7
9
|
private meta: ModelMetadata
|
|
10
|
+
private cls!: Model<T>
|
|
8
11
|
private lastKey?: Record<string, any>
|
|
9
12
|
|
|
10
13
|
constructor(
|
|
11
|
-
cls:
|
|
14
|
+
cls: Model<T> | ModelMetadata,
|
|
12
15
|
private db: AwsLiteDynamoDB,
|
|
13
16
|
private queryBuilder?: QueryBuilder,
|
|
14
17
|
private model?: AbstractModel<T>
|
|
@@ -18,19 +21,19 @@ export default class AbstractModel<T extends object> {
|
|
|
18
21
|
return
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
// @ts-ignore
|
|
22
24
|
const meta = getModelMetadata(cls)
|
|
23
25
|
if (!meta)
|
|
24
26
|
throw new Error('Missing model metadata')
|
|
25
27
|
|
|
26
28
|
this.meta = meta
|
|
29
|
+
this.cls = cls as Model<T>
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
get table(): string {
|
|
30
33
|
return this.meta.table
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
get
|
|
36
|
+
get keySchema() {
|
|
34
37
|
return this.meta.keys
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -45,48 +48,57 @@ export default class AbstractModel<T extends object> {
|
|
|
45
48
|
return this.lastKey
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
schema(pk: string, sk?: string) {
|
|
49
|
-
if (!this.meta.keys) return {}
|
|
50
|
-
|
|
51
|
-
const keys = { [this.meta.keys.PK]: pk }
|
|
52
|
-
if (sk && this.meta.keys.SK)
|
|
53
|
-
keys[this.meta.keys.SK] = sk
|
|
54
|
-
|
|
55
|
-
return keys
|
|
56
|
-
}
|
|
57
|
-
|
|
58
51
|
where(builderFn: (q: QueryBuilder) => void) {
|
|
59
52
|
const qb = new QueryBuilder()
|
|
60
53
|
builderFn(qb)
|
|
61
|
-
|
|
62
|
-
return model
|
|
54
|
+
return new AbstractModel<T>(this.meta, this.db, qb, this)
|
|
63
55
|
}
|
|
64
56
|
|
|
65
|
-
async scan(filterFn?:
|
|
57
|
+
async scan(filterFn?: Filter<T>) {
|
|
66
58
|
const result = await this.db.Scan({ TableName: this.table, ...this.queryBuilder?.filters })
|
|
59
|
+
|
|
67
60
|
this.lastEvaluatedKey = result.LastEvaluatedKey
|
|
68
|
-
|
|
69
|
-
return filterFn ? items.filter(filterFn) : items
|
|
61
|
+
return this.processItems(result.Items, filterFn)
|
|
70
62
|
}
|
|
71
63
|
|
|
72
|
-
async query(filterFn?:
|
|
64
|
+
async query(filterFn?: Filter<T>) {
|
|
73
65
|
const result = await this.db.Query({ TableName: this.table, ...this.queryBuilder?.conditions })
|
|
66
|
+
|
|
74
67
|
this.lastEvaluatedKey = result.LastEvaluatedKey
|
|
75
|
-
|
|
76
|
-
return filterFn ? items.filter(filterFn) : items
|
|
68
|
+
return this.processItems(result.Items, filterFn)
|
|
77
69
|
}
|
|
78
70
|
|
|
79
|
-
async get(
|
|
80
|
-
const result = await this.db.GetItem({ TableName: this.table, Key: this.
|
|
81
|
-
return result.Item
|
|
71
|
+
async get(key: Keys, sk?: string) {
|
|
72
|
+
const result = await this.db.GetItem({ TableName: this.table, Key: this.key(key, sk) })
|
|
73
|
+
return result.Item ? this.processItem(result.Item) : undefined
|
|
82
74
|
}
|
|
83
75
|
|
|
84
|
-
async put(item: Partial<T
|
|
76
|
+
async put(item: Partial<T>, key: Keys) {
|
|
77
|
+
let keys
|
|
78
|
+
if (this.meta.zip) {
|
|
79
|
+
keys = this.getItemKey(item, key)
|
|
80
|
+
this.validateKeys(keys)
|
|
81
|
+
// @ts-ignore
|
|
82
|
+
item = { ...keys, V: Compact.encode(this.getItemWithoutKeys(item), this.meta.fields)}
|
|
83
|
+
} else {
|
|
84
|
+
this.validateKeys(item)
|
|
85
|
+
}
|
|
86
|
+
|
|
85
87
|
await this.db.PutItem({ TableName: this.table, Item: item })
|
|
86
|
-
return item
|
|
88
|
+
return this.processItem(item, keys)
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
async update(
|
|
91
|
+
async update(attrs: Partial<T>, key: Keys) {
|
|
92
|
+
let keys
|
|
93
|
+
if (this.meta.zip) {
|
|
94
|
+
keys = this.getItemKey(attrs, key)
|
|
95
|
+
this.validateKeys(keys)
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
attrs = { V: Compact.encode(this.getItemWithoutKeys(attrs), this.meta.fields)}
|
|
98
|
+
} else {
|
|
99
|
+
this.validateKeys(attrs)
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
const UpdateExpressionParts: string[] = []
|
|
91
103
|
const ExpressionAttributeValues: any = {}
|
|
92
104
|
for (const [k, v] of Object.entries(attrs)) {
|
|
@@ -96,32 +108,34 @@ export default class AbstractModel<T extends object> {
|
|
|
96
108
|
const UpdateExpression = 'SET ' + UpdateExpressionParts.join(', ')
|
|
97
109
|
const ExpressionAttributeNames = Object.fromEntries(Object.keys(attrs).map(k => [`#${k}`, k]))
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
await this.db.UpdateItem({
|
|
100
112
|
TableName: this.table,
|
|
101
|
-
Key:
|
|
113
|
+
Key: this.key(key),
|
|
102
114
|
UpdateExpression,
|
|
103
115
|
ExpressionAttributeValues,
|
|
104
116
|
ExpressionAttributeNames
|
|
105
117
|
})
|
|
118
|
+
|
|
119
|
+
return this.processItem(attrs, keys)
|
|
106
120
|
}
|
|
107
121
|
|
|
108
|
-
async delete(
|
|
109
|
-
return this.db.DeleteItem({ TableName: this.table, Key: this.
|
|
122
|
+
async delete(key: Keys, sk?: string) {
|
|
123
|
+
return this.db.DeleteItem({ TableName: this.table, Key: this.key(key, sk) })
|
|
110
124
|
}
|
|
111
125
|
|
|
112
|
-
async batchGet(keys: Array<
|
|
126
|
+
async batchGet(keys: Array<Keys>) {
|
|
113
127
|
const result = await this.db.BatchGetItem({ RequestItems: {
|
|
114
|
-
[this.table]: { Keys: keys.map(
|
|
128
|
+
[this.table]: { Keys: keys.map(key => this.key(key)) }
|
|
115
129
|
} })
|
|
116
|
-
return result.Responses?.[this.table] as T[]
|
|
130
|
+
return (result.Responses?.[this.table] as T[] || []).map(item => this.processItem(item))
|
|
117
131
|
}
|
|
118
132
|
|
|
119
|
-
async batchWrite(items: Array<{ put?: Partial<T>, delete?:
|
|
133
|
+
async batchWrite(items: Array<{ put?: Partial<T>, delete?: Keys }>) {
|
|
120
134
|
const WriteRequests = items.map(i => {
|
|
121
135
|
if (i.put) {
|
|
122
136
|
return { PutRequest: { Item: i.put } }
|
|
123
137
|
} else if (i.delete) {
|
|
124
|
-
return { DeleteRequest: { Key: this.
|
|
138
|
+
return { DeleteRequest: { Key: this.key(i.delete) } }
|
|
125
139
|
}
|
|
126
140
|
return null
|
|
127
141
|
}).filter(Boolean)
|
|
@@ -129,11 +143,121 @@ export default class AbstractModel<T extends object> {
|
|
|
129
143
|
return this.db.BatchWriteItem({ RequestItems: {[this.table]: WriteRequests} })
|
|
130
144
|
}
|
|
131
145
|
|
|
132
|
-
async deleteMany(keys: Array<
|
|
146
|
+
async deleteMany(keys: Array<Keys>) {
|
|
133
147
|
return this.batchWrite(keys.map(k => ({ delete: k })))
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
async putMany(items: Array<Partial<T>>) {
|
|
137
|
-
return this.batchWrite(items.map(
|
|
151
|
+
return this.batchWrite(items.map(item => ({ put: item })))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private key(key: Keys, sk?: string) {
|
|
155
|
+
if (!this.meta.keys) return {}
|
|
156
|
+
|
|
157
|
+
let pk: string
|
|
158
|
+
let skValue: string | undefined
|
|
159
|
+
if (Array.isArray(key)) {
|
|
160
|
+
pk = key[0]
|
|
161
|
+
skValue = key[1] ?? sk
|
|
162
|
+
} else {
|
|
163
|
+
pk = key
|
|
164
|
+
skValue = sk
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const keys = { [this.meta.keys.PK]: pk }
|
|
168
|
+
|
|
169
|
+
if (this.meta.keys?.SK) {
|
|
170
|
+
if (skValue) {
|
|
171
|
+
keys[this.meta.keys.SK] = skValue
|
|
172
|
+
} else if (this.meta.defaultSK) {
|
|
173
|
+
keys[this.meta.keys.SK] = this.meta.defaultSK
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return keys
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private getItemKey(item: Partial<T>, key?: Keys): Record<string, string> {
|
|
181
|
+
if (!this.meta.keys) return {}
|
|
182
|
+
|
|
183
|
+
const keys: Record<string, string> = {}
|
|
184
|
+
if (key)
|
|
185
|
+
this.processExplicitKey(keys, key)
|
|
186
|
+
else if (getLength(item) > 0)
|
|
187
|
+
this.processItemKeys(keys, item)
|
|
188
|
+
|
|
189
|
+
return keys
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private processExplicitKey(keys: Record<string, string>, key: Keys): void {
|
|
193
|
+
if (!this.meta.keys) return
|
|
194
|
+
if (Array.isArray(key)) {
|
|
195
|
+
keys[this.meta.keys.PK] = key[0]
|
|
196
|
+
|
|
197
|
+
if (this.meta.keys?.SK) {
|
|
198
|
+
if (key.length > 1)
|
|
199
|
+
// @ts-ignore
|
|
200
|
+
keys[this.meta.keys.SK] = key[1]
|
|
201
|
+
else if (this.meta.defaultSK)
|
|
202
|
+
keys[this.meta.keys.SK] = this.meta.defaultSK
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
keys[this.meta.keys.PK] = String(key)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private processItemKeys(keys: Record<string, string>, item: Partial<T>): void {
|
|
210
|
+
if (!this.meta.keys) return
|
|
211
|
+
|
|
212
|
+
const pkValue = item[this.meta.keys.PK as keyof Partial<T>]
|
|
213
|
+
if (pkValue !== undefined)
|
|
214
|
+
keys[this.meta.keys.PK] = String(pkValue)
|
|
215
|
+
|
|
216
|
+
if (this.meta.keys?.SK) {
|
|
217
|
+
const skValue = item[this.meta.keys.SK as keyof Partial<T>]
|
|
218
|
+
if (skValue !== undefined)
|
|
219
|
+
keys[this.meta.keys.SK] = String(skValue)
|
|
220
|
+
else if (this.meta.defaultSK)
|
|
221
|
+
keys[this.meta.keys.SK] = this.meta.defaultSK
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private validateKeys(keys: Record<string, any>) {
|
|
226
|
+
if (!this.meta.keys)
|
|
227
|
+
throw new Error(`Missing keys of table "${this.table}"`)
|
|
228
|
+
|
|
229
|
+
if (!(this.meta.keys.PK in keys))
|
|
230
|
+
throw new Error(`Missing partition key of table "${this.table}" `)
|
|
231
|
+
|
|
232
|
+
if (this.meta.keys?.SK && !(this.meta.keys.SK in keys))
|
|
233
|
+
throw new Error(`Missing sort key of table "${this.table}"`)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private getItemWithoutKeys(item: Partial<T>): Partial<T> {
|
|
237
|
+
if (!this.meta.keys || !item) return { ...item }
|
|
238
|
+
|
|
239
|
+
const { PK, SK } = this.meta.keys
|
|
240
|
+
const { [PK as keyof T]: _, [SK as keyof T]: __, ...rest } = item
|
|
241
|
+
|
|
242
|
+
return rest as Partial<T>
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private processItems(items: any[] | undefined, filterFn?: Filter<T>): T[] {
|
|
246
|
+
if (!items) return []
|
|
247
|
+
|
|
248
|
+
items = this.meta.zip ? Compact.smartDecode<T[]>(items, this.meta.fields) : items as T[]
|
|
249
|
+
return filterFn ? items.filter(filterFn) : items
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private processItem(item: any, keys?: Record<string, string>): T {
|
|
253
|
+
if (this.meta.zip && item?.V) {
|
|
254
|
+
const model = new this.cls(Compact.decode(item.V, this.meta.fields))
|
|
255
|
+
if (!keys) keys = this.getItemKey(item)
|
|
256
|
+
|
|
257
|
+
// @ts-ignore
|
|
258
|
+
return model.withKey(keys[this.meta.keys.PK], keys[this.meta.keys.SK] || undefined)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return new this.cls(item)
|
|
138
262
|
}
|
|
139
263
|
}
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
3
|
-
type Condition = {
|
|
4
|
-
type: 'filter' | 'keyCondition',
|
|
5
|
-
field: string,
|
|
6
|
-
operator: Operator,
|
|
7
|
-
value: any
|
|
8
|
-
}
|
|
1
|
+
import type { Condition, Operator } from './types'
|
|
9
2
|
|
|
10
3
|
export default class QueryBuilder {
|
|
11
4
|
private _conditions: Condition[] = []
|
package/src/dynamodb/schema.ts
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import { z, ZodTypeAny } from 'zod'
|
|
2
|
+
import type { SchemaStructure } from './types'
|
|
2
3
|
|
|
3
|
-
function extractZodKeys(schema: ZodTypeAny):
|
|
4
|
+
function extractZodKeys(schema: ZodTypeAny): SchemaStructure {
|
|
4
5
|
if (schema instanceof z.ZodObject) {
|
|
5
6
|
const shape = schema.shape
|
|
7
|
+
|
|
6
8
|
return Object.entries(shape).map(([key, value]) => {
|
|
7
9
|
const inner = unwrap(value as ZodTypeAny)
|
|
8
10
|
|
|
9
|
-
if (inner instanceof z.ZodObject)
|
|
11
|
+
if (inner instanceof z.ZodObject)
|
|
10
12
|
return { [key]: extractZodKeys(inner) }
|
|
11
|
-
}
|
|
12
13
|
|
|
13
14
|
if (inner instanceof z.ZodArray) {
|
|
14
15
|
const item = unwrap(inner._def.type as ZodTypeAny)
|
|
15
|
-
|
|
16
|
-
return { [key]: extractZodKeys(item) }
|
|
17
|
-
|
|
18
|
-
return key
|
|
16
|
+
return item instanceof z.ZodObject ? { [key]: extractZodKeys(item) } : key
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
return key
|
|
@@ -38,13 +36,29 @@ function unwrap(schema: ZodTypeAny): ZodTypeAny {
|
|
|
38
36
|
export default function Schema<T extends ZodTypeAny>(schema: T) {
|
|
39
37
|
return class {
|
|
40
38
|
static _schema = schema
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
39
|
+
static defaultSortKey?: string = undefined
|
|
40
|
+
#PK?: string = undefined
|
|
41
|
+
#SK?: string = undefined
|
|
45
42
|
|
|
46
43
|
constructor(data: z.infer<T>) {
|
|
47
44
|
Object.assign(this, data)
|
|
48
45
|
}
|
|
46
|
+
|
|
47
|
+
get PK() { return this.#PK }
|
|
48
|
+
get SK() { return this.#SK }
|
|
49
|
+
|
|
50
|
+
static get schema() {
|
|
51
|
+
return extractZodKeys(this._schema)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static get defaultSK() {
|
|
55
|
+
return this.defaultSortKey
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
withKey(key: string, sk?: string) {
|
|
59
|
+
this.#PK = key
|
|
60
|
+
if (sk) this.#SK = sk
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
49
63
|
}
|
|
50
64
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type Operator = '=' | '<>' | '<' | '<=' | '>' | '>=' | 'begins_with' | 'between' | 'in' | 'attribute_exists' | 'attribute_not_exists' | 'attribute_type' | 'contains' | 'size'
|
|
2
|
+
|
|
3
|
+
export type Condition = {
|
|
4
|
+
type: 'filter' | 'keyCondition',
|
|
5
|
+
field: string,
|
|
6
|
+
operator: Operator,
|
|
7
|
+
value: any
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type ISchemaStructure = string | Record<string, ISchemaStructure[]>
|
|
11
|
+
export type SchemaStructure = ISchemaStructure[]
|
|
12
|
+
|
|
13
|
+
export type KeySchema = Record<'PK' | 'SK', string>
|
|
14
|
+
export type ModelMetadata = {
|
|
15
|
+
table: string,
|
|
16
|
+
keys?: KeySchema,
|
|
17
|
+
defaultSK?: string,
|
|
18
|
+
zip: boolean,
|
|
19
|
+
fields: SchemaStructure,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ModelOpts = string | {
|
|
23
|
+
table?: string,
|
|
24
|
+
partitionKey?: string,
|
|
25
|
+
sortKey?: string,
|
|
26
|
+
defaultSK?: string,
|
|
27
|
+
zip?: boolean,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type Keys = string | [string] | [string, string]
|
|
31
|
+
export type Model<T extends object> = new (...args: any[]) => T
|
|
32
|
+
export type Filter<T> = (item: T) => boolean
|
package/src/types.ts
CHANGED
|
@@ -25,13 +25,9 @@ export type LambdaResponse = {
|
|
|
25
25
|
export type Errors = Record<string, string | string[]>
|
|
26
26
|
export type ErrorResponse = {
|
|
27
27
|
m?: string, // message
|
|
28
|
-
// c?: number, // http code
|
|
29
28
|
e?: Errors, // error bag
|
|
30
|
-
// e?: Record<string, string | string[]>, // error bag
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
// export type Response<E> = E | ErrorResponse
|
|
34
|
-
|
|
35
31
|
export type ResponseHeadersInit = [
|
|
36
32
|
string,
|
|
37
33
|
string
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export default function getLength(item: any): number {
|
|
2
|
+
const type = typeof item
|
|
3
|
+
|
|
4
|
+
switch (type) {
|
|
5
|
+
case 'string':
|
|
6
|
+
return item.length
|
|
7
|
+
|
|
8
|
+
case 'number':
|
|
9
|
+
case 'bigint':
|
|
10
|
+
// case 'function':
|
|
11
|
+
return item.toString().length
|
|
12
|
+
|
|
13
|
+
// case 'boolean':
|
|
14
|
+
// return item ? 1 : 0
|
|
15
|
+
|
|
16
|
+
// case 'symbol':
|
|
17
|
+
// return item.toString().length - 8
|
|
18
|
+
|
|
19
|
+
case 'object':
|
|
20
|
+
if (item === null)
|
|
21
|
+
return 0
|
|
22
|
+
|
|
23
|
+
if (Array.isArray(item))
|
|
24
|
+
return item.length
|
|
25
|
+
|
|
26
|
+
return Object.keys(item).length
|
|
27
|
+
|
|
28
|
+
case 'undefined':
|
|
29
|
+
default:
|
|
30
|
+
return 0
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/utils/resolve.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import Action, { ActionType } from '../action'
|
|
2
|
-
// import BaseMiddleware, { MiddlewareType } from '../middleware'
|
|
3
2
|
|
|
4
3
|
export default function resolve(obj: ActionType) {
|
|
5
4
|
if (typeof obj === 'function' && obj?.length === 2)
|
|
@@ -17,40 +16,3 @@ export default function resolve(obj: ActionType) {
|
|
|
17
16
|
|
|
18
17
|
throw new Error('Invalid action')
|
|
19
18
|
}
|
|
20
|
-
|
|
21
|
-
// export function resolveMiddleware(obj: MiddlewareType) {
|
|
22
|
-
// if (typeof obj === 'function' && obj.length === 2)
|
|
23
|
-
// return obj
|
|
24
|
-
|
|
25
|
-
// if (obj instanceof BaseMiddleware)
|
|
26
|
-
// return obj.handle
|
|
27
|
-
|
|
28
|
-
// if (BaseMiddleware.isPrototypeOf(obj)) {
|
|
29
|
-
// const instance = new (obj as new () => BaseMiddleware)()
|
|
30
|
-
// return instance.handle
|
|
31
|
-
// }
|
|
32
|
-
|
|
33
|
-
// throw new Error('Invalid middleware provided. Must be a Hono middleware function or MiddlewareClass instance/constructor')
|
|
34
|
-
// }
|
|
35
|
-
|
|
36
|
-
// // import Action, { ActionType } from '../action'
|
|
37
|
-
|
|
38
|
-
// export default function resolve(obj: any) {
|
|
39
|
-
// if (typeof obj === 'function' && obj?.length === 2)
|
|
40
|
-
// return [obj]
|
|
41
|
-
|
|
42
|
-
// // if (obj instanceof Action)
|
|
43
|
-
// // return obj.run()
|
|
44
|
-
|
|
45
|
-
// // const instance = new (obj as new () => Action)()
|
|
46
|
-
// // @ts-ignore
|
|
47
|
-
// const instance = new obj()
|
|
48
|
-
// // if (Action.isPrototypeOf(obj))
|
|
49
|
-
// if (obj?.prototype?.run)
|
|
50
|
-
// return instance.run()
|
|
51
|
-
|
|
52
|
-
// if (obj?.prototype?.handle)
|
|
53
|
-
// return [instance.handle]
|
|
54
|
-
|
|
55
|
-
// throw new Error('Invalid action')
|
|
56
|
-
// }
|