@openforis/arena-core 2.16.8 → 2.16.10

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,7 +2,7 @@ import { Nodes } from '../../node';
2
2
  import { NodeDefs } from '../../nodeDef';
3
3
  import { Surveys } from '../../survey';
4
4
  import { Numbers } from '../../utils';
5
- import { getChildren, getRoot } from './recordGetters';
5
+ import { getChildren, getCycle, getRoot } from './recordGetters';
6
6
  const emptyStats = () => ({ total: 0, filled: 0 });
7
7
  const addStats = (statsAcc, statsToAdd) => {
8
8
  statsAcc.total += statsToAdd.total;
@@ -49,7 +49,7 @@ const getChildEntityCompletionStats = (params) => {
49
49
  export const getEntityCompletionStats = (params) => {
50
50
  const { survey, record, entity, includeNestedEntities = true } = params;
51
51
  const entityDef = Surveys.getNodeDefByUuid({ survey, uuid: entity.nodeDefUuid });
52
- const childDefs = Surveys.getNodeDefChildren({ survey, nodeDef: entityDef });
52
+ const childDefs = Surveys.getNodeDefChildren({ survey, nodeDef: entityDef }).filter(NodeDefs.isInCycle(getCycle(record)));
53
53
  const stats = emptyStats();
54
54
  for (const childDef of childDefs) {
55
55
  if (!Nodes.isChildApplicable(entity, childDef.uuid))
@@ -1 +1 @@
1
- {"version":3,"file":"recordCompletion.js","sourceRoot":"","sources":["../../../../src/record/_records/recordCompletion.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, Nodes } from '../../node'\nimport { NodeDef, NodeDefProps, NodeDefs, NodeDefType } from '../../nodeDef'\nimport { Survey, Surveys } from '../../survey'\nimport { Numbers } from '../../utils'\nimport { ArenaRecord } from '../record'\nimport { getChildren, getRoot } from './recordGetters'\n\nexport interface EntityCompletionStats {\n total: number\n filled: number\n}\n\nconst emptyStats = (): EntityCompletionStats => ({ total: 0, filled: 0 })\n\nconst addStats = (statsAcc: EntityCompletionStats, statsToAdd: EntityCompletionStats): void => {\n statsAcc.total += statsToAdd.total\n statsAcc.filled += statsToAdd.filled\n}\n\nconst isAttributeRequired = (entity: ArenaRecordNode, nodeDef: NodeDef<NodeDefType, NodeDefProps>): boolean =>\n NodeDefs.isKey(nodeDef) ||\n NodeDefs.isRequired(nodeDef) ||\n (NodeDefs.isRequiredIfDefined(nodeDef) && Nodes.isChildRequired(entity, nodeDef.uuid))\n\nconst getAttributeCompletionStats = (params: {\n record: ArenaRecord\n entity: ArenaRecordNode\n nodeDef: NodeDef<NodeDefType, NodeDefProps>\n}): EntityCompletionStats => {\n const { record, entity, nodeDef } = params\n\n if (NodeDefs.isMultiple(nodeDef)) {\n const minCount = Nodes.getChildrenMinCount({ parentNode: entity, nodeDef })\n if (Number.isNaN(minCount) || minCount <= 0) return emptyStats()\n\n const filledNodesCount = getChildren(entity, nodeDef.uuid)(record).filter(Nodes.isValueNotBlank).length\n\n return { total: minCount, filled: Math.min(filledNodesCount, minCount) }\n }\n\n if (!isAttributeRequired(entity, nodeDef)) return emptyStats()\n\n const node = getChildren(entity, nodeDef.uuid)(record)[0]\n return { total: 1, filled: node && Nodes.isValueNotBlank(node) ? 1 : 0 }\n}\n\nconst getChildEntityCompletionStats = (params: {\n survey: Survey\n record: ArenaRecord\n parentEntity: ArenaRecordNode\n nodeDef: NodeDef<NodeDefType, NodeDefProps>\n}): EntityCompletionStats => {\n const { survey, record, parentEntity, nodeDef } = params\n const childEntities = getChildren(parentEntity, nodeDef.uuid)(record)\n\n if (NodeDefs.isSingle(nodeDef)) {\n const childEntity = childEntities[0]\n return childEntity ? getEntityCompletionStats({ survey, record, entity: childEntity }) : emptyStats()\n }\n\n const minCountRaw = Nodes.getChildrenMinCount({ parentNode: parentEntity, nodeDef })\n const minCount = Number.isNaN(minCountRaw) ? 0 : Math.max(minCountRaw, 0)\n // entities added beyond the min count are still expected to be complete\n const expectedCount = Math.max(minCount, childEntities.length)\n if (expectedCount === 0) return emptyStats()\n\n const stats = emptyStats()\n for (let index = 0; index < expectedCount; index++) {\n const childEntity = childEntities[index]\n // a missing repetition (below the min count) counts as a single incomplete unit\n addStats(\n stats,\n childEntity ? getEntityCompletionStats({ survey, record, entity: childEntity }) : { total: 1, filled: 0 }\n )\n }\n return stats\n}\n\nexport const getEntityCompletionStats = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n includeNestedEntities?: boolean\n}): EntityCompletionStats => {\n const { survey, record, entity, includeNestedEntities = true } = params\n const entityDef = Surveys.getNodeDefByUuid({ survey, uuid: entity.nodeDefUuid })\n const childDefs = Surveys.getNodeDefChildren({ survey, nodeDef: entityDef })\n\n const stats = emptyStats()\n for (const childDef of childDefs) {\n if (!Nodes.isChildApplicable(entity, childDef.uuid)) continue\n\n if (NodeDefs.isEntity(childDef)) {\n if (!includeNestedEntities) continue\n addStats(stats, getChildEntityCompletionStats({ survey, record, parentEntity: entity, nodeDef: childDef }))\n } else {\n addStats(stats, getAttributeCompletionStats({ record, entity, nodeDef: childDef }))\n }\n }\n return stats\n}\n\nconst toCompletionPercent = (stats: EntityCompletionStats): number => {\n const { total, filled } = stats\n if (total <= 0) return 100\n return Numbers.roundToPrecision((filled / total) * 100, 2)\n}\n\n/**\n * Returns the percentage of completion of the given entity (0-100), considering every descendant recursively.\n * A required or key attribute is considered filled when it has a non empty value; multiple attributes and\n * entities are considered filled based on their min count (see Nodes.getChildrenMinCount).\n */\nexport const getEntityCompletionPercent = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n}): number => toCompletionPercent(getEntityCompletionStats(params))\n\n/**\n * Returns the percentage of completion of the given entity (0-100), considering only its own attributes\n * (nested entities are ignored).\n */\nexport const getEntityOwnCompletionPercent = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n}): number => toCompletionPercent(getEntityCompletionStats({ ...params, includeNestedEntities: false }))\n\n/**\n * Returns the percentage of completion of the given record (0-100), i.e. the completion of its root entity.\n */\nexport const getRecordCompletionPercent = (params: { survey: Survey; record: ArenaRecord }): number => {\n const { survey, record } = params\n const rootEntity = getRoot(record)\n return rootEntity ? getEntityCompletionPercent({ survey, record, entity: rootEntity }) : 0\n}\n"]}
1
+ {"version":3,"file":"recordCompletion.js","sourceRoot":"","sources":["../../../../src/record/_records/recordCompletion.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, Nodes } from '../../node'\nimport { NodeDef, NodeDefProps, NodeDefs, NodeDefType } from '../../nodeDef'\nimport { Survey, Surveys } from '../../survey'\nimport { Numbers } from '../../utils'\nimport { ArenaRecord } from '../record'\nimport { getChildren, getCycle, getRoot } from './recordGetters'\n\nexport interface EntityCompletionStats {\n total: number\n filled: number\n}\n\nconst emptyStats = (): EntityCompletionStats => ({ total: 0, filled: 0 })\n\nconst addStats = (statsAcc: EntityCompletionStats, statsToAdd: EntityCompletionStats): void => {\n statsAcc.total += statsToAdd.total\n statsAcc.filled += statsToAdd.filled\n}\n\nconst isAttributeRequired = (entity: ArenaRecordNode, nodeDef: NodeDef<NodeDefType, NodeDefProps>): boolean =>\n NodeDefs.isKey(nodeDef) ||\n NodeDefs.isRequired(nodeDef) ||\n (NodeDefs.isRequiredIfDefined(nodeDef) && Nodes.isChildRequired(entity, nodeDef.uuid))\n\nconst getAttributeCompletionStats = (params: {\n record: ArenaRecord\n entity: ArenaRecordNode\n nodeDef: NodeDef<NodeDefType, NodeDefProps>\n}): EntityCompletionStats => {\n const { record, entity, nodeDef } = params\n\n if (NodeDefs.isMultiple(nodeDef)) {\n const minCount = Nodes.getChildrenMinCount({ parentNode: entity, nodeDef })\n if (Number.isNaN(minCount) || minCount <= 0) return emptyStats()\n\n const filledNodesCount = getChildren(entity, nodeDef.uuid)(record).filter(Nodes.isValueNotBlank).length\n\n return { total: minCount, filled: Math.min(filledNodesCount, minCount) }\n }\n\n if (!isAttributeRequired(entity, nodeDef)) return emptyStats()\n\n const node = getChildren(entity, nodeDef.uuid)(record)[0]\n return { total: 1, filled: node && Nodes.isValueNotBlank(node) ? 1 : 0 }\n}\n\nconst getChildEntityCompletionStats = (params: {\n survey: Survey\n record: ArenaRecord\n parentEntity: ArenaRecordNode\n nodeDef: NodeDef<NodeDefType, NodeDefProps>\n}): EntityCompletionStats => {\n const { survey, record, parentEntity, nodeDef } = params\n const childEntities = getChildren(parentEntity, nodeDef.uuid)(record)\n\n if (NodeDefs.isSingle(nodeDef)) {\n const childEntity = childEntities[0]\n return childEntity ? getEntityCompletionStats({ survey, record, entity: childEntity }) : emptyStats()\n }\n\n const minCountRaw = Nodes.getChildrenMinCount({ parentNode: parentEntity, nodeDef })\n const minCount = Number.isNaN(minCountRaw) ? 0 : Math.max(minCountRaw, 0)\n // entities added beyond the min count are still expected to be complete\n const expectedCount = Math.max(minCount, childEntities.length)\n if (expectedCount === 0) return emptyStats()\n\n const stats = emptyStats()\n for (let index = 0; index < expectedCount; index++) {\n const childEntity = childEntities[index]\n // a missing repetition (below the min count) counts as a single incomplete unit\n addStats(\n stats,\n childEntity ? getEntityCompletionStats({ survey, record, entity: childEntity }) : { total: 1, filled: 0 }\n )\n }\n return stats\n}\n\nexport const getEntityCompletionStats = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n includeNestedEntities?: boolean\n}): EntityCompletionStats => {\n const { survey, record, entity, includeNestedEntities = true } = params\n const entityDef = Surveys.getNodeDefByUuid({ survey, uuid: entity.nodeDefUuid })\n const childDefs = Surveys.getNodeDefChildren({ survey, nodeDef: entityDef }).filter(\n NodeDefs.isInCycle(getCycle(record))\n )\n\n const stats = emptyStats()\n for (const childDef of childDefs) {\n if (!Nodes.isChildApplicable(entity, childDef.uuid)) continue\n\n if (NodeDefs.isEntity(childDef)) {\n if (!includeNestedEntities) continue\n addStats(stats, getChildEntityCompletionStats({ survey, record, parentEntity: entity, nodeDef: childDef }))\n } else {\n addStats(stats, getAttributeCompletionStats({ record, entity, nodeDef: childDef }))\n }\n }\n return stats\n}\n\nconst toCompletionPercent = (stats: EntityCompletionStats): number => {\n const { total, filled } = stats\n if (total <= 0) return 100\n return Numbers.roundToPrecision((filled / total) * 100, 2)\n}\n\n/**\n * Returns the percentage of completion of the given entity (0-100), considering every descendant recursively.\n * A required or key attribute is considered filled when it has a non empty value; multiple attributes and\n * entities are considered filled based on their min count (see Nodes.getChildrenMinCount).\n */\nexport const getEntityCompletionPercent = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n}): number => toCompletionPercent(getEntityCompletionStats(params))\n\n/**\n * Returns the percentage of completion of the given entity (0-100), considering only its own attributes\n * (nested entities are ignored).\n */\nexport const getEntityOwnCompletionPercent = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n}): number => toCompletionPercent(getEntityCompletionStats({ ...params, includeNestedEntities: false }))\n\n/**\n * Returns the percentage of completion of the given record (0-100), i.e. the completion of its root entity.\n */\nexport const getRecordCompletionPercent = (params: { survey: Survey; record: ArenaRecord }): number => {\n const { survey, record } = params\n const rootEntity = getRoot(record)\n return rootEntity ? getEntityCompletionPercent({ survey, record, entity: rootEntity }) : 0\n}\n"]}
@@ -130,9 +130,9 @@ const removeExcludedNodes = (params) => {
130
130
  const { survey, record, sideEffect } = params;
131
131
  const cycle = record.cycle;
132
132
  const result = new RecordUpdateResult({ record });
133
+ // all the node defs are visited (not only the ones in the cycle): nodes of the ones not in the cycle must be removed
133
134
  Surveys.visitDescendantsAndSelfNodeDef({
134
135
  survey,
135
- cycle,
136
136
  nodeDef: Surveys.getNodeDefRoot({ survey }),
137
137
  visitor: (nodeDef) => {
138
138
  const hasAncestorCodeDefExcludedInClone = nodeDef.type === NodeDefType.code &&
@@ -1 +1 @@
1
- {"version":3,"file":"recordCloner.js","sourceRoot":"","sources":["../../../src/record/recordCloner.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, NodeValues, Nodes } from '../node'\nimport { NodeKeys, NodeMetaKeys } from '../node/node'\nimport { NodeDefCode, NodeDefType, NodeDefs } from '../nodeDef'\nimport { Survey, Surveys } from '../survey'\nimport { Dates, Objects, UUIDs } from '../utils'\nimport { Validation } from '../validation'\nimport { ArenaRecord } from './record'\nimport { RecordFixer } from './recordFixer'\nimport { RecordUpdateResult } from './recordNodesUpdater'\nimport { RecordValidations } from './recordValidations'\nimport { Records } from './records'\n\ntype OldUuidToNewUuidMap = {\n [key: string]: string\n}\n\nconst assignNewUuidsToNodes = (params: {\n record: ArenaRecord\n sideEffect: boolean\n}): { newNodeUuidsByOldUuid: OldUuidToNewUuidMap; newFileUuidsByOldUuid: OldUuidToNewUuidMap; record: ArenaRecord } => {\n const { record, sideEffect } = params\n\n // generate a new uuid for each node\n const newNodeUuidsByOldUuid: OldUuidToNewUuidMap = {}\n const newFileUuidsByOldUuid: OldUuidToNewUuidMap = {}\n\n // sort nodes before removing the id, to preserve their hierarchy (faster than comparing each meta.h property)\n const nodesArray = Records.getNodesArray(record).sort(\n (nodeA: ArenaRecordNode, nodeB: ArenaRecordNode): number =>\n Nodes.getHierarchy(nodeA).length - Nodes.getHierarchy(nodeB).length\n )\n\n const recordUpdated = sideEffect ? record : { ...record }\n\n const nodes = recordUpdated.nodes ?? {}\n const nodesUpdated = sideEffect ? nodes : { ...nodes }\n\n // new uuids generated in advance: meta.hCode can reference nodes cloned later (e.g. sibling code attributes)\n for (const node of nodesArray) {\n newNodeUuidsByOldUuid[node.uuid] = UUIDs.v4()\n }\n\n // update nodes recordUuid and properties (it does side effect on nodes)\n nodesArray.forEach((node) => {\n let nodeUpdated = sideEffect ? node : { ...node }\n nodeUpdated.recordUuid = record.uuid\n nodeUpdated.dateCreated = nodeUpdated.dateModified = Dates.nowFormattedForStorage()\n\n const oldUuid = node.uuid\n const newUuid = newNodeUuidsByOldUuid[oldUuid]\n nodeUpdated.uuid = newUuid\n // consider every node as just created node\n delete nodeUpdated.id\n nodeUpdated.created = true // this flag will be used by the RDB generator)\n\n // update internal node uuids, meta hierarchy, file uuid (if any)\n const parentUuid = node.parentUuid\n if (parentUuid) {\n nodeUpdated.parentUuid = newNodeUuidsByOldUuid[parentUuid]\n }\n const hierarchy = node.meta?.h ?? []\n if (hierarchy.length > 0) {\n const hierarchyUpdated = hierarchy.map((ancestorUuid) => newNodeUuidsByOldUuid[ancestorUuid])\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.meta, NodeMetaKeys.h],\n value: hierarchyUpdated,\n sideEffect,\n })\n }\n const hierarchyCode = Nodes.getHierarchyCode(node)\n if (hierarchyCode.length > 0) {\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.meta, NodeMetaKeys.hCode],\n value: hierarchyCode.map((ancestorCodeUuid) => newNodeUuidsByOldUuid[ancestorCodeUuid]),\n sideEffect,\n })\n }\n // assign new file uuid (file should be cloned elsewhere)\n const fileUuid = NodeValues.getFileUuid(node)\n if (fileUuid) {\n const newFileUuid = UUIDs.v4()\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.value, NodeValues.ValuePropsFile.fileUuid],\n value: newFileUuid,\n })\n newFileUuidsByOldUuid[fileUuid] = newFileUuid\n }\n\n delete nodesUpdated[oldUuid]\n nodesUpdated[newUuid] = nodeUpdated\n })\n\n recordUpdated.nodes = nodesUpdated\n\n return { newNodeUuidsByOldUuid, newFileUuidsByOldUuid, record: recordUpdated }\n}\n\nconst assignNewUuidsToValidation = (params: {\n validation: Validation\n newNodeUuidsByOldUuid: OldUuidToNewUuidMap\n sideEffect: boolean\n}): Validation => {\n const { validation, newNodeUuidsByOldUuid, sideEffect } = params\n\n const validationFields = validation?.fields\n if (!validationFields) return validation\n\n const validationFieldsUpdated = sideEffect ? validationFields : { ...validationFields }\n\n Object.entries(validationFields).forEach(([oldFieldKey, validationField]) => {\n let newFieldKey\n if (oldFieldKey.startsWith(RecordValidations.prefixValidationFieldChildrenCount)) {\n const oldParentUuid = oldFieldKey.substring(\n RecordValidations.prefixValidationFieldChildrenCount.length,\n oldFieldKey.lastIndexOf('_')\n )\n const newParentUuid = newNodeUuidsByOldUuid[oldParentUuid]\n newFieldKey = oldFieldKey.replace(oldParentUuid, newParentUuid)\n } else {\n newFieldKey = newNodeUuidsByOldUuid[oldFieldKey]\n }\n if (newFieldKey) {\n validationFieldsUpdated[newFieldKey] = validationField\n }\n delete validationFieldsUpdated[oldFieldKey]\n })\n return Objects.assoc({\n obj: validation,\n prop: 'fields',\n value: validationFieldsUpdated,\n sideEffect,\n })\n}\n\nconst assignNewUuids = (params: {\n record: ArenaRecord\n sideEffect: boolean\n}): { newNodeUuidsByOldUuid: OldUuidToNewUuidMap; newFileUuidsByOldUuid: OldUuidToNewUuidMap; record: ArenaRecord } => {\n const { record, sideEffect } = params\n let recordUpdated = sideEffect ? record : { ...record }\n recordUpdated.uuid = UUIDs.v4()\n\n const {\n newNodeUuidsByOldUuid,\n newFileUuidsByOldUuid,\n record: recordUpdatedUuids,\n } = assignNewUuidsToNodes({ record: recordUpdated, sideEffect })\n\n recordUpdated = recordUpdatedUuids\n\n const { validation } = recordUpdated\n if (validation) {\n const validationUpdated = assignNewUuidsToValidation({\n validation,\n newNodeUuidsByOldUuid,\n sideEffect,\n })\n recordUpdated = Objects.assoc({ obj: recordUpdated, prop: 'validation', value: validationUpdated, sideEffect })\n }\n // re-build nodes index\n const nodes = recordUpdated.nodes!\n delete recordUpdated._nodesIndex\n delete recordUpdated.nodes\n recordUpdated = Records.addNodes(nodes, { sideEffect, sortNodes: true })(recordUpdated)\n\n return { newNodeUuidsByOldUuid, newFileUuidsByOldUuid, record: recordUpdated }\n}\n\nconst removeExcludedNodes = (params: {\n survey: Survey\n record: ArenaRecord\n sideEffect: boolean\n}): RecordUpdateResult => {\n const { survey, record, sideEffect } = params\n const cycle = record.cycle!\n const result = new RecordUpdateResult({ record })\n Surveys.visitDescendantsAndSelfNodeDef({\n survey,\n cycle,\n nodeDef: Surveys.getNodeDefRoot({ survey }),\n visitor: (nodeDef) => {\n const hasAncestorCodeDefExcludedInClone =\n nodeDef.type === NodeDefType.code &&\n Surveys.getNodeDefAncestorCodes({ survey, nodeDef: nodeDef as NodeDefCode }).some(NodeDefs.isExcludedInClone)\n\n if (\n !NodeDefs.isInCycle(cycle)(nodeDef) ||\n NodeDefs.isExcludedInClone(nodeDef) ||\n hasAncestorCodeDefExcludedInClone\n ) {\n const nodesToDelete = Records.getNodesByDefUuid(nodeDef.uuid)(result.record)\n if (nodesToDelete.length > 0) {\n const partialUpdateResult = Records.removeNodes(\n nodesToDelete.map((node) => node.uuid),\n { sideEffect }\n )(result.record)\n result.merge(partialUpdateResult)\n }\n }\n },\n })\n return result\n}\n\nconst cloneRecord = (params: { survey: Survey; record: ArenaRecord; cycleTo: string; sideEffect?: boolean }) => {\n const { survey, record, cycleTo, sideEffect = false } = params\n\n const recordUpdated = sideEffect ? record : { ...record }\n recordUpdated.cycle = cycleTo\n recordUpdated.dateCreated = recordUpdated.dateModified = Dates.nowFormattedForStorage()\n delete recordUpdated.id\n\n // delete nodes not included in clone\n const updateResult = new RecordUpdateResult({ record: recordUpdated })\n updateResult.merge(removeExcludedNodes({ survey, record: updateResult.record, sideEffect }))\n updateResult.merge(RecordFixer.insertMissingSingleNodes({ survey, record: updateResult.record, sideEffect }))\n\n // assign new UUIDs with side effect on record and nodes, faster when record is big\n const {\n newNodeUuidsByOldUuid,\n newFileUuidsByOldUuid,\n record: recordUpdatedUuids,\n } = assignNewUuids({ record: updateResult.record, sideEffect })\n\n updateResult.merge(new RecordUpdateResult({ record: recordUpdatedUuids }))\n\n return { record: updateResult.record, newNodeUuidsByOldUuid, newFileUuidsByOldUuid }\n}\n\nexport const RecordCloner = {\n cloneRecord,\n}\n"]}
1
+ {"version":3,"file":"recordCloner.js","sourceRoot":"","sources":["../../../src/record/recordCloner.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, NodeValues, Nodes } from '../node'\nimport { NodeKeys, NodeMetaKeys } from '../node/node'\nimport { NodeDefCode, NodeDefType, NodeDefs } from '../nodeDef'\nimport { Survey, Surveys } from '../survey'\nimport { Dates, Objects, UUIDs } from '../utils'\nimport { Validation } from '../validation'\nimport { ArenaRecord } from './record'\nimport { RecordFixer } from './recordFixer'\nimport { RecordUpdateResult } from './recordNodesUpdater'\nimport { RecordValidations } from './recordValidations'\nimport { Records } from './records'\n\ntype OldUuidToNewUuidMap = {\n [key: string]: string\n}\n\nconst assignNewUuidsToNodes = (params: {\n record: ArenaRecord\n sideEffect: boolean\n}): { newNodeUuidsByOldUuid: OldUuidToNewUuidMap; newFileUuidsByOldUuid: OldUuidToNewUuidMap; record: ArenaRecord } => {\n const { record, sideEffect } = params\n\n // generate a new uuid for each node\n const newNodeUuidsByOldUuid: OldUuidToNewUuidMap = {}\n const newFileUuidsByOldUuid: OldUuidToNewUuidMap = {}\n\n // sort nodes before removing the id, to preserve their hierarchy (faster than comparing each meta.h property)\n const nodesArray = Records.getNodesArray(record).sort(\n (nodeA: ArenaRecordNode, nodeB: ArenaRecordNode): number =>\n Nodes.getHierarchy(nodeA).length - Nodes.getHierarchy(nodeB).length\n )\n\n const recordUpdated = sideEffect ? record : { ...record }\n\n const nodes = recordUpdated.nodes ?? {}\n const nodesUpdated = sideEffect ? nodes : { ...nodes }\n\n // new uuids generated in advance: meta.hCode can reference nodes cloned later (e.g. sibling code attributes)\n for (const node of nodesArray) {\n newNodeUuidsByOldUuid[node.uuid] = UUIDs.v4()\n }\n\n // update nodes recordUuid and properties (it does side effect on nodes)\n nodesArray.forEach((node) => {\n let nodeUpdated = sideEffect ? node : { ...node }\n nodeUpdated.recordUuid = record.uuid\n nodeUpdated.dateCreated = nodeUpdated.dateModified = Dates.nowFormattedForStorage()\n\n const oldUuid = node.uuid\n const newUuid = newNodeUuidsByOldUuid[oldUuid]\n nodeUpdated.uuid = newUuid\n // consider every node as just created node\n delete nodeUpdated.id\n nodeUpdated.created = true // this flag will be used by the RDB generator)\n\n // update internal node uuids, meta hierarchy, file uuid (if any)\n const parentUuid = node.parentUuid\n if (parentUuid) {\n nodeUpdated.parentUuid = newNodeUuidsByOldUuid[parentUuid]\n }\n const hierarchy = node.meta?.h ?? []\n if (hierarchy.length > 0) {\n const hierarchyUpdated = hierarchy.map((ancestorUuid) => newNodeUuidsByOldUuid[ancestorUuid])\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.meta, NodeMetaKeys.h],\n value: hierarchyUpdated,\n sideEffect,\n })\n }\n const hierarchyCode = Nodes.getHierarchyCode(node)\n if (hierarchyCode.length > 0) {\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.meta, NodeMetaKeys.hCode],\n value: hierarchyCode.map((ancestorCodeUuid) => newNodeUuidsByOldUuid[ancestorCodeUuid]),\n sideEffect,\n })\n }\n // assign new file uuid (file should be cloned elsewhere)\n const fileUuid = NodeValues.getFileUuid(node)\n if (fileUuid) {\n const newFileUuid = UUIDs.v4()\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.value, NodeValues.ValuePropsFile.fileUuid],\n value: newFileUuid,\n })\n newFileUuidsByOldUuid[fileUuid] = newFileUuid\n }\n\n delete nodesUpdated[oldUuid]\n nodesUpdated[newUuid] = nodeUpdated\n })\n\n recordUpdated.nodes = nodesUpdated\n\n return { newNodeUuidsByOldUuid, newFileUuidsByOldUuid, record: recordUpdated }\n}\n\nconst assignNewUuidsToValidation = (params: {\n validation: Validation\n newNodeUuidsByOldUuid: OldUuidToNewUuidMap\n sideEffect: boolean\n}): Validation => {\n const { validation, newNodeUuidsByOldUuid, sideEffect } = params\n\n const validationFields = validation?.fields\n if (!validationFields) return validation\n\n const validationFieldsUpdated = sideEffect ? validationFields : { ...validationFields }\n\n Object.entries(validationFields).forEach(([oldFieldKey, validationField]) => {\n let newFieldKey\n if (oldFieldKey.startsWith(RecordValidations.prefixValidationFieldChildrenCount)) {\n const oldParentUuid = oldFieldKey.substring(\n RecordValidations.prefixValidationFieldChildrenCount.length,\n oldFieldKey.lastIndexOf('_')\n )\n const newParentUuid = newNodeUuidsByOldUuid[oldParentUuid]\n newFieldKey = oldFieldKey.replace(oldParentUuid, newParentUuid)\n } else {\n newFieldKey = newNodeUuidsByOldUuid[oldFieldKey]\n }\n if (newFieldKey) {\n validationFieldsUpdated[newFieldKey] = validationField\n }\n delete validationFieldsUpdated[oldFieldKey]\n })\n return Objects.assoc({\n obj: validation,\n prop: 'fields',\n value: validationFieldsUpdated,\n sideEffect,\n })\n}\n\nconst assignNewUuids = (params: {\n record: ArenaRecord\n sideEffect: boolean\n}): { newNodeUuidsByOldUuid: OldUuidToNewUuidMap; newFileUuidsByOldUuid: OldUuidToNewUuidMap; record: ArenaRecord } => {\n const { record, sideEffect } = params\n let recordUpdated = sideEffect ? record : { ...record }\n recordUpdated.uuid = UUIDs.v4()\n\n const {\n newNodeUuidsByOldUuid,\n newFileUuidsByOldUuid,\n record: recordUpdatedUuids,\n } = assignNewUuidsToNodes({ record: recordUpdated, sideEffect })\n\n recordUpdated = recordUpdatedUuids\n\n const { validation } = recordUpdated\n if (validation) {\n const validationUpdated = assignNewUuidsToValidation({\n validation,\n newNodeUuidsByOldUuid,\n sideEffect,\n })\n recordUpdated = Objects.assoc({ obj: recordUpdated, prop: 'validation', value: validationUpdated, sideEffect })\n }\n // re-build nodes index\n const nodes = recordUpdated.nodes!\n delete recordUpdated._nodesIndex\n delete recordUpdated.nodes\n recordUpdated = Records.addNodes(nodes, { sideEffect, sortNodes: true })(recordUpdated)\n\n return { newNodeUuidsByOldUuid, newFileUuidsByOldUuid, record: recordUpdated }\n}\n\nconst removeExcludedNodes = (params: {\n survey: Survey\n record: ArenaRecord\n sideEffect: boolean\n}): RecordUpdateResult => {\n const { survey, record, sideEffect } = params\n const cycle = record.cycle!\n const result = new RecordUpdateResult({ record })\n // all the node defs are visited (not only the ones in the cycle): nodes of the ones not in the cycle must be removed\n Surveys.visitDescendantsAndSelfNodeDef({\n survey,\n nodeDef: Surveys.getNodeDefRoot({ survey }),\n visitor: (nodeDef) => {\n const hasAncestorCodeDefExcludedInClone =\n nodeDef.type === NodeDefType.code &&\n Surveys.getNodeDefAncestorCodes({ survey, nodeDef: nodeDef as NodeDefCode }).some(NodeDefs.isExcludedInClone)\n\n if (\n !NodeDefs.isInCycle(cycle)(nodeDef) ||\n NodeDefs.isExcludedInClone(nodeDef) ||\n hasAncestorCodeDefExcludedInClone\n ) {\n const nodesToDelete = Records.getNodesByDefUuid(nodeDef.uuid)(result.record)\n if (nodesToDelete.length > 0) {\n const partialUpdateResult = Records.removeNodes(\n nodesToDelete.map((node) => node.uuid),\n { sideEffect }\n )(result.record)\n result.merge(partialUpdateResult)\n }\n }\n },\n })\n return result\n}\n\nconst cloneRecord = (params: { survey: Survey; record: ArenaRecord; cycleTo: string; sideEffect?: boolean }) => {\n const { survey, record, cycleTo, sideEffect = false } = params\n\n const recordUpdated = sideEffect ? record : { ...record }\n recordUpdated.cycle = cycleTo\n recordUpdated.dateCreated = recordUpdated.dateModified = Dates.nowFormattedForStorage()\n delete recordUpdated.id\n\n // delete nodes not included in clone\n const updateResult = new RecordUpdateResult({ record: recordUpdated })\n updateResult.merge(removeExcludedNodes({ survey, record: updateResult.record, sideEffect }))\n updateResult.merge(RecordFixer.insertMissingSingleNodes({ survey, record: updateResult.record, sideEffect }))\n\n // assign new UUIDs with side effect on record and nodes, faster when record is big\n const {\n newNodeUuidsByOldUuid,\n newFileUuidsByOldUuid,\n record: recordUpdatedUuids,\n } = assignNewUuids({ record: updateResult.record, sideEffect })\n\n updateResult.merge(new RecordUpdateResult({ record: recordUpdatedUuids }))\n\n return { record: updateResult.record, newNodeUuidsByOldUuid, newFileUuidsByOldUuid }\n}\n\nexport const RecordCloner = {\n cloneRecord,\n}\n"]}
@@ -45,11 +45,13 @@ const insertMissingSingleNode = (params) => {
45
45
  const insertMissingSingleNodes = (params) => {
46
46
  const { survey, record, sideEffect } = params;
47
47
  const updateResult = new RecordUpdateResult({ record });
48
+ const cycle = Records.getCycle(record);
48
49
  Surveys.visitNodeDefs({
49
50
  survey,
50
51
  visitor: (nodeDef) => {
51
52
  const parentDefUuid = nodeDef.parentUuid;
52
- if (parentDefUuid) {
53
+ // node defs not in the record cycle have no nodes
54
+ if (parentDefUuid && NodeDefs.isInCycle(cycle)(nodeDef)) {
53
55
  const parentNodes = Records.getNodesByDefUuid(parentDefUuid)(updateResult.record);
54
56
  for (const parentNode of parentNodes) {
55
57
  const partialUpdateResult = insertMissingSingleNode({
@@ -1 +1 @@
1
- {"version":3,"file":"recordFixer.js","sourceRoot":"","sources":["../../../src/record/recordFixer.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, NodeFactory, Nodes } from '../node'\nimport { NodeDef, NodeDefCode, NodeDefs, NodeDefType } from '../nodeDef'\nimport { Survey, Surveys } from '../survey'\nimport { Objects } from '../utils'\nimport type { ArenaRecord } from './record'\nimport { RecordUpdateResult } from './recordNodesUpdater'\nimport { Records } from './records'\n\nconst fixCodeAttribute = (params: {\n survey: Survey\n nodeDef: NodeDefCode\n record: ArenaRecord\n node: ArenaRecordNode\n sideEffect: boolean\n}): ArenaRecordNode => {\n const { survey, nodeDef, record, node, sideEffect } = params\n if (!NodeDefs.getParentCodeDefUuid(nodeDef) || Objects.isNotEmpty(Nodes.getHierarchyCode(node))) {\n // nodeDef is not a code attribute or meta.hCode already populated: do nothing\n return node\n }\n const parentNode = Records.getParent(node)(record)\n if (!parentNode) {\n // missing parent node; node parentUuid could be invalid\n return node\n }\n // populate meta.hCode with ancestor code attribute node uuids\n const hCode = Records.getAncestorCodeAttributes(\n survey,\n parentNode,\n nodeDef\n )(record).map((codeAttribute) => codeAttribute.uuid)\n const nodeUpdated = sideEffect ? node : { ...node }\n nodeUpdated.meta = { ...nodeUpdated.meta, hCode }\n return nodeUpdated\n}\n\nconst insertMissingSingleNode = (params: {\n survey: Survey\n nodeDef: NodeDef<any>\n record: ArenaRecord\n parentNode: ArenaRecordNode\n sideEffect: boolean\n}): RecordUpdateResult | null => {\n const { survey, nodeDef, record, parentNode, sideEffect } = params\n if (!NodeDefs.isSingle(nodeDef)) {\n // multiple node: don't insert it\n return null\n }\n const nodeDefUuid = nodeDef.uuid\n const children = Records.getChildren(parentNode, nodeDef.uuid)(record)\n if (!Objects.isEmpty(children)) {\n // single node already inserted\n return null\n }\n // insert missing single node\n const recordUuid = record.uuid\n let node = NodeFactory.createInstance({ nodeDefUuid, recordUuid, parentNode })\n\n if (nodeDef.type === NodeDefType.code) {\n node = fixCodeAttribute({ survey, nodeDef: nodeDef as NodeDefCode, record, node, sideEffect })\n }\n\n const recordUpdated = Records.addNode(node, { sideEffect })(record)\n return new RecordUpdateResult({ record: recordUpdated, nodes: { [node.uuid]: node } })\n}\n\nconst insertMissingSingleNodes = (params: {\n survey: Survey\n record: ArenaRecord\n sideEffect: boolean\n}): RecordUpdateResult => {\n const { survey, record, sideEffect } = params\n const updateResult = new RecordUpdateResult({ record })\n Surveys.visitNodeDefs({\n survey,\n visitor: (nodeDef) => {\n const parentDefUuid = nodeDef.parentUuid\n if (parentDefUuid) {\n const parentNodes = Records.getNodesByDefUuid(parentDefUuid)(updateResult.record)\n for (const parentNode of parentNodes) {\n const partialUpdateResult = insertMissingSingleNode({\n survey,\n nodeDef,\n record: updateResult.record,\n parentNode,\n sideEffect,\n })\n if (partialUpdateResult) {\n updateResult.merge(partialUpdateResult)\n }\n }\n }\n },\n })\n return updateResult\n}\n\nconst deleteNodesByDefUuid = (params: { record: ArenaRecord; nodeDefUuid: string; sideEffect: boolean }) => {\n const { record, nodeDefUuid, sideEffect } = params\n const updateResult = new RecordUpdateResult({ record })\n\n const recordUpdateOptions = { sideEffect }\n\n const nodesToDelete = Records.getNodesByDefUuid(nodeDefUuid)(updateResult.record)\n for (const nodeToDelete of nodesToDelete) {\n // cleanup child applicability\n const parentNode = Records.getParent(nodeToDelete)(updateResult.record)\n if (parentNode && !Nodes.isChildApplicable(parentNode, nodeDefUuid)) {\n const parentNodeUpdated = Nodes.dissocChildApplicability(parentNode, nodeDefUuid)\n const recordWithParentNodeUpdated = Records.addNode(parentNodeUpdated, recordUpdateOptions)(updateResult.record)\n updateResult.merge(new RecordUpdateResult({ record: recordWithParentNodeUpdated }))\n }\n }\n const nodeUuidsToDelete = nodesToDelete.map((node) => node.uuid)\n const nodesDeleteUpdateResult = Records.removeNodes(nodeUuidsToDelete, recordUpdateOptions)(updateResult.record)\n updateResult.merge(nodesDeleteUpdateResult)\n\n return updateResult\n}\n\n/**\n * Fix a record by:\n * - inserting missing single nodes\n * - deleting nodes with non existing node defs\n * - removing status flags (created, deleted, updated) from all nodes\n */\nconst fixRecord = (params: { survey: Survey; record: ArenaRecord; sideEffect?: boolean }): RecordUpdateResult => {\n const { survey, record, sideEffect = false } = params\n const result = new RecordUpdateResult({ record })\n\n // 1. delete nodes with non existing node defs (and their descendants), once per missing node def\n const missingNodeDefUuids = new Set<string>()\n for (const { nodeDefUuid } of Records.getNodesArray(record)) {\n if (!Surveys.findNodeDefByUuid({ survey, uuid: nodeDefUuid })) {\n missingNodeDefUuids.add(nodeDefUuid)\n }\n }\n for (const nodeDefUuid of missingNodeDefUuids) {\n result.merge(deleteNodesByDefUuid({ record: result.record, nodeDefUuid, sideEffect }))\n }\n\n // 2. fix the remaining nodes (read from the updated record, without the deleted nodes); add them all at once\n const nodesUpdated: { [key: string]: ArenaRecordNode } = {}\n for (const node of Records.getNodesArray(result.record)) {\n const nodeDef = Surveys.getNodeDefByUuid({ survey, uuid: node.nodeDefUuid })\n // remove status flags\n let nodeUpdated = Nodes.removeStatusFlags({ node, sideEffect })\n\n if (nodeDef.type === NodeDefType.code) {\n nodeUpdated = fixCodeAttribute({\n survey,\n nodeDef: nodeDef as NodeDefCode,\n record: result.record,\n node: nodeUpdated,\n sideEffect,\n })\n }\n nodesUpdated[nodeUpdated.uuid] = nodeUpdated\n }\n const recordWithNodesUpdated = Records.addNodes(nodesUpdated, { sideEffect })(result.record)\n result.merge(new RecordUpdateResult({ record: recordWithNodesUpdated, nodes: nodesUpdated }))\n\n const missingNodesUpdateResult = insertMissingSingleNodes({ survey, record: result.record, sideEffect })\n result.merge(missingNodesUpdateResult)\n return result\n}\n\nexport const RecordFixer = {\n fixRecord,\n insertMissingSingleNodes,\n}\n"]}
1
+ {"version":3,"file":"recordFixer.js","sourceRoot":"","sources":["../../../src/record/recordFixer.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, NodeFactory, Nodes } from '../node'\nimport { NodeDef, NodeDefCode, NodeDefs, NodeDefType } from '../nodeDef'\nimport { Survey, Surveys } from '../survey'\nimport { Objects } from '../utils'\nimport type { ArenaRecord } from './record'\nimport { RecordUpdateResult } from './recordNodesUpdater'\nimport { Records } from './records'\n\nconst fixCodeAttribute = (params: {\n survey: Survey\n nodeDef: NodeDefCode\n record: ArenaRecord\n node: ArenaRecordNode\n sideEffect: boolean\n}): ArenaRecordNode => {\n const { survey, nodeDef, record, node, sideEffect } = params\n if (!NodeDefs.getParentCodeDefUuid(nodeDef) || Objects.isNotEmpty(Nodes.getHierarchyCode(node))) {\n // nodeDef is not a code attribute or meta.hCode already populated: do nothing\n return node\n }\n const parentNode = Records.getParent(node)(record)\n if (!parentNode) {\n // missing parent node; node parentUuid could be invalid\n return node\n }\n // populate meta.hCode with ancestor code attribute node uuids\n const hCode = Records.getAncestorCodeAttributes(\n survey,\n parentNode,\n nodeDef\n )(record).map((codeAttribute) => codeAttribute.uuid)\n const nodeUpdated = sideEffect ? node : { ...node }\n nodeUpdated.meta = { ...nodeUpdated.meta, hCode }\n return nodeUpdated\n}\n\nconst insertMissingSingleNode = (params: {\n survey: Survey\n nodeDef: NodeDef<any>\n record: ArenaRecord\n parentNode: ArenaRecordNode\n sideEffect: boolean\n}): RecordUpdateResult | null => {\n const { survey, nodeDef, record, parentNode, sideEffect } = params\n if (!NodeDefs.isSingle(nodeDef)) {\n // multiple node: don't insert it\n return null\n }\n const nodeDefUuid = nodeDef.uuid\n const children = Records.getChildren(parentNode, nodeDef.uuid)(record)\n if (!Objects.isEmpty(children)) {\n // single node already inserted\n return null\n }\n // insert missing single node\n const recordUuid = record.uuid\n let node = NodeFactory.createInstance({ nodeDefUuid, recordUuid, parentNode })\n\n if (nodeDef.type === NodeDefType.code) {\n node = fixCodeAttribute({ survey, nodeDef: nodeDef as NodeDefCode, record, node, sideEffect })\n }\n\n const recordUpdated = Records.addNode(node, { sideEffect })(record)\n return new RecordUpdateResult({ record: recordUpdated, nodes: { [node.uuid]: node } })\n}\n\nconst insertMissingSingleNodes = (params: {\n survey: Survey\n record: ArenaRecord\n sideEffect: boolean\n}): RecordUpdateResult => {\n const { survey, record, sideEffect } = params\n const updateResult = new RecordUpdateResult({ record })\n const cycle = Records.getCycle(record)\n Surveys.visitNodeDefs({\n survey,\n visitor: (nodeDef) => {\n const parentDefUuid = nodeDef.parentUuid\n // node defs not in the record cycle have no nodes\n if (parentDefUuid && NodeDefs.isInCycle(cycle)(nodeDef)) {\n const parentNodes = Records.getNodesByDefUuid(parentDefUuid)(updateResult.record)\n for (const parentNode of parentNodes) {\n const partialUpdateResult = insertMissingSingleNode({\n survey,\n nodeDef,\n record: updateResult.record,\n parentNode,\n sideEffect,\n })\n if (partialUpdateResult) {\n updateResult.merge(partialUpdateResult)\n }\n }\n }\n },\n })\n return updateResult\n}\n\nconst deleteNodesByDefUuid = (params: { record: ArenaRecord; nodeDefUuid: string; sideEffect: boolean }) => {\n const { record, nodeDefUuid, sideEffect } = params\n const updateResult = new RecordUpdateResult({ record })\n\n const recordUpdateOptions = { sideEffect }\n\n const nodesToDelete = Records.getNodesByDefUuid(nodeDefUuid)(updateResult.record)\n for (const nodeToDelete of nodesToDelete) {\n // cleanup child applicability\n const parentNode = Records.getParent(nodeToDelete)(updateResult.record)\n if (parentNode && !Nodes.isChildApplicable(parentNode, nodeDefUuid)) {\n const parentNodeUpdated = Nodes.dissocChildApplicability(parentNode, nodeDefUuid)\n const recordWithParentNodeUpdated = Records.addNode(parentNodeUpdated, recordUpdateOptions)(updateResult.record)\n updateResult.merge(new RecordUpdateResult({ record: recordWithParentNodeUpdated }))\n }\n }\n const nodeUuidsToDelete = nodesToDelete.map((node) => node.uuid)\n const nodesDeleteUpdateResult = Records.removeNodes(nodeUuidsToDelete, recordUpdateOptions)(updateResult.record)\n updateResult.merge(nodesDeleteUpdateResult)\n\n return updateResult\n}\n\n/**\n * Fix a record by:\n * - inserting missing single nodes\n * - deleting nodes with non existing node defs\n * - removing status flags (created, deleted, updated) from all nodes\n */\nconst fixRecord = (params: { survey: Survey; record: ArenaRecord; sideEffect?: boolean }): RecordUpdateResult => {\n const { survey, record, sideEffect = false } = params\n const result = new RecordUpdateResult({ record })\n\n // 1. delete nodes with non existing node defs (and their descendants), once per missing node def\n const missingNodeDefUuids = new Set<string>()\n for (const { nodeDefUuid } of Records.getNodesArray(record)) {\n if (!Surveys.findNodeDefByUuid({ survey, uuid: nodeDefUuid })) {\n missingNodeDefUuids.add(nodeDefUuid)\n }\n }\n for (const nodeDefUuid of missingNodeDefUuids) {\n result.merge(deleteNodesByDefUuid({ record: result.record, nodeDefUuid, sideEffect }))\n }\n\n // 2. fix the remaining nodes (read from the updated record, without the deleted nodes); add them all at once\n const nodesUpdated: { [key: string]: ArenaRecordNode } = {}\n for (const node of Records.getNodesArray(result.record)) {\n const nodeDef = Surveys.getNodeDefByUuid({ survey, uuid: node.nodeDefUuid })\n // remove status flags\n let nodeUpdated = Nodes.removeStatusFlags({ node, sideEffect })\n\n if (nodeDef.type === NodeDefType.code) {\n nodeUpdated = fixCodeAttribute({\n survey,\n nodeDef: nodeDef as NodeDefCode,\n record: result.record,\n node: nodeUpdated,\n sideEffect,\n })\n }\n nodesUpdated[nodeUpdated.uuid] = nodeUpdated\n }\n const recordWithNodesUpdated = Records.addNodes(nodesUpdated, { sideEffect })(result.record)\n result.merge(new RecordUpdateResult({ record: recordWithNodesUpdated, nodes: nodesUpdated }))\n\n const missingNodesUpdateResult = insertMissingSingleNodes({ survey, record: result.record, sideEffect })\n result.merge(missingNodesUpdateResult)\n return result\n}\n\nexport const RecordFixer = {\n fixRecord,\n insertMissingSingleNodes,\n}\n"]}
@@ -12,7 +12,7 @@ import { NodeFactory, Nodes } from '../../node';
12
12
  import { NodeValues } from '../../node/nodeValues';
13
13
  import { NodeDefType, NodeDefs } from '../../nodeDef';
14
14
  import { getNodeDefChildren, getNodeDefEnumerator } from '../../survey/surveys/nodeDefs';
15
- import { getAncestorCodeAttributes } from '../_records/recordGetters';
15
+ import { getAncestorCodeAttributes, getCycle } from '../_records/recordGetters';
16
16
  import { getEnumeratingCategoryItems } from '../_records/recordUtils';
17
17
  import { getEnumeratingItemsAllowedCodes } from './recordEnumeratingItemsExpressionEvaluator';
18
18
  import { RecordUpdateResult } from './recordUpdateResult';
@@ -103,7 +103,8 @@ export const addDescendants = (params) => __awaiter(void 0, void 0, void 0, func
103
103
  const { survey, record, nodeDef } = params;
104
104
  const updateResult = new RecordUpdateResult({ record });
105
105
  if (NodeDefs.isEntity(nodeDef)) {
106
- const childDefs = getNodeDefChildren({ survey, nodeDef });
106
+ // node defs not in the record cycle have no nodes
107
+ const childDefs = getNodeDefChildren({ survey, nodeDef }).filter(NodeDefs.isInCycle(getCycle(record)));
107
108
  // Add only child single nodes (it allows to apply default values)
108
109
  for (const childDef of childDefs) {
109
110
  // sequential: each iteration updates the record
@@ -1 +1 @@
1
- {"version":3,"file":"recordNodesCreator.js","sourceRoot":"","sources":["../../../../src/record/recordNodesUpdater/recordNodesCreator.ts"],"names":[],"mappings":"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","sourcesContent":["import { User } from '../../auth'\nimport { SystemError, SystemErrorKeys } from '../../error'\nimport { CategoryItem } from '../../category'\nimport { ArenaRecordNode, NodeFactory, Nodes } from '../../node'\nimport { NodeValues } from '../../node/nodeValues'\nimport { NodeDef, NodeDefCode, NodeDefEntity, NodeDefType, NodeDefs } from '../../nodeDef'\nimport { CategoryItemProvider } from '../../nodeDefExpressionEvaluator/categoryItemProvider'\nimport { Survey } from '../../survey'\nimport { getNodeDefChildren, getNodeDefEnumerator } from '../../survey/surveys/nodeDefs'\nimport { getAncestorCodeAttributes } from '../_records/recordGetters'\nimport { getEnumeratingCategoryItems } from '../_records/recordUtils'\nimport { getEnumeratingItemsAllowedCodes } from './recordEnumeratingItemsExpressionEvaluator'\nimport { RecordExpressionEvaluationContext } from './recordExpressionEvaluationContext'\nimport { RecordUpdateResult } from './recordUpdateResult'\n\nexport type NodesUpdateParams = RecordExpressionEvaluationContext & {\n dateModified?: string\n clearNonApplicableValues?: boolean\n}\n\nexport type NodeCreateParams = NodesUpdateParams & {\n parentNode?: ArenaRecordNode\n nodeDef: NodeDef<any>\n createMultipleEntities?: boolean\n}\n\nconst getNodesToInsertCount = (params: { parentNode: ArenaRecordNode | undefined; nodeDef: NodeDef<any> }): number => {\n const { nodeDef, parentNode } = params\n if (!parentNode || NodeDefs.isSingle(nodeDef)) return 1\n if (nodeDef.type === NodeDefType.code) return 0 // never create nodes for multiple code attributes\n\n if (NodeDefs.isMultipleEntity(nodeDef) && !NodeDefs.isAutoCreateMinCountItems(nodeDef)) return 0\n\n const minCount = Nodes.getChildrenMinCount({ parentNode, nodeDef })\n if (Number.isNaN(minCount) || minCount <= 0) return 0\n return minCount\n}\n\nexport const createEnumeratedEntityNode = async (params: {\n user: User\n survey: Survey\n parentNode: ArenaRecordNode\n nodeDef: NodeDefEntity\n enumeratorDef: NodeDefCode\n categoryItem: CategoryItem\n updateResult: RecordUpdateResult\n sideEffect?: boolean\n}): Promise<void> => {\n const { user, survey, parentNode, nodeDef, enumeratorDef, categoryItem, updateResult, sideEffect } = params\n\n const { record } = updateResult\n\n const childUpdateResult = await addNodeAndDescendants({\n user,\n survey,\n record: updateResult.record,\n parentNode,\n nodeDef,\n sideEffect,\n })\n const enumeratorNode = Object.values(childUpdateResult.nodes).find((node) => node.nodeDefUuid === enumeratorDef.uuid)\n if (!enumeratorNode) {\n throw new SystemError(SystemErrorKeys.record.enumeratorNodeNotFound, {\n recordUuid: record.uuid,\n entityDef: NodeDefs.getName(nodeDef),\n enumerator: NodeDefs.getName(enumeratorDef),\n })\n }\n enumeratorNode.value = NodeValues.newCodeValue({ itemUuid: categoryItem.uuid })\n enumeratorNode.meta = { ...enumeratorNode.meta, defaultValueApplied: true }\n enumeratorNode.refData = { categoryItem }\n\n updateResult.merge(childUpdateResult)\n}\n\nexport const createEnumeratedEntityNodes = async (params: {\n user: User\n survey: Survey\n parentNode: ArenaRecordNode\n nodeDef: NodeDefEntity\n updateResult: RecordUpdateResult\n categoryItemProvider?: CategoryItemProvider\n sideEffect?: boolean\n}): Promise<boolean> => {\n const { user, survey, parentNode, nodeDef, updateResult, categoryItemProvider } = params\n\n const enumeratorDef = getNodeDefEnumerator({ survey, entityDef: nodeDef })\n if (!enumeratorDef) return false\n\n const allowedCodes = await getEnumeratingItemsAllowedCodes({\n survey,\n user,\n record: updateResult.record,\n entityDef: nodeDef,\n parentNode,\n })\n\n const categoryItems = await getEnumeratingCategoryItems({\n survey,\n enumeratorDef,\n parentNode,\n categoryItemProvider,\n allowedCodes,\n })(updateResult.record)\n if (categoryItems.length === 0) return false\n\n for (const categoryItem of categoryItems) {\n // sequential: each iteration updates the record\n await createEnumeratedEntityNode({ ...params, enumeratorDef, categoryItem }) // NOSONAR\n }\n return true\n}\n\nconst createChildNodesBasedOnMinCount = async (\n params: NodeCreateParams & { updateResult: RecordUpdateResult }\n): Promise<void> => {\n const { parentNode, nodeDef, updateResult, createMultipleEntities = true } = params\n\n if (NodeDefs.isMultipleEntity(nodeDef) && NodeDefs.isEnumerate(nodeDef)) {\n if (await createEnumeratedEntityNodes({ ...params, parentNode: parentNode! })) {\n return\n }\n }\n\n const nodesToInsertCount = getNodesToInsertCount({ parentNode, nodeDef })\n if (\n nodesToInsertCount === 0 ||\n (NodeDefs.isMultipleEntity(nodeDef) &&\n (!createMultipleEntities || (parentNode && !Nodes.isChildApplicable(parentNode, nodeDef.uuid))))\n ) {\n return // do nothing\n }\n for (let index = 0; index < nodesToInsertCount; index++) {\n // sequential: each iteration updates the record\n const childUpdateResult = await addNodeAndDescendants({ ...params, record: updateResult.record }) // NOSONAR\n updateResult.merge(childUpdateResult)\n }\n}\n\nexport const addDescendants = async (params: NodeCreateParams): Promise<RecordUpdateResult> => {\n const { survey, record, nodeDef } = params\n\n const updateResult = new RecordUpdateResult({ record })\n\n if (NodeDefs.isEntity(nodeDef)) {\n const childDefs = getNodeDefChildren({ survey, nodeDef })\n\n // Add only child single nodes (it allows to apply default values)\n for (const childDef of childDefs) {\n // sequential: each iteration updates the record\n await createChildNodesBasedOnMinCount({ ...params, updateResult, nodeDef: childDef }) // NOSONAR\n }\n }\n return updateResult\n}\n\nexport const addNodeAndDescendants = async (params: NodeCreateParams): Promise<RecordUpdateResult> => {\n const { survey, record, parentNode, nodeDef, sideEffect = false } = params\n\n const node = NodeFactory.createInstance({\n nodeDefUuid: nodeDef.uuid,\n recordUuid: record.uuid,\n parentNode,\n surveyUuid: survey.uuid,\n })\n if (parentNode && nodeDef.type === NodeDefType.code && NodeDefs.getParentCodeDefUuid(nodeDef as NodeDefCode)) {\n // meta.hCode indexes the node as dependent of its ancestor code attributes (cleared when they change)\n const ancestorCodeAttributes = getAncestorCodeAttributes(survey, parentNode, nodeDef as NodeDefCode)(record)\n node.meta = { ...node.meta, hCode: ancestorCodeAttributes.map((codeAttribute) => codeAttribute.uuid) }\n }\n\n const updateResult = new RecordUpdateResult({ record })\n updateResult.addNode(node, { sideEffect })\n\n // Add children if entity\n if (NodeDefs.isEntity(nodeDef)) {\n const descendantsUpdateResult = await addDescendants({\n ...params,\n record: updateResult.record,\n parentNode: node,\n })\n updateResult.merge(descendantsUpdateResult)\n }\n return updateResult\n}\n"]}
1
+ {"version":3,"file":"recordNodesCreator.js","sourceRoot":"","sources":["../../../../src/record/recordNodesUpdater/recordNodesCreator.ts"],"names":[],"mappings":"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","sourcesContent":["import { User } from '../../auth'\nimport { SystemError, SystemErrorKeys } from '../../error'\nimport { CategoryItem } from '../../category'\nimport { ArenaRecordNode, NodeFactory, Nodes } from '../../node'\nimport { NodeValues } from '../../node/nodeValues'\nimport { NodeDef, NodeDefCode, NodeDefEntity, NodeDefType, NodeDefs } from '../../nodeDef'\nimport { CategoryItemProvider } from '../../nodeDefExpressionEvaluator/categoryItemProvider'\nimport { Survey } from '../../survey'\nimport { getNodeDefChildren, getNodeDefEnumerator } from '../../survey/surveys/nodeDefs'\nimport { getAncestorCodeAttributes, getCycle } from '../_records/recordGetters'\nimport { getEnumeratingCategoryItems } from '../_records/recordUtils'\nimport { getEnumeratingItemsAllowedCodes } from './recordEnumeratingItemsExpressionEvaluator'\nimport { RecordExpressionEvaluationContext } from './recordExpressionEvaluationContext'\nimport { RecordUpdateResult } from './recordUpdateResult'\n\nexport type NodesUpdateParams = RecordExpressionEvaluationContext & {\n dateModified?: string\n clearNonApplicableValues?: boolean\n}\n\nexport type NodeCreateParams = NodesUpdateParams & {\n parentNode?: ArenaRecordNode\n nodeDef: NodeDef<any>\n createMultipleEntities?: boolean\n}\n\nconst getNodesToInsertCount = (params: { parentNode: ArenaRecordNode | undefined; nodeDef: NodeDef<any> }): number => {\n const { nodeDef, parentNode } = params\n if (!parentNode || NodeDefs.isSingle(nodeDef)) return 1\n if (nodeDef.type === NodeDefType.code) return 0 // never create nodes for multiple code attributes\n\n if (NodeDefs.isMultipleEntity(nodeDef) && !NodeDefs.isAutoCreateMinCountItems(nodeDef)) return 0\n\n const minCount = Nodes.getChildrenMinCount({ parentNode, nodeDef })\n if (Number.isNaN(minCount) || minCount <= 0) return 0\n return minCount\n}\n\nexport const createEnumeratedEntityNode = async (params: {\n user: User\n survey: Survey\n parentNode: ArenaRecordNode\n nodeDef: NodeDefEntity\n enumeratorDef: NodeDefCode\n categoryItem: CategoryItem\n updateResult: RecordUpdateResult\n sideEffect?: boolean\n}): Promise<void> => {\n const { user, survey, parentNode, nodeDef, enumeratorDef, categoryItem, updateResult, sideEffect } = params\n\n const { record } = updateResult\n\n const childUpdateResult = await addNodeAndDescendants({\n user,\n survey,\n record: updateResult.record,\n parentNode,\n nodeDef,\n sideEffect,\n })\n const enumeratorNode = Object.values(childUpdateResult.nodes).find((node) => node.nodeDefUuid === enumeratorDef.uuid)\n if (!enumeratorNode) {\n throw new SystemError(SystemErrorKeys.record.enumeratorNodeNotFound, {\n recordUuid: record.uuid,\n entityDef: NodeDefs.getName(nodeDef),\n enumerator: NodeDefs.getName(enumeratorDef),\n })\n }\n enumeratorNode.value = NodeValues.newCodeValue({ itemUuid: categoryItem.uuid })\n enumeratorNode.meta = { ...enumeratorNode.meta, defaultValueApplied: true }\n enumeratorNode.refData = { categoryItem }\n\n updateResult.merge(childUpdateResult)\n}\n\nexport const createEnumeratedEntityNodes = async (params: {\n user: User\n survey: Survey\n parentNode: ArenaRecordNode\n nodeDef: NodeDefEntity\n updateResult: RecordUpdateResult\n categoryItemProvider?: CategoryItemProvider\n sideEffect?: boolean\n}): Promise<boolean> => {\n const { user, survey, parentNode, nodeDef, updateResult, categoryItemProvider } = params\n\n const enumeratorDef = getNodeDefEnumerator({ survey, entityDef: nodeDef })\n if (!enumeratorDef) return false\n\n const allowedCodes = await getEnumeratingItemsAllowedCodes({\n survey,\n user,\n record: updateResult.record,\n entityDef: nodeDef,\n parentNode,\n })\n\n const categoryItems = await getEnumeratingCategoryItems({\n survey,\n enumeratorDef,\n parentNode,\n categoryItemProvider,\n allowedCodes,\n })(updateResult.record)\n if (categoryItems.length === 0) return false\n\n for (const categoryItem of categoryItems) {\n // sequential: each iteration updates the record\n await createEnumeratedEntityNode({ ...params, enumeratorDef, categoryItem }) // NOSONAR\n }\n return true\n}\n\nconst createChildNodesBasedOnMinCount = async (\n params: NodeCreateParams & { updateResult: RecordUpdateResult }\n): Promise<void> => {\n const { parentNode, nodeDef, updateResult, createMultipleEntities = true } = params\n\n if (NodeDefs.isMultipleEntity(nodeDef) && NodeDefs.isEnumerate(nodeDef)) {\n if (await createEnumeratedEntityNodes({ ...params, parentNode: parentNode! })) {\n return\n }\n }\n\n const nodesToInsertCount = getNodesToInsertCount({ parentNode, nodeDef })\n if (\n nodesToInsertCount === 0 ||\n (NodeDefs.isMultipleEntity(nodeDef) &&\n (!createMultipleEntities || (parentNode && !Nodes.isChildApplicable(parentNode, nodeDef.uuid))))\n ) {\n return // do nothing\n }\n for (let index = 0; index < nodesToInsertCount; index++) {\n // sequential: each iteration updates the record\n const childUpdateResult = await addNodeAndDescendants({ ...params, record: updateResult.record }) // NOSONAR\n updateResult.merge(childUpdateResult)\n }\n}\n\nexport const addDescendants = async (params: NodeCreateParams): Promise<RecordUpdateResult> => {\n const { survey, record, nodeDef } = params\n\n const updateResult = new RecordUpdateResult({ record })\n\n if (NodeDefs.isEntity(nodeDef)) {\n // node defs not in the record cycle have no nodes\n const childDefs = getNodeDefChildren({ survey, nodeDef }).filter(NodeDefs.isInCycle(getCycle(record)))\n\n // Add only child single nodes (it allows to apply default values)\n for (const childDef of childDefs) {\n // sequential: each iteration updates the record\n await createChildNodesBasedOnMinCount({ ...params, updateResult, nodeDef: childDef }) // NOSONAR\n }\n }\n return updateResult\n}\n\nexport const addNodeAndDescendants = async (params: NodeCreateParams): Promise<RecordUpdateResult> => {\n const { survey, record, parentNode, nodeDef, sideEffect = false } = params\n\n const node = NodeFactory.createInstance({\n nodeDefUuid: nodeDef.uuid,\n recordUuid: record.uuid,\n parentNode,\n surveyUuid: survey.uuid,\n })\n if (parentNode && nodeDef.type === NodeDefType.code && NodeDefs.getParentCodeDefUuid(nodeDef as NodeDefCode)) {\n // meta.hCode indexes the node as dependent of its ancestor code attributes (cleared when they change)\n const ancestorCodeAttributes = getAncestorCodeAttributes(survey, parentNode, nodeDef as NodeDefCode)(record)\n node.meta = { ...node.meta, hCode: ancestorCodeAttributes.map((codeAttribute) => codeAttribute.uuid) }\n }\n\n const updateResult = new RecordUpdateResult({ record })\n updateResult.addNode(node, { sideEffect })\n\n // Add children if entity\n if (NodeDefs.isEntity(nodeDef)) {\n const descendantsUpdateResult = await addDescendants({\n ...params,\n record: updateResult.record,\n parentNode: node,\n })\n updateResult.merge(descendantsUpdateResult)\n }\n return updateResult\n}\n"]}
@@ -9,6 +9,8 @@ export class FileProcessor {
9
9
  // true while a chunk is being processed (resume must not start a second processing chain)
10
10
  this.processingChunk = false;
11
11
  this.completed = false;
12
+ // incremented when a processing run is stopped: callbacks of the chunks of previous runs are ignored
13
+ this.runId = 0;
12
14
  this.file = file;
13
15
  this.filePath = filePath;
14
16
  this.chunkProcessor = chunkProcessor;
@@ -22,7 +24,14 @@ export class FileProcessor {
22
24
  }
23
25
  this.reset();
24
26
  }
27
+ cancelCurrentRun() {
28
+ var _a;
29
+ this.runId += 1;
30
+ (_a = this.retryProcessor) === null || _a === void 0 ? void 0 : _a.stop();
31
+ this.retryProcessor = undefined;
32
+ }
25
33
  reset() {
34
+ this.cancelCurrentRun();
26
35
  this.running = false;
27
36
  this.totalChunks = 0;
28
37
  this.currentChunkNumber = 0;
@@ -47,14 +56,19 @@ export class FileProcessor {
47
56
  return Promise.resolve(file.slice((currentChunkNumber - 1) * chunkSize, currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize));
48
57
  }
49
58
  processNextChunk() {
50
- const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay } = this;
59
+ const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay, runId } = this;
60
+ const isStale = () => runId !== this.runId;
51
61
  this.processingChunk = true;
52
62
  this.extractCurrentFileChunk()
53
63
  .then((content) => {
64
+ if (isStale())
65
+ return;
54
66
  const retryProcessor = new RetryProcessor({
55
67
  processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),
56
68
  onSuccess: (chunkResult) => {
57
69
  var _a;
70
+ if (isStale())
71
+ return;
58
72
  this.processingChunk = false;
59
73
  if (this.currentChunkNumber < totalChunks) {
60
74
  // move to the next chunk also when paused: resume will continue from it
@@ -71,6 +85,8 @@ export class FileProcessor {
71
85
  }
72
86
  },
73
87
  onFail: (error) => {
88
+ if (isStale())
89
+ return;
74
90
  this.processingChunk = false;
75
91
  this.onFail(error);
76
92
  this.running = false;
@@ -78,20 +94,29 @@ export class FileProcessor {
78
94
  maxTryings,
79
95
  retryDelay,
80
96
  });
97
+ this.retryProcessor = retryProcessor;
81
98
  retryProcessor.start();
82
99
  })
83
100
  .catch((error) => {
101
+ if (isStale())
102
+ return;
84
103
  this.processingChunk = false;
85
104
  this.onFail(error);
86
105
  this.running = false;
87
106
  });
88
107
  }
89
108
  start(startFromChunk = 1) {
109
+ // a previous run still in progress must not continue in parallel
110
+ this.cancelCurrentRun();
111
+ const { runId } = this;
112
+ this.processingChunk = false;
90
113
  this.running = true;
91
114
  this.completed = false;
92
115
  this.currentChunkNumber = startFromChunk;
93
116
  this.calculateFileSize()
94
117
  .then((fileSize) => {
118
+ if (runId !== this.runId)
119
+ return;
95
120
  this.totalFileSize = fileSize;
96
121
  this.totalChunks = Math.ceil(fileSize / this.chunkSize);
97
122
  if (this.totalChunks > 0) {
@@ -99,6 +124,8 @@ export class FileProcessor {
99
124
  }
100
125
  })
101
126
  .catch((error) => {
127
+ if (runId !== this.runId)
128
+ return;
102
129
  this.onFail(error);
103
130
  this.running = false;
104
131
  });
@@ -1 +1 @@
1
- {"version":3,"file":"fileProcessor.js","sourceRoot":"","sources":["../../../src/utils/fileProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { RetryProcessor } from './retryProcessor'\n\ntype ChunkProcessor = (args: {\n chunk: number\n content: Blob | string | Uint8Array\n totalChunks: number\n totalFileSize: number\n}) => Promise<any>\n\nexport type FileProcessorConstructorArgs = {\n file?: File\n filePath?: string\n chunkProcessor: ChunkProcessor\n onError?: (error: Error) => void\n onComplete?: (result: any) => void\n chunkSize?: number\n maxTryings?: number\n /**\n * Delay (in milliseconds) before retrying a failed chunk, doubled at every further retry.\n */\n retryDelay?: number\n}\n\nexport class FileProcessor {\n static readonly defaultChunkSize = 1024 * 1024 * 10 // 10MB\n static readonly defaultMaxTryings = 5\n static readonly defaultRetryDelay = 1000 // 1s, then 2s, 4s...\n\n private readonly file?: File\n private readonly filePath?: string\n private readonly chunkProcessor: ChunkProcessor\n private readonly chunkSize: number\n private readonly maxTryings: number\n private readonly retryDelay: number\n private readonly onError?: (error: Error) => void\n private readonly onComplete?: (result?: any) => void\n\n // State properties\n protected totalFileSize: number = 0\n protected running: boolean = false\n protected totalChunks: number = 0\n protected currentChunkNumber: number = 0\n // true while a chunk is being processed (resume must not start a second processing chain)\n protected processingChunk: boolean = false\n protected completed: boolean = false\n\n constructor({\n file,\n filePath,\n chunkProcessor,\n onError,\n onComplete,\n chunkSize = FileProcessor.defaultChunkSize,\n maxTryings = FileProcessor.defaultMaxTryings,\n retryDelay = FileProcessor.defaultRetryDelay,\n }: FileProcessorConstructorArgs) {\n this.file = file\n this.filePath = filePath\n this.chunkProcessor = chunkProcessor\n this.chunkSize = chunkSize\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n this.onError = onError\n this.onComplete = onComplete\n\n if (!this.file && !this.filePath) {\n throw new Error('File or filePath not specified')\n }\n\n this.reset()\n }\n\n protected reset(): void {\n this.running = false\n this.totalChunks = 0\n this.currentChunkNumber = 0\n this.totalFileSize = 0\n this.processingChunk = false\n this.completed = false\n }\n\n protected calculateFileSize(): Promise<number> {\n return Promise.resolve(this.file?.size ?? 0)\n }\n\n protected onFail(error: Error): void {\n this.onError?.(error)\n }\n\n protected extractCurrentFileChunk(): Promise<Blob | string | Uint8Array> {\n const { file, currentChunkNumber, totalChunks, chunkSize } = this\n if (!file) {\n return Promise.reject(new Error('File property not initialized'))\n }\n return Promise.resolve(\n file.slice(\n (currentChunkNumber - 1) * chunkSize,\n currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize\n )\n )\n }\n\n protected processNextChunk(): void {\n const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay } = this\n\n this.processingChunk = true\n this.extractCurrentFileChunk()\n .then((content) => {\n const retryProcessor = new RetryProcessor<any>({\n processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),\n onSuccess: (chunkResult) => {\n this.processingChunk = false\n if (this.currentChunkNumber < totalChunks) {\n // move to the next chunk also when paused: resume will continue from it\n this.currentChunkNumber += 1\n if (this.running) {\n this.processNextChunk()\n }\n } else {\n this.running = false\n this.completed = true\n // called outside the retried processor: if it throws, the last chunk is not processed again\n this.onComplete?.(chunkResult)\n }\n },\n onFail: (error: Error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n },\n maxTryings,\n retryDelay,\n })\n retryProcessor.start()\n })\n .catch((error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n })\n }\n\n start(startFromChunk: number = 1): void {\n this.running = true\n this.completed = false\n this.currentChunkNumber = startFromChunk\n this.calculateFileSize()\n .then((fileSize) => {\n this.totalFileSize = fileSize\n this.totalChunks = Math.ceil(fileSize / this.chunkSize)\n if (this.totalChunks > 0) {\n this.processNextChunk()\n }\n })\n .catch((error) => {\n this.onFail(error)\n this.running = false\n })\n }\n\n stop(): void {\n this.reset()\n }\n\n pause(): void {\n this.running = false\n }\n\n resume(): void {\n if (this.running || this.completed) return\n this.running = true\n // if a chunk is still being processed, processing continues when it completes\n if (!this.processingChunk && this.totalChunks > 0) {\n this.processNextChunk()\n }\n }\n}\n"]}
1
+ {"version":3,"file":"fileProcessor.js","sourceRoot":"","sources":["../../../src/utils/fileProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { RetryProcessor } from './retryProcessor'\n\ntype ChunkProcessor = (args: {\n chunk: number\n content: Blob | string | Uint8Array\n totalChunks: number\n totalFileSize: number\n}) => Promise<any>\n\nexport type FileProcessorConstructorArgs = {\n file?: File\n filePath?: string\n chunkProcessor: ChunkProcessor\n onError?: (error: Error) => void\n onComplete?: (result: any) => void\n chunkSize?: number\n maxTryings?: number\n /**\n * Delay (in milliseconds) before retrying a failed chunk, doubled at every further retry.\n */\n retryDelay?: number\n}\n\nexport class FileProcessor {\n static readonly defaultChunkSize = 1024 * 1024 * 10 // 10MB\n static readonly defaultMaxTryings = 5\n static readonly defaultRetryDelay = 1000 // 1s, then 2s, 4s...\n\n private readonly file?: File\n private readonly filePath?: string\n private readonly chunkProcessor: ChunkProcessor\n private readonly chunkSize: number\n private readonly maxTryings: number\n private readonly retryDelay: number\n private readonly onError?: (error: Error) => void\n private readonly onComplete?: (result?: any) => void\n\n // State properties\n protected totalFileSize: number = 0\n protected running: boolean = false\n protected totalChunks: number = 0\n protected currentChunkNumber: number = 0\n // true while a chunk is being processed (resume must not start a second processing chain)\n protected processingChunk: boolean = false\n protected completed: boolean = false\n // incremented when a processing run is stopped: callbacks of the chunks of previous runs are ignored\n private runId: number = 0\n private retryProcessor?: RetryProcessor<any>\n\n constructor({\n file,\n filePath,\n chunkProcessor,\n onError,\n onComplete,\n chunkSize = FileProcessor.defaultChunkSize,\n maxTryings = FileProcessor.defaultMaxTryings,\n retryDelay = FileProcessor.defaultRetryDelay,\n }: FileProcessorConstructorArgs) {\n this.file = file\n this.filePath = filePath\n this.chunkProcessor = chunkProcessor\n this.chunkSize = chunkSize\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n this.onError = onError\n this.onComplete = onComplete\n\n if (!this.file && !this.filePath) {\n throw new Error('File or filePath not specified')\n }\n\n this.reset()\n }\n\n private cancelCurrentRun(): void {\n this.runId += 1\n this.retryProcessor?.stop()\n this.retryProcessor = undefined\n }\n\n protected reset(): void {\n this.cancelCurrentRun()\n this.running = false\n this.totalChunks = 0\n this.currentChunkNumber = 0\n this.totalFileSize = 0\n this.processingChunk = false\n this.completed = false\n }\n\n protected calculateFileSize(): Promise<number> {\n return Promise.resolve(this.file?.size ?? 0)\n }\n\n protected onFail(error: Error): void {\n this.onError?.(error)\n }\n\n protected extractCurrentFileChunk(): Promise<Blob | string | Uint8Array> {\n const { file, currentChunkNumber, totalChunks, chunkSize } = this\n if (!file) {\n return Promise.reject(new Error('File property not initialized'))\n }\n return Promise.resolve(\n file.slice(\n (currentChunkNumber - 1) * chunkSize,\n currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize\n )\n )\n }\n\n protected processNextChunk(): void {\n const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay, runId } = this\n const isStale = () => runId !== this.runId\n\n this.processingChunk = true\n this.extractCurrentFileChunk()\n .then((content) => {\n if (isStale()) return\n const retryProcessor = new RetryProcessor<any>({\n processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),\n onSuccess: (chunkResult) => {\n if (isStale()) return\n this.processingChunk = false\n if (this.currentChunkNumber < totalChunks) {\n // move to the next chunk also when paused: resume will continue from it\n this.currentChunkNumber += 1\n if (this.running) {\n this.processNextChunk()\n }\n } else {\n this.running = false\n this.completed = true\n // called outside the retried processor: if it throws, the last chunk is not processed again\n this.onComplete?.(chunkResult)\n }\n },\n onFail: (error: Error) => {\n if (isStale()) return\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n },\n maxTryings,\n retryDelay,\n })\n this.retryProcessor = retryProcessor\n retryProcessor.start()\n })\n .catch((error) => {\n if (isStale()) return\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n })\n }\n\n start(startFromChunk: number = 1): void {\n // a previous run still in progress must not continue in parallel\n this.cancelCurrentRun()\n const { runId } = this\n this.processingChunk = false\n this.running = true\n this.completed = false\n this.currentChunkNumber = startFromChunk\n this.calculateFileSize()\n .then((fileSize) => {\n if (runId !== this.runId) return\n this.totalFileSize = fileSize\n this.totalChunks = Math.ceil(fileSize / this.chunkSize)\n if (this.totalChunks > 0) {\n this.processNextChunk()\n }\n })\n .catch((error) => {\n if (runId !== this.runId) return\n this.onFail(error)\n this.running = false\n })\n }\n\n stop(): void {\n this.reset()\n }\n\n pause(): void {\n this.running = false\n }\n\n resume(): void {\n if (this.running || this.completed) return\n this.running = true\n // if a chunk is still being processed, processing continues when it completes\n if (!this.processingChunk && this.totalChunks > 0) {\n this.processNextChunk()\n }\n }\n}\n"]}
@@ -49,6 +49,7 @@ export class RetryProcessor {
49
49
  }
50
50
  // Public methods
51
51
  start() {
52
+ this.currentTrying = 0;
52
53
  this.running = true;
53
54
  this.processNext();
54
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"retryProcessor.js","sourceRoot":"","sources":["../../../src/utils/retryProcessor.ts"],"names":[],"mappings":"AAYA,MAAM,OAAO,GAAG,CAAC,KAAc,EAAS,EAAE,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAEtG,MAAM,OAAO,cAAc;IAWzB,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,EAAuC;QAJjH,mBAAmB;QACX,kBAAa,GAAW,CAAC,CAAA;QACzB,YAAO,GAAY,KAAK,CAAA;QAG9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAES,KAAK;QACb,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAES,WAAW;QACnB,IAAI,CAAC,aAAa,IAAI,CAAC,CAAA;QAEvB,2FAA2F;QAC3F,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACnB,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EACtC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACrD,CAAA;IACH,CAAC;IAEO,aAAa,CAAC,MAAuB;QAC3C,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,KAAY;QACvC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3D,kCAAkC;YAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAClB,OAAM;QACR,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,GAAG,SAAA,CAAC,EAAI,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA,CAAA;QAC7D,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,WAAW,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QACD,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,WAAW,EAAE,CAAA;YACpB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAA;IACX,CAAC;IAED,iBAAiB;IACjB,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;CACF","sourcesContent":["interface RetryProcessorArgs<ProcessorResult> {\n processor: () => Promise<ProcessorResult>\n onSuccess: (result: ProcessorResult) => void\n onFail: (error: Error) => void\n maxTryings?: number\n /**\n * Delay (in milliseconds) before the first retry, doubled at every further retry (exponential backoff).\n * Defaults to 0 (immediate retry).\n */\n retryDelay?: number\n}\n\nconst toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error)))\n\nexport class RetryProcessor<ProcessorResult> {\n private readonly processor: () => Promise<ProcessorResult>\n private readonly onSuccess: (result: ProcessorResult) => void\n private readonly onFail: (error: Error) => void\n private readonly maxTryings: number\n private readonly retryDelay: number\n\n // State properties\n private currentTrying: number = 0\n private running: boolean = false\n\n constructor({ processor, onSuccess, onFail, maxTryings = 2, retryDelay = 0 }: RetryProcessorArgs<ProcessorResult>) {\n this.processor = processor\n this.onSuccess = onSuccess\n this.onFail = onFail\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n }\n\n protected reset() {\n this.currentTrying = 0\n this.running = false\n }\n\n protected processNext(): void {\n this.currentTrying += 1\n\n // onSuccess runs outside the retried chain: if it throws, the processor must not run again\n this.processor().then(\n (result) => this.handleSuccess(result),\n (error) => this.handleProcessorError(toError(error))\n )\n }\n\n private handleSuccess(result: ProcessorResult): void {\n try {\n this.onSuccess(result)\n } catch (error) {\n this.onFail(toError(error))\n }\n }\n\n private handleProcessorError(error: Error): void {\n if (!this.running || this.currentTrying >= this.maxTryings) {\n // stopped or max attempts reached\n this.onFail(error)\n return\n }\n const delay = this.retryDelay * 2 ** (this.currentTrying - 1)\n if (delay <= 0) {\n this.processNext()\n return\n }\n setTimeout(() => {\n if (this.running) {\n this.processNext()\n } else {\n this.onFail(error)\n }\n }, delay)\n }\n\n // Public methods\n start(): void {\n this.running = true\n this.processNext()\n }\n\n stop(): void {\n this.reset()\n }\n}\n"]}
1
+ {"version":3,"file":"retryProcessor.js","sourceRoot":"","sources":["../../../src/utils/retryProcessor.ts"],"names":[],"mappings":"AAYA,MAAM,OAAO,GAAG,CAAC,KAAc,EAAS,EAAE,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAEtG,MAAM,OAAO,cAAc;IAWzB,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,EAAuC;QAJjH,mBAAmB;QACX,kBAAa,GAAW,CAAC,CAAA;QACzB,YAAO,GAAY,KAAK,CAAA;QAG9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAES,KAAK;QACb,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAES,WAAW;QACnB,IAAI,CAAC,aAAa,IAAI,CAAC,CAAA;QAEvB,2FAA2F;QAC3F,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACnB,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EACtC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACrD,CAAA;IACH,CAAC;IAEO,aAAa,CAAC,MAAuB;QAC3C,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,KAAY;QACvC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3D,kCAAkC;YAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAClB,OAAM;QACR,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,GAAG,SAAA,CAAC,EAAI,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA,CAAA;QAC7D,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,WAAW,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QACD,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,WAAW,EAAE,CAAA;YACpB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAA;IACX,CAAC;IAED,iBAAiB;IACjB,KAAK;QACH,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;CACF","sourcesContent":["interface RetryProcessorArgs<ProcessorResult> {\n processor: () => Promise<ProcessorResult>\n onSuccess: (result: ProcessorResult) => void\n onFail: (error: Error) => void\n maxTryings?: number\n /**\n * Delay (in milliseconds) before the first retry, doubled at every further retry (exponential backoff).\n * Defaults to 0 (immediate retry).\n */\n retryDelay?: number\n}\n\nconst toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error)))\n\nexport class RetryProcessor<ProcessorResult> {\n private readonly processor: () => Promise<ProcessorResult>\n private readonly onSuccess: (result: ProcessorResult) => void\n private readonly onFail: (error: Error) => void\n private readonly maxTryings: number\n private readonly retryDelay: number\n\n // State properties\n private currentTrying: number = 0\n private running: boolean = false\n\n constructor({ processor, onSuccess, onFail, maxTryings = 2, retryDelay = 0 }: RetryProcessorArgs<ProcessorResult>) {\n this.processor = processor\n this.onSuccess = onSuccess\n this.onFail = onFail\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n }\n\n protected reset() {\n this.currentTrying = 0\n this.running = false\n }\n\n protected processNext(): void {\n this.currentTrying += 1\n\n // onSuccess runs outside the retried chain: if it throws, the processor must not run again\n this.processor().then(\n (result) => this.handleSuccess(result),\n (error) => this.handleProcessorError(toError(error))\n )\n }\n\n private handleSuccess(result: ProcessorResult): void {\n try {\n this.onSuccess(result)\n } catch (error) {\n this.onFail(toError(error))\n }\n }\n\n private handleProcessorError(error: Error): void {\n if (!this.running || this.currentTrying >= this.maxTryings) {\n // stopped or max attempts reached\n this.onFail(error)\n return\n }\n const delay = this.retryDelay * 2 ** (this.currentTrying - 1)\n if (delay <= 0) {\n this.processNext()\n return\n }\n setTimeout(() => {\n if (this.running) {\n this.processNext()\n } else {\n this.onFail(error)\n }\n }, delay)\n }\n\n // Public methods\n start(): void {\n this.currentTrying = 0\n this.running = true\n this.processNext()\n }\n\n stop(): void {\n this.reset()\n }\n}\n"]}
@@ -52,7 +52,7 @@ const getChildEntityCompletionStats = (params) => {
52
52
  const getEntityCompletionStats = (params) => {
53
53
  const { survey, record, entity, includeNestedEntities = true } = params;
54
54
  const entityDef = survey_1.Surveys.getNodeDefByUuid({ survey, uuid: entity.nodeDefUuid });
55
- const childDefs = survey_1.Surveys.getNodeDefChildren({ survey, nodeDef: entityDef });
55
+ const childDefs = survey_1.Surveys.getNodeDefChildren({ survey, nodeDef: entityDef }).filter(nodeDef_1.NodeDefs.isInCycle((0, recordGetters_1.getCycle)(record)));
56
56
  const stats = emptyStats();
57
57
  for (const childDef of childDefs) {
58
58
  if (!node_1.Nodes.isChildApplicable(entity, childDef.uuid))
@@ -1 +1 @@
1
- {"version":3,"file":"recordCompletion.js","sourceRoot":"","sources":["../../../src/record/_records/recordCompletion.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, Nodes } from '../../node'\nimport { NodeDef, NodeDefProps, NodeDefs, NodeDefType } from '../../nodeDef'\nimport { Survey, Surveys } from '../../survey'\nimport { Numbers } from '../../utils'\nimport { ArenaRecord } from '../record'\nimport { getChildren, getRoot } from './recordGetters'\n\nexport interface EntityCompletionStats {\n total: number\n filled: number\n}\n\nconst emptyStats = (): EntityCompletionStats => ({ total: 0, filled: 0 })\n\nconst addStats = (statsAcc: EntityCompletionStats, statsToAdd: EntityCompletionStats): void => {\n statsAcc.total += statsToAdd.total\n statsAcc.filled += statsToAdd.filled\n}\n\nconst isAttributeRequired = (entity: ArenaRecordNode, nodeDef: NodeDef<NodeDefType, NodeDefProps>): boolean =>\n NodeDefs.isKey(nodeDef) ||\n NodeDefs.isRequired(nodeDef) ||\n (NodeDefs.isRequiredIfDefined(nodeDef) && Nodes.isChildRequired(entity, nodeDef.uuid))\n\nconst getAttributeCompletionStats = (params: {\n record: ArenaRecord\n entity: ArenaRecordNode\n nodeDef: NodeDef<NodeDefType, NodeDefProps>\n}): EntityCompletionStats => {\n const { record, entity, nodeDef } = params\n\n if (NodeDefs.isMultiple(nodeDef)) {\n const minCount = Nodes.getChildrenMinCount({ parentNode: entity, nodeDef })\n if (Number.isNaN(minCount) || minCount <= 0) return emptyStats()\n\n const filledNodesCount = getChildren(entity, nodeDef.uuid)(record).filter(Nodes.isValueNotBlank).length\n\n return { total: minCount, filled: Math.min(filledNodesCount, minCount) }\n }\n\n if (!isAttributeRequired(entity, nodeDef)) return emptyStats()\n\n const node = getChildren(entity, nodeDef.uuid)(record)[0]\n return { total: 1, filled: node && Nodes.isValueNotBlank(node) ? 1 : 0 }\n}\n\nconst getChildEntityCompletionStats = (params: {\n survey: Survey\n record: ArenaRecord\n parentEntity: ArenaRecordNode\n nodeDef: NodeDef<NodeDefType, NodeDefProps>\n}): EntityCompletionStats => {\n const { survey, record, parentEntity, nodeDef } = params\n const childEntities = getChildren(parentEntity, nodeDef.uuid)(record)\n\n if (NodeDefs.isSingle(nodeDef)) {\n const childEntity = childEntities[0]\n return childEntity ? getEntityCompletionStats({ survey, record, entity: childEntity }) : emptyStats()\n }\n\n const minCountRaw = Nodes.getChildrenMinCount({ parentNode: parentEntity, nodeDef })\n const minCount = Number.isNaN(minCountRaw) ? 0 : Math.max(minCountRaw, 0)\n // entities added beyond the min count are still expected to be complete\n const expectedCount = Math.max(minCount, childEntities.length)\n if (expectedCount === 0) return emptyStats()\n\n const stats = emptyStats()\n for (let index = 0; index < expectedCount; index++) {\n const childEntity = childEntities[index]\n // a missing repetition (below the min count) counts as a single incomplete unit\n addStats(\n stats,\n childEntity ? getEntityCompletionStats({ survey, record, entity: childEntity }) : { total: 1, filled: 0 }\n )\n }\n return stats\n}\n\nexport const getEntityCompletionStats = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n includeNestedEntities?: boolean\n}): EntityCompletionStats => {\n const { survey, record, entity, includeNestedEntities = true } = params\n const entityDef = Surveys.getNodeDefByUuid({ survey, uuid: entity.nodeDefUuid })\n const childDefs = Surveys.getNodeDefChildren({ survey, nodeDef: entityDef })\n\n const stats = emptyStats()\n for (const childDef of childDefs) {\n if (!Nodes.isChildApplicable(entity, childDef.uuid)) continue\n\n if (NodeDefs.isEntity(childDef)) {\n if (!includeNestedEntities) continue\n addStats(stats, getChildEntityCompletionStats({ survey, record, parentEntity: entity, nodeDef: childDef }))\n } else {\n addStats(stats, getAttributeCompletionStats({ record, entity, nodeDef: childDef }))\n }\n }\n return stats\n}\n\nconst toCompletionPercent = (stats: EntityCompletionStats): number => {\n const { total, filled } = stats\n if (total <= 0) return 100\n return Numbers.roundToPrecision((filled / total) * 100, 2)\n}\n\n/**\n * Returns the percentage of completion of the given entity (0-100), considering every descendant recursively.\n * A required or key attribute is considered filled when it has a non empty value; multiple attributes and\n * entities are considered filled based on their min count (see Nodes.getChildrenMinCount).\n */\nexport const getEntityCompletionPercent = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n}): number => toCompletionPercent(getEntityCompletionStats(params))\n\n/**\n * Returns the percentage of completion of the given entity (0-100), considering only its own attributes\n * (nested entities are ignored).\n */\nexport const getEntityOwnCompletionPercent = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n}): number => toCompletionPercent(getEntityCompletionStats({ ...params, includeNestedEntities: false }))\n\n/**\n * Returns the percentage of completion of the given record (0-100), i.e. the completion of its root entity.\n */\nexport const getRecordCompletionPercent = (params: { survey: Survey; record: ArenaRecord }): number => {\n const { survey, record } = params\n const rootEntity = getRoot(record)\n return rootEntity ? getEntityCompletionPercent({ survey, record, entity: rootEntity }) : 0\n}\n"]}
1
+ {"version":3,"file":"recordCompletion.js","sourceRoot":"","sources":["../../../src/record/_records/recordCompletion.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, Nodes } from '../../node'\nimport { NodeDef, NodeDefProps, NodeDefs, NodeDefType } from '../../nodeDef'\nimport { Survey, Surveys } from '../../survey'\nimport { Numbers } from '../../utils'\nimport { ArenaRecord } from '../record'\nimport { getChildren, getCycle, getRoot } from './recordGetters'\n\nexport interface EntityCompletionStats {\n total: number\n filled: number\n}\n\nconst emptyStats = (): EntityCompletionStats => ({ total: 0, filled: 0 })\n\nconst addStats = (statsAcc: EntityCompletionStats, statsToAdd: EntityCompletionStats): void => {\n statsAcc.total += statsToAdd.total\n statsAcc.filled += statsToAdd.filled\n}\n\nconst isAttributeRequired = (entity: ArenaRecordNode, nodeDef: NodeDef<NodeDefType, NodeDefProps>): boolean =>\n NodeDefs.isKey(nodeDef) ||\n NodeDefs.isRequired(nodeDef) ||\n (NodeDefs.isRequiredIfDefined(nodeDef) && Nodes.isChildRequired(entity, nodeDef.uuid))\n\nconst getAttributeCompletionStats = (params: {\n record: ArenaRecord\n entity: ArenaRecordNode\n nodeDef: NodeDef<NodeDefType, NodeDefProps>\n}): EntityCompletionStats => {\n const { record, entity, nodeDef } = params\n\n if (NodeDefs.isMultiple(nodeDef)) {\n const minCount = Nodes.getChildrenMinCount({ parentNode: entity, nodeDef })\n if (Number.isNaN(minCount) || minCount <= 0) return emptyStats()\n\n const filledNodesCount = getChildren(entity, nodeDef.uuid)(record).filter(Nodes.isValueNotBlank).length\n\n return { total: minCount, filled: Math.min(filledNodesCount, minCount) }\n }\n\n if (!isAttributeRequired(entity, nodeDef)) return emptyStats()\n\n const node = getChildren(entity, nodeDef.uuid)(record)[0]\n return { total: 1, filled: node && Nodes.isValueNotBlank(node) ? 1 : 0 }\n}\n\nconst getChildEntityCompletionStats = (params: {\n survey: Survey\n record: ArenaRecord\n parentEntity: ArenaRecordNode\n nodeDef: NodeDef<NodeDefType, NodeDefProps>\n}): EntityCompletionStats => {\n const { survey, record, parentEntity, nodeDef } = params\n const childEntities = getChildren(parentEntity, nodeDef.uuid)(record)\n\n if (NodeDefs.isSingle(nodeDef)) {\n const childEntity = childEntities[0]\n return childEntity ? getEntityCompletionStats({ survey, record, entity: childEntity }) : emptyStats()\n }\n\n const minCountRaw = Nodes.getChildrenMinCount({ parentNode: parentEntity, nodeDef })\n const minCount = Number.isNaN(minCountRaw) ? 0 : Math.max(minCountRaw, 0)\n // entities added beyond the min count are still expected to be complete\n const expectedCount = Math.max(minCount, childEntities.length)\n if (expectedCount === 0) return emptyStats()\n\n const stats = emptyStats()\n for (let index = 0; index < expectedCount; index++) {\n const childEntity = childEntities[index]\n // a missing repetition (below the min count) counts as a single incomplete unit\n addStats(\n stats,\n childEntity ? getEntityCompletionStats({ survey, record, entity: childEntity }) : { total: 1, filled: 0 }\n )\n }\n return stats\n}\n\nexport const getEntityCompletionStats = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n includeNestedEntities?: boolean\n}): EntityCompletionStats => {\n const { survey, record, entity, includeNestedEntities = true } = params\n const entityDef = Surveys.getNodeDefByUuid({ survey, uuid: entity.nodeDefUuid })\n const childDefs = Surveys.getNodeDefChildren({ survey, nodeDef: entityDef }).filter(\n NodeDefs.isInCycle(getCycle(record))\n )\n\n const stats = emptyStats()\n for (const childDef of childDefs) {\n if (!Nodes.isChildApplicable(entity, childDef.uuid)) continue\n\n if (NodeDefs.isEntity(childDef)) {\n if (!includeNestedEntities) continue\n addStats(stats, getChildEntityCompletionStats({ survey, record, parentEntity: entity, nodeDef: childDef }))\n } else {\n addStats(stats, getAttributeCompletionStats({ record, entity, nodeDef: childDef }))\n }\n }\n return stats\n}\n\nconst toCompletionPercent = (stats: EntityCompletionStats): number => {\n const { total, filled } = stats\n if (total <= 0) return 100\n return Numbers.roundToPrecision((filled / total) * 100, 2)\n}\n\n/**\n * Returns the percentage of completion of the given entity (0-100), considering every descendant recursively.\n * A required or key attribute is considered filled when it has a non empty value; multiple attributes and\n * entities are considered filled based on their min count (see Nodes.getChildrenMinCount).\n */\nexport const getEntityCompletionPercent = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n}): number => toCompletionPercent(getEntityCompletionStats(params))\n\n/**\n * Returns the percentage of completion of the given entity (0-100), considering only its own attributes\n * (nested entities are ignored).\n */\nexport const getEntityOwnCompletionPercent = (params: {\n survey: Survey\n record: ArenaRecord\n entity: ArenaRecordNode\n}): number => toCompletionPercent(getEntityCompletionStats({ ...params, includeNestedEntities: false }))\n\n/**\n * Returns the percentage of completion of the given record (0-100), i.e. the completion of its root entity.\n */\nexport const getRecordCompletionPercent = (params: { survey: Survey; record: ArenaRecord }): number => {\n const { survey, record } = params\n const rootEntity = getRoot(record)\n return rootEntity ? getEntityCompletionPercent({ survey, record, entity: rootEntity }) : 0\n}\n"]}
@@ -133,9 +133,9 @@ const removeExcludedNodes = (params) => {
133
133
  const { survey, record, sideEffect } = params;
134
134
  const cycle = record.cycle;
135
135
  const result = new recordNodesUpdater_1.RecordUpdateResult({ record });
136
+ // all the node defs are visited (not only the ones in the cycle): nodes of the ones not in the cycle must be removed
136
137
  survey_1.Surveys.visitDescendantsAndSelfNodeDef({
137
138
  survey,
138
- cycle,
139
139
  nodeDef: survey_1.Surveys.getNodeDefRoot({ survey }),
140
140
  visitor: (nodeDef) => {
141
141
  const hasAncestorCodeDefExcludedInClone = nodeDef.type === nodeDef_1.NodeDefType.code &&
@@ -1 +1 @@
1
- {"version":3,"file":"recordCloner.js","sourceRoot":"","sources":["../../src/record/recordCloner.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, NodeValues, Nodes } from '../node'\nimport { NodeKeys, NodeMetaKeys } from '../node/node'\nimport { NodeDefCode, NodeDefType, NodeDefs } from '../nodeDef'\nimport { Survey, Surveys } from '../survey'\nimport { Dates, Objects, UUIDs } from '../utils'\nimport { Validation } from '../validation'\nimport { ArenaRecord } from './record'\nimport { RecordFixer } from './recordFixer'\nimport { RecordUpdateResult } from './recordNodesUpdater'\nimport { RecordValidations } from './recordValidations'\nimport { Records } from './records'\n\ntype OldUuidToNewUuidMap = {\n [key: string]: string\n}\n\nconst assignNewUuidsToNodes = (params: {\n record: ArenaRecord\n sideEffect: boolean\n}): { newNodeUuidsByOldUuid: OldUuidToNewUuidMap; newFileUuidsByOldUuid: OldUuidToNewUuidMap; record: ArenaRecord } => {\n const { record, sideEffect } = params\n\n // generate a new uuid for each node\n const newNodeUuidsByOldUuid: OldUuidToNewUuidMap = {}\n const newFileUuidsByOldUuid: OldUuidToNewUuidMap = {}\n\n // sort nodes before removing the id, to preserve their hierarchy (faster than comparing each meta.h property)\n const nodesArray = Records.getNodesArray(record).sort(\n (nodeA: ArenaRecordNode, nodeB: ArenaRecordNode): number =>\n Nodes.getHierarchy(nodeA).length - Nodes.getHierarchy(nodeB).length\n )\n\n const recordUpdated = sideEffect ? record : { ...record }\n\n const nodes = recordUpdated.nodes ?? {}\n const nodesUpdated = sideEffect ? nodes : { ...nodes }\n\n // new uuids generated in advance: meta.hCode can reference nodes cloned later (e.g. sibling code attributes)\n for (const node of nodesArray) {\n newNodeUuidsByOldUuid[node.uuid] = UUIDs.v4()\n }\n\n // update nodes recordUuid and properties (it does side effect on nodes)\n nodesArray.forEach((node) => {\n let nodeUpdated = sideEffect ? node : { ...node }\n nodeUpdated.recordUuid = record.uuid\n nodeUpdated.dateCreated = nodeUpdated.dateModified = Dates.nowFormattedForStorage()\n\n const oldUuid = node.uuid\n const newUuid = newNodeUuidsByOldUuid[oldUuid]\n nodeUpdated.uuid = newUuid\n // consider every node as just created node\n delete nodeUpdated.id\n nodeUpdated.created = true // this flag will be used by the RDB generator)\n\n // update internal node uuids, meta hierarchy, file uuid (if any)\n const parentUuid = node.parentUuid\n if (parentUuid) {\n nodeUpdated.parentUuid = newNodeUuidsByOldUuid[parentUuid]\n }\n const hierarchy = node.meta?.h ?? []\n if (hierarchy.length > 0) {\n const hierarchyUpdated = hierarchy.map((ancestorUuid) => newNodeUuidsByOldUuid[ancestorUuid])\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.meta, NodeMetaKeys.h],\n value: hierarchyUpdated,\n sideEffect,\n })\n }\n const hierarchyCode = Nodes.getHierarchyCode(node)\n if (hierarchyCode.length > 0) {\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.meta, NodeMetaKeys.hCode],\n value: hierarchyCode.map((ancestorCodeUuid) => newNodeUuidsByOldUuid[ancestorCodeUuid]),\n sideEffect,\n })\n }\n // assign new file uuid (file should be cloned elsewhere)\n const fileUuid = NodeValues.getFileUuid(node)\n if (fileUuid) {\n const newFileUuid = UUIDs.v4()\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.value, NodeValues.ValuePropsFile.fileUuid],\n value: newFileUuid,\n })\n newFileUuidsByOldUuid[fileUuid] = newFileUuid\n }\n\n delete nodesUpdated[oldUuid]\n nodesUpdated[newUuid] = nodeUpdated\n })\n\n recordUpdated.nodes = nodesUpdated\n\n return { newNodeUuidsByOldUuid, newFileUuidsByOldUuid, record: recordUpdated }\n}\n\nconst assignNewUuidsToValidation = (params: {\n validation: Validation\n newNodeUuidsByOldUuid: OldUuidToNewUuidMap\n sideEffect: boolean\n}): Validation => {\n const { validation, newNodeUuidsByOldUuid, sideEffect } = params\n\n const validationFields = validation?.fields\n if (!validationFields) return validation\n\n const validationFieldsUpdated = sideEffect ? validationFields : { ...validationFields }\n\n Object.entries(validationFields).forEach(([oldFieldKey, validationField]) => {\n let newFieldKey\n if (oldFieldKey.startsWith(RecordValidations.prefixValidationFieldChildrenCount)) {\n const oldParentUuid = oldFieldKey.substring(\n RecordValidations.prefixValidationFieldChildrenCount.length,\n oldFieldKey.lastIndexOf('_')\n )\n const newParentUuid = newNodeUuidsByOldUuid[oldParentUuid]\n newFieldKey = oldFieldKey.replace(oldParentUuid, newParentUuid)\n } else {\n newFieldKey = newNodeUuidsByOldUuid[oldFieldKey]\n }\n if (newFieldKey) {\n validationFieldsUpdated[newFieldKey] = validationField\n }\n delete validationFieldsUpdated[oldFieldKey]\n })\n return Objects.assoc({\n obj: validation,\n prop: 'fields',\n value: validationFieldsUpdated,\n sideEffect,\n })\n}\n\nconst assignNewUuids = (params: {\n record: ArenaRecord\n sideEffect: boolean\n}): { newNodeUuidsByOldUuid: OldUuidToNewUuidMap; newFileUuidsByOldUuid: OldUuidToNewUuidMap; record: ArenaRecord } => {\n const { record, sideEffect } = params\n let recordUpdated = sideEffect ? record : { ...record }\n recordUpdated.uuid = UUIDs.v4()\n\n const {\n newNodeUuidsByOldUuid,\n newFileUuidsByOldUuid,\n record: recordUpdatedUuids,\n } = assignNewUuidsToNodes({ record: recordUpdated, sideEffect })\n\n recordUpdated = recordUpdatedUuids\n\n const { validation } = recordUpdated\n if (validation) {\n const validationUpdated = assignNewUuidsToValidation({\n validation,\n newNodeUuidsByOldUuid,\n sideEffect,\n })\n recordUpdated = Objects.assoc({ obj: recordUpdated, prop: 'validation', value: validationUpdated, sideEffect })\n }\n // re-build nodes index\n const nodes = recordUpdated.nodes!\n delete recordUpdated._nodesIndex\n delete recordUpdated.nodes\n recordUpdated = Records.addNodes(nodes, { sideEffect, sortNodes: true })(recordUpdated)\n\n return { newNodeUuidsByOldUuid, newFileUuidsByOldUuid, record: recordUpdated }\n}\n\nconst removeExcludedNodes = (params: {\n survey: Survey\n record: ArenaRecord\n sideEffect: boolean\n}): RecordUpdateResult => {\n const { survey, record, sideEffect } = params\n const cycle = record.cycle!\n const result = new RecordUpdateResult({ record })\n Surveys.visitDescendantsAndSelfNodeDef({\n survey,\n cycle,\n nodeDef: Surveys.getNodeDefRoot({ survey }),\n visitor: (nodeDef) => {\n const hasAncestorCodeDefExcludedInClone =\n nodeDef.type === NodeDefType.code &&\n Surveys.getNodeDefAncestorCodes({ survey, nodeDef: nodeDef as NodeDefCode }).some(NodeDefs.isExcludedInClone)\n\n if (\n !NodeDefs.isInCycle(cycle)(nodeDef) ||\n NodeDefs.isExcludedInClone(nodeDef) ||\n hasAncestorCodeDefExcludedInClone\n ) {\n const nodesToDelete = Records.getNodesByDefUuid(nodeDef.uuid)(result.record)\n if (nodesToDelete.length > 0) {\n const partialUpdateResult = Records.removeNodes(\n nodesToDelete.map((node) => node.uuid),\n { sideEffect }\n )(result.record)\n result.merge(partialUpdateResult)\n }\n }\n },\n })\n return result\n}\n\nconst cloneRecord = (params: { survey: Survey; record: ArenaRecord; cycleTo: string; sideEffect?: boolean }) => {\n const { survey, record, cycleTo, sideEffect = false } = params\n\n const recordUpdated = sideEffect ? record : { ...record }\n recordUpdated.cycle = cycleTo\n recordUpdated.dateCreated = recordUpdated.dateModified = Dates.nowFormattedForStorage()\n delete recordUpdated.id\n\n // delete nodes not included in clone\n const updateResult = new RecordUpdateResult({ record: recordUpdated })\n updateResult.merge(removeExcludedNodes({ survey, record: updateResult.record, sideEffect }))\n updateResult.merge(RecordFixer.insertMissingSingleNodes({ survey, record: updateResult.record, sideEffect }))\n\n // assign new UUIDs with side effect on record and nodes, faster when record is big\n const {\n newNodeUuidsByOldUuid,\n newFileUuidsByOldUuid,\n record: recordUpdatedUuids,\n } = assignNewUuids({ record: updateResult.record, sideEffect })\n\n updateResult.merge(new RecordUpdateResult({ record: recordUpdatedUuids }))\n\n return { record: updateResult.record, newNodeUuidsByOldUuid, newFileUuidsByOldUuid }\n}\n\nexport const RecordCloner = {\n cloneRecord,\n}\n"]}
1
+ {"version":3,"file":"recordCloner.js","sourceRoot":"","sources":["../../src/record/recordCloner.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, NodeValues, Nodes } from '../node'\nimport { NodeKeys, NodeMetaKeys } from '../node/node'\nimport { NodeDefCode, NodeDefType, NodeDefs } from '../nodeDef'\nimport { Survey, Surveys } from '../survey'\nimport { Dates, Objects, UUIDs } from '../utils'\nimport { Validation } from '../validation'\nimport { ArenaRecord } from './record'\nimport { RecordFixer } from './recordFixer'\nimport { RecordUpdateResult } from './recordNodesUpdater'\nimport { RecordValidations } from './recordValidations'\nimport { Records } from './records'\n\ntype OldUuidToNewUuidMap = {\n [key: string]: string\n}\n\nconst assignNewUuidsToNodes = (params: {\n record: ArenaRecord\n sideEffect: boolean\n}): { newNodeUuidsByOldUuid: OldUuidToNewUuidMap; newFileUuidsByOldUuid: OldUuidToNewUuidMap; record: ArenaRecord } => {\n const { record, sideEffect } = params\n\n // generate a new uuid for each node\n const newNodeUuidsByOldUuid: OldUuidToNewUuidMap = {}\n const newFileUuidsByOldUuid: OldUuidToNewUuidMap = {}\n\n // sort nodes before removing the id, to preserve their hierarchy (faster than comparing each meta.h property)\n const nodesArray = Records.getNodesArray(record).sort(\n (nodeA: ArenaRecordNode, nodeB: ArenaRecordNode): number =>\n Nodes.getHierarchy(nodeA).length - Nodes.getHierarchy(nodeB).length\n )\n\n const recordUpdated = sideEffect ? record : { ...record }\n\n const nodes = recordUpdated.nodes ?? {}\n const nodesUpdated = sideEffect ? nodes : { ...nodes }\n\n // new uuids generated in advance: meta.hCode can reference nodes cloned later (e.g. sibling code attributes)\n for (const node of nodesArray) {\n newNodeUuidsByOldUuid[node.uuid] = UUIDs.v4()\n }\n\n // update nodes recordUuid and properties (it does side effect on nodes)\n nodesArray.forEach((node) => {\n let nodeUpdated = sideEffect ? node : { ...node }\n nodeUpdated.recordUuid = record.uuid\n nodeUpdated.dateCreated = nodeUpdated.dateModified = Dates.nowFormattedForStorage()\n\n const oldUuid = node.uuid\n const newUuid = newNodeUuidsByOldUuid[oldUuid]\n nodeUpdated.uuid = newUuid\n // consider every node as just created node\n delete nodeUpdated.id\n nodeUpdated.created = true // this flag will be used by the RDB generator)\n\n // update internal node uuids, meta hierarchy, file uuid (if any)\n const parentUuid = node.parentUuid\n if (parentUuid) {\n nodeUpdated.parentUuid = newNodeUuidsByOldUuid[parentUuid]\n }\n const hierarchy = node.meta?.h ?? []\n if (hierarchy.length > 0) {\n const hierarchyUpdated = hierarchy.map((ancestorUuid) => newNodeUuidsByOldUuid[ancestorUuid])\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.meta, NodeMetaKeys.h],\n value: hierarchyUpdated,\n sideEffect,\n })\n }\n const hierarchyCode = Nodes.getHierarchyCode(node)\n if (hierarchyCode.length > 0) {\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.meta, NodeMetaKeys.hCode],\n value: hierarchyCode.map((ancestorCodeUuid) => newNodeUuidsByOldUuid[ancestorCodeUuid]),\n sideEffect,\n })\n }\n // assign new file uuid (file should be cloned elsewhere)\n const fileUuid = NodeValues.getFileUuid(node)\n if (fileUuid) {\n const newFileUuid = UUIDs.v4()\n nodeUpdated = Objects.assocPath({\n obj: nodeUpdated,\n path: [NodeKeys.value, NodeValues.ValuePropsFile.fileUuid],\n value: newFileUuid,\n })\n newFileUuidsByOldUuid[fileUuid] = newFileUuid\n }\n\n delete nodesUpdated[oldUuid]\n nodesUpdated[newUuid] = nodeUpdated\n })\n\n recordUpdated.nodes = nodesUpdated\n\n return { newNodeUuidsByOldUuid, newFileUuidsByOldUuid, record: recordUpdated }\n}\n\nconst assignNewUuidsToValidation = (params: {\n validation: Validation\n newNodeUuidsByOldUuid: OldUuidToNewUuidMap\n sideEffect: boolean\n}): Validation => {\n const { validation, newNodeUuidsByOldUuid, sideEffect } = params\n\n const validationFields = validation?.fields\n if (!validationFields) return validation\n\n const validationFieldsUpdated = sideEffect ? validationFields : { ...validationFields }\n\n Object.entries(validationFields).forEach(([oldFieldKey, validationField]) => {\n let newFieldKey\n if (oldFieldKey.startsWith(RecordValidations.prefixValidationFieldChildrenCount)) {\n const oldParentUuid = oldFieldKey.substring(\n RecordValidations.prefixValidationFieldChildrenCount.length,\n oldFieldKey.lastIndexOf('_')\n )\n const newParentUuid = newNodeUuidsByOldUuid[oldParentUuid]\n newFieldKey = oldFieldKey.replace(oldParentUuid, newParentUuid)\n } else {\n newFieldKey = newNodeUuidsByOldUuid[oldFieldKey]\n }\n if (newFieldKey) {\n validationFieldsUpdated[newFieldKey] = validationField\n }\n delete validationFieldsUpdated[oldFieldKey]\n })\n return Objects.assoc({\n obj: validation,\n prop: 'fields',\n value: validationFieldsUpdated,\n sideEffect,\n })\n}\n\nconst assignNewUuids = (params: {\n record: ArenaRecord\n sideEffect: boolean\n}): { newNodeUuidsByOldUuid: OldUuidToNewUuidMap; newFileUuidsByOldUuid: OldUuidToNewUuidMap; record: ArenaRecord } => {\n const { record, sideEffect } = params\n let recordUpdated = sideEffect ? record : { ...record }\n recordUpdated.uuid = UUIDs.v4()\n\n const {\n newNodeUuidsByOldUuid,\n newFileUuidsByOldUuid,\n record: recordUpdatedUuids,\n } = assignNewUuidsToNodes({ record: recordUpdated, sideEffect })\n\n recordUpdated = recordUpdatedUuids\n\n const { validation } = recordUpdated\n if (validation) {\n const validationUpdated = assignNewUuidsToValidation({\n validation,\n newNodeUuidsByOldUuid,\n sideEffect,\n })\n recordUpdated = Objects.assoc({ obj: recordUpdated, prop: 'validation', value: validationUpdated, sideEffect })\n }\n // re-build nodes index\n const nodes = recordUpdated.nodes!\n delete recordUpdated._nodesIndex\n delete recordUpdated.nodes\n recordUpdated = Records.addNodes(nodes, { sideEffect, sortNodes: true })(recordUpdated)\n\n return { newNodeUuidsByOldUuid, newFileUuidsByOldUuid, record: recordUpdated }\n}\n\nconst removeExcludedNodes = (params: {\n survey: Survey\n record: ArenaRecord\n sideEffect: boolean\n}): RecordUpdateResult => {\n const { survey, record, sideEffect } = params\n const cycle = record.cycle!\n const result = new RecordUpdateResult({ record })\n // all the node defs are visited (not only the ones in the cycle): nodes of the ones not in the cycle must be removed\n Surveys.visitDescendantsAndSelfNodeDef({\n survey,\n nodeDef: Surveys.getNodeDefRoot({ survey }),\n visitor: (nodeDef) => {\n const hasAncestorCodeDefExcludedInClone =\n nodeDef.type === NodeDefType.code &&\n Surveys.getNodeDefAncestorCodes({ survey, nodeDef: nodeDef as NodeDefCode }).some(NodeDefs.isExcludedInClone)\n\n if (\n !NodeDefs.isInCycle(cycle)(nodeDef) ||\n NodeDefs.isExcludedInClone(nodeDef) ||\n hasAncestorCodeDefExcludedInClone\n ) {\n const nodesToDelete = Records.getNodesByDefUuid(nodeDef.uuid)(result.record)\n if (nodesToDelete.length > 0) {\n const partialUpdateResult = Records.removeNodes(\n nodesToDelete.map((node) => node.uuid),\n { sideEffect }\n )(result.record)\n result.merge(partialUpdateResult)\n }\n }\n },\n })\n return result\n}\n\nconst cloneRecord = (params: { survey: Survey; record: ArenaRecord; cycleTo: string; sideEffect?: boolean }) => {\n const { survey, record, cycleTo, sideEffect = false } = params\n\n const recordUpdated = sideEffect ? record : { ...record }\n recordUpdated.cycle = cycleTo\n recordUpdated.dateCreated = recordUpdated.dateModified = Dates.nowFormattedForStorage()\n delete recordUpdated.id\n\n // delete nodes not included in clone\n const updateResult = new RecordUpdateResult({ record: recordUpdated })\n updateResult.merge(removeExcludedNodes({ survey, record: updateResult.record, sideEffect }))\n updateResult.merge(RecordFixer.insertMissingSingleNodes({ survey, record: updateResult.record, sideEffect }))\n\n // assign new UUIDs with side effect on record and nodes, faster when record is big\n const {\n newNodeUuidsByOldUuid,\n newFileUuidsByOldUuid,\n record: recordUpdatedUuids,\n } = assignNewUuids({ record: updateResult.record, sideEffect })\n\n updateResult.merge(new RecordUpdateResult({ record: recordUpdatedUuids }))\n\n return { record: updateResult.record, newNodeUuidsByOldUuid, newFileUuidsByOldUuid }\n}\n\nexport const RecordCloner = {\n cloneRecord,\n}\n"]}
@@ -48,11 +48,13 @@ const insertMissingSingleNode = (params) => {
48
48
  const insertMissingSingleNodes = (params) => {
49
49
  const { survey, record, sideEffect } = params;
50
50
  const updateResult = new recordNodesUpdater_1.RecordUpdateResult({ record });
51
+ const cycle = records_1.Records.getCycle(record);
51
52
  survey_1.Surveys.visitNodeDefs({
52
53
  survey,
53
54
  visitor: (nodeDef) => {
54
55
  const parentDefUuid = nodeDef.parentUuid;
55
- if (parentDefUuid) {
56
+ // node defs not in the record cycle have no nodes
57
+ if (parentDefUuid && nodeDef_1.NodeDefs.isInCycle(cycle)(nodeDef)) {
56
58
  const parentNodes = records_1.Records.getNodesByDefUuid(parentDefUuid)(updateResult.record);
57
59
  for (const parentNode of parentNodes) {
58
60
  const partialUpdateResult = insertMissingSingleNode({
@@ -1 +1 @@
1
- {"version":3,"file":"recordFixer.js","sourceRoot":"","sources":["../../src/record/recordFixer.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, NodeFactory, Nodes } from '../node'\nimport { NodeDef, NodeDefCode, NodeDefs, NodeDefType } from '../nodeDef'\nimport { Survey, Surveys } from '../survey'\nimport { Objects } from '../utils'\nimport type { ArenaRecord } from './record'\nimport { RecordUpdateResult } from './recordNodesUpdater'\nimport { Records } from './records'\n\nconst fixCodeAttribute = (params: {\n survey: Survey\n nodeDef: NodeDefCode\n record: ArenaRecord\n node: ArenaRecordNode\n sideEffect: boolean\n}): ArenaRecordNode => {\n const { survey, nodeDef, record, node, sideEffect } = params\n if (!NodeDefs.getParentCodeDefUuid(nodeDef) || Objects.isNotEmpty(Nodes.getHierarchyCode(node))) {\n // nodeDef is not a code attribute or meta.hCode already populated: do nothing\n return node\n }\n const parentNode = Records.getParent(node)(record)\n if (!parentNode) {\n // missing parent node; node parentUuid could be invalid\n return node\n }\n // populate meta.hCode with ancestor code attribute node uuids\n const hCode = Records.getAncestorCodeAttributes(\n survey,\n parentNode,\n nodeDef\n )(record).map((codeAttribute) => codeAttribute.uuid)\n const nodeUpdated = sideEffect ? node : { ...node }\n nodeUpdated.meta = { ...nodeUpdated.meta, hCode }\n return nodeUpdated\n}\n\nconst insertMissingSingleNode = (params: {\n survey: Survey\n nodeDef: NodeDef<any>\n record: ArenaRecord\n parentNode: ArenaRecordNode\n sideEffect: boolean\n}): RecordUpdateResult | null => {\n const { survey, nodeDef, record, parentNode, sideEffect } = params\n if (!NodeDefs.isSingle(nodeDef)) {\n // multiple node: don't insert it\n return null\n }\n const nodeDefUuid = nodeDef.uuid\n const children = Records.getChildren(parentNode, nodeDef.uuid)(record)\n if (!Objects.isEmpty(children)) {\n // single node already inserted\n return null\n }\n // insert missing single node\n const recordUuid = record.uuid\n let node = NodeFactory.createInstance({ nodeDefUuid, recordUuid, parentNode })\n\n if (nodeDef.type === NodeDefType.code) {\n node = fixCodeAttribute({ survey, nodeDef: nodeDef as NodeDefCode, record, node, sideEffect })\n }\n\n const recordUpdated = Records.addNode(node, { sideEffect })(record)\n return new RecordUpdateResult({ record: recordUpdated, nodes: { [node.uuid]: node } })\n}\n\nconst insertMissingSingleNodes = (params: {\n survey: Survey\n record: ArenaRecord\n sideEffect: boolean\n}): RecordUpdateResult => {\n const { survey, record, sideEffect } = params\n const updateResult = new RecordUpdateResult({ record })\n Surveys.visitNodeDefs({\n survey,\n visitor: (nodeDef) => {\n const parentDefUuid = nodeDef.parentUuid\n if (parentDefUuid) {\n const parentNodes = Records.getNodesByDefUuid(parentDefUuid)(updateResult.record)\n for (const parentNode of parentNodes) {\n const partialUpdateResult = insertMissingSingleNode({\n survey,\n nodeDef,\n record: updateResult.record,\n parentNode,\n sideEffect,\n })\n if (partialUpdateResult) {\n updateResult.merge(partialUpdateResult)\n }\n }\n }\n },\n })\n return updateResult\n}\n\nconst deleteNodesByDefUuid = (params: { record: ArenaRecord; nodeDefUuid: string; sideEffect: boolean }) => {\n const { record, nodeDefUuid, sideEffect } = params\n const updateResult = new RecordUpdateResult({ record })\n\n const recordUpdateOptions = { sideEffect }\n\n const nodesToDelete = Records.getNodesByDefUuid(nodeDefUuid)(updateResult.record)\n for (const nodeToDelete of nodesToDelete) {\n // cleanup child applicability\n const parentNode = Records.getParent(nodeToDelete)(updateResult.record)\n if (parentNode && !Nodes.isChildApplicable(parentNode, nodeDefUuid)) {\n const parentNodeUpdated = Nodes.dissocChildApplicability(parentNode, nodeDefUuid)\n const recordWithParentNodeUpdated = Records.addNode(parentNodeUpdated, recordUpdateOptions)(updateResult.record)\n updateResult.merge(new RecordUpdateResult({ record: recordWithParentNodeUpdated }))\n }\n }\n const nodeUuidsToDelete = nodesToDelete.map((node) => node.uuid)\n const nodesDeleteUpdateResult = Records.removeNodes(nodeUuidsToDelete, recordUpdateOptions)(updateResult.record)\n updateResult.merge(nodesDeleteUpdateResult)\n\n return updateResult\n}\n\n/**\n * Fix a record by:\n * - inserting missing single nodes\n * - deleting nodes with non existing node defs\n * - removing status flags (created, deleted, updated) from all nodes\n */\nconst fixRecord = (params: { survey: Survey; record: ArenaRecord; sideEffect?: boolean }): RecordUpdateResult => {\n const { survey, record, sideEffect = false } = params\n const result = new RecordUpdateResult({ record })\n\n // 1. delete nodes with non existing node defs (and their descendants), once per missing node def\n const missingNodeDefUuids = new Set<string>()\n for (const { nodeDefUuid } of Records.getNodesArray(record)) {\n if (!Surveys.findNodeDefByUuid({ survey, uuid: nodeDefUuid })) {\n missingNodeDefUuids.add(nodeDefUuid)\n }\n }\n for (const nodeDefUuid of missingNodeDefUuids) {\n result.merge(deleteNodesByDefUuid({ record: result.record, nodeDefUuid, sideEffect }))\n }\n\n // 2. fix the remaining nodes (read from the updated record, without the deleted nodes); add them all at once\n const nodesUpdated: { [key: string]: ArenaRecordNode } = {}\n for (const node of Records.getNodesArray(result.record)) {\n const nodeDef = Surveys.getNodeDefByUuid({ survey, uuid: node.nodeDefUuid })\n // remove status flags\n let nodeUpdated = Nodes.removeStatusFlags({ node, sideEffect })\n\n if (nodeDef.type === NodeDefType.code) {\n nodeUpdated = fixCodeAttribute({\n survey,\n nodeDef: nodeDef as NodeDefCode,\n record: result.record,\n node: nodeUpdated,\n sideEffect,\n })\n }\n nodesUpdated[nodeUpdated.uuid] = nodeUpdated\n }\n const recordWithNodesUpdated = Records.addNodes(nodesUpdated, { sideEffect })(result.record)\n result.merge(new RecordUpdateResult({ record: recordWithNodesUpdated, nodes: nodesUpdated }))\n\n const missingNodesUpdateResult = insertMissingSingleNodes({ survey, record: result.record, sideEffect })\n result.merge(missingNodesUpdateResult)\n return result\n}\n\nexport const RecordFixer = {\n fixRecord,\n insertMissingSingleNodes,\n}\n"]}
1
+ {"version":3,"file":"recordFixer.js","sourceRoot":"","sources":["../../src/record/recordFixer.ts"],"names":[],"mappings":"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","sourcesContent":["import { ArenaRecordNode, NodeFactory, Nodes } from '../node'\nimport { NodeDef, NodeDefCode, NodeDefs, NodeDefType } from '../nodeDef'\nimport { Survey, Surveys } from '../survey'\nimport { Objects } from '../utils'\nimport type { ArenaRecord } from './record'\nimport { RecordUpdateResult } from './recordNodesUpdater'\nimport { Records } from './records'\n\nconst fixCodeAttribute = (params: {\n survey: Survey\n nodeDef: NodeDefCode\n record: ArenaRecord\n node: ArenaRecordNode\n sideEffect: boolean\n}): ArenaRecordNode => {\n const { survey, nodeDef, record, node, sideEffect } = params\n if (!NodeDefs.getParentCodeDefUuid(nodeDef) || Objects.isNotEmpty(Nodes.getHierarchyCode(node))) {\n // nodeDef is not a code attribute or meta.hCode already populated: do nothing\n return node\n }\n const parentNode = Records.getParent(node)(record)\n if (!parentNode) {\n // missing parent node; node parentUuid could be invalid\n return node\n }\n // populate meta.hCode with ancestor code attribute node uuids\n const hCode = Records.getAncestorCodeAttributes(\n survey,\n parentNode,\n nodeDef\n )(record).map((codeAttribute) => codeAttribute.uuid)\n const nodeUpdated = sideEffect ? node : { ...node }\n nodeUpdated.meta = { ...nodeUpdated.meta, hCode }\n return nodeUpdated\n}\n\nconst insertMissingSingleNode = (params: {\n survey: Survey\n nodeDef: NodeDef<any>\n record: ArenaRecord\n parentNode: ArenaRecordNode\n sideEffect: boolean\n}): RecordUpdateResult | null => {\n const { survey, nodeDef, record, parentNode, sideEffect } = params\n if (!NodeDefs.isSingle(nodeDef)) {\n // multiple node: don't insert it\n return null\n }\n const nodeDefUuid = nodeDef.uuid\n const children = Records.getChildren(parentNode, nodeDef.uuid)(record)\n if (!Objects.isEmpty(children)) {\n // single node already inserted\n return null\n }\n // insert missing single node\n const recordUuid = record.uuid\n let node = NodeFactory.createInstance({ nodeDefUuid, recordUuid, parentNode })\n\n if (nodeDef.type === NodeDefType.code) {\n node = fixCodeAttribute({ survey, nodeDef: nodeDef as NodeDefCode, record, node, sideEffect })\n }\n\n const recordUpdated = Records.addNode(node, { sideEffect })(record)\n return new RecordUpdateResult({ record: recordUpdated, nodes: { [node.uuid]: node } })\n}\n\nconst insertMissingSingleNodes = (params: {\n survey: Survey\n record: ArenaRecord\n sideEffect: boolean\n}): RecordUpdateResult => {\n const { survey, record, sideEffect } = params\n const updateResult = new RecordUpdateResult({ record })\n const cycle = Records.getCycle(record)\n Surveys.visitNodeDefs({\n survey,\n visitor: (nodeDef) => {\n const parentDefUuid = nodeDef.parentUuid\n // node defs not in the record cycle have no nodes\n if (parentDefUuid && NodeDefs.isInCycle(cycle)(nodeDef)) {\n const parentNodes = Records.getNodesByDefUuid(parentDefUuid)(updateResult.record)\n for (const parentNode of parentNodes) {\n const partialUpdateResult = insertMissingSingleNode({\n survey,\n nodeDef,\n record: updateResult.record,\n parentNode,\n sideEffect,\n })\n if (partialUpdateResult) {\n updateResult.merge(partialUpdateResult)\n }\n }\n }\n },\n })\n return updateResult\n}\n\nconst deleteNodesByDefUuid = (params: { record: ArenaRecord; nodeDefUuid: string; sideEffect: boolean }) => {\n const { record, nodeDefUuid, sideEffect } = params\n const updateResult = new RecordUpdateResult({ record })\n\n const recordUpdateOptions = { sideEffect }\n\n const nodesToDelete = Records.getNodesByDefUuid(nodeDefUuid)(updateResult.record)\n for (const nodeToDelete of nodesToDelete) {\n // cleanup child applicability\n const parentNode = Records.getParent(nodeToDelete)(updateResult.record)\n if (parentNode && !Nodes.isChildApplicable(parentNode, nodeDefUuid)) {\n const parentNodeUpdated = Nodes.dissocChildApplicability(parentNode, nodeDefUuid)\n const recordWithParentNodeUpdated = Records.addNode(parentNodeUpdated, recordUpdateOptions)(updateResult.record)\n updateResult.merge(new RecordUpdateResult({ record: recordWithParentNodeUpdated }))\n }\n }\n const nodeUuidsToDelete = nodesToDelete.map((node) => node.uuid)\n const nodesDeleteUpdateResult = Records.removeNodes(nodeUuidsToDelete, recordUpdateOptions)(updateResult.record)\n updateResult.merge(nodesDeleteUpdateResult)\n\n return updateResult\n}\n\n/**\n * Fix a record by:\n * - inserting missing single nodes\n * - deleting nodes with non existing node defs\n * - removing status flags (created, deleted, updated) from all nodes\n */\nconst fixRecord = (params: { survey: Survey; record: ArenaRecord; sideEffect?: boolean }): RecordUpdateResult => {\n const { survey, record, sideEffect = false } = params\n const result = new RecordUpdateResult({ record })\n\n // 1. delete nodes with non existing node defs (and their descendants), once per missing node def\n const missingNodeDefUuids = new Set<string>()\n for (const { nodeDefUuid } of Records.getNodesArray(record)) {\n if (!Surveys.findNodeDefByUuid({ survey, uuid: nodeDefUuid })) {\n missingNodeDefUuids.add(nodeDefUuid)\n }\n }\n for (const nodeDefUuid of missingNodeDefUuids) {\n result.merge(deleteNodesByDefUuid({ record: result.record, nodeDefUuid, sideEffect }))\n }\n\n // 2. fix the remaining nodes (read from the updated record, without the deleted nodes); add them all at once\n const nodesUpdated: { [key: string]: ArenaRecordNode } = {}\n for (const node of Records.getNodesArray(result.record)) {\n const nodeDef = Surveys.getNodeDefByUuid({ survey, uuid: node.nodeDefUuid })\n // remove status flags\n let nodeUpdated = Nodes.removeStatusFlags({ node, sideEffect })\n\n if (nodeDef.type === NodeDefType.code) {\n nodeUpdated = fixCodeAttribute({\n survey,\n nodeDef: nodeDef as NodeDefCode,\n record: result.record,\n node: nodeUpdated,\n sideEffect,\n })\n }\n nodesUpdated[nodeUpdated.uuid] = nodeUpdated\n }\n const recordWithNodesUpdated = Records.addNodes(nodesUpdated, { sideEffect })(result.record)\n result.merge(new RecordUpdateResult({ record: recordWithNodesUpdated, nodes: nodesUpdated }))\n\n const missingNodesUpdateResult = insertMissingSingleNodes({ survey, record: result.record, sideEffect })\n result.merge(missingNodesUpdateResult)\n return result\n}\n\nexport const RecordFixer = {\n fixRecord,\n insertMissingSingleNodes,\n}\n"]}
@@ -108,7 +108,8 @@ const addDescendants = (params) => __awaiter(void 0, void 0, void 0, function* (
108
108
  const { survey, record, nodeDef } = params;
109
109
  const updateResult = new recordUpdateResult_1.RecordUpdateResult({ record });
110
110
  if (nodeDef_1.NodeDefs.isEntity(nodeDef)) {
111
- const childDefs = (0, nodeDefs_1.getNodeDefChildren)({ survey, nodeDef });
111
+ // node defs not in the record cycle have no nodes
112
+ const childDefs = (0, nodeDefs_1.getNodeDefChildren)({ survey, nodeDef }).filter(nodeDef_1.NodeDefs.isInCycle((0, recordGetters_1.getCycle)(record)));
112
113
  // Add only child single nodes (it allows to apply default values)
113
114
  for (const childDef of childDefs) {
114
115
  // sequential: each iteration updates the record
@@ -1 +1 @@
1
- {"version":3,"file":"recordNodesCreator.js","sourceRoot":"","sources":["../../../src/record/recordNodesUpdater/recordNodesCreator.ts"],"names":[],"mappings":"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","sourcesContent":["import { User } from '../../auth'\nimport { SystemError, SystemErrorKeys } from '../../error'\nimport { CategoryItem } from '../../category'\nimport { ArenaRecordNode, NodeFactory, Nodes } from '../../node'\nimport { NodeValues } from '../../node/nodeValues'\nimport { NodeDef, NodeDefCode, NodeDefEntity, NodeDefType, NodeDefs } from '../../nodeDef'\nimport { CategoryItemProvider } from '../../nodeDefExpressionEvaluator/categoryItemProvider'\nimport { Survey } from '../../survey'\nimport { getNodeDefChildren, getNodeDefEnumerator } from '../../survey/surveys/nodeDefs'\nimport { getAncestorCodeAttributes } from '../_records/recordGetters'\nimport { getEnumeratingCategoryItems } from '../_records/recordUtils'\nimport { getEnumeratingItemsAllowedCodes } from './recordEnumeratingItemsExpressionEvaluator'\nimport { RecordExpressionEvaluationContext } from './recordExpressionEvaluationContext'\nimport { RecordUpdateResult } from './recordUpdateResult'\n\nexport type NodesUpdateParams = RecordExpressionEvaluationContext & {\n dateModified?: string\n clearNonApplicableValues?: boolean\n}\n\nexport type NodeCreateParams = NodesUpdateParams & {\n parentNode?: ArenaRecordNode\n nodeDef: NodeDef<any>\n createMultipleEntities?: boolean\n}\n\nconst getNodesToInsertCount = (params: { parentNode: ArenaRecordNode | undefined; nodeDef: NodeDef<any> }): number => {\n const { nodeDef, parentNode } = params\n if (!parentNode || NodeDefs.isSingle(nodeDef)) return 1\n if (nodeDef.type === NodeDefType.code) return 0 // never create nodes for multiple code attributes\n\n if (NodeDefs.isMultipleEntity(nodeDef) && !NodeDefs.isAutoCreateMinCountItems(nodeDef)) return 0\n\n const minCount = Nodes.getChildrenMinCount({ parentNode, nodeDef })\n if (Number.isNaN(minCount) || minCount <= 0) return 0\n return minCount\n}\n\nexport const createEnumeratedEntityNode = async (params: {\n user: User\n survey: Survey\n parentNode: ArenaRecordNode\n nodeDef: NodeDefEntity\n enumeratorDef: NodeDefCode\n categoryItem: CategoryItem\n updateResult: RecordUpdateResult\n sideEffect?: boolean\n}): Promise<void> => {\n const { user, survey, parentNode, nodeDef, enumeratorDef, categoryItem, updateResult, sideEffect } = params\n\n const { record } = updateResult\n\n const childUpdateResult = await addNodeAndDescendants({\n user,\n survey,\n record: updateResult.record,\n parentNode,\n nodeDef,\n sideEffect,\n })\n const enumeratorNode = Object.values(childUpdateResult.nodes).find((node) => node.nodeDefUuid === enumeratorDef.uuid)\n if (!enumeratorNode) {\n throw new SystemError(SystemErrorKeys.record.enumeratorNodeNotFound, {\n recordUuid: record.uuid,\n entityDef: NodeDefs.getName(nodeDef),\n enumerator: NodeDefs.getName(enumeratorDef),\n })\n }\n enumeratorNode.value = NodeValues.newCodeValue({ itemUuid: categoryItem.uuid })\n enumeratorNode.meta = { ...enumeratorNode.meta, defaultValueApplied: true }\n enumeratorNode.refData = { categoryItem }\n\n updateResult.merge(childUpdateResult)\n}\n\nexport const createEnumeratedEntityNodes = async (params: {\n user: User\n survey: Survey\n parentNode: ArenaRecordNode\n nodeDef: NodeDefEntity\n updateResult: RecordUpdateResult\n categoryItemProvider?: CategoryItemProvider\n sideEffect?: boolean\n}): Promise<boolean> => {\n const { user, survey, parentNode, nodeDef, updateResult, categoryItemProvider } = params\n\n const enumeratorDef = getNodeDefEnumerator({ survey, entityDef: nodeDef })\n if (!enumeratorDef) return false\n\n const allowedCodes = await getEnumeratingItemsAllowedCodes({\n survey,\n user,\n record: updateResult.record,\n entityDef: nodeDef,\n parentNode,\n })\n\n const categoryItems = await getEnumeratingCategoryItems({\n survey,\n enumeratorDef,\n parentNode,\n categoryItemProvider,\n allowedCodes,\n })(updateResult.record)\n if (categoryItems.length === 0) return false\n\n for (const categoryItem of categoryItems) {\n // sequential: each iteration updates the record\n await createEnumeratedEntityNode({ ...params, enumeratorDef, categoryItem }) // NOSONAR\n }\n return true\n}\n\nconst createChildNodesBasedOnMinCount = async (\n params: NodeCreateParams & { updateResult: RecordUpdateResult }\n): Promise<void> => {\n const { parentNode, nodeDef, updateResult, createMultipleEntities = true } = params\n\n if (NodeDefs.isMultipleEntity(nodeDef) && NodeDefs.isEnumerate(nodeDef)) {\n if (await createEnumeratedEntityNodes({ ...params, parentNode: parentNode! })) {\n return\n }\n }\n\n const nodesToInsertCount = getNodesToInsertCount({ parentNode, nodeDef })\n if (\n nodesToInsertCount === 0 ||\n (NodeDefs.isMultipleEntity(nodeDef) &&\n (!createMultipleEntities || (parentNode && !Nodes.isChildApplicable(parentNode, nodeDef.uuid))))\n ) {\n return // do nothing\n }\n for (let index = 0; index < nodesToInsertCount; index++) {\n // sequential: each iteration updates the record\n const childUpdateResult = await addNodeAndDescendants({ ...params, record: updateResult.record }) // NOSONAR\n updateResult.merge(childUpdateResult)\n }\n}\n\nexport const addDescendants = async (params: NodeCreateParams): Promise<RecordUpdateResult> => {\n const { survey, record, nodeDef } = params\n\n const updateResult = new RecordUpdateResult({ record })\n\n if (NodeDefs.isEntity(nodeDef)) {\n const childDefs = getNodeDefChildren({ survey, nodeDef })\n\n // Add only child single nodes (it allows to apply default values)\n for (const childDef of childDefs) {\n // sequential: each iteration updates the record\n await createChildNodesBasedOnMinCount({ ...params, updateResult, nodeDef: childDef }) // NOSONAR\n }\n }\n return updateResult\n}\n\nexport const addNodeAndDescendants = async (params: NodeCreateParams): Promise<RecordUpdateResult> => {\n const { survey, record, parentNode, nodeDef, sideEffect = false } = params\n\n const node = NodeFactory.createInstance({\n nodeDefUuid: nodeDef.uuid,\n recordUuid: record.uuid,\n parentNode,\n surveyUuid: survey.uuid,\n })\n if (parentNode && nodeDef.type === NodeDefType.code && NodeDefs.getParentCodeDefUuid(nodeDef as NodeDefCode)) {\n // meta.hCode indexes the node as dependent of its ancestor code attributes (cleared when they change)\n const ancestorCodeAttributes = getAncestorCodeAttributes(survey, parentNode, nodeDef as NodeDefCode)(record)\n node.meta = { ...node.meta, hCode: ancestorCodeAttributes.map((codeAttribute) => codeAttribute.uuid) }\n }\n\n const updateResult = new RecordUpdateResult({ record })\n updateResult.addNode(node, { sideEffect })\n\n // Add children if entity\n if (NodeDefs.isEntity(nodeDef)) {\n const descendantsUpdateResult = await addDescendants({\n ...params,\n record: updateResult.record,\n parentNode: node,\n })\n updateResult.merge(descendantsUpdateResult)\n }\n return updateResult\n}\n"]}
1
+ {"version":3,"file":"recordNodesCreator.js","sourceRoot":"","sources":["../../../src/record/recordNodesUpdater/recordNodesCreator.ts"],"names":[],"mappings":"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","sourcesContent":["import { User } from '../../auth'\nimport { SystemError, SystemErrorKeys } from '../../error'\nimport { CategoryItem } from '../../category'\nimport { ArenaRecordNode, NodeFactory, Nodes } from '../../node'\nimport { NodeValues } from '../../node/nodeValues'\nimport { NodeDef, NodeDefCode, NodeDefEntity, NodeDefType, NodeDefs } from '../../nodeDef'\nimport { CategoryItemProvider } from '../../nodeDefExpressionEvaluator/categoryItemProvider'\nimport { Survey } from '../../survey'\nimport { getNodeDefChildren, getNodeDefEnumerator } from '../../survey/surveys/nodeDefs'\nimport { getAncestorCodeAttributes, getCycle } from '../_records/recordGetters'\nimport { getEnumeratingCategoryItems } from '../_records/recordUtils'\nimport { getEnumeratingItemsAllowedCodes } from './recordEnumeratingItemsExpressionEvaluator'\nimport { RecordExpressionEvaluationContext } from './recordExpressionEvaluationContext'\nimport { RecordUpdateResult } from './recordUpdateResult'\n\nexport type NodesUpdateParams = RecordExpressionEvaluationContext & {\n dateModified?: string\n clearNonApplicableValues?: boolean\n}\n\nexport type NodeCreateParams = NodesUpdateParams & {\n parentNode?: ArenaRecordNode\n nodeDef: NodeDef<any>\n createMultipleEntities?: boolean\n}\n\nconst getNodesToInsertCount = (params: { parentNode: ArenaRecordNode | undefined; nodeDef: NodeDef<any> }): number => {\n const { nodeDef, parentNode } = params\n if (!parentNode || NodeDefs.isSingle(nodeDef)) return 1\n if (nodeDef.type === NodeDefType.code) return 0 // never create nodes for multiple code attributes\n\n if (NodeDefs.isMultipleEntity(nodeDef) && !NodeDefs.isAutoCreateMinCountItems(nodeDef)) return 0\n\n const minCount = Nodes.getChildrenMinCount({ parentNode, nodeDef })\n if (Number.isNaN(minCount) || minCount <= 0) return 0\n return minCount\n}\n\nexport const createEnumeratedEntityNode = async (params: {\n user: User\n survey: Survey\n parentNode: ArenaRecordNode\n nodeDef: NodeDefEntity\n enumeratorDef: NodeDefCode\n categoryItem: CategoryItem\n updateResult: RecordUpdateResult\n sideEffect?: boolean\n}): Promise<void> => {\n const { user, survey, parentNode, nodeDef, enumeratorDef, categoryItem, updateResult, sideEffect } = params\n\n const { record } = updateResult\n\n const childUpdateResult = await addNodeAndDescendants({\n user,\n survey,\n record: updateResult.record,\n parentNode,\n nodeDef,\n sideEffect,\n })\n const enumeratorNode = Object.values(childUpdateResult.nodes).find((node) => node.nodeDefUuid === enumeratorDef.uuid)\n if (!enumeratorNode) {\n throw new SystemError(SystemErrorKeys.record.enumeratorNodeNotFound, {\n recordUuid: record.uuid,\n entityDef: NodeDefs.getName(nodeDef),\n enumerator: NodeDefs.getName(enumeratorDef),\n })\n }\n enumeratorNode.value = NodeValues.newCodeValue({ itemUuid: categoryItem.uuid })\n enumeratorNode.meta = { ...enumeratorNode.meta, defaultValueApplied: true }\n enumeratorNode.refData = { categoryItem }\n\n updateResult.merge(childUpdateResult)\n}\n\nexport const createEnumeratedEntityNodes = async (params: {\n user: User\n survey: Survey\n parentNode: ArenaRecordNode\n nodeDef: NodeDefEntity\n updateResult: RecordUpdateResult\n categoryItemProvider?: CategoryItemProvider\n sideEffect?: boolean\n}): Promise<boolean> => {\n const { user, survey, parentNode, nodeDef, updateResult, categoryItemProvider } = params\n\n const enumeratorDef = getNodeDefEnumerator({ survey, entityDef: nodeDef })\n if (!enumeratorDef) return false\n\n const allowedCodes = await getEnumeratingItemsAllowedCodes({\n survey,\n user,\n record: updateResult.record,\n entityDef: nodeDef,\n parentNode,\n })\n\n const categoryItems = await getEnumeratingCategoryItems({\n survey,\n enumeratorDef,\n parentNode,\n categoryItemProvider,\n allowedCodes,\n })(updateResult.record)\n if (categoryItems.length === 0) return false\n\n for (const categoryItem of categoryItems) {\n // sequential: each iteration updates the record\n await createEnumeratedEntityNode({ ...params, enumeratorDef, categoryItem }) // NOSONAR\n }\n return true\n}\n\nconst createChildNodesBasedOnMinCount = async (\n params: NodeCreateParams & { updateResult: RecordUpdateResult }\n): Promise<void> => {\n const { parentNode, nodeDef, updateResult, createMultipleEntities = true } = params\n\n if (NodeDefs.isMultipleEntity(nodeDef) && NodeDefs.isEnumerate(nodeDef)) {\n if (await createEnumeratedEntityNodes({ ...params, parentNode: parentNode! })) {\n return\n }\n }\n\n const nodesToInsertCount = getNodesToInsertCount({ parentNode, nodeDef })\n if (\n nodesToInsertCount === 0 ||\n (NodeDefs.isMultipleEntity(nodeDef) &&\n (!createMultipleEntities || (parentNode && !Nodes.isChildApplicable(parentNode, nodeDef.uuid))))\n ) {\n return // do nothing\n }\n for (let index = 0; index < nodesToInsertCount; index++) {\n // sequential: each iteration updates the record\n const childUpdateResult = await addNodeAndDescendants({ ...params, record: updateResult.record }) // NOSONAR\n updateResult.merge(childUpdateResult)\n }\n}\n\nexport const addDescendants = async (params: NodeCreateParams): Promise<RecordUpdateResult> => {\n const { survey, record, nodeDef } = params\n\n const updateResult = new RecordUpdateResult({ record })\n\n if (NodeDefs.isEntity(nodeDef)) {\n // node defs not in the record cycle have no nodes\n const childDefs = getNodeDefChildren({ survey, nodeDef }).filter(NodeDefs.isInCycle(getCycle(record)))\n\n // Add only child single nodes (it allows to apply default values)\n for (const childDef of childDefs) {\n // sequential: each iteration updates the record\n await createChildNodesBasedOnMinCount({ ...params, updateResult, nodeDef: childDef }) // NOSONAR\n }\n }\n return updateResult\n}\n\nexport const addNodeAndDescendants = async (params: NodeCreateParams): Promise<RecordUpdateResult> => {\n const { survey, record, parentNode, nodeDef, sideEffect = false } = params\n\n const node = NodeFactory.createInstance({\n nodeDefUuid: nodeDef.uuid,\n recordUuid: record.uuid,\n parentNode,\n surveyUuid: survey.uuid,\n })\n if (parentNode && nodeDef.type === NodeDefType.code && NodeDefs.getParentCodeDefUuid(nodeDef as NodeDefCode)) {\n // meta.hCode indexes the node as dependent of its ancestor code attributes (cleared when they change)\n const ancestorCodeAttributes = getAncestorCodeAttributes(survey, parentNode, nodeDef as NodeDefCode)(record)\n node.meta = { ...node.meta, hCode: ancestorCodeAttributes.map((codeAttribute) => codeAttribute.uuid) }\n }\n\n const updateResult = new RecordUpdateResult({ record })\n updateResult.addNode(node, { sideEffect })\n\n // Add children if entity\n if (NodeDefs.isEntity(nodeDef)) {\n const descendantsUpdateResult = await addDescendants({\n ...params,\n record: updateResult.record,\n parentNode: node,\n })\n updateResult.merge(descendantsUpdateResult)\n }\n return updateResult\n}\n"]}
@@ -35,7 +35,10 @@ export declare class FileProcessor {
35
35
  protected currentChunkNumber: number;
36
36
  protected processingChunk: boolean;
37
37
  protected completed: boolean;
38
+ private runId;
39
+ private retryProcessor?;
38
40
  constructor({ file, filePath, chunkProcessor, onError, onComplete, chunkSize, maxTryings, retryDelay, }: FileProcessorConstructorArgs);
41
+ private cancelCurrentRun;
39
42
  protected reset(): void;
40
43
  protected calculateFileSize(): Promise<number>;
41
44
  protected onFail(error: Error): void;
@@ -12,6 +12,8 @@ class FileProcessor {
12
12
  // true while a chunk is being processed (resume must not start a second processing chain)
13
13
  this.processingChunk = false;
14
14
  this.completed = false;
15
+ // incremented when a processing run is stopped: callbacks of the chunks of previous runs are ignored
16
+ this.runId = 0;
15
17
  this.file = file;
16
18
  this.filePath = filePath;
17
19
  this.chunkProcessor = chunkProcessor;
@@ -25,7 +27,14 @@ class FileProcessor {
25
27
  }
26
28
  this.reset();
27
29
  }
30
+ cancelCurrentRun() {
31
+ var _a;
32
+ this.runId += 1;
33
+ (_a = this.retryProcessor) === null || _a === void 0 ? void 0 : _a.stop();
34
+ this.retryProcessor = undefined;
35
+ }
28
36
  reset() {
37
+ this.cancelCurrentRun();
29
38
  this.running = false;
30
39
  this.totalChunks = 0;
31
40
  this.currentChunkNumber = 0;
@@ -50,14 +59,19 @@ class FileProcessor {
50
59
  return Promise.resolve(file.slice((currentChunkNumber - 1) * chunkSize, currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize));
51
60
  }
52
61
  processNextChunk() {
53
- const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay } = this;
62
+ const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay, runId } = this;
63
+ const isStale = () => runId !== this.runId;
54
64
  this.processingChunk = true;
55
65
  this.extractCurrentFileChunk()
56
66
  .then((content) => {
67
+ if (isStale())
68
+ return;
57
69
  const retryProcessor = new retryProcessor_1.RetryProcessor({
58
70
  processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),
59
71
  onSuccess: (chunkResult) => {
60
72
  var _a;
73
+ if (isStale())
74
+ return;
61
75
  this.processingChunk = false;
62
76
  if (this.currentChunkNumber < totalChunks) {
63
77
  // move to the next chunk also when paused: resume will continue from it
@@ -74,6 +88,8 @@ class FileProcessor {
74
88
  }
75
89
  },
76
90
  onFail: (error) => {
91
+ if (isStale())
92
+ return;
77
93
  this.processingChunk = false;
78
94
  this.onFail(error);
79
95
  this.running = false;
@@ -81,20 +97,29 @@ class FileProcessor {
81
97
  maxTryings,
82
98
  retryDelay,
83
99
  });
100
+ this.retryProcessor = retryProcessor;
84
101
  retryProcessor.start();
85
102
  })
86
103
  .catch((error) => {
104
+ if (isStale())
105
+ return;
87
106
  this.processingChunk = false;
88
107
  this.onFail(error);
89
108
  this.running = false;
90
109
  });
91
110
  }
92
111
  start(startFromChunk = 1) {
112
+ // a previous run still in progress must not continue in parallel
113
+ this.cancelCurrentRun();
114
+ const { runId } = this;
115
+ this.processingChunk = false;
93
116
  this.running = true;
94
117
  this.completed = false;
95
118
  this.currentChunkNumber = startFromChunk;
96
119
  this.calculateFileSize()
97
120
  .then((fileSize) => {
121
+ if (runId !== this.runId)
122
+ return;
98
123
  this.totalFileSize = fileSize;
99
124
  this.totalChunks = Math.ceil(fileSize / this.chunkSize);
100
125
  if (this.totalChunks > 0) {
@@ -102,6 +127,8 @@ class FileProcessor {
102
127
  }
103
128
  })
104
129
  .catch((error) => {
130
+ if (runId !== this.runId)
131
+ return;
105
132
  this.onFail(error);
106
133
  this.running = false;
107
134
  });
@@ -1 +1 @@
1
- {"version":3,"file":"fileProcessor.js","sourceRoot":"","sources":["../../src/utils/fileProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { RetryProcessor } from './retryProcessor'\n\ntype ChunkProcessor = (args: {\n chunk: number\n content: Blob | string | Uint8Array\n totalChunks: number\n totalFileSize: number\n}) => Promise<any>\n\nexport type FileProcessorConstructorArgs = {\n file?: File\n filePath?: string\n chunkProcessor: ChunkProcessor\n onError?: (error: Error) => void\n onComplete?: (result: any) => void\n chunkSize?: number\n maxTryings?: number\n /**\n * Delay (in milliseconds) before retrying a failed chunk, doubled at every further retry.\n */\n retryDelay?: number\n}\n\nexport class FileProcessor {\n static readonly defaultChunkSize = 1024 * 1024 * 10 // 10MB\n static readonly defaultMaxTryings = 5\n static readonly defaultRetryDelay = 1000 // 1s, then 2s, 4s...\n\n private readonly file?: File\n private readonly filePath?: string\n private readonly chunkProcessor: ChunkProcessor\n private readonly chunkSize: number\n private readonly maxTryings: number\n private readonly retryDelay: number\n private readonly onError?: (error: Error) => void\n private readonly onComplete?: (result?: any) => void\n\n // State properties\n protected totalFileSize: number = 0\n protected running: boolean = false\n protected totalChunks: number = 0\n protected currentChunkNumber: number = 0\n // true while a chunk is being processed (resume must not start a second processing chain)\n protected processingChunk: boolean = false\n protected completed: boolean = false\n\n constructor({\n file,\n filePath,\n chunkProcessor,\n onError,\n onComplete,\n chunkSize = FileProcessor.defaultChunkSize,\n maxTryings = FileProcessor.defaultMaxTryings,\n retryDelay = FileProcessor.defaultRetryDelay,\n }: FileProcessorConstructorArgs) {\n this.file = file\n this.filePath = filePath\n this.chunkProcessor = chunkProcessor\n this.chunkSize = chunkSize\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n this.onError = onError\n this.onComplete = onComplete\n\n if (!this.file && !this.filePath) {\n throw new Error('File or filePath not specified')\n }\n\n this.reset()\n }\n\n protected reset(): void {\n this.running = false\n this.totalChunks = 0\n this.currentChunkNumber = 0\n this.totalFileSize = 0\n this.processingChunk = false\n this.completed = false\n }\n\n protected calculateFileSize(): Promise<number> {\n return Promise.resolve(this.file?.size ?? 0)\n }\n\n protected onFail(error: Error): void {\n this.onError?.(error)\n }\n\n protected extractCurrentFileChunk(): Promise<Blob | string | Uint8Array> {\n const { file, currentChunkNumber, totalChunks, chunkSize } = this\n if (!file) {\n return Promise.reject(new Error('File property not initialized'))\n }\n return Promise.resolve(\n file.slice(\n (currentChunkNumber - 1) * chunkSize,\n currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize\n )\n )\n }\n\n protected processNextChunk(): void {\n const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay } = this\n\n this.processingChunk = true\n this.extractCurrentFileChunk()\n .then((content) => {\n const retryProcessor = new RetryProcessor<any>({\n processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),\n onSuccess: (chunkResult) => {\n this.processingChunk = false\n if (this.currentChunkNumber < totalChunks) {\n // move to the next chunk also when paused: resume will continue from it\n this.currentChunkNumber += 1\n if (this.running) {\n this.processNextChunk()\n }\n } else {\n this.running = false\n this.completed = true\n // called outside the retried processor: if it throws, the last chunk is not processed again\n this.onComplete?.(chunkResult)\n }\n },\n onFail: (error: Error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n },\n maxTryings,\n retryDelay,\n })\n retryProcessor.start()\n })\n .catch((error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n })\n }\n\n start(startFromChunk: number = 1): void {\n this.running = true\n this.completed = false\n this.currentChunkNumber = startFromChunk\n this.calculateFileSize()\n .then((fileSize) => {\n this.totalFileSize = fileSize\n this.totalChunks = Math.ceil(fileSize / this.chunkSize)\n if (this.totalChunks > 0) {\n this.processNextChunk()\n }\n })\n .catch((error) => {\n this.onFail(error)\n this.running = false\n })\n }\n\n stop(): void {\n this.reset()\n }\n\n pause(): void {\n this.running = false\n }\n\n resume(): void {\n if (this.running || this.completed) return\n this.running = true\n // if a chunk is still being processed, processing continues when it completes\n if (!this.processingChunk && this.totalChunks > 0) {\n this.processNextChunk()\n }\n }\n}\n"]}
1
+ {"version":3,"file":"fileProcessor.js","sourceRoot":"","sources":["../../src/utils/fileProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { RetryProcessor } from './retryProcessor'\n\ntype ChunkProcessor = (args: {\n chunk: number\n content: Blob | string | Uint8Array\n totalChunks: number\n totalFileSize: number\n}) => Promise<any>\n\nexport type FileProcessorConstructorArgs = {\n file?: File\n filePath?: string\n chunkProcessor: ChunkProcessor\n onError?: (error: Error) => void\n onComplete?: (result: any) => void\n chunkSize?: number\n maxTryings?: number\n /**\n * Delay (in milliseconds) before retrying a failed chunk, doubled at every further retry.\n */\n retryDelay?: number\n}\n\nexport class FileProcessor {\n static readonly defaultChunkSize = 1024 * 1024 * 10 // 10MB\n static readonly defaultMaxTryings = 5\n static readonly defaultRetryDelay = 1000 // 1s, then 2s, 4s...\n\n private readonly file?: File\n private readonly filePath?: string\n private readonly chunkProcessor: ChunkProcessor\n private readonly chunkSize: number\n private readonly maxTryings: number\n private readonly retryDelay: number\n private readonly onError?: (error: Error) => void\n private readonly onComplete?: (result?: any) => void\n\n // State properties\n protected totalFileSize: number = 0\n protected running: boolean = false\n protected totalChunks: number = 0\n protected currentChunkNumber: number = 0\n // true while a chunk is being processed (resume must not start a second processing chain)\n protected processingChunk: boolean = false\n protected completed: boolean = false\n // incremented when a processing run is stopped: callbacks of the chunks of previous runs are ignored\n private runId: number = 0\n private retryProcessor?: RetryProcessor<any>\n\n constructor({\n file,\n filePath,\n chunkProcessor,\n onError,\n onComplete,\n chunkSize = FileProcessor.defaultChunkSize,\n maxTryings = FileProcessor.defaultMaxTryings,\n retryDelay = FileProcessor.defaultRetryDelay,\n }: FileProcessorConstructorArgs) {\n this.file = file\n this.filePath = filePath\n this.chunkProcessor = chunkProcessor\n this.chunkSize = chunkSize\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n this.onError = onError\n this.onComplete = onComplete\n\n if (!this.file && !this.filePath) {\n throw new Error('File or filePath not specified')\n }\n\n this.reset()\n }\n\n private cancelCurrentRun(): void {\n this.runId += 1\n this.retryProcessor?.stop()\n this.retryProcessor = undefined\n }\n\n protected reset(): void {\n this.cancelCurrentRun()\n this.running = false\n this.totalChunks = 0\n this.currentChunkNumber = 0\n this.totalFileSize = 0\n this.processingChunk = false\n this.completed = false\n }\n\n protected calculateFileSize(): Promise<number> {\n return Promise.resolve(this.file?.size ?? 0)\n }\n\n protected onFail(error: Error): void {\n this.onError?.(error)\n }\n\n protected extractCurrentFileChunk(): Promise<Blob | string | Uint8Array> {\n const { file, currentChunkNumber, totalChunks, chunkSize } = this\n if (!file) {\n return Promise.reject(new Error('File property not initialized'))\n }\n return Promise.resolve(\n file.slice(\n (currentChunkNumber - 1) * chunkSize,\n currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize\n )\n )\n }\n\n protected processNextChunk(): void {\n const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay, runId } = this\n const isStale = () => runId !== this.runId\n\n this.processingChunk = true\n this.extractCurrentFileChunk()\n .then((content) => {\n if (isStale()) return\n const retryProcessor = new RetryProcessor<any>({\n processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),\n onSuccess: (chunkResult) => {\n if (isStale()) return\n this.processingChunk = false\n if (this.currentChunkNumber < totalChunks) {\n // move to the next chunk also when paused: resume will continue from it\n this.currentChunkNumber += 1\n if (this.running) {\n this.processNextChunk()\n }\n } else {\n this.running = false\n this.completed = true\n // called outside the retried processor: if it throws, the last chunk is not processed again\n this.onComplete?.(chunkResult)\n }\n },\n onFail: (error: Error) => {\n if (isStale()) return\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n },\n maxTryings,\n retryDelay,\n })\n this.retryProcessor = retryProcessor\n retryProcessor.start()\n })\n .catch((error) => {\n if (isStale()) return\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n })\n }\n\n start(startFromChunk: number = 1): void {\n // a previous run still in progress must not continue in parallel\n this.cancelCurrentRun()\n const { runId } = this\n this.processingChunk = false\n this.running = true\n this.completed = false\n this.currentChunkNumber = startFromChunk\n this.calculateFileSize()\n .then((fileSize) => {\n if (runId !== this.runId) return\n this.totalFileSize = fileSize\n this.totalChunks = Math.ceil(fileSize / this.chunkSize)\n if (this.totalChunks > 0) {\n this.processNextChunk()\n }\n })\n .catch((error) => {\n if (runId !== this.runId) return\n this.onFail(error)\n this.running = false\n })\n }\n\n stop(): void {\n this.reset()\n }\n\n pause(): void {\n this.running = false\n }\n\n resume(): void {\n if (this.running || this.completed) return\n this.running = true\n // if a chunk is still being processed, processing continues when it completes\n if (!this.processingChunk && this.totalChunks > 0) {\n this.processNextChunk()\n }\n }\n}\n"]}
@@ -52,6 +52,7 @@ class RetryProcessor {
52
52
  }
53
53
  // Public methods
54
54
  start() {
55
+ this.currentTrying = 0;
55
56
  this.running = true;
56
57
  this.processNext();
57
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"retryProcessor.js","sourceRoot":"","sources":["../../src/utils/retryProcessor.ts"],"names":[],"mappings":";;;AAYA,MAAM,OAAO,GAAG,CAAC,KAAc,EAAS,EAAE,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAEtG;IAWE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,EAAuC;QAJjH,mBAAmB;QACX,kBAAa,GAAW,CAAC,CAAA;QACzB,YAAO,GAAY,KAAK,CAAA;QAG9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAES,KAAK;QACb,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAES,WAAW;QACnB,IAAI,CAAC,aAAa,IAAI,CAAC,CAAA;QAEvB,2FAA2F;QAC3F,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACnB,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EACtC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACrD,CAAA;IACH,CAAC;IAEO,aAAa,CAAC,MAAuB;QAC3C,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,KAAY;QACvC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3D,kCAAkC;YAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAClB,OAAM;QACR,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,GAAG,SAAA,CAAC,EAAI,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA,CAAA;QAC7D,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,WAAW,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QACD,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,WAAW,EAAE,CAAA;YACpB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAA;IACX,CAAC;IAED,iBAAiB;IACjB,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;CACF","sourcesContent":["interface RetryProcessorArgs<ProcessorResult> {\n processor: () => Promise<ProcessorResult>\n onSuccess: (result: ProcessorResult) => void\n onFail: (error: Error) => void\n maxTryings?: number\n /**\n * Delay (in milliseconds) before the first retry, doubled at every further retry (exponential backoff).\n * Defaults to 0 (immediate retry).\n */\n retryDelay?: number\n}\n\nconst toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error)))\n\nexport class RetryProcessor<ProcessorResult> {\n private readonly processor: () => Promise<ProcessorResult>\n private readonly onSuccess: (result: ProcessorResult) => void\n private readonly onFail: (error: Error) => void\n private readonly maxTryings: number\n private readonly retryDelay: number\n\n // State properties\n private currentTrying: number = 0\n private running: boolean = false\n\n constructor({ processor, onSuccess, onFail, maxTryings = 2, retryDelay = 0 }: RetryProcessorArgs<ProcessorResult>) {\n this.processor = processor\n this.onSuccess = onSuccess\n this.onFail = onFail\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n }\n\n protected reset() {\n this.currentTrying = 0\n this.running = false\n }\n\n protected processNext(): void {\n this.currentTrying += 1\n\n // onSuccess runs outside the retried chain: if it throws, the processor must not run again\n this.processor().then(\n (result) => this.handleSuccess(result),\n (error) => this.handleProcessorError(toError(error))\n )\n }\n\n private handleSuccess(result: ProcessorResult): void {\n try {\n this.onSuccess(result)\n } catch (error) {\n this.onFail(toError(error))\n }\n }\n\n private handleProcessorError(error: Error): void {\n if (!this.running || this.currentTrying >= this.maxTryings) {\n // stopped or max attempts reached\n this.onFail(error)\n return\n }\n const delay = this.retryDelay * 2 ** (this.currentTrying - 1)\n if (delay <= 0) {\n this.processNext()\n return\n }\n setTimeout(() => {\n if (this.running) {\n this.processNext()\n } else {\n this.onFail(error)\n }\n }, delay)\n }\n\n // Public methods\n start(): void {\n this.running = true\n this.processNext()\n }\n\n stop(): void {\n this.reset()\n }\n}\n"]}
1
+ {"version":3,"file":"retryProcessor.js","sourceRoot":"","sources":["../../src/utils/retryProcessor.ts"],"names":[],"mappings":";;;AAYA,MAAM,OAAO,GAAG,CAAC,KAAc,EAAS,EAAE,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAEtG;IAWE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,EAAuC;QAJjH,mBAAmB;QACX,kBAAa,GAAW,CAAC,CAAA;QACzB,YAAO,GAAY,KAAK,CAAA;QAG9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAES,KAAK;QACb,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAES,WAAW;QACnB,IAAI,CAAC,aAAa,IAAI,CAAC,CAAA;QAEvB,2FAA2F;QAC3F,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACnB,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EACtC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACrD,CAAA;IACH,CAAC;IAEO,aAAa,CAAC,MAAuB;QAC3C,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,KAAY;QACvC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3D,kCAAkC;YAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAClB,OAAM;QACR,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,GAAG,SAAA,CAAC,EAAI,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA,CAAA;QAC7D,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,WAAW,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QACD,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,WAAW,EAAE,CAAA;YACpB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAA;IACX,CAAC;IAED,iBAAiB;IACjB,KAAK;QACH,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;CACF","sourcesContent":["interface RetryProcessorArgs<ProcessorResult> {\n processor: () => Promise<ProcessorResult>\n onSuccess: (result: ProcessorResult) => void\n onFail: (error: Error) => void\n maxTryings?: number\n /**\n * Delay (in milliseconds) before the first retry, doubled at every further retry (exponential backoff).\n * Defaults to 0 (immediate retry).\n */\n retryDelay?: number\n}\n\nconst toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error)))\n\nexport class RetryProcessor<ProcessorResult> {\n private readonly processor: () => Promise<ProcessorResult>\n private readonly onSuccess: (result: ProcessorResult) => void\n private readonly onFail: (error: Error) => void\n private readonly maxTryings: number\n private readonly retryDelay: number\n\n // State properties\n private currentTrying: number = 0\n private running: boolean = false\n\n constructor({ processor, onSuccess, onFail, maxTryings = 2, retryDelay = 0 }: RetryProcessorArgs<ProcessorResult>) {\n this.processor = processor\n this.onSuccess = onSuccess\n this.onFail = onFail\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n }\n\n protected reset() {\n this.currentTrying = 0\n this.running = false\n }\n\n protected processNext(): void {\n this.currentTrying += 1\n\n // onSuccess runs outside the retried chain: if it throws, the processor must not run again\n this.processor().then(\n (result) => this.handleSuccess(result),\n (error) => this.handleProcessorError(toError(error))\n )\n }\n\n private handleSuccess(result: ProcessorResult): void {\n try {\n this.onSuccess(result)\n } catch (error) {\n this.onFail(toError(error))\n }\n }\n\n private handleProcessorError(error: Error): void {\n if (!this.running || this.currentTrying >= this.maxTryings) {\n // stopped or max attempts reached\n this.onFail(error)\n return\n }\n const delay = this.retryDelay * 2 ** (this.currentTrying - 1)\n if (delay <= 0) {\n this.processNext()\n return\n }\n setTimeout(() => {\n if (this.running) {\n this.processNext()\n } else {\n this.onFail(error)\n }\n }, delay)\n }\n\n // Public methods\n start(): void {\n this.currentTrying = 0\n this.running = true\n this.processNext()\n }\n\n stop(): void {\n this.reset()\n }\n}\n"]}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "OpenForis",
5
5
  "email": "openforis.arena@gmail.com"
6
6
  },
7
- "version": "2.16.8",
7
+ "version": "2.16.10",
8
8
  "description": "Arena core module",
9
9
  "repository": {
10
10
  "type": "git",