@milaboratories/pl-middle-layer 1.50.1 → 1.52.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.
Files changed (70) hide show
  1. package/dist/cfg_render/executor.cjs +2 -2
  2. package/dist/cfg_render/executor.js +1 -1
  3. package/dist/debug/index.cjs +4 -1
  4. package/dist/debug/index.cjs.map +1 -1
  5. package/dist/debug/index.js +4 -1
  6. package/dist/debug/index.js.map +1 -1
  7. package/dist/index.cjs +14 -0
  8. package/dist/index.d.ts +3 -2
  9. package/dist/index.js +2 -1
  10. package/dist/js_render/computable_context.cjs +36 -4
  11. package/dist/js_render/computable_context.cjs.map +1 -1
  12. package/dist/js_render/computable_context.js +36 -4
  13. package/dist/js_render/computable_context.js.map +1 -1
  14. package/dist/js_render/context.cjs +36 -3
  15. package/dist/js_render/context.cjs.map +1 -1
  16. package/dist/js_render/context.js +36 -3
  17. package/dist/js_render/context.js.map +1 -1
  18. package/dist/js_render/index.cjs +5 -1
  19. package/dist/js_render/index.cjs.map +1 -1
  20. package/dist/js_render/index.js +5 -1
  21. package/dist/js_render/index.js.map +1 -1
  22. package/dist/js_render/spec_driver.cjs +43 -0
  23. package/dist/js_render/spec_driver.cjs.map +1 -0
  24. package/dist/js_render/spec_driver.js +42 -0
  25. package/dist/js_render/spec_driver.js.map +1 -0
  26. package/dist/middle_layer/project.cjs +8 -5
  27. package/dist/middle_layer/project.cjs.map +1 -1
  28. package/dist/middle_layer/project.js +8 -5
  29. package/dist/middle_layer/project.js.map +1 -1
  30. package/dist/middle_layer/project_overview.cjs +28 -22
  31. package/dist/middle_layer/project_overview.cjs.map +1 -1
  32. package/dist/middle_layer/project_overview.js +28 -22
  33. package/dist/middle_layer/project_overview.js.map +1 -1
  34. package/dist/model/block_pack_spec.cjs.map +1 -1
  35. package/dist/model/block_pack_spec.d.ts +2 -2
  36. package/dist/model/block_pack_spec.js.map +1 -1
  37. package/dist/model/project_helper.cjs.map +1 -1
  38. package/dist/model/project_helper.d.ts +2 -1
  39. package/dist/model/project_helper.js.map +1 -1
  40. package/dist/model/template_spec.d.ts +7 -2
  41. package/dist/mutator/block-pack/block_pack.cjs +20 -1
  42. package/dist/mutator/block-pack/block_pack.cjs.map +1 -1
  43. package/dist/mutator/block-pack/block_pack.d.ts +4 -0
  44. package/dist/mutator/block-pack/block_pack.js +19 -1
  45. package/dist/mutator/block-pack/block_pack.js.map +1 -1
  46. package/dist/mutator/template/template_cache.cjs +515 -0
  47. package/dist/mutator/template/template_cache.cjs.map +1 -0
  48. package/dist/mutator/template/template_cache.d.ts +78 -0
  49. package/dist/mutator/template/template_cache.js +502 -0
  50. package/dist/mutator/template/template_cache.js.map +1 -0
  51. package/dist/mutator/template/template_loading.cjs +3 -1
  52. package/dist/mutator/template/template_loading.cjs.map +1 -1
  53. package/dist/mutator/template/template_loading.js +3 -1
  54. package/dist/mutator/template/template_loading.js.map +1 -1
  55. package/package.json +18 -17
  56. package/src/debug/index.ts +6 -0
  57. package/src/index.ts +1 -0
  58. package/src/js_render/computable_context.ts +69 -4
  59. package/src/js_render/context.ts +58 -5
  60. package/src/js_render/index.ts +8 -1
  61. package/src/js_render/spec_driver.ts +55 -0
  62. package/src/middle_layer/project.ts +12 -8
  63. package/src/middle_layer/project_overview.ts +6 -0
  64. package/src/model/block_pack_spec.ts +2 -2
  65. package/src/model/project_helper.ts +2 -7
  66. package/src/model/template_spec.ts +11 -1
  67. package/src/mutator/block-pack/block_pack.ts +35 -1
  68. package/src/mutator/template/template_cache.test.ts +373 -0
  69. package/src/mutator/template/template_cache.ts +763 -0
  70. package/src/mutator/template/template_loading.ts +3 -0
@@ -30,7 +30,8 @@ async function prepareTemplateSpec(tpl) {
30
30
  content: await node_fs.default.promises.readFile(tpl.path)
31
31
  };
32
32
  case "from-registry":
33
- case "explicit": return tpl;
33
+ case "explicit":
34
+ case "cached": return tpl;
34
35
  case "prepared": return tpl;
35
36
  default: return (0, _milaboratories_ts_helpers.assertNever)(tpl);
36
37
  }
@@ -49,6 +50,7 @@ function loadTemplate(tx, spec) {
49
50
  case "from-registry": return loadTemplateFromRegistry(tx, spec);
50
51
  case "explicit": return require_direct_template_loader.loadTemplateFromExplicitDirect(tx, spec);
51
52
  case "prepared": return require_direct_template_loader.loadTemplateFromPrepared(tx, spec);
53
+ case "cached": return spec.resourceId;
52
54
  default: return (0, _milaboratories_ts_helpers.assertNever)(spec);
53
55
  }
54
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"template_loading.cjs","names":["fs","Pl","loadTemplateFromExplicitDirect","loadTemplateFromPrepared"],"sources":["../../../src/mutator/template/template_loading.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport fs from \"node:fs\";\nimport type {\n TemplateFromRegistry,\n TemplateSpecAny,\n TemplateSpecPrepared,\n} from \"../../model/template_spec\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { loadTemplateFromExplicitDirect, loadTemplateFromPrepared } from \"./direct_template_loader\";\n\n//\n// Resource schema\n//\n\nexport const TengoTemplateGet: ResourceType = { name: \"TengoTemplateGet\", version: \"1\" };\nexport const TengoTemplateGetRegistry = \"registry\";\nexport const TengoTemplateGetTemplateURI = \"templateURI\";\nexport const TengoTemplateGetTemplate = \"template\";\n\nexport const TengoTemplatePack: ResourceType = { name: \"TengoTemplatePack\", version: \"1\" };\nexport const TengoTemplatePackConvert: ResourceType = {\n name: \"TengoTemplatePackConvert\",\n version: \"1\",\n};\nexport const TengoTemplatePackConvertTemplatePack = \"templatePack\";\nexport const TengoTemplatePackConvertTemplate = \"template\";\n\nexport async function prepareTemplateSpec(tpl: TemplateSpecAny): Promise<TemplateSpecPrepared> {\n switch (tpl.type) {\n case \"from-file\":\n return {\n type: \"explicit\",\n content: await fs.promises.readFile(tpl.path),\n };\n case \"from-registry\":\n case \"explicit\":\n return tpl;\n case \"prepared\":\n return tpl;\n default:\n return assertNever(tpl);\n }\n}\n\nfunction loadTemplateFromRegistry(tx: PlTransaction, spec: TemplateFromRegistry): AnyRef {\n const getTemplate = tx.createStruct(TengoTemplateGet);\n const registry = field(getTemplate, TengoTemplateGetRegistry);\n const uri = field(getTemplate, TengoTemplateGetTemplateURI);\n const templateFromRegistry = field(getTemplate, TengoTemplateGetTemplate);\n\n // Note: it has a resource schema, so platforma creates fields by itself.\n\n tx.setField(registry, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.registry))));\n tx.setField(uri, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.path))));\n\n return templateFromRegistry;\n}\n\nexport function loadTemplate(tx: PlTransaction, spec: TemplateSpecPrepared): AnyRef {\n switch (spec.type) {\n case \"from-registry\":\n return loadTemplateFromRegistry(tx, spec);\n case \"explicit\":\n return loadTemplateFromExplicitDirect(tx, spec);\n case \"prepared\":\n return loadTemplateFromPrepared(tx, spec);\n default:\n return assertNever(spec);\n }\n}\n"],"mappings":";;;;;;;;AAeA,MAAa,mBAAiC;CAAE,MAAM;CAAoB,SAAS;CAAK;AACxF,MAAa,2BAA2B;AACxC,MAAa,8BAA8B;AAC3C,MAAa,2BAA2B;AAExC,MAAa,oBAAkC;CAAE,MAAM;CAAqB,SAAS;CAAK;AAC1F,MAAa,2BAAyC;CACpD,MAAM;CACN,SAAS;CACV;AACD,MAAa,uCAAuC;AACpD,MAAa,mCAAmC;AAEhD,eAAsB,oBAAoB,KAAqD;AAC7F,SAAQ,IAAI,MAAZ;EACE,KAAK,YACH,QAAO;GACL,MAAM;GACN,SAAS,MAAMA,gBAAG,SAAS,SAAS,IAAI,KAAK;GAC9C;EACH,KAAK;EACL,KAAK,WACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,QACE,oDAAmB,IAAI;;;AAI7B,SAAS,yBAAyB,IAAmB,MAAoC;CACvF,MAAM,cAAc,GAAG,aAAa,iBAAiB;CACrD,MAAM,gDAAiB,aAAa,yBAAyB;CAC7D,MAAM,2CAAY,aAAa,4BAA4B;CAC3D,MAAM,4DAA6B,aAAa,yBAAyB;AAIzE,IAAG,SAAS,UAAU,GAAG,YAAYC,6BAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC;AAChG,IAAG,SAAS,KAAK,GAAG,YAAYA,6BAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC;AAEvF,QAAO;;AAGT,SAAgB,aAAa,IAAmB,MAAoC;AAClF,SAAQ,KAAK,MAAb;EACE,KAAK,gBACH,QAAO,yBAAyB,IAAI,KAAK;EAC3C,KAAK,WACH,QAAOC,8DAA+B,IAAI,KAAK;EACjD,KAAK,WACH,QAAOC,wDAAyB,IAAI,KAAK;EAC3C,QACE,oDAAmB,KAAK"}
1
+ {"version":3,"file":"template_loading.cjs","names":["fs","Pl","loadTemplateFromExplicitDirect","loadTemplateFromPrepared"],"sources":["../../../src/mutator/template/template_loading.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport fs from \"node:fs\";\nimport type {\n TemplateFromRegistry,\n TemplateSpecAny,\n TemplateSpecPrepared,\n} from \"../../model/template_spec\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { loadTemplateFromExplicitDirect, loadTemplateFromPrepared } from \"./direct_template_loader\";\n\n//\n// Resource schema\n//\n\nexport const TengoTemplateGet: ResourceType = { name: \"TengoTemplateGet\", version: \"1\" };\nexport const TengoTemplateGetRegistry = \"registry\";\nexport const TengoTemplateGetTemplateURI = \"templateURI\";\nexport const TengoTemplateGetTemplate = \"template\";\n\nexport const TengoTemplatePack: ResourceType = { name: \"TengoTemplatePack\", version: \"1\" };\nexport const TengoTemplatePackConvert: ResourceType = {\n name: \"TengoTemplatePackConvert\",\n version: \"1\",\n};\nexport const TengoTemplatePackConvertTemplatePack = \"templatePack\";\nexport const TengoTemplatePackConvertTemplate = \"template\";\n\nexport async function prepareTemplateSpec(tpl: TemplateSpecAny): Promise<TemplateSpecPrepared> {\n switch (tpl.type) {\n case \"from-file\":\n return {\n type: \"explicit\",\n content: await fs.promises.readFile(tpl.path),\n };\n case \"from-registry\":\n case \"explicit\":\n case \"cached\":\n return tpl;\n case \"prepared\":\n return tpl;\n default:\n return assertNever(tpl);\n }\n}\n\nfunction loadTemplateFromRegistry(tx: PlTransaction, spec: TemplateFromRegistry): AnyRef {\n const getTemplate = tx.createStruct(TengoTemplateGet);\n const registry = field(getTemplate, TengoTemplateGetRegistry);\n const uri = field(getTemplate, TengoTemplateGetTemplateURI);\n const templateFromRegistry = field(getTemplate, TengoTemplateGetTemplate);\n\n // Note: it has a resource schema, so platforma creates fields by itself.\n\n tx.setField(registry, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.registry))));\n tx.setField(uri, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.path))));\n\n return templateFromRegistry;\n}\n\nexport function loadTemplate(tx: PlTransaction, spec: TemplateSpecPrepared): AnyRef {\n switch (spec.type) {\n case \"from-registry\":\n return loadTemplateFromRegistry(tx, spec);\n case \"explicit\":\n return loadTemplateFromExplicitDirect(tx, spec);\n case \"prepared\":\n return loadTemplateFromPrepared(tx, spec);\n case \"cached\":\n return spec.resourceId;\n default:\n return assertNever(spec);\n }\n}\n"],"mappings":";;;;;;;;AAeA,MAAa,mBAAiC;CAAE,MAAM;CAAoB,SAAS;CAAK;AACxF,MAAa,2BAA2B;AACxC,MAAa,8BAA8B;AAC3C,MAAa,2BAA2B;AAExC,MAAa,oBAAkC;CAAE,MAAM;CAAqB,SAAS;CAAK;AAC1F,MAAa,2BAAyC;CACpD,MAAM;CACN,SAAS;CACV;AACD,MAAa,uCAAuC;AACpD,MAAa,mCAAmC;AAEhD,eAAsB,oBAAoB,KAAqD;AAC7F,SAAQ,IAAI,MAAZ;EACE,KAAK,YACH,QAAO;GACL,MAAM;GACN,SAAS,MAAMA,gBAAG,SAAS,SAAS,IAAI,KAAK;GAC9C;EACH,KAAK;EACL,KAAK;EACL,KAAK,SACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,QACE,oDAAmB,IAAI;;;AAI7B,SAAS,yBAAyB,IAAmB,MAAoC;CACvF,MAAM,cAAc,GAAG,aAAa,iBAAiB;CACrD,MAAM,gDAAiB,aAAa,yBAAyB;CAC7D,MAAM,2CAAY,aAAa,4BAA4B;CAC3D,MAAM,4DAA6B,aAAa,yBAAyB;AAIzE,IAAG,SAAS,UAAU,GAAG,YAAYC,6BAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC;AAChG,IAAG,SAAS,KAAK,GAAG,YAAYA,6BAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC;AAEvF,QAAO;;AAGT,SAAgB,aAAa,IAAmB,MAAoC;AAClF,SAAQ,KAAK,MAAb;EACE,KAAK,gBACH,QAAO,yBAAyB,IAAI,KAAK;EAC3C,KAAK,WACH,QAAOC,8DAA+B,IAAI,KAAK;EACjD,KAAK,WACH,QAAOC,wDAAyB,IAAI,KAAK;EAC3C,KAAK,SACH,QAAO,KAAK;EACd,QACE,oDAAmB,KAAK"}
@@ -28,7 +28,8 @@ async function prepareTemplateSpec(tpl) {
28
28
  content: await fs.promises.readFile(tpl.path)
29
29
  };
30
30
  case "from-registry":
31
- case "explicit": return tpl;
31
+ case "explicit":
32
+ case "cached": return tpl;
32
33
  case "prepared": return tpl;
33
34
  default: return assertNever(tpl);
34
35
  }
@@ -47,6 +48,7 @@ function loadTemplate(tx, spec) {
47
48
  case "from-registry": return loadTemplateFromRegistry(tx, spec);
48
49
  case "explicit": return loadTemplateFromExplicitDirect(tx, spec);
49
50
  case "prepared": return loadTemplateFromPrepared(tx, spec);
51
+ case "cached": return spec.resourceId;
50
52
  default: return assertNever(spec);
51
53
  }
52
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"template_loading.js","names":[],"sources":["../../../src/mutator/template/template_loading.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport fs from \"node:fs\";\nimport type {\n TemplateFromRegistry,\n TemplateSpecAny,\n TemplateSpecPrepared,\n} from \"../../model/template_spec\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { loadTemplateFromExplicitDirect, loadTemplateFromPrepared } from \"./direct_template_loader\";\n\n//\n// Resource schema\n//\n\nexport const TengoTemplateGet: ResourceType = { name: \"TengoTemplateGet\", version: \"1\" };\nexport const TengoTemplateGetRegistry = \"registry\";\nexport const TengoTemplateGetTemplateURI = \"templateURI\";\nexport const TengoTemplateGetTemplate = \"template\";\n\nexport const TengoTemplatePack: ResourceType = { name: \"TengoTemplatePack\", version: \"1\" };\nexport const TengoTemplatePackConvert: ResourceType = {\n name: \"TengoTemplatePackConvert\",\n version: \"1\",\n};\nexport const TengoTemplatePackConvertTemplatePack = \"templatePack\";\nexport const TengoTemplatePackConvertTemplate = \"template\";\n\nexport async function prepareTemplateSpec(tpl: TemplateSpecAny): Promise<TemplateSpecPrepared> {\n switch (tpl.type) {\n case \"from-file\":\n return {\n type: \"explicit\",\n content: await fs.promises.readFile(tpl.path),\n };\n case \"from-registry\":\n case \"explicit\":\n return tpl;\n case \"prepared\":\n return tpl;\n default:\n return assertNever(tpl);\n }\n}\n\nfunction loadTemplateFromRegistry(tx: PlTransaction, spec: TemplateFromRegistry): AnyRef {\n const getTemplate = tx.createStruct(TengoTemplateGet);\n const registry = field(getTemplate, TengoTemplateGetRegistry);\n const uri = field(getTemplate, TengoTemplateGetTemplateURI);\n const templateFromRegistry = field(getTemplate, TengoTemplateGetTemplate);\n\n // Note: it has a resource schema, so platforma creates fields by itself.\n\n tx.setField(registry, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.registry))));\n tx.setField(uri, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.path))));\n\n return templateFromRegistry;\n}\n\nexport function loadTemplate(tx: PlTransaction, spec: TemplateSpecPrepared): AnyRef {\n switch (spec.type) {\n case \"from-registry\":\n return loadTemplateFromRegistry(tx, spec);\n case \"explicit\":\n return loadTemplateFromExplicitDirect(tx, spec);\n case \"prepared\":\n return loadTemplateFromPrepared(tx, spec);\n default:\n return assertNever(spec);\n }\n}\n"],"mappings":";;;;;;AAeA,MAAa,mBAAiC;CAAE,MAAM;CAAoB,SAAS;CAAK;AACxF,MAAa,2BAA2B;AACxC,MAAa,8BAA8B;AAC3C,MAAa,2BAA2B;AAExC,MAAa,oBAAkC;CAAE,MAAM;CAAqB,SAAS;CAAK;AAC1F,MAAa,2BAAyC;CACpD,MAAM;CACN,SAAS;CACV;AACD,MAAa,uCAAuC;AACpD,MAAa,mCAAmC;AAEhD,eAAsB,oBAAoB,KAAqD;AAC7F,SAAQ,IAAI,MAAZ;EACE,KAAK,YACH,QAAO;GACL,MAAM;GACN,SAAS,MAAM,GAAG,SAAS,SAAS,IAAI,KAAK;GAC9C;EACH,KAAK;EACL,KAAK,WACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,QACE,QAAO,YAAY,IAAI;;;AAI7B,SAAS,yBAAyB,IAAmB,MAAoC;CACvF,MAAM,cAAc,GAAG,aAAa,iBAAiB;CACrD,MAAM,WAAW,MAAM,aAAa,yBAAyB;CAC7D,MAAM,MAAM,MAAM,aAAa,4BAA4B;CAC3D,MAAM,uBAAuB,MAAM,aAAa,yBAAyB;AAIzE,IAAG,SAAS,UAAU,GAAG,YAAY,GAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC;AAChG,IAAG,SAAS,KAAK,GAAG,YAAY,GAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC;AAEvF,QAAO;;AAGT,SAAgB,aAAa,IAAmB,MAAoC;AAClF,SAAQ,KAAK,MAAb;EACE,KAAK,gBACH,QAAO,yBAAyB,IAAI,KAAK;EAC3C,KAAK,WACH,QAAO,+BAA+B,IAAI,KAAK;EACjD,KAAK,WACH,QAAO,yBAAyB,IAAI,KAAK;EAC3C,QACE,QAAO,YAAY,KAAK"}
1
+ {"version":3,"file":"template_loading.js","names":[],"sources":["../../../src/mutator/template/template_loading.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport fs from \"node:fs\";\nimport type {\n TemplateFromRegistry,\n TemplateSpecAny,\n TemplateSpecPrepared,\n} from \"../../model/template_spec\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { loadTemplateFromExplicitDirect, loadTemplateFromPrepared } from \"./direct_template_loader\";\n\n//\n// Resource schema\n//\n\nexport const TengoTemplateGet: ResourceType = { name: \"TengoTemplateGet\", version: \"1\" };\nexport const TengoTemplateGetRegistry = \"registry\";\nexport const TengoTemplateGetTemplateURI = \"templateURI\";\nexport const TengoTemplateGetTemplate = \"template\";\n\nexport const TengoTemplatePack: ResourceType = { name: \"TengoTemplatePack\", version: \"1\" };\nexport const TengoTemplatePackConvert: ResourceType = {\n name: \"TengoTemplatePackConvert\",\n version: \"1\",\n};\nexport const TengoTemplatePackConvertTemplatePack = \"templatePack\";\nexport const TengoTemplatePackConvertTemplate = \"template\";\n\nexport async function prepareTemplateSpec(tpl: TemplateSpecAny): Promise<TemplateSpecPrepared> {\n switch (tpl.type) {\n case \"from-file\":\n return {\n type: \"explicit\",\n content: await fs.promises.readFile(tpl.path),\n };\n case \"from-registry\":\n case \"explicit\":\n case \"cached\":\n return tpl;\n case \"prepared\":\n return tpl;\n default:\n return assertNever(tpl);\n }\n}\n\nfunction loadTemplateFromRegistry(tx: PlTransaction, spec: TemplateFromRegistry): AnyRef {\n const getTemplate = tx.createStruct(TengoTemplateGet);\n const registry = field(getTemplate, TengoTemplateGetRegistry);\n const uri = field(getTemplate, TengoTemplateGetTemplateURI);\n const templateFromRegistry = field(getTemplate, TengoTemplateGetTemplate);\n\n // Note: it has a resource schema, so platforma creates fields by itself.\n\n tx.setField(registry, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.registry))));\n tx.setField(uri, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.path))));\n\n return templateFromRegistry;\n}\n\nexport function loadTemplate(tx: PlTransaction, spec: TemplateSpecPrepared): AnyRef {\n switch (spec.type) {\n case \"from-registry\":\n return loadTemplateFromRegistry(tx, spec);\n case \"explicit\":\n return loadTemplateFromExplicitDirect(tx, spec);\n case \"prepared\":\n return loadTemplateFromPrepared(tx, spec);\n case \"cached\":\n return spec.resourceId;\n default:\n return assertNever(spec);\n }\n}\n"],"mappings":";;;;;;AAeA,MAAa,mBAAiC;CAAE,MAAM;CAAoB,SAAS;CAAK;AACxF,MAAa,2BAA2B;AACxC,MAAa,8BAA8B;AAC3C,MAAa,2BAA2B;AAExC,MAAa,oBAAkC;CAAE,MAAM;CAAqB,SAAS;CAAK;AAC1F,MAAa,2BAAyC;CACpD,MAAM;CACN,SAAS;CACV;AACD,MAAa,uCAAuC;AACpD,MAAa,mCAAmC;AAEhD,eAAsB,oBAAoB,KAAqD;AAC7F,SAAQ,IAAI,MAAZ;EACE,KAAK,YACH,QAAO;GACL,MAAM;GACN,SAAS,MAAM,GAAG,SAAS,SAAS,IAAI,KAAK;GAC9C;EACH,KAAK;EACL,KAAK;EACL,KAAK,SACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,QACE,QAAO,YAAY,IAAI;;;AAI7B,SAAS,yBAAyB,IAAmB,MAAoC;CACvF,MAAM,cAAc,GAAG,aAAa,iBAAiB;CACrD,MAAM,WAAW,MAAM,aAAa,yBAAyB;CAC7D,MAAM,MAAM,MAAM,aAAa,4BAA4B;CAC3D,MAAM,uBAAuB,MAAM,aAAa,yBAAyB;AAIzE,IAAG,SAAS,UAAU,GAAG,YAAY,GAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC;AAChG,IAAG,SAAS,KAAK,GAAG,YAAY,GAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC;AAEvF,QAAO;;AAGT,SAAgB,aAAa,IAAmB,MAAoC;AAClF,SAAQ,KAAK,MAAb;EACE,KAAK,gBACH,QAAO,yBAAyB,IAAI,KAAK;EAC3C,KAAK,WACH,QAAO,+BAA+B,IAAI,KAAK;EACjD,KAAK,WACH,QAAO,yBAAyB,IAAI,KAAK;EAC3C,KAAK,SACH,QAAO,KAAK;EACd,QACE,QAAO,YAAY,KAAK"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-middle-layer",
3
- "version": "1.50.1",
3
+ "version": "1.52.0",
4
4
  "description": "Pl Middle Layer",
5
5
  "keywords": [],
6
6
  "license": "UNLICENSED",
@@ -20,6 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@milaboratories/pframes-rs-node": "1.1.14",
23
+ "@milaboratories/pframes-rs-wasm": "1.1.14",
23
24
  "canonicalize": "~2.1.0",
24
25
  "denque": "^2.1.0",
25
26
  "es-toolkit": "^1.39.10",
@@ -29,22 +30,22 @@
29
30
  "utility-types": "^3.11.0",
30
31
  "yaml": "^2.8.0",
31
32
  "zod": "~3.23.8",
32
- "@milaboratories/pf-driver": "1.0.68",
33
- "@milaboratories/pl-deployments": "2.15.24",
34
- "@milaboratories/pl-client": "2.17.12",
35
- "@milaboratories/computable": "2.8.6",
36
- "@milaboratories/pl-errors": "1.1.74",
33
+ "@milaboratories/pf-driver": "1.1.0",
34
+ "@milaboratories/pl-client": "2.18.0",
35
+ "@milaboratories/pl-deployments": "2.16.0",
36
+ "@milaboratories/computable": "2.9.0",
37
+ "@milaboratories/pl-errors": "1.2.0",
38
+ "@milaboratories/pl-drivers": "1.12.1",
37
39
  "@milaboratories/pl-http": "1.2.4",
38
- "@milaboratories/pl-model-middle-layer": "1.13.1",
39
- "@milaboratories/pl-drivers": "1.11.66",
40
- "@milaboratories/pl-model-common": "1.26.1",
41
- "@milaboratories/pl-model-backend": "1.1.59",
42
- "@milaboratories/resolve-helper": "1.1.3",
43
- "@milaboratories/pl-tree": "1.8.52",
40
+ "@milaboratories/pl-model-backend": "1.2.0",
41
+ "@milaboratories/pl-model-common": "1.27.0",
42
+ "@milaboratories/pl-tree": "1.9.1",
43
+ "@milaboratories/pl-model-middle-layer": "1.14.0",
44
44
  "@milaboratories/ts-helpers": "1.7.3",
45
- "@platforma-sdk/block-tools": "2.6.70",
46
- "@platforma-sdk/model": "1.59.3",
47
- "@platforma-sdk/workflow-tengo": "5.10.1"
45
+ "@milaboratories/resolve-helper": "1.1.3",
46
+ "@platforma-sdk/block-tools": "2.7.0",
47
+ "@platforma-sdk/model": "1.60.0",
48
+ "@platforma-sdk/workflow-tengo": "5.11.0"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@types/node": "~24.5.2",
@@ -54,8 +55,8 @@
54
55
  "typescript": "~5.9.3",
55
56
  "vitest": "^4.0.18",
56
57
  "@milaboratories/build-configs": "1.5.2",
57
- "@milaboratories/ts-builder": "1.3.0",
58
- "@milaboratories/ts-configs": "1.2.2"
58
+ "@milaboratories/ts-configs": "1.2.2",
59
+ "@milaboratories/ts-builder": "1.3.0"
59
60
  },
60
61
  "engines": {
61
62
  "node": ">=22.19.0"
@@ -1,9 +1,12 @@
1
1
  export type MlDebugFlags = {
2
2
  logTreeStats?: "cumulative" | "per-request";
3
3
  logProjectMutationStat: boolean;
4
+ logTemplateCacheStat: boolean;
4
5
  dumpInitialTreeState: boolean;
5
6
  logOutputStatus?: "any" | "unstable-only";
6
7
  logOutputRecalculations?: boolean;
8
+ logProjectOverviewStat: boolean;
9
+ logJsExecStat: boolean;
7
10
  };
8
11
 
9
12
  let flags: MlDebugFlags | undefined = undefined;
@@ -12,7 +15,10 @@ export function getDebugFlags() {
12
15
  flags = {
13
16
  dumpInitialTreeState: process.env.MI_DUMP_INITIAL_TREE_STATE !== undefined,
14
17
  logProjectMutationStat: process.env.MI_LOG_PROJECT_MUTATION_STAT !== undefined,
18
+ logTemplateCacheStat: process.env.MI_LOG_TEMPLATE_CACHE_STAT !== undefined,
15
19
  logOutputRecalculations: process.env.MI_LOG_OUTPUT_RECALCULATIONS !== undefined,
20
+ logProjectOverviewStat: process.env.MI_LOG_PROJECT_OVERVIEW_STAT !== undefined,
21
+ logJsExecStat: process.env.MI_LOG_JS_EXEC_STAT !== undefined,
16
22
  };
17
23
  if (process.env.MI_LOG_OUTPUT_STATUS)
18
24
  flags.logOutputStatus =
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ export type { InternalLsDriver } from "@milaboratories/pl-drivers";
19
19
 
20
20
  // for tests etc..
21
21
  export * from "./mutator/template/template_loading";
22
+ export * from "./mutator/template/template_cache";
22
23
  export * from "./mutator/template/render_template";
23
24
  export * from "./model/template_spec";
24
25
 
@@ -44,6 +44,13 @@ import type { ResultPool } from "../pool/result_pool";
44
44
  import type { JsExecutionContext } from "./context";
45
45
  import type { VmFunctionImplementation } from "quickjs-emscripten";
46
46
  import { Scope, type QuickJSHandle } from "quickjs-emscripten";
47
+ import type {
48
+ DiscoverColumnsRequest,
49
+ DiscoverColumnsResponse,
50
+ PColumnSpec,
51
+ SpecFrameHandle,
52
+ } from "@milaboratories/pl-model-common";
53
+ import { SpecDriver } from "./spec_driver";
47
54
 
48
55
  function bytesToBase64(data: Uint8Array | undefined): string | undefined {
49
56
  return data !== undefined ? Buffer.from(data).toString("base64") : undefined;
@@ -57,8 +64,17 @@ export class ComputableContextHelper implements JsRenderInternal.GlobalCfgRender
57
64
 
58
65
  private computableCtx: ComputableCtx | undefined;
59
66
  private readonly accessors = new Map<string, PlTreeNodeAccessor | undefined>();
67
+ private readonly specDriver = new SpecDriver();
60
68
 
61
- private readonly meta: Map<string, Block>;
69
+ private _meta: Map<string, Block> | undefined;
70
+ private get meta(): Map<string, Block> {
71
+ if (this._meta === undefined) {
72
+ if (this.computableCtx === undefined)
73
+ throw new Error("blockMeta can't be resolved in this context");
74
+ this._meta = this.blockCtx.blockMeta(this.computableCtx);
75
+ }
76
+ return this._meta;
77
+ }
62
78
 
63
79
  constructor(
64
80
  private readonly parent: JsExecutionContext,
@@ -68,7 +84,6 @@ export class ComputableContextHelper implements JsRenderInternal.GlobalCfgRender
68
84
  computableCtx: ComputableCtx,
69
85
  ) {
70
86
  this.computableCtx = computableCtx;
71
- this.meta = blockCtx.blockMeta(computableCtx);
72
87
  }
73
88
 
74
89
  public resetComputableCtx() {
@@ -426,6 +441,27 @@ export class ComputableContextHelper implements JsRenderInternal.GlobalCfgRender
426
441
  return key;
427
442
  }
428
443
 
444
+ //
445
+ // Spec Frames
446
+ //
447
+
448
+ public createSpecFrame(specs: Record<string, PColumnSpec>): SpecFrameHandle {
449
+ const handle = this.specDriver.createSpecFrame(specs);
450
+ this.computableCtx?.addOnDestroy(() => this.specDriver.disposeSpecFrame(handle));
451
+ return handle;
452
+ }
453
+
454
+ public specFrameDiscoverColumns(
455
+ handle: SpecFrameHandle,
456
+ request: DiscoverColumnsRequest,
457
+ ): DiscoverColumnsResponse {
458
+ return this.specDriver.specFrameDiscoverColumns(handle as SpecFrameHandle, request);
459
+ }
460
+
461
+ public specFrameDispose(handle: SpecFrameHandle): void {
462
+ this.specDriver.disposeSpecFrame(handle as SpecFrameHandle);
463
+ }
464
+
429
465
  /**
430
466
  * Transforms input data for PFrame/PTable creation
431
467
  * - Converts string handles to accessors
@@ -504,11 +540,15 @@ export class ComputableContextHelper implements JsRenderInternal.GlobalCfgRender
504
540
  // QuickJS strips all fields from errors apart from 'name' and 'message'.
505
541
  // That's why here we need to store them, and rethrow them when we exit
506
542
  // from QuickJS code.
543
+ const t0 = performance.now();
507
544
  try {
508
545
  return (fn as any)(...args);
509
546
  } catch (e: unknown) {
510
547
  const newErr = parent.errorRepo.setAndRecreateForQuickJS(e);
511
548
  throw vm.newError(newErr);
549
+ } finally {
550
+ parent.stats.ctxMethodCalls++;
551
+ parent.stats.ctxMethodMs += performance.now() - t0;
512
552
  }
513
553
  };
514
554
 
@@ -648,11 +688,11 @@ export class ComputableContextHelper implements JsRenderInternal.GlobalCfgRender
648
688
  });
649
689
 
650
690
  exportCtxFunction("listOutputFields", (handle) => {
651
- return parent.exportObjectViaJson(this.listInputFields(vm.getString(handle)), undefined);
691
+ return parent.exportObjectViaJson(this.listOutputFields(vm.getString(handle)), undefined);
652
692
  });
653
693
 
654
694
  exportCtxFunction("listDynamicFields", (handle) => {
655
- return parent.exportObjectViaJson(this.listInputFields(vm.getString(handle)), undefined);
695
+ return parent.exportObjectViaJson(this.listDynamicFields(vm.getString(handle)), undefined);
656
696
  });
657
697
 
658
698
  exportCtxFunction("getKeyValueBase64", (handle, key) => {
@@ -858,6 +898,31 @@ export class ComputableContextHelper implements JsRenderInternal.GlobalCfgRender
858
898
  );
859
899
  });
860
900
 
901
+ //
902
+ // Spec Frames
903
+ //
904
+
905
+ exportCtxFunction("createSpecFrame", (specs) => {
906
+ return parent.exportSingleValue(
907
+ this.createSpecFrame(parent.importObjectViaJson(specs) as Record<string, PColumnSpec>),
908
+ undefined,
909
+ );
910
+ });
911
+
912
+ exportCtxFunction("specFrameDiscoverColumns", (handle, request) => {
913
+ return parent.exportObjectViaJson(
914
+ this.specFrameDiscoverColumns(
915
+ vm.getString(handle) as SpecFrameHandle,
916
+ parent.importObjectViaJson(request) as DiscoverColumnsRequest,
917
+ ),
918
+ undefined,
919
+ );
920
+ });
921
+
922
+ exportCtxFunction("specFrameDispose", (handle) => {
923
+ this.specFrameDispose(vm.getString(handle) as SpecFrameHandle);
924
+ });
925
+
861
926
  //
862
927
  // Computable
863
928
  //
@@ -39,6 +39,26 @@ export type ComputableEnv = {
39
39
  computableCtx: ComputableCtx;
40
40
  };
41
41
 
42
+ /** Execution stats accumulated during the lifetime of a JsExecutionContext. */
43
+ export type JsExecStats = {
44
+ bundleEvalMs: number;
45
+ bundleBytes: number;
46
+
47
+ callbackMs: number;
48
+ callbackCount: number;
49
+
50
+ serInMs: number;
51
+ serInBytes: number;
52
+ serInCalls: number;
53
+
54
+ serOutMs: number;
55
+ serOutBytes: number;
56
+ serOutCalls: number;
57
+
58
+ ctxMethodCalls: number;
59
+ ctxMethodMs: number;
60
+ };
61
+
42
62
  export class JsExecutionContext {
43
63
  private readonly callbackRegistry: QuickJSHandle;
44
64
  private readonly fnJSONStringify: QuickJSHandle;
@@ -48,6 +68,21 @@ export class JsExecutionContext {
48
68
 
49
69
  public readonly computableHelper: ComputableContextHelper | undefined;
50
70
 
71
+ public readonly stats: JsExecStats = {
72
+ bundleEvalMs: 0,
73
+ bundleBytes: 0,
74
+ callbackMs: 0,
75
+ callbackCount: 0,
76
+ serInMs: 0,
77
+ serInBytes: 0,
78
+ serInCalls: 0,
79
+ serOutMs: 0,
80
+ serOutBytes: 0,
81
+ serOutCalls: 0,
82
+ ctxMethodCalls: 0,
83
+ ctxMethodMs: 0,
84
+ };
85
+
51
86
  /**
52
87
  * Creates a new JS execution context.
53
88
  *
@@ -111,6 +146,7 @@ export class JsExecutionContext {
111
146
  // }
112
147
 
113
148
  public evaluateBundle(code: string) {
149
+ const t0 = performance.now();
114
150
  try {
115
151
  this.deadlineSetter({
116
152
  currentExecutionTarget: "evaluateBundle",
@@ -122,10 +158,13 @@ export class JsExecutionContext {
122
158
  throw err;
123
159
  } finally {
124
160
  this.deadlineSetter(undefined);
161
+ this.stats.bundleEvalMs += performance.now() - t0;
162
+ this.stats.bundleBytes += code.length;
125
163
  }
126
164
  }
127
165
 
128
166
  public runCallback(cbName: string, ...args: unknown[]): QuickJSHandle {
167
+ const t0 = performance.now();
129
168
  try {
130
169
  this.deadlineSetter({ currentExecutionTarget: cbName, deadline: Date.now() + 10000 });
131
170
  return Scope.withScope((localScope) => {
@@ -150,6 +189,8 @@ export class JsExecutionContext {
150
189
  throw original;
151
190
  } finally {
152
191
  this.deadlineSetter(undefined);
192
+ this.stats.callbackMs += performance.now() - t0;
193
+ this.stats.callbackCount++;
153
194
  }
154
195
  }
155
196
 
@@ -210,11 +251,16 @@ export class JsExecutionContext {
210
251
  }
211
252
 
212
253
  public exportObjectViaJson(obj: unknown, scope: Scope | undefined): QuickJSHandle {
254
+ const t0 = performance.now();
255
+ const json = JSON.stringify(obj);
256
+ this.stats.serInBytes += json.length;
257
+ this.stats.serInCalls++;
213
258
  const result = this.vm
214
- .newString(JSON.stringify(obj))
215
- .consume((json) =>
216
- this.vm.unwrapResult(this.vm.callFunction(this.fnJSONParse, this.vm.undefined, json)),
259
+ .newString(json)
260
+ .consume((jsonHandle) =>
261
+ this.vm.unwrapResult(this.vm.callFunction(this.fnJSONParse, this.vm.undefined, jsonHandle)),
217
262
  );
263
+ this.stats.serInMs += performance.now() - t0;
218
264
  return scope !== undefined ? scope.manage(result) : result;
219
265
  }
220
266
 
@@ -233,13 +279,20 @@ export class JsExecutionContext {
233
279
  }
234
280
 
235
281
  public importObjectViaJson(handle: QuickJSHandle): unknown {
282
+ const t0 = performance.now();
236
283
  const text = this.vm
237
284
  .unwrapResult(this.vm.callFunction(this.fnJSONStringify, this.vm.undefined, handle))
238
285
  .consume((strHandle) => this.vm.getString(strHandle));
239
- if (text === "undefined")
286
+ this.stats.serOutBytes += text.length;
287
+ this.stats.serOutCalls++;
288
+ if (text === "undefined") {
240
289
  // special case with futures
290
+ this.stats.serOutMs += performance.now() - t0;
241
291
  return undefined;
242
- return JSON.parse(text);
292
+ }
293
+ const result = JSON.parse(text);
294
+ this.stats.serOutMs += performance.now() - t0;
295
+ return result;
243
296
  }
244
297
 
245
298
  private injectCtx() {
@@ -135,6 +135,8 @@ export function computableFromRF(
135
135
 
136
136
  if (Object.keys(toBeResolved).length === 0) {
137
137
  const importedResult = rCtx.importObjectUniversal(result);
138
+ if (getDebugFlags().logJsExecStat)
139
+ console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);
138
140
  logOutputStatus(
139
141
  fh.handle,
140
142
  importedResult,
@@ -161,6 +163,8 @@ export function computableFromRF(
161
163
 
162
164
  // logging
163
165
  recalculationCounter++;
166
+ if (getDebugFlags().logJsExecStat)
167
+ console.log(`[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`);
164
168
  logOutputStatus(fh.handle, renderedResult, stable, recalculationCounter, unstableMarker);
165
169
 
166
170
  return renderedResult;
@@ -208,7 +212,10 @@ export function executeSingleLambda(
208
212
  rCtx.evaluateBundle(code.content);
209
213
 
210
214
  // Running the lambda with arguments (e.g., state for args(), args for enrichmentTargets())
211
- return rCtx.importObjectUniversal(rCtx.runCallback(fh.handle, ...args));
215
+ const importedResult = rCtx.importObjectUniversal(rCtx.runCallback(fh.handle, ...args));
216
+ if (getDebugFlags().logJsExecStat)
217
+ console.log(`[jsExec] ${fh.handle}: ${JSON.stringify(rCtx.stats)}`);
218
+ return importedResult;
212
219
  } finally {
213
220
  scope.dispose();
214
221
  }
@@ -0,0 +1,55 @@
1
+ import { createPFrame } from "@milaboratories/pframes-rs-wasm";
2
+ import type { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
3
+ import type {
4
+ PColumnSpec,
5
+ PSpecDriver,
6
+ SpecFrameHandle,
7
+ DiscoverColumnsRequest,
8
+ DiscoverColumnsResponse,
9
+ } from "@milaboratories/pl-model-common";
10
+ import { randomUUID } from "node:crypto";
11
+
12
+ /**
13
+ * Manages spec-only PFrame instances (WASM) with handle-based lifecycle.
14
+ *
15
+ * All operations are synchronous — WASM computes results immediately.
16
+ */
17
+ export class SpecDriver implements PSpecDriver {
18
+ private readonly frames = new Map<string, PFrameInternal.PFrameWasm>();
19
+
20
+ createSpecFrame(specs: Record<string, PColumnSpec>): SpecFrameHandle {
21
+ const frame = createPFrame(specs);
22
+ const handle = randomUUID() as SpecFrameHandle;
23
+ this.frames.set(handle, frame);
24
+ return handle;
25
+ }
26
+
27
+ specFrameDiscoverColumns(
28
+ handle: SpecFrameHandle,
29
+ request: DiscoverColumnsRequest,
30
+ ): DiscoverColumnsResponse {
31
+ return this.getFrame(handle).discoverColumns(request);
32
+ }
33
+
34
+ disposeSpecFrame(handle: SpecFrameHandle): void {
35
+ const frame = this.frames.get(handle);
36
+ if (frame) {
37
+ frame[Symbol.dispose]();
38
+ this.frames.delete(handle);
39
+ }
40
+ }
41
+
42
+ /** Dispose all managed spec frames. */
43
+ disposeAll(): void {
44
+ for (const frame of this.frames.values()) {
45
+ frame[Symbol.dispose]();
46
+ }
47
+ this.frames.clear();
48
+ }
49
+
50
+ private getFrame(handle: string): PFrameInternal.PFrameWasm {
51
+ const frame = this.frames.get(handle);
52
+ if (frame === undefined) throw new Error(`No such spec frame: ${handle}`);
53
+ return frame;
54
+ }
55
+ }
@@ -47,6 +47,7 @@ import canonicalize from "canonicalize";
47
47
  import type { ProjectOverviewLight } from "./project_overview_light";
48
48
  import { projectOverviewLight } from "./project_overview_light";
49
49
  import { applyProjectMigrations } from "../mutator/migration";
50
+ import { cacheBlockPackTemplate } from "../mutator/template/template_cache";
50
51
 
51
52
  type BlockStateComputables = {
52
53
  readonly fullState: Computable<BlockStateInternalV3>;
@@ -210,18 +211,20 @@ export class Project {
210
211
  blockId: string = randomUUID(),
211
212
  ): Promise<string> {
212
213
  const preparedBp = await this.env.bpPreparer.prepare(blockPackSpec);
213
- const blockCfgContainer = await this.env.bpPreparer.getBlockConfigContainer(blockPackSpec);
214
- const blockCfg = extractConfig(blockCfgContainer); // full content of this var should never be persisted
214
+ const blockCfg = extractConfig(preparedBp.config);
215
215
 
216
216
  this.env.runtimeCapabilities.throwIfIncompatible(blockCfg.featureFlags);
217
217
 
218
+ // Pre-materialize template via cache (separate transaction(s))
219
+ const cachedBp = await cacheBlockPackTemplate(this.env.pl, preparedBp);
220
+
218
221
  // Build NewBlockSpec based on model API version
219
222
  const newBlockSpec =
220
223
  blockCfg.modelAPIVersion === BLOCK_STORAGE_FACADE_VERSION
221
- ? { storageMode: "fromModel" as const, blockPack: preparedBp }
224
+ ? { storageMode: "fromModel" as const, blockPack: cachedBp }
222
225
  : {
223
226
  storageMode: "legacy" as const,
224
- blockPack: preparedBp,
227
+ blockPack: cachedBp,
225
228
  legacyState: canonicalize({
226
229
  args: blockCfg.initialArgs,
227
230
  uiState: blockCfg.initialUiState,
@@ -301,12 +304,13 @@ export class Project {
301
304
  author?: AuthorMarker,
302
305
  ): Promise<void> {
303
306
  const preparedBp = await this.env.bpPreparer.prepare(blockPackSpec);
304
- const blockCfg = extractConfig(
305
- await this.env.bpPreparer.getBlockConfigContainer(blockPackSpec),
306
- );
307
+ const blockCfg = extractConfig(preparedBp.config);
307
308
 
308
309
  this.env.runtimeCapabilities.throwIfIncompatible(blockCfg.featureFlags);
309
310
 
311
+ // Pre-materialize template via cache (separate transaction(s))
312
+ const cachedBp = await cacheBlockPackTemplate(this.env.pl, preparedBp);
313
+
310
314
  // resetState signals to mutator to reset storage
311
315
  // For v2+ blocks: mutator gets initial storage directly via getInitialStorageInVM
312
316
  // For v1 blocks: we pass the legacy state format
@@ -323,7 +327,7 @@ export class Project {
323
327
  this.env.pl,
324
328
  this.rid,
325
329
  author,
326
- (mut) => mut.migrateBlockPack(blockId, preparedBp, resetState),
330
+ (mut) => mut.migrateBlockPack(blockId, cachedBp, resetState),
327
331
  { name: "updateBlockPack", lockId: this.projectLockId },
328
332
  );
329
333
  await this.projectTree.refreshState();
@@ -30,6 +30,7 @@ import type { NavigationStates } from "./navigation_states";
30
30
  import { getBlockPackInfo } from "./util";
31
31
  import { resourceIdToString, type ResourceId } from "@milaboratories/pl-client";
32
32
  import { omitBy, isEqual } from "es-toolkit";
33
+ import { getDebugFlags } from "../debug";
33
34
 
34
35
  type BlockInfo = {
35
36
  argsRid?: ResourceId;
@@ -346,6 +347,11 @@ export function projectOverview(
346
347
  }),
347
348
  };
348
349
  },
350
+ onRecalculation: getDebugFlags().logProjectOverviewStat
351
+ ? (stats) => {
352
+ console.log(`[projectOverview] ${JSON.stringify(stats)}`);
353
+ }
354
+ : undefined,
349
355
  },
350
356
  ).withStableType();
351
357
  }
@@ -1,4 +1,4 @@
1
- import type { ExplicitTemplate, PreparedTemplate } from "./template_spec";
1
+ import type { CachedTemplate, ExplicitTemplate, PreparedTemplate } from "./template_spec";
2
2
  import type { ResourceType } from "@milaboratories/pl-client";
3
3
  import type { BlockConfigContainer } from "@platforma-sdk/model";
4
4
  import type { BlockPackSpec } from "@milaboratories/pl-model-middle-layer";
@@ -48,7 +48,7 @@ export interface BlockPackExplicit {
48
48
  /** Block-pack spec that can be materialized in pl. */
49
49
  export type BlockPackSpecPrepared = {
50
50
  type: "prepared";
51
- template: PreparedTemplate;
51
+ template: PreparedTemplate | CachedTemplate;
52
52
  config: BlockConfigContainer;
53
53
  frontend: FrontendSpec;
54
54
  source: BlockPackSpec;
@@ -1,10 +1,4 @@
1
- import type {
2
- ResultOrError,
3
- BlockConfig,
4
- BlockStorage,
5
- PlRef,
6
- StorageDebugView,
7
- } from "@platforma-sdk/model";
1
+ import type { ResultOrError, BlockConfig, BlockStorage, PlRef } from "@platforma-sdk/model";
8
2
  import type { StringifiedJson } from "@milaboratories/pl-model-common";
9
3
  import {
10
4
  extractCodeWithInfo,
@@ -17,6 +11,7 @@ import type { QuickJSWASMModule } from "quickjs-emscripten";
17
11
  import { executeSingleLambda } from "../js_render";
18
12
  import type { ResourceId } from "@milaboratories/pl-client";
19
13
  import { ConsoleLoggerAdapter, type MiLogger } from "@milaboratories/ts-helpers";
14
+ import type { StorageDebugView } from "@milaboratories/pl-model-middle-layer";
20
15
 
21
16
  type EnrichmentTargetsRequest = {
22
17
  blockConfig: () => BlockConfig;
@@ -1,3 +1,4 @@
1
+ import type { ResourceId } from "@milaboratories/pl-client";
1
2
  import type { CompiledTemplateV3, TemplateData } from "@milaboratories/pl-model-backend";
2
3
 
3
4
  export interface TemplateFromRegistry {
@@ -16,10 +17,19 @@ export interface PreparedTemplate {
16
17
  data: TemplateData | CompiledTemplateV3;
17
18
  }
18
19
 
20
+ export interface CachedTemplate {
21
+ readonly type: "cached";
22
+ readonly resourceId: ResourceId;
23
+ }
24
+
19
25
  export interface TemplateFromFile {
20
26
  readonly type: "from-file";
21
27
  path: string;
22
28
  }
23
29
 
24
- export type TemplateSpecPrepared = TemplateFromRegistry | ExplicitTemplate | PreparedTemplate;
30
+ export type TemplateSpecPrepared =
31
+ | TemplateFromRegistry
32
+ | ExplicitTemplate
33
+ | PreparedTemplate
34
+ | CachedTemplate;
25
35
  export type TemplateSpecAny = TemplateSpecPrepared | TemplateFromFile;