rajt 0.0.58 → 0.0.60

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.
@@ -1,258 +0,0 @@
1
- import type { ModelMetadata, Keys, Model, Filter } from './types'
2
- import { getModelMetadata } from './decorators'
3
- import QueryBuilder from './query-builder'
4
- import Compact from './compact'
5
- import { RawClient } from './client'
6
- import { isArraySchema } from './schema'
7
- import { getLength } from 't0n'
8
-
9
- export default class AbstractModel<T extends object> {
10
- #meta: ModelMetadata
11
- cls?: Model<T>
12
- lastKey?: Record<string, any>
13
- #queryBuilder?: QueryBuilder
14
- #model?: AbstractModel<T>
15
-
16
- constructor(
17
- cls: Model<T> | ModelMetadata,
18
- queryBuilder?: QueryBuilder,
19
- model?: AbstractModel<T>
20
- ) {
21
- this.#queryBuilder = queryBuilder
22
- this.#model = model
23
-
24
- if (typeof (cls as ModelMetadata).table == 'string') {
25
- this.#meta = cls as ModelMetadata
26
- this.cls = model?.cls
27
- return
28
- }
29
-
30
- const meta = getModelMetadata(cls)
31
- if (!meta)
32
- throw new Error('Missing model metadata')
33
-
34
- this.#meta = meta
35
- this.cls = cls as Model<T>
36
- }
37
-
38
- get table(): string {
39
- return this.#meta.table
40
- }
41
-
42
- get keySchema() {
43
- return this.#meta.keys
44
- }
45
-
46
- set lastEvaluatedKey(val: Record<string, any> | undefined) {
47
- if (this.#model) {
48
- this.#model.lastKey = val
49
- } else {
50
- this.lastKey = val
51
- }
52
- }
53
- get lastEvaluatedKey() {
54
- return this.lastKey
55
- }
56
-
57
- where(builderFn: (q: QueryBuilder) => void) {
58
- const qb = new QueryBuilder()
59
- builderFn(qb)
60
- return new AbstractModel<T>(this.#meta, qb, this)
61
- }
62
-
63
- async scan(filterFn?: Filter<T>) {
64
- const result = await RawClient.scan(this.table, this.#queryBuilder?.filters)
65
-
66
- this.lastEvaluatedKey = result.LastEvaluatedKey
67
- return this.#processItems(result.Items, filterFn)
68
- }
69
-
70
- async query(filterFn?: Filter<T>) {
71
- const result = await RawClient.query(this.table, this.#queryBuilder?.conditions)
72
-
73
- this.lastEvaluatedKey = result.LastEvaluatedKey
74
- return this.#processItems(result.Items, filterFn)
75
- }
76
-
77
- async get(key: Keys, sk?: string) {
78
- const result = await RawClient.get(this.table, this.#key(key, sk))
79
- return result.Item ? this.#processItem(result.Item) : undefined
80
- }
81
-
82
- async put(item: Partial<T>, key: Keys) {
83
- let keys
84
- if (this.#meta.zip) {
85
- keys = this.#getItemKey(item, key)
86
- this.#validateKeys(keys)
87
- // @ts-ignore
88
- item = { ...keys, V: Compact.encode(this.#getItemWithoutKeys(item), this.#meta.fields) }
89
- } else {
90
- this.#validateKeys(item)
91
- }
92
-
93
- await RawClient.put(this.table, item)
94
- return this.#processItem(item, keys)
95
- }
96
-
97
- async update(attrs: Partial<T>, key: Keys) {
98
- let keys
99
- if (this.#meta.zip) {
100
- keys = this.#getItemKey(attrs, key)
101
- this.#validateKeys(keys)
102
- // @ts-ignore
103
- attrs = { V: Compact.encode(this.#getItemWithoutKeys(attrs), this.#meta.fields) }
104
- } else {
105
- this.#validateKeys(attrs)
106
- }
107
-
108
- const UpdateExpressionParts: string[] = []
109
- const ExpressionAttributeValues: any = {}
110
- for (const [k, v] of Object.entries(attrs)) {
111
- UpdateExpressionParts.push(`#${k} = :${k}`)
112
- ExpressionAttributeValues[`:${k}`] = v
113
- }
114
- const UpdateExpression = 'SET ' + UpdateExpressionParts.join(', ')
115
- const ExpressionAttributeNames = Object.fromEntries(Object.keys(attrs).map(k => [`#${k}`, k]))
116
-
117
- await RawClient.update(this.table, {
118
- UpdateExpression,
119
- ExpressionAttributeValues,
120
- ExpressionAttributeNames,
121
- }, this.#key(key))
122
-
123
- return this.#processItem(attrs, keys)
124
- }
125
-
126
- async delete(key: Keys, sk?: string) {
127
- return RawClient.delete(this.table, this.#key(key, sk))
128
- }
129
-
130
- async batchGet(keys: Array<Keys>) {
131
- const result = await RawClient.batchGet({
132
- RequestItems: { [this.table]: { Keys: keys.map(key => this.#key(key)) } },
133
- })
134
- return (result.Responses?.[this.table] as T[] || []).map(item => this.#processItem(item))
135
- }
136
-
137
- async batchWrite(items: Array<{ put?: Partial<T>, delete?: Keys }>) {
138
- const WriteRequests = items.map(i => {
139
- if (i.put) {
140
- return { PutRequest: { Item: i.put } }
141
- } else if (i.delete) {
142
- return { DeleteRequest: { Key: this.#key(i.delete) } }
143
- }
144
- return null
145
- }).filter(Boolean) as any[]
146
-
147
- return RawClient.batchWrite({ RequestItems: { [this.table]: WriteRequests } })
148
- }
149
-
150
- async deleteMany(keys: Array<Keys>) {
151
- return this.batchWrite(keys.map(k => ({ delete: k })))
152
- }
153
-
154
- async putMany(items: Array<Partial<T>>) {
155
- return this.batchWrite(items.map(item => ({ put: item })))
156
- }
157
-
158
- #key(key: Keys, sk?: string) {
159
- if (!this.#meta.keys) return {}
160
- return RawClient.key(key, sk, this.#meta.keys, this.#meta.defaultSK)
161
- }
162
-
163
- #getItemKey(item: Partial<T>, key?: Keys): Record<string, string> {
164
- if (!this.#meta.keys) return {}
165
-
166
- const keys: Record<string, string> = {}
167
- if (key)
168
- this.#processExplicitKey(keys, key)
169
- else if (getLength(item) > 0)
170
- this.#processItemKeys(keys, item)
171
-
172
- return keys
173
- }
174
-
175
- #processExplicitKey(keys: Record<string, string>, key: Keys): void {
176
- if (!this.#meta.keys) return
177
- if (Array.isArray(key)) {
178
- keys[this.#meta.keys.PK] = key[0]
179
-
180
- if (this.#meta.keys?.SK) {
181
- if (key.length > 1)
182
- // @ts-ignore
183
- keys[this.#meta.keys.SK] = key[1]
184
- else if (this.#meta.defaultSK)
185
- keys[this.#meta.keys.SK] = this.#meta.defaultSK
186
- }
187
- } else {
188
- keys[this.#meta.keys.PK] = String(key)
189
- }
190
- }
191
-
192
- #processItemKeys(keys: Record<string, string>, item: Partial<T>): void {
193
- if (!this.#meta.keys) return
194
-
195
- const pkValue = item[this.#meta.keys.PK as keyof Partial<T>]
196
- if (pkValue !== undefined)
197
- keys[this.#meta.keys.PK] = String(pkValue)
198
-
199
- if (this.#meta.keys?.SK) {
200
- const skValue = item[this.#meta.keys.SK as keyof Partial<T>]
201
- if (skValue !== undefined)
202
- keys[this.#meta.keys.SK] = String(skValue)
203
- else if (this.#meta.defaultSK)
204
- keys[this.#meta.keys.SK] = this.#meta.defaultSK
205
- }
206
- }
207
-
208
- #validateKeys(keys: Record<string, any>) {
209
- if (!this.#meta.keys)
210
- throw new Error(`Missing keys of table "${this.table}"`)
211
-
212
- if (!(this.#meta.keys.PK in keys))
213
- throw new Error(`Missing partition key of table "${this.table}" `)
214
-
215
- if (this.#meta.keys?.SK && !(this.#meta.keys.SK in keys))
216
- throw new Error(`Missing sort key of table "${this.table}"`)
217
- }
218
-
219
- #getItemWithoutKeys(item: Partial<T>): Partial<T> {
220
- if (Array.isArray(item))
221
- return item?.length ? item.map(i => this.#getItemWithoutKeys(i)) as T : [] as T
222
-
223
- if (!this.#meta.keys || !item) return { ...item }
224
-
225
- const { PK, SK } = this.#meta.keys
226
- const { [PK as keyof T]: _, [SK as keyof T]: __, ...rest } = item
227
-
228
- return rest as Partial<T>
229
- }
230
-
231
- #processItems(items: any[] | undefined, filterFn?: Filter<T>): T[] {
232
- if (!items || !items.length) return []
233
- items = items.map(item => this.#processItem(item))
234
- return filterFn ? items.filter(filterFn) : items
235
- }
236
-
237
- #processItem(item: any, keys?: Record<string, string>): T {
238
- if (this.#meta.zip && item?.V) {
239
- const value = Compact.decode<T>(item.V, this.#meta.fields)
240
- const model = isArraySchema(this.#meta.fields) && Array.isArray(value)
241
- ? value.map(v => new this.cls!(v))
242
- : new this.cls!(value)
243
-
244
- if (!keys) keys = this.#getItemKey(item)
245
-
246
- return this.#withKey(model as T, keys)
247
- }
248
-
249
- return new this.cls!(item)
250
- }
251
-
252
- #withKey(model: T, keys: Record<string, string>): T {
253
- // @ts-ignore
254
- if (Array.isArray(model)) return model.map(m => this.#withKey(m, keys))
255
- // @ts-ignore
256
- return model.withKey(keys[this.#meta.keys.PK], keys[this.#meta.keys.SK] || undefined)
257
- }
258
- }
@@ -1,174 +0,0 @@
1
- import type { Condition, Operator } from './types'
2
-
3
- export default class QueryBuilder {
4
- #filters: Condition[] = []
5
- #keyConditions: Condition[] = []
6
- #limit?: number
7
- #startKey?: Record<string, any>
8
- #index?: string
9
- #attrCounter = 1
10
- #valCounter = 1
11
- #fieldAttrMap: Record<string, string> = {}
12
- #fieldValMap: Record<string, string> = {}
13
-
14
- filter(field: string, operator: Operator, value: any = null) {
15
- this.#filters.push({ type: 'filter', field, operator, value })
16
- return this
17
- }
18
-
19
- keyCondition(field: string, operator: Operator | any, value?: any) {
20
- const noVal = value === undefined
21
- this.#keyConditions.push({ type: 'keyCondition', field, operator: noVal ? '=' : operator, value: noVal ? operator : value })
22
- return this
23
- }
24
-
25
- limit(n: number) {
26
- this.#limit = n
27
- return this
28
- }
29
-
30
- exclusiveStartKey(key: Record<string, any>) {
31
- this.#startKey = key
32
- return this
33
- }
34
-
35
- index(name: string) {
36
- this.#index = name
37
- return this
38
- }
39
-
40
- private attrName(field: string) {
41
- if (!this.#fieldAttrMap[field])
42
- this.#fieldAttrMap[field] = '#a'+ this.#attrCounter++
43
-
44
- return this.#fieldAttrMap[field]
45
- }
46
-
47
- private valName(val: any) {
48
- val = String(val)
49
- if (!this.#fieldValMap[val])
50
- this.#fieldValMap[val] = ':v'+ this.#valCounter++
51
-
52
- return this.#fieldValMap[val]
53
- }
54
-
55
- #resetCounters() {
56
- this.#attrCounter = 0
57
- this.#valCounter = 0
58
- this.#fieldAttrMap = {}
59
- this.#fieldValMap = {}
60
- }
61
-
62
- buildExpression(conditions: Condition[]) {
63
- const exprParts: string[] = []
64
- const values: Record<string, any> = {}
65
- const names: Record<string, string> = {}
66
-
67
- for (const cond of conditions) {
68
- const attr = this.attrName(cond.field)
69
- const val = Array.isArray(cond.value) ? '' : this.valName(cond.value)
70
- names[attr] = cond.field
71
-
72
- switch (cond.operator) {
73
- case 'between': {
74
- const val0 = this.valName(cond.value[0])
75
- const val1 = this.valName(cond.value[1])
76
- exprParts.push(`${attr} BETWEEN ${val0} AND ${val1}`)
77
- values[val0] = cond.value[0]
78
- values[val1] = cond.value[1]
79
- break
80
- }
81
- case 'begins_with': {
82
- exprParts.push(`begins_with(${attr}, ${val})`)
83
- values[val] = cond.value
84
- break
85
- }
86
- case 'in': {
87
- const inVals = cond.value.map((v: any) => {
88
- const key = this.valName(v)
89
- values[key] = v
90
- return key
91
- })
92
- exprParts.push(`${attr} IN (${inVals.join(', ')})`)
93
- break
94
- }
95
- case 'attribute_exists': {
96
- exprParts.push(`attribute_exists(${attr})`)
97
- break
98
- }
99
- case 'attribute_not_exists': {
100
- exprParts.push(`attribute_not_exists(${attr})`)
101
- break
102
- }
103
- case 'attribute_type': {
104
- exprParts.push(`attribute_type(${attr}, ${val})`)
105
- values[val] = cond.value
106
- break
107
- }
108
- case 'contains': {
109
- exprParts.push(`contains(${attr}, ${val})`)
110
- values[val] = cond.value
111
- break
112
- }
113
- case 'size': {
114
- exprParts.push(`size(${attr}) = ${val}`)
115
- values[val] = cond.value
116
- break
117
- }
118
- default: {
119
- exprParts.push(`${attr} ${cond.operator} ${val}`)
120
- values[val] = cond.value
121
- }
122
- }
123
- }
124
-
125
- return {
126
- expression: exprParts.length ? exprParts.join(' AND ') : undefined,
127
- names: Object.keys(names).length ? names : undefined,
128
- values: Object.keys(values).length ? values : undefined,
129
- }
130
- }
131
-
132
- get filters() {
133
- const filter = this.buildExpression(this.#filters)
134
- const params: any = {}
135
-
136
- if (this.#limit)
137
- params.Limit = this.#limit
138
-
139
- if (this.#startKey)
140
- params.ExclusiveStartKey = this.#startKey
141
-
142
- if (filter.expression)
143
- params.FilterExpression = filter.expression
144
-
145
- if (filter.names)
146
- params.ExpressionAttributeNames = filter.names
147
-
148
- if (filter.values)
149
- params.ExpressionAttributeValues = filter.values
150
-
151
- return params
152
- }
153
-
154
- get conditions() {
155
- const keys = this.buildExpression(this.#keyConditions)
156
- const filters = this.filters
157
-
158
- const params: any = { ...filters }
159
-
160
- if (this.#index)
161
- params.IndexName = this.#index
162
-
163
- if (keys.expression)
164
- params.KeyConditionExpression = keys.expression
165
-
166
- if (keys.names || filters?.ExpressionAttributeNames)
167
- params.ExpressionAttributeNames = { ...(keys?.names || {}), ...(filters?.ExpressionAttributeNames || {}) }
168
-
169
- if (keys.values || filters?.ExpressionAttributeValues)
170
- params.ExpressionAttributeValues = { ...(keys?.values || {}), ...(filters?.ExpressionAttributeValues || {}) }
171
-
172
- return params
173
- }
174
- }
@@ -1,31 +0,0 @@
1
- import { z, ZodTypeAny } from 'zod'
2
- import { Dynamodb } from './client'
3
- import { Schema } from './schema'
4
- import { _model } from './decorators'
5
- import type { ModelOpts } from './types'
6
-
7
- export function Repository<
8
- S extends ZodTypeAny,
9
- B extends new (...args: any[]) => any
10
- >(
11
- schema: S,
12
- base?: B | ModelOpts,
13
- opts?: ModelOpts
14
- ) {
15
- const isClass = typeof base == 'function'
16
- type M = z.infer<S>
17
-
18
- const Repo = Schema(schema, isClass ? base : undefined)
19
- _model(Repo, isClass ? opts : base)
20
-
21
- return class extends Repo {
22
- static model = Dynamodb.model<M>(Repo as any)
23
-
24
- static get lastKey() {
25
- return this.model?.lastEvaluatedKey || null
26
- }
27
- } as unknown as (typeof Repo) & {
28
- new (...args: any[]): InstanceType<typeof Repo>
29
- model: ReturnType<typeof Dynamodb.model<M>>
30
- }
31
- }
@@ -1,107 +0,0 @@
1
- import { z, ZodTypeAny } from 'zod'
2
- import type { SchemaStructure } from './types'
3
-
4
- const m = Symbol('a')
5
- export function isArraySchema(v: any): boolean {
6
- return v[m] || false
7
- }
8
-
9
- export function arraySchema(v: any): any {
10
- // @ts-ignore
11
- v[m] = true
12
- return v
13
- }
14
-
15
- export function extractZodKeys(schema: ZodTypeAny): SchemaStructure {
16
- if (schema instanceof z.ZodObject) {
17
- return Object.entries(schema.shape).map(([key, value]) => {
18
- const inner = unwrap(value as ZodTypeAny)
19
-
20
- if (inner instanceof z.ZodObject)
21
- return notEmpty(key, extractZodKeys(inner))
22
-
23
- if (inner instanceof z.ZodArray) {
24
- const item = unwrap(inner._def.type as ZodTypeAny)
25
- return item instanceof z.ZodObject ? notEmpty(key, extractZodKeys(item)) : key
26
- }
27
-
28
- return key
29
- })
30
- }
31
-
32
- if (schema instanceof z.ZodArray) {
33
- const item = unwrap(schema._def.type as ZodTypeAny)
34
- if (item instanceof z.ZodObject)
35
- return arraySchema(extractZodKeys(item))
36
-
37
- return []
38
- }
39
-
40
- return []
41
- }
42
-
43
- export function unwrap(schema: ZodTypeAny): ZodTypeAny {
44
- if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable)
45
- return unwrap(schema._def.innerType)
46
-
47
- if (schema instanceof z.ZodDefault)
48
- return unwrap(schema._def.innerType)
49
-
50
- // if (schema instanceof z.ZodUnion)
51
- // return unwrap(schema._def.options[0] as ZodTypeAny)
52
-
53
- if (schema instanceof z.ZodUnion) {
54
- const options = schema._def.options as ZodTypeAny[]
55
- const nonEmptyOption = options.find(opt => !(opt instanceof z.ZodUndefined) && !(opt instanceof z.ZodNull))
56
- return nonEmptyOption ? unwrap(nonEmptyOption) : options[0]
57
- }
58
-
59
- if (schema instanceof z.ZodEffects)
60
- return unwrap(schema._def.schema)
61
-
62
- return schema
63
- }
64
-
65
- function notEmpty(key: string, schema: SchemaStructure): string | Record<string, SchemaStructure> {
66
- return schema?.length ? {[key]: schema} : key
67
- }
68
-
69
- export function Schema<
70
- T extends ZodTypeAny,
71
- B extends object
72
- >(
73
- schema: T,
74
- BaseClass?: new (...args: any[]) => B
75
- ) {
76
- const Base = (BaseClass || class {})
77
-
78
- return class extends Base {
79
- static _schema = schema
80
- static defaultSortKey?: string
81
-
82
- #PK?: string
83
- #SK?: string
84
-
85
- constructor(data: z.infer<T>) {
86
- super()
87
- Object.assign(this, data)
88
- }
89
-
90
- get PK() { return this.#PK }
91
- get SK() { return this.#SK }
92
-
93
- static get schema() {
94
- return extractZodKeys(this._schema)
95
- }
96
-
97
- static get defaultSK() {
98
- return this.defaultSortKey
99
- }
100
-
101
- withKey(key: string, sk?: string) {
102
- this.#PK = key
103
- if (sk) this.#SK = sk
104
- return this
105
- }
106
- }
107
- }
@@ -1,32 +0,0 @@
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