diff --git a/packages/objectloader2/src/operations/__snapshots__/traverser.spec.ts.snap b/packages/objectloader2/src/operations/__snapshots__/traverser.spec.ts.snap new file mode 100644 index 000000000..e911fe867 --- /dev/null +++ b/packages/objectloader2/src/operations/__snapshots__/traverser.spec.ts.snap @@ -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", +} +`; diff --git a/packages/objectloader2/src/operations/traverser.spec.ts b/packages/objectloader2/src/operations/traverser.spec.ts new file mode 100644 index 000000000..ebce891cd --- /dev/null +++ b/packages/objectloader2/src/operations/traverser.spec.ts @@ -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() + }) +}) diff --git a/packages/objectloader2/src/operations/traverser.ts b/packages/objectloader2/src/operations/traverser.ts index bee0296eb..2c8304908 100644 --- a/packages/objectloader2/src/operations/traverser.ts +++ b/packages/objectloader2/src/operations/traverser.ts @@ -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 | undefined = undefined - let first = true + async traverse(onProgress?: OnProgress): Promise { + let firstObjectPromise: Promise | 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, onProgress: OnProgress): Promise { - const promises: Promise[] = [] - for (const arrayItem of obj) { - if (isBase(arrayItem)) { - promises.push(this.traverseBase(arrayItem, onProgress)) + async traverseArray(array: Array, onProgress?: OnProgress): Promise { + 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 { + async traverseBase(base: Base, onProgress?: OnProgress): Promise { 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[] = [] + if (base.__closure) { + const ids = Object.keys(base.__closure) + const promises: Promise[] = [] 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)[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)[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 } } diff --git a/packages/objectloader2/src/types/types.ts b/packages/objectloader2/src/types/types.ts index af3c25191..af53829c1 100644 --- a/packages/objectloader2/src/types/types.ts +++ b/packages/objectloader2/src/types/types.ts @@ -13,7 +13,12 @@ export interface Item { export interface Base { id: string speckle_type: string - referenceId?: string + __closure?: Record +} + +export interface Reference { + speckle_type: string + referencedId: string __closure?: Record } @@ -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' + ) +}