@speckle/objectloader 2.9.0 → 2.9.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/.eslintrc.cjs +5 -3
- package/dist/objectloader.esm.js +1 -1
- package/dist/objectloader.js +1 -1
- package/examples/node/script.mjs +4 -2
- package/package.json +5 -3
- package/readme.md +8 -2
- package/src/errors/index.js +26 -0
- package/src/helpers/stream.js +18 -0
- package/src/index.js +77 -26
- package/types/index.d.ts +49 -0
package/.eslintrc.cjs
CHANGED
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
/** @type {import("eslint").Linter.Config} */
|
|
7
7
|
const config = {
|
|
8
8
|
env: {
|
|
9
|
-
browser: true
|
|
9
|
+
browser: true,
|
|
10
|
+
node: true
|
|
10
11
|
},
|
|
11
12
|
parserOptions: {
|
|
12
|
-
sourceType: 'module'
|
|
13
|
+
sourceType: 'module',
|
|
14
|
+
ecmaVersion: 2022
|
|
13
15
|
},
|
|
14
|
-
ignorePatterns: ['examples/browser/objectloader.web.js']
|
|
16
|
+
ignorePatterns: ['examples/browser/objectloader.web.js', 'types/**/*']
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
module.exports = config
|
package/dist/objectloader.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"core-js/modules/es.symbol.description.js";import"core-js/modules/es.array.flat.js";import"core-js/modules/es.array.flat-map.js";import"core-js/modules/es.array.includes.js";import"core-js/modules/es.array.reduce.js";import"core-js/modules/es.array.reduce-right.js";import"core-js/modules/es.array.sort.js";import"core-js/modules/es.array.unscopables.flat.js";import"core-js/modules/es.array.unscopables.flat-map.js";import"core-js/modules/es.math.hypot.js";import"core-js/modules/es.object.from-entries.js";import"core-js/modules/es.promise.js";import"core-js/modules/es.promise.finally.js";import"core-js/modules/es.regexp.constructor.js";import"core-js/modules/es.regexp.exec.js";import"core-js/modules/es.regexp.flags.js";import"core-js/modules/es.string.replace.js";import"core-js/modules/es.typed-array.float32-array.js";import"core-js/modules/es.typed-array.float64-array.js";import"core-js/modules/es.typed-array.int8-array.js";import"core-js/modules/es.typed-array.int16-array.js";import"core-js/modules/es.typed-array.int32-array.js";import"core-js/modules/es.typed-array.uint8-array.js";import"core-js/modules/es.typed-array.uint8-clamped-array.js";import"core-js/modules/es.typed-array.uint16-array.js";import"core-js/modules/es.typed-array.uint32-array.js";import"core-js/modules/es.typed-array.from.js";import"core-js/modules/es.typed-array.of.js";import"core-js/modules/es.typed-array.set.js";import"core-js/modules/es.typed-array.sort.js";import"core-js/modules/esnext.aggregate-error.js";import"core-js/modules/esnext.array.last-index.js";import"core-js/modules/esnext.array.last-item.js";import"core-js/modules/esnext.composite-key.js";import"core-js/modules/esnext.composite-symbol.js";import"core-js/modules/esnext.global-this.js";import"core-js/modules/esnext.map.delete-all.js";import"core-js/modules/esnext.map.every.js";import"core-js/modules/esnext.map.filter.js";import"core-js/modules/esnext.map.find.js";import"core-js/modules/esnext.map.find-key.js";import"core-js/modules/esnext.map.from.js";import"core-js/modules/esnext.map.group-by.js";import"core-js/modules/esnext.map.includes.js";import"core-js/modules/esnext.map.key-by.js";import"core-js/modules/esnext.map.key-of.js";import"core-js/modules/esnext.map.map-keys.js";import"core-js/modules/esnext.map.map-values.js";import"core-js/modules/esnext.map.merge.js";import"core-js/modules/esnext.map.of.js";import"core-js/modules/esnext.map.reduce.js";import"core-js/modules/esnext.map.some.js";import"core-js/modules/esnext.map.update.js";import"core-js/modules/esnext.math.clamp.js";import"core-js/modules/esnext.math.deg-per-rad.js";import"core-js/modules/esnext.math.degrees.js";import"core-js/modules/esnext.math.fscale.js";import"core-js/modules/esnext.math.iaddh.js";import"core-js/modules/esnext.math.imulh.js";import"core-js/modules/esnext.math.isubh.js";import"core-js/modules/esnext.math.rad-per-deg.js";import"core-js/modules/esnext.math.radians.js";import"core-js/modules/esnext.math.scale.js";import"core-js/modules/esnext.math.seeded-prng.js";import"core-js/modules/esnext.math.signbit.js";import"core-js/modules/esnext.math.umulh.js";import"core-js/modules/esnext.number.from-string.js";import"core-js/modules/esnext.observable.js";import"core-js/modules/esnext.promise.all-settled.js";import"core-js/modules/esnext.promise.any.js";import"core-js/modules/esnext.promise.try.js";import"core-js/modules/esnext.reflect.define-metadata.js";import"core-js/modules/esnext.reflect.delete-metadata.js";import"core-js/modules/esnext.reflect.get-metadata.js";import"core-js/modules/esnext.reflect.get-metadata-keys.js";import"core-js/modules/esnext.reflect.get-own-metadata.js";import"core-js/modules/esnext.reflect.get-own-metadata-keys.js";import"core-js/modules/esnext.reflect.has-metadata.js";import"core-js/modules/esnext.reflect.has-own-metadata.js";import"core-js/modules/esnext.reflect.metadata.js";import"core-js/modules/esnext.set.add-all.js";import"core-js/modules/esnext.set.delete-all.js";import"core-js/modules/esnext.set.difference.js";import"core-js/modules/esnext.set.every.js";import"core-js/modules/esnext.set.filter.js";import"core-js/modules/esnext.set.find.js";import"core-js/modules/esnext.set.from.js";import"core-js/modules/esnext.set.intersection.js";import"core-js/modules/esnext.set.is-disjoint-from.js";import"core-js/modules/esnext.set.is-subset-of.js";import"core-js/modules/esnext.set.is-superset-of.js";import"core-js/modules/esnext.set.join.js";import"core-js/modules/esnext.set.map.js";import"core-js/modules/esnext.set.of.js";import"core-js/modules/esnext.set.reduce.js";import"core-js/modules/esnext.set.some.js";import"core-js/modules/esnext.set.symmetric-difference.js";import"core-js/modules/esnext.set.union.js";import"core-js/modules/esnext.string.at.js";import"core-js/modules/esnext.string.code-points.js";import"core-js/modules/esnext.string.match-all.js";import"core-js/modules/esnext.string.replace-all.js";import"core-js/modules/esnext.symbol.dispose.js";import"core-js/modules/esnext.symbol.observable.js";import"core-js/modules/esnext.symbol.pattern-match.js";import"core-js/modules/esnext.weak-map.delete-all.js";import"core-js/modules/esnext.weak-map.from.js";import"core-js/modules/esnext.weak-map.of.js";import"core-js/modules/esnext.weak-set.add-all.js";import"core-js/modules/esnext.weak-set.delete-all.js";import"core-js/modules/esnext.weak-set.from.js";import"core-js/modules/esnext.weak-set.of.js";import"core-js/modules/web.dom-collections.iterator.js";import"core-js/modules/web.immediate.js";import"core-js/modules/web.queue-microtask.js";import"core-js/modules/web.url.js";import"core-js/modules/web.url.to-json.js";import"core-js/modules/web.url-search-params.js";class e{constructor(e){let{serverUrl:s,streamId:t,token:o,objectId:r,options:i={enableCaching:!0,fullyTraverseArrays:!1,excludeProps:[],fetch:null}}=e;this.INTERVAL_MS=20,this.TIMEOUT_MS=18e4,this.serverUrl=s||window.location.origin,this.streamId=t,this.objectId=r,console.log("Object loader constructor called!");try{this.token=o||localStorage.getItem("AuthToken")}catch(e){}this.headers={Accept:"text/plain"},this.token&&(this.headers.Authorization="Bearer ".concat(this.token)),this.requestUrlRootObj="".concat(this.serverUrl,"/objects/").concat(this.streamId,"/").concat(this.objectId,"/single"),this.requestUrlChildren="".concat(this.serverUrl,"/api/getobjects/").concat(this.streamId),this.promises=[],this.intervals={},this.buffer=[],this.isLoading=!1,this.totalChildrenCount=0,this.traversedReferencesCount=0,this.options=i,this.options.numConnections=this.options.numConnections||4,this.cacheDB=null,this.lastAsyncPause=Date.now(),this.existingAsyncPause=null,this.preferredFetch=i.fetch,this.fetch=function(){const e=this.preferredFetch||fetch;return e(...arguments)}}async asyncPause(){Date.now()-this.lastAsyncPause>=100&&(this.lastAsyncPause=Date.now(),this.existingAsyncPause=new Promise((e=>setTimeout(e,0))),await this.existingAsyncPause,this.existingAsyncPause=null,Date.now()-this.lastAsyncPause>500&&console.log("Loader Event loop lag: ",Date.now()-this.lastAsyncPause))}dispose(){this.buffer=[],this.intervals.forEach((e=>clearInterval(e.interval)))}async getAndConstructObject(e){await this.downloadObjectsInBuffer(e);const s=await this.getObject(this.objectId);return this.traverseAndConstruct(s,e)}async downloadObjectsInBuffer(e){let s=!0,t=0;for await(const o of this.getObjectIterator())s&&(this.totalChildrenCount=o.totalChildrenCount,s=!1,this.isLoading=!0),t++,e&&e({stage:"download",current:t,total:this.totalChildrenCount});this.isLoading=!1}async traverseAndConstruct(e,s){if(e){if("object"!=typeof e)return e;if(Array.isArray(e)&&0!==e.length){var t,o;const r=[];for(const t of e){if("object"!=typeof t&&!this.options.fullyTraverseArrays)return e;const o=t.referencedId?await this.getObject(t.referencedId):t;t.referencedId&&s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount}),r.push(await this.traverseAndConstruct(o,s))}return null!==(t=r[0])&&void 0!==t&&null!==(o=t.speckle_type)&&void 0!==o&&o.toLowerCase().includes("datachunk")?r.reduce(((e,s)=>e.concat(s.data)),[]):r}for(const s of this.options.excludeProps)delete e[s];for(const t in e)"object"==typeof e[t]&&null!==e[t]&&(e[t].referencedId&&(e[t]=await this.getObject(e[t].referencedId),s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount})),e[t]=await this.traverseAndConstruct(e[t],s));return e}}async getObject(e){if(this.buffer[e])return this.buffer[e];return new Promise(((s,t)=>{if(this.promises.push({id:e,resolve:s,reject:t}),this.intervals[e])this.intervals[e].elapsed=0;else{const s=setInterval(this.tryResolvePromise.bind(this),this.INTERVAL_MS,e);this.intervals[e]={interval:s,elapsed:0}}}))}tryResolvePromise(e){if(this.intervals[e].elapsed+=this.INTERVAL_MS,this.buffer[e]){for(const s of this.promises.filter((s=>s.id===e)))s.resolve(this.buffer[e]);return clearInterval(this.intervals[e].interval),void delete this.intervals[e]}this.intervals[e].elapsed>this.TIMEOUT_MS&&(console.warn("Timeout resolving ".concat(e,". HIC SVNT DRACONES.")),clearInterval(this.intervals[e].interval),this.promises.filter((s=>s.id===e)).forEach((e=>e.reject())),this.promises=this.promises.filter((e=>e.id!=e.id)))}async*getObjectIterator(){const e=Date.now();let s=0;for await(const e of this.getRawObjectIterator()){const{id:t,obj:o}=this.processLine(e);this.buffer[t]=o,s+=1,yield o}console.log("Loaded ".concat(s," objects in: ").concat((Date.now()-e)/1e3))}processLine(e){const s=e.split("\t");return{id:s[0],obj:JSON.parse(s[1])}}async*getRawObjectIterator(){if(this.options.enableCaching&&window.indexedDB&&null===this.cacheDB){await function(){if(navigator.userAgentData||!/Safari\//.test(navigator.userAgent)||/Chrom(e|ium)\//.test(navigator.userAgent)||!indexedDB.databases)return Promise.resolve();let e;return new Promise((s=>{const t=()=>indexedDB.databases().finally(s);e=setInterval(t,100),t()})).finally((()=>clearInterval(e)))}();const e=indexedDB.open("speckle-object-cache",1);e.onupgradeneeded=()=>e.result.createObjectStore("objects"),this.cacheDB=await this.promisifyIdbRequest(e)}const e=await this.getRawRootObject();yield"".concat(this.objectId,"\t").concat(e);const s=JSON.parse(e);if(!s.__closure)return;let t=Object.keys(s.__closure).sort(((e,t)=>s.__closure[e]-s.__closure[t]));if(0===t.length)return;let o=[];if(t.length>50){const e=[[],[],[],[]];let r=0;for(;r<.05*t.length;r++)e[0].push(t[r]);for(;r<.2*t.length;r++)e[1].push(t[r]);for(;r<.6*t.length;r++)e[2].push(t[r]);for(;r<t.length;r++)e[3].push(t[r]);console.log("Cache check for: ",e);const i=[];let n=this.cacheGetObjects(e[0]);for(let t=0;t<4;t++){const o=await n;t<3&&(n=this.cacheGetObjects(e[t+1]));const r=Object.keys(o).sort(((e,t)=>s.__closure[e]-s.__closure[t]));for(const e of r)yield"".concat(e,"\t").concat(o[e]);const a=e[t].filter((e=>!(e in o)));i.push(...a)}if(0===i.length)return;if(i.length<=50)o.push(i);else{for(o=[[],[],[],[]],r=0;r<.05*i.length;r++)o[0].push(i[r]);for(;r<.2*i.length;r++)o[1].push(i[r]);for(;r<.6*i.length;r++)o[2].push(i[r]);for(;r<i.length;r++)o[3].push(i[r])}}else{const e=await this.cacheGetObjects(t),r=Object.keys(e).sort(((e,t)=>s.__closure[e]-s.__closure[t]));for(const s of r)yield"".concat(s,"\t").concat(e[s]);if(t=t.filter((s=>!(s in e))),0===t.length)return;o.push(t)}const r=[],i=[],n=[],a=[],c=[];for(let e=0;e<o.length;e++)r.push(new TextDecoder),i.push(null),n.push(null),a.push(""),c.push(!1),this.fetch(this.requestUrlChildren,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({objects:JSON.stringify(o[e])})}).then((s=>{const t=s.body.getReader();i[e]=t;const o=t.read().then((s=>(s.reqId=e,s)));n[e]=o}));for(;;){const e=n.filter((e=>!!e));if(0===e.length){if(c.every((e=>e)))break;await new Promise((e=>{setTimeout(e,10)}));continue}const s=await Promise.any(e);let{value:t,done:o,reqId:l}=s;if(c[l]=o,o)a[l].length>0&&(yield a[l],a[l]=""),n[l]=null;else{const e=i[l].read().then((e=>(e.reqId=l,e)));n[l]=e}if(!t)continue;t=r[l].decode(t);const m=(a[l]+t).split(/\r\n|\n|\r/),d=m.pop();a[l]=d;for(const e of m)yield e;this.cacheStoreObjects(m)}}async getRawRootObject(){const e=await this.cacheGetObjects([this.objectId]);if(e[this.objectId])return e[this.objectId];const s=await this.fetch(this.requestUrlRootObj,{headers:this.headers}),t=await s.text();return this.cacheStoreObjects(["".concat(this.objectId,"\t").concat(t)]),t}promisifyIdbRequest(e){return new Promise(((s,t)=>{e.oncomplete=e.onsuccess=()=>s(e.result),e.onabort=e.onerror=()=>t(e.error)}))}async cacheGetObjects(e){if(!this.options.enableCaching||!window.indexedDB)return{};const s={};for(let t=0;t<e.length;t+=500){const o=e.slice(t,t+500),r=this.cacheDB.transaction("objects","readonly").objectStore("objects"),i=o.map((e=>this.promisifyIdbRequest(r.get(e)).then((s=>({id:e,data:s}))))),n=await Promise.all(i);for(const e of n)e.data&&(s[e.id]=e.data)}return s}cacheStoreObjects(e){if(!this.options.enableCaching||!window.indexedDB)return{};const s=this.cacheDB.transaction("objects","readwrite").objectStore("objects");for(const t of e){const e=t.split("\t");s.put(e[1],e[0])}return this.promisifyIdbRequest(s.transaction)}}export{e as default};
|
|
1
|
+
import"core-js/modules/es.symbol.description.js";import"core-js/modules/es.array.flat.js";import"core-js/modules/es.array.flat-map.js";import"core-js/modules/es.array.includes.js";import"core-js/modules/es.array.reduce.js";import"core-js/modules/es.array.reduce-right.js";import"core-js/modules/es.array.sort.js";import"core-js/modules/es.array.unscopables.flat.js";import"core-js/modules/es.array.unscopables.flat-map.js";import"core-js/modules/es.math.hypot.js";import"core-js/modules/es.object.from-entries.js";import"core-js/modules/es.promise.js";import"core-js/modules/es.promise.finally.js";import"core-js/modules/es.regexp.constructor.js";import"core-js/modules/es.regexp.exec.js";import"core-js/modules/es.regexp.flags.js";import"core-js/modules/es.string.replace.js";import"core-js/modules/es.typed-array.float32-array.js";import"core-js/modules/es.typed-array.float64-array.js";import"core-js/modules/es.typed-array.int8-array.js";import"core-js/modules/es.typed-array.int16-array.js";import"core-js/modules/es.typed-array.int32-array.js";import"core-js/modules/es.typed-array.uint8-array.js";import"core-js/modules/es.typed-array.uint8-clamped-array.js";import"core-js/modules/es.typed-array.uint16-array.js";import"core-js/modules/es.typed-array.uint32-array.js";import"core-js/modules/es.typed-array.from.js";import"core-js/modules/es.typed-array.of.js";import"core-js/modules/es.typed-array.set.js";import"core-js/modules/es.typed-array.sort.js";import"core-js/modules/esnext.aggregate-error.js";import"core-js/modules/esnext.array.last-index.js";import"core-js/modules/esnext.array.last-item.js";import"core-js/modules/esnext.composite-key.js";import"core-js/modules/esnext.composite-symbol.js";import"core-js/modules/esnext.global-this.js";import"core-js/modules/esnext.map.delete-all.js";import"core-js/modules/esnext.map.every.js";import"core-js/modules/esnext.map.filter.js";import"core-js/modules/esnext.map.find.js";import"core-js/modules/esnext.map.find-key.js";import"core-js/modules/esnext.map.from.js";import"core-js/modules/esnext.map.group-by.js";import"core-js/modules/esnext.map.includes.js";import"core-js/modules/esnext.map.key-by.js";import"core-js/modules/esnext.map.key-of.js";import"core-js/modules/esnext.map.map-keys.js";import"core-js/modules/esnext.map.map-values.js";import"core-js/modules/esnext.map.merge.js";import"core-js/modules/esnext.map.of.js";import"core-js/modules/esnext.map.reduce.js";import"core-js/modules/esnext.map.some.js";import"core-js/modules/esnext.map.update.js";import"core-js/modules/esnext.math.clamp.js";import"core-js/modules/esnext.math.deg-per-rad.js";import"core-js/modules/esnext.math.degrees.js";import"core-js/modules/esnext.math.fscale.js";import"core-js/modules/esnext.math.iaddh.js";import"core-js/modules/esnext.math.imulh.js";import"core-js/modules/esnext.math.isubh.js";import"core-js/modules/esnext.math.rad-per-deg.js";import"core-js/modules/esnext.math.radians.js";import"core-js/modules/esnext.math.scale.js";import"core-js/modules/esnext.math.seeded-prng.js";import"core-js/modules/esnext.math.signbit.js";import"core-js/modules/esnext.math.umulh.js";import"core-js/modules/esnext.number.from-string.js";import"core-js/modules/esnext.observable.js";import"core-js/modules/esnext.promise.all-settled.js";import"core-js/modules/esnext.promise.any.js";import"core-js/modules/esnext.promise.try.js";import"core-js/modules/esnext.reflect.define-metadata.js";import"core-js/modules/esnext.reflect.delete-metadata.js";import"core-js/modules/esnext.reflect.get-metadata.js";import"core-js/modules/esnext.reflect.get-metadata-keys.js";import"core-js/modules/esnext.reflect.get-own-metadata.js";import"core-js/modules/esnext.reflect.get-own-metadata-keys.js";import"core-js/modules/esnext.reflect.has-metadata.js";import"core-js/modules/esnext.reflect.has-own-metadata.js";import"core-js/modules/esnext.reflect.metadata.js";import"core-js/modules/esnext.set.add-all.js";import"core-js/modules/esnext.set.delete-all.js";import"core-js/modules/esnext.set.difference.js";import"core-js/modules/esnext.set.every.js";import"core-js/modules/esnext.set.filter.js";import"core-js/modules/esnext.set.find.js";import"core-js/modules/esnext.set.from.js";import"core-js/modules/esnext.set.intersection.js";import"core-js/modules/esnext.set.is-disjoint-from.js";import"core-js/modules/esnext.set.is-subset-of.js";import"core-js/modules/esnext.set.is-superset-of.js";import"core-js/modules/esnext.set.join.js";import"core-js/modules/esnext.set.map.js";import"core-js/modules/esnext.set.of.js";import"core-js/modules/esnext.set.reduce.js";import"core-js/modules/esnext.set.some.js";import"core-js/modules/esnext.set.symmetric-difference.js";import"core-js/modules/esnext.set.union.js";import"core-js/modules/esnext.string.at.js";import"core-js/modules/esnext.string.code-points.js";import"core-js/modules/esnext.string.match-all.js";import"core-js/modules/esnext.string.replace-all.js";import"core-js/modules/esnext.symbol.dispose.js";import"core-js/modules/esnext.symbol.observable.js";import"core-js/modules/esnext.symbol.pattern-match.js";import"core-js/modules/esnext.weak-map.delete-all.js";import"core-js/modules/esnext.weak-map.from.js";import"core-js/modules/esnext.weak-map.of.js";import"core-js/modules/esnext.weak-set.add-all.js";import"core-js/modules/esnext.weak-set.delete-all.js";import"core-js/modules/esnext.weak-set.from.js";import"core-js/modules/esnext.weak-set.of.js";import"core-js/modules/web.dom-collections.iterator.js";import"core-js/modules/web.immediate.js";import"core-js/modules/web.queue-microtask.js";import"core-js/modules/web.url.js";import"core-js/modules/web.url.to-json.js";import"core-js/modules/web.url-search-params.js";import{SafeLocalStorage as e}from"@speckle/shared";function s(e,s,t){return s in e?Object.defineProperty(e,s,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[s]=t,e}class t extends Error{constructor(e,s){e||(e=new.target.defaultMessage),super(e,s)}}s(t,"defaultMessage","Unexpected error occurred");class o extends t{}s(o,"defaultMessage","Object loader configured incorrectly!");class r extends t{}s(r,"defaultMessage","Object loader encountered a runtime problem!");class i{constructor(s){var t;let{serverUrl:i,streamId:n,token:a,objectId:c,options:l={enableCaching:!0,fullyTraverseArrays:!1,excludeProps:[],fetch:null,customLogger:void 0,customWarner:void 0}}=s;if(this.logger=l.customLogger||console.log,this.warner=l.customWarner||console.warn,this.INTERVAL_MS=20,this.TIMEOUT_MS=18e4,this.serverUrl=i||(null===globalThis||void 0===globalThis||null===(t=globalThis.location)||void 0===t?void 0:t.origin),!this.serverUrl)throw new o("Invalid serverUrl specified!");if(this.streamId=n,this.objectId=c,!this.streamId)throw new o("Invalid streamId specified!");if(!this.objectId)throw new o("Invalid objectId specified!");this.logger("Object loader constructor called!");try{this.token=a||e.get("AuthToken")}catch(e){}this.headers={Accept:"text/plain"},this.token&&(this.headers.Authorization="Bearer ".concat(this.token)),this.requestUrlRootObj="".concat(this.serverUrl,"/objects/").concat(this.streamId,"/").concat(this.objectId,"/single"),this.requestUrlChildren="".concat(this.serverUrl,"/api/getobjects/").concat(this.streamId),this.promises=[],this.intervals={},this.buffer=[],this.isLoading=!1,this.totalChildrenCount=0,this.traversedReferencesCount=0,this.options=l,this.options.numConnections=this.options.numConnections||4,this.cacheDB=null,this.lastAsyncPause=Date.now(),this.existingAsyncPause=null,this.preferredFetch=l.fetch,this.fetch=function(){const e=this.preferredFetch||fetch;if(!e)throw new r("Couldn't find fetch implementation! If running in a node environment, make sure you pass it in through the constructor!");return e(...arguments)}}async asyncPause(){Date.now()-this.lastAsyncPause>=100&&(this.lastAsyncPause=Date.now(),this.existingAsyncPause=new Promise((e=>setTimeout(e,0))),await this.existingAsyncPause,this.existingAsyncPause=null,Date.now()-this.lastAsyncPause>500&&this.logger("Loader Event loop lag: ",Date.now()-this.lastAsyncPause))}dispose(){this.buffer=[],this.intervals.forEach((e=>clearInterval(e.interval)))}async getAndConstructObject(e){await this.downloadObjectsInBuffer(e);const s=await this.getObject(this.objectId);return this.traverseAndConstruct(s,e)}async downloadObjectsInBuffer(e){let s=!0,t=0;for await(const o of this.getObjectIterator())s&&(this.totalChildrenCount=o.totalChildrenCount,s=!1,this.isLoading=!0),t++,e&&e({stage:"download",current:t,total:this.totalChildrenCount});this.isLoading=!1}async traverseAndConstruct(e,s){if(e){if("object"!=typeof e)return e;if(Array.isArray(e)&&0!==e.length){var t,o;const r=[];for(const t of e){if("object"!=typeof t&&!this.options.fullyTraverseArrays)return e;const o=t.referencedId?await this.getObject(t.referencedId):t;t.referencedId&&s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount}),r.push(await this.traverseAndConstruct(o,s))}return null!==(t=r[0])&&void 0!==t&&null!==(o=t.speckle_type)&&void 0!==o&&o.toLowerCase().includes("datachunk")?r.reduce(((e,s)=>e.concat(s.data)),[]):r}for(const s of this.options.excludeProps)delete e[s];for(const t in e)"object"==typeof e[t]&&null!==e[t]&&(e[t].referencedId&&(e[t]=await this.getObject(e[t].referencedId),s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount})),e[t]=await this.traverseAndConstruct(e[t],s));return e}}async getObject(e){if(this.buffer[e])return this.buffer[e];return new Promise(((s,t)=>{if(this.promises.push({id:e,resolve:s,reject:t}),this.intervals[e])this.intervals[e].elapsed=0;else{const s=setInterval(this.tryResolvePromise.bind(this),this.INTERVAL_MS,e);this.intervals[e]={interval:s,elapsed:0}}}))}tryResolvePromise(e){if(this.intervals[e].elapsed+=this.INTERVAL_MS,this.buffer[e]){for(const s of this.promises.filter((s=>s.id===e)))s.resolve(this.buffer[e]);return clearInterval(this.intervals[e].interval),void delete this.intervals[e]}this.intervals[e].elapsed>this.TIMEOUT_MS&&(this.warner("Timeout resolving ".concat(e,". HIC SVNT DRACONES.")),clearInterval(this.intervals[e].interval),this.promises.filter((s=>s.id===e)).forEach((e=>e.reject())),this.promises=this.promises.filter((e=>e.id!=e.id)))}async*getObjectIterator(){const e=Date.now();let s=0;for await(const e of this.getRawObjectIterator()){const{id:t,obj:o}=this.processLine(e);this.buffer[t]=o,s+=1,yield o}this.logger("Loaded ".concat(s," objects in: ").concat((Date.now()-e)/1e3))}processLine(e){const s=e.split("\t");return{id:s[0],obj:JSON.parse(s[1])}}supportsCache(){return!(!this.options.enableCaching||!globalThis.indexedDB)}async setupCacheDb(){if(!this.supportsCache()||null!==this.cacheDB)return;await function(){if(navigator.userAgentData||!/Safari\//.test(navigator.userAgent)||/Chrom(e|ium)\//.test(navigator.userAgent)||!indexedDB.databases)return Promise.resolve();let e;return new Promise((s=>{const t=()=>indexedDB.databases().finally(s);e=setInterval(t,100),t()})).finally((()=>clearInterval(e)))}();const e=indexedDB.open("speckle-object-cache",1);e.onupgradeneeded=()=>e.result.createObjectStore("objects"),this.cacheDB=await this.promisifyIdbRequest(e)}async*getRawObjectIterator(){await this.setupCacheDb();const e=await this.getRawRootObject();yield"".concat(this.objectId,"\t").concat(e);const s=JSON.parse(e);if(!s.__closure)return;let t=Object.keys(s.__closure).filter((e=>!e.includes("blob"))).sort(((e,t)=>s.__closure[e]-s.__closure[t]));if(0===t.length)return;let o=[];if(t.length>50){const e=[[],[],[],[]];let r=0;for(;r<.05*t.length;r++)e[0].push(t[r]);for(;r<.2*t.length;r++)e[1].push(t[r]);for(;r<.6*t.length;r++)e[2].push(t[r]);for(;r<t.length;r++)e[3].push(t[r]);this.logger("Cache check for: ",e);const i=[];let n=this.cacheGetObjects(e[0]);for(let t=0;t<4;t++){const o=await n;t<3&&(n=this.cacheGetObjects(e[t+1]));const r=Object.keys(o).sort(((e,t)=>s.__closure[e]-s.__closure[t]));for(const e of r)yield"".concat(e,"\t").concat(o[e]);const a=e[t].filter((e=>!(e in o)));i.push(...a)}if(0===i.length)return;if(i.length<=50)o.push(i);else{for(o=[[],[],[],[]],r=0;r<.05*i.length;r++)o[0].push(i[r]);for(;r<.2*i.length;r++)o[1].push(i[r]);for(;r<.6*i.length;r++)o[2].push(i[r]);for(;r<i.length;r++)o[3].push(i[r])}}else{const e=await this.cacheGetObjects(t),r=Object.keys(e).sort(((e,t)=>s.__closure[e]-s.__closure[t]));for(const s of r)yield"".concat(s,"\t").concat(e[s]);if(t=t.filter((s=>!(s in e))),0===t.length)return;o.push(t)}const r=[],i=[],n=[],a=[],c=[];for(let e=0;e<o.length;e++)r.push(new TextDecoder),i.push(null),n.push(null),a.push(""),c.push(!1),this.fetch(this.requestUrlChildren,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({objects:JSON.stringify(o[e])})}).then((s=>{s.body.getReader&&(s.body.iterator=async function*(){const e=this.getReader();for(;;){const s=await e.read();if(s.done)return s.value;yield s.value}});const t=s.body.iterator();i[e]=t;const o=t.next().then((s=>(s.reqId=e,s)));n[e]=o}));for(;;){const e=n.filter((e=>!!e));if(0===e.length){if(c.every((e=>e)))break;await new Promise((e=>{setTimeout(e,10)}));continue}const s=await Promise.any(e);let{value:t,done:o,reqId:l}=s;if(c[l]=o,o)a[l].length>0&&(yield a[l],a[l]=""),n[l]=null;else{const e=i[l].next().then((e=>(e.reqId=l,e)));n[l]=e}if(!t)continue;t=r[l].decode(t);const m=(a[l]+t).split(/\r\n|\n|\r/),d=m.pop();a[l]=d;for(const e of m)yield e;this.cacheStoreObjects(m)}}async getRawRootObject(){const e=await this.cacheGetObjects([this.objectId]);if(e[this.objectId])return e[this.objectId];const s=await this.fetch(this.requestUrlRootObj,{headers:this.headers}),t=await s.text();return this.cacheStoreObjects(["".concat(this.objectId,"\t").concat(t)]),t}promisifyIdbRequest(e){return new Promise(((s,t)=>{e.oncomplete=e.onsuccess=()=>s(e.result),e.onabort=e.onerror=()=>t(e.error)}))}async cacheGetObjects(e){if(!this.supportsCache())return{};const s={};for(let t=0;t<e.length;t+=500){const o=e.slice(t,t+500),r=this.cacheDB.transaction("objects","readonly").objectStore("objects"),i=o.map((e=>this.promisifyIdbRequest(r.get(e)).then((s=>({id:e,data:s}))))),n=await Promise.all(i);for(const e of n)e.data&&(s[e.id]=e.data)}return s}cacheStoreObjects(e){if(!this.supportsCache())return{};const s=this.cacheDB.transaction("objects","readwrite").objectStore("objects");for(const t of e){const e=t.split("\t");s.put(e[1],e[0])}return this.promisifyIdbRequest(s.transaction)}}export{i as default};
|
package/dist/objectloader.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";require("core-js/modules/es.symbol.description.js"),require("core-js/modules/es.array.flat.js"),require("core-js/modules/es.array.flat-map.js"),require("core-js/modules/es.array.includes.js"),require("core-js/modules/es.array.reduce.js"),require("core-js/modules/es.array.reduce-right.js"),require("core-js/modules/es.array.sort.js"),require("core-js/modules/es.array.unscopables.flat.js"),require("core-js/modules/es.array.unscopables.flat-map.js"),require("core-js/modules/es.math.hypot.js"),require("core-js/modules/es.object.from-entries.js"),require("core-js/modules/es.promise.js"),require("core-js/modules/es.promise.finally.js"),require("core-js/modules/es.regexp.constructor.js"),require("core-js/modules/es.regexp.exec.js"),require("core-js/modules/es.regexp.flags.js"),require("core-js/modules/es.string.replace.js"),require("core-js/modules/es.typed-array.float32-array.js"),require("core-js/modules/es.typed-array.float64-array.js"),require("core-js/modules/es.typed-array.int8-array.js"),require("core-js/modules/es.typed-array.int16-array.js"),require("core-js/modules/es.typed-array.int32-array.js"),require("core-js/modules/es.typed-array.uint8-array.js"),require("core-js/modules/es.typed-array.uint8-clamped-array.js"),require("core-js/modules/es.typed-array.uint16-array.js"),require("core-js/modules/es.typed-array.uint32-array.js"),require("core-js/modules/es.typed-array.from.js"),require("core-js/modules/es.typed-array.of.js"),require("core-js/modules/es.typed-array.set.js"),require("core-js/modules/es.typed-array.sort.js"),require("core-js/modules/esnext.aggregate-error.js"),require("core-js/modules/esnext.array.last-index.js"),require("core-js/modules/esnext.array.last-item.js"),require("core-js/modules/esnext.composite-key.js"),require("core-js/modules/esnext.composite-symbol.js"),require("core-js/modules/esnext.global-this.js"),require("core-js/modules/esnext.map.delete-all.js"),require("core-js/modules/esnext.map.every.js"),require("core-js/modules/esnext.map.filter.js"),require("core-js/modules/esnext.map.find.js"),require("core-js/modules/esnext.map.find-key.js"),require("core-js/modules/esnext.map.from.js"),require("core-js/modules/esnext.map.group-by.js"),require("core-js/modules/esnext.map.includes.js"),require("core-js/modules/esnext.map.key-by.js"),require("core-js/modules/esnext.map.key-of.js"),require("core-js/modules/esnext.map.map-keys.js"),require("core-js/modules/esnext.map.map-values.js"),require("core-js/modules/esnext.map.merge.js"),require("core-js/modules/esnext.map.of.js"),require("core-js/modules/esnext.map.reduce.js"),require("core-js/modules/esnext.map.some.js"),require("core-js/modules/esnext.map.update.js"),require("core-js/modules/esnext.math.clamp.js"),require("core-js/modules/esnext.math.deg-per-rad.js"),require("core-js/modules/esnext.math.degrees.js"),require("core-js/modules/esnext.math.fscale.js"),require("core-js/modules/esnext.math.iaddh.js"),require("core-js/modules/esnext.math.imulh.js"),require("core-js/modules/esnext.math.isubh.js"),require("core-js/modules/esnext.math.rad-per-deg.js"),require("core-js/modules/esnext.math.radians.js"),require("core-js/modules/esnext.math.scale.js"),require("core-js/modules/esnext.math.seeded-prng.js"),require("core-js/modules/esnext.math.signbit.js"),require("core-js/modules/esnext.math.umulh.js"),require("core-js/modules/esnext.number.from-string.js"),require("core-js/modules/esnext.observable.js"),require("core-js/modules/esnext.promise.all-settled.js"),require("core-js/modules/esnext.promise.any.js"),require("core-js/modules/esnext.promise.try.js"),require("core-js/modules/esnext.reflect.define-metadata.js"),require("core-js/modules/esnext.reflect.delete-metadata.js"),require("core-js/modules/esnext.reflect.get-metadata.js"),require("core-js/modules/esnext.reflect.get-metadata-keys.js"),require("core-js/modules/esnext.reflect.get-own-metadata.js"),require("core-js/modules/esnext.reflect.get-own-metadata-keys.js"),require("core-js/modules/esnext.reflect.has-metadata.js"),require("core-js/modules/esnext.reflect.has-own-metadata.js"),require("core-js/modules/esnext.reflect.metadata.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.from.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.of.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js"),require("core-js/modules/esnext.string.at.js"),require("core-js/modules/esnext.string.code-points.js"),require("core-js/modules/esnext.string.match-all.js"),require("core-js/modules/esnext.string.replace-all.js"),require("core-js/modules/esnext.symbol.dispose.js"),require("core-js/modules/esnext.symbol.observable.js"),require("core-js/modules/esnext.symbol.pattern-match.js"),require("core-js/modules/esnext.weak-map.delete-all.js"),require("core-js/modules/esnext.weak-map.from.js"),require("core-js/modules/esnext.weak-map.of.js"),require("core-js/modules/esnext.weak-set.add-all.js"),require("core-js/modules/esnext.weak-set.delete-all.js"),require("core-js/modules/esnext.weak-set.from.js"),require("core-js/modules/esnext.weak-set.of.js"),require("core-js/modules/web.dom-collections.iterator.js"),require("core-js/modules/web.immediate.js"),require("core-js/modules/web.queue-microtask.js"),require("core-js/modules/web.url.js"),require("core-js/modules/web.url.to-json.js"),require("core-js/modules/web.url-search-params.js");module.exports=class{constructor(e){let{serverUrl:s,streamId:r,token:t,objectId:o,options:i={enableCaching:!0,fullyTraverseArrays:!1,excludeProps:[],fetch:null}}=e;this.INTERVAL_MS=20,this.TIMEOUT_MS=18e4,this.serverUrl=s||window.location.origin,this.streamId=r,this.objectId=o,console.log("Object loader constructor called!");try{this.token=t||localStorage.getItem("AuthToken")}catch(e){}this.headers={Accept:"text/plain"},this.token&&(this.headers.Authorization="Bearer ".concat(this.token)),this.requestUrlRootObj="".concat(this.serverUrl,"/objects/").concat(this.streamId,"/").concat(this.objectId,"/single"),this.requestUrlChildren="".concat(this.serverUrl,"/api/getobjects/").concat(this.streamId),this.promises=[],this.intervals={},this.buffer=[],this.isLoading=!1,this.totalChildrenCount=0,this.traversedReferencesCount=0,this.options=i,this.options.numConnections=this.options.numConnections||4,this.cacheDB=null,this.lastAsyncPause=Date.now(),this.existingAsyncPause=null,this.preferredFetch=i.fetch,this.fetch=function(){const e=this.preferredFetch||fetch;return e(...arguments)}}async asyncPause(){Date.now()-this.lastAsyncPause>=100&&(this.lastAsyncPause=Date.now(),this.existingAsyncPause=new Promise((e=>setTimeout(e,0))),await this.existingAsyncPause,this.existingAsyncPause=null,Date.now()-this.lastAsyncPause>500&&console.log("Loader Event loop lag: ",Date.now()-this.lastAsyncPause))}dispose(){this.buffer=[],this.intervals.forEach((e=>clearInterval(e.interval)))}async getAndConstructObject(e){await this.downloadObjectsInBuffer(e);const s=await this.getObject(this.objectId);return this.traverseAndConstruct(s,e)}async downloadObjectsInBuffer(e){let s=!0,r=0;for await(const t of this.getObjectIterator())s&&(this.totalChildrenCount=t.totalChildrenCount,s=!1,this.isLoading=!0),r++,e&&e({stage:"download",current:r,total:this.totalChildrenCount});this.isLoading=!1}async traverseAndConstruct(e,s){if(e){if("object"!=typeof e)return e;if(Array.isArray(e)&&0!==e.length){var r,t;const o=[];for(const r of e){if("object"!=typeof r&&!this.options.fullyTraverseArrays)return e;const t=r.referencedId?await this.getObject(r.referencedId):r;r.referencedId&&s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount}),o.push(await this.traverseAndConstruct(t,s))}return null!==(r=o[0])&&void 0!==r&&null!==(t=r.speckle_type)&&void 0!==t&&t.toLowerCase().includes("datachunk")?o.reduce(((e,s)=>e.concat(s.data)),[]):o}for(const s of this.options.excludeProps)delete e[s];for(const r in e)"object"==typeof e[r]&&null!==e[r]&&(e[r].referencedId&&(e[r]=await this.getObject(e[r].referencedId),s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount})),e[r]=await this.traverseAndConstruct(e[r],s));return e}}async getObject(e){if(this.buffer[e])return this.buffer[e];return new Promise(((s,r)=>{if(this.promises.push({id:e,resolve:s,reject:r}),this.intervals[e])this.intervals[e].elapsed=0;else{const s=setInterval(this.tryResolvePromise.bind(this),this.INTERVAL_MS,e);this.intervals[e]={interval:s,elapsed:0}}}))}tryResolvePromise(e){if(this.intervals[e].elapsed+=this.INTERVAL_MS,this.buffer[e]){for(const s of this.promises.filter((s=>s.id===e)))s.resolve(this.buffer[e]);return clearInterval(this.intervals[e].interval),void delete this.intervals[e]}this.intervals[e].elapsed>this.TIMEOUT_MS&&(console.warn("Timeout resolving ".concat(e,". HIC SVNT DRACONES.")),clearInterval(this.intervals[e].interval),this.promises.filter((s=>s.id===e)).forEach((e=>e.reject())),this.promises=this.promises.filter((e=>e.id!=e.id)))}async*getObjectIterator(){const e=Date.now();let s=0;for await(const e of this.getRawObjectIterator()){const{id:r,obj:t}=this.processLine(e);this.buffer[r]=t,s+=1,yield t}console.log("Loaded ".concat(s," objects in: ").concat((Date.now()-e)/1e3))}processLine(e){const s=e.split("\t");return{id:s[0],obj:JSON.parse(s[1])}}async*getRawObjectIterator(){if(this.options.enableCaching&&window.indexedDB&&null===this.cacheDB){await function(){if(navigator.userAgentData||!/Safari\//.test(navigator.userAgent)||/Chrom(e|ium)\//.test(navigator.userAgent)||!indexedDB.databases)return Promise.resolve();let e;return new Promise((s=>{const r=()=>indexedDB.databases().finally(s);e=setInterval(r,100),r()})).finally((()=>clearInterval(e)))}();const e=indexedDB.open("speckle-object-cache",1);e.onupgradeneeded=()=>e.result.createObjectStore("objects"),this.cacheDB=await this.promisifyIdbRequest(e)}const e=await this.getRawRootObject();yield"".concat(this.objectId,"\t").concat(e);const s=JSON.parse(e);if(!s.__closure)return;let r=Object.keys(s.__closure).sort(((e,r)=>s.__closure[e]-s.__closure[r]));if(0===r.length)return;let t=[];if(r.length>50){const e=[[],[],[],[]];let o=0;for(;o<.05*r.length;o++)e[0].push(r[o]);for(;o<.2*r.length;o++)e[1].push(r[o]);for(;o<.6*r.length;o++)e[2].push(r[o]);for(;o<r.length;o++)e[3].push(r[o]);console.log("Cache check for: ",e);const i=[];let n=this.cacheGetObjects(e[0]);for(let r=0;r<4;r++){const t=await n;r<3&&(n=this.cacheGetObjects(e[r+1]));const o=Object.keys(t).sort(((e,r)=>s.__closure[e]-s.__closure[r]));for(const e of o)yield"".concat(e,"\t").concat(t[e]);const a=e[r].filter((e=>!(e in t)));i.push(...a)}if(0===i.length)return;if(i.length<=50)t.push(i);else{for(t=[[],[],[],[]],o=0;o<.05*i.length;o++)t[0].push(i[o]);for(;o<.2*i.length;o++)t[1].push(i[o]);for(;o<.6*i.length;o++)t[2].push(i[o]);for(;o<i.length;o++)t[3].push(i[o])}}else{const e=await this.cacheGetObjects(r),o=Object.keys(e).sort(((e,r)=>s.__closure[e]-s.__closure[r]));for(const s of o)yield"".concat(s,"\t").concat(e[s]);if(r=r.filter((s=>!(s in e))),0===r.length)return;t.push(r)}const o=[],i=[],n=[],a=[],c=[];for(let e=0;e<t.length;e++)o.push(new TextDecoder),i.push(null),n.push(null),a.push(""),c.push(!1),this.fetch(this.requestUrlChildren,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({objects:JSON.stringify(t[e])})}).then((s=>{const r=s.body.getReader();i[e]=r;const t=r.read().then((s=>(s.reqId=e,s)));n[e]=t}));for(;;){const e=n.filter((e=>!!e));if(0===e.length){if(c.every((e=>e)))break;await new Promise((e=>{setTimeout(e,10)}));continue}const s=await Promise.any(e);let{value:r,done:t,reqId:u}=s;if(c[u]=t,t)a[u].length>0&&(yield a[u],a[u]=""),n[u]=null;else{const e=i[u].read().then((e=>(e.reqId=u,e)));n[u]=e}if(!r)continue;r=o[u].decode(r);const l=(a[u]+r).split(/\r\n|\n|\r/),d=l.pop();a[u]=d;for(const e of l)yield e;this.cacheStoreObjects(l)}}async getRawRootObject(){const e=await this.cacheGetObjects([this.objectId]);if(e[this.objectId])return e[this.objectId];const s=await this.fetch(this.requestUrlRootObj,{headers:this.headers}),r=await s.text();return this.cacheStoreObjects(["".concat(this.objectId,"\t").concat(r)]),r}promisifyIdbRequest(e){return new Promise(((s,r)=>{e.oncomplete=e.onsuccess=()=>s(e.result),e.onabort=e.onerror=()=>r(e.error)}))}async cacheGetObjects(e){if(!this.options.enableCaching||!window.indexedDB)return{};const s={};for(let r=0;r<e.length;r+=500){const t=e.slice(r,r+500),o=this.cacheDB.transaction("objects","readonly").objectStore("objects"),i=t.map((e=>this.promisifyIdbRequest(o.get(e)).then((s=>({id:e,data:s}))))),n=await Promise.all(i);for(const e of n)e.data&&(s[e.id]=e.data)}return s}cacheStoreObjects(e){if(!this.options.enableCaching||!window.indexedDB)return{};const s=this.cacheDB.transaction("objects","readwrite").objectStore("objects");for(const r of e){const e=r.split("\t");s.put(e[1],e[0])}return this.promisifyIdbRequest(s.transaction)}};
|
|
1
|
+
"use strict";require("core-js/modules/es.symbol.description.js"),require("core-js/modules/es.array.flat.js"),require("core-js/modules/es.array.flat-map.js"),require("core-js/modules/es.array.includes.js"),require("core-js/modules/es.array.reduce.js"),require("core-js/modules/es.array.reduce-right.js"),require("core-js/modules/es.array.sort.js"),require("core-js/modules/es.array.unscopables.flat.js"),require("core-js/modules/es.array.unscopables.flat-map.js"),require("core-js/modules/es.math.hypot.js"),require("core-js/modules/es.object.from-entries.js"),require("core-js/modules/es.promise.js"),require("core-js/modules/es.promise.finally.js"),require("core-js/modules/es.regexp.constructor.js"),require("core-js/modules/es.regexp.exec.js"),require("core-js/modules/es.regexp.flags.js"),require("core-js/modules/es.string.replace.js"),require("core-js/modules/es.typed-array.float32-array.js"),require("core-js/modules/es.typed-array.float64-array.js"),require("core-js/modules/es.typed-array.int8-array.js"),require("core-js/modules/es.typed-array.int16-array.js"),require("core-js/modules/es.typed-array.int32-array.js"),require("core-js/modules/es.typed-array.uint8-array.js"),require("core-js/modules/es.typed-array.uint8-clamped-array.js"),require("core-js/modules/es.typed-array.uint16-array.js"),require("core-js/modules/es.typed-array.uint32-array.js"),require("core-js/modules/es.typed-array.from.js"),require("core-js/modules/es.typed-array.of.js"),require("core-js/modules/es.typed-array.set.js"),require("core-js/modules/es.typed-array.sort.js"),require("core-js/modules/esnext.aggregate-error.js"),require("core-js/modules/esnext.array.last-index.js"),require("core-js/modules/esnext.array.last-item.js"),require("core-js/modules/esnext.composite-key.js"),require("core-js/modules/esnext.composite-symbol.js"),require("core-js/modules/esnext.global-this.js"),require("core-js/modules/esnext.map.delete-all.js"),require("core-js/modules/esnext.map.every.js"),require("core-js/modules/esnext.map.filter.js"),require("core-js/modules/esnext.map.find.js"),require("core-js/modules/esnext.map.find-key.js"),require("core-js/modules/esnext.map.from.js"),require("core-js/modules/esnext.map.group-by.js"),require("core-js/modules/esnext.map.includes.js"),require("core-js/modules/esnext.map.key-by.js"),require("core-js/modules/esnext.map.key-of.js"),require("core-js/modules/esnext.map.map-keys.js"),require("core-js/modules/esnext.map.map-values.js"),require("core-js/modules/esnext.map.merge.js"),require("core-js/modules/esnext.map.of.js"),require("core-js/modules/esnext.map.reduce.js"),require("core-js/modules/esnext.map.some.js"),require("core-js/modules/esnext.map.update.js"),require("core-js/modules/esnext.math.clamp.js"),require("core-js/modules/esnext.math.deg-per-rad.js"),require("core-js/modules/esnext.math.degrees.js"),require("core-js/modules/esnext.math.fscale.js"),require("core-js/modules/esnext.math.iaddh.js"),require("core-js/modules/esnext.math.imulh.js"),require("core-js/modules/esnext.math.isubh.js"),require("core-js/modules/esnext.math.rad-per-deg.js"),require("core-js/modules/esnext.math.radians.js"),require("core-js/modules/esnext.math.scale.js"),require("core-js/modules/esnext.math.seeded-prng.js"),require("core-js/modules/esnext.math.signbit.js"),require("core-js/modules/esnext.math.umulh.js"),require("core-js/modules/esnext.number.from-string.js"),require("core-js/modules/esnext.observable.js"),require("core-js/modules/esnext.promise.all-settled.js"),require("core-js/modules/esnext.promise.any.js"),require("core-js/modules/esnext.promise.try.js"),require("core-js/modules/esnext.reflect.define-metadata.js"),require("core-js/modules/esnext.reflect.delete-metadata.js"),require("core-js/modules/esnext.reflect.get-metadata.js"),require("core-js/modules/esnext.reflect.get-metadata-keys.js"),require("core-js/modules/esnext.reflect.get-own-metadata.js"),require("core-js/modules/esnext.reflect.get-own-metadata-keys.js"),require("core-js/modules/esnext.reflect.has-metadata.js"),require("core-js/modules/esnext.reflect.has-own-metadata.js"),require("core-js/modules/esnext.reflect.metadata.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.from.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.of.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js"),require("core-js/modules/esnext.string.at.js"),require("core-js/modules/esnext.string.code-points.js"),require("core-js/modules/esnext.string.match-all.js"),require("core-js/modules/esnext.string.replace-all.js"),require("core-js/modules/esnext.symbol.dispose.js"),require("core-js/modules/esnext.symbol.observable.js"),require("core-js/modules/esnext.symbol.pattern-match.js"),require("core-js/modules/esnext.weak-map.delete-all.js"),require("core-js/modules/esnext.weak-map.from.js"),require("core-js/modules/esnext.weak-map.of.js"),require("core-js/modules/esnext.weak-set.add-all.js"),require("core-js/modules/esnext.weak-set.delete-all.js"),require("core-js/modules/esnext.weak-set.from.js"),require("core-js/modules/esnext.weak-set.of.js"),require("core-js/modules/web.dom-collections.iterator.js"),require("core-js/modules/web.immediate.js"),require("core-js/modules/web.queue-microtask.js"),require("core-js/modules/web.url.js"),require("core-js/modules/web.url.to-json.js"),require("core-js/modules/web.url-search-params.js");var e=require("@speckle/shared");function s(e,s,r){return s in e?Object.defineProperty(e,s,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[s]=r,e}class r extends Error{constructor(e,s){e||(e=new.target.defaultMessage),super(e,s)}}s(r,"defaultMessage","Unexpected error occurred");class t extends r{}s(t,"defaultMessage","Object loader configured incorrectly!");class o extends r{}s(o,"defaultMessage","Object loader encountered a runtime problem!");module.exports=class{constructor(s){var r;let{serverUrl:i,streamId:a,token:n,objectId:c,options:u={enableCaching:!0,fullyTraverseArrays:!1,excludeProps:[],fetch:null,customLogger:void 0,customWarner:void 0}}=s;if(this.logger=u.customLogger||console.log,this.warner=u.customWarner||console.warn,this.INTERVAL_MS=20,this.TIMEOUT_MS=18e4,this.serverUrl=i||(null===globalThis||void 0===globalThis||null===(r=globalThis.location)||void 0===r?void 0:r.origin),!this.serverUrl)throw new t("Invalid serverUrl specified!");if(this.streamId=a,this.objectId=c,!this.streamId)throw new t("Invalid streamId specified!");if(!this.objectId)throw new t("Invalid objectId specified!");this.logger("Object loader constructor called!");try{this.token=n||e.SafeLocalStorage.get("AuthToken")}catch(e){}this.headers={Accept:"text/plain"},this.token&&(this.headers.Authorization="Bearer ".concat(this.token)),this.requestUrlRootObj="".concat(this.serverUrl,"/objects/").concat(this.streamId,"/").concat(this.objectId,"/single"),this.requestUrlChildren="".concat(this.serverUrl,"/api/getobjects/").concat(this.streamId),this.promises=[],this.intervals={},this.buffer=[],this.isLoading=!1,this.totalChildrenCount=0,this.traversedReferencesCount=0,this.options=u,this.options.numConnections=this.options.numConnections||4,this.cacheDB=null,this.lastAsyncPause=Date.now(),this.existingAsyncPause=null,this.preferredFetch=u.fetch,this.fetch=function(){const e=this.preferredFetch||fetch;if(!e)throw new o("Couldn't find fetch implementation! If running in a node environment, make sure you pass it in through the constructor!");return e(...arguments)}}async asyncPause(){Date.now()-this.lastAsyncPause>=100&&(this.lastAsyncPause=Date.now(),this.existingAsyncPause=new Promise((e=>setTimeout(e,0))),await this.existingAsyncPause,this.existingAsyncPause=null,Date.now()-this.lastAsyncPause>500&&this.logger("Loader Event loop lag: ",Date.now()-this.lastAsyncPause))}dispose(){this.buffer=[],this.intervals.forEach((e=>clearInterval(e.interval)))}async getAndConstructObject(e){await this.downloadObjectsInBuffer(e);const s=await this.getObject(this.objectId);return this.traverseAndConstruct(s,e)}async downloadObjectsInBuffer(e){let s=!0,r=0;for await(const t of this.getObjectIterator())s&&(this.totalChildrenCount=t.totalChildrenCount,s=!1,this.isLoading=!0),r++,e&&e({stage:"download",current:r,total:this.totalChildrenCount});this.isLoading=!1}async traverseAndConstruct(e,s){if(e){if("object"!=typeof e)return e;if(Array.isArray(e)&&0!==e.length){var r,t;const o=[];for(const r of e){if("object"!=typeof r&&!this.options.fullyTraverseArrays)return e;const t=r.referencedId?await this.getObject(r.referencedId):r;r.referencedId&&s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount}),o.push(await this.traverseAndConstruct(t,s))}return null!==(r=o[0])&&void 0!==r&&null!==(t=r.speckle_type)&&void 0!==t&&t.toLowerCase().includes("datachunk")?o.reduce(((e,s)=>e.concat(s.data)),[]):o}for(const s of this.options.excludeProps)delete e[s];for(const r in e)"object"==typeof e[r]&&null!==e[r]&&(e[r].referencedId&&(e[r]=await this.getObject(e[r].referencedId),s&&s({stage:"construction",current:++this.traversedReferencesCount>this.totalChildrenCount?this.totalChildrenCount:this.traversedReferencesCount,total:this.totalChildrenCount})),e[r]=await this.traverseAndConstruct(e[r],s));return e}}async getObject(e){if(this.buffer[e])return this.buffer[e];return new Promise(((s,r)=>{if(this.promises.push({id:e,resolve:s,reject:r}),this.intervals[e])this.intervals[e].elapsed=0;else{const s=setInterval(this.tryResolvePromise.bind(this),this.INTERVAL_MS,e);this.intervals[e]={interval:s,elapsed:0}}}))}tryResolvePromise(e){if(this.intervals[e].elapsed+=this.INTERVAL_MS,this.buffer[e]){for(const s of this.promises.filter((s=>s.id===e)))s.resolve(this.buffer[e]);return clearInterval(this.intervals[e].interval),void delete this.intervals[e]}this.intervals[e].elapsed>this.TIMEOUT_MS&&(this.warner("Timeout resolving ".concat(e,". HIC SVNT DRACONES.")),clearInterval(this.intervals[e].interval),this.promises.filter((s=>s.id===e)).forEach((e=>e.reject())),this.promises=this.promises.filter((e=>e.id!=e.id)))}async*getObjectIterator(){const e=Date.now();let s=0;for await(const e of this.getRawObjectIterator()){const{id:r,obj:t}=this.processLine(e);this.buffer[r]=t,s+=1,yield t}this.logger("Loaded ".concat(s," objects in: ").concat((Date.now()-e)/1e3))}processLine(e){const s=e.split("\t");return{id:s[0],obj:JSON.parse(s[1])}}supportsCache(){return!(!this.options.enableCaching||!globalThis.indexedDB)}async setupCacheDb(){if(!this.supportsCache()||null!==this.cacheDB)return;await function(){if(navigator.userAgentData||!/Safari\//.test(navigator.userAgent)||/Chrom(e|ium)\//.test(navigator.userAgent)||!indexedDB.databases)return Promise.resolve();let e;return new Promise((s=>{const r=()=>indexedDB.databases().finally(s);e=setInterval(r,100),r()})).finally((()=>clearInterval(e)))}();const e=indexedDB.open("speckle-object-cache",1);e.onupgradeneeded=()=>e.result.createObjectStore("objects"),this.cacheDB=await this.promisifyIdbRequest(e)}async*getRawObjectIterator(){await this.setupCacheDb();const e=await this.getRawRootObject();yield"".concat(this.objectId,"\t").concat(e);const s=JSON.parse(e);if(!s.__closure)return;let r=Object.keys(s.__closure).filter((e=>!e.includes("blob"))).sort(((e,r)=>s.__closure[e]-s.__closure[r]));if(0===r.length)return;let t=[];if(r.length>50){const e=[[],[],[],[]];let o=0;for(;o<.05*r.length;o++)e[0].push(r[o]);for(;o<.2*r.length;o++)e[1].push(r[o]);for(;o<.6*r.length;o++)e[2].push(r[o]);for(;o<r.length;o++)e[3].push(r[o]);this.logger("Cache check for: ",e);const i=[];let a=this.cacheGetObjects(e[0]);for(let r=0;r<4;r++){const t=await a;r<3&&(a=this.cacheGetObjects(e[r+1]));const o=Object.keys(t).sort(((e,r)=>s.__closure[e]-s.__closure[r]));for(const e of o)yield"".concat(e,"\t").concat(t[e]);const n=e[r].filter((e=>!(e in t)));i.push(...n)}if(0===i.length)return;if(i.length<=50)t.push(i);else{for(t=[[],[],[],[]],o=0;o<.05*i.length;o++)t[0].push(i[o]);for(;o<.2*i.length;o++)t[1].push(i[o]);for(;o<.6*i.length;o++)t[2].push(i[o]);for(;o<i.length;o++)t[3].push(i[o])}}else{const e=await this.cacheGetObjects(r),o=Object.keys(e).sort(((e,r)=>s.__closure[e]-s.__closure[r]));for(const s of o)yield"".concat(s,"\t").concat(e[s]);if(r=r.filter((s=>!(s in e))),0===r.length)return;t.push(r)}const o=[],i=[],a=[],n=[],c=[];for(let e=0;e<t.length;e++)o.push(new TextDecoder),i.push(null),a.push(null),n.push(""),c.push(!1),this.fetch(this.requestUrlChildren,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({objects:JSON.stringify(t[e])})}).then((s=>{s.body.getReader&&(s.body.iterator=async function*(){const e=this.getReader();for(;;){const s=await e.read();if(s.done)return s.value;yield s.value}});const r=s.body.iterator();i[e]=r;const t=r.next().then((s=>(s.reqId=e,s)));a[e]=t}));for(;;){const e=a.filter((e=>!!e));if(0===e.length){if(c.every((e=>e)))break;await new Promise((e=>{setTimeout(e,10)}));continue}const s=await Promise.any(e);let{value:r,done:t,reqId:u}=s;if(c[u]=t,t)n[u].length>0&&(yield n[u],n[u]=""),a[u]=null;else{const e=i[u].next().then((e=>(e.reqId=u,e)));a[u]=e}if(!r)continue;r=o[u].decode(r);const l=(n[u]+r).split(/\r\n|\n|\r/),d=l.pop();n[u]=d;for(const e of l)yield e;this.cacheStoreObjects(l)}}async getRawRootObject(){const e=await this.cacheGetObjects([this.objectId]);if(e[this.objectId])return e[this.objectId];const s=await this.fetch(this.requestUrlRootObj,{headers:this.headers}),r=await s.text();return this.cacheStoreObjects(["".concat(this.objectId,"\t").concat(r)]),r}promisifyIdbRequest(e){return new Promise(((s,r)=>{e.oncomplete=e.onsuccess=()=>s(e.result),e.onabort=e.onerror=()=>r(e.error)}))}async cacheGetObjects(e){if(!this.supportsCache())return{};const s={};for(let r=0;r<e.length;r+=500){const t=e.slice(r,r+500),o=this.cacheDB.transaction("objects","readonly").objectStore("objects"),i=t.map((e=>this.promisifyIdbRequest(o.get(e)).then((s=>({id:e,data:s}))))),a=await Promise.all(i);for(const e of a)e.data&&(s[e.id]=e.data)}return s}cacheStoreObjects(e){if(!this.supportsCache())return{};const s=this.cacheDB.transaction("objects","readwrite").objectStore("objects");for(const r of e){const e=r.split("\t");s.put(e[1],e[0])}return this.promisifyIdbRequest(s.transaction)}};
|
package/examples/node/script.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Since Node v<18 does not provide fetch, we need to pass it in the options object. Popular fetch implementations like cross-fetch or node-fetch should work
|
|
3
|
+
*/
|
|
4
|
+
import fetch from 'cross-fetch'
|
|
3
5
|
import ObjectLoader from '../../dist/objectloader.js'
|
|
4
6
|
|
|
5
7
|
const loader = new ObjectLoader({
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speckle/objectloader",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "Simple API helper to stream in objects from the Speckle Server.",
|
|
5
5
|
"main": "dist/objectloader.js",
|
|
6
6
|
"module": "dist/objectloader.esm.js",
|
|
7
|
+
"types": "types/index.d.ts",
|
|
7
8
|
"homepage": "https://speckle.systems",
|
|
8
9
|
"repository": {
|
|
9
10
|
"type": "git",
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
"license": "Apache-2.0",
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"@babel/core": "^7.17.9",
|
|
34
|
+
"@speckle/shared": "^2.9.1",
|
|
33
35
|
"core-js": "^3.21.1",
|
|
34
36
|
"regenerator-runtime": "^0.13.7"
|
|
35
37
|
},
|
|
@@ -40,14 +42,14 @@
|
|
|
40
42
|
"@rollup/plugin-babel": "^5.3.1",
|
|
41
43
|
"@rollup/plugin-commonjs": "^21.0.3",
|
|
42
44
|
"@rollup/plugin-node-resolve": "^13.1.3",
|
|
45
|
+
"cross-fetch": "^3.1.5",
|
|
43
46
|
"eslint": "^8.11.0",
|
|
44
47
|
"eslint-config-prettier": "^8.5.0",
|
|
45
48
|
"http-server": "^14.1.0",
|
|
46
49
|
"prettier": "^2.5.1",
|
|
47
50
|
"rollup": "^2.70.1",
|
|
48
51
|
"rollup-plugin-delete": "^2.0.0",
|
|
49
|
-
"rollup-plugin-terser": "^7.0.2"
|
|
50
|
-
"undici": "^4.14.1"
|
|
52
|
+
"rollup-plugin-terser": "^7.0.2"
|
|
51
53
|
},
|
|
52
54
|
"gitHead": "5627e490f9a3ecadf19cc4686ad15f344d9ad2d3"
|
|
53
55
|
}
|
package/readme.md
CHANGED
|
@@ -64,11 +64,11 @@ let obj = await loader.getAndConstructObject((e) => console.log('Progress', e))
|
|
|
64
64
|
|
|
65
65
|
### On the server
|
|
66
66
|
|
|
67
|
-
Since Node.js does not yet support the [`fetch API`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch), you'll need to provide your own `fetch` function in the options object.
|
|
67
|
+
Since Node.js does not yet support the [`fetch API`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch), you'll need to provide your own `fetch` function in the options object, e.g., from `node-fetch` or `cross-fetch`
|
|
68
68
|
|
|
69
69
|
```js
|
|
70
70
|
import ObjectLoader from '@speckle/objectloader'
|
|
71
|
-
import
|
|
71
|
+
import fetch from 'cross-fetch'
|
|
72
72
|
|
|
73
73
|
let loader = new ObjectLoader({
|
|
74
74
|
serverUrl: 'https://latest.speckle.dev',
|
|
@@ -83,6 +83,12 @@ let loader = new ObjectLoader({
|
|
|
83
83
|
Run `yarn build` to build prod release, run `yarn build:dev` to build dev release.
|
|
84
84
|
Or run `yarn dev` to run the build in `watch` mode.
|
|
85
85
|
|
|
86
|
+
### TS types
|
|
87
|
+
|
|
88
|
+
The library isn't written in TypeScript so there's no typing information to be generated out of the box, but since we do want this library to be usable in TypeScript projects we write the types ourselves (for now).
|
|
89
|
+
|
|
90
|
+
So whenever you make any changes to the API, make sure the types file in `types/index.d.ts` is updated
|
|
91
|
+
|
|
86
92
|
## Community
|
|
87
93
|
|
|
88
94
|
If in trouble, the Speckle Community hangs out on [the forum](https://speckle.community). Do join and introduce yourself! We're happy to help.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base ObjectLoader error
|
|
3
|
+
*/
|
|
4
|
+
class BaseError extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* Default message if none is passed
|
|
7
|
+
*/
|
|
8
|
+
static defaultMessage = 'Unexpected error occurred'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} [message]
|
|
12
|
+
* @param {ErrorOptions} options
|
|
13
|
+
*/
|
|
14
|
+
constructor(message, options) {
|
|
15
|
+
message ||= new.target.defaultMessage
|
|
16
|
+
super(message, options)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ObjectLoaderConfigurationError extends BaseError {
|
|
21
|
+
static defaultMessage = 'Object loader configured incorrectly!'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class ObjectLoaderRuntimeError extends BaseError {
|
|
25
|
+
static defaultMessage = 'Object loader encountered a runtime problem!'
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This adjusts a browser ReadableStream to make it work similarly to Node streams, which further enables us to use the
|
|
3
|
+
* same code to read both kinds of streams. We don't mutate the ReadableStream prototype cause this specific polyfill
|
|
4
|
+
* might not work well in other circumstances (https://github.com/node-fetch/node-fetch/issues/387#issuecomment-417433509)
|
|
5
|
+
*
|
|
6
|
+
* See more: https://github.com/node-fetch/node-fetch/issues/754#issuecomment-602184022
|
|
7
|
+
* @param {ReadableStream} stream
|
|
8
|
+
*/
|
|
9
|
+
export function polyfillReadableStreamForAsyncIterator(stream) {
|
|
10
|
+
stream.iterator = async function* () {
|
|
11
|
+
const reader = this.getReader()
|
|
12
|
+
while (1) {
|
|
13
|
+
const chunk = await reader.read()
|
|
14
|
+
if (chunk.done) return chunk.value
|
|
15
|
+
yield chunk.value
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
import 'core-js'
|
|
3
3
|
import 'regenerator-runtime/runtime'
|
|
4
4
|
|
|
5
|
+
import { SafeLocalStorage } from '@speckle/shared'
|
|
6
|
+
import {
|
|
7
|
+
ObjectLoaderConfigurationError,
|
|
8
|
+
ObjectLoaderRuntimeError
|
|
9
|
+
} from './errors/index.js'
|
|
10
|
+
import { polyfillReadableStreamForAsyncIterator } from './helpers/stream.js'
|
|
11
|
+
|
|
5
12
|
/**
|
|
6
13
|
* Simple client that streams object info from a Speckle Server.
|
|
7
14
|
* TODO: Object construction progress reporting is weird.
|
|
@@ -21,18 +28,34 @@ export default class ObjectLoader {
|
|
|
21
28
|
enableCaching: true,
|
|
22
29
|
fullyTraverseArrays: false,
|
|
23
30
|
excludeProps: [],
|
|
24
|
-
fetch: null
|
|
31
|
+
fetch: null,
|
|
32
|
+
customLogger: undefined,
|
|
33
|
+
customWarner: undefined
|
|
25
34
|
}
|
|
26
35
|
}) {
|
|
36
|
+
this.logger = options.customLogger || console.log
|
|
37
|
+
this.warner = options.customWarner || console.warn
|
|
38
|
+
|
|
27
39
|
this.INTERVAL_MS = 20
|
|
28
40
|
this.TIMEOUT_MS = 180000 // three mins
|
|
29
41
|
|
|
30
|
-
this.serverUrl = serverUrl ||
|
|
42
|
+
this.serverUrl = serverUrl || globalThis?.location?.origin
|
|
43
|
+
if (!this.serverUrl) {
|
|
44
|
+
throw new ObjectLoaderConfigurationError('Invalid serverUrl specified!')
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
this.streamId = streamId
|
|
32
48
|
this.objectId = objectId
|
|
33
|
-
|
|
49
|
+
if (!this.streamId) {
|
|
50
|
+
throw new ObjectLoaderConfigurationError('Invalid streamId specified!')
|
|
51
|
+
}
|
|
52
|
+
if (!this.objectId) {
|
|
53
|
+
throw new ObjectLoaderConfigurationError('Invalid objectId specified!')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.logger('Object loader constructor called!')
|
|
34
57
|
try {
|
|
35
|
-
this.token = token ||
|
|
58
|
+
this.token = token || SafeLocalStorage.get('AuthToken')
|
|
36
59
|
} catch (error) {
|
|
37
60
|
// Accessing localStorage may throw when executing on sandboxed document, ignore.
|
|
38
61
|
}
|
|
@@ -56,6 +79,7 @@ export default class ObjectLoader {
|
|
|
56
79
|
this.options = options
|
|
57
80
|
this.options.numConnections = this.options.numConnections || 4
|
|
58
81
|
|
|
82
|
+
/** @type {IDBDatabase | null} */
|
|
59
83
|
this.cacheDB = null
|
|
60
84
|
|
|
61
85
|
this.lastAsyncPause = Date.now()
|
|
@@ -64,8 +88,16 @@ export default class ObjectLoader {
|
|
|
64
88
|
// we can't simply bind fetch to this.fetch, so instead we have to do some acrobatics:
|
|
65
89
|
// https://stackoverflow.com/questions/69337187/uncaught-in-promise-typeerror-failed-to-execute-fetch-on-workerglobalscope#comment124731316_69337187
|
|
66
90
|
this.preferredFetch = options.fetch
|
|
91
|
+
|
|
92
|
+
/** @type {globalThis.fetch} */
|
|
67
93
|
this.fetch = function (...args) {
|
|
68
94
|
const currentFetch = this.preferredFetch || fetch
|
|
95
|
+
if (!currentFetch) {
|
|
96
|
+
throw new ObjectLoaderRuntimeError(
|
|
97
|
+
"Couldn't find fetch implementation! If running in a node environment, make sure you pass it in through the constructor!"
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
69
101
|
return currentFetch(...args)
|
|
70
102
|
}
|
|
71
103
|
}
|
|
@@ -81,7 +113,7 @@ export default class ObjectLoader {
|
|
|
81
113
|
await this.existingAsyncPause
|
|
82
114
|
this.existingAsyncPause = null
|
|
83
115
|
if (Date.now() - this.lastAsyncPause > 500)
|
|
84
|
-
|
|
116
|
+
this.logger('Loader Event loop lag: ', Date.now() - this.lastAsyncPause)
|
|
85
117
|
}
|
|
86
118
|
}
|
|
87
119
|
|
|
@@ -237,7 +269,7 @@ export default class ObjectLoader {
|
|
|
237
269
|
}
|
|
238
270
|
|
|
239
271
|
if (this.intervals[id].elapsed > this.TIMEOUT_MS) {
|
|
240
|
-
|
|
272
|
+
this.warner(`Timeout resolving ${id}. HIC SVNT DRACONES.`)
|
|
241
273
|
clearInterval(this.intervals[id].interval)
|
|
242
274
|
this.promises.filter((p) => p.id === id).forEach((p) => p.reject())
|
|
243
275
|
this.promises = this.promises.filter((p) => p.id !== p.id) // clear out
|
|
@@ -253,7 +285,7 @@ export default class ObjectLoader {
|
|
|
253
285
|
count += 1
|
|
254
286
|
yield obj
|
|
255
287
|
}
|
|
256
|
-
|
|
288
|
+
this.logger(`Loaded ${count} objects in: ${(Date.now() - t0) / 1000}`)
|
|
257
289
|
}
|
|
258
290
|
|
|
259
291
|
processLine(chunk) {
|
|
@@ -261,26 +293,36 @@ export default class ObjectLoader {
|
|
|
261
293
|
return { id: pieces[0], obj: JSON.parse(pieces[1]) }
|
|
262
294
|
}
|
|
263
295
|
|
|
296
|
+
supportsCache() {
|
|
297
|
+
return !!(this.options.enableCaching && globalThis.indexedDB)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async setupCacheDb() {
|
|
301
|
+
if (!this.supportsCache() || this.cacheDB !== null) return
|
|
302
|
+
|
|
303
|
+
// Initialize
|
|
304
|
+
await safariFix()
|
|
305
|
+
const idbOpenRequest = indexedDB.open('speckle-object-cache', 1)
|
|
306
|
+
idbOpenRequest.onupgradeneeded = () =>
|
|
307
|
+
idbOpenRequest.result.createObjectStore('objects')
|
|
308
|
+
this.cacheDB = await this.promisifyIdbRequest(idbOpenRequest)
|
|
309
|
+
}
|
|
310
|
+
|
|
264
311
|
async *getRawObjectIterator() {
|
|
265
|
-
|
|
266
|
-
await safariFix()
|
|
267
|
-
const idbOpenRequest = indexedDB.open('speckle-object-cache', 1)
|
|
268
|
-
idbOpenRequest.onupgradeneeded = () =>
|
|
269
|
-
idbOpenRequest.result.createObjectStore('objects')
|
|
270
|
-
this.cacheDB = await this.promisifyIdbRequest(idbOpenRequest)
|
|
271
|
-
}
|
|
312
|
+
await this.setupCacheDb()
|
|
272
313
|
|
|
273
314
|
const rootObjJson = await this.getRawRootObject()
|
|
274
|
-
//
|
|
315
|
+
// this.logger("Root in: ", Date.now() - tSTART)
|
|
275
316
|
|
|
276
317
|
yield `${this.objectId}\t${rootObjJson}`
|
|
277
318
|
|
|
278
319
|
const rootObj = JSON.parse(rootObjJson)
|
|
279
320
|
if (!rootObj.__closure) return
|
|
280
321
|
|
|
281
|
-
let childrenIds = Object.keys(rootObj.__closure)
|
|
282
|
-
(
|
|
283
|
-
|
|
322
|
+
let childrenIds = Object.keys(rootObj.__closure)
|
|
323
|
+
.filter((id) => !id.includes('blob'))
|
|
324
|
+
.sort((a, b) => rootObj.__closure[a] - rootObj.__closure[b])
|
|
325
|
+
|
|
284
326
|
if (childrenIds.length === 0) return
|
|
285
327
|
|
|
286
328
|
let splitHttpRequests = []
|
|
@@ -303,7 +345,7 @@ export default class ObjectLoader {
|
|
|
303
345
|
splitBeforeCacheCheck[3].push(childrenIds[crtChildIndex])
|
|
304
346
|
}
|
|
305
347
|
|
|
306
|
-
|
|
348
|
+
this.logger('Cache check for: ', splitBeforeCacheCheck)
|
|
307
349
|
|
|
308
350
|
const newChildren = []
|
|
309
351
|
let nextCachePromise = this.cacheGetObjects(splitBeforeCacheCheck[0])
|
|
@@ -385,9 +427,15 @@ export default class ObjectLoader {
|
|
|
385
427
|
headers: { ...this.headers, 'Content-Type': 'application/json' },
|
|
386
428
|
body: JSON.stringify({ objects: JSON.stringify(splitHttpRequests[i]) })
|
|
387
429
|
}).then((crtResponse) => {
|
|
388
|
-
|
|
430
|
+
// Polyfill web streams so that we can work with them the same way we do in Node
|
|
431
|
+
if (crtResponse.body.getReader) {
|
|
432
|
+
polyfillReadableStreamForAsyncIterator(crtResponse.body)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Get stream async iterator
|
|
436
|
+
const crtReader = crtResponse.body.iterator()
|
|
389
437
|
readers[i] = crtReader
|
|
390
|
-
const crtReadPromise = crtReader.
|
|
438
|
+
const crtReadPromise = crtReader.next().then((x) => {
|
|
391
439
|
x.reqId = i
|
|
392
440
|
return x
|
|
393
441
|
})
|
|
@@ -417,7 +465,7 @@ export default class ObjectLoader {
|
|
|
417
465
|
|
|
418
466
|
// Replace read promise on this request with a new `read` call
|
|
419
467
|
if (!readerDone) {
|
|
420
|
-
const crtReadPromise = readers[reqId].
|
|
468
|
+
const crtReadPromise = readers[reqId].next().then((x) => {
|
|
421
469
|
x.reqId = reqId
|
|
422
470
|
return x
|
|
423
471
|
})
|
|
@@ -464,7 +512,7 @@ export default class ObjectLoader {
|
|
|
464
512
|
}
|
|
465
513
|
|
|
466
514
|
async cacheGetObjects(ids) {
|
|
467
|
-
if (!this.
|
|
515
|
+
if (!this.supportsCache()) {
|
|
468
516
|
return {}
|
|
469
517
|
}
|
|
470
518
|
|
|
@@ -481,7 +529,7 @@ export default class ObjectLoader {
|
|
|
481
529
|
)
|
|
482
530
|
const cachedData = await Promise.all(idbChildrenPromises)
|
|
483
531
|
|
|
484
|
-
//
|
|
532
|
+
// this.logger("Cache check for : ", idsChunk.length, Date.now() - t0)
|
|
485
533
|
|
|
486
534
|
for (const cachedObj of cachedData) {
|
|
487
535
|
if (!cachedObj.data)
|
|
@@ -495,7 +543,7 @@ export default class ObjectLoader {
|
|
|
495
543
|
}
|
|
496
544
|
|
|
497
545
|
cacheStoreObjects(objects) {
|
|
498
|
-
if (!this.
|
|
546
|
+
if (!this.supportsCache()) {
|
|
499
547
|
return {}
|
|
500
548
|
}
|
|
501
549
|
|
|
@@ -511,7 +559,10 @@ export default class ObjectLoader {
|
|
|
511
559
|
}
|
|
512
560
|
}
|
|
513
561
|
|
|
514
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Fixes a Safari bug where IndexedDB requests get lost and never resolve - invoke before you use IndexedDB
|
|
564
|
+
* @link Credits and more info: https://github.com/jakearchibald/safari-14-idb-fix
|
|
565
|
+
*/
|
|
515
566
|
function safariFix() {
|
|
516
567
|
const isSafari =
|
|
517
568
|
!navigator.userAgentData &&
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is written manually & should be kept up to date when the API changes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface SpeckleObject extends Record<string, unknown> {
|
|
6
|
+
totalChildrenCount?: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type Logger = (...args: unknown[]) => void
|
|
10
|
+
|
|
11
|
+
export type ProgressStage = 'download' | 'construction'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ObjectLoader class
|
|
15
|
+
*/
|
|
16
|
+
export default class ObjectLoader {
|
|
17
|
+
constructor(params: {
|
|
18
|
+
serverUrl: string
|
|
19
|
+
streamId: string
|
|
20
|
+
objectId: string
|
|
21
|
+
token?: string
|
|
22
|
+
options?: Partial<{
|
|
23
|
+
/**
|
|
24
|
+
* Whether IndexedDB caching is enabled (disabled by default in node envs where IndexedDB is not available)
|
|
25
|
+
*/
|
|
26
|
+
enableCaching: boolean
|
|
27
|
+
fullyTraverseArrays: boolean
|
|
28
|
+
excludeProps: Array
|
|
29
|
+
/**
|
|
30
|
+
* Override fetch implementation (necessary in node environment)
|
|
31
|
+
*/
|
|
32
|
+
fetch: GlobalFetch['fetch']
|
|
33
|
+
/**
|
|
34
|
+
* Optionally provide alternative for console.log
|
|
35
|
+
*/
|
|
36
|
+
customLogger: Logger
|
|
37
|
+
/**
|
|
38
|
+
* Optionally provide alternative for console.warn
|
|
39
|
+
*/
|
|
40
|
+
customWarner: Logger
|
|
41
|
+
}>
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
async getAndConstructObject(
|
|
45
|
+
onProgress: (e: { stage: ProgressStage; current: number; total: number }) => void
|
|
46
|
+
): SpeckleObject | SpeckleObject[]
|
|
47
|
+
|
|
48
|
+
async *getObjectIterator(): Generator<SpeckleObject, SpeckleObject>
|
|
49
|
+
}
|