@milaboratories/pl-middle-layer 1.59.14 → 1.60.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"project_list.js","names":[],"sources":["../../src/middle_layer/project_list.ts"],"sourcesContent":["import type { PruningFunction } from \"@milaboratories/pl-tree\";\nimport { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport type { PlClient, SignedResourceId, ResourceType } from \"@milaboratories/pl-client\";\nimport { resourceIdToString, resourceTypesEqual } from \"@milaboratories/pl-client\";\nimport type { TreeAndComputableU } from \"./types\";\nimport type { WatchableValue } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { ProjectId, ProjectListEntry } from \"../model/project_model\";\nimport {\n ProjectCreatedTimestamp,\n ProjectLastModifiedTimestamp,\n ProjectMetaKey,\n} from \"../model/project_model\";\nimport type { MiddleLayerEnvironment } from \"./middle_layer\";\nimport { notEmpty } from \"@milaboratories/ts-helpers\";\nimport type { ProjectMeta } from \"@milaboratories/pl-model-middle-layer\";\n\nexport const ProjectsField = \"projects\";\nexport const ProjectsResourceType: ResourceType = { name: \"Projects\", version: \"1\" };\n\nexport const ProjectsListTreePruningFunction: PruningFunction = (resource) => {\n if (!resourceTypesEqual(resource.type, ProjectsResourceType)) return [];\n return resource.fields;\n};\n\nexport async function createProjectList(\n pl: PlClient,\n rid: SignedResourceId,\n openedProjects: WatchableValue<ProjectId[]>,\n env: MiddleLayerEnvironment,\n): Promise<TreeAndComputableU<ProjectListEntry[]>> {\n const tree = await SynchronizedTreeState.init(\n pl,\n rid,\n {\n ...env.ops.defaultTreeOptions,\n pruning: ProjectsListTreePruningFunction,\n },\n env.logger,\n );\n\n const c = Computable.make((ctx) => {\n const node = ctx.accessor(tree.entry()).node();\n const oProjects = openedProjects.getValue(ctx);\n if (node === undefined) return undefined;\n const result: ProjectListEntry[] = [];\n\n // Projects list resource keeps projects assigned to fields. Each field name is project's UUID\n for (const field of node.listDynamicFields()) {\n const prj = node.traverse(field);\n if (prj === undefined) continue;\n const meta = notEmpty(prj.getKeyValueAsJson<ProjectMeta>(ProjectMetaKey));\n const created = notEmpty(prj.getKeyValueAsJson<number>(ProjectCreatedTimestamp));\n const lastModified = notEmpty(prj.getKeyValueAsJson<number>(ProjectLastModifiedTimestamp));\n const projectId = resourceIdToString(prj.id) as ProjectId;\n result.push({\n id: projectId,\n created: new Date(created),\n lastModified: new Date(lastModified),\n opened: oProjects.indexOf(projectId) >= 0,\n meta,\n });\n }\n result.sort((p) => -p.lastModified.valueOf());\n return result;\n }).withStableType();\n\n return { computable: c, tree };\n}\n"],"mappings":";;;;;;AAiBA,MAAa,gBAAgB;AAC7B,MAAa,uBAAqC;CAAE,MAAM;CAAY,SAAS;CAAK;AAEpF,MAAa,mCAAoD,aAAa;AAC5E,KAAI,CAAC,mBAAmB,SAAS,MAAM,qBAAqB,CAAE,QAAO,EAAE;AACvE,QAAO,SAAS;;AAGlB,eAAsB,kBACpB,IACA,KACA,gBACA,KACiD;CACjD,MAAM,OAAO,MAAM,sBAAsB,KACvC,IACA,KACA;EACE,GAAG,IAAI,IAAI;EACX,SAAS;EACV,EACD,IAAI,OACL;AA4BD,QAAO;EAAE,YA1BC,WAAW,MAAM,QAAQ;GACjC,MAAM,OAAO,IAAI,SAAS,KAAK,OAAO,CAAC,CAAC,MAAM;GAC9C,MAAM,YAAY,eAAe,SAAS,IAAI;AAC9C,OAAI,SAAS,KAAA,EAAW,QAAO,KAAA;GAC/B,MAAM,SAA6B,EAAE;AAGrC,QAAK,MAAM,SAAS,KAAK,mBAAmB,EAAE;IAC5C,MAAM,MAAM,KAAK,SAAS,MAAM;AAChC,QAAI,QAAQ,KAAA,EAAW;IACvB,MAAM,OAAO,SAAS,IAAI,kBAA+B,eAAe,CAAC;IACzE,MAAM,UAAU,SAAS,IAAI,kBAA0B,wBAAwB,CAAC;IAChF,MAAM,eAAe,SAAS,IAAI,kBAA0B,6BAA6B,CAAC;IAC1F,MAAM,YAAY,mBAAmB,IAAI,GAAG;AAC5C,WAAO,KAAK;KACV,IAAI;KACJ,SAAS,IAAI,KAAK,QAAQ;KAC1B,cAAc,IAAI,KAAK,aAAa;KACpC,QAAQ,UAAU,QAAQ,UAAU,IAAI;KACxC;KACD,CAAC;;AAEJ,UAAO,MAAM,MAAM,CAAC,EAAE,aAAa,SAAS,CAAC;AAC7C,UAAO;IACP,CAAC,gBAAgB;EAEK;EAAM"}
1
+ {"version":3,"file":"project_list.js","names":[],"sources":["../../src/middle_layer/project_list.ts"],"sourcesContent":["import type { PruningFunction } from \"@milaboratories/pl-tree\";\nimport { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport type { Filter, PlClient, ResourceType, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { resourceIdToString, resourceTypesEqual, treeFilter } from \"@milaboratories/pl-client\";\nimport type { TreeAndComputableU } from \"./types\";\nimport type { WatchableValue } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { ProjectId, ProjectListEntry } from \"../model/project_model\";\nimport {\n ProjectCreatedTimestamp,\n ProjectLastModifiedTimestamp,\n ProjectMetaKey,\n} from \"../model/project_model\";\nimport type { MiddleLayerEnvironment } from \"./middle_layer\";\nimport { notEmpty } from \"@milaboratories/ts-helpers\";\nimport type { ProjectMeta } from \"@milaboratories/pl-model-middle-layer\";\n\nexport const ProjectsField = \"projects\";\nexport const ProjectsResourceType: ResourceType = { name: \"Projects\", version: \"1\" };\n\nexport const ProjectsListTreePruningFunction: PruningFunction = (resource) => {\n if (!resourceTypesEqual(resource.type, ProjectsResourceType)) return [];\n return resource.fields;\n};\n\nexport const projectsListFieldFilter: Filter = treeFilter.resourceTypeEq(\"Projects\");\n\nexport async function createProjectList(\n pl: PlClient,\n rid: SignedResourceId,\n openedProjects: WatchableValue<ProjectId[]>,\n env: MiddleLayerEnvironment,\n): Promise<TreeAndComputableU<ProjectListEntry[]>> {\n const tree = await SynchronizedTreeState.init(\n pl,\n rid,\n {\n ...env.ops.defaultTreeOptions,\n pruning: ProjectsListTreePruningFunction,\n fieldFilter: projectsListFieldFilter,\n },\n env.logger,\n );\n\n const c = Computable.make((ctx) => {\n const node = ctx.accessor(tree.entry()).node();\n const oProjects = openedProjects.getValue(ctx);\n if (node === undefined) return undefined;\n const result: ProjectListEntry[] = [];\n\n // Projects list resource keeps projects assigned to fields. Each field name is project's UUID\n for (const field of node.listDynamicFields()) {\n const prj = node.traverse(field);\n if (prj === undefined) continue;\n const meta = notEmpty(prj.getKeyValueAsJson<ProjectMeta>(ProjectMetaKey));\n const created = notEmpty(prj.getKeyValueAsJson<number>(ProjectCreatedTimestamp));\n const lastModified = notEmpty(prj.getKeyValueAsJson<number>(ProjectLastModifiedTimestamp));\n const projectId = resourceIdToString(prj.id) as ProjectId;\n result.push({\n id: projectId,\n created: new Date(created),\n lastModified: new Date(lastModified),\n opened: oProjects.indexOf(projectId) >= 0,\n meta,\n });\n }\n result.sort((p) => -p.lastModified.valueOf());\n return result;\n }).withStableType();\n\n return { computable: c, tree };\n}\n"],"mappings":";;;;;;AAiBA,MAAa,gBAAgB;AAC7B,MAAa,uBAAqC;CAAE,MAAM;CAAY,SAAS;CAAK;AAEpF,MAAa,mCAAoD,aAAa;AAC5E,KAAI,CAAC,mBAAmB,SAAS,MAAM,qBAAqB,CAAE,QAAO,EAAE;AACvE,QAAO,SAAS;;AAGlB,MAAa,0BAAkC,WAAW,eAAe,WAAW;AAEpF,eAAsB,kBACpB,IACA,KACA,gBACA,KACiD;CACjD,MAAM,OAAO,MAAM,sBAAsB,KACvC,IACA,KACA;EACE,GAAG,IAAI,IAAI;EACX,SAAS;EACT,aAAa;EACd,EACD,IAAI,OACL;AA4BD,QAAO;EAAE,YA1BC,WAAW,MAAM,QAAQ;GACjC,MAAM,OAAO,IAAI,SAAS,KAAK,OAAO,CAAC,CAAC,MAAM;GAC9C,MAAM,YAAY,eAAe,SAAS,IAAI;AAC9C,OAAI,SAAS,KAAA,EAAW,QAAO,KAAA;GAC/B,MAAM,SAA6B,EAAE;AAGrC,QAAK,MAAM,SAAS,KAAK,mBAAmB,EAAE;IAC5C,MAAM,MAAM,KAAK,SAAS,MAAM;AAChC,QAAI,QAAQ,KAAA,EAAW;IACvB,MAAM,OAAO,SAAS,IAAI,kBAA+B,eAAe,CAAC;IACzE,MAAM,UAAU,SAAS,IAAI,kBAA0B,wBAAwB,CAAC;IAChF,MAAM,eAAe,SAAS,IAAI,kBAA0B,6BAA6B,CAAC;IAC1F,MAAM,YAAY,mBAAmB,IAAI,GAAG;AAC5C,WAAO,KAAK;KACV,IAAI;KACJ,SAAS,IAAI,KAAK,QAAQ;KAC1B,cAAc,IAAI,KAAK,aAAa;KACpC,QAAQ,UAAU,QAAQ,UAAU,IAAI;KACxC;KACD,CAAC;;AAEJ,UAAO,MAAM,MAAM,CAAC,EAAE,aAAa,SAAS,CAAC;AAC7C,UAAO;IACP,CAAC,gBAAgB;EAEK;EAAM"}
@@ -2,7 +2,7 @@ import { SynchronizedTreeOps, SynchronizedTreeState } from "@milaboratories/pl-t
2
2
  import { Computable } from "@milaboratories/computable";
3
3
 
4
4
  //#region src/middle_layer/types.d.ts
5
- type TemporalSynchronizedTreeOps = Pick<SynchronizedTreeOps, "pollingInterval" | "stopPollingDelay" | "logStat" | "initialTreeLoadingTimeout">;
5
+ type TemporalSynchronizedTreeOps = Pick<SynchronizedTreeOps, "pollingInterval" | "stopPollingDelay" | "logStat" | "initialTreeLoadingTimeout" | "traversalMode">;
6
6
  //#endregion
7
7
  export { TemporalSynchronizedTreeOps };
8
8
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-middle-layer",
3
- "version": "1.59.14",
3
+ "version": "1.60.0",
4
4
  "description": "Pl Middle Layer",
5
5
  "keywords": [],
6
6
  "license": "UNLICENSED",
@@ -30,24 +30,24 @@
30
30
  "utility-types": "^3.11.0",
31
31
  "yaml": "^2.8.0",
32
32
  "zod": "~3.25.76",
33
- "@milaboratories/computable": "2.9.4",
34
- "@milaboratories/helpers": "1.14.2",
35
33
  "@milaboratories/pf-spec-driver": "1.3.16",
36
- "@milaboratories/pl-client": "3.4.2",
37
- "@milaboratories/pl-deployments": "2.17.17",
34
+ "@milaboratories/helpers": "1.14.2",
35
+ "@milaboratories/computable": "2.9.4",
38
36
  "@milaboratories/pf-driver": "1.4.11",
39
- "@milaboratories/pl-errors": "1.4.6",
40
- "@milaboratories/pl-drivers": "1.14.6",
41
- "@milaboratories/pl-model-backend": "1.2.28",
42
- "@milaboratories/pl-model-common": "1.42.0",
43
- "@milaboratories/resolve-helper": "1.1.3",
37
+ "@milaboratories/pl-client": "3.5.0",
38
+ "@milaboratories/pl-deployments": "2.17.17",
39
+ "@milaboratories/pl-drivers": "1.14.7",
40
+ "@milaboratories/pl-errors": "1.4.7",
41
+ "@milaboratories/pl-model-backend": "1.2.29",
44
42
  "@milaboratories/pl-http": "1.2.4",
45
- "@milaboratories/pl-tree": "1.10.6",
46
43
  "@milaboratories/pl-model-middle-layer": "1.19.4",
47
- "@platforma-sdk/model": "1.76.4",
44
+ "@milaboratories/pl-model-common": "1.42.0",
45
+ "@milaboratories/pl-tree": "1.11.0",
46
+ "@milaboratories/resolve-helper": "1.1.3",
48
47
  "@milaboratories/ts-helpers": "1.8.2",
49
48
  "@platforma-sdk/block-tools": "2.7.25",
50
- "@platforma-sdk/workflow-tengo": "5.23.0"
49
+ "@platforma-sdk/workflow-tengo": "5.23.0",
50
+ "@platforma-sdk/model": "1.76.5"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "~24.5.2",
@@ -56,8 +56,8 @@
56
56
  "semver": "^7.7.2",
57
57
  "typescript": "~5.9.3",
58
58
  "vitest": "^4.1.3",
59
- "@milaboratories/ts-builder": "1.4.0",
60
59
  "@milaboratories/build-configs": "2.0.0",
60
+ "@milaboratories/ts-builder": "1.4.0",
61
61
  "@milaboratories/ts-configs": "1.2.3"
62
62
  },
63
63
  "engines": {
@@ -7,8 +7,32 @@ export type MlDebugFlags = {
7
7
  logOutputRecalculations?: boolean;
8
8
  logProjectOverviewStat: boolean;
9
9
  logJsExecStat: boolean;
10
+ /** Resolved value of MI_TREE_TRAVERSAL env var, or undefined if not set. */
11
+ treeTraversalMode?: "auto" | "client-bfs" | "backend-streaming";
10
12
  };
11
13
 
14
+ const VALID_TRAVERSAL_MODES = ["auto", "client-bfs", "backend-streaming"] as const;
15
+
16
+ /**
17
+ * Parse the raw MI_TREE_TRAVERSAL env value into a typed TraversalMode.
18
+ * Returns undefined when raw is undefined (env var absent).
19
+ * Logs a warning and returns "auto" on unrecognised values (AC-TM5).
20
+ * Exported for unit testing.
21
+ */
22
+ export function parseTraversalMode(
23
+ raw: string | undefined,
24
+ warn: (msg: string) => void = console.warn,
25
+ ): "auto" | "client-bfs" | "backend-streaming" | undefined {
26
+ if (raw === undefined) return undefined;
27
+ if ((VALID_TRAVERSAL_MODES as readonly string[]).includes(raw))
28
+ return raw as "auto" | "client-bfs" | "backend-streaming";
29
+ warn(
30
+ `MI_TREE_TRAVERSAL="${raw}" is not a valid traversal mode ` +
31
+ `(valid: ${VALID_TRAVERSAL_MODES.join(", ")}); falling back to "auto"`,
32
+ );
33
+ return "auto";
34
+ }
35
+
12
36
  let flags: MlDebugFlags | undefined = undefined;
13
37
  export function getDebugFlags() {
14
38
  if (flags) return flags;
@@ -26,5 +50,7 @@ export function getDebugFlags() {
26
50
  if (process.env.MI_LOG_TREE_STAT)
27
51
  flags.logTreeStats =
28
52
  process.env.MI_LOG_TREE_STAT === "cumulative" ? "cumulative" : "per-request";
53
+ const treeTraversalMode = parseTraversalMode(process.env.MI_TREE_TRAVERSAL);
54
+ if (treeTraversalMode !== undefined) flags.treeTraversalMode = treeTraversalMode;
29
55
  return flags;
30
56
  }
@@ -344,6 +344,12 @@ export class MiddleLayer {
344
344
  // overriding debug options from environment variables
345
345
  ops.defaultTreeOptions.logStat = getDebugFlags().logTreeStats;
346
346
  ops.debugOps.dumpInitialTreeState = getDebugFlags().dumpInitialTreeState;
347
+ // apply MI_TREE_TRAVERSAL only when the embedder hasn't set an explicit mode
348
+ if (
349
+ ops.defaultTreeOptions.traversalMode === undefined &&
350
+ getDebugFlags().treeTraversalMode !== undefined
351
+ )
352
+ ops.defaultTreeOptions.traversalMode = getDebugFlags().treeTraversalMode;
347
353
 
348
354
  const projects = await pl.withWriteTx("MLInitialization", async (tx) => {
349
355
  const projectsField = field(tx.clientRoot, ProjectsField);
@@ -238,7 +238,7 @@ export const DefaultMiddleLayerOpsSettings: Pick<
238
238
  > = {
239
239
  ...DefaultDriverKitOpsSettings,
240
240
  defaultTreeOptions: {
241
- pollingInterval: 350,
241
+ pollingInterval: 200,
242
242
  stopPollingDelay: 2500,
243
243
  initialTreeLoadingTimeout: 100 * 60 * 60 * 1000, // disable timeout for loading project tree (100 hours)
244
244
  },
@@ -1,5 +1,10 @@
1
1
  import type { MiddleLayerEnvironment } from "./middle_layer";
2
- import type { FieldData, OptionalAnyResourceId, SignedResourceId } from "@milaboratories/pl-client";
2
+ import type {
3
+ FieldData,
4
+ Filter,
5
+ OptionalAnyResourceId,
6
+ SignedResourceId,
7
+ } from "@milaboratories/pl-client";
3
8
  import {
4
9
  DefaultRetryOptions,
5
10
  ensureSignedResourceIdNotNull,
@@ -8,6 +13,9 @@ import {
8
13
  isTimeoutOrCancelError,
9
14
  Pl,
10
15
  resourceIdToString,
16
+ ResourceTypeName,
17
+ ResourceTypePrefix,
18
+ treeFilter,
11
19
  } from "@milaboratories/pl-client";
12
20
  import type { ComputableStableDefined, ComputableValueOrErrors } from "@milaboratories/computable";
13
21
  import { Computable } from "@milaboratories/computable";
@@ -723,6 +731,8 @@ export class Project {
723
731
  {
724
732
  ...env.ops.defaultTreeOptions,
725
733
  pruning: projectTreePruning(env.logger),
734
+ fieldFilter: projectTreeFieldFilter(),
735
+ traverseStopRules: projectTreeTraverseStopRules(),
726
736
  },
727
737
  env.logger,
728
738
  );
@@ -739,7 +749,7 @@ export class Project {
739
749
  }
740
750
  }
741
751
 
742
- function projectTreePruning(logger: MiLogger): PruningFunction {
752
+ export function projectTreePruning(logger: MiLogger): PruningFunction {
743
753
  return (r: ExtendedResourceData): FieldData[] => {
744
754
  if (r.fields.length > 1000)
745
755
  logger.warn(
@@ -763,6 +773,172 @@ function projectTreePruning(logger: MiLogger): PruningFunction {
763
773
  };
764
774
  }
765
775
 
776
+ /** ResourceTree analogue of projectTreePruning() used by modern backend path. */
777
+ export function projectTreeFieldFilter(): Filter {
778
+ return treeFilter.not(
779
+ treeFilter.or(
780
+ // StreamWorkdir/* — pruned entirely
781
+ treeFilter.resourceTypeMatch("^StreamWorkdir/"),
782
+ // BlockPackCustom: drop `template`
783
+ treeFilter.and(
784
+ treeFilter.resourceTypeEq("BlockPackCustom"),
785
+ treeFilter.fieldNameEq("template"),
786
+ ),
787
+ // UserProject: drop `__serviceTemplate*`
788
+ treeFilter.and(
789
+ treeFilter.resourceTypeEq("UserProject"),
790
+ treeFilter.fieldNameMatch("^__serviceTemplate"),
791
+ ),
792
+ // Blob — pruned entirely
793
+ treeFilter.resourceTypeEq("Blob"),
794
+ ),
795
+ );
796
+ }
797
+
798
+ /**
799
+ * Stop-rules for the ResourceTree backend path.
800
+ *
801
+ * Mirrors every case of DefaultFinalResourceDataPredicate in the same order.
802
+ * The mapping from BFS predicate logic to backend filter conditions:
803
+ *
804
+ * BFS predicate always true
805
+ * → no readyOrDuplicateOrError guard; backend stops traversal unconditionally.
806
+ *
807
+ * BFS predicate: readyOrDuplicateOrError(r)
808
+ * → readyOrDuplicateOrError(): stops when resource_ready_for_calculation,
809
+ * is_duplicate, or has_errors is true — exactly mirroring the BFS predicate.
810
+ *
811
+ * BFS predicate: readyAndHasAllOutputsFilled(r)
812
+ * → isFinal(true) + allOutputsFinal(true).
813
+ *
814
+ * BFS predicate always false (UserProject, Projects, ClientRoot)
815
+ * → no entry here; traversal always continues into them.
816
+ */
817
+ export function projectTreeTraverseStopRules(): Filter {
818
+ return treeFilter.or(
819
+ // BFS: readyOrDuplicateOrError(r) AND (fields===undefined OR error OR stream.value===downloadable.value).
820
+ // datactl sets stream field to point at the same resource as downloadable once processing
821
+ // is complete, so stream.value===downloadable.value is the "done" signal.
822
+ treeFilter.and(
823
+ treeFilter.resourceTypeEq(ResourceTypeName.StreamManager),
824
+ treeFilter.readyOrDuplicateOrError(),
825
+ ),
826
+ // BFS: readyOrDuplicateOrError(r)
827
+ treeFilter.and(
828
+ treeFilter.resourceTypeEq(ResourceTypeName.StdMap),
829
+ treeFilter.readyOrDuplicateOrError(),
830
+ ),
831
+ treeFilter.and(
832
+ treeFilter.resourceTypeEq(ResourceTypeName.StdMapSlash),
833
+ treeFilter.readyOrDuplicateOrError(),
834
+ ),
835
+ treeFilter.and(
836
+ treeFilter.resourceTypeEq(ResourceTypeName.EphStdMap),
837
+ treeFilter.readyOrDuplicateOrError(),
838
+ ),
839
+ treeFilter.and(
840
+ treeFilter.resourceTypeEq(ResourceTypeName.PFrame),
841
+ treeFilter.readyOrDuplicateOrError(),
842
+ ),
843
+ treeFilter.and(
844
+ treeFilter.resourceTypeEq(ResourceTypeName.ParquetChunk),
845
+ treeFilter.readyOrDuplicateOrError(),
846
+ ),
847
+ treeFilter.and(
848
+ treeFilter.resourceTypeEq(ResourceTypeName.BContext),
849
+ treeFilter.readyOrDuplicateOrError(),
850
+ ),
851
+ treeFilter.and(
852
+ treeFilter.resourceTypeEq(ResourceTypeName.BlockPackCustom),
853
+ treeFilter.readyOrDuplicateOrError(),
854
+ ),
855
+ treeFilter.and(
856
+ treeFilter.resourceTypeEq(ResourceTypeName.BinaryMap),
857
+ treeFilter.readyOrDuplicateOrError(),
858
+ ),
859
+ treeFilter.and(
860
+ treeFilter.resourceTypeEq(ResourceTypeName.BinaryValue),
861
+ treeFilter.readyOrDuplicateOrError(),
862
+ ),
863
+ treeFilter.and(
864
+ treeFilter.resourceTypeEq(ResourceTypeName.BlobMap),
865
+ treeFilter.readyOrDuplicateOrError(),
866
+ ),
867
+ treeFilter.and(
868
+ treeFilter.resourceTypeEq(ResourceTypeName.BResolveSingle),
869
+ treeFilter.readyOrDuplicateOrError(),
870
+ ),
871
+ treeFilter.and(
872
+ treeFilter.resourceTypeEq(ResourceTypeName.BResolveSingleNoResult),
873
+ treeFilter.readyOrDuplicateOrError(),
874
+ ),
875
+ treeFilter.and(
876
+ treeFilter.resourceTypeEq(ResourceTypeName.BQueryResult),
877
+ treeFilter.readyOrDuplicateOrError(),
878
+ ),
879
+ treeFilter.and(
880
+ treeFilter.resourceTypeEq(ResourceTypeName.TengoTemplate),
881
+ treeFilter.readyOrDuplicateOrError(),
882
+ ),
883
+ treeFilter.and(
884
+ treeFilter.resourceTypeEq(ResourceTypeName.TengoLib),
885
+ treeFilter.readyOrDuplicateOrError(),
886
+ ),
887
+ treeFilter.and(
888
+ treeFilter.resourceTypeEq(ResourceTypeName.SoftwareInfo),
889
+ treeFilter.readyOrDuplicateOrError(),
890
+ ),
891
+ treeFilter.and(
892
+ treeFilter.resourceTypeEq(ResourceTypeName.Dummy),
893
+ treeFilter.readyOrDuplicateOrError(),
894
+ ),
895
+ // BFS: r.type.version === "1" → always true → no state guard needed
896
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonResourceError),
897
+ // BFS: return true (unconditionally) → no readyOrDuplicateOrError guard needed
898
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonObject),
899
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonGzObject),
900
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonString),
901
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonArray),
902
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonNumber),
903
+ treeFilter.resourceTypeEq(ResourceTypeName.BContextEnd),
904
+ treeFilter.resourceTypeEq(ResourceTypeName.FrontendFromUrl),
905
+ treeFilter.resourceTypeEq(ResourceTypeName.FrontendFromFolder),
906
+ treeFilter.resourceTypeEq(ResourceTypeName.BObjectSpec),
907
+ treeFilter.resourceTypeEq(ResourceTypeName.Blob),
908
+ treeFilter.resourceTypeEq(ResourceTypeName.Null),
909
+ treeFilter.resourceTypeEq(ResourceTypeName.Binary),
910
+ treeFilter.resourceTypeEq(ResourceTypeName.LSProvider),
911
+ treeFilter.resourceTypeEq(ResourceTypeName.WorkingDirectory),
912
+ // BFS default branch: startsWith prefix → return true → no guard needed
913
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.Blob),
914
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.LS),
915
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.WorkingDirectory),
916
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.StorageSpaceAllocation),
917
+ // BFS default branch: readyAndHasAllOutputsFilled → readyOrDuplicateOrError() + outputsLocked(true) + allOutputsFinal(true)
918
+ treeFilter.and(
919
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.BlobUpload),
920
+ treeFilter.readyOrDuplicateOrError(),
921
+ treeFilter.outputsLocked(true),
922
+ treeFilter.allOutputsFinal(true),
923
+ ),
924
+ treeFilter.and(
925
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.BlobIndex),
926
+ treeFilter.readyOrDuplicateOrError(),
927
+ treeFilter.outputsLocked(true),
928
+ treeFilter.allOutputsFinal(true),
929
+ ),
930
+ // BFS default branch: readyOrDuplicateOrError
931
+ treeFilter.and(
932
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.PColumnData),
933
+ treeFilter.readyOrDuplicateOrError(),
934
+ ),
935
+ treeFilter.and(
936
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.StreamWorkdir),
937
+ treeFilter.readyOrDuplicateOrError(),
938
+ ),
939
+ );
940
+ }
941
+
766
942
  /** Returns true if sdk version of the block is old and we need to convert
767
943
  * ErrorLike errors to strings like it was.
768
944
  * We need it for keeping old blocks and new UI compatibility. */
@@ -1,7 +1,7 @@
1
1
  import type { PruningFunction } from "@milaboratories/pl-tree";
2
2
  import { SynchronizedTreeState } from "@milaboratories/pl-tree";
3
- import type { PlClient, SignedResourceId, ResourceType } from "@milaboratories/pl-client";
4
- import { resourceIdToString, resourceTypesEqual } from "@milaboratories/pl-client";
3
+ import type { Filter, PlClient, ResourceType, SignedResourceId } from "@milaboratories/pl-client";
4
+ import { resourceIdToString, resourceTypesEqual, treeFilter } from "@milaboratories/pl-client";
5
5
  import type { TreeAndComputableU } from "./types";
6
6
  import type { WatchableValue } from "@milaboratories/computable";
7
7
  import { Computable } from "@milaboratories/computable";
@@ -23,6 +23,8 @@ export const ProjectsListTreePruningFunction: PruningFunction = (resource) => {
23
23
  return resource.fields;
24
24
  };
25
25
 
26
+ export const projectsListFieldFilter: Filter = treeFilter.resourceTypeEq("Projects");
27
+
26
28
  export async function createProjectList(
27
29
  pl: PlClient,
28
30
  rid: SignedResourceId,
@@ -35,6 +37,7 @@ export async function createProjectList(
35
37
  {
36
38
  ...env.ops.defaultTreeOptions,
37
39
  pruning: ProjectsListTreePruningFunction,
40
+ fieldFilter: projectsListFieldFilter,
38
41
  },
39
42
  env.logger,
40
43
  );