@xyo-network/archivist 2.33.15 → 2.35.0

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.
Files changed (60) hide show
  1. package/dist/cjs/Archivist.d.ts +1 -1
  2. package/dist/cjs/Archivist.d.ts.map +1 -1
  3. package/dist/cjs/Queries/Insert.d.ts +1 -2
  4. package/dist/cjs/Queries/Insert.d.ts.map +1 -1
  5. package/dist/cjs/Queries/Insert.js.map +1 -1
  6. package/dist/cjs/XyoArchivist.d.ts +2 -2
  7. package/dist/cjs/XyoArchivist.d.ts.map +1 -1
  8. package/dist/cjs/XyoArchivist.js +26 -16
  9. package/dist/cjs/XyoArchivist.js.map +1 -1
  10. package/dist/cjs/XyoArchivistWrapper.d.ts +1 -1
  11. package/dist/cjs/XyoArchivistWrapper.d.ts.map +1 -1
  12. package/dist/cjs/XyoArchivistWrapper.js +21 -8
  13. package/dist/cjs/XyoArchivistWrapper.js.map +1 -1
  14. package/dist/cjs/XyoCookieArchivist.d.ts +1 -1
  15. package/dist/cjs/XyoCookieArchivist.d.ts.map +1 -1
  16. package/dist/cjs/XyoCookieArchivist.js +11 -5
  17. package/dist/cjs/XyoCookieArchivist.js.map +1 -1
  18. package/dist/cjs/XyoMemoryArchivist.d.ts +1 -1
  19. package/dist/cjs/XyoMemoryArchivist.d.ts.map +1 -1
  20. package/dist/cjs/XyoMemoryArchivist.js +11 -5
  21. package/dist/cjs/XyoMemoryArchivist.js.map +1 -1
  22. package/dist/cjs/XyoStorageArchivist.d.ts +1 -1
  23. package/dist/cjs/XyoStorageArchivist.d.ts.map +1 -1
  24. package/dist/cjs/XyoStorageArchivist.js +11 -5
  25. package/dist/cjs/XyoStorageArchivist.js.map +1 -1
  26. package/dist/docs.json +8808 -3428
  27. package/dist/esm/Archivist.d.ts +1 -1
  28. package/dist/esm/Archivist.d.ts.map +1 -1
  29. package/dist/esm/Queries/Insert.d.ts +1 -2
  30. package/dist/esm/Queries/Insert.d.ts.map +1 -1
  31. package/dist/esm/Queries/Insert.js.map +1 -1
  32. package/dist/esm/XyoArchivist.d.ts +2 -2
  33. package/dist/esm/XyoArchivist.d.ts.map +1 -1
  34. package/dist/esm/XyoArchivist.js +25 -16
  35. package/dist/esm/XyoArchivist.js.map +1 -1
  36. package/dist/esm/XyoArchivistWrapper.d.ts +1 -1
  37. package/dist/esm/XyoArchivistWrapper.d.ts.map +1 -1
  38. package/dist/esm/XyoArchivistWrapper.js +21 -8
  39. package/dist/esm/XyoArchivistWrapper.js.map +1 -1
  40. package/dist/esm/XyoCookieArchivist.d.ts +1 -1
  41. package/dist/esm/XyoCookieArchivist.d.ts.map +1 -1
  42. package/dist/esm/XyoCookieArchivist.js +10 -5
  43. package/dist/esm/XyoCookieArchivist.js.map +1 -1
  44. package/dist/esm/XyoMemoryArchivist.d.ts +1 -1
  45. package/dist/esm/XyoMemoryArchivist.d.ts.map +1 -1
  46. package/dist/esm/XyoMemoryArchivist.js +10 -5
  47. package/dist/esm/XyoMemoryArchivist.js.map +1 -1
  48. package/dist/esm/XyoStorageArchivist.d.ts +1 -1
  49. package/dist/esm/XyoStorageArchivist.d.ts.map +1 -1
  50. package/dist/esm/XyoStorageArchivist.js +10 -5
  51. package/dist/esm/XyoStorageArchivist.js.map +1 -1
  52. package/package.json +6 -6
  53. package/src/Archivist.ts +1 -1
  54. package/src/Queries/Insert.ts +1 -2
  55. package/src/XyoArchivist.ts +32 -17
  56. package/src/XyoArchivistWrapper.ts +25 -10
  57. package/src/XyoCookieArchivist.ts +16 -9
  58. package/src/XyoMemoryArchivist.ts +16 -9
  59. package/src/XyoStorageArchivist.ts +16 -9
  60. package/src/test.spec.test/testArchivist.ts +6 -4
@@ -52,7 +52,7 @@ export abstract class XyoArchivist<TConfig extends XyoPayload = XyoPayload>
52
52
  throw Error('Not implemented')
53
53
  }
54
54
 
55
- public commit(): Promisable<XyoBoundWitness> {
55
+ public commit(): Promisable<XyoBoundWitness[]> {
56
56
  throw Error('Not implemented')
57
57
  }
58
58
 
@@ -68,40 +68,50 @@ export abstract class XyoArchivist<TConfig extends XyoPayload = XyoPayload>
68
68
 
69
69
  abstract insert(item: XyoPayload[]): Promisable<XyoBoundWitness>
70
70
 
71
- override async query<T extends XyoQuery = XyoQuery>(query: T): Promise<XyoModuleQueryResult<XyoPayload>> {
72
- if (!this.queries().find((schema) => schema === query.schema)) {
73
- console.error(`Undeclared Module Query: ${query.schema}`)
74
- }
71
+ override async query<T extends XyoQuery = XyoQuery>(
72
+ bw: XyoBoundWitness,
73
+ query: T,
74
+ payloads?: XyoPayload[],
75
+ ): Promise<XyoModuleQueryResult<XyoPayload>> {
76
+ assertEx(this.queryable(query.schema, bw.addresses))
75
77
 
76
- const payloads: (XyoPayload | null)[] = []
78
+ const result: (XyoPayload | null)[] = []
77
79
  const queryAccount = new XyoAccount()
78
80
  const typedQuery = query as XyoArchivistQuery
79
81
  switch (typedQuery.schema) {
80
82
  case XyoArchivistAllQuerySchema:
81
- payloads.push(...(await this.all()))
83
+ result.push(...(await this.all()))
82
84
  break
83
85
  case XyoArchivistClearQuerySchema:
84
86
  await this.clear()
85
87
  break
86
88
  case XyoArchivistCommitQuerySchema:
87
- payloads.push(await this.commit())
89
+ result.push(...(await this.commit()))
88
90
  break
89
91
  case XyoArchivistDeleteQuerySchema:
90
92
  await this.delete(typedQuery.hashes)
91
93
  break
92
94
  case XyoArchivistFindQuerySchema:
93
- payloads.push(...(await this.find(typedQuery.filter)))
95
+ result.push(...(await this.find(typedQuery.filter)))
94
96
  break
95
97
  case XyoArchivistGetQuerySchema:
96
- payloads.push(...(await this.get(typedQuery.hashes)))
98
+ result.push(...(await this.get(typedQuery.hashes)))
97
99
  break
98
- case XyoArchivistInsertQuerySchema:
99
- payloads.push(await this.insert(typedQuery.payloads), ...typedQuery.payloads)
100
+ case XyoArchivistInsertQuerySchema: {
101
+ const actualHashes = payloads?.map((payload) => PayloadWrapper.hash(payload))
102
+ const resolvedPayloads = compact(
103
+ typedQuery.payloads.map((hash) => {
104
+ const index = actualHashes?.indexOf(hash)
105
+ return index !== undefined ? (index > -1 ? payloads?.[index] ?? null : null) : null
106
+ }),
107
+ )
108
+ result.push(await this.insert(resolvedPayloads))
100
109
  break
110
+ }
101
111
  default:
102
- return super.query(typedQuery)
112
+ return super.query(bw, typedQuery)
103
113
  }
104
- return this.bindPayloads(payloads, queryAccount)
114
+ return this.bindPayloads(result, queryAccount)
105
115
  }
106
116
 
107
117
  private resolveArchivists(archivists?: Record<string, PayloadArchivist | null | undefined>) {
@@ -119,7 +129,8 @@ export abstract class XyoArchivist<TConfig extends XyoPayload = XyoPayload>
119
129
  await Promise.all(
120
130
  Object.values(this.parents?.read ?? {}).map(async (parent) => {
121
131
  const query: XyoArchivistGetQuery = { hashes: [hash], schema: XyoArchivistGetQuerySchema }
122
- const [, payloads] = (await parent?.query(query)) ?? []
132
+ const bw = (await this.bindPayloads([query]))[0]
133
+ const [, payloads] = (await parent?.query(bw, query)) ?? []
123
134
  const wrapper = payloads?.[0] ? new PayloadWrapper(payloads?.[0]) : undefined
124
135
  if (wrapper && wrapper.hash !== hash) {
125
136
  console.warn(`Parent [${parent?.address}] returned payload with invalid hash [${hash} != ${wrapper.hash}]`)
@@ -132,8 +143,12 @@ export abstract class XyoArchivist<TConfig extends XyoPayload = XyoPayload>
132
143
  }
133
144
 
134
145
  protected async writeToParent(parent: PayloadArchivist, payloads: XyoPayload[]) {
135
- const query: XyoArchivistInsertQuery = { payloads, schema: XyoArchivistInsertQuerySchema }
136
- const [, writtenPayloads] = (await parent?.query(query)) ?? []
146
+ const query: XyoArchivistInsertQuery = {
147
+ payloads: payloads.map((payload) => PayloadWrapper.hash(payload)),
148
+ schema: XyoArchivistInsertQuerySchema,
149
+ }
150
+ const bw = (await this.bindPayloads([query, ...payloads]))[0]
151
+ const [, writtenPayloads] = (await parent?.query(bw, query, payloads)) ?? []
137
152
  return writtenPayloads
138
153
  }
139
154
 
@@ -1,6 +1,6 @@
1
1
  import { XyoBoundWitness } from '@xyo-network/boundwitness'
2
2
  import { XyoModuleWrapper } from '@xyo-network/module'
3
- import { XyoPayload } from '@xyo-network/payload'
3
+ import { PayloadWrapper, XyoPayload } from '@xyo-network/payload'
4
4
 
5
5
  import { PayloadArchivist } from './Archivist'
6
6
  import {
@@ -24,36 +24,51 @@ import { XyoPayloadFindFilter } from './XyoPayloadFindFilter'
24
24
  export class XyoArchivistWrapper extends XyoModuleWrapper implements PayloadArchivist {
25
25
  public async delete(hashes: string[]) {
26
26
  const query: XyoArchivistDeleteQuery = { hashes, schema: XyoArchivistDeleteQuerySchema }
27
- return (await this.module.query(query))[0].payload_hashes.map(() => true)
27
+ const bw = (await this.bindPayloads([query]))[0]
28
+ return (await this.module.query(bw, query))[0].payload_hashes.map(() => true)
28
29
  }
29
30
 
30
31
  public async clear(): Promise<void> {
31
32
  const query: XyoArchivistClearQuery = { schema: XyoArchivistClearQuerySchema }
32
- await this.module.query(query)
33
+ const bw = (await this.bindPayloads([query]))[0]
34
+ await this.module.query(bw, query)
33
35
  }
34
36
 
35
37
  public async get(hashes: string[]): Promise<(XyoPayload | null)[]> {
36
38
  const query: XyoArchivistGetQuery = { hashes, schema: XyoArchivistGetQuerySchema }
37
- return (await this.module.query(query))[1]
39
+ const bw = (await this.bindPayloads([query]))[0]
40
+ return (await this.module.query(bw, query))[1]
38
41
  }
39
42
 
40
43
  public async insert(payloads: XyoPayload[]): Promise<XyoBoundWitness> {
41
- const query: XyoArchivistInsertQuery = { payloads, schema: XyoArchivistInsertQuerySchema }
42
- return (await this.module.query(query))[0]
44
+ const query: XyoArchivistInsertQuery = {
45
+ payloads: payloads.map((payload) => PayloadWrapper.hash(payload)),
46
+ schema: XyoArchivistInsertQuerySchema,
47
+ }
48
+
49
+ const bw = (await this.bindPayloads([query, ...payloads]))[0]
50
+ const result = await this.module.query(bw, query, payloads)
51
+
52
+ console.log(`result: ${JSON.stringify(result, null, 2)}`)
53
+
54
+ return result[1][0] as XyoBoundWitness
43
55
  }
44
56
 
45
57
  public async find(filter?: XyoPayloadFindFilter): Promise<(XyoPayload | null)[]> {
46
58
  const query: XyoArchivistFindQuery = { filter, schema: XyoArchivistFindQuerySchema }
47
- return (await this.module.query(query))[1]
59
+ const bw = (await this.bindPayloads([query]))[0]
60
+ return (await this.module.query(bw, query))[1]
48
61
  }
49
62
 
50
63
  public async all(): Promise<(XyoPayload | null)[]> {
51
64
  const query: XyoArchivistAllQuery = { schema: XyoArchivistAllQuerySchema }
52
- return (await this.module.query(query))[1]
65
+ const bw = (await this.bindPayloads([query]))[0]
66
+ return (await this.module.query(bw, query))[1]
53
67
  }
54
68
 
55
- public async commit(): Promise<XyoBoundWitness> {
69
+ public async commit(): Promise<XyoBoundWitness[]> {
56
70
  const query: XyoArchivistCommitQuery = { schema: XyoArchivistCommitQuerySchema }
57
- return (await this.module.query(query))[0]
71
+ const bw = (await this.bindPayloads([query]))[0]
72
+ return (await this.module.query(bw, query))[1] as XyoBoundWitness[]
58
73
  }
59
74
  }
@@ -74,7 +74,7 @@ export class XyoCookieArchivist extends XyoArchivist<XyoCookieArchivistConfig> {
74
74
  return `${this.namespace}-${hash}`
75
75
  }
76
76
 
77
- public delete(hashes: string[]): PromisableArray<boolean> {
77
+ public override delete(hashes: string[]): PromisableArray<boolean> {
78
78
  try {
79
79
  return hashes.map((hash) => {
80
80
  Cookies.remove(this.keyFromHash(hash))
@@ -134,7 +134,7 @@ export class XyoCookieArchivist extends XyoArchivist<XyoCookieArchivistConfig> {
134
134
  }
135
135
  }
136
136
 
137
- public async find(filter?: XyoPayloadFindFilter): Promise<XyoPayload[]> {
137
+ public override async find(filter?: XyoPayloadFindFilter): Promise<XyoPayload[]> {
138
138
  try {
139
139
  const x = (await this.all()).filter((payload) => {
140
140
  if (filter?.schema && filter.schema !== payload.schema) {
@@ -149,7 +149,7 @@ export class XyoCookieArchivist extends XyoArchivist<XyoCookieArchivistConfig> {
149
149
  }
150
150
  }
151
151
 
152
- public all(): PromisableArray<XyoPayload> {
152
+ public override all(): PromisableArray<XyoPayload> {
153
153
  try {
154
154
  return Object.entries(Cookies.get())
155
155
  .filter(([key]) => key.startsWith(`${this.namespace}-`))
@@ -160,21 +160,28 @@ export class XyoCookieArchivist extends XyoArchivist<XyoCookieArchivistConfig> {
160
160
  }
161
161
  }
162
162
 
163
- public async commit(): Promise<XyoBoundWitness> {
163
+ public override async commit(): Promise<XyoBoundWitness[]> {
164
164
  try {
165
165
  const payloads = await this.all()
166
166
  assertEx(payloads.length > 0, 'Nothing to commit')
167
- const [block] = await this.bindPayloads(payloads)
168
- await Promise.allSettled(
167
+ const settled = await Promise.allSettled(
169
168
  compact(
170
169
  Object.values(this.parents?.commit ?? [])?.map(async (parent) => {
171
- const query: XyoArchivistInsertQuery = { payloads: [block, ...payloads], schema: XyoArchivistInsertQuerySchema }
172
- return await parent?.query(query)
170
+ const query: XyoArchivistInsertQuery = {
171
+ payloads: payloads.map((payload) => PayloadWrapper.hash(payload)),
172
+ schema: XyoArchivistInsertQuerySchema,
173
+ }
174
+ const bw = (await this.bindPayloads([query]))[0]
175
+ return await parent?.query(bw, query)
173
176
  }),
174
177
  ),
175
178
  )
176
179
  await this.clear()
177
- return block
180
+ return compact(
181
+ settled.map((result) => {
182
+ return result.status === 'fulfilled' ? result.value?.[0] : null
183
+ }),
184
+ )
178
185
  } catch (ex) {
179
186
  console.error(`Error: ${JSON.stringify(ex, null, 2)}`)
180
187
  throw new CookieArchivistError('commit', ex, 'unexpected')
@@ -60,7 +60,7 @@ export class XyoMemoryArchivist extends XyoArchivist<XyoMemoryArchivistConfig> {
60
60
  this.cache = new LruCache<string, XyoPayload>({ max: this.max })
61
61
  }
62
62
 
63
- public delete(hashes: string[]): PromisableArray<boolean> {
63
+ public override delete(hashes: string[]): PromisableArray<boolean> {
64
64
  try {
65
65
  return hashes.map((hash) => {
66
66
  return this.cache.delete(hash)
@@ -116,7 +116,7 @@ export class XyoMemoryArchivist extends XyoArchivist<XyoMemoryArchivistConfig> {
116
116
  }
117
117
  }
118
118
 
119
- public find<R extends XyoPayload = XyoPayload>(filter: XyoPayloadFindFilter): PromisableArray<R> {
119
+ public override find<R extends XyoPayload = XyoPayload>(filter: XyoPayloadFindFilter): PromisableArray<R> {
120
120
  try {
121
121
  const result: R[] = []
122
122
  this.cache.forEach((value) => {
@@ -131,7 +131,7 @@ export class XyoMemoryArchivist extends XyoArchivist<XyoMemoryArchivistConfig> {
131
131
  }
132
132
  }
133
133
 
134
- public all(): PromisableArray<XyoPayload> {
134
+ public override all(): PromisableArray<XyoPayload> {
135
135
  try {
136
136
  return this.cache.dump().map((value) => value[1].value)
137
137
  } catch (ex) {
@@ -140,20 +140,27 @@ export class XyoMemoryArchivist extends XyoArchivist<XyoMemoryArchivistConfig> {
140
140
  }
141
141
  }
142
142
 
143
- public async commit(): Promise<XyoBoundWitness> {
143
+ public override async commit(): Promise<XyoBoundWitness[]> {
144
144
  try {
145
145
  const payloads = assertEx(await this.all(), 'Nothing to commit')
146
- const [block] = await this.bindPayloads(payloads)
147
- await Promise.allSettled(
146
+ const settled = await Promise.allSettled(
148
147
  compact(
149
148
  Object.values(this.parents?.commit ?? [])?.map(async (parent) => {
150
- const query: XyoArchivistInsertQuery = { payloads: [block, ...payloads], schema: XyoArchivistInsertQuerySchema }
151
- return await parent?.query(query)
149
+ const query: XyoArchivistInsertQuery = {
150
+ payloads: payloads.map((payload) => PayloadWrapper.hash(payload)),
151
+ schema: XyoArchivistInsertQuerySchema,
152
+ }
153
+ const bw = (await this.bindPayloads([query]))[0]
154
+ return await parent?.query(bw, query)
152
155
  }),
153
156
  ),
154
157
  )
155
158
  await this.clear()
156
- return block
159
+ return compact(
160
+ settled.map((result) => {
161
+ return result.status === 'fulfilled' ? result.value?.[0] : null
162
+ }),
163
+ )
157
164
  } catch (ex) {
158
165
  console.error(`Error: ${JSON.stringify(ex, null, 2)}`)
159
166
  throw new MemoryArchivistError('commit', ex, 'unexpected')
@@ -71,7 +71,7 @@ export class XyoStorageArchivist extends XyoArchivist<XyoStorageArchivistConfig>
71
71
  this.storage = store[this.type].namespace(this.namespace)
72
72
  }
73
73
 
74
- public delete(hashes: string[]): PromisableArray<boolean> {
74
+ public override delete(hashes: string[]): PromisableArray<boolean> {
75
75
  try {
76
76
  return hashes.map((hash) => {
77
77
  this.storage.remove(hash)
@@ -127,7 +127,7 @@ export class XyoStorageArchivist extends XyoArchivist<XyoStorageArchivistConfig>
127
127
  }
128
128
  }
129
129
 
130
- public async find(filter?: XyoPayloadFindFilter): Promise<XyoPayload[]> {
130
+ public override async find(filter?: XyoPayloadFindFilter): Promise<XyoPayload[]> {
131
131
  try {
132
132
  const x = (await this.all()).filter((payload) => {
133
133
  if (filter?.schema && filter.schema !== payload.schema) {
@@ -142,7 +142,7 @@ export class XyoStorageArchivist extends XyoArchivist<XyoStorageArchivistConfig>
142
142
  }
143
143
  }
144
144
 
145
- public all(): PromisableArray<XyoPayload> {
145
+ public override all(): PromisableArray<XyoPayload> {
146
146
  try {
147
147
  return Object.entries(this.storage.getAll()).map(([, value]) => value)
148
148
  } catch (ex) {
@@ -151,21 +151,28 @@ export class XyoStorageArchivist extends XyoArchivist<XyoStorageArchivistConfig>
151
151
  }
152
152
  }
153
153
 
154
- public async commit(): Promise<XyoBoundWitness> {
154
+ public override async commit(): Promise<XyoBoundWitness[]> {
155
155
  try {
156
156
  const payloads = await this.all()
157
157
  assertEx(payloads.length > 0, 'Nothing to commit')
158
- const [block] = await this.bindPayloads(payloads)
159
- await Promise.allSettled(
158
+ const settled = await Promise.allSettled(
160
159
  compact(
161
160
  Object.values(this.parents?.commit ?? [])?.map(async (parent) => {
162
- const query: XyoArchivistInsertQuery = { payloads: [block, ...payloads], schema: XyoArchivistInsertQuerySchema }
163
- return await parent?.query(query)
161
+ const query: XyoArchivistInsertQuery = {
162
+ payloads: payloads.map((payload) => PayloadWrapper.hash(payload)),
163
+ schema: XyoArchivistInsertQuerySchema,
164
+ }
165
+ const bw = (await this.bindPayloads([query]))[0]
166
+ return await parent?.query(bw, query)
164
167
  }),
165
168
  ),
166
169
  )
167
170
  await this.clear()
168
- return block
171
+ return compact(
172
+ settled.map((result) => {
173
+ return result.status === 'fulfilled' ? result.value?.[0] : null
174
+ }),
175
+ )
169
176
  } catch (ex) {
170
177
  console.error(`Error: ${JSON.stringify(ex, null, 2)}`)
171
178
  throw new StorageArchivistError('commit', ex, 'unexpected')
@@ -11,13 +11,15 @@ import { XyoArchivistWrapper } from '../XyoArchivistWrapper'
11
11
  export const testArchivistRoundTrip = (archivist: XyoArchivist, name: string) => {
12
12
  test(`XyoArchivist [${name}]`, async () => {
13
13
  const idPayload: XyoPayload<{ salt: string }> = {
14
- salt: 'test',
14
+ salt: Date.now().toString(),
15
15
  schema: 'network.xyo.id',
16
16
  }
17
17
  const payloadWrapper = new PayloadWrapper(idPayload)
18
+
18
19
  const archivistWrapper = new XyoArchivistWrapper(archivist)
19
20
  const insertResult = await archivistWrapper.insert([idPayload])
20
21
  expect(insertResult).toBeDefined()
22
+
21
23
  expect(insertResult.payload_hashes.find((hash) => hash === payloadWrapper.hash)).toBeDefined()
22
24
  const getResult = await archivistWrapper.get([payloadWrapper.hash])
23
25
  expect(getResult).toBeDefined()
@@ -33,16 +35,16 @@ export const testArchivistRoundTrip = (archivist: XyoArchivist, name: string) =>
33
35
  export const testArchivistAll = (archivist: XyoArchivist, name: string) => {
34
36
  test(`XyoArchivist [${name}]`, async () => {
35
37
  const idPayload = {
36
- salt: 'test',
38
+ salt: Date.now().toString(),
37
39
  schema: 'network.xyo.id',
38
40
  }
39
41
  const archivistWrapper = new XyoArchivistWrapper(archivist)
40
42
  for (let x = 0; x < 10; x++) {
41
- await archivistWrapper.insert([{ ...idPayload, salt: Date.now().toString() } as XyoPayload<{ salt: string }>])
43
+ await archivistWrapper.insert([idPayload])
42
44
  await delay(10)
43
45
  }
44
46
  const getResult = await archivistWrapper.all()
45
47
  expect(getResult).toBeDefined()
46
- expect(getResult.length).toBe(11)
48
+ expect(getResult.length).toBe(2)
47
49
  })
48
50
  }