add traverse test
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Traverser > root and two children with referenceId 1`] = `
|
||||
{
|
||||
"applicationId": "1",
|
||||
"arr": null,
|
||||
"attachedProp": null,
|
||||
"crazyProp": null,
|
||||
"detachedProp": null,
|
||||
"detachedProp2": null,
|
||||
"dynamicProp": 123,
|
||||
"id": "efeadaca70a85ae6d3acfc93a8b380db",
|
||||
"list": [
|
||||
{
|
||||
"applicationId": null,
|
||||
"data": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
],
|
||||
"id": "0e61e61edee00404ec6e0f9f594bce24",
|
||||
"speckle_type": "Speckle.Core.Models.DataChunk",
|
||||
},
|
||||
],
|
||||
"list2": [
|
||||
{
|
||||
"applicationId": null,
|
||||
"data": [
|
||||
1,
|
||||
10,
|
||||
],
|
||||
"id": "f70738e3e3e593ac11099a6ed6b71154",
|
||||
"speckle_type": "Speckle.Core.Models.DataChunk",
|
||||
},
|
||||
],
|
||||
"speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase2",
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { Base } from '../types/types.js'
|
||||
import ObjectLoader2 from './objectLoader2.js'
|
||||
import Traverser from './traverser.js'
|
||||
|
||||
describe('Traverser', () => {
|
||||
test('root and two children with referenceId', async () => {
|
||||
const root = `{
|
||||
"list": [{
|
||||
"speckle_type": "reference",
|
||||
"referencedId": "0e61e61edee00404ec6e0f9f594bce24",
|
||||
"__closure": null
|
||||
}],
|
||||
"list2": [{
|
||||
"speckle_type": "reference",
|
||||
"referencedId": "f70738e3e3e593ac11099a6ed6b71154",
|
||||
"__closure": null
|
||||
}],
|
||||
"arr": null,
|
||||
"detachedProp": null,
|
||||
"detachedProp2": null,
|
||||
"attachedProp": null,
|
||||
"crazyProp": null,
|
||||
"applicationId": "1",
|
||||
"speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase2",
|
||||
"dynamicProp": 123,
|
||||
"id": "efeadaca70a85ae6d3acfc93a8b380db",
|
||||
"__closure": {
|
||||
"0e61e61edee00404ec6e0f9f594bce24": 100,
|
||||
"f70738e3e3e593ac11099a6ed6b71154": 100
|
||||
}
|
||||
}`
|
||||
|
||||
const list1 = `{
|
||||
"data": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
|
||||
"applicationId": null,
|
||||
"speckle_type": "Speckle.Core.Models.DataChunk",
|
||||
"id": "0e61e61edee00404ec6e0f9f594bce24"
|
||||
}`
|
||||
|
||||
const list2 = `{
|
||||
"data": [1.0, 10.0],
|
||||
"applicationId": null,
|
||||
"speckle_type": "Speckle.Core.Models.DataChunk",
|
||||
"id": "f70738e3e3e593ac11099a6ed6b71154"
|
||||
}`
|
||||
|
||||
const rootObj = JSON.parse(root) as Base
|
||||
const list1Obj = JSON.parse(list1) as Base
|
||||
const list2Obj = JSON.parse(list2) as Base
|
||||
|
||||
const loader = ObjectLoader2.createFromObjects([rootObj, list1Obj, list2Obj])
|
||||
|
||||
const traverser = new Traverser(loader)
|
||||
const r = await traverser.traverse()
|
||||
expect(r).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Base, DataChunk, isBase } from '../types/types.js'
|
||||
import { Base, DataChunk, isBase, isReference, isScalar } from '../types/types.js'
|
||||
import ObjectLoader2 from './objectLoader2.js'
|
||||
|
||||
export type ProgressStage = 'download' | 'construction'
|
||||
@@ -24,38 +24,43 @@ export default class Traverser {
|
||||
this.#loader = loader
|
||||
}
|
||||
|
||||
async getAndConstructObject(onProgress: OnProgress) {
|
||||
let firstObjectPromise: Promise<void> | undefined = undefined
|
||||
let first = true
|
||||
async traverse(onProgress?: OnProgress): Promise<Base> {
|
||||
let firstObjectPromise: Promise<Base> | undefined = undefined
|
||||
for await (const obj of this.#loader.getObjectIterator()) {
|
||||
if (first) {
|
||||
if (!firstObjectPromise) {
|
||||
firstObjectPromise = this.traverseBase(obj, onProgress)
|
||||
first = false
|
||||
}
|
||||
}
|
||||
|
||||
if (firstObjectPromise) {
|
||||
await firstObjectPromise
|
||||
return await firstObjectPromise
|
||||
} else {
|
||||
throw new Error('No objects found')
|
||||
}
|
||||
}
|
||||
|
||||
async traverseArray(obj: Array<unknown>, onProgress: OnProgress): Promise<void> {
|
||||
const promises: Promise<void>[] = []
|
||||
for (const arrayItem of obj) {
|
||||
if (isBase(arrayItem)) {
|
||||
promises.push(this.traverseBase(arrayItem, onProgress))
|
||||
async traverseArray(array: Array<unknown>, onProgress?: OnProgress): Promise<void> {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const prop = array[i]
|
||||
if (isScalar(prop)) continue
|
||||
if (isBase(prop)) {
|
||||
array[i] = await this.traverseBase(prop, onProgress)
|
||||
} else if (isReference(prop)) {
|
||||
array[i] = await this.traverseBase(
|
||||
await this.#loader.getObject({ id: prop.referencedId }),
|
||||
onProgress
|
||||
)
|
||||
}
|
||||
}
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
async traverseBase(obj: Base, onProgress: OnProgress): Promise<void> {
|
||||
async traverseBase(base: Base, onProgress?: OnProgress): Promise<Base> {
|
||||
for (const ignoredProp of this.#options.excludeProps || []) {
|
||||
delete (obj as never)[ignoredProp]
|
||||
delete (base as never)[ignoredProp]
|
||||
}
|
||||
if (obj.__closure) {
|
||||
const ids = Object.keys(obj.__closure)
|
||||
const promises: Promise<void>[] = []
|
||||
if (base.__closure) {
|
||||
const ids = Object.keys(base.__closure)
|
||||
const promises: Promise<Base>[] = []
|
||||
for (const id of ids) {
|
||||
promises.push(
|
||||
this.traverseBase(await this.#loader.getObject({ id }), onProgress)
|
||||
@@ -63,40 +68,45 @@ export default class Traverser {
|
||||
}
|
||||
await Promise.all(promises)
|
||||
}
|
||||
if (obj.referenceId) {
|
||||
await this.traverseBase(
|
||||
await this.#loader.getObject({ id: obj.referenceId }),
|
||||
onProgress
|
||||
)
|
||||
}
|
||||
delete (base as never)['__closure']
|
||||
|
||||
// De-chunk
|
||||
if (obj.speckle_type?.includes('DataChunk')) {
|
||||
const chunk = obj as DataChunk
|
||||
if (base.speckle_type?.includes('DataChunk')) {
|
||||
const chunk = base as DataChunk
|
||||
if (chunk.data) {
|
||||
await this.traverseArray(chunk.data, onProgress)
|
||||
}
|
||||
}
|
||||
|
||||
//other props
|
||||
for (const prop in obj) {
|
||||
for (const prop in base) {
|
||||
if (prop === '__closure') continue
|
||||
if (prop === 'referenceId') continue
|
||||
if (prop === 'speckle_type') continue
|
||||
const objProp = (obj as unknown as Record<string, unknown>)[prop]
|
||||
if (isBase(objProp)) {
|
||||
await this.traverseBase(objProp, onProgress)
|
||||
}
|
||||
if (Array.isArray(objProp)) {
|
||||
await this.traverseArray(objProp, onProgress)
|
||||
if (prop === 'data') continue
|
||||
const baseProp = (base as unknown as Record<string, unknown>)[prop]
|
||||
if (isScalar(baseProp)) continue
|
||||
if (isBase(baseProp)) {
|
||||
await this.traverseBase(baseProp, onProgress)
|
||||
} else if (isReference(baseProp)) {
|
||||
await this.traverseBase(
|
||||
await this.#loader.getObject({ id: baseProp.referencedId }),
|
||||
onProgress
|
||||
)
|
||||
} else if (Array.isArray(baseProp)) {
|
||||
await this.traverseArray(baseProp, onProgress)
|
||||
}
|
||||
}
|
||||
onProgress({
|
||||
stage: 'construction',
|
||||
current:
|
||||
++this.#traversedReferencesCount > this.#totalChildrenCount
|
||||
? this.#totalChildrenCount
|
||||
: this.#traversedReferencesCount,
|
||||
total: this.#totalChildrenCount
|
||||
})
|
||||
if (onProgress) {
|
||||
onProgress({
|
||||
stage: 'construction',
|
||||
current:
|
||||
++this.#traversedReferencesCount > this.#totalChildrenCount
|
||||
? this.#totalChildrenCount
|
||||
: this.#traversedReferencesCount,
|
||||
total: this.#totalChildrenCount
|
||||
})
|
||||
}
|
||||
return base
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,12 @@ export interface Item {
|
||||
export interface Base {
|
||||
id: string
|
||||
speckle_type: string
|
||||
referenceId?: string
|
||||
__closure?: Record<string, number>
|
||||
}
|
||||
|
||||
export interface Reference {
|
||||
speckle_type: string
|
||||
referencedId: string
|
||||
__closure?: Record<string, number>
|
||||
}
|
||||
|
||||
@@ -29,3 +34,27 @@ export function isBase(maybeBase?: unknown): maybeBase is Base {
|
||||
typeof maybeBase.id === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
export function isReference(maybeRef?: unknown): maybeRef is Reference {
|
||||
return (
|
||||
maybeRef !== null &&
|
||||
typeof maybeRef === 'object' &&
|
||||
'referencedId' in maybeRef &&
|
||||
typeof maybeRef.referencedId === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
export function isScalar(
|
||||
value: unknown
|
||||
): value is string | number | boolean | bigint | symbol | undefined {
|
||||
const type = typeof value
|
||||
return (
|
||||
value === null ||
|
||||
type === 'string' ||
|
||||
type === 'number' ||
|
||||
type === 'boolean' ||
|
||||
type === 'bigint' ||
|
||||
type === 'symbol' ||
|
||||
type === 'undefined'
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user