@outbuild-company/schedule-core 1.9.2 → 1.9.3

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.
package/dist/index.cjs CHANGED
@@ -12543,28 +12543,6 @@ function assertValidAssignments(assignments, entityName, readCurrent, ownerByBac
12543
12543
  );
12544
12544
  }
12545
12545
  }
12546
- function assertValidReconciledProgress(entries, deps) {
12547
- const seen = /* @__PURE__ */ new Set();
12548
- for (const entry of entries) {
12549
- const id = String(entry.activityId);
12550
- if (id.length === 0 || seen.has(id)) {
12551
- throw new Error(
12552
- `persistence-acknowledge: duplicate or empty reconciled activity id "${id}"`
12553
- );
12554
- }
12555
- if (!Number.isFinite(entry.progress) || entry.progress < 0 || entry.progress > 100) {
12556
- throw new Error(
12557
- `persistence-acknowledge: invalid reconciled progress ${String(entry.progress)} for "${id}"`
12558
- );
12559
- }
12560
- if (!deps.adapter.getActivity(id)) {
12561
- throw new Error(
12562
- `persistence-acknowledge: unknown reconciled activity id "${id}"`
12563
- );
12564
- }
12565
- seen.add(id);
12566
- }
12567
- }
12568
12546
  async function applyReconciledProgress(entries, deps) {
12569
12547
  const progressToApply = entries.filter((entry) => {
12570
12548
  const current = deps.adapter.getActivity(String(entry.activityId));
@@ -12595,19 +12573,25 @@ async function applyReconciledProgress(entries, deps) {
12595
12573
  async function dispatchPersistenceAcknowledge(action, deps) {
12596
12574
  const activities = action.activities ?? [];
12597
12575
  const links = action.links ?? [];
12598
- const reconciledProgress = action.reconciledProgress ?? [];
12599
- assertValidReconciledProgress(reconciledProgress, deps);
12600
12576
  const currentActivities = deps.adapter.getAllActivities();
12601
12577
  const currentLinks = deps.adapter.getAllLinks();
12602
- const activityOwnerByBackendId = persistedOwnersOf(currentActivities);
12603
- const linkOwnerByBackendId = persistedOwnersOf(currentLinks);
12578
+ const checkpointActivities = action.checkpoint?.activities ?? currentActivities;
12579
+ const checkpointLinks = action.checkpoint?.links ?? currentLinks;
12580
+ const checkpointActivityById = new Map(
12581
+ checkpointActivities.map((activity) => [String(activity.id), activity])
12582
+ );
12583
+ const checkpointLinkById = new Map(
12584
+ checkpointLinks.map((link) => [String(link.id), link])
12585
+ );
12586
+ const activityOwnerByBackendId = persistedOwnersOf(checkpointActivities);
12587
+ const linkOwnerByBackendId = persistedOwnersOf(checkpointLinks);
12604
12588
  assertValidAssignments(
12605
12589
  activities,
12606
12590
  "activity",
12607
12591
  (id) => {
12608
- const activity = deps.adapter.getActivity(id);
12592
+ const activity = checkpointActivityById.get(id);
12609
12593
  return {
12610
- exists: activity !== null,
12594
+ exists: activity !== void 0,
12611
12595
  current: activity?.proplannerId ?? null
12612
12596
  };
12613
12597
  },
@@ -12617,17 +12601,21 @@ async function dispatchPersistenceAcknowledge(action, deps) {
12617
12601
  links,
12618
12602
  "link",
12619
12603
  (id) => {
12620
- const link = deps.adapter.getLink(id);
12621
- return { exists: link !== null, current: link?.proplannerId ?? null };
12604
+ const link = checkpointLinkById.get(id);
12605
+ return {
12606
+ exists: link !== void 0,
12607
+ current: link?.proplannerId ?? null
12608
+ };
12622
12609
  },
12623
12610
  linkOwnerByBackendId
12624
12611
  );
12625
- assertAllNewEntitiesAcknowledged(currentActivities, activities, "activity");
12626
- assertAllNewEntitiesAcknowledged(currentLinks, links, "link");
12627
- const activityChanges = await applyReconciledProgress(
12628
- reconciledProgress,
12629
- deps
12612
+ assertAllNewEntitiesAcknowledged(
12613
+ checkpointActivities,
12614
+ activities,
12615
+ "activity"
12630
12616
  );
12617
+ assertAllNewEntitiesAcknowledged(checkpointLinks, links, "link");
12618
+ const activityChanges = [];
12631
12619
  const linkChanges = [];
12632
12620
  deps.adapter.batchUpdate(() => {
12633
12621
  for (const assignment of activities) {
@@ -18441,7 +18429,9 @@ function mergeCoalesced(top, next) {
18441
18429
  return buildUndoEntry(changeSet);
18442
18430
  }
18443
18431
  function getDispatchHistoryPolicy(action) {
18444
- if (action.kind === "persistence-acknowledge") return "clear-on-success";
18432
+ if (action.kind === "persistence-acknowledge") {
18433
+ return "checkpoint-on-success";
18434
+ }
18445
18435
  if (action.kind === "sir-sync" || action.kind === "activity-lookahead-sync" || action.kind === "baseline-apply" || action.kind === "ponderator-criterion-set" || action.kind === "status-criteria-set" || action.kind === "activity-clipboard-clear" || action.kind === "activity-copy" || action.kind === "selection-toggle" || action.kind === "selection-replace" || action.kind === "visibility-set" || action.kind === "filter-set" || action.kind === "sort-set") {
18446
18436
  return "skip";
18447
18437
  }
@@ -18697,22 +18687,25 @@ var UndoRecorder = class _UndoRecorder {
18697
18687
  maxDepth;
18698
18688
  undoStack = [];
18699
18689
  redoStack = [];
18690
+ sequence = 0;
18700
18691
  _lastKey = null;
18701
18692
  _lastTime = 0;
18702
18693
  fork() {
18703
18694
  const fork = new _UndoRecorder(this.maxDepth);
18704
18695
  fork.undoStack.push(...cloneDomainValue(this.undoStack));
18705
18696
  fork.redoStack.push(...cloneDomainValue(this.redoStack));
18697
+ fork.sequence = this.sequence;
18706
18698
  fork._lastKey = this._lastKey;
18707
18699
  fork._lastTime = this._lastTime;
18708
18700
  return fork;
18709
18701
  }
18710
18702
  record(entry, coalesceKey = null, now = 0) {
18711
18703
  const top = this.undoStack[this.undoStack.length - 1];
18712
- if (coalesceKey != null && coalesceKey === this._lastKey && now - this._lastTime <= COALESCE_WINDOW_MS && top !== void 0 && canCoalesce(top, entry)) {
18713
- this.undoStack[this.undoStack.length - 1] = mergeCoalesced(top, entry);
18704
+ if (coalesceKey != null && coalesceKey === this._lastKey && now - this._lastTime <= COALESCE_WINDOW_MS && top !== void 0 && canCoalesce(top.entry, entry)) {
18705
+ top.entry = mergeCoalesced(top.entry, entry);
18706
+ top.touchedAt = this.touch();
18714
18707
  } else {
18715
- this.undoStack.push(entry);
18708
+ this.undoStack.push({ entry, touchedAt: this.touch() });
18716
18709
  if (this.undoStack.length > this.maxDepth) {
18717
18710
  this.undoStack.splice(0, this.undoStack.length - this.maxDepth);
18718
18711
  }
@@ -18725,16 +18718,16 @@ var UndoRecorder = class _UndoRecorder {
18725
18718
  this._lastKey = null;
18726
18719
  }
18727
18720
  takeUndo() {
18728
- return this.undoStack.pop() ?? null;
18721
+ return this.undoStack.pop()?.entry ?? null;
18729
18722
  }
18730
18723
  takeRedo() {
18731
- return this.redoStack.pop() ?? null;
18724
+ return this.redoStack.pop()?.entry ?? null;
18732
18725
  }
18733
18726
  pushRedo(entry) {
18734
- this.redoStack.push(entry);
18727
+ this.redoStack.push({ entry, touchedAt: this.touch() });
18735
18728
  }
18736
18729
  pushUndo(entry) {
18737
- this.undoStack.push(entry);
18730
+ this.undoStack.push({ entry, touchedAt: this.touch() });
18738
18731
  }
18739
18732
  canUndo() {
18740
18733
  return this.undoStack.length > 0;
@@ -18745,11 +18738,64 @@ var UndoRecorder = class _UndoRecorder {
18745
18738
  undoDepth() {
18746
18739
  return this.undoStack.length;
18747
18740
  }
18741
+ capturePersistenceBoundary() {
18742
+ this.resetCoalesce();
18743
+ return this.sequence;
18744
+ }
18745
+ discardThrough(boundary) {
18746
+ this.removeEntriesThrough(this.undoStack, boundary);
18747
+ this.removeEntriesThrough(this.redoStack, boundary);
18748
+ this.resetCoalesce();
18749
+ }
18750
+ applyPersistedIdentities(activities, links) {
18751
+ const activityIds = new Map(
18752
+ activities.map(({ id, proplannerId }) => [String(id), proplannerId])
18753
+ );
18754
+ const linkIds = new Map(
18755
+ links.map(({ id, proplannerId }) => [String(id), proplannerId])
18756
+ );
18757
+ for (const stored of [...this.undoStack, ...this.redoStack]) {
18758
+ stored.entry = this.withPersistedIdentities(
18759
+ stored.entry,
18760
+ activityIds,
18761
+ linkIds
18762
+ );
18763
+ }
18764
+ }
18748
18765
  clear() {
18749
18766
  this.undoStack.length = 0;
18750
18767
  this.redoStack.length = 0;
18751
18768
  this.resetCoalesce();
18752
18769
  }
18770
+ touch() {
18771
+ this.sequence += 1;
18772
+ return this.sequence;
18773
+ }
18774
+ removeEntriesThrough(stack, boundary) {
18775
+ const retained = stack.filter(({ touchedAt }) => touchedAt > boundary);
18776
+ stack.splice(0, stack.length, ...retained);
18777
+ }
18778
+ withPersistedIdentities(entry, activityIds, linkIds) {
18779
+ const patchActivities = (snapshots) => snapshots === void 0 ? void 0 : new Map(
18780
+ Array.from(snapshots, ([id, activity]) => [
18781
+ id,
18782
+ activityIds.has(id) ? { ...activity, proplannerId: activityIds.get(id) } : activity
18783
+ ])
18784
+ );
18785
+ const patchLinks = (snapshots) => snapshots === void 0 ? void 0 : new Map(
18786
+ Array.from(snapshots, ([id, link]) => [
18787
+ id,
18788
+ linkIds.has(id) ? { ...link, proplannerId: linkIds.get(id) } : link
18789
+ ])
18790
+ );
18791
+ return {
18792
+ ...entry,
18793
+ beforeSnap: patchActivities(entry.beforeSnap),
18794
+ afterSnap: patchActivities(entry.afterSnap),
18795
+ beforeLinks: patchLinks(entry.beforeLinks),
18796
+ afterLinks: patchLinks(entry.afterLinks)
18797
+ };
18798
+ }
18753
18799
  };
18754
18800
 
18755
18801
  // src/dispatch/shared/resequenced-parents.ts
@@ -18907,10 +18953,12 @@ function snapshotFieldValue(field, value) {
18907
18953
  return value;
18908
18954
  }
18909
18955
  function activityChangedSince(current, saved) {
18910
- for (const field of PERSISTABLE_ACTIVITY_FIELDS) {
18911
- if (!fieldEquals(field, current[field], saved[field])) return true;
18912
- }
18913
- return false;
18956
+ return changedPersistableActivityFields(current, saved).length > 0;
18957
+ }
18958
+ function changedPersistableActivityFields(current, saved) {
18959
+ return PERSISTABLE_ACTIVITY_FIELDS.filter(
18960
+ (field) => !fieldEquals(field, current[field], saved[field])
18961
+ );
18914
18962
  }
18915
18963
  function fieldEquals(field, currentValue, savedValue) {
18916
18964
  if (DATE_FIELDS2.has(field)) return dateEquals(currentValue, savedValue);
@@ -19019,11 +19067,60 @@ var SaveTracker = class _SaveTracker {
19019
19067
  }
19020
19068
  this.initialized = true;
19021
19069
  }
19022
- acknowledge(activities, links) {
19070
+ acknowledge(activities, links, activityIdentities = [], linkIdentities = [], currentActivities, currentLinks) {
19023
19071
  if (!this.trackingEnabled) return;
19024
- assertValidPersistedIdentities(activities, links);
19025
- this.snapshotActivities(activities);
19026
- this.snapshotLinks(links);
19072
+ const activityIds = new Map(
19073
+ activityIdentities.map(({ id, proplannerId }) => [
19074
+ String(id),
19075
+ proplannerId
19076
+ ])
19077
+ );
19078
+ const linkIds = new Map(
19079
+ linkIdentities.map(({ id, proplannerId }) => [String(id), proplannerId])
19080
+ );
19081
+ const persistedActivities = activities.map((activity) => ({
19082
+ ...activity,
19083
+ proplannerId: activityIds.get(String(activity.id)) ?? activity.proplannerId
19084
+ }));
19085
+ const persistedLinks = links.map((link) => {
19086
+ const proplannerId = linkIds.get(String(link.id));
19087
+ return proplannerId === void 0 ? { ...link } : { ...link, proplannerId };
19088
+ });
19089
+ assertValidPersistedIdentities(persistedActivities, persistedLinks);
19090
+ this.snapshotActivities(persistedActivities);
19091
+ this.snapshotLinks(persistedLinks);
19092
+ if (!currentActivities || !currentLinks) return;
19093
+ const currentActivityById = new Map(
19094
+ currentActivities.map((activity) => [String(activity.id), activity])
19095
+ );
19096
+ const currentLinkById = new Map(
19097
+ currentLinks.map((link) => [String(link.id), link])
19098
+ );
19099
+ for (const activity of persistedActivities) {
19100
+ const activityId = String(activity.id);
19101
+ if (this.activityIsDirty(
19102
+ activityId,
19103
+ currentActivityById.get(activityId) ?? null
19104
+ )) {
19105
+ this.dirtyActivityIds.add(activityId);
19106
+ }
19107
+ }
19108
+ for (const activity of currentActivities) {
19109
+ const activityId = String(activity.id);
19110
+ if (this.activityIsDirty(activityId, activity)) {
19111
+ this.dirtyActivityIds.add(activityId);
19112
+ }
19113
+ }
19114
+ for (const link of persistedLinks) {
19115
+ const linkId = String(link.id);
19116
+ if (this.linkIsDirty(linkId, currentLinkById.get(linkId) ?? null)) {
19117
+ this.dirtyLinkIds.add(linkId);
19118
+ }
19119
+ }
19120
+ for (const link of currentLinks) {
19121
+ const linkId = String(link.id);
19122
+ if (this.linkIsDirty(linkId, link)) this.dirtyLinkIds.add(linkId);
19123
+ }
19027
19124
  }
19028
19125
  snapshotActivities(activities) {
19029
19126
  if (!this.trackingEnabled) return;
@@ -19207,6 +19304,21 @@ var SaveTracker = class _SaveTracker {
19207
19304
  }
19208
19305
  return modified;
19209
19306
  }
19307
+ modifiedActivityFields(getActivity) {
19308
+ const result = /* @__PURE__ */ new Map();
19309
+ if (!this.trackingEnabled || !this.initialized) return result;
19310
+ for (const activityId of this.dirtyActivityIds) {
19311
+ const current = getActivity(activityId);
19312
+ const saved = this.activitiesAtLastSave.get(activityId);
19313
+ if (!current || !saved) continue;
19314
+ const fields = changedPersistableActivityFields(
19315
+ current,
19316
+ saved.persistable
19317
+ );
19318
+ if (fields.length > 0) result.set(activityId, fields);
19319
+ }
19320
+ return result;
19321
+ }
19210
19322
  modifiedLinks(getLink) {
19211
19323
  if (!this.trackingEnabled || !this.initialized) return [];
19212
19324
  const modified = [];
@@ -21299,6 +21411,14 @@ var ScheduleCore = class _ScheduleCore {
21299
21411
  (activityId) => this.coreRuntime.state.getActivity(activityId)
21300
21412
  ).map(cloneDomainValue);
21301
21413
  }
21414
+ getModifiedActivityFields() {
21415
+ this.assertReady();
21416
+ return cloneDomainValue(
21417
+ this._saveTracker.modifiedActivityFields(
21418
+ (activityId) => this.coreRuntime.state.getActivity(activityId)
21419
+ )
21420
+ );
21421
+ }
21302
21422
  getNewActivities() {
21303
21423
  this.assertReady();
21304
21424
  return this._saveTracker.newActivities(
@@ -21312,6 +21432,10 @@ var ScheduleCore = class _ScheduleCore {
21312
21432
  getScheduleRevision() {
21313
21433
  return this._scheduleRevision;
21314
21434
  }
21435
+ capturePersistenceHistoryBoundary() {
21436
+ this.assertReady();
21437
+ return this._undo.capturePersistenceBoundary();
21438
+ }
21315
21439
  getCanonicalDigest() {
21316
21440
  this.assertReady();
21317
21441
  const byId = (left, right) => String(left.id).localeCompare(String(right.id));
@@ -21482,12 +21606,29 @@ var ScheduleCore = class _ScheduleCore {
21482
21606
  );
21483
21607
  }
21484
21608
  if (result.ok) {
21485
- if (historyPolicy === "clear-on-success") {
21609
+ if (historyPolicy === "checkpoint-on-success") {
21610
+ if (action.kind !== "persistence-acknowledge") {
21611
+ throw new Error(
21612
+ "checkpoint-on-success requires persistence acknowledge"
21613
+ );
21614
+ }
21615
+ const currentActivities = this.coreRuntime.state.getAllActivities();
21616
+ const currentLinks = this.coreRuntime.state.getAllLinks();
21617
+ const checkpoint = action.checkpoint;
21618
+ const boundary = checkpoint?.historyBoundary ?? this._undo.capturePersistenceBoundary();
21486
21619
  this._saveTracker.acknowledge(
21487
- this.coreRuntime.state.getAllActivities(),
21488
- this.coreRuntime.state.getAllLinks()
21620
+ checkpoint?.activities ?? currentActivities,
21621
+ checkpoint?.links ?? currentLinks,
21622
+ action.activities,
21623
+ action.links,
21624
+ currentActivities,
21625
+ currentLinks
21626
+ );
21627
+ this._undo.discardThrough(boundary);
21628
+ this._undo.applyPersistedIdentities(
21629
+ action.activities ?? [],
21630
+ action.links ?? []
21489
21631
  );
21490
- this._undo.clear();
21491
21632
  } else if (action.kind === "activity-lookahead-sync" && action.acknowledgePersistedLookahead === true) {
21492
21633
  this._saveTracker.acknowledgePersistedLookahead(
21493
21634
  result.changes.activities,