@milaboratories/pl-middle-layer 1.54.2 → 1.54.4

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.
@@ -2,8 +2,9 @@ import { parseDataInfoResource, traverseParquetChunkResource } from "./data.js";
2
2
  import { PFrameDriverError, ensureError, isAbortError, isDataInfo, mapDataInfo } from "@platforma-sdk/model";
3
3
  import { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
4
4
  import path from "node:path";
5
- import { RefCountPoolBase, emptyDir } from "@milaboratories/ts-helpers";
5
+ import { emptyDir } from "@milaboratories/ts-helpers";
6
6
  import { isPlTreeNodeAccessor } from "@milaboratories/pl-tree";
7
+ import { RefCountPoolBase } from "@milaboratories/helpers";
7
8
  import { AbstractPFrameDriver, AbstractPFrameDriverOpsDefaults, makeJsonDataInfo } from "@milaboratories/pf-driver";
8
9
  import { HttpHelpers } from "@milaboratories/pframes-rs-node";
9
10
  import { Readable } from "node:stream";
@@ -1 +1 @@
1
- {"version":3,"file":"driver.js","names":[],"sources":["../../src/pool/driver.ts"],"sourcesContent":["import {\n mapDataInfo,\n isDataInfo,\n ensureError,\n PFrameDriverError,\n isAbortError,\n type LocalBlobHandleAndSize,\n type RemoteBlobHandleAndSize,\n type RemoteBlobHandle,\n type ContentHandler,\n type PColumnSpec,\n type PColumnDataUniversal,\n} from \"@platforma-sdk/model\";\nimport { PFrameInternal } from \"@milaboratories/pl-model-middle-layer\";\nimport {\n emptyDir,\n RefCountPoolBase,\n type PoolEntry,\n type MiLogger,\n} from \"@milaboratories/ts-helpers\";\nimport type { DownloadDriver } from \"@milaboratories/pl-drivers\";\nimport {\n isPlTreeNodeAccessor,\n type PlTreeEntry,\n type PlTreeNodeAccessor,\n} from \"@milaboratories/pl-tree\";\nimport type { Computable, ComputableStableDefined } from \"@milaboratories/computable\";\nimport {\n makeJsonDataInfo,\n AbstractPFrameDriver,\n AbstractPFrameDriverOpsDefaults,\n type AbstractInternalPFrameDriver,\n type AbstractPFrameDriverOps,\n type LocalBlobProvider,\n type RemoteBlobProvider,\n} from \"@milaboratories/pf-driver\";\nimport { HttpHelpers } from \"@milaboratories/pframes-rs-node\";\nimport path from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { parseDataInfoResource, traverseParquetChunkResource } from \"./data\";\nimport { isDownloadNetworkError400 } from \"@milaboratories/pl-drivers\";\n\nfunction makeBlobId(res: PlTreeEntry): PFrameInternal.PFrameBlobId {\n return String(res.rid) as PFrameInternal.PFrameBlobId;\n}\n\ntype LocalBlob = ComputableStableDefined<LocalBlobHandleAndSize>;\nclass LocalBlobProviderImpl\n extends RefCountPoolBase<PlTreeEntry, PFrameInternal.PFrameBlobId, LocalBlob>\n implements LocalBlobProvider<PlTreeEntry>\n{\n constructor(\n private readonly blobDriver: DownloadDriver,\n private readonly logger: PFrameInternal.Logger,\n ) {\n super();\n }\n\n protected calculateParamsKey(params: PlTreeEntry): PFrameInternal.PFrameBlobId {\n return makeBlobId(params);\n }\n\n protected createNewResource(params: PlTreeEntry, _key: PFrameInternal.PFrameBlobId): LocalBlob {\n return this.blobDriver.getDownloadedBlob(params);\n }\n\n public getByKey(blobId: PFrameInternal.PFrameBlobId): LocalBlob {\n const resource = super.tryGetByKey(blobId);\n if (!resource) throw new PFrameDriverError(`Local blob with id ${blobId} not found.`);\n return resource;\n }\n\n public makeDataSource(\n signal: AbortSignal,\n ): Omit<PFrameInternal.PFrameDataSourceV2, \"parquetServer\"> {\n return {\n preloadBlob: async (blobIds: PFrameInternal.PFrameBlobId[]) => {\n try {\n await Promise.all(\n blobIds.map((blobId) => this.getByKey(blobId).awaitStableFullValue(signal)),\n );\n } catch (err: unknown) {\n if (!isAbortError(err)) throw err;\n }\n },\n resolveBlobContent: async (blobId: PFrameInternal.PFrameBlobId) => {\n const computable = this.getByKey(blobId);\n const blob = await computable.awaitStableValue(signal);\n return await this.blobDriver.getContent(blob.handle, { signal });\n },\n };\n }\n}\n\ntype RemoteBlob = Computable<RemoteBlobHandleAndSize>;\nclass RemoteBlobPool extends RefCountPoolBase<\n PlTreeEntry,\n PFrameInternal.PFrameBlobId,\n RemoteBlob\n> {\n constructor(\n private readonly blobDriver: DownloadDriver,\n private readonly logger: PFrameInternal.Logger,\n ) {\n super();\n }\n\n protected calculateParamsKey(params: PlTreeEntry): PFrameInternal.PFrameBlobId {\n return makeBlobId(params);\n }\n\n protected createNewResource(params: PlTreeEntry, _key: PFrameInternal.PFrameBlobId): RemoteBlob {\n return this.blobDriver.getOnDemandBlob(params);\n }\n\n public getByKey(blobId: PFrameInternal.PFrameBlobId): RemoteBlob {\n const resource = super.tryGetByKey(blobId);\n if (!resource) throw new PFrameDriverError(`Remote blob with id ${blobId} not found.`);\n return resource;\n }\n\n public async withContent<T>(\n handle: RemoteBlobHandle,\n options: {\n range: PFrameInternal.FileRange;\n signal: AbortSignal;\n handler: ContentHandler<T>;\n },\n ): Promise<T> {\n return await this.blobDriver.withContent(handle, {\n range: {\n from: options.range.start,\n to: options.range.end + 1,\n },\n signal: options.signal,\n handler: options.handler,\n });\n }\n}\n\ninterface BlobStoreOptions extends PFrameInternal.ObjectStoreOptions {\n remoteBlobProvider: RemoteBlobPool;\n}\n\nclass BlobStore extends PFrameInternal.BaseObjectStore {\n private readonly remoteBlobProvider: RemoteBlobPool;\n\n constructor(options: BlobStoreOptions) {\n super(options);\n this.remoteBlobProvider = options.remoteBlobProvider;\n }\n\n public override async request(\n filename: PFrameInternal.ParquetFileName,\n params: {\n method: PFrameInternal.HttpMethod;\n range?: PFrameInternal.HttpRange;\n signal: AbortSignal;\n callback: (response: PFrameInternal.ObjectStoreResponse) => Promise<void>;\n },\n ): Promise<void> {\n const blobId = filename.slice(\n 0,\n -PFrameInternal.ParquetExtension.length,\n ) as PFrameInternal.PFrameBlobId;\n const respond = async (response: PFrameInternal.ObjectStoreResponse): Promise<void> => {\n try {\n await params.callback(response);\n } catch (error: unknown) {\n this.logger(\n \"warn\",\n `PFrames blob store received unhandled rejection from HTTP handler: ${ensureError(error)}`,\n );\n }\n };\n\n try {\n const computable = this.remoteBlobProvider.tryGetByKey(blobId);\n if (!computable) return await respond({ type: \"NotFound\" });\n\n let blob: RemoteBlobHandleAndSize;\n try {\n blob = await computable.getValue();\n } catch (error: unknown) {\n this.logger(\n \"error\",\n `PFrames blob store failed to get blob from computable: ${ensureError(error)}`,\n );\n return await respond({ type: \"InternalError\" });\n }\n params.signal.throwIfAborted();\n\n const translatedRange = this.translate(blob.size, params.range);\n if (!translatedRange) {\n return await respond({\n type: \"RangeNotSatisfiable\",\n size: blob.size,\n });\n }\n\n if (params.method === \"HEAD\") {\n return await respond({\n type: \"Ok\",\n size: blob.size,\n range: translatedRange,\n });\n }\n\n this.logger(\n \"info\",\n `PFrames blob store requesting content for ${blobId}, ` +\n `range [${translatedRange.start}..=${translatedRange.end}]`,\n );\n return await this.remoteBlobProvider.withContent(blob.handle, {\n range: translatedRange,\n signal: params.signal,\n handler: async (data) => {\n return await respond({\n type: \"Ok\",\n size: blob.size,\n range: translatedRange,\n data: Readable.fromWeb(data),\n });\n },\n });\n } catch (error: unknown) {\n if (!isAbortError(error)) {\n if (isDownloadNetworkError400(error) && error.statusCode === 404) {\n this.logger(\"info\", `PFrames blob store known race error: ${ensureError(error)}`);\n } else {\n this.logger(\"warn\", `PFrames blob store unhandled error: ${ensureError(error)}`);\n }\n }\n return await respond({ type: \"InternalError\" });\n }\n }\n}\n\nclass RemoteBlobProviderImpl implements RemoteBlobProvider<PlTreeEntry> {\n constructor(\n private readonly pool: RemoteBlobPool,\n private readonly server: PFrameInternal.HttpServer,\n ) {}\n\n public static async init(\n blobDriver: DownloadDriver,\n logger: PFrameInternal.Logger,\n serverOptions: Omit<PFrameInternal.HttpServerOptions, \"handler\">,\n ): Promise<RemoteBlobProviderImpl> {\n const pool = new RemoteBlobPool(blobDriver, logger);\n const store = new BlobStore({ remoteBlobProvider: pool, logger });\n\n const handler = HttpHelpers.createRequestHandler({ store });\n const server = await HttpHelpers.createHttpServer({ ...serverOptions, handler });\n logger(\"info\", `PFrames HTTP server started on ${server.info.url}`);\n\n return new RemoteBlobProviderImpl(pool, server);\n }\n\n public acquire(params: PlTreeEntry): PoolEntry<PFrameInternal.PFrameBlobId> {\n return this.pool.acquire(params);\n }\n\n public httpServerInfo(): PFrameInternal.HttpServerInfo {\n return this.server.info;\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.server.stop();\n await this.pool[Symbol.asyncDispose]();\n }\n}\n\nexport interface InternalPFrameDriver extends AbstractInternalPFrameDriver<\n PColumnDataUniversal<PlTreeNodeAccessor>\n> {}\n\nexport type PFrameDriverOps = AbstractPFrameDriverOps & {\n /** Port to run parquet HTTP server on. */\n parquetServerPort: number;\n};\n\nexport const PFrameDriverOpsDefaults: PFrameDriverOps = {\n ...AbstractPFrameDriverOpsDefaults,\n parquetServerPort: 0, // 0 means that some unused port will be assigned by the OS\n};\n\nexport async function createPFrameDriver(params: {\n blobDriver: DownloadDriver;\n logger: MiLogger;\n spillPath: string;\n options: PFrameDriverOps;\n}): Promise<InternalPFrameDriver> {\n const resolvedSpillPath = path.resolve(params.spillPath);\n await emptyDir(resolvedSpillPath);\n\n const logger: PFrameInternal.Logger = (level, message) => params.logger[level](message);\n const localBlobProvider = new LocalBlobProviderImpl(params.blobDriver, logger);\n const remoteBlobProvider = await RemoteBlobProviderImpl.init(params.blobDriver, logger, {\n port: params.options.parquetServerPort,\n });\n\n const resolveDataInfo = (spec: PColumnSpec, data: PColumnDataUniversal<PlTreeNodeAccessor>) => {\n return isPlTreeNodeAccessor(data)\n ? parseDataInfoResource(data)\n : isDataInfo(data)\n ? data.type === \"ParquetPartitioned\"\n ? mapDataInfo(data, (a) => traverseParquetChunkResource(a))\n : mapDataInfo(data, (a) => a.persist())\n : makeJsonDataInfo(spec, data);\n };\n\n return new AbstractPFrameDriver({\n logger,\n localBlobProvider,\n remoteBlobProvider,\n spillPath: resolvedSpillPath,\n options: params.options,\n resolveDataInfo,\n });\n}\n"],"mappings":";;;;;;;;;;;;AA0CA,SAAS,WAAW,KAA+C;AACjE,QAAO,OAAO,IAAI,IAAI;;AAIxB,IAAM,wBAAN,cACU,iBAEV;CACE,YACE,AAAiB,YACjB,AAAiB,QACjB;AACA,SAAO;EAHU;EACA;;CAKnB,AAAU,mBAAmB,QAAkD;AAC7E,SAAO,WAAW,OAAO;;CAG3B,AAAU,kBAAkB,QAAqB,MAA8C;AAC7F,SAAO,KAAK,WAAW,kBAAkB,OAAO;;CAGlD,AAAO,SAAS,QAAgD;EAC9D,MAAM,WAAW,MAAM,YAAY,OAAO;AAC1C,MAAI,CAAC,SAAU,OAAM,IAAI,kBAAkB,sBAAsB,OAAO,aAAa;AACrF,SAAO;;CAGT,AAAO,eACL,QAC0D;AAC1D,SAAO;GACL,aAAa,OAAO,YAA2C;AAC7D,QAAI;AACF,WAAM,QAAQ,IACZ,QAAQ,KAAK,WAAW,KAAK,SAAS,OAAO,CAAC,qBAAqB,OAAO,CAAC,CAC5E;aACM,KAAc;AACrB,SAAI,CAAC,aAAa,IAAI,CAAE,OAAM;;;GAGlC,oBAAoB,OAAO,WAAwC;IAEjE,MAAM,OAAO,MADM,KAAK,SAAS,OAAO,CACV,iBAAiB,OAAO;AACtD,WAAO,MAAM,KAAK,WAAW,WAAW,KAAK,QAAQ,EAAE,QAAQ,CAAC;;GAEnE;;;AAKL,IAAM,iBAAN,cAA6B,iBAI3B;CACA,YACE,AAAiB,YACjB,AAAiB,QACjB;AACA,SAAO;EAHU;EACA;;CAKnB,AAAU,mBAAmB,QAAkD;AAC7E,SAAO,WAAW,OAAO;;CAG3B,AAAU,kBAAkB,QAAqB,MAA+C;AAC9F,SAAO,KAAK,WAAW,gBAAgB,OAAO;;CAGhD,AAAO,SAAS,QAAiD;EAC/D,MAAM,WAAW,MAAM,YAAY,OAAO;AAC1C,MAAI,CAAC,SAAU,OAAM,IAAI,kBAAkB,uBAAuB,OAAO,aAAa;AACtF,SAAO;;CAGT,MAAa,YACX,QACA,SAKY;AACZ,SAAO,MAAM,KAAK,WAAW,YAAY,QAAQ;GAC/C,OAAO;IACL,MAAM,QAAQ,MAAM;IACpB,IAAI,QAAQ,MAAM,MAAM;IACzB;GACD,QAAQ,QAAQ;GAChB,SAAS,QAAQ;GAClB,CAAC;;;AAQN,IAAM,YAAN,cAAwB,eAAe,gBAAgB;CACrD,AAAiB;CAEjB,YAAY,SAA2B;AACrC,QAAM,QAAQ;AACd,OAAK,qBAAqB,QAAQ;;CAGpC,MAAsB,QACpB,UACA,QAMe;EACf,MAAM,SAAS,SAAS,MACtB,GACA,CAAC,eAAe,iBAAiB,OAClC;EACD,MAAM,UAAU,OAAO,aAAgE;AACrF,OAAI;AACF,UAAM,OAAO,SAAS,SAAS;YACxB,OAAgB;AACvB,SAAK,OACH,QACA,sEAAsE,YAAY,MAAM,GACzF;;;AAIL,MAAI;GACF,MAAM,aAAa,KAAK,mBAAmB,YAAY,OAAO;AAC9D,OAAI,CAAC,WAAY,QAAO,MAAM,QAAQ,EAAE,MAAM,YAAY,CAAC;GAE3D,IAAI;AACJ,OAAI;AACF,WAAO,MAAM,WAAW,UAAU;YAC3B,OAAgB;AACvB,SAAK,OACH,SACA,0DAA0D,YAAY,MAAM,GAC7E;AACD,WAAO,MAAM,QAAQ,EAAE,MAAM,iBAAiB,CAAC;;AAEjD,UAAO,OAAO,gBAAgB;GAE9B,MAAM,kBAAkB,KAAK,UAAU,KAAK,MAAM,OAAO,MAAM;AAC/D,OAAI,CAAC,gBACH,QAAO,MAAM,QAAQ;IACnB,MAAM;IACN,MAAM,KAAK;IACZ,CAAC;AAGJ,OAAI,OAAO,WAAW,OACpB,QAAO,MAAM,QAAQ;IACnB,MAAM;IACN,MAAM,KAAK;IACX,OAAO;IACR,CAAC;AAGJ,QAAK,OACH,QACA,6CAA6C,OAAO,WACxC,gBAAgB,MAAM,KAAK,gBAAgB,IAAI,GAC5D;AACD,UAAO,MAAM,KAAK,mBAAmB,YAAY,KAAK,QAAQ;IAC5D,OAAO;IACP,QAAQ,OAAO;IACf,SAAS,OAAO,SAAS;AACvB,YAAO,MAAM,QAAQ;MACnB,MAAM;MACN,MAAM,KAAK;MACX,OAAO;MACP,MAAM,SAAS,QAAQ,KAAK;MAC7B,CAAC;;IAEL,CAAC;WACK,OAAgB;AACvB,OAAI,CAAC,aAAa,MAAM,CACtB,KAAI,0BAA0B,MAAM,IAAI,MAAM,eAAe,IAC3D,MAAK,OAAO,QAAQ,wCAAwC,YAAY,MAAM,GAAG;OAEjF,MAAK,OAAO,QAAQ,uCAAuC,YAAY,MAAM,GAAG;AAGpF,UAAO,MAAM,QAAQ,EAAE,MAAM,iBAAiB,CAAC;;;;AAKrD,IAAM,yBAAN,MAAM,uBAAkE;CACtE,YACE,AAAiB,MACjB,AAAiB,QACjB;EAFiB;EACA;;CAGnB,aAAoB,KAClB,YACA,QACA,eACiC;EACjC,MAAM,OAAO,IAAI,eAAe,YAAY,OAAO;EACnD,MAAM,QAAQ,IAAI,UAAU;GAAE,oBAAoB;GAAM;GAAQ,CAAC;EAEjE,MAAM,UAAU,YAAY,qBAAqB,EAAE,OAAO,CAAC;EAC3D,MAAM,SAAS,MAAM,YAAY,iBAAiB;GAAE,GAAG;GAAe;GAAS,CAAC;AAChF,SAAO,QAAQ,kCAAkC,OAAO,KAAK,MAAM;AAEnE,SAAO,IAAI,uBAAuB,MAAM,OAAO;;CAGjD,AAAO,QAAQ,QAA6D;AAC1E,SAAO,KAAK,KAAK,QAAQ,OAAO;;CAGlC,AAAO,iBAAgD;AACrD,SAAO,KAAK,OAAO;;CAGrB,OAAO,OAAO,gBAA+B;AAC3C,QAAM,KAAK,OAAO,MAAM;AACxB,QAAM,KAAK,KAAK,OAAO,eAAe;;;AAa1C,MAAa,0BAA2C;CACtD,GAAG;CACH,mBAAmB;CACpB;AAED,eAAsB,mBAAmB,QAKP;CAChC,MAAM,oBAAoB,KAAK,QAAQ,OAAO,UAAU;AACxD,OAAM,SAAS,kBAAkB;CAEjC,MAAM,UAAiC,OAAO,YAAY,OAAO,OAAO,OAAO,QAAQ;CACvF,MAAM,oBAAoB,IAAI,sBAAsB,OAAO,YAAY,OAAO;CAC9E,MAAM,qBAAqB,MAAM,uBAAuB,KAAK,OAAO,YAAY,QAAQ,EACtF,MAAM,OAAO,QAAQ,mBACtB,CAAC;CAEF,MAAM,mBAAmB,MAAmB,SAAmD;AAC7F,SAAO,qBAAqB,KAAK,GAC7B,sBAAsB,KAAK,GAC3B,WAAW,KAAK,GACd,KAAK,SAAS,uBACZ,YAAY,OAAO,MAAM,6BAA6B,EAAE,CAAC,GACzD,YAAY,OAAO,MAAM,EAAE,SAAS,CAAC,GACvC,iBAAiB,MAAM,KAAK;;AAGpC,QAAO,IAAI,qBAAqB;EAC9B;EACA;EACA;EACA,WAAW;EACX,SAAS,OAAO;EAChB;EACD,CAAC"}
1
+ {"version":3,"file":"driver.js","names":[],"sources":["../../src/pool/driver.ts"],"sourcesContent":["import {\n mapDataInfo,\n isDataInfo,\n ensureError,\n PFrameDriverError,\n isAbortError,\n type LocalBlobHandleAndSize,\n type RemoteBlobHandleAndSize,\n type RemoteBlobHandle,\n type ContentHandler,\n type PColumnSpec,\n type PColumnDataUniversal,\n} from \"@platforma-sdk/model\";\nimport { PFrameInternal } from \"@milaboratories/pl-model-middle-layer\";\nimport { emptyDir } from \"@milaboratories/ts-helpers\";\nimport { RefCountPoolBase, type PoolEntry, type MiLogger } from \"@milaboratories/helpers\";\nimport type { DownloadDriver } from \"@milaboratories/pl-drivers\";\nimport {\n isPlTreeNodeAccessor,\n type PlTreeEntry,\n type PlTreeNodeAccessor,\n} from \"@milaboratories/pl-tree\";\nimport type { Computable, ComputableStableDefined } from \"@milaboratories/computable\";\nimport {\n makeJsonDataInfo,\n AbstractPFrameDriver,\n AbstractPFrameDriverOpsDefaults,\n type AbstractInternalPFrameDriver,\n type AbstractPFrameDriverOps,\n type LocalBlobProvider,\n type RemoteBlobProvider,\n} from \"@milaboratories/pf-driver\";\nimport { HttpHelpers } from \"@milaboratories/pframes-rs-node\";\nimport path from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { parseDataInfoResource, traverseParquetChunkResource } from \"./data\";\nimport { isDownloadNetworkError400 } from \"@milaboratories/pl-drivers\";\n\nfunction makeBlobId(res: PlTreeEntry): PFrameInternal.PFrameBlobId {\n return String(res.rid) as PFrameInternal.PFrameBlobId;\n}\n\ntype LocalBlob = ComputableStableDefined<LocalBlobHandleAndSize>;\nclass LocalBlobProviderImpl\n extends RefCountPoolBase<PlTreeEntry, PFrameInternal.PFrameBlobId, LocalBlob>\n implements LocalBlobProvider<PlTreeEntry>\n{\n constructor(\n private readonly blobDriver: DownloadDriver,\n private readonly logger: PFrameInternal.Logger,\n ) {\n super();\n }\n\n protected calculateParamsKey(params: PlTreeEntry): PFrameInternal.PFrameBlobId {\n return makeBlobId(params);\n }\n\n protected createNewResource(params: PlTreeEntry, _key: PFrameInternal.PFrameBlobId): LocalBlob {\n return this.blobDriver.getDownloadedBlob(params);\n }\n\n public getByKey(blobId: PFrameInternal.PFrameBlobId): LocalBlob {\n const resource = super.tryGetByKey(blobId);\n if (!resource) throw new PFrameDriverError(`Local blob with id ${blobId} not found.`);\n return resource;\n }\n\n public makeDataSource(\n signal: AbortSignal,\n ): Omit<PFrameInternal.PFrameDataSourceV2, \"parquetServer\"> {\n return {\n preloadBlob: async (blobIds: PFrameInternal.PFrameBlobId[]) => {\n try {\n await Promise.all(\n blobIds.map((blobId) => this.getByKey(blobId).awaitStableFullValue(signal)),\n );\n } catch (err: unknown) {\n if (!isAbortError(err)) throw err;\n }\n },\n resolveBlobContent: async (blobId: PFrameInternal.PFrameBlobId) => {\n const computable = this.getByKey(blobId);\n const blob = await computable.awaitStableValue(signal);\n return await this.blobDriver.getContent(blob.handle, { signal });\n },\n };\n }\n}\n\ntype RemoteBlob = Computable<RemoteBlobHandleAndSize>;\nclass RemoteBlobPool extends RefCountPoolBase<\n PlTreeEntry,\n PFrameInternal.PFrameBlobId,\n RemoteBlob\n> {\n constructor(\n private readonly blobDriver: DownloadDriver,\n private readonly logger: PFrameInternal.Logger,\n ) {\n super();\n }\n\n protected calculateParamsKey(params: PlTreeEntry): PFrameInternal.PFrameBlobId {\n return makeBlobId(params);\n }\n\n protected createNewResource(params: PlTreeEntry, _key: PFrameInternal.PFrameBlobId): RemoteBlob {\n return this.blobDriver.getOnDemandBlob(params);\n }\n\n public getByKey(blobId: PFrameInternal.PFrameBlobId): RemoteBlob {\n const resource = super.tryGetByKey(blobId);\n if (!resource) throw new PFrameDriverError(`Remote blob with id ${blobId} not found.`);\n return resource;\n }\n\n public async withContent<T>(\n handle: RemoteBlobHandle,\n options: {\n range: PFrameInternal.FileRange;\n signal: AbortSignal;\n handler: ContentHandler<T>;\n },\n ): Promise<T> {\n return await this.blobDriver.withContent(handle, {\n range: {\n from: options.range.start,\n to: options.range.end + 1,\n },\n signal: options.signal,\n handler: options.handler,\n });\n }\n}\n\ninterface BlobStoreOptions extends PFrameInternal.ObjectStoreOptions {\n remoteBlobProvider: RemoteBlobPool;\n}\n\nclass BlobStore extends PFrameInternal.BaseObjectStore {\n private readonly remoteBlobProvider: RemoteBlobPool;\n\n constructor(options: BlobStoreOptions) {\n super(options);\n this.remoteBlobProvider = options.remoteBlobProvider;\n }\n\n public override async request(\n filename: PFrameInternal.ParquetFileName,\n params: {\n method: PFrameInternal.HttpMethod;\n range?: PFrameInternal.HttpRange;\n signal: AbortSignal;\n callback: (response: PFrameInternal.ObjectStoreResponse) => Promise<void>;\n },\n ): Promise<void> {\n const blobId = filename.slice(\n 0,\n -PFrameInternal.ParquetExtension.length,\n ) as PFrameInternal.PFrameBlobId;\n const respond = async (response: PFrameInternal.ObjectStoreResponse): Promise<void> => {\n try {\n await params.callback(response);\n } catch (error: unknown) {\n this.logger(\n \"warn\",\n `PFrames blob store received unhandled rejection from HTTP handler: ${ensureError(error)}`,\n );\n }\n };\n\n try {\n const computable = this.remoteBlobProvider.tryGetByKey(blobId);\n if (!computable) return await respond({ type: \"NotFound\" });\n\n let blob: RemoteBlobHandleAndSize;\n try {\n blob = await computable.getValue();\n } catch (error: unknown) {\n this.logger(\n \"error\",\n `PFrames blob store failed to get blob from computable: ${ensureError(error)}`,\n );\n return await respond({ type: \"InternalError\" });\n }\n params.signal.throwIfAborted();\n\n const translatedRange = this.translate(blob.size, params.range);\n if (!translatedRange) {\n return await respond({\n type: \"RangeNotSatisfiable\",\n size: blob.size,\n });\n }\n\n if (params.method === \"HEAD\") {\n return await respond({\n type: \"Ok\",\n size: blob.size,\n range: translatedRange,\n });\n }\n\n this.logger(\n \"info\",\n `PFrames blob store requesting content for ${blobId}, ` +\n `range [${translatedRange.start}..=${translatedRange.end}]`,\n );\n return await this.remoteBlobProvider.withContent(blob.handle, {\n range: translatedRange,\n signal: params.signal,\n handler: async (data) => {\n return await respond({\n type: \"Ok\",\n size: blob.size,\n range: translatedRange,\n data: Readable.fromWeb(data),\n });\n },\n });\n } catch (error: unknown) {\n if (!isAbortError(error)) {\n if (isDownloadNetworkError400(error) && error.statusCode === 404) {\n this.logger(\"info\", `PFrames blob store known race error: ${ensureError(error)}`);\n } else {\n this.logger(\"warn\", `PFrames blob store unhandled error: ${ensureError(error)}`);\n }\n }\n return await respond({ type: \"InternalError\" });\n }\n }\n}\n\nclass RemoteBlobProviderImpl implements RemoteBlobProvider<PlTreeEntry> {\n constructor(\n private readonly pool: RemoteBlobPool,\n private readonly server: PFrameInternal.HttpServer,\n ) {}\n\n public static async init(\n blobDriver: DownloadDriver,\n logger: PFrameInternal.Logger,\n serverOptions: Omit<PFrameInternal.HttpServerOptions, \"handler\">,\n ): Promise<RemoteBlobProviderImpl> {\n const pool = new RemoteBlobPool(blobDriver, logger);\n const store = new BlobStore({ remoteBlobProvider: pool, logger });\n\n const handler = HttpHelpers.createRequestHandler({ store });\n const server = await HttpHelpers.createHttpServer({ ...serverOptions, handler });\n logger(\"info\", `PFrames HTTP server started on ${server.info.url}`);\n\n return new RemoteBlobProviderImpl(pool, server);\n }\n\n public acquire(params: PlTreeEntry): PoolEntry<PFrameInternal.PFrameBlobId> {\n return this.pool.acquire(params);\n }\n\n public httpServerInfo(): PFrameInternal.HttpServerInfo {\n return this.server.info;\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.server.stop();\n await this.pool[Symbol.asyncDispose]();\n }\n}\n\nexport interface InternalPFrameDriver extends AbstractInternalPFrameDriver<\n PColumnDataUniversal<PlTreeNodeAccessor>\n> {}\n\nexport type PFrameDriverOps = AbstractPFrameDriverOps & {\n /** Port to run parquet HTTP server on. */\n parquetServerPort: number;\n};\n\nexport const PFrameDriverOpsDefaults: PFrameDriverOps = {\n ...AbstractPFrameDriverOpsDefaults,\n parquetServerPort: 0, // 0 means that some unused port will be assigned by the OS\n};\n\nexport async function createPFrameDriver(params: {\n blobDriver: DownloadDriver;\n logger: MiLogger;\n spillPath: string;\n options: PFrameDriverOps;\n}): Promise<InternalPFrameDriver> {\n const resolvedSpillPath = path.resolve(params.spillPath);\n await emptyDir(resolvedSpillPath);\n\n const logger: PFrameInternal.Logger = (level, message) => params.logger[level](message);\n const localBlobProvider = new LocalBlobProviderImpl(params.blobDriver, logger);\n const remoteBlobProvider = await RemoteBlobProviderImpl.init(params.blobDriver, logger, {\n port: params.options.parquetServerPort,\n });\n\n const resolveDataInfo = (spec: PColumnSpec, data: PColumnDataUniversal<PlTreeNodeAccessor>) => {\n return isPlTreeNodeAccessor(data)\n ? parseDataInfoResource(data)\n : isDataInfo(data)\n ? data.type === \"ParquetPartitioned\"\n ? mapDataInfo(data, (a) => traverseParquetChunkResource(a))\n : mapDataInfo(data, (a) => a.persist())\n : makeJsonDataInfo(spec, data);\n };\n\n return new AbstractPFrameDriver({\n logger,\n localBlobProvider,\n remoteBlobProvider,\n spillPath: resolvedSpillPath,\n options: params.options,\n resolveDataInfo,\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-middle-layer",
3
- "version": "1.54.2",
3
+ "version": "1.54.4",
4
4
  "description": "Pl Middle Layer",
5
5
  "keywords": [],
6
6
  "license": "UNLICENSED",
@@ -30,22 +30,23 @@
30
30
  "utility-types": "^3.11.0",
31
31
  "yaml": "^2.8.0",
32
32
  "zod": "~3.23.8",
33
- "@milaboratories/computable": "2.9.1",
34
- "@milaboratories/pf-spec-driver": "1.2.2",
35
- "@milaboratories/pf-driver": "1.3.2",
36
- "@milaboratories/pl-deployments": "2.16.4",
37
- "@milaboratories/pl-client": "2.18.4",
38
- "@milaboratories/pl-errors": "1.2.4",
33
+ "@milaboratories/computable": "2.9.2",
34
+ "@milaboratories/pf-driver": "1.3.3",
35
+ "@milaboratories/pl-client": "2.18.5",
36
+ "@milaboratories/pf-spec-driver": "1.2.3",
37
+ "@milaboratories/pl-deployments": "2.16.5",
38
+ "@milaboratories/helpers": "1.14.1",
39
+ "@milaboratories/pl-errors": "1.2.5",
40
+ "@milaboratories/pl-drivers": "1.12.7",
41
+ "@milaboratories/pl-model-common": "1.31.1",
39
42
  "@milaboratories/pl-http": "1.2.4",
40
- "@milaboratories/pl-drivers": "1.12.6",
41
- "@milaboratories/pl-model-middle-layer": "1.16.2",
42
- "@milaboratories/pl-model-common": "1.31.0",
43
- "@milaboratories/pl-model-backend": "1.2.4",
43
+ "@milaboratories/pl-model-backend": "1.2.5",
44
+ "@milaboratories/pl-tree": "1.9.6",
45
+ "@milaboratories/pl-model-middle-layer": "1.16.3",
44
46
  "@milaboratories/resolve-helper": "1.1.3",
45
- "@milaboratories/pl-tree": "1.9.5",
46
- "@milaboratories/ts-helpers": "1.8.0",
47
- "@platforma-sdk/model": "1.63.0",
48
- "@platforma-sdk/block-tools": "2.7.4",
47
+ "@milaboratories/ts-helpers": "1.8.1",
48
+ "@platforma-sdk/block-tools": "2.7.5",
49
+ "@platforma-sdk/model": "1.63.1",
49
50
  "@platforma-sdk/workflow-tengo": "5.11.0"
50
51
  },
51
52
  "devDependencies": {
@@ -15,7 +15,8 @@ import {
15
15
  } from "@milaboratories/pl-drivers";
16
16
  import type * as Sdk from "@milaboratories/pl-model-common";
17
17
  import type { Signer } from "@milaboratories/ts-helpers";
18
- import { HmacSha256Signer, isAsyncDisposable } from "@milaboratories/ts-helpers";
18
+ import { isAsyncDisposable } from "@milaboratories/helpers";
19
+ import { HmacSha256Signer } from "@milaboratories/ts-helpers";
19
20
  import type { InternalPFrameDriver } from "../pool";
20
21
  import { createPFrameDriver } from "../pool";
21
22
  import type { DriverKitOps, DriverKitOpsConstructor } from "./ops";
@@ -118,7 +118,7 @@ function cached<ModId, T>(modIdCb: () => ModId, valueCb: () => T): () => T {
118
118
  lastModId = currentModId;
119
119
  value = valueCb();
120
120
  }
121
- return valueCb();
121
+ return value!;
122
122
  };
123
123
  }
124
124
 
@@ -136,21 +136,35 @@ class BlockInfo {
136
136
 
137
137
  if ((this.fields.prodOutput === undefined) !== (this.fields.prodCtx === undefined))
138
138
  throw new Error("inconsistent prod fields");
139
+ if ((this.fields.prodOutput === undefined) !== (this.fields.prodUiCtx === undefined))
140
+ throw new Error("inconsistent prod fields (prodUiCtx)");
139
141
 
140
142
  if ((this.fields.stagingOutput === undefined) !== (this.fields.stagingCtx === undefined))
141
143
  throw new Error("inconsistent stage fields");
144
+ if ((this.fields.stagingOutput === undefined) !== (this.fields.stagingUiCtx === undefined))
145
+ throw new Error("inconsistent stage fields (stagingUiCtx)");
142
146
 
143
147
  if (
144
148
  (this.fields.prodOutputPrevious === undefined) !==
145
149
  (this.fields.prodCtxPrevious === undefined)
146
150
  )
147
151
  throw new Error("inconsistent prod cache fields");
152
+ if (
153
+ (this.fields.prodOutputPrevious === undefined) !==
154
+ (this.fields.prodUiCtxPrevious === undefined)
155
+ )
156
+ throw new Error("inconsistent prod cache fields (prodUiCtxPrevious)");
148
157
 
149
158
  if (
150
159
  (this.fields.stagingOutputPrevious === undefined) !==
151
160
  (this.fields.stagingCtxPrevious === undefined)
152
161
  )
153
162
  throw new Error("inconsistent stage cache fields");
163
+ if (
164
+ (this.fields.stagingOutputPrevious === undefined) !==
165
+ (this.fields.stagingUiCtxPrevious === undefined)
166
+ )
167
+ throw new Error("inconsistent stage cache fields (stagingUiCtxPrevious)");
154
168
 
155
169
  if (this.fields.blockPack === undefined) throw new Error("no block pack field");
156
170
 
@@ -337,19 +351,28 @@ export class ProjectMutator {
337
351
  ) {}
338
352
 
339
353
  private fixProblemsAndMigrate() {
340
- // Fix inconsistent production fields
354
+ // Fix inconsistent production fields.
355
+ // All four fields (prodArgs, prodOutput, prodCtx, prodUiCtx) must be present together.
356
+ // prodUiCtx can be missing after project duplication: prodCtx uses a holder wrapper
357
+ // (always non-null), but prodUiCtx is a raw FieldRef that may still be NullResourceId
358
+ // at snapshot time and thus not copied.
341
359
  this.blockInfos.forEach((blockInfo) => {
342
360
  if (
343
361
  blockInfo.fields.prodArgs === undefined ||
344
362
  blockInfo.fields.prodOutput === undefined ||
345
- blockInfo.fields.prodCtx === undefined
363
+ blockInfo.fields.prodCtx === undefined ||
364
+ blockInfo.fields.prodUiCtx === undefined
346
365
  )
347
366
  this.deleteBlockFields(blockInfo.id, "prodArgs", "prodOutput", "prodCtx", "prodUiCtx");
348
367
  });
349
368
 
350
- // Fix inconsistent staging fields
369
+ // Fix inconsistent staging fields (same FieldRef issue for stagingUiCtx)
351
370
  this.blockInfos.forEach((blockInfo) => {
352
- if (blockInfo.fields.stagingOutput === undefined || blockInfo.fields.stagingCtx === undefined)
371
+ if (
372
+ blockInfo.fields.stagingOutput === undefined ||
373
+ blockInfo.fields.stagingCtx === undefined ||
374
+ blockInfo.fields.stagingUiCtx === undefined
375
+ )
353
376
  this.deleteBlockFields(blockInfo.id, "stagingOutput", "stagingCtx", "stagingUiCtx");
354
377
  });
355
378
 
@@ -357,7 +380,8 @@ export class ProjectMutator {
357
380
  this.blockInfos.forEach((blockInfo) => {
358
381
  if (
359
382
  blockInfo.fields.prodOutputPrevious === undefined ||
360
- blockInfo.fields.prodCtxPrevious === undefined
383
+ blockInfo.fields.prodCtxPrevious === undefined ||
384
+ blockInfo.fields.prodUiCtxPrevious === undefined
361
385
  )
362
386
  this.deleteBlockFields(
363
387
  blockInfo.id,
@@ -367,7 +391,8 @@ export class ProjectMutator {
367
391
  );
368
392
  if (
369
393
  blockInfo.fields.stagingOutputPrevious === undefined ||
370
- blockInfo.fields.stagingCtxPrevious === undefined
394
+ blockInfo.fields.stagingCtxPrevious === undefined ||
395
+ blockInfo.fields.stagingUiCtxPrevious === undefined
371
396
  )
372
397
  this.deleteBlockFields(
373
398
  blockInfo.id,
@@ -1984,11 +2009,16 @@ export async function duplicateProject(
1984
2009
  tx.setKValue(newPrj, ProjectCreatedTimestamp, ts);
1985
2010
  tx.setKValue(newPrj, ProjectLastModifiedTimestamp, ts);
1986
2011
 
1987
- // Copy all dynamic fields by sharing references
2012
+ // Copy only persistent block fields (FieldsToDuplicate).
2013
+ // Transient fields (prodChainCtx, staging*, *Previous) are rebuilt on project open
2014
+ // by fixProblemsAndMigrate(). Copying them is both unnecessary and dangerous:
2015
+ // some fields use raw FieldRefs (not holder-wrapped) whose values may be NullResourceId
2016
+ // at snapshot time, leading to partially copied field groups and broken project state.
1988
2017
  for (const f of sourceData.fields) {
1989
- if (isNotNullResourceId(f.value)) {
1990
- tx.createField(field(newPrj, f.name), "Dynamic", f.value);
1991
- }
2018
+ if (isNullResourceId(f.value)) continue;
2019
+ const parsed = parseProjectField(f.name);
2020
+ if (parsed !== undefined && !FieldsToDuplicate.has(parsed.fieldName)) continue;
2021
+ tx.createField(field(newPrj, f.name), "Dynamic", f.value);
1992
2022
  }
1993
2023
 
1994
2024
  return newPrj;
@@ -12,12 +12,8 @@ import {
12
12
  type PColumnDataUniversal,
13
13
  } from "@platforma-sdk/model";
14
14
  import { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
15
- import {
16
- emptyDir,
17
- RefCountPoolBase,
18
- type PoolEntry,
19
- type MiLogger,
20
- } from "@milaboratories/ts-helpers";
15
+ import { emptyDir } from "@milaboratories/ts-helpers";
16
+ import { RefCountPoolBase, type PoolEntry, type MiLogger } from "@milaboratories/helpers";
21
17
  import type { DownloadDriver } from "@milaboratories/pl-drivers";
22
18
  import {
23
19
  isPlTreeNodeAccessor,