pict 1.0.387 → 1.0.388

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/dist/pict.js CHANGED
@@ -6218,7 +6218,7 @@ try{if(!global.localStorage)return false;}catch(_){return false;}var val=global.
6218
6218
  // presumably different callback function.
6219
6219
  // This makes sure that own properties are retained, so that
6220
6220
  // decorations and such are not lost along the way.
6221
- module.exports=wrappy;function wrappy(fn,cb){if(fn&&cb)return wrappy(fn)(cb);if(typeof fn!=='function')throw new TypeError('need wrapper function');Object.keys(fn).forEach(function(k){wrapper[k]=fn[k];});return wrapper;function wrapper(){var args=new Array(arguments.length);for(var i=0;i<args.length;i++){args[i]=arguments[i];}var ret=fn.apply(this,args);var cb=args[args.length-1];if(typeof ret==='function'&&ret!==cb){Object.keys(cb).forEach(function(k){ret[k]=cb[k];});}return ret;}}},{}],191:[function(require,module,exports){module.exports=extend;var hasOwnProperty=Object.prototype.hasOwnProperty;function extend(){var target={};for(var i=0;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;}},{}],192:[function(require,module,exports){module.exports={"name":"pict","version":"1.0.387","description":"Pict browser library.","main":"source/Pict.js","scripts":{"start":"node source/Pict.js","test":"npx quack test","tests":"npx quack test -g","coverage":"npx quack coverage","build":"npx quack build","prepublishOnly":"npx quack build","docker-dev-build":"docker build ./ -f Dockerfile_LUXURYCode -t pict-image:local","docker-dev-run":"docker run -it -d --name pict-dev -p 37447:8080 -p 19506:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/pict\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" pict-image:local","docker-dev-shell":"docker exec -it pict-dev /bin/bash","lint":"eslint source/**/*.js test/**/*.js","types":"tsc -p ."},"files":["source/","dist/","types/"],"types":"types/source/Pict.d.ts","mocha":{"diff":true,"extension":["js"],"package":"./package.json","reporter":"spec","slow":"75","timeout":"5000","ui":"tdd","watch-files":["source/**/*.js","test/**/*.js"],"watch-ignore":["lib/vendor"]},"repository":{"type":"git","url":"git+https://stevenvelozo@github.com/stevenvelozo/pict.git"},"author":"steven velozo <steven@velozo.com>","license":"MIT","bugs":{"url":"https://github.com/fable-retold/pict/issues"},"homepage":"https://github.com/fable-retold/pict#readme","devDependencies":{"@eslint/js":"^9.39.1","@types/jquery":"^3.5.33","@types/sinon":"^17.0.4","eslint":"^9.39.1","globals":"^16.5.0","pict-docuserve":"^1.4.19","quackage":"^1.3.0","sinon":"^20.0.0","typescript":"^5.9.3"},"dependencies":{"cachetrax":"^1.0.6","fable":"^3.1.78","pict-application":"^1.0.34","pict-provider":"^1.0.13","pict-template":"^1.0.15","pict-view":"^1.0.68"}};},{}],193:[function(require,module,exports){// This assumes Pict has been required in the browser. Delcare these as globals so linter can do its job.
6221
+ module.exports=wrappy;function wrappy(fn,cb){if(fn&&cb)return wrappy(fn)(cb);if(typeof fn!=='function')throw new TypeError('need wrapper function');Object.keys(fn).forEach(function(k){wrapper[k]=fn[k];});return wrapper;function wrapper(){var args=new Array(arguments.length);for(var i=0;i<args.length;i++){args[i]=arguments[i];}var ret=fn.apply(this,args);var cb=args[args.length-1];if(typeof ret==='function'&&ret!==cb){Object.keys(cb).forEach(function(k){ret[k]=cb[k];});}return ret;}}},{}],191:[function(require,module,exports){module.exports=extend;var hasOwnProperty=Object.prototype.hasOwnProperty;function extend(){var target={};for(var i=0;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(hasOwnProperty.call(source,key)){target[key]=source[key];}}}return target;}},{}],192:[function(require,module,exports){module.exports={"name":"pict","version":"1.0.388","description":"Pict browser library.","main":"source/Pict.js","scripts":{"start":"node source/Pict.js","test":"npx quack test","tests":"npx quack test -g","coverage":"npx quack coverage","build":"npx quack build","prepublishOnly":"npx quack build","docker-dev-build":"docker build ./ -f Dockerfile_LUXURYCode -t pict-image:local","docker-dev-run":"docker run -it -d --name pict-dev -p 37447:8080 -p 19506:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/pict\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" pict-image:local","docker-dev-shell":"docker exec -it pict-dev /bin/bash","lint":"eslint source/**/*.js test/**/*.js","types":"tsc -p ."},"files":["source/","dist/","types/"],"types":"types/source/Pict.d.ts","mocha":{"diff":true,"extension":["js"],"package":"./package.json","reporter":"spec","slow":"75","timeout":"5000","ui":"tdd","watch-files":["source/**/*.js","test/**/*.js"],"watch-ignore":["lib/vendor"]},"repository":{"type":"git","url":"git+https://stevenvelozo@github.com/stevenvelozo/pict.git"},"author":"steven velozo <steven@velozo.com>","license":"MIT","bugs":{"url":"https://github.com/fable-retold/pict/issues"},"homepage":"https://github.com/fable-retold/pict#readme","devDependencies":{"@eslint/js":"^9.39.1","@types/jquery":"^3.5.33","@types/sinon":"^17.0.4","eslint":"^9.39.1","globals":"^16.5.0","pict-docuserve":"^1.4.19","quackage":"^1.3.0","sinon":"^20.0.0","typescript":"^5.9.3"},"dependencies":{"cachetrax":"^1.0.6","fable":"^3.1.78","pict-application":"^1.0.34","pict-provider":"^1.0.13","pict-template":"^1.0.15","pict-view":"^1.0.68"}};},{}],193:[function(require,module,exports){// This assumes Pict has been required in the browser. Delcare these as globals so linter can do its job.
6222
6222
  /* global Pict, _Pict: writeable *//**
6223
6223
  * Simple function to load a pict Application
6224
6224
  *
@@ -6435,12 +6435,108 @@ const tmpTargetElement=typeof pAddress==='string'?window.jQuery(pAddress):window
6435
6435
  */toggleClass(pAddress,pClass){if(this.hasClass(pAddress,pClass)){this.removeClass(pAddress,pClass);}else{this.addClass(pAddress,pClass);}}}module.exports=PictContentAssignment;},{"fable":68}],197:[function(require,module,exports){const libFableServiceBase=require('fable').ServiceProviderBase;class PictDataProvider extends libFableServiceBase{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);/** @type {import('fable') & import('./Pict')} */this.fable;this.serviceType='PictDataProvider';}/**
6436
6436
  * @param {string} pAddress - The address of the data to retrieve
6437
6437
  * @param {object} [pData] - (optional) The record to provide to the address resolver
6438
- */getDataByAddress(pAddress,pData){let tmpData=typeof pData==='undefined'?{}:pData;const tmpAddressSpace={Fable:this.fable,Pict:this.fable,AppData:this.fable.AppData,Bundle:this.fable.Bundle,Record:tmpData};return this.fable.manifest.getValueByHash(tmpAddressSpace,pAddress);}}module.exports=PictDataProvider;},{"fable":68}],198:[function(require,module,exports){const libFableServiceBase=require('fable').ServiceProviderBase;class PictMeadowEntityProvider extends libFableServiceBase{constructor(pFable,pOptions,pServiceHash){var _this$fable$RestClien;super(pFable,pOptions,pServiceHash);/** @type {any} */this.options;/** @type {import('./Pict') & { settings: any } & { newAnticipate: any }} */this.fable;/** @type {any} */this.log;this.serviceType='PictMeadowProvider';if(this.fable.settings.PictDefaultURLPrefix){this.options.urlPrefix=this.fable.settings.PictDefaultURLPrefix;}else if(!this.options.urlPrefix){this.options.urlPrefix='/1.0/';}if(!this.options.downloadBatchSize){if(typeof this.fable.settings.PictDefaultDownloadBatchSize==='number'){this.options.downloadBatchSize=this.fable.settings.PictDefaultDownloadBatchSize;}else{this.options.downloadBatchSize=100;}}if(typeof this.options.downloadPageConcurrency!=='number'){this.options.downloadPageConcurrency=typeof this.fable.settings.PictDefaultDownloadPageConcurrency==='number'?this.fable.settings.PictDefaultDownloadPageConcurrency:4;}if(typeof this.options.maxBundleConcurrency!=='number'){this.options.maxBundleConcurrency=typeof this.fable.settings.PictDefaultMaxBundleConcurrency==='number'?this.fable.settings.PictDefaultMaxBundleConcurrency:8;}//@ts-ignore - FIXME - remove once we have fable types
6438
+ */getDataByAddress(pAddress,pData){let tmpData=typeof pData==='undefined'?{}:pData;const tmpAddressSpace={Fable:this.fable,Pict:this.fable,AppData:this.fable.AppData,Bundle:this.fable.Bundle,Record:tmpData};return this.fable.manifest.getValueByHash(tmpAddressSpace,pAddress);}}module.exports=PictDataProvider;},{"fable":68}],198:[function(require,module,exports){const libFableServiceBase=require('fable').ServiceProviderBase;// Minimum meadow-endpoints version, per major, that serves the body-driven
6439
+ // POST /:Entity/Query read route. The route was added in 2.1.0 and 4.1.0 but
6440
+ // never shipped on the 3.x line or 4.0.x — so support is NOT monotonic across
6441
+ // majors and a flat ">= x" comparison would be wrong. Used only as a fallback
6442
+ // when the server does not advertise an explicit Capabilities flag.
6443
+ const QUERY_ENDPOINT_MIN_VERSION_BY_MAJOR={2:'2.1.0',4:'4.1.0'};class PictMeadowEntityProvider extends libFableServiceBase{constructor(pFable,pOptions,pServiceHash){var _this$fable$RestClien;super(pFable,pOptions,pServiceHash);/** @type {any} */this.options;/** @type {import('./Pict') & { settings: any } & { newAnticipate: any }} */this.fable;/** @type {any} */this.log;this.serviceType='PictMeadowProvider';if(this.fable.settings.PictDefaultURLPrefix){this.options.urlPrefix=this.fable.settings.PictDefaultURLPrefix;}else if(!this.options.urlPrefix){this.options.urlPrefix='/1.0/';}if(!this.options.downloadBatchSize){if(typeof this.fable.settings.PictDefaultDownloadBatchSize==='number'){this.options.downloadBatchSize=this.fable.settings.PictDefaultDownloadBatchSize;}else{this.options.downloadBatchSize=100;}}if(typeof this.options.downloadPageConcurrency!=='number'){this.options.downloadPageConcurrency=typeof this.fable.settings.PictDefaultDownloadPageConcurrency==='number'?this.fable.settings.PictDefaultDownloadPageConcurrency:4;}if(typeof this.options.maxBundleConcurrency!=='number'){this.options.maxBundleConcurrency=typeof this.fable.settings.PictDefaultMaxBundleConcurrency==='number'?this.fable.settings.PictDefaultMaxBundleConcurrency:8;}//@ts-ignore - FIXME - remove once we have fable types
6439
6444
  this.restClient=(_this$fable$RestClien=this.fable.RestClient)!==null&&_this$fable$RestClien!==void 0?_this$fable$RestClien:this.fable.instantiateServiceProviderWithoutRegistration('RestClient');/** @type {Record<string, import('cachetrax')>} */this.recordCache={};/** @type {Record<string, import('cachetrax')>} */this.recordSetCache={};this.entityColumnTranslations={CreatingIDUser:'User',UpdatingIDUser:'User',DeletingIDUser:'User'};/** @type {(pOptions: Record<string, any>) => Record<string, any>} */this.prepareRequestOptions=pOptions=>{return pOptions;};/**
6440
6445
  * After buildBundleWaves() is called by gatherDataFromServer(), this
6441
6446
  * property holds the computed wave schedule for inspection/debugging.
6442
6447
  * @type {Array<Array<{Index: number, Step: Record<string, any>}>>|null}
6443
- */this.lastBundleWaves=null;}/**
6448
+ */this.lastBundleWaves=null;// Master switch for the body-driven POST /:Entity/Query transport. When
6449
+ // false the provider always uses the legacy GET reads (no capability
6450
+ // probe is ever issued). Defaults on; opt out via option or setting.
6451
+ if(typeof this.options.UseQueryEndpoint==='boolean'){this.useQueryEndpoint=this.options.UseQueryEndpoint;}else if(typeof this.fable.settings.PictMeadowUseQueryEndpoint==='boolean'){this.useQueryEndpoint=this.fable.settings.PictMeadowUseQueryEndpoint;}else{this.useQueryEndpoint=true;}/**
6452
+ * Per-(urlPrefix, entity) capability cache. Different entities can resolve
6453
+ * to different backend services (and thus different meadow-endpoints
6454
+ * versions) behind the same urlPrefix, so support is cached per entity.
6455
+ * @type {Record<string, { SupportsQuery: boolean, Metadata: (Record<string, any>|null) }>}
6456
+ */this.endpointCapabilityCache={};/**
6457
+ * In-flight capability probes, keyed identically to the cache, so
6458
+ * concurrent reads of the same entity collapse onto a single Schema probe.
6459
+ * @type {Record<string, Array<(pError: Error|null, pSupportsQuery: boolean) => void>>}
6460
+ */this.endpointCapabilityInflight={};}/**
6461
+ * Compute the capability cache key for an (entity, urlPrefix) pair.
6462
+ *
6463
+ * @param {string} pEntity - The entity name.
6464
+ * @param {string} [pURLPrefix] - The URL prefix in play (defaults to the provider default).
6465
+ * @return {string} The cache key.
6466
+ */_capabilityKey(pEntity,pURLPrefix){return`${pURLPrefix||this.options.urlPrefix}::${pEntity}`;}/**
6467
+ * Major-version-aware check of whether a meadow-endpoints version string
6468
+ * serves the POST /:Entity/Query route. Support is keyed off the major
6469
+ * version (see QUERY_ENDPOINT_MIN_VERSION_BY_MAJOR) because the route was
6470
+ * backported to 2.1.0 and added in 4.1.0, but absent on 3.x and 4.0.x.
6471
+ *
6472
+ * @param {string} pVersion - A semver string (e.g. '4.1.0').
6473
+ * @return {boolean} True if the version is known to serve the Query route.
6474
+ */isMeadowEndpointsVersionQueryCapable(pVersion){if(typeof pVersion!=='string'){return false;}const tmpParts=pVersion.split('.').map(pPart=>{return parseInt(pPart,10);});if(tmpParts.length<1||isNaN(tmpParts[0])){return false;}const tmpMajor=tmpParts[0];const tmpMinimum=QUERY_ENDPOINT_MIN_VERSION_BY_MAJOR[tmpMajor];if(!tmpMinimum){return false;}const tmpMinimumParts=tmpMinimum.split('.').map(pPart=>{return parseInt(pPart,10);});// Lexicographic compare of [major, minor, patch] with missing parts as 0.
6475
+ for(let i=0;i<3;i++){const tmpHave=isNaN(tmpParts[i])?0:tmpParts[i];const tmpNeed=isNaN(tmpMinimumParts[i])?0:tmpMinimumParts[i];if(tmpHave>tmpNeed){return true;}if(tmpHave<tmpNeed){return false;}}return true;}/**
6476
+ * Decide, from a Schema endpoint response body, whether the serving
6477
+ * meadow-endpoints supports the POST /:Entity/Query route. An explicit
6478
+ * Capabilities flag wins when present; otherwise fall back to a
6479
+ * major-version-aware check of the advertised meadow-endpoints version.
6480
+ * Servers that advertise nothing (older deployments) are unsupported.
6481
+ *
6482
+ * @param {Record<string, any>} pSchemaBody - The Schema endpoint response body.
6483
+ * @return {boolean} True if POST /:Entity/Query is supported.
6484
+ */evaluateQueryEndpointSupport(pSchemaBody){if(!pSchemaBody||typeof pSchemaBody!=='object'){return false;}const tmpMetadata=pSchemaBody.RetoldMetadata;if(!tmpMetadata||typeof tmpMetadata!=='object'){return false;}if(tmpMetadata.Capabilities&&typeof tmpMetadata.Capabilities.QueryEndpoint==='boolean'){return tmpMetadata.Capabilities.QueryEndpoint;}const tmpVersion=tmpMetadata.PackageVersions&&tmpMetadata.PackageVersions['meadow-endpoints'];if(typeof tmpVersion==='string'){return this.isMeadowEndpointsVersionQueryCapable(tmpVersion);}return false;}/**
6485
+ * Seed the capability cache for an entity from a Schema response the caller
6486
+ * already has in hand (e.g. pict-section-recordset fetches the schema during
6487
+ * initialization). Avoids a redundant capability probe.
6488
+ *
6489
+ * @param {string} pEntity - The entity name.
6490
+ * @param {Record<string, any>} pSchemaBody - The Schema endpoint response body.
6491
+ * @param {string} [pURLPrefix] - The URL prefix the schema was fetched from.
6492
+ * @return {boolean} The resolved support value now cached.
6493
+ */primeEntityCapabilityFromSchema(pEntity,pSchemaBody,pURLPrefix){const tmpSupportsQuery=this.evaluateQueryEndpointSupport(pSchemaBody);this.endpointCapabilityCache[this._capabilityKey(pEntity,pURLPrefix)]={SupportsQuery:tmpSupportsQuery,Metadata:pSchemaBody&&pSchemaBody.RetoldMetadata||null};return tmpSupportsQuery;}/**
6494
+ * Resolve whether POST /:Entity/Query is usable for an entity, probing the
6495
+ * Schema endpoint once and caching the result per (urlPrefix, entity).
6496
+ * Concurrent calls for the same key collapse onto a single probe.
6497
+ *
6498
+ * Probe failures (network/parse) are NOT cached — they resolve to false (GET
6499
+ * fallback) for this call but allow a later retry, so a transient blip does
6500
+ * not permanently disable the faster transport for the session. A successful
6501
+ * probe of an older server (no metadata) caches false and never re-probes.
6502
+ *
6503
+ * @param {string} pEntity - The entity name.
6504
+ * @param {string} pURLPrefix - The URL prefix in play.
6505
+ * @param {(pError: Error|null, pSupportsQuery: boolean) => void} fCallback - Completion callback.
6506
+ * @return {void}
6507
+ */resolveEntityQuerySupport(pEntity,pURLPrefix,fCallback){if(!this.useQueryEndpoint){return fCallback(null,false);}const tmpKey=this._capabilityKey(pEntity,pURLPrefix);if(tmpKey in this.endpointCapabilityCache){return fCallback(null,this.endpointCapabilityCache[tmpKey].SupportsQuery);}if(this.endpointCapabilityInflight[tmpKey]){this.endpointCapabilityInflight[tmpKey].push(fCallback);return;}this.endpointCapabilityInflight[tmpKey]=[fCallback];/** @type {Record<string, any>} */let tmpOptions={url:`${pURLPrefix||this.options.urlPrefix}${pEntity}/Schema`};tmpOptions=this.prepareRequestOptions(tmpOptions);this.restClient.getJSON(tmpOptions,(pError,pResponse,pBody)=>{let tmpSupportsQuery=false;if(!pError){try{tmpSupportsQuery=this.evaluateQueryEndpointSupport(pBody);}catch(pEvaluateError){tmpSupportsQuery=false;}// Only cache on a clean probe; transient errors stay re-probeable.
6508
+ this.endpointCapabilityCache[tmpKey]={SupportsQuery:tmpSupportsQuery,Metadata:pBody&&pBody.RetoldMetadata||null};}else{this.log.warn(`EntityProvider capability probe for [${pEntity}] failed; falling back to GET reads: ${pError}`);}const tmpCallbacks=this.endpointCapabilityInflight[tmpKey];delete this.endpointCapabilityInflight[tmpKey];for(let i=0;i<tmpCallbacks.length;i++){tmpCallbacks[i](null,tmpSupportsQuery);}});}/**
6509
+ * Build the POST /:Entity/Query request body for a filtered read.
6510
+ *
6511
+ * @param {string} pMeadowFilterExpression - The meadow filter string (may be empty).
6512
+ * @param {number|null} [pBegin] - Pagination start cursor.
6513
+ * @param {number|null} [pCap] - Pagination page size.
6514
+ * @param {Record<string, any>} [pProjection] - Optional { Mode:'LiteExtended', ExtraColumns:[...] }.
6515
+ * @return {Record<string, any>} The request body envelope.
6516
+ */_buildQueryReadBody(pMeadowFilterExpression,pBegin,pCap,pProjection){/** @type {Record<string, any>} */const tmpBody={};if(pMeadowFilterExpression){tmpBody.Filter=pMeadowFilterExpression;}if(typeof pBegin==='number'){tmpBody.Begin=pBegin;}if(typeof pCap==='number'){tmpBody.Cap=pCap;}if(pProjection&&pProjection.Mode==='LiteExtended'&&Array.isArray(pProjection.ExtraColumns)&&pProjection.ExtraColumns.length>0){// LiteExtended GET maps to a Lite read carrying ExtraColumns on Query.
6517
+ tmpBody.Lite=true;tmpBody.ExtraColumns=pProjection.ExtraColumns.join(',');}return tmpBody;}/**
6518
+ * Read a page of an entity set, using POST /:Entity/Query when supported and
6519
+ * falling back to the legacy GET read otherwise. The callback mirrors
6520
+ * restClient.getJSON exactly: (pError, pResponse, pBody).
6521
+ *
6522
+ * @param {string} pEntity - The entity name.
6523
+ * @param {string} pMeadowFilterExpression - The meadow filter string (may be empty).
6524
+ * @param {number|null} pBegin - Pagination start cursor (null for unpaged).
6525
+ * @param {number|null} pCap - Pagination page size (null for unpaged).
6526
+ * @param {Record<string, any>} pReadOptions - { SupportsQuery, URLPrefix, Postfix, Projection }.
6527
+ * @param {(pError: Error|null, pResponse: any, pBody: any) => void} fCallback - Completion callback.
6528
+ * @return {void}
6529
+ */_readEntityPage(pEntity,pMeadowFilterExpression,pBegin,pCap,pReadOptions,fCallback){const tmpURLPrefix=pReadOptions&&pReadOptions.URLPrefix||this.options.urlPrefix;const tmpPostfix=pReadOptions&&pReadOptions.Postfix||'';const tmpProjection=pReadOptions?pReadOptions.Projection:null;if(pReadOptions&&pReadOptions.SupportsQuery){const tmpRequestOptions={url:`${tmpURLPrefix}${pEntity}s/Query${tmpPostfix}`,body:this._buildQueryReadBody(pMeadowFilterExpression,pBegin,pCap,tmpProjection)};return this.restClient.postJSON(tmpRequestOptions,fCallback);}const tmpFilterStanza=pMeadowFilterExpression?`/FilteredTo/${pMeadowFilterExpression}`:'';const tmpProjectionStanza=tmpProjection&&tmpProjection.Mode==='LiteExtended'&&Array.isArray(tmpProjection.ExtraColumns)&&tmpProjection.ExtraColumns.length>0?`/LiteExtended/${tmpProjection.ExtraColumns.join(',')}`:'';const tmpPageStanza=typeof pBegin==='number'&&typeof pCap==='number'?`/${pBegin}/${pCap}`:'';const tmpURL=`${tmpURLPrefix}${pEntity}s${tmpProjectionStanza}${tmpFilterStanza}${tmpPageStanza}${tmpPostfix}`;return this.restClient.getJSON(tmpURL,fCallback);}/**
6530
+ * Read the count of an entity set, using POST /:Entity/Query (Count mode)
6531
+ * when supported and the legacy GET Count otherwise. The callback mirrors
6532
+ * restClient.getJSON: (pError, pResponse, pBody) where pBody carries .Count.
6533
+ *
6534
+ * @param {string} pEntity - The entity name.
6535
+ * @param {string} pMeadowFilterExpression - The meadow filter string (may be empty).
6536
+ * @param {Record<string, any>} pReadOptions - { SupportsQuery, URLPrefix, Postfix }.
6537
+ * @param {(pError: Error|null, pResponse: any, pBody: any) => void} fCallback - Completion callback.
6538
+ * @return {void}
6539
+ */_readEntityCount(pEntity,pMeadowFilterExpression,pReadOptions,fCallback){const tmpURLPrefix=pReadOptions&&pReadOptions.URLPrefix||this.options.urlPrefix;const tmpPostfix=pReadOptions&&pReadOptions.Postfix||'';if(pReadOptions&&pReadOptions.SupportsQuery){const tmpBody=this._buildQueryReadBody(pMeadowFilterExpression,null,null,null);tmpBody.Count=true;const tmpRequestOptions={url:`${tmpURLPrefix}${pEntity}s/Query${tmpPostfix}`,body:tmpBody};return this.restClient.postJSON(tmpRequestOptions,fCallback);}const tmpFilterStanza=pMeadowFilterExpression?`/FilteredTo/${pMeadowFilterExpression}`:'';const tmpURL=`${tmpURLPrefix}${pEntity}s/Count${tmpFilterStanza}${tmpPostfix}`;return this.restClient.getJSON(tmpURL,fCallback);}/**
6444
6540
  * Compute the cache bucket key for an entity, optionally namespaced by a scope.
6445
6541
  * A non-empty scope yields an isolated bucket (`Entity::Scope`) so scoped/partial
6446
6542
  * (e.g. Lite) records never touch the global `Entity` cache that full-record
@@ -6534,7 +6630,7 @@ this.fable.manifest.setValueByHash(tmpDestinationEntity,tmpBucketAddress,tmpSour
6534
6630
  *
6535
6631
  * @param {Record<string, any>} pConfiguration - The configuration object for the dataset projection.
6536
6632
  * @param {Record<string, any>} pContext - The context object to use when parsing the record prototype template and resolving the output recordset address mapping.
6537
- */projectDataset(pConfiguration,pContext){let tmpInputRecordset=this.fable.manifest.getValueByHash(pContext,pConfiguration.InputRecordsetAddress);if(typeof tmpInputRecordset==null||typeof tmpInputRecordset!=='object'){throw new Error(`EntityBundleRequest failed to project dataset because the input recordset [${pConfiguration.InputRecordsetAddress}] did not return an valid object.`);}if(!Array.isArray(tmpInputRecordset)){tmpInputRecordset=[tmpInputRecordset];}let tmpDefaultOutputRecordset=this.fable.manifest.getValueByHash(pContext,pConfiguration.OutputRecordsetAddress);if(!tmpDefaultOutputRecordset){tmpDefaultOutputRecordset=[];this.fable.manifest.setValueByHash(pContext,pConfiguration.OutputRecordsetAddress,tmpDefaultOutputRecordset);}for(const tmpInputRecord of tmpInputRecordset){let tmpOutputRecordset=tmpDefaultOutputRecordset;let tmpOutputRecordsetAddressOverride;if(typeof pConfiguration.OutputRecordsetAddressMapping==='object'){tmpOutputRecordsetAddressOverride=this._resolveOutputRecordsetAddressMapping(pConfiguration,pContext,tmpInputRecord);if(tmpOutputRecordsetAddressOverride){tmpOutputRecordset=this.fable.manifest.getValueByHash(pContext,tmpOutputRecordsetAddressOverride);if(!tmpOutputRecordset){tmpOutputRecordset=[];this.fable.manifest.setValueByHash(pContext,tmpOutputRecordsetAddressOverride,tmpOutputRecordset);}}if(!tmpOutputRecordset||!Array.isArray(tmpOutputRecordset)){tmpOutputRecordset=tmpDefaultOutputRecordset;}}const tmpPrototypeAddress=this.fable.parseTemplate(pConfiguration.RecordPrototypeAddress,Object.assign({InputRecord:tmpInputRecord},pContext));const tmpRecordPrototype=this.fable.manifest.getValueByHash(Object.assign({InputRecord:tmpInputRecord,OutputRecordset:tmpOutputRecordset},pContext),tmpPrototypeAddress);let tmpOutputRecord={};if(Array.isArray(tmpRecordPrototype)&&tmpRecordPrototype.length>0){tmpOutputRecord=tmpRecordPrototype[0];}else{tmpOutputRecordset.push(tmpOutputRecord);}let tmpRecordFieldMapping=pConfiguration.RecordFieldMapping[tmpOutputRecordsetAddressOverride]||pConfiguration.RecordFieldMapping.Default;if(!tmpRecordFieldMapping){tmpRecordFieldMapping=pConfiguration.RecordFieldMapping[Object.keys(pConfiguration.RecordFieldMapping)[0]];}if(!tmpRecordFieldMapping){throw new Error(`EntityBundleRequest failed to project dataset because the record field mapping for [${tmpOutputRecordsetAddressOverride}] did not return a mapping.`);}for(const tmpInputFieldAddress of Object.keys(tmpRecordFieldMapping)){const tmpOutputFieldAddress=tmpRecordFieldMapping[tmpInputFieldAddress];const tmpInputFieldValue=this.fable.manifest.getValueByHash(Object.assign({InputRecord:tmpInputRecord},pContext),tmpInputFieldAddress);this.fable.manifest.setValueByHash(Object.assign({OutputRecord:tmpOutputRecord},pContext),tmpOutputFieldAddress,tmpInputFieldValue);}}}/**
6633
+ */projectDataset(pConfiguration,pContext){let tmpInputRecordset=this.fable.manifest.getValueByHash(pContext,pConfiguration.InputRecordsetAddress);if(tmpInputRecordset==null||typeof tmpInputRecordset!=='object'){throw new Error(`EntityBundleRequest failed to project dataset because the input recordset [${pConfiguration.InputRecordsetAddress}] did not return an valid object.`);}if(!Array.isArray(tmpInputRecordset)){tmpInputRecordset=[tmpInputRecordset];}let tmpDefaultOutputRecordset=this.fable.manifest.getValueByHash(pContext,pConfiguration.OutputRecordsetAddress);if(!tmpDefaultOutputRecordset){tmpDefaultOutputRecordset=[];this.fable.manifest.setValueByHash(pContext,pConfiguration.OutputRecordsetAddress,tmpDefaultOutputRecordset);}for(const tmpInputRecord of tmpInputRecordset){let tmpOutputRecordset=tmpDefaultOutputRecordset;let tmpOutputRecordsetAddressOverride;if(typeof pConfiguration.OutputRecordsetAddressMapping==='object'){tmpOutputRecordsetAddressOverride=this._resolveOutputRecordsetAddressMapping(pConfiguration,pContext,tmpInputRecord);if(tmpOutputRecordsetAddressOverride){tmpOutputRecordset=this.fable.manifest.getValueByHash(pContext,tmpOutputRecordsetAddressOverride);if(!tmpOutputRecordset){tmpOutputRecordset=[];this.fable.manifest.setValueByHash(pContext,tmpOutputRecordsetAddressOverride,tmpOutputRecordset);}}if(!tmpOutputRecordset||!Array.isArray(tmpOutputRecordset)){tmpOutputRecordset=tmpDefaultOutputRecordset;}}const tmpPrototypeAddress=this.fable.parseTemplate(pConfiguration.RecordPrototypeAddress,Object.assign({InputRecord:tmpInputRecord},pContext));const tmpRecordPrototype=this.fable.manifest.getValueByHash(Object.assign({InputRecord:tmpInputRecord,OutputRecordset:tmpOutputRecordset},pContext),tmpPrototypeAddress);let tmpOutputRecord={};if(Array.isArray(tmpRecordPrototype)&&tmpRecordPrototype.length>0){tmpOutputRecord=tmpRecordPrototype[0];}else{tmpOutputRecordset.push(tmpOutputRecord);}let tmpRecordFieldMapping=pConfiguration.RecordFieldMapping[tmpOutputRecordsetAddressOverride]||pConfiguration.RecordFieldMapping.Default;if(!tmpRecordFieldMapping){tmpRecordFieldMapping=pConfiguration.RecordFieldMapping[Object.keys(pConfiguration.RecordFieldMapping)[0]];}if(!tmpRecordFieldMapping){throw new Error(`EntityBundleRequest failed to project dataset because the record field mapping for [${tmpOutputRecordsetAddressOverride}] did not return a mapping.`);}for(const tmpInputFieldAddress of Object.keys(tmpRecordFieldMapping)){const tmpOutputFieldAddress=tmpRecordFieldMapping[tmpInputFieldAddress];const tmpInputFieldValue=this.fable.manifest.getValueByHash(Object.assign({InputRecord:tmpInputRecord},pContext),tmpInputFieldAddress);this.fable.manifest.setValueByHash(Object.assign({OutputRecord:tmpOutputRecord},pContext),tmpOutputFieldAddress,tmpInputFieldValue);}}}/**
6538
6634
  * @param {Record<string, any>} pConfiguration - The configuration object for the dataset projection.
6539
6635
  * @param {Record<string, any>} pContext - The context object to use when resolving the output recordset address mapping.
6540
6636
  * @param {Record<string, any>} pInputRecord - The input record to use when resolving the output recordset address mapping.
@@ -6663,18 +6759,28 @@ this.recordCache[tmpCacheKey].prune(()=>{});// First pass: gather IDs to fetch
6663
6759
  for(const tmpRecord of pRecordSet){const tmpIDValue=tmpRecord[tmpEntityIDSourceField];if(tmpIDValue){const tmpCachedRecord=this.recordCache[tmpCacheKey].read(tmpIDValue);if(!tmpCachedRecord){tmpEntityIDsToFetch.add(tmpIDValue);}}}// Now if there are records to fetch, do the request.
6664
6760
  if(tmpEntityIDsToFetch.size>0){tmpAnticipate.anticipate(function(fRequestComplete){const tmpIDRecordsArray=Array.from(tmpEntityIDsToFetch);this.getEntitySetByIDListChunked(tmpEntityName,tmpIDRecordsArray,{NoCount:true,Scope:pScope},fRequestComplete);}.bind(this));}}tmpAnticipate.wait(pError=>{if(pError){this.log.error(`cacheConnectedEntityRecords error gathering connected entity records: ${pError}`,{Stack:pError.stack});return fCallback(pError);}return fCallback();});}/**
6665
6761
  * Fetch a set of entity records by primary-key ID list, chunking the meadow IN
6666
- * filter so the generated GET URL never exceeds HTTP/2 header-size limits on
6667
- * large sets (oversized URLs trip a connection-level reset that takes sibling
6668
- * multiplexed requests down with it). Records are cached as a side effect of
6669
- * getEntitySet; the callback returns no data.
6762
+ * filter into requests.
6763
+ *
6764
+ * Chunk size is capability-aware: the legacy GET read embeds the IN-list in
6765
+ * the URL, so it is chunked small (ConnectedEntityIDChunkSize, default 200) to
6766
+ * keep the URL under HTTP/2 header-size limits — oversized URLs trip a
6767
+ * connection-level reset that takes sibling multiplexed requests down with it.
6768
+ * When the endpoint serves POST /:Entity/Query the IN-list rides in the body
6769
+ * (no URI limit), so a much larger chunk is used (ConnectedEntityIDQueryChunkSize,
6770
+ * default 5000) — bounding response size rather than URL length, and collapsing
6771
+ * what used to be many small requests into one (or a few). Records are cached
6772
+ * as a side effect of getEntitySet; the callback returns no data.
6670
6773
  *
6671
6774
  * @param {string} pEntityName - The entity name (e.g. 'Project').
6672
6775
  * @param {Array<number|string>} pIDRecordsArray - The primary-key IDs to fetch.
6673
- * @param {Object} pOptions - Options passed through to getEntitySet (Scope, NoCount, etc).
6776
+ * @param {Object} pOptions - Options passed through to getEntitySet (Scope, NoCount, URLPrefix, etc).
6674
6777
  * @param {(error?: Error) => void} fCallback - Completion callback.
6675
6778
  *
6676
6779
  * @return {void}
6677
- */getEntitySetByIDListChunked(pEntityName,pIDRecordsArray,pOptions,fCallback){if(!Array.isArray(pIDRecordsArray)||pIDRecordsArray.length<1){return fCallback();}const tmpAnticipate=this.fable.newAnticipate();const tmpChunkSize=this.options&&this.options.ConnectedEntityIDChunkSize||200;for(let i=0;i<pIDRecordsArray.length;i+=tmpChunkSize){const tmpIDChunk=pIDRecordsArray.slice(i,i+tmpChunkSize);tmpAnticipate.anticipate(function(fChunkComplete){const tmpMeadowFilterExpression=`FBL~ID${pEntityName}~INN~${tmpIDChunk.join(',')}`;this.getEntitySet(pEntityName,tmpMeadowFilterExpression,pError=>{if(pError){this.log.error(`getEntitySetByIDListChunked error getting connected entity records for [${pEntityName}] with IDs [${tmpIDChunk.join(',')}]: ${pError}`,{Stack:pError.stack});return fChunkComplete(pError);}return fChunkComplete();},null,pOptions);}.bind(this));}tmpAnticipate.wait(fCallback);}/**
6780
+ */getEntitySetByIDListChunked(pEntityName,pIDRecordsArray,pOptions,fCallback){if(!Array.isArray(pIDRecordsArray)||pIDRecordsArray.length<1){return fCallback();}const tmpOptions=pOptions||{};// Resolve transport capability once (cached; getEntitySet reuses it) so the
6781
+ // chunk size matches the transport. The probe URL prefix matches the one
6782
+ // getEntitySet will read from tmpOptions.URLPrefix.
6783
+ this.resolveEntityQuerySupport(pEntityName,tmpOptions.URLPrefix,(pSupportError,pSupportsQuery)=>{const tmpChunkSize=pSupportsQuery?this.options&&this.options.ConnectedEntityIDQueryChunkSize||5000:this.options&&this.options.ConnectedEntityIDChunkSize||200;const tmpAnticipate=this.fable.newAnticipate();for(let i=0;i<pIDRecordsArray.length;i+=tmpChunkSize){const tmpIDChunk=pIDRecordsArray.slice(i,i+tmpChunkSize);tmpAnticipate.anticipate(function(fChunkComplete){const tmpMeadowFilterExpression=`FBL~ID${pEntityName}~INN~${tmpIDChunk.join(',')}`;this.getEntitySet(pEntityName,tmpMeadowFilterExpression,pError=>{if(pError){this.log.error(`getEntitySetByIDListChunked error getting connected entity records for [${pEntityName}] with IDs [${tmpIDChunk.join(',')}]: ${pError}`,{Stack:pError.stack});return fChunkComplete(pError);}return fChunkComplete();},null,tmpOptions);}.bind(this));}tmpAnticipate.wait(fCallback);});}/**
6678
6784
  * For a given list of objects, cache connected entity records.
6679
6785
  *
6680
6786
  * @param {Array} pRecordSet - An array of objects to check cache on joined records for, and, get/cache the records as needed.
@@ -6709,22 +6815,22 @@ const tmpSpeculativeRecordIDColumn=`ID${pEntity}`;if(tmpEntitySet[0]&&typeof tmp
6709
6815
  * @param {string} [postfix] - Optional, adds a postfix string to the url.
6710
6816
  * @param {string} [pURLPrefix] - Optional per-request URL prefix; overrides the provider default (e.g. a private-data-lake route).
6711
6817
  * @param {Record<string, any>} [pOptions] - Optional { Scope, Projection }: cache scope and Lite/LiteExtended projection.
6712
- */getEntitySetPage(pEntity,pMeadowFilterExpression,pRecordStartCursor,pRecordCount,fCallback){let postfix=arguments.length>5&&arguments[5]!==undefined?arguments[5]:'';let pURLPrefix=arguments.length>6&&arguments[6]!==undefined?arguments[6]:'';let pOptions=arguments.length>7&&arguments[7]!==undefined?arguments[7]:{};const tmpFilterStanza=pMeadowFilterExpression?`/FilteredTo/${pMeadowFilterExpression}`:'';// LiteExtended projection: fetch only ID/GUID/owner/update + the requested
6818
+ */getEntitySetPage(pEntity,pMeadowFilterExpression,pRecordStartCursor,pRecordCount,fCallback){let postfix=arguments.length>5&&arguments[5]!==undefined?arguments[5]:'';let pURLPrefix=arguments.length>6&&arguments[6]!==undefined?arguments[6]:'';let pOptions=arguments.length>7&&arguments[7]!==undefined?arguments[7]:{};const tmpScope=pOptions&&pOptions.Scope?pOptions.Scope:'';// Per-request URL prefix override (positional, e.g. a private-data-lake route);
6819
+ // also accepted via pOptions.URLPrefix. Falls back to the provider default.
6820
+ const tmpURLPrefix=pURLPrefix||pOptions&&pOptions.URLPrefix||this.options.urlPrefix;// LiteExtended projection: fetch only ID/GUID/owner/update + the requested
6713
6821
  // ExtraColumns (drops blob columns). Used by scoped list fetches to avoid the
6714
6822
  // heavy full-record payload.
6715
- const tmpProjectionStanza=pOptions&&pOptions.Projection&&pOptions.Projection.Mode==='LiteExtended'&&Array.isArray(pOptions.Projection.ExtraColumns)&&pOptions.Projection.ExtraColumns.length>0?`/LiteExtended/${pOptions.Projection.ExtraColumns.join(',')}`:'';const tmpScope=pOptions&&pOptions.Scope?pOptions.Scope:'';// Per-request URL prefix override (positional, e.g. a private-data-lake route);
6716
- // also accepted via pOptions.URLPrefix. Falls back to the provider default.
6717
- const tmpURLPrefix=pURLPrefix||pOptions&&pOptions.URLPrefix||this.options.urlPrefix;const tmpURL=`${tmpURLPrefix}${pEntity}s${tmpProjectionStanza}${tmpFilterStanza}/${pRecordStartCursor}/${pRecordCount}`;return this.restClient.getJSON(tmpURL+(postfix||''),function(pDownloadError,pDownloadResponse,pDownloadBody){if(pDownloadResponse&&pDownloadResponse.statusCode&&pDownloadResponse.statusCode>=400){this.log.error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}] [${pRecordStartCursor}/${pRecordCount}] from url [${tmpURL}]: ${pDownloadResponse.statusCode} ${pDownloadResponse.statusMessage}`);return fCallback(new Error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}] [${pRecordStartCursor}/${pRecordCount}] from url [${tmpURL}]: ${pDownloadResponse.statusCode} ${JSON.stringify(pDownloadBody||{})}`));}// Do not cache projected (Lite/partial) records in the entity cache — a
6823
+ const tmpProjection=pOptions&&pOptions.Projection?pOptions.Projection:null;this.resolveEntityQuerySupport(pEntity,tmpURLPrefix,(pSupportError,pSupportsQuery)=>{this._readEntityPage(pEntity,pMeadowFilterExpression,pRecordStartCursor,pRecordCount,{SupportsQuery:pSupportsQuery,URLPrefix:tmpURLPrefix,Postfix:postfix||'',Projection:tmpProjection},(pDownloadError,pDownloadResponse,pDownloadBody)=>{if(pDownloadResponse&&pDownloadResponse.statusCode&&pDownloadResponse.statusCode>=400){this.log.error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}] [${pRecordStartCursor}/${pRecordCount}]: ${pDownloadResponse.statusCode} ${pDownloadResponse.statusMessage}`);return fCallback(new Error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}] [${pRecordStartCursor}/${pRecordCount}]: ${pDownloadResponse.statusCode} ${JSON.stringify(pDownloadBody||{})}`));}// Do not cache projected (Lite/partial) records in the entity cache — a
6718
6824
  // partial record would shadow the full record for other consumers
6719
6825
  // (row-click View, {~E:~}). Projected fetches are rendered straight from
6720
6826
  // the list state and never need to be in the entity cache.
6721
- if(!(pOptions&&pOptions.Projection)){this.cacheIndividualEntityRecords(pEntity,pDownloadBody,tmpScope);}return fCallback(pDownloadError,pDownloadBody);}.bind(this));}/**
6827
+ if(!tmpProjection){this.cacheIndividualEntityRecords(pEntity,pDownloadBody,tmpScope);}return fCallback(pDownloadError,pDownloadBody);});});}/**
6722
6828
  * @param {string} pEntity - The name of the entity to get the count of.
6723
6829
  * @param {string} pMeadowFilterExpression - The meadow filter expression to filter the entity set by.
6724
6830
  * @param {(pError?: Error, pRecordCount?: number) => void} fCallback - The callback function to call when the operation is complete.
6725
6831
  * @param {string} [postfix] - Optional, adds a postfix string to the count url
6726
6832
  * @param {string} [pURLPrefix] - Optional per-request URL prefix; overrides the provider default.
6727
- */getEntitySetRecordCount(pEntity,pMeadowFilterExpression,fCallback){let postfix=arguments.length>3&&arguments[3]!==undefined?arguments[3]:'';let pURLPrefix=arguments.length>4&&arguments[4]!==undefined?arguments[4]:'';const tmpFilterStanza=pMeadowFilterExpression?`/FilteredTo/${pMeadowFilterExpression}`:'';const tmpURL=`${pURLPrefix||this.options.urlPrefix}${pEntity}s/Count${tmpFilterStanza}`;return this.restClient.getJSON(tmpURL+(postfix||''),function(pError,pResponse,pBody){if(pResponse&&pResponse.statusCode&&pResponse.statusCode>=400){this.log.error(`Error getting entity count of [${pEntity}] filtered to [${pMeadowFilterExpression}] from url [${tmpURL}]: ${pResponse.statusCode} ${pResponse.statusMessage}`);return fCallback(new Error(`Error getting entity count of [${pEntity}] filtered to [${pMeadowFilterExpression}] from url [${tmpURL}]: ${pResponse.statusCode} ${JSON.stringify(pBody||{})}`));}if(pError){this.log.error(`Error getting entity count of [${pEntity}] filtered to [${pMeadowFilterExpression}] from url [${tmpURL}]: ${pError}`);return fCallback(pError);}let tmpRecordCount=0;if(pBody.Count){tmpRecordCount=pBody.Count;}return fCallback(pError,tmpRecordCount);}.bind(this));}/**
6833
+ */getEntitySetRecordCount(pEntity,pMeadowFilterExpression,fCallback){let postfix=arguments.length>3&&arguments[3]!==undefined?arguments[3]:'';let pURLPrefix=arguments.length>4&&arguments[4]!==undefined?arguments[4]:'';this.resolveEntityQuerySupport(pEntity,pURLPrefix,(pSupportError,pSupportsQuery)=>{this._readEntityCount(pEntity,pMeadowFilterExpression,{SupportsQuery:pSupportsQuery,URLPrefix:pURLPrefix||'',Postfix:postfix||''},(pError,pResponse,pBody)=>{if(pResponse&&pResponse.statusCode&&pResponse.statusCode>=400){this.log.error(`Error getting entity count of [${pEntity}] filtered to [${pMeadowFilterExpression}]: ${pResponse.statusCode} ${pResponse.statusMessage}`);return fCallback(new Error(`Error getting entity count of [${pEntity}] filtered to [${pMeadowFilterExpression}]: ${pResponse.statusCode} ${JSON.stringify(pBody||{})}`));}if(pError){this.log.error(`Error getting entity count of [${pEntity}] filtered to [${pMeadowFilterExpression}]: ${pError}`);return fCallback(pError);}let tmpRecordCount=0;if(pBody&&pBody.Count){tmpRecordCount=pBody.Count;}return fCallback(pError,tmpRecordCount);});});}/**
6728
6834
  * @param {string} pEntity - The name of the entity to get.
6729
6835
  * @param {string} pMeadowFilterExpression - The meadow filter expression to filter the entity set by.
6730
6836
  * @param {(pError?: Error, pEntitySet?: Array<Record<string, any>>) => void} fCallback - The callback function to call when the operation is complete.
@@ -6740,17 +6846,18 @@ else if(tmpColumnName in this.entityColumnTranslations){tmpIDColumnsToCache.push
6740
6846
  * @param {Record<string, any>} [pOptions] - Optional, per-call options (e.g. { DownloadPageConcurrency: 1 }).
6741
6847
  *
6742
6848
  * @return {void}
6743
- */getEntitySet(pEntity,pMeadowFilterExpression,fCallback){let postfix=arguments.length>3&&arguments[3]!==undefined?arguments[3]:'';let pOptions=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};const tmpURLPrefix=pOptions.URLPrefix||this.options.urlPrefix;// TODO: Should we test for too many record IDs here by string length in pMeadowFilterExpression?
6744
- // FBL~ID${pDestinationEntity}~INN~${tmpIDRecordsCommaSeparated}
6745
- // If the list is mega-long we can parse it and break it into chunks.
6746
- const tmpScope=pOptions&&pOptions.Scope?pOptions.Scope:'';this.initializeCache(pEntity,tmpScope);const tmpCacheKey=this._cacheKey(pEntity,tmpScope);// Discard anything from the cache that has expired or is over size.
6849
+ */getEntitySet(pEntity,pMeadowFilterExpression,fCallback){let postfix=arguments.length>3&&arguments[3]!==undefined?arguments[3]:'';let pOptions=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};const tmpURLPrefix=pOptions.URLPrefix||this.options.urlPrefix;const tmpScope=pOptions&&pOptions.Scope?pOptions.Scope:'';this.initializeCache(pEntity,tmpScope);const tmpCacheKey=this._cacheKey(pEntity,tmpScope);// Resolve transport capability once for the whole read (count + every
6850
+ // page share the same SupportsQuery flag). The legacy GET reads embed the
6851
+ // filter (and any large IN-list) in the URI, which is exactly the long-URI
6852
+ // failure POST /:Entity/Query avoids when the server supports it.
6853
+ this.resolveEntityQuerySupport(pEntity,tmpURLPrefix,(pSupportError,pSupportsQuery)=>{const tmpReadOptions={SupportsQuery:pSupportsQuery,URLPrefix:tmpURLPrefix,Postfix:postfix||''};// Discard anything from the cache that has expired or is over size.
6747
6854
  this.recordSetCache[tmpCacheKey].prune(function(){let tmpPossibleRecords=this.recordSetCache[tmpCacheKey].read(pMeadowFilterExpression);if(tmpPossibleRecords){return fCallback(null,tmpPossibleRecords);}if(pOptions.NoCount){// Lazily load until we hit a not full page rather than using couns.
6748
6855
  // Does not respect parallelization.
6749
- const pageSize=250;let page=0;let returnSet=[];const tmpFilterStanza=pMeadowFilterExpression?`/FilteredTo/${pMeadowFilterExpression}`:'';const recursiveCallback=(pDownloadError,pDownloadResponse,pDownloadBody)=>{if(pDownloadResponse&&pDownloadResponse.statusCode&&pDownloadResponse.statusCode>=400||!Array.isArray(pDownloadBody)){this.log.error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}]: ${pDownloadResponse.statusCode} ${pDownloadResponse.statusMessage}`);return fCallback(new Error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}]: ${pDownloadResponse.statusCode} ${JSON.stringify(pDownloadBody||{})}`),[]);}returnSet=returnSet.concat(pDownloadBody);if((pDownloadBody===null||pDownloadBody===void 0?void 0:pDownloadBody.length)<pageSize){this.recordSetCache[tmpCacheKey].put(returnSet,pMeadowFilterExpression);this.cacheIndividualEntityRecords(pEntity,returnSet,tmpScope);fCallback(null,returnSet);}else{page+=1;this.restClient.getJSON(`${tmpURLPrefix}${pEntity}s${tmpFilterStanza}/${page*pageSize}/${pageSize}`+(postfix||''),recursiveCallback);}};return this.restClient.getJSON(`${tmpURLPrefix}${pEntity}s${tmpFilterStanza}/${page*pageSize}/${pageSize}`+(postfix||''),recursiveCallback);}return this.getEntitySetRecordCount(pEntity,pMeadowFilterExpression,(pRecordCountError,pRecordCount)=>{if(pRecordCountError){return fCallback(pRecordCountError);}let tmpRecordCount=pRecordCount;if(isNaN(pRecordCount)){this.log.error(`Entity count did not return a number for [${pEntity}] filtered to [${pMeadowFilterExpression}]... something is fatally wrong from the server accessed in getEntitySet call.`);return fCallback(new Error('Entity count did not return a number in getEntitySet.'));}let tmpDownloadBatchSize=this.options.downloadBatchSize;const tmpFilterStanza=pMeadowFilterExpression?`/FilteredTo/${pMeadowFilterExpression}`:'';const tmpPageCount=Math.ceil(tmpRecordCount/tmpDownloadBatchSize);// Build an indexed array of page descriptors to preserve ordering
6750
- const tmpPages=[];for(let i=0;i<tmpPageCount;i++){tmpPages.push({Index:i,URL:`${tmpURLPrefix}${pEntity}s${tmpFilterStanza}/${i*tmpDownloadBatchSize}/${tmpDownloadBatchSize}`,Records:null});}// Fetch pages concurrently and reassemble in order.
6856
+ const pageSize=250;let page=0;let returnSet=[];const recursiveCallback=(pDownloadError,pDownloadResponse,pDownloadBody)=>{if(pDownloadResponse&&pDownloadResponse.statusCode&&pDownloadResponse.statusCode>=400||!Array.isArray(pDownloadBody)){this.log.error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}]: ${pDownloadResponse.statusCode} ${pDownloadResponse.statusMessage}`);return fCallback(new Error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}]: ${pDownloadResponse.statusCode} ${JSON.stringify(pDownloadBody||{})}`),[]);}returnSet=returnSet.concat(pDownloadBody);if((pDownloadBody===null||pDownloadBody===void 0?void 0:pDownloadBody.length)<pageSize){this.recordSetCache[tmpCacheKey].put(returnSet,pMeadowFilterExpression);this.cacheIndividualEntityRecords(pEntity,returnSet,tmpScope);fCallback(null,returnSet);}else{page+=1;this._readEntityPage(pEntity,pMeadowFilterExpression,page*pageSize,pageSize,tmpReadOptions,recursiveCallback);}};return this._readEntityPage(pEntity,pMeadowFilterExpression,page*pageSize,pageSize,tmpReadOptions,recursiveCallback);}return this.getEntitySetRecordCount(pEntity,pMeadowFilterExpression,(pRecordCountError,pRecordCount)=>{if(pRecordCountError){return fCallback(pRecordCountError);}let tmpRecordCount=pRecordCount;if(isNaN(pRecordCount)){this.log.error(`Entity count did not return a number for [${pEntity}] filtered to [${pMeadowFilterExpression}]... something is fatally wrong from the server accessed in getEntitySet call.`);return fCallback(new Error('Entity count did not return a number in getEntitySet.'));}let tmpDownloadBatchSize=this.options.downloadBatchSize;const tmpPageCount=Math.ceil(tmpRecordCount/tmpDownloadBatchSize);// Build an indexed array of page descriptors to preserve ordering
6857
+ const tmpPages=[];for(let i=0;i<tmpPageCount;i++){tmpPages.push({Index:i,Begin:i*tmpDownloadBatchSize,Records:null});}// Fetch pages concurrently and reassemble in order.
6751
6858
  // Per-call DownloadPageConcurrency overrides the provider-level default.
6752
- const tmpPageConcurrency=typeof pOptions.DownloadPageConcurrency==='number'?pOptions.DownloadPageConcurrency:typeof this.options.downloadPageConcurrency==='number'?this.options.downloadPageConcurrency:4;this.fable.Utility.eachLimit(tmpPages,tmpPageConcurrency,(pPage,fDownloadCallback)=>{this.restClient.getJSON(pPage.URL+(postfix||''),(pDownloadError,pDownloadResponse,pDownloadBody)=>{if(pDownloadResponse&&pDownloadResponse.statusCode&&pDownloadResponse.statusCode>=400){this.log.error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}] from url [${pPage.URL}]: ${pDownloadResponse.statusCode} ${pDownloadResponse.statusMessage}`);return fDownloadCallback(new Error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}] from url [${pPage.URL}]: ${pDownloadResponse.statusCode} ${JSON.stringify(pDownloadBody||{})}`));}if(Array.isArray(pDownloadBody)){pPage.Records=pDownloadBody;}return fDownloadCallback(pDownloadError);});},pFullDownloadError=>{// Reassemble pages in index order to maintain consistent ordering
6753
- let tmpEntitySet=[];for(let i=0;i<tmpPages.length;i++){if(Array.isArray(tmpPages[i].Records)){tmpEntitySet=tmpEntitySet.concat(tmpPages[i].Records);}}if(tmpEntitySet){this.recordSetCache[tmpCacheKey].put(tmpEntitySet,pMeadowFilterExpression);}this.cacheIndividualEntityRecords(pEntity,tmpEntitySet,tmpScope);return fCallback(pFullDownloadError,tmpEntitySet);});},postfix,pOptions.URLPrefix);}.bind(this));}////////////////////////////////////////////////////////////////////////////////
6859
+ const tmpPageConcurrency=typeof pOptions.DownloadPageConcurrency==='number'?pOptions.DownloadPageConcurrency:typeof this.options.downloadPageConcurrency==='number'?this.options.downloadPageConcurrency:4;this.fable.Utility.eachLimit(tmpPages,tmpPageConcurrency,(pPage,fDownloadCallback)=>{this._readEntityPage(pEntity,pMeadowFilterExpression,pPage.Begin,tmpDownloadBatchSize,tmpReadOptions,(pDownloadError,pDownloadResponse,pDownloadBody)=>{if(pDownloadResponse&&pDownloadResponse.statusCode&&pDownloadResponse.statusCode>=400){this.log.error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}] [${pPage.Begin}/${tmpDownloadBatchSize}]: ${pDownloadResponse.statusCode} ${pDownloadResponse.statusMessage}`);return fDownloadCallback(new Error(`Error getting entity set of [${pEntity}] filtered to [${pMeadowFilterExpression}] [${pPage.Begin}/${tmpDownloadBatchSize}]: ${pDownloadResponse.statusCode} ${JSON.stringify(pDownloadBody||{})}`));}if(Array.isArray(pDownloadBody)){pPage.Records=pDownloadBody;}return fDownloadCallback(pDownloadError);});},pFullDownloadError=>{// Reassemble pages in index order to maintain consistent ordering
6860
+ let tmpEntitySet=[];for(let i=0;i<tmpPages.length;i++){if(Array.isArray(tmpPages[i].Records)){tmpEntitySet=tmpEntitySet.concat(tmpPages[i].Records);}}if(tmpEntitySet){this.recordSetCache[tmpCacheKey].put(tmpEntitySet,pMeadowFilterExpression);}this.cacheIndividualEntityRecords(pEntity,tmpEntitySet,tmpScope);return fCallback(pFullDownloadError,tmpEntitySet);});},postfix,pOptions.URLPrefix);}.bind(this));});}////////////////////////////////////////////////////////////////////////////////
6754
6861
  // Entity Creation and Update Methods
6755
6862
  /**
6756
6863
  * @param {string} pEntityType - The type of the entity to format the URL for.
@@ -7336,7 +7443,9 @@ return this.contentMap[pAddress];}this.pict.log.warn(`Mock read from Address ${p
7336
7443
  * PreparedFilters: Array<PreparedFilter>,
7337
7444
  * BundleConfig?: Array<Record<string, any>>,
7338
7445
  * UserFilters?: Array<string>,
7339
- * Postfix?: string
7446
+ * Postfix?: string,
7447
+ * Scope?: string,
7448
+ * Projection?: Record<string, any>,
7340
7449
  * }} FilterState
7341
7450
  *
7342
7451
  * @typedef { 'None' |