@xyo-network/payload-builder 2.88.2 → 2.89.0-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.
package/src/Builder.ts CHANGED
@@ -1,26 +1,24 @@
1
1
  import { assertEx } from '@xylabs/assert'
2
- import { deepOmitUnderscoreFields, PayloadHasher, removeEmptyFields } from '@xyo-network/hash'
3
- import { AnyObject } from '@xyo-network/object'
4
- import { Payload } from '@xyo-network/payload-model'
2
+ import { Hash } from '@xylabs/hex'
3
+ import { JsonObject } from '@xylabs/object'
4
+ import { deepOmitPrefixedFields, PayloadHasher, removeEmptyFields } from '@xyo-network/hash'
5
+ import { Payload, PayloadWithMeta, WithMeta } from '@xyo-network/payload-model'
5
6
 
6
- export interface PayloadBuilderOptions {
7
+ export interface PayloadBuilderOptions<T> {
8
+ fields?: Partial<T>
9
+ meta?: JsonObject
7
10
  schema: string
8
11
  }
9
12
 
10
- export class PayloadBuilder<T extends Payload = Payload<AnyObject>> {
11
- private _$meta?: AnyObject
12
- private _client = 'js'
13
+ export class PayloadBuilder<T extends Payload = Payload> {
14
+ private _$meta?: JsonObject
13
15
  private _fields: Partial<T> = {}
14
16
  private _schema: string
15
- private _timestamp = Date.now()
16
17
 
17
- constructor({ schema }: PayloadBuilderOptions) {
18
+ constructor({ schema, fields, meta }: PayloadBuilderOptions<T>) {
18
19
  this._schema = schema
19
- }
20
-
21
- get externalMeta() {
22
- const _hash = PayloadHasher.hashAsync(this.hashableFields)
23
- return { _client: this._client, _hash, _timestamp: this._timestamp }
20
+ this._fields = fields ?? {}
21
+ this._$meta = meta
24
22
  }
25
23
 
26
24
  get schema() {
@@ -28,31 +26,107 @@ export class PayloadBuilder<T extends Payload = Payload<AnyObject>> {
28
26
  return this._schema
29
27
  }
30
28
 
31
- $meta(fields?: AnyObject) {
32
- this._$meta = fields
33
- return this
29
+ static async build<T extends Payload>(payload: T) {
30
+ const builder = new PayloadBuilder<T>({ schema: payload.schema })
31
+ builder.fields(payload)
32
+ return await builder.build()
34
33
  }
35
34
 
36
- async build(withExternalMeta = false) {
37
- let hashableFields = this.hashableFields()
38
- if (this._$meta) {
39
- const $hash = await PayloadHasher.hashAsync(hashableFields)
40
- hashableFields = { ...hashableFields, $hash, $meta: this._$meta }
35
+ static async dataHashPairs<T extends Payload>(payloads: T[]): Promise<[WithMeta<T>, Hash][]> {
36
+ return await Promise.all(
37
+ payloads.map(async (payload) => {
38
+ const built = await PayloadBuilder.build(payload)
39
+ return [built, built.$hash]
40
+ }),
41
+ )
42
+ }
43
+
44
+ static async dataHashes<T extends Payload>(payloads: T[]): Promise<Hash[]> {
45
+ return await Promise.all(
46
+ payloads.map(async (payload) => {
47
+ const built = await PayloadBuilder.build(payload)
48
+ return built.$hash
49
+ }),
50
+ )
51
+ }
52
+
53
+ static async filterExclude<T extends Payload>(payloads: T[] = [], hash: Hash[] | Hash): Promise<T[]> {
54
+ const hashes = Array.isArray(hash) ? hash : [hash]
55
+ return (await this.hashPairs(payloads)).filter(([_, objHash]) => !hashes.includes(objHash))?.map((pair) => pair[0])
56
+ }
57
+
58
+ static async filterInclude<T extends Payload>(payloads: T[] = [], hash: Hash[] | Hash): Promise<T[]> {
59
+ const hashes = Array.isArray(hash) ? hash : [hash]
60
+ return (await this.hashPairs(payloads)).filter(([_, objHash]) => hashes.includes(objHash))?.map((pair) => pair[0])
61
+ }
62
+
63
+ static async find<T extends Payload>(payloads: T[] = [], hash: Hash): Promise<T | undefined> {
64
+ return (await this.hashPairs(payloads)).find(([_, objHash]) => objHash === hash)?.[0]
65
+ }
66
+
67
+ /**
68
+ * Creates an array of payload/hash tuples based on the payloads passed in
69
+ * @param objs Any array of payloads
70
+ * @returns An array of payload/hash tuples
71
+ */
72
+ static async hashPairs<T extends Payload>(payloads: T[]): Promise<[WithMeta<T>, Hash][]> {
73
+ return await Promise.all(
74
+ payloads.map<Promise<[WithMeta<T>, Hash]>>(async (payload) => {
75
+ const built = await PayloadBuilder.build(payload)
76
+ return [built, await PayloadHasher.hashAsync(built)]
77
+ }),
78
+ )
79
+ }
80
+
81
+ static async toDataHashMap<T extends Payload>(objs: T[]): Promise<Record<Hash, T>> {
82
+ const result: Record<Hash, T> = {}
83
+ for (const pair of await this.dataHashPairs(objs)) {
84
+ result[pair[1]] = pair[0]
41
85
  }
42
- return withExternalMeta ? { ...hashableFields, ...this.externalMeta } : hashableFields
86
+ return result
43
87
  }
44
88
 
45
- fields(fields?: Partial<T>) {
46
- if (fields) {
47
- this._fields = { ...this._fields, ...removeEmptyFields(fields) }
89
+ /**
90
+ * Creates an object map of payload hashes to payloads based on the payloads passed in
91
+ * @param objs Any array of payloads
92
+ * @returns A map of hashes to payloads
93
+ */
94
+ static async toHashMap<T extends Payload>(objs: T[]): Promise<Record<Hash, T>> {
95
+ const result: Record<Hash, T> = {}
96
+ for (const pair of await this.hashPairs(objs)) {
97
+ result[pair[1]] = pair[0]
48
98
  }
99
+ return result
100
+ }
101
+
102
+ $meta(fields?: JsonObject) {
103
+ this._$meta = fields
49
104
  return this
50
105
  }
51
106
 
52
- hashableFields() {
107
+ async build(): Promise<WithMeta<T>> {
108
+ const dataHashableFields = this.dataHashableFields()
109
+ const $hash = await PayloadHasher.hashAsync(dataHashableFields)
110
+ const hashableFields: PayloadWithMeta = { ...dataHashableFields, $hash }
111
+
112
+ //only add $meta if it exists and has at least one field
113
+ if (this._$meta && Object.keys(this._$meta).length > 0) {
114
+ hashableFields['$meta'] = this._$meta
115
+ }
116
+ return hashableFields as WithMeta<T>
117
+ }
118
+
119
+ dataHashableFields() {
53
120
  return {
54
- ...removeEmptyFields(deepOmitUnderscoreFields(this._fields)),
121
+ ...removeEmptyFields(deepOmitPrefixedFields(deepOmitPrefixedFields(this._fields, '$'), '_')),
55
122
  schema: assertEx(this.schema, 'Payload: Missing Schema'),
56
123
  } as T
57
124
  }
125
+
126
+ fields(fields?: Partial<T>) {
127
+ if (fields) {
128
+ this._fields = { ...this._fields, ...removeEmptyFields(fields) }
129
+ }
130
+ return this
131
+ }
58
132
  }