@milaboratories/pl-middle-layer 1.36.4 → 1.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/index.js +19 -19
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +2031 -1918
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/js_render/computable_context.d.ts +68 -0
  6. package/dist/js_render/computable_context.d.ts.map +1 -0
  7. package/dist/js_render/context.d.ts +22 -64
  8. package/dist/js_render/context.d.ts.map +1 -1
  9. package/dist/js_render/index.d.ts +2 -0
  10. package/dist/js_render/index.d.ts.map +1 -1
  11. package/dist/middle_layer/block.d.ts.map +1 -1
  12. package/dist/middle_layer/block_ctx.d.ts +5 -0
  13. package/dist/middle_layer/block_ctx.d.ts.map +1 -1
  14. package/dist/middle_layer/middle_layer.d.ts +2 -0
  15. package/dist/middle_layer/middle_layer.d.ts.map +1 -1
  16. package/dist/middle_layer/project.d.ts.map +1 -1
  17. package/dist/middle_layer/project_overview.d.ts.map +1 -1
  18. package/dist/middle_layer/util.d.ts +2 -0
  19. package/dist/middle_layer/util.d.ts.map +1 -1
  20. package/dist/model/args.d.ts +4 -2
  21. package/dist/model/args.d.ts.map +1 -1
  22. package/dist/model/project_helper.d.ts +14 -0
  23. package/dist/model/project_helper.d.ts.map +1 -0
  24. package/dist/model/project_model_util.d.ts +14 -4
  25. package/dist/model/project_model_util.d.ts.map +1 -1
  26. package/dist/mutator/project.d.ts +16 -10
  27. package/dist/mutator/project.d.ts.map +1 -1
  28. package/package.json +14 -14
  29. package/src/js_render/computable_context.ts +753 -0
  30. package/src/js_render/context.ts +32 -720
  31. package/src/js_render/index.ts +37 -3
  32. package/src/middle_layer/block.ts +2 -0
  33. package/src/middle_layer/block_ctx.ts +6 -0
  34. package/src/middle_layer/middle_layer.ts +7 -2
  35. package/src/middle_layer/project.ts +15 -17
  36. package/src/middle_layer/project_overview.ts +13 -4
  37. package/src/middle_layer/util.ts +3 -1
  38. package/src/model/args.ts +12 -6
  39. package/src/model/project_helper.ts +41 -0
  40. package/src/model/project_model_util.test.ts +13 -4
  41. package/src/model/project_model_util.ts +37 -12
  42. package/src/mutator/project.test.ts +18 -12
  43. package/src/mutator/project.ts +159 -61
  44. package/src/mutator/template/template_render.test.ts +2 -2
@@ -12,18 +12,22 @@ import {
12
12
  BPSpecSumV042NotPrepared,
13
13
  TestBPPreparer
14
14
  } from '../test/block_packs';
15
+ import { getQuickJS } from 'quickjs-emscripten';
16
+ import { ProjectHelper } from '../model/project_helper';
15
17
 
16
18
  test('simple test #1', async () => {
19
+ const quickJs = await getQuickJS();
20
+
17
21
  await TestHelpers.withTempRoot(async (pl) => {
18
22
  const prj = await pl.withWriteTx('CreatingProject', async (tx) => {
19
23
  const prjRef = await createProject(tx);
20
24
  tx.createField(field(tx.clientRoot, 'prj'), 'Dynamic', prjRef);
21
25
  await tx.commit();
22
26
  return await toGlobalResourceId(prjRef);
23
- });
27
+ });
24
28
 
25
29
  await pl.withWriteTx('AddBlock1', async (tx) => {
26
- const mut = await ProjectMutator.load(tx, prj);
30
+ const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
27
31
  mut.addBlock(
28
32
  { id: 'block1', label: 'Block1', renderingMode: 'Heavy' },
29
33
  {
@@ -37,7 +41,7 @@ test('simple test #1', async () => {
37
41
  });
38
42
 
39
43
  await pl.withWriteTx('AddBlock2', async (tx) => {
40
- const mut = await ProjectMutator.load(tx, prj);
44
+ const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
41
45
  mut.addBlock(
42
46
  { id: 'block2', label: 'Block2', renderingMode: 'Heavy' },
43
47
  {
@@ -52,7 +56,7 @@ test('simple test #1', async () => {
52
56
  });
53
57
 
54
58
  await pl.withWriteTx('AddBlock3', async (tx) => {
55
- const mut = await ProjectMutator.load(tx, prj);
59
+ const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
56
60
  mut.addBlock(
57
61
  { id: 'block3', label: 'Block3', renderingMode: 'Heavy' },
58
62
  {
@@ -98,7 +102,7 @@ test('simple test #1', async () => {
98
102
  });
99
103
 
100
104
  await pl.withWriteTx('DeleteBlock2', async (tx) => {
101
- const mut = await ProjectMutator.load(tx, prj);
105
+ const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
102
106
  mut.deleteBlock('block2');
103
107
  mut.save();
104
108
  await tx.commit();
@@ -110,7 +114,7 @@ test('simple test #1', async () => {
110
114
  ).toBeUndefined();
111
115
  });
112
116
 
113
- await withProject(pl, prj, (mut) => {
117
+ await withProject(new ProjectHelper(quickJs), pl, prj, (mut) => {
114
118
  mut.setUiState('block3', undefined);
115
119
  });
116
120
 
@@ -120,7 +124,7 @@ test('simple test #1', async () => {
120
124
  ).toBeUndefined();
121
125
  });
122
126
 
123
- await withProject(pl, prj, (mut) => {
127
+ await withProject(new ProjectHelper(quickJs), pl, prj, (mut) => {
124
128
  mut.setUiState('block3', undefined);
125
129
  });
126
130
 
@@ -140,7 +144,7 @@ test('simple test #1', async () => {
140
144
  });
141
145
 
142
146
  await pl.withWriteTx('Refresh', async (tx) => {
143
- const mut = await ProjectMutator.load(tx, prj);
147
+ const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
144
148
  mut.doRefresh();
145
149
  mut.save();
146
150
  await tx.commit();
@@ -158,7 +162,7 @@ test('simple test #1', async () => {
158
162
  });
159
163
 
160
164
  await pl.withWriteTx('RenderProduction', async (tx) => {
161
- const mut = await ProjectMutator.load(tx, prj);
165
+ const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
162
166
  mut.renderProduction(['block1', 'block3']);
163
167
  mut.doRefresh();
164
168
  mut.save();
@@ -180,6 +184,8 @@ test('simple test #1', async () => {
180
184
  });
181
185
 
182
186
  test('simple test #2 with bp migration', async () => {
187
+ const quickJs = await getQuickJS();
188
+
183
189
  await TestHelpers.withTempRoot(async (pl) => {
184
190
  const prj = await pl.withWriteTx('CreatingProject', async (tx) => {
185
191
  const prjRef = await createProject(tx);
@@ -189,7 +195,7 @@ test('simple test #2 with bp migration', async () => {
189
195
  });
190
196
 
191
197
  await pl.withWriteTx('AddBlock1', async (tx) => {
192
- const mut = await ProjectMutator.load(tx, prj);
198
+ const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
193
199
  mut.addBlock(
194
200
  { id: 'block1', label: 'Block1', renderingMode: 'Heavy' },
195
201
  {
@@ -243,7 +249,7 @@ test('simple test #2 with bp migration', async () => {
243
249
  });
244
250
 
245
251
  await pl.withWriteTx('MigrateBlock2', async (tx) => {
246
- const mut = await ProjectMutator.load(tx, prj);
252
+ const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
247
253
  // TODO change to dev
248
254
  mut.migrateBlockPack('block2', await TestBPPreparer.prepare(BPSpecEnterV041NotPrepared));
249
255
  mut.save();
@@ -260,7 +266,7 @@ test('simple test #2 with bp migration', async () => {
260
266
  });
261
267
 
262
268
  await pl.withWriteTx('Refresh', async (tx) => {
263
- const mut = await ProjectMutator.load(tx, prj);
269
+ const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
264
270
  mut.doRefresh();
265
271
  mut.save();
266
272
  await tx.commit();
@@ -3,6 +3,7 @@ import type {
3
3
  AnyResourceRef,
4
4
  BasicResourceData,
5
5
  PlTransaction,
6
+ ResourceData,
6
7
  ResourceId } from '@milaboratories/pl-client';
7
8
  import {
8
9
  ensureResourceIdNotNull,
@@ -10,6 +11,7 @@ import {
10
11
  isNotNullResourceId,
11
12
  isNullResourceId,
12
13
  isResource,
14
+ isResourceId,
13
15
  isResourceRef,
14
16
  Pl,
15
17
  PlClient,
@@ -42,7 +44,8 @@ import {
42
44
  } from '../model/project_model';
43
45
  import { BlockPackTemplateField, createBlockPack } from './block-pack/block_pack';
44
46
  import type {
45
- BlockGraph } from '../model/project_model_util';
47
+ BlockGraph,
48
+ ProductionGraphBlockInfo } from '../model/project_model_util';
46
49
  import {
47
50
  allBlocks,
48
51
  graphDiff,
@@ -52,6 +55,7 @@ import {
52
55
  import type { BlockPackSpecPrepared } from '../model';
53
56
  import type {
54
57
  AuthorMarker,
58
+ BlockPackSpec,
55
59
  BlockSettings,
56
60
  ProjectMeta,
57
61
  } from '@milaboratories/pl-model-middle-layer';
@@ -61,7 +65,10 @@ import {
61
65
  import Denque from 'denque';
62
66
  import { exportContext, getPreparedExportTemplateEnvelope } from './context_export';
63
67
  import { loadTemplate } from './template/template_loading';
64
- import { deepFreeze } from '@milaboratories/ts-helpers';
68
+ import { cachedDeserialize, notEmpty } from '@milaboratories/ts-helpers';
69
+ import type { ProjectHelper } from '../model/project_helper';
70
+ import { extractConfig, type BlockConfig } from '@platforma-sdk/model';
71
+ import type { BlockPackInfo } from '../model/block_pack';
65
72
  type FieldStatus = 'NotReady' | 'Ready' | 'Error';
66
73
 
67
74
  interface BlockFieldState {
@@ -77,6 +84,8 @@ type BlockFieldStateValue = Omit<BlockFieldState, 'modCount'>;
77
84
  interface BlockInfoState {
78
85
  readonly id: string;
79
86
  readonly fields: BlockFieldStates;
87
+ blockConfig?: BlockConfig;
88
+ blockPack?: BlockPackSpec;
80
89
  }
81
90
 
82
91
  function cached<ModId, T>(modIdCb: () => ModId, valueCb: () => T): () => T {
@@ -103,6 +112,8 @@ class BlockInfo {
103
112
  constructor(
104
113
  public readonly id: string,
105
114
  public readonly fields: BlockFieldStates,
115
+ public readonly config: BlockConfig,
116
+ public readonly source: BlockPackSpec,
106
117
  ) {}
107
118
 
108
119
  public check() {
@@ -131,22 +142,22 @@ class BlockInfo {
131
142
  if (this.fields.currentArgs === undefined) throw new Error('no current args field');
132
143
  }
133
144
 
134
- private readonly currentInputsC = cached(
145
+ private readonly currentArgsC = cached(
135
146
  () => this.fields.currentArgs!.modCount,
136
- () => deepFreeze(JSON.parse(Buffer.from(this.fields.currentArgs!.value!).toString())) as unknown,
147
+ () => cachedDeserialize(this.fields.currentArgs!.value!),
137
148
  );
138
149
 
139
- private readonly actualProductionInputsC = cached(
150
+ private readonly prodArgsC = cached(
140
151
  () => this.fields.prodArgs?.modCount,
141
152
  () => {
142
153
  const bin = this.fields.prodArgs?.value;
143
154
  if (bin === undefined) return undefined;
144
- return deepFreeze(JSON.parse(Buffer.from(bin).toString())) as unknown;
155
+ return cachedDeserialize(bin);
145
156
  },
146
157
  );
147
158
 
148
- get currentInputs(): unknown {
149
- return this.currentInputsC();
159
+ get currentArgs(): unknown {
160
+ return this.currentArgsC();
150
161
  }
151
162
 
152
163
  get stagingRendered(): boolean {
@@ -176,8 +187,8 @@ class BlockInfo {
176
187
  return !this.productionRendered || this.productionStaleC() || this.productionHasErrors;
177
188
  }
178
189
 
179
- get actualProductionInputs(): unknown {
180
- return this.actualProductionInputsC();
190
+ get prodArgs(): unknown {
191
+ return this.prodArgsC();
181
192
  }
182
193
 
183
194
  public getTemplate(tx: PlTransaction): AnyRef {
@@ -247,6 +258,7 @@ export class ProjectMutator {
247
258
  private readonly blockInfos: Map<string, BlockInfo>,
248
259
  private readonly blockFrontendStates: Map<string, string>,
249
260
  private readonly ctxExportTplHolder: AnyResourceRef,
261
+ private readonly projectHelper: ProjectHelper,
250
262
  ) {}
251
263
 
252
264
  private fixProblemsAndMigrate() {
@@ -300,11 +312,41 @@ export class ProjectMutator {
300
312
  return this.stagingGraph;
301
313
  }
302
314
 
315
+ private getProductionGraphBlockInfo(blockId: string, prod: boolean): ProductionGraphBlockInfo | undefined {
316
+ const bInfo = this.getBlockInfo(blockId);
317
+
318
+ let argsField: BlockFieldState;
319
+ let args: unknown;
320
+
321
+ if (prod) {
322
+ if (bInfo.fields.prodArgs === undefined) return undefined;
323
+ argsField = bInfo.fields.prodArgs;
324
+ args = bInfo.prodArgs;
325
+ } else {
326
+ argsField = notEmpty(bInfo.fields.currentArgs);
327
+ args = bInfo.currentArgs;
328
+ }
329
+
330
+ const blockPackField = notEmpty(bInfo.fields.blockPack);
331
+
332
+ if (isResourceId(argsField.ref!) && isResourceId(blockPackField.ref!))
333
+ return {
334
+ args,
335
+ enrichmentTargets: this.projectHelper.getEnrichmentTargets(() => bInfo.config, () => args,
336
+ { argsRid: argsField.ref, blockPackRid: blockPackField.ref }),
337
+ };
338
+ else
339
+ return {
340
+ args,
341
+ enrichmentTargets: this.projectHelper.getEnrichmentTargets(() => bInfo.config, () => args),
342
+ };
343
+ }
344
+
303
345
  private getPendingProductionGraph(): BlockGraph {
304
346
  if (this.pendingProductionGraph === undefined)
305
347
  this.pendingProductionGraph = productionGraph(
306
348
  this.struct,
307
- (blockId) => this.getBlockInfo(blockId).currentInputs,
349
+ (blockId) => this.getProductionGraphBlockInfo(blockId, false),
308
350
  );
309
351
  return this.pendingProductionGraph;
310
352
  }
@@ -313,7 +355,7 @@ export class ProjectMutator {
313
355
  if (this.actualProductionGraph === undefined)
314
356
  this.actualProductionGraph = productionGraph(
315
357
  this.struct,
316
- (blockId) => this.getBlockInfo(blockId).actualProductionInputs,
358
+ (blockId) => this.getProductionGraphBlockInfo(blockId, true),
317
359
  );
318
360
  return this.actualProductionGraph;
319
361
  }
@@ -597,14 +639,7 @@ export class ProjectMutator {
597
639
 
598
640
  const newStagingGraph = stagingGraph(newStructure);
599
641
 
600
- // new actual production graph without new blocks
601
- const newActualProductionGraph = productionGraph(
602
- newStructure,
603
- (blockId) => this.blockInfos.get(blockId)?.actualProductionInputs,
604
- );
605
-
606
642
  const stagingDiff = graphDiff(currentStagingGraph, newStagingGraph);
607
- const prodDiff = graphDiff(currentActualProductionGraph, newActualProductionGraph);
608
643
 
609
644
  // removing blocks
610
645
  for (const blockId of stagingDiff.onlyInA) {
@@ -617,10 +652,12 @@ export class ProjectMutator {
617
652
 
618
653
  // creating new blocks
619
654
  for (const blockId of stagingDiff.onlyInB) {
620
- const info = new BlockInfo(blockId, {});
621
- this.blockInfos.set(blockId, info);
622
655
  const spec = newBlockSpecProvider(blockId);
623
656
 
657
+ // adding new block info
658
+ const info = new BlockInfo(blockId, {}, extractConfig(spec.blockPack.config), spec.blockPack.source);
659
+ this.blockInfos.set(blockId, info);
660
+
624
661
  // block pack
625
662
  const bp = createBlockPack(this.tx, spec.blockPack);
626
663
  this.setBlockField(blockId, 'blockPack', Pl.wrapInHolder(this.tx, bp), 'NotReady');
@@ -648,6 +685,14 @@ export class ProjectMutator {
648
685
  // resetting stagings affected by topology change
649
686
  for (const blockId of stagingDiff.different) this.resetStaging(blockId);
650
687
 
688
+ // new actual production graph without new blocks
689
+ const newActualProductionGraph = productionGraph(
690
+ newStructure,
691
+ (blockId) => this.getProductionGraphBlockInfo(blockId, true),
692
+ );
693
+
694
+ const prodDiff = graphDiff(currentActualProductionGraph, newActualProductionGraph);
695
+
651
696
  // applying changes due to topology change in production to affected nodes and
652
697
  // all their downstreams
653
698
  currentActualProductionGraph.traverse('downstream', [...prodDiff.different], (node) => {
@@ -965,10 +1010,15 @@ export class ProjectMutator {
965
1010
  }
966
1011
 
967
1012
  public static async load(
1013
+ projectHelper: ProjectHelper,
968
1014
  tx: PlTransaction,
969
1015
  rid: ResourceId,
970
1016
  author?: AuthorMarker,
971
1017
  ): Promise<ProjectMutator> {
1018
+ //
1019
+ // Sending initial requests to read project state (start of round-trip #1)
1020
+ //
1021
+
972
1022
  const fullResourceStateP = tx.getResourceData(rid, true);
973
1023
  const schemaP = tx.getKValueJson<string>(rid, SchemaVersionKey);
974
1024
  const lastModifiedP = tx.getKValueJson<number>(rid, ProjectLastModifiedTimestamp);
@@ -978,9 +1028,52 @@ export class ProjectMutator {
978
1028
 
979
1029
  const allKVP = tx.listKeyValuesString(rid);
980
1030
 
1031
+ const fullResourceState = await fullResourceStateP;
1032
+
1033
+ // loading field information
1034
+ const blockInfoStates = new Map<string, BlockInfoState>();
1035
+ for (const f of fullResourceState.fields) {
1036
+ const projectField = parseProjectField(f.name);
1037
+
1038
+ // processing only fields with known structure
1039
+ if (projectField === undefined) continue;
1040
+
1041
+ let info = blockInfoStates.get(projectField.blockId);
1042
+ if (info === undefined) {
1043
+ info = {
1044
+ id: projectField.blockId,
1045
+ fields: {},
1046
+ };
1047
+ blockInfoStates.set(projectField.blockId, info);
1048
+ }
1049
+
1050
+ info.fields[projectField.fieldName] = isNullResourceId(f.value)
1051
+ ? { modCount: 0 }
1052
+ : { modCount: 0, ref: f.value };
1053
+ }
1054
+
1055
+ //
1056
+ // Roundtrip #1 not yet finished, but as soon as field list is received,
1057
+ // we can start sending requests to read states of referenced resources
1058
+ // (start of round-trip #2)
1059
+ //
1060
+
1061
+ const blockFieldRequests: [BlockInfoState, ProjectField['fieldName'], BlockFieldState, Promise<BasicResourceData | ResourceData>][] = [];
1062
+ blockInfoStates.forEach((info) => {
1063
+ const fields = info.fields;
1064
+ for (const [fName, state] of Object.entries(fields)) {
1065
+ if (state.ref === undefined) continue;
1066
+ if (!isResource(state.ref) || isResourceRef(state.ref))
1067
+ throw new Error('unexpected behaviour');
1068
+ const fieldName = fName as ProjectField['fieldName'];
1069
+ blockFieldRequests.push([
1070
+ info, fieldName,
1071
+ state, tx.getResourceData(state.ref, fieldName == 'blockPack')]);
1072
+ }
1073
+ });
1074
+
981
1075
  // loading jsons
982
1076
  const [
983
- fullResourceState,
984
1077
  schema,
985
1078
  lastModified,
986
1079
  meta,
@@ -988,7 +1081,6 @@ export class ProjectMutator {
988
1081
  { stagingRefreshTimestamp, blocksInLimbo },
989
1082
  allKV,
990
1083
  ] = await Promise.all([
991
- fullResourceStateP,
992
1084
  schemaP,
993
1085
  lastModifiedP,
994
1086
  metaP,
@@ -1001,28 +1093,48 @@ export class ProjectMutator {
1001
1093
  `Can't act on this project resource because it has a wrong schema version: ${schema}`,
1002
1094
  );
1003
1095
 
1004
- // loading field information
1005
- const blockInfoStates = new Map<string, BlockInfoState>();
1006
- for (const f of fullResourceState.fields) {
1007
- const projectField = parseProjectField(f.name);
1096
+ //
1097
+ // <- at this point we have all the responses from round-trip #1
1098
+ //
1008
1099
 
1009
- // processing only fields with known structure
1010
- if (projectField === undefined) continue;
1100
+ //
1101
+ // Receiving responses from round-trip #2 and sending requests to read block pack descriptions
1102
+ // (start of round-trip #3)
1103
+ //
1011
1104
 
1012
- let info = blockInfoStates.get(projectField.blockId);
1013
- if (info === undefined) {
1014
- info = {
1015
- id: projectField.blockId,
1016
- fields: {},
1017
- };
1018
- blockInfoStates.set(projectField.blockId, info);
1105
+ const blockPackRequests: [BlockInfoState, Promise<BasicResourceData>][] = [];
1106
+ for (const [info, fieldName, state, response] of blockFieldRequests) {
1107
+ const result = await response;
1108
+ state.value = result.data;
1109
+ if (isNotNullResourceId(result.error)) state.status = 'Error';
1110
+ else if (result.resourceReady || isNotNullResourceId(result.originalResourceId))
1111
+ state.status = 'Ready';
1112
+ else state.status = 'NotReady';
1113
+
1114
+ // For block pack we need to traverse the ref field from the resource data
1115
+ if (fieldName === 'blockPack') {
1116
+ const refField = (result as ResourceData).fields.find((f) => f.name === Pl.HolderRefField);
1117
+ if (refField === undefined)
1118
+ throw new Error('Block pack ref field is missing');
1119
+ blockPackRequests.push([info, tx.getResourceData(ensureResourceIdNotNull(refField.value), false)]);
1019
1120
  }
1121
+ }
1020
1122
 
1021
- info.fields[projectField.fieldName] = isNullResourceId(f.value)
1022
- ? { modCount: 0 }
1023
- : { modCount: 0, ref: f.value };
1123
+ //
1124
+ // <- at this point we have all the responses from round-trip #2
1125
+ //
1126
+
1127
+ for (const [info, response] of blockPackRequests) {
1128
+ const result = await response;
1129
+ const bpInfo = cachedDeserialize(notEmpty(result.data)) as BlockPackInfo;
1130
+ info.blockConfig = extractConfig(bpInfo.config);
1131
+ info.blockPack = bpInfo.source;
1024
1132
  }
1025
1133
 
1134
+ //
1135
+ // <- at this point we have all the responses from round-trip #3
1136
+ //
1137
+
1026
1138
  // loading ctx export template to check if we already have cached materialized template in our project
1027
1139
  const ctxExportTplEnvelope = await getPreparedExportTemplateEnvelope();
1028
1140
 
@@ -1053,26 +1165,9 @@ export class ProjectMutator {
1053
1165
  blockFrontendStates.set(blockId, kv.value);
1054
1166
  }
1055
1167
 
1056
- const requests: [BlockFieldState, Promise<BasicResourceData>][] = [];
1057
- blockInfoStates.forEach(({ fields }) => {
1058
- for (const [, state] of Object.entries(fields)) {
1059
- if (state.ref === undefined) continue;
1060
- if (!isResource(state.ref) || isResourceRef(state.ref))
1061
- throw new Error('unexpected behaviour');
1062
- requests.push([state, tx.getResourceData(state.ref, false)]);
1063
- }
1064
- });
1065
- for (const [state, response] of requests) {
1066
- const result = await response;
1067
- state.value = result.data;
1068
- if (isNotNullResourceId(result.error)) state.status = 'Error';
1069
- else if (result.resourceReady || isNotNullResourceId(result.originalResourceId))
1070
- state.status = 'Ready';
1071
- else state.status = 'NotReady';
1072
- }
1073
-
1074
1168
  const blockInfos = new Map<string, BlockInfo>();
1075
- blockInfoStates.forEach(({ id, fields }) => blockInfos.set(id, new BlockInfo(id, fields)));
1169
+ blockInfoStates.forEach(({ id, fields, blockConfig, blockPack }) => blockInfos.set(id,
1170
+ new BlockInfo(id, fields, notEmpty(blockConfig), notEmpty(blockPack))));
1076
1171
 
1077
1172
  // check consistency of project state
1078
1173
  const blockInStruct = new Set<string>();
@@ -1101,6 +1196,7 @@ export class ProjectMutator {
1101
1196
  blockInfos,
1102
1197
  blockFrontendStates,
1103
1198
  ctxExportTplHolder,
1199
+ projectHelper,
1104
1200
  );
1105
1201
 
1106
1202
  prj.fixProblemsAndMigrate();
@@ -1140,14 +1236,16 @@ export async function createProject(
1140
1236
  }
1141
1237
 
1142
1238
  export async function withProject<T>(
1239
+ projectHelper: ProjectHelper,
1143
1240
  txOrPl: PlTransaction | PlClient,
1144
1241
  rid: ResourceId,
1145
1242
  cb: (p: ProjectMutator) => T | Promise<T>,
1146
1243
  ): Promise<T> {
1147
- return withProjectAuthored(txOrPl, rid, undefined, cb);
1244
+ return withProjectAuthored(projectHelper, txOrPl, rid, undefined, cb);
1148
1245
  }
1149
1246
 
1150
1247
  export async function withProjectAuthored<T>(
1248
+ projectHelper: ProjectHelper,
1151
1249
  txOrPl: PlTransaction | PlClient,
1152
1250
  rid: ResourceId,
1153
1251
  author: AuthorMarker | undefined,
@@ -1155,7 +1253,7 @@ export async function withProjectAuthored<T>(
1155
1253
  ): Promise<T> {
1156
1254
  if (txOrPl instanceof PlClient) {
1157
1255
  return await txOrPl.withWriteTx('ProjectAction', async (tx) => {
1158
- const mut = await ProjectMutator.load(tx, rid, author);
1256
+ const mut = await ProjectMutator.load(projectHelper, tx, rid, author);
1159
1257
  const result = await cb(mut);
1160
1258
  if (!mut.wasModified)
1161
1259
  // skipping save and commit altogether if no modifications were
@@ -1166,7 +1264,7 @@ export async function withProjectAuthored<T>(
1166
1264
  return result;
1167
1265
  });
1168
1266
  } else {
1169
- const mut = await ProjectMutator.load(txOrPl, rid, author);
1267
+ const mut = await ProjectMutator.load(projectHelper, txOrPl, rid, author);
1170
1268
  const result = await cb(mut);
1171
1269
  mut.save();
1172
1270
  return result;
@@ -15,7 +15,7 @@ import {
15
15
  } from '@milaboratories/pl-client';
16
16
  import { loadTemplate } from './template_loading';
17
17
  import { createBContextEnd, createRenderHeavyBlock, HeavyBlockOutputs } from './render_block';
18
- import { notEmpty, sleep } from '@milaboratories/ts-helpers';
18
+ import { cachedDeserialize, notEmpty, sleep } from '@milaboratories/ts-helpers';
19
19
  import { TemplateSpecPrepared } from '../../model/template_spec';
20
20
  import {
21
21
  TplSpecEnterExplicit,
@@ -269,4 +269,4 @@ function expectFields(res: ResourceData, fields: string[]) {
269
269
 
270
270
  const jsonToData = (data: unknown) => Buffer.from(JSON.stringify(data));
271
271
 
272
- const resDataToJson = (res: ResourceData) => JSON.parse(notEmpty(res.data).toString());
272
+ const resDataToJson = (res: ResourceData) => cachedDeserialize(notEmpty(res.data));