@candidstartup/event-sourced-spreadsheet-data 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,12 +1,20 @@
1
- import { LogEntry, CellValue, SpreadsheetData, EventLog, ItemOffsetMapping, Result, SpreadsheetDataError, ValidationError } from '@candidstartup/infinisheet-types';
1
+ import { LogEntry, CellValue, SpreadsheetData, EventLog, Result, StorageError, ItemOffsetMapping, ResultAsync, SpreadsheetDataError, ValidationError } from '@candidstartup/infinisheet-types';
2
2
 
3
+ /**
4
+ * Log entry that captures change from calling {@link SpreadsheetData.setCellValueAndFormat}
5
+ */
3
6
  interface SetCellValueAndFormatLogEntry extends LogEntry {
4
7
  type: 'SetCellValueAndFormat';
8
+ /** Row index of cell being modified */
5
9
  row: number;
10
+ /** Column index of cell being modified */
6
11
  column: number;
12
+ /** Value of cell being modified */
7
13
  value: CellValue;
14
+ /** Format of cell being modified */
8
15
  format?: string | undefined;
9
16
  }
17
+ /** Union of all types of Spreadsheet related log entries */
10
18
  type SpreadsheetLogEntry = SetCellValueAndFormatLogEntry;
11
19
 
12
20
  /**
@@ -19,7 +27,7 @@ declare enum _EventSourcedSnapshotBrand {
19
27
  _DO_NOT_USE = ""
20
28
  }
21
29
  /**
22
- * Opaque type representing a {@link SimpleSpreadsheetData} snapshot. All the
30
+ * Opaque type representing an {@link EventSourcedSpreadsheetData} snapshot. All the
23
31
  * internal implementation details are hidden from the exported API.
24
32
  */
25
33
  interface EventSourcedSnapshot {
@@ -31,18 +39,28 @@ interface EventSourcedSnapshot {
31
39
  *
32
40
  */
33
41
  declare class EventSourcedSpreadsheetData implements SpreadsheetData<EventSourcedSnapshot> {
34
- #private;
35
42
  constructor(eventLog: EventLog<SpreadsheetLogEntry>);
36
43
  subscribe(onDataChange: () => void): () => void;
37
44
  getSnapshot(): EventSourcedSnapshot;
45
+ getLoadStatus(snapshot: EventSourcedSnapshot): Result<boolean, StorageError>;
38
46
  getRowCount(snapshot: EventSourcedSnapshot): number;
39
47
  getRowItemOffsetMapping(_snapshot: EventSourcedSnapshot): ItemOffsetMapping;
40
48
  getColumnCount(snapshot: EventSourcedSnapshot): number;
41
49
  getColumnItemOffsetMapping(_snapshot: EventSourcedSnapshot): ItemOffsetMapping;
42
50
  getCellValue(snapshot: EventSourcedSnapshot, row: number, column: number): CellValue;
43
51
  getCellFormat(snapshot: EventSourcedSnapshot, row: number, column: number): string | undefined;
44
- setCellValueAndFormat(row: number, column: number, value: CellValue, format: string | undefined): Result<void, SpreadsheetDataError>;
52
+ setCellValueAndFormat(row: number, column: number, value: CellValue, format: string | undefined): ResultAsync<void, SpreadsheetDataError>;
45
53
  isValidCellValueAndFormat(_row: number, _column: number, _value: CellValue, _format: string | undefined): Result<void, ValidationError>;
54
+ private notifyListeners;
55
+ private getCellValueAndFormatEntry;
56
+ private syncLogs;
57
+ private syncLogsAsync;
58
+ private intervalId;
59
+ private isInSyncLogs;
60
+ private eventLog;
61
+ private listeners;
62
+ private content;
46
63
  }
47
64
 
48
- export { type EventSourcedSnapshot, EventSourcedSpreadsheetData, type SetCellValueAndFormatLogEntry, type SpreadsheetLogEntry, _EventSourcedSnapshotBrand };
65
+ export { EventSourcedSpreadsheetData, _EventSourcedSnapshotBrand };
66
+ export type { EventSourcedSnapshot, SetCellValueAndFormatLogEntry, SpreadsheetLogEntry };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { FixedSizeItemOffsetMapping, ok } from '@candidstartup/infinisheet-types';
1
+ import { FixedSizeItemOffsetMapping, ok, storageError, err } from '@candidstartup/infinisheet-types';
2
2
 
3
3
  const EVENT_LOG_CHECK_DELAY = 10000;
4
4
  /**
@@ -25,33 +25,36 @@ function asSnapshot(snapshot) {
25
25
  */
26
26
  class EventSourcedSpreadsheetData {
27
27
  constructor(eventLog) {
28
- this.#intervalId = undefined;
29
- this.#isInSyncLogs = false;
30
- this.#eventLog = eventLog;
31
- this.#listeners = [];
32
- this.#content = {
28
+ this.intervalId = undefined;
29
+ this.isInSyncLogs = false;
30
+ this.eventLog = eventLog;
31
+ this.listeners = [];
32
+ this.content = {
33
33
  endSequenceId: 0n,
34
34
  logSegment: { startSequenceId: 0n, entries: [] },
35
- isComplete: false,
35
+ loadStatus: ok(false),
36
36
  rowCount: 0,
37
37
  colCount: 0
38
38
  };
39
- this.#syncLogs();
39
+ this.syncLogs();
40
40
  }
41
41
  subscribe(onDataChange) {
42
- if (!this.#intervalId)
43
- this.#intervalId = setInterval(() => { this.#syncLogs(); }, EVENT_LOG_CHECK_DELAY);
44
- this.#listeners = [...this.#listeners, onDataChange];
42
+ if (!this.intervalId)
43
+ this.intervalId = setInterval(() => { this.syncLogs(); }, EVENT_LOG_CHECK_DELAY);
44
+ this.listeners = [...this.listeners, onDataChange];
45
45
  return () => {
46
- this.#listeners = this.#listeners.filter(l => l !== onDataChange);
47
- if (this.#listeners.length == 0 && this.#intervalId !== undefined) {
48
- clearInterval(this.#intervalId);
49
- this.#intervalId = undefined;
46
+ this.listeners = this.listeners.filter(l => l !== onDataChange);
47
+ if (this.listeners.length == 0 && this.intervalId !== undefined) {
48
+ clearInterval(this.intervalId);
49
+ this.intervalId = undefined;
50
50
  }
51
51
  };
52
52
  }
53
53
  getSnapshot() {
54
- return asSnapshot(this.#content);
54
+ return asSnapshot(this.content);
55
+ }
56
+ getLoadStatus(snapshot) {
57
+ return asContent(snapshot).loadStatus;
55
58
  }
56
59
  getRowCount(snapshot) {
57
60
  return asContent(snapshot).rowCount;
@@ -66,43 +69,54 @@ class EventSourcedSpreadsheetData {
66
69
  return columnItemOffsetMapping;
67
70
  }
68
71
  getCellValue(snapshot, row, column) {
69
- const entry = this.#getCellValueAndFormatEntry(snapshot, row, column);
72
+ const entry = this.getCellValueAndFormatEntry(snapshot, row, column);
70
73
  return entry?.value;
71
74
  }
72
75
  getCellFormat(snapshot, row, column) {
73
- const entry = this.#getCellValueAndFormatEntry(snapshot, row, column);
76
+ const entry = this.getCellValueAndFormatEntry(snapshot, row, column);
74
77
  return entry?.format;
75
78
  }
76
79
  setCellValueAndFormat(row, column, value, format) {
77
- const curr = this.#content;
78
- const result = this.#eventLog.addEntry({ type: 'SetCellValueAndFormat', row, column, value, format }, curr.endSequenceId);
79
- result.andTee(() => {
80
- if (this.#content == curr) {
80
+ const curr = this.content;
81
+ const result = this.eventLog.addEntry({ type: 'SetCellValueAndFormat', row, column, value, format }, curr.endSequenceId);
82
+ return result.andTee(() => {
83
+ if (this.content == curr) {
81
84
  // Nothing else has updated local copy (no async load has snuck in), so safe to do it myself avoiding round trip with event log
82
85
  curr.logSegment.entries.push({ type: 'SetCellValueAndFormat', row, column, value, format });
83
86
  // Snapshot semantics preserved by treating EventSourcedSnapshot as an immutable data structure which is
84
87
  // replaced with a modified copy on every update.
85
- this.#content = {
88
+ this.content = {
86
89
  endSequenceId: curr.endSequenceId + 1n,
87
90
  logSegment: curr.logSegment,
88
- isComplete: true,
91
+ loadStatus: ok(true),
89
92
  rowCount: Math.max(curr.rowCount, row + 1),
90
93
  colCount: Math.max(curr.colCount, column + 1)
91
94
  };
92
- this.#notifyListeners();
95
+ this.notifyListeners();
93
96
  }
94
- }).orElse((err) => { throw Error(err.message); });
95
- // Oh no, this method needs to become async ...
96
- return ok();
97
+ }).mapErr((err) => {
98
+ switch (err.type) {
99
+ case 'ConflictError':
100
+ if (this.content == curr) {
101
+ // Out of date wrt to event log, nothing else has updated content since then, so set
102
+ // status for in progress load and trigger sync.
103
+ this.content = { ...curr, loadStatus: ok(false) };
104
+ this.syncLogs();
105
+ }
106
+ return storageError("Client out of sync", 409);
107
+ case 'StorageError':
108
+ return err;
109
+ }
110
+ });
97
111
  }
98
112
  isValidCellValueAndFormat(_row, _column, _value, _format) {
99
113
  return ok();
100
114
  }
101
- #notifyListeners() {
102
- for (const listener of this.#listeners)
115
+ notifyListeners() {
116
+ for (const listener of this.listeners)
103
117
  listener();
104
118
  }
105
- #getCellValueAndFormatEntry(snapshot, row, column) {
119
+ getCellValueAndFormatEntry(snapshot, row, column) {
106
120
  const content = asContent(snapshot);
107
121
  const endIndex = Number(content.endSequenceId - content.logSegment.startSequenceId);
108
122
  for (let i = endIndex - 1; i >= 0; i--) {
@@ -112,32 +126,50 @@ class EventSourcedSpreadsheetData {
112
126
  }
113
127
  return undefined;
114
128
  }
115
- #syncLogs() {
116
- if (this.#isInSyncLogs)
129
+ syncLogs() {
130
+ if (this.isInSyncLogs)
117
131
  return;
118
- this.#syncLogsAsync().catch((reason) => { throw Error("Rejected promise from #syncLogsAsync", { cause: reason }); });
132
+ this.syncLogsAsync().catch((reason) => { throw Error("Rejected promise from syncLogsAsync", { cause: reason }); });
119
133
  }
120
- async #syncLogsAsync() {
121
- this.#isInSyncLogs = true;
134
+ async syncLogsAsync() {
135
+ this.isInSyncLogs = true;
122
136
  // Set up load of first batch of entries
123
- const segment = this.#content.logSegment;
137
+ const segment = this.content.logSegment;
124
138
  let isComplete = false;
125
139
  while (!isComplete) {
126
- const curr = this.#content;
140
+ const curr = this.content;
127
141
  const start = (segment.entries.length == 0) ? 'snapshot' : curr.endSequenceId;
128
- const result = await this.#eventLog.query(start, 'end');
142
+ const result = await this.eventLog.query(start, 'end');
143
+ if (curr != this.content) {
144
+ // Must have had setCellValueAndFormat complete successfully and update content to match
145
+ // Query result no longer relevant
146
+ break;
147
+ }
129
148
  if (!result.isOk()) {
130
- // Depending on error may need to retry (limited times, jitter and backoff), reload from scratch or panic
131
- throw Error("Error querying log entries");
149
+ if (result.error.type == 'InfinisheetRangeError') {
150
+ // Once we have proper snapshot system would expect this if client gets too far behind, for
151
+ // now shouldn't happen.
152
+ throw Error("Query resulted in range error, reload from scratch?", { cause: result.error });
153
+ }
154
+ // Could do some immediate retries of intermittent errors (limited times, jitter and backoff).
155
+ // For now wait for interval timer to try another sync
156
+ // For persistent failures should stop interval timer and have some mechanism for user to trigger
157
+ // manual retry.
158
+ this.content = { ...curr, loadStatus: err(result.error) };
159
+ this.notifyListeners();
160
+ break;
132
161
  }
133
162
  // Extend the current loaded segment.
134
163
  // Once snapshots supported need to look out for new snapshot and start new segment
135
164
  const value = result.value;
136
165
  if (segment.entries.length == 0)
137
166
  segment.startSequenceId = value.startSequenceId;
138
- else if (curr.endSequenceId != value.startSequenceId)
167
+ else if (curr.endSequenceId != value.startSequenceId) {
168
+ // Shouldn't happen unless we have buggy event log implementation
139
169
  throw Error(`Query returned start ${value.startSequenceId}, expected ${curr.endSequenceId}`);
170
+ }
140
171
  isComplete = value.isComplete;
172
+ // Don't create new snapshot if nothing has changed
141
173
  if (value.entries.length > 0) {
142
174
  segment.entries.push(...value.entries);
143
175
  // Create a new snapshot based on the new data
@@ -147,21 +179,27 @@ class EventSourcedSpreadsheetData {
147
179
  rowCount = Math.max(rowCount, entry.row + 1);
148
180
  colCount = Math.max(colCount, entry.column + 1);
149
181
  }
150
- this.#content = {
182
+ this.content = {
151
183
  endSequenceId: value.endSequenceId,
152
184
  logSegment: segment,
153
- isComplete, rowCount, colCount
185
+ loadStatus: ok(isComplete),
186
+ rowCount, colCount
154
187
  };
155
- this.#notifyListeners();
188
+ this.notifyListeners();
189
+ }
190
+ else if (curr.loadStatus.isErr() || curr.loadStatus.value != isComplete) {
191
+ // Careful, even if no entries returned, loadStatus may have changed
192
+ this.content = { ...curr, loadStatus: ok(isComplete) };
193
+ this.notifyListeners();
156
194
  }
157
195
  }
158
- this.#isInSyncLogs = false;
196
+ this.isInSyncLogs = false;
159
197
  }
160
- #intervalId;
161
- #isInSyncLogs;
162
- #eventLog;
163
- #listeners;
164
- #content;
198
+ intervalId;
199
+ isInSyncLogs;
200
+ eventLog;
201
+ listeners;
202
+ content;
165
203
  }
166
204
 
167
205
  export { EventSourcedSpreadsheetData, _EventSourcedSnapshotBrand };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/EventSourcedSpreadsheetData.ts"],"sourcesContent":["import type { CellValue, SpreadsheetData, ItemOffsetMapping, Result, \n SpreadsheetDataError, ValidationError, SequenceId, BlobId, EventLog } from \"@candidstartup/infinisheet-types\";\nimport { FixedSizeItemOffsetMapping, ok } from \"@candidstartup/infinisheet-types\";\n\nimport type { SpreadsheetLogEntry, SetCellValueAndFormatLogEntry } from \"./SpreadsheetLogEntry\";\n\nconst EVENT_LOG_CHECK_DELAY = 10000;\n\ninterface LogSegment {\n startSequenceId: SequenceId;\n entries: SpreadsheetLogEntry[];\n snapshot?: BlobId | undefined;\n}\n\ninterface EventSourcedSnapshotContent {\n endSequenceId: SequenceId;\n logSegment: LogSegment;\n isComplete: boolean;\n rowCount: number;\n colCount: number;\n}\n\n/** \n * Branding Enum. Used by {@link EventSourcedSnapshot} to ensure that\n * you'll get a type error if you pass some random object where a `EventSourcedSnapshot`\n * is expected.\n * @internal\n */\nexport enum _EventSourcedSnapshotBrand { _DO_NOT_USE=\"\" };\n\n/**\n * Opaque type representing a {@link SimpleSpreadsheetData} snapshot. All the\n * internal implementation details are hidden from the exported API.\n */\nexport interface EventSourcedSnapshot {\n /** @internal */\n _brand: _EventSourcedSnapshotBrand;\n}\n\nconst rowItemOffsetMapping = new FixedSizeItemOffsetMapping(30);\nconst columnItemOffsetMapping = new FixedSizeItemOffsetMapping(100);\n\nfunction asContent(snapshot: EventSourcedSnapshot) {\n return snapshot as unknown as EventSourcedSnapshotContent;\n}\n\nfunction asSnapshot(snapshot: EventSourcedSnapshotContent) {\n return snapshot as unknown as EventSourcedSnapshot;\n}\n\n/**\n * Event sourced implementation of {@link SpreadsheetData}\n *\n */\nexport class EventSourcedSpreadsheetData implements SpreadsheetData<EventSourcedSnapshot> {\n constructor (eventLog: EventLog<SpreadsheetLogEntry>) {\n this.#intervalId = undefined;\n this.#isInSyncLogs = false;\n this.#eventLog = eventLog;\n this.#listeners = [];\n this.#content = {\n endSequenceId: 0n,\n logSegment: { startSequenceId: 0n, entries: [] },\n isComplete: false,\n rowCount: 0,\n colCount: 0\n }\n\n this.#syncLogs();\n }\n\n subscribe(onDataChange: () => void): () => void {\n if (!this.#intervalId)\n this.#intervalId = setInterval(() => { this.#syncLogs() }, EVENT_LOG_CHECK_DELAY);\n this.#listeners = [...this.#listeners, onDataChange];\n return () => {\n this.#listeners = this.#listeners.filter(l => l !== onDataChange);\n if (this.#listeners.length == 0 && this.#intervalId !== undefined) {\n clearInterval(this.#intervalId);\n this.#intervalId = undefined;\n }\n }\n }\n\n getSnapshot(): EventSourcedSnapshot {\n return asSnapshot(this.#content);\n }\n\n getRowCount(snapshot: EventSourcedSnapshot): number {\n return asContent(snapshot).rowCount;\n }\n\n getRowItemOffsetMapping(_snapshot: EventSourcedSnapshot): ItemOffsetMapping {\n return rowItemOffsetMapping;\n }\n\n getColumnCount(snapshot: EventSourcedSnapshot): number {\n return asContent(snapshot).colCount;\n }\n\n getColumnItemOffsetMapping(_snapshot: EventSourcedSnapshot): ItemOffsetMapping {\n return columnItemOffsetMapping\n }\n\n getCellValue(snapshot: EventSourcedSnapshot, row: number, column: number): CellValue {\n const entry = this.#getCellValueAndFormatEntry(snapshot, row, column);\n return entry?.value;\n }\n\n getCellFormat(snapshot: EventSourcedSnapshot, row: number, column: number): string | undefined {\n const entry = this.#getCellValueAndFormatEntry(snapshot, row, column);\n return entry?.format;\n }\n\n setCellValueAndFormat(row: number, column: number, value: CellValue, format: string | undefined): Result<void,SpreadsheetDataError> {\n const curr = this.#content;\n\n const result = this.#eventLog.addEntry({ type: 'SetCellValueAndFormat', row, column, value, format}, curr.endSequenceId);\n result.andTee(() => {\n if (this.#content == curr) {\n // Nothing else has updated local copy (no async load has snuck in), so safe to do it myself avoiding round trip with event log\n curr.logSegment.entries.push({ type: 'SetCellValueAndFormat', row, column, value, format});\n\n // Snapshot semantics preserved by treating EventSourcedSnapshot as an immutable data structure which is \n // replaced with a modified copy on every update.\n this.#content = {\n endSequenceId: curr.endSequenceId + 1n,\n logSegment: curr.logSegment,\n isComplete: true,\n rowCount: Math.max(curr.rowCount, row+1),\n colCount: Math.max(curr.colCount, column+1)\n }\n\n this.#notifyListeners();\n }\n }).orElse((err) => { throw Error(err.message); });\n\n // Oh no, this method needs to become async ...\n return ok();\n }\n\n isValidCellValueAndFormat(_row: number, _column: number, _value: CellValue, _format: string | undefined): Result<void,ValidationError> {\n return ok(); \n }\n\n #notifyListeners() {\n for (const listener of this.#listeners)\n listener();\n }\n\n #getCellValueAndFormatEntry(snapshot: EventSourcedSnapshot, row: number, column: number): SetCellValueAndFormatLogEntry | undefined {\n const content = asContent(snapshot);\n const endIndex = Number(content.endSequenceId-content.logSegment.startSequenceId);\n for (let i = endIndex-1; i >= 0; i --) {\n const entry = content.logSegment.entries[i]!;\n if (entry.row == row && entry.column == column)\n return entry;\n }\n return undefined;\n }\n\n #syncLogs(): void {\n if (this.#isInSyncLogs)\n return;\n\n this.#syncLogsAsync().catch((reason) => { throw Error(\"Rejected promise from #syncLogsAsync\", { cause: reason }) });\n }\n\n async #syncLogsAsync(): Promise<void> {\n this.#isInSyncLogs = true;\n\n // Set up load of first batch of entries\n const segment = this.#content.logSegment;\n let isComplete = false;\n\n while (!isComplete) {\n const curr = this.#content;\n const start = (segment.entries.length == 0) ? 'snapshot' : curr.endSequenceId;\n const result = await this.#eventLog.query(start, 'end');\n\n if (!result.isOk()) {\n // Depending on error may need to retry (limited times, jitter and backoff), reload from scratch or panic\n throw Error(\"Error querying log entries\");\n }\n\n // Extend the current loaded segment.\n // Once snapshots supported need to look out for new snapshot and start new segment\n const value = result.value;\n if (segment.entries.length == 0)\n segment.startSequenceId = value.startSequenceId;\n else if (curr.endSequenceId != value.startSequenceId)\n throw Error(`Query returned start ${value.startSequenceId}, expected ${curr.endSequenceId}`);\n isComplete = value.isComplete;\n\n if (value.entries.length > 0) {\n segment.entries.push(...value.entries);\n\n // Create a new snapshot based on the new data\n let rowCount = curr.rowCount;\n let colCount = curr.colCount;\n for (const entry of value.entries) {\n rowCount = Math.max(rowCount, entry.row+1);\n colCount = Math.max(colCount, entry.column+1);\n }\n\n this.#content = {\n endSequenceId: value.endSequenceId,\n logSegment: segment,\n isComplete, rowCount, colCount\n }\n\n this.#notifyListeners();\n }\n }\n\n this.#isInSyncLogs = false;\n }\n\n #intervalId: ReturnType<typeof setInterval> | undefined;\n #isInSyncLogs: boolean;\n #eventLog: EventLog<SpreadsheetLogEntry>;\n #listeners: (() => void)[];\n #content: EventSourcedSnapshotContent;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/EventSourcedSpreadsheetData.ts"],"sourcesContent":["import type { CellValue, SpreadsheetData, ItemOffsetMapping, Result, ResultAsync, StorageError, \n SpreadsheetDataError, ValidationError, SequenceId, BlobId, EventLog } from \"@candidstartup/infinisheet-types\";\nimport { FixedSizeItemOffsetMapping, ok, err, storageError } from \"@candidstartup/infinisheet-types\";\n\nimport type { SpreadsheetLogEntry, SetCellValueAndFormatLogEntry } from \"./SpreadsheetLogEntry\";\n\nconst EVENT_LOG_CHECK_DELAY = 10000;\n\ninterface LogSegment {\n startSequenceId: SequenceId;\n entries: SpreadsheetLogEntry[];\n snapshot?: BlobId | undefined;\n}\n\ninterface EventSourcedSnapshotContent {\n endSequenceId: SequenceId;\n logSegment: LogSegment;\n loadStatus: Result<boolean,StorageError>;\n rowCount: number;\n colCount: number;\n}\n\n/** \n * Branding Enum. Used by {@link EventSourcedSnapshot} to ensure that\n * you'll get a type error if you pass some random object where a `EventSourcedSnapshot`\n * is expected.\n * @internal\n */\nexport enum _EventSourcedSnapshotBrand { _DO_NOT_USE=\"\" };\n\n/**\n * Opaque type representing an {@link EventSourcedSpreadsheetData} snapshot. All the\n * internal implementation details are hidden from the exported API.\n */\nexport interface EventSourcedSnapshot {\n /** @internal */\n _brand: _EventSourcedSnapshotBrand;\n}\n\nconst rowItemOffsetMapping = new FixedSizeItemOffsetMapping(30);\nconst columnItemOffsetMapping = new FixedSizeItemOffsetMapping(100);\n\nfunction asContent(snapshot: EventSourcedSnapshot) {\n return snapshot as unknown as EventSourcedSnapshotContent;\n}\n\nfunction asSnapshot(snapshot: EventSourcedSnapshotContent) {\n return snapshot as unknown as EventSourcedSnapshot;\n}\n\n/**\n * Event sourced implementation of {@link SpreadsheetData}\n *\n */\nexport class EventSourcedSpreadsheetData implements SpreadsheetData<EventSourcedSnapshot> {\n constructor (eventLog: EventLog<SpreadsheetLogEntry>) {\n this.intervalId = undefined;\n this.isInSyncLogs = false;\n this.eventLog = eventLog;\n this.listeners = [];\n this.content = {\n endSequenceId: 0n,\n logSegment: { startSequenceId: 0n, entries: [] },\n loadStatus: ok(false),\n rowCount: 0,\n colCount: 0\n }\n\n this.syncLogs();\n }\n\n subscribe(onDataChange: () => void): () => void {\n if (!this.intervalId)\n this.intervalId = setInterval(() => { this.syncLogs() }, EVENT_LOG_CHECK_DELAY);\n this.listeners = [...this.listeners, onDataChange];\n return () => {\n this.listeners = this.listeners.filter(l => l !== onDataChange);\n if (this.listeners.length == 0 && this.intervalId !== undefined) {\n clearInterval(this.intervalId);\n this.intervalId = undefined;\n }\n }\n }\n\n getSnapshot(): EventSourcedSnapshot {\n return asSnapshot(this.content);\n }\n\n getLoadStatus(snapshot: EventSourcedSnapshot): Result<boolean,StorageError> {\n return asContent(snapshot).loadStatus;\n }\n\n getRowCount(snapshot: EventSourcedSnapshot): number {\n return asContent(snapshot).rowCount;\n }\n\n getRowItemOffsetMapping(_snapshot: EventSourcedSnapshot): ItemOffsetMapping {\n return rowItemOffsetMapping;\n }\n\n getColumnCount(snapshot: EventSourcedSnapshot): number {\n return asContent(snapshot).colCount;\n }\n\n getColumnItemOffsetMapping(_snapshot: EventSourcedSnapshot): ItemOffsetMapping {\n return columnItemOffsetMapping\n }\n\n getCellValue(snapshot: EventSourcedSnapshot, row: number, column: number): CellValue {\n const entry = this.getCellValueAndFormatEntry(snapshot, row, column);\n return entry?.value;\n }\n\n getCellFormat(snapshot: EventSourcedSnapshot, row: number, column: number): string | undefined {\n const entry = this.getCellValueAndFormatEntry(snapshot, row, column);\n return entry?.format;\n }\n\n setCellValueAndFormat(row: number, column: number, value: CellValue, format: string | undefined): ResultAsync<void,SpreadsheetDataError> {\n const curr = this.content;\n\n const result = this.eventLog.addEntry({ type: 'SetCellValueAndFormat', row, column, value, format}, curr.endSequenceId);\n return result.andTee(() => {\n if (this.content == curr) {\n // Nothing else has updated local copy (no async load has snuck in), so safe to do it myself avoiding round trip with event log\n curr.logSegment.entries.push({ type: 'SetCellValueAndFormat', row, column, value, format});\n\n // Snapshot semantics preserved by treating EventSourcedSnapshot as an immutable data structure which is \n // replaced with a modified copy on every update.\n this.content = {\n endSequenceId: curr.endSequenceId + 1n,\n logSegment: curr.logSegment,\n loadStatus: ok(true),\n rowCount: Math.max(curr.rowCount, row+1),\n colCount: Math.max(curr.colCount, column+1)\n }\n\n this.notifyListeners();\n }\n }).mapErr((err): SpreadsheetDataError => {\n switch (err.type) {\n case 'ConflictError':\n if (this.content == curr) {\n // Out of date wrt to event log, nothing else has updated content since then, so set\n // status for in progress load and trigger sync.\n this.content = { ...curr, loadStatus: ok(false) }\n this.syncLogs();\n }\n return storageError(\"Client out of sync\", 409);\n case 'StorageError': \n return err;\n }\n });\n }\n\n isValidCellValueAndFormat(_row: number, _column: number, _value: CellValue, _format: string | undefined): Result<void,ValidationError> {\n return ok(); \n }\n\n private notifyListeners() {\n for (const listener of this.listeners)\n listener();\n }\n\n private getCellValueAndFormatEntry(snapshot: EventSourcedSnapshot, row: number, column: number): SetCellValueAndFormatLogEntry | undefined {\n const content = asContent(snapshot);\n const endIndex = Number(content.endSequenceId-content.logSegment.startSequenceId);\n for (let i = endIndex-1; i >= 0; i --) {\n const entry = content.logSegment.entries[i]!;\n if (entry.row == row && entry.column == column)\n return entry;\n }\n return undefined;\n }\n\n private syncLogs(): void {\n if (this.isInSyncLogs)\n return;\n\n this.syncLogsAsync().catch((reason) => { throw Error(\"Rejected promise from syncLogsAsync\", { cause: reason }) });\n }\n\n private async syncLogsAsync(): Promise<void> {\n this.isInSyncLogs = true;\n\n // Set up load of first batch of entries\n const segment = this.content.logSegment;\n let isComplete = false;\n\n while (!isComplete) {\n const curr = this.content;\n const start = (segment.entries.length == 0) ? 'snapshot' : curr.endSequenceId;\n const result = await this.eventLog.query(start, 'end');\n\n if (curr != this.content) {\n // Must have had setCellValueAndFormat complete successfully and update content to match\n // Query result no longer relevant\n break;\n }\n\n if (!result.isOk()) {\n if (result.error.type == 'InfinisheetRangeError') {\n // Once we have proper snapshot system would expect this if client gets too far behind, for\n // now shouldn't happen.\n throw Error(\"Query resulted in range error, reload from scratch?\", { cause: result.error });\n }\n\n // Could do some immediate retries of intermittent errors (limited times, jitter and backoff).\n // For now wait for interval timer to try another sync\n // For persistent failures should stop interval timer and have some mechanism for user to trigger\n // manual retry. \n this.content = { ...curr, loadStatus: err(result.error)};\n this.notifyListeners();\n break;\n }\n\n // Extend the current loaded segment.\n // Once snapshots supported need to look out for new snapshot and start new segment\n const value = result.value;\n if (segment.entries.length == 0)\n segment.startSequenceId = value.startSequenceId;\n else if (curr.endSequenceId != value.startSequenceId) {\n // Shouldn't happen unless we have buggy event log implementation\n throw Error(`Query returned start ${value.startSequenceId}, expected ${curr.endSequenceId}`);\n }\n isComplete = value.isComplete;\n\n // Don't create new snapshot if nothing has changed\n if (value.entries.length > 0) {\n segment.entries.push(...value.entries);\n\n // Create a new snapshot based on the new data\n let rowCount = curr.rowCount;\n let colCount = curr.colCount;\n for (const entry of value.entries) {\n rowCount = Math.max(rowCount, entry.row+1);\n colCount = Math.max(colCount, entry.column+1);\n }\n\n this.content = {\n endSequenceId: value.endSequenceId,\n logSegment: segment,\n loadStatus: ok(isComplete),\n rowCount, colCount\n }\n\n this.notifyListeners();\n } else if (curr.loadStatus.isErr() || curr.loadStatus.value != isComplete) {\n // Careful, even if no entries returned, loadStatus may have changed\n this.content = { ...curr, loadStatus: ok(isComplete) }\n this.notifyListeners();\n }\n }\n\n this.isInSyncLogs = false;\n }\n\n private intervalId: ReturnType<typeof setInterval> | undefined;\n private isInSyncLogs: boolean;\n private eventLog: EventLog<SpreadsheetLogEntry>;\n private listeners: (() => void)[];\n private content: EventSourcedSnapshotContent;\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@candidstartup/event-sourced-spreadsheet-data",
3
3
  "private": false,
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "description": "Event sourced implementation of SpreadsheetData",
6
6
  "author": "Tim Wiegand <tim.wiegand@thecandidstartup.org>",
7
7
  "license": "BSD-3-Clause",
@@ -49,7 +49,7 @@
49
49
  "test": "vitest"
50
50
  },
51
51
  "dependencies": {
52
- "@candidstartup/infinisheet-types": "^0.11.0"
52
+ "@candidstartup/infinisheet-types": "^0.12.0"
53
53
  },
54
- "gitHead": "0c72d04996502cb84f8720972bfda453e4989106"
54
+ "gitHead": "03b639807d367f0c167dc5b6653b3935412cbde8"
55
55
  }