@sentio/sdk 2.39.3 → 2.39.4-rc.1

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.
@@ -0,0 +1,271 @@
1
+ import { RichStruct, RichValue, RichValue_NullValue } from '@sentio/protos'
2
+ import { String, Int, Float, ID, Bytes, DateTime, Boolean } from './types.js'
3
+ import { Entity } from './entity.js'
4
+ import { BigDecimal } from '@sentio/bigdecimal'
5
+
6
+ export interface ValueConverter<T> {
7
+ from: (value: T) => RichValue
8
+ to: (value: RichValue) => T
9
+ }
10
+
11
+ export function required_<T>(converter: ValueConverter<T | undefined>): ValueConverter<T> {
12
+ return {
13
+ from: (value: T | undefined) => {
14
+ if (value == null) {
15
+ throw new Error('Value is required but received null or undefined')
16
+ }
17
+ return converter.from(value)
18
+ },
19
+ to: (value: RichValue) => {
20
+ if (value == null || value.nullValue) {
21
+ throw new Error('Value is required but received null')
22
+ }
23
+ return converter.to(value)!
24
+ }
25
+ }
26
+ }
27
+
28
+ export function array_<T>(converter: ValueConverter<T>): ValueConverter<T[]> {
29
+ return {
30
+ from: (value: T[]) => {
31
+ return {
32
+ listValue: {
33
+ values: value.map(converter.from)
34
+ }
35
+ }
36
+ },
37
+ to: (value: RichValue) => {
38
+ return value.listValue?.values.map(converter.to) || []
39
+ }
40
+ }
41
+ }
42
+
43
+ export function enumerate_<T extends string | number>(values: Record<T, string>): ValueConverter<T> {
44
+ return {
45
+ from: (value?: T) => {
46
+ if (value == null) {
47
+ return {
48
+ nullValue: RichValue_NullValue.NULL_VALUE
49
+ }
50
+ }
51
+ return {
52
+ stringValue: values[value]
53
+ }
54
+ },
55
+ to(v: RichValue): T {
56
+ return v.stringValue as T
57
+ }
58
+ }
59
+ }
60
+
61
+ export function objectId_<T>(): ValueConverter<T | ID> {
62
+ return {
63
+ from: (value: T | ID) => {
64
+ if (typeof value == 'string') {
65
+ return {
66
+ stringValue: value
67
+ }
68
+ }
69
+ if (value instanceof Entity) {
70
+ return {
71
+ stringValue: value.id
72
+ }
73
+ }
74
+ return {
75
+ nullValue: RichValue_NullValue.NULL_VALUE
76
+ }
77
+ },
78
+ to(v) {
79
+ return v.stringValue as T | ID
80
+ }
81
+ }
82
+ }
83
+
84
+ /* eslint-disable */
85
+ export const StringConverter: ValueConverter<String | undefined> = {
86
+ from: (value?: String) => {
87
+ if (value == null) {
88
+ return {
89
+ nullValue: RichValue_NullValue.NULL_VALUE
90
+ }
91
+ }
92
+ return {
93
+ stringValue: value
94
+ }
95
+ },
96
+ to(v) {
97
+ return v.stringValue
98
+ }
99
+ }
100
+
101
+ export const IntConverter: ValueConverter<Int | undefined> = {
102
+ from: (value?: Int) => {
103
+ if (value == null) {
104
+ return {
105
+ nullValue: RichValue_NullValue.NULL_VALUE
106
+ }
107
+ }
108
+ return {
109
+ intValue: value
110
+ }
111
+ },
112
+ to(v) {
113
+ return v.intValue
114
+ }
115
+ }
116
+
117
+ export const FloatConverter: ValueConverter<Float | undefined> = {
118
+ from: (value?: Float) => {
119
+ if (value == null) {
120
+ return {
121
+ nullValue: RichValue_NullValue.NULL_VALUE
122
+ }
123
+ }
124
+ return {
125
+ floatValue: value
126
+ }
127
+ },
128
+ to(v) {
129
+ return v.floatValue
130
+ }
131
+ }
132
+
133
+ export const BooleanConverter: ValueConverter<Boolean | undefined> = {
134
+ from: (value?: Boolean) => {
135
+ if (value == null) {
136
+ return {
137
+ nullValue: RichValue_NullValue.NULL_VALUE
138
+ }
139
+ }
140
+ return {
141
+ boolValue: value
142
+ }
143
+ },
144
+ to(v) {
145
+ return v.boolValue
146
+ }
147
+ }
148
+
149
+ export const DateTimeConverter: ValueConverter<DateTime | undefined> = {
150
+ from: (value: DateTime | undefined) => {
151
+ if (value == null) {
152
+ return {
153
+ nullValue: RichValue_NullValue.NULL_VALUE
154
+ }
155
+ }
156
+ return {
157
+ timestampValue: value
158
+ }
159
+ },
160
+ to(v) {
161
+ return v.timestampValue
162
+ }
163
+ }
164
+
165
+ export const BytesConverter: ValueConverter<Bytes | undefined> = {
166
+ from: (value?: Bytes) => {
167
+ if (value == null) {
168
+ return {
169
+ nullValue: RichValue_NullValue.NULL_VALUE
170
+ }
171
+ }
172
+ return {
173
+ bytesValue: value
174
+ }
175
+ },
176
+ to(v) {
177
+ return v.bytesValue
178
+ }
179
+ }
180
+
181
+ export const IDConverter: ValueConverter<ID | undefined> = StringConverter
182
+
183
+ export const BigDecimalConverter: ValueConverter<BigDecimal | undefined> = {
184
+ from: (value?: BigDecimal): RichValue => {
185
+ if (value == null) {
186
+ return {
187
+ nullValue: RichValue_NullValue.NULL_VALUE
188
+ }
189
+ }
190
+
191
+ const exp = value.decimalPlaces() ?? 0
192
+ const s = value.multipliedBy(new BigDecimal(10).pow(exp)).toFixed()
193
+
194
+ return {
195
+ bigdecimalValue: {
196
+ value: toBigInteger(BigInt(s)),
197
+ exp: exp
198
+ }
199
+ }
200
+ },
201
+ to(v) {
202
+ const d = v.bigdecimalValue
203
+ if (d) {
204
+ const i = bytesToBigInt(d.value!.data)
205
+ return new BigDecimal(`${i}`).dividedBy(new BigDecimal(10).pow(d.exp)).multipliedBy(d.value?.negative ? -1 : 1)
206
+ }
207
+ return undefined
208
+ }
209
+ }
210
+
211
+ export const BigIntConverter: ValueConverter<bigint | undefined> = {
212
+ from: (value?: bigint) => {
213
+ if (value == null) {
214
+ return {
215
+ nullValue: RichValue_NullValue.NULL_VALUE
216
+ }
217
+ }
218
+ return {
219
+ bigintValue: toBigInteger(value)
220
+ }
221
+ },
222
+ to(v) {
223
+ if (v.bigintValue) {
224
+ let res = bytesToBigInt(v.bigintValue?.data)
225
+ if (v.bigintValue.negative) {
226
+ res = -res
227
+ }
228
+ return res
229
+ }
230
+ return undefined
231
+ }
232
+ }
233
+
234
+ export class StructConverter<T extends Entity> {
235
+ constructor(private converters: Record<string, ValueConverter<any>>) {}
236
+
237
+ from(data: any): RichStruct {
238
+ const fields: Record<string, RichValue> = {}
239
+ for (const [field, value] of Object.entries(data)) {
240
+ if (this.converters[field] !== undefined) {
241
+ fields[field] = this.converters[field].from(value)
242
+ }
243
+ }
244
+ return { fields }
245
+ }
246
+ }
247
+
248
+ export function bytesToBigInt(bytes: Uint8Array) {
249
+ let intValue = BigInt(0)
250
+ for (let i = 0; i < bytes.length; i++) {
251
+ intValue = intValue * BigInt(256) + BigInt(bytes[i])
252
+ }
253
+ return intValue
254
+ }
255
+
256
+ export function toBigInteger(a: bigint) {
257
+ const negative = a < 0
258
+ if (negative) {
259
+ a = -a
260
+ }
261
+ let hex = a.toString(16)
262
+ if (hex.length % 2 === 1) {
263
+ hex = '0' + hex
264
+ }
265
+ const buffer = Buffer.from(hex, 'hex')
266
+
267
+ return {
268
+ negative: negative,
269
+ data: new Uint8Array(buffer)
270
+ }
271
+ }
@@ -1,6 +1,8 @@
1
1
  import { ID } from './types.js'
2
2
  import { PluginManager } from '@sentio/runtime'
3
3
  import { Store } from './store.js'
4
+ import { RichStruct } from '@sentio/protos'
5
+ import { array_, IDConverter, required_, ValueConverter } from './convert.js'
4
6
 
5
7
  export interface EntityClass<T extends Entity> {
6
8
  new (data: any): T
@@ -8,24 +10,25 @@ export interface EntityClass<T extends Entity> {
8
10
 
9
11
  export class Entity {
10
12
  get id(): ID {
11
- return this.get('id')
13
+ return this.get('id', required_(IDConverter))
12
14
  }
13
15
 
14
- data: Record<string, any> = {}
15
- constructor(data: any) {
16
- Object.entries(data).forEach(([key, value]) => {
17
- if (Array.isArray(value)) {
18
- this.data[key] = value.map((v) => this.getIdFromEntity(v))
19
- } else {
20
- this.data[key] = this.getIdFromEntity(value)
16
+ protected fromPojo(data: any, converters: Record<string, ValueConverter<any>>) {
17
+ for (const [field, value] of Object.entries(data)) {
18
+ if (converters[field] !== undefined) {
19
+ this.set(field, value, converters[field])
21
20
  }
22
- })
21
+ }
22
+ }
23
+
24
+ constructor(private _data: RichStruct = { fields: {} }) {}
25
+
26
+ setData(data: RichStruct) {
27
+ this._data = data
23
28
  }
24
29
 
25
30
  private getIdFromEntity(entity: any): any {
26
- if (entity instanceof Entity) {
27
- return entity.id
28
- } else if (typeof entity === 'object' && entity.id) {
31
+ if (typeof entity === 'object' && entity.id) {
29
32
  return entity.id
30
33
  }
31
34
  return entity
@@ -39,26 +42,32 @@ export class Entity {
39
42
  return undefined
40
43
  }
41
44
 
42
- get<T>(field: string): T {
43
- return this.data[field]
45
+ get<T>(field: string, converter: ValueConverter<T>): T {
46
+ const value = this._data.fields[field]
47
+ return converter.to(value)
44
48
  }
45
49
 
46
- set<T>(field: string, value: T | T[] | ID | ID[]): void {
47
- if (Array.isArray(value) && value instanceof Entity) {
48
- this.data[field] = value.map((v) => (v as Entity).id)
49
- } else if (value instanceof Entity) {
50
- this.data[field] = (value as Entity).id
50
+ set<T>(field: string, value: T, serializer: ValueConverter<T>): void {
51
+ if (Array.isArray(value)) {
52
+ this._data.fields[field] = {
53
+ listValue: { values: value.map((v) => serializer.from(v)) }
54
+ }
55
+ } else {
56
+ this._data.fields[field] = serializer.from(value)
51
57
  }
52
- this.data[field] = value
53
58
  }
54
59
 
55
- protected getFieldObject<T extends Entity>(entity: EntityClass<T> | string, field: string): Promise<T | undefined> {
56
- const id = this.data[field]
60
+ protected getObject<T extends Entity>(entity: EntityClass<T> | string, field: string): Promise<T | undefined> {
61
+ const id = this.get(field, IDConverter)
57
62
  return id ? (this.getStore()?.get(entity, id) as Promise<T>) : Promise.resolve(undefined)
58
63
  }
59
64
 
65
+ protected setObject(field: string, value: any): void {
66
+ this.set(field, this.getIdFromEntity(value), IDConverter)
67
+ }
68
+
60
69
  protected getFieldObjectArray<T extends Entity>(entity: EntityClass<T>, field: string): Promise<T[]> {
61
- const ids = this.data[field]
70
+ const ids = this.get(field, array_(IDConverter))
62
71
  const promises = ids.map((id: string) => this.getStore()?.get(entity, id))
63
72
  return Promise.all(promises) as Promise<T[]>
64
73
  }
@@ -66,6 +75,10 @@ export class Entity {
66
75
  toString(): string {
67
76
  const entityName = this.constructor.prototype.entityName
68
77
  const id = this.id
69
- return `${entityName}#${id} ${JSON.stringify(this.data)}`
78
+ return `${entityName}#${id} ${JSON.stringify(this._data)}`
79
+ }
80
+
81
+ serialize(): RichStruct {
82
+ return this._data
70
83
  }
71
84
  }
@@ -3,3 +3,4 @@ export * from './types.js'
3
3
  export * from './entity.js'
4
4
  export * from './store.js'
5
5
  export * from './context.js'
6
+ export * from './convert.js'
@@ -2,6 +2,12 @@ import { Entity, EntityClass } from './entity.js'
2
2
  import { StoreContext } from './context.js'
3
3
  import { DatabaseSchema } from '../core/index.js'
4
4
  import { BigDecimal } from '@sentio/bigdecimal'
5
+ import { Bytes, DateTime, Float, ID, Int } from './types.js'
6
+ import { DBRequest_DBOperator, DBResponse } from '@sentio/protos'
7
+ import type { RichStruct, RichValue } from '@sentio/protos'
8
+ import { toBigInteger } from './convert.js'
9
+
10
+ type Value = ID | string | Int | Float | boolean | DateTime | Bytes | BigDecimal | bigint
5
11
 
6
12
  export class Store {
7
13
  constructor(private readonly context: StoreContext) {}
@@ -14,10 +20,12 @@ export class Store {
14
20
  }
15
21
  })
16
22
 
17
- const data = (await promise) as any
18
- if (data?.['id'] != null) {
19
- return this.newEntity(entity, data)
23
+ const data = (await promise) as DBResponse
24
+ if (data.entities?.entities[0]) {
25
+ const entityData = data.entities.entities[0]
26
+ return this.newEntity(entity, entityData)
20
27
  }
28
+
21
29
  return undefined
22
30
  }
23
31
 
@@ -43,65 +51,127 @@ export class Store {
43
51
  const promise = this.context.sendRequest({
44
52
  upsert: {
45
53
  entity: entities.map((e) => e.constructor.prototype.entityName),
46
- data: entities.map((e) => serialize(e.data)),
47
- id: entities.map((e) => e.id)
54
+ // data: entities.map((e) => serialize(e.data)),
55
+ id: entities.map((e) => e.id),
56
+ entityData: entities.map((e) => e.serialize())
48
57
  }
49
58
  })
50
59
 
51
60
  await promise
52
61
  }
53
62
 
54
- async list<T extends Entity>(entity: EntityClass<T>, limit?: number, offset?: number): Promise<T[]> {
55
- const promise = this.context.sendRequest({
56
- list: {
57
- entity: entity.prototype.entityName,
58
- limit,
59
- offset
60
- }
61
- })
63
+ async *list<T extends Entity>(entity: EntityClass<T>, filters?: ListFilter<T>[]) {
64
+ let cursor: string | undefined = undefined
62
65
 
63
- const list = (await promise) as any[]
64
- return list.map((data) => {
65
- return this.newEntity(entity, data)
66
- })
66
+ while (true) {
67
+ const promise = this.context.sendRequest({
68
+ list: {
69
+ entity: entity.prototype.entityName,
70
+ cursor,
71
+ filters:
72
+ filters?.map((f) => ({
73
+ field: f.field as string,
74
+ op: ops[f.op],
75
+ value: { values: Array.isArray(f.value) ? f.value.map((v) => serialize(v)) : [serialize(f.value)] }
76
+ })) || []
77
+ }
78
+ })
79
+ const response = (await promise) as DBResponse
80
+ for (const data of response.entities?.entities || []) {
81
+ yield this.newEntity(entity, data)
82
+ }
83
+ if (!response.nextCursor) {
84
+ break
85
+ }
86
+ cursor = response.nextCursor
87
+ }
67
88
  }
68
89
 
69
- private newEntity<T extends Entity>(entity: EntityClass<T> | string, data: any) {
90
+ private newEntity<T extends Entity>(entity: EntityClass<T> | string, data: RichStruct) {
70
91
  if (typeof entity == 'string') {
71
92
  const en = DatabaseSchema.findEntity(entity)
72
93
  if (!en) {
73
94
  // it is an interface
74
- return new Entity(data) as T
95
+ return new Entity() as T
75
96
  }
76
97
  entity = en
77
98
  }
78
99
 
79
- return new (entity as EntityClass<T>)(data)
100
+ const res = new (entity as EntityClass<T>)({}) as T
101
+ res.setData(data)
102
+ return res
80
103
  }
81
104
  }
82
105
 
83
- function serialize(data: Record<string, any>) {
84
- const ret: Record<string, any> = {}
85
- for (const [k, v] of Object.entries(data)) {
86
- if (v instanceof Entity) {
87
- ret[k] = v.id
88
- } else if (Array.isArray(v) && v[0] instanceof Entity) {
89
- ret[k] = v.map((e) => e.id)
90
- } else if (typeof v === 'bigint') {
91
- ret[k] = v.toString()
92
- } else if (typeof v === 'object') {
93
- if (v instanceof Date) {
94
- ret[k] = v.toISOString()
95
- } else if (v instanceof Uint8Array) {
96
- ret[k] = Buffer.from(v).toString('hex')
97
- } else if (v instanceof BigDecimal) {
98
- ret[k] = v.toString()
99
- } else {
100
- ret[k] = serialize(v)
101
- }
102
- } else {
103
- ret[k] = v
106
+ export type Operators = '=' | '!=' | '<' | '<=' | '>' | '>=' | 'in' | 'not in'
107
+
108
+ export interface ListFilter<T extends Entity> {
109
+ field: keyof T
110
+ op: Operators
111
+ value: Value | Value[] | null
112
+ }
113
+
114
+ export interface ListOptions<T extends Entity> {
115
+ cursor: string
116
+ }
117
+
118
+ const ops: Record<Operators, DBRequest_DBOperator> = {
119
+ '=': DBRequest_DBOperator.EQ,
120
+ '!=': DBRequest_DBOperator.NE,
121
+ '<': DBRequest_DBOperator.LT,
122
+ '<=': DBRequest_DBOperator.LE,
123
+ '>': DBRequest_DBOperator.GT,
124
+ '>=': DBRequest_DBOperator.GE,
125
+ in: DBRequest_DBOperator.IN,
126
+ 'not in': DBRequest_DBOperator.NOT_IN
127
+ }
128
+
129
+ function serialize(v: any): RichValue {
130
+ if (v == null) {
131
+ return { nullValue: 0 }
132
+ }
133
+ if (typeof v == 'boolean') {
134
+ return { boolValue: v }
135
+ }
136
+ if (typeof v == 'string') {
137
+ return { stringValue: v }
138
+ }
139
+
140
+ if (typeof v == 'number') {
141
+ return { floatValue: v }
142
+ }
143
+ if (typeof v == 'bigint') {
144
+ return {
145
+ bigintValue: toBigInteger(v)
146
+ }
147
+ }
148
+
149
+ if (v instanceof BigDecimal) {
150
+ return serializeBigDecimal(v)
151
+ }
152
+
153
+ if (v instanceof Date) {
154
+ return {
155
+ timestampValue: v
104
156
  }
105
157
  }
106
- return ret
158
+
159
+ if (v instanceof Uint8Array) {
160
+ return { bytesValue: v }
161
+ }
162
+
163
+ if (Array.isArray(v)) {
164
+ return {
165
+ listValue: { values: v.map((v) => serialize(v)) }
166
+ }
167
+ }
168
+ return {
169
+ nullValue: 0
170
+ }
171
+ }
172
+
173
+ function serializeBigDecimal(v: BigDecimal): RichValue {
174
+ return {
175
+ bigdecimalValue: undefined
176
+ }
107
177
  }
@@ -1,10 +1,7 @@
1
- export type { BigDecimal } from '@sentio/bigdecimal'
2
-
3
1
  export type ID = string
4
2
  export type String = string
5
3
  export type Int = number
6
4
  export type Float = number
7
5
  export type Boolean = boolean
8
6
  export type DateTime = Date
9
- export type Json = any
10
7
  export type Bytes = Uint8Array