@candidstartup/event-sourced-spreadsheet-data 0.11.0 → 0.13.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,14 +1,113 @@
1
- import { LogEntry, CellValue, SpreadsheetData, EventLog, ItemOffsetMapping, Result, SpreadsheetDataError, ValidationError } from '@candidstartup/infinisheet-types';
1
+ import { LogEntry, CellData, CellValue, CellFormat, BlobId, BlobDir, Result, StorageError, SequenceId, SpreadsheetViewport, EventLog, BlobStore, InfiniSheetWorker, PendingWorkflowMessage, SpreadsheetData, WorkerHost, ItemOffsetMapping, ResultAsync, SpreadsheetDataError, ValidationError } from '@candidstartup/infinisheet-types';
2
2
 
3
- interface SetCellValueAndFormatLogEntry extends LogEntry {
3
+ /**
4
+ * Log entry that captures change from calling {@link SpreadsheetData.setCellValueAndFormat}
5
+ */
6
+ interface SetCellValueAndFormatLogEntry extends LogEntry, CellData {
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;
7
- value: CellValue;
8
- format?: string | undefined;
9
12
  }
13
+ /** Union of all types of Spreadsheet related log entries */
10
14
  type SpreadsheetLogEntry = SetCellValueAndFormatLogEntry;
11
15
 
16
+ /**
17
+ * Entry stored in a {@link SpreadsheetCellMap}
18
+ * @internal
19
+ */
20
+ interface CellMapEntry extends CellData {
21
+ /** Index of entry within `LogSegment` */
22
+ logIndex?: number | undefined;
23
+ }
24
+ /** @internal */
25
+ interface CellMapExtents {
26
+ rowMin: number;
27
+ rowMax: number;
28
+ columnMin: number;
29
+ columnMax: number;
30
+ }
31
+ /** @internal */
32
+ declare class SpreadsheetCellMap {
33
+ constructor();
34
+ addEntries(entries: SetCellValueAndFormatLogEntry[], baseIndex: number): void;
35
+ addEntry(row: number, column: number, logIndex: number, value: CellValue, format?: CellFormat): void;
36
+ /** Return entry with highest index smaller than `snapshotIndex` */
37
+ findEntry(row: number, column: number, snapshotIndex: number): CellMapEntry | undefined;
38
+ calcExtents(snapshotIndex: number): CellMapExtents;
39
+ /** Saves snapshot containing highest entry smaller than snapshotIndex for each cell */
40
+ saveSnapshot(snapshotIndex: number): Uint8Array;
41
+ /** Initializes map with content of snapshot */
42
+ loadSnapshot(snapshot: Uint8Array): void;
43
+ /** Equivalent to {@link saveSnapshot} followed by {@link loadSnapshot} */
44
+ loadAsSnapshot(src: SpreadsheetCellMap, snapshotIndex: number): void;
45
+ private map;
46
+ }
47
+
48
+ /** In-memory representation of snapshot metadata
49
+ * @internal
50
+ */
51
+ declare class SpreadsheetSnapshot {
52
+ constructor(id: BlobId, snapshotDir: BlobDir<unknown>, tileDir: BlobDir<unknown>);
53
+ saveIndex(): Promise<Result<void, StorageError>>;
54
+ loadIndex(): Promise<Result<void, StorageError>>;
55
+ saveTile(rowMin: number, colMin: number, rowCount: number, colCount: number, blob: Uint8Array): Promise<Result<void, StorageError>>;
56
+ loadTile(rowMin: number, colMin: number, rowCount: number, colCount: number): Promise<Result<Uint8Array, StorageError>>;
57
+ id: BlobId;
58
+ snapshotDir: BlobDir<unknown>;
59
+ tileDir: BlobDir<unknown>;
60
+ rowCount: number;
61
+ colCount: number;
62
+ }
63
+ declare function openSnapshot(rootDir: BlobDir<unknown>, snapshotId: BlobId): Promise<Result<SpreadsheetSnapshot, StorageError>>;
64
+
65
+ /** @internal */
66
+ interface LogSegment {
67
+ startSequenceId: SequenceId;
68
+ entries: SpreadsheetLogEntry[];
69
+ snapshotId?: BlobId | undefined;
70
+ snapshot?: SpreadsheetSnapshot | undefined;
71
+ }
72
+ /** @internal */
73
+ interface EventSourcedSnapshotContent {
74
+ endSequenceId: SequenceId;
75
+ logSegment: LogSegment;
76
+ logLoadStatus: Result<boolean, StorageError>;
77
+ cellMap: SpreadsheetCellMap;
78
+ mapLoadStatus: Result<boolean, StorageError>;
79
+ rowCount: number;
80
+ colCount: number;
81
+ viewport: SpreadsheetViewport | undefined;
82
+ }
83
+ /**
84
+ * Low level engine for working with spreadsheet data
85
+ * @internal
86
+ */
87
+ declare abstract class EventSourcedSpreadsheetEngine {
88
+ constructor(eventLog: EventLog<SpreadsheetLogEntry>, blobStore: BlobStore<unknown>, viewport?: SpreadsheetViewport);
89
+ setViewport(viewport: SpreadsheetViewport | undefined): void;
90
+ protected syncLogs(endSequenceId?: SequenceId): void;
91
+ protected abstract notifyListeners(): void;
92
+ protected syncLogsAsync(endSequenceId?: SequenceId): Promise<void>;
93
+ protected eventLog: EventLog<SpreadsheetLogEntry>;
94
+ protected blobStore: BlobStore<unknown>;
95
+ protected content: EventSourcedSnapshotContent;
96
+ private isInSyncLogs;
97
+ }
98
+
99
+ /**
100
+ * Event sourced implementation of spreadsheet {@link EventLog} triggered workflows
101
+ *
102
+ */
103
+ declare class EventSourcedSpreadsheetWorkflow extends EventSourcedSpreadsheetEngine {
104
+ constructor(eventLog: EventLog<SpreadsheetLogEntry>, blobStore: BlobStore<unknown>, worker: InfiniSheetWorker<PendingWorkflowMessage>);
105
+ protected notifyListeners(): void;
106
+ private onReceiveMessage;
107
+ private onReceiveMessageAsync;
108
+ protected worker: InfiniSheetWorker<PendingWorkflowMessage>;
109
+ }
110
+
12
111
  /**
13
112
  * Branding Enum. Used by {@link EventSourcedSnapshot} to ensure that
14
113
  * you'll get a type error if you pass some random object where a `EventSourcedSnapshot`
@@ -19,30 +118,52 @@ declare enum _EventSourcedSnapshotBrand {
19
118
  _DO_NOT_USE = ""
20
119
  }
21
120
  /**
22
- * Opaque type representing a {@link SimpleSpreadsheetData} snapshot. All the
121
+ * Opaque type representing an {@link EventSourcedSpreadsheetData} snapshot. All the
23
122
  * internal implementation details are hidden from the exported API.
24
123
  */
25
124
  interface EventSourcedSnapshot {
26
125
  /** @internal */
27
126
  _brand: _EventSourcedSnapshotBrand;
28
127
  }
128
+ /** Additional options for {@link EventSourcedSpreadsheetData} */
129
+ interface EventSourcedSpreadsheetDataOptions {
130
+ /** Minimum number of log entries before creation of next snapshot
131
+ * @defaultValue 100
132
+ */
133
+ snapshotInterval?: number | undefined;
134
+ /** Should pending workflows be restarted on initial load of event log?
135
+ * @defaultValue false
136
+ */
137
+ restartPendingWorkflowsOnLoad?: boolean | undefined;
138
+ /** Initial viewport */
139
+ viewport?: SpreadsheetViewport | undefined;
140
+ }
29
141
  /**
30
142
  * Event sourced implementation of {@link SpreadsheetData}
31
143
  *
32
144
  */
33
- declare class EventSourcedSpreadsheetData implements SpreadsheetData<EventSourcedSnapshot> {
34
- #private;
35
- constructor(eventLog: EventLog<SpreadsheetLogEntry>);
145
+ declare class EventSourcedSpreadsheetData extends EventSourcedSpreadsheetEngine implements SpreadsheetData<EventSourcedSnapshot> {
146
+ constructor(eventLog: EventLog<SpreadsheetLogEntry>, blobStore: BlobStore<unknown>, workerHost?: WorkerHost<PendingWorkflowMessage>, options?: EventSourcedSpreadsheetDataOptions);
36
147
  subscribe(onDataChange: () => void): () => void;
37
148
  getSnapshot(): EventSourcedSnapshot;
149
+ getLoadStatus(snapshot: EventSourcedSnapshot): Result<boolean, StorageError>;
38
150
  getRowCount(snapshot: EventSourcedSnapshot): number;
39
151
  getRowItemOffsetMapping(_snapshot: EventSourcedSnapshot): ItemOffsetMapping;
40
152
  getColumnCount(snapshot: EventSourcedSnapshot): number;
41
153
  getColumnItemOffsetMapping(_snapshot: EventSourcedSnapshot): ItemOffsetMapping;
42
154
  getCellValue(snapshot: EventSourcedSnapshot, row: number, column: number): CellValue;
43
- getCellFormat(snapshot: EventSourcedSnapshot, row: number, column: number): string | undefined;
44
- setCellValueAndFormat(row: number, column: number, value: CellValue, format: string | undefined): Result<void, SpreadsheetDataError>;
45
- isValidCellValueAndFormat(_row: number, _column: number, _value: CellValue, _format: string | undefined): Result<void, ValidationError>;
155
+ getCellFormat(snapshot: EventSourcedSnapshot, row: number, column: number): CellFormat;
156
+ setCellValueAndFormat(row: number, column: number, value: CellValue, format: CellFormat): ResultAsync<void, SpreadsheetDataError>;
157
+ isValidCellValueAndFormat(_row: number, _column: number, _value: CellValue, _format: CellFormat): Result<void, ValidationError>;
158
+ getViewport(snapshot: EventSourcedSnapshot): SpreadsheetViewport | undefined;
159
+ protected notifyListeners(): void;
160
+ private addEntry;
161
+ private getCellValueAndFormatEntry;
162
+ protected workerHost?: WorkerHost<PendingWorkflowMessage> | undefined;
163
+ private snapshotInterval;
164
+ private intervalId;
165
+ private listeners;
46
166
  }
47
167
 
48
- export { type EventSourcedSnapshot, EventSourcedSpreadsheetData, type SetCellValueAndFormatLogEntry, type SpreadsheetLogEntry, _EventSourcedSnapshotBrand };
168
+ export { EventSourcedSpreadsheetData, EventSourcedSpreadsheetEngine, EventSourcedSpreadsheetWorkflow, SpreadsheetCellMap, SpreadsheetSnapshot, _EventSourcedSnapshotBrand, openSnapshot };
169
+ export type { CellMapEntry, CellMapExtents, EventSourcedSnapshot, EventSourcedSnapshotContent, EventSourcedSpreadsheetDataOptions, LogSegment, SetCellValueAndFormatLogEntry, SpreadsheetLogEntry };
package/dist/index.js CHANGED
@@ -1,6 +1,438 @@
1
- import { FixedSizeItemOffsetMapping, ok } from '@candidstartup/infinisheet-types';
1
+ import { rowColCoordsToRef, rowColRefToCoords, err, ok, equalViewports, ResultAsync, FixedSizeItemOffsetMapping, storageError } from '@candidstartup/infinisheet-types';
2
2
 
3
- const EVENT_LOG_CHECK_DELAY = 10000;
3
+ function bestEntry(entry, snapshotIndex) {
4
+ if (!Array.isArray(entry))
5
+ return (entry.logIndex === undefined || entry.logIndex < snapshotIndex) ? entry : undefined;
6
+ // Future optimization: Check 3 entries then switch to binary chop
7
+ for (let i = entry.length - 1; i >= 0; i--) {
8
+ const t = entry[i];
9
+ if (t.logIndex === undefined || t.logIndex < snapshotIndex)
10
+ return t;
11
+ }
12
+ return undefined;
13
+ }
14
+ /** @internal */
15
+ class SpreadsheetCellMap {
16
+ constructor() {
17
+ this.map = new Map();
18
+ }
19
+ addEntries(entries, baseIndex) {
20
+ entries.forEach((value, index) => {
21
+ this.addEntry(value.row, value.column, baseIndex + index, value.value, value.format);
22
+ });
23
+ }
24
+ addEntry(row, column, logIndex, value, format) {
25
+ const key = rowColCoordsToRef(row, column);
26
+ const newEntry = { value, format, logIndex };
27
+ const entry = this.map.get(key);
28
+ if (!entry) {
29
+ this.map.set(key, newEntry);
30
+ return;
31
+ }
32
+ if (Array.isArray(entry)) {
33
+ entry.push(newEntry);
34
+ }
35
+ else {
36
+ this.map.set(key, [entry, newEntry]);
37
+ }
38
+ }
39
+ /** Return entry with highest index smaller than `snapshotIndex` */
40
+ findEntry(row, column, snapshotIndex) {
41
+ const key = rowColCoordsToRef(row, column);
42
+ const entry = this.map.get(key);
43
+ return entry ? bestEntry(entry, snapshotIndex) : undefined;
44
+ }
45
+ calcExtents(snapshotIndex) {
46
+ let extents = undefined;
47
+ for (const [key, value] of this.map.entries()) {
48
+ const entry = bestEntry(value, snapshotIndex);
49
+ if (entry) {
50
+ const [row, column] = rowColRefToCoords(key);
51
+ if (extents) {
52
+ extents.rowMin = Math.min(extents.rowMin, row);
53
+ extents.rowMax = Math.max(extents.rowMax, row + 1);
54
+ extents.columnMin = Math.min(extents.columnMin, column);
55
+ extents.columnMax = Math.max(extents.columnMax, column + 1);
56
+ }
57
+ else {
58
+ extents = { rowMin: row, rowMax: row + 1, columnMin: column, columnMax: column + 1 };
59
+ }
60
+ }
61
+ }
62
+ return extents ? extents : { rowMin: 0, columnMin: 0, rowMax: 0, columnMax: 0 };
63
+ }
64
+ /** Saves snapshot containing highest entry smaller than snapshotIndex for each cell */
65
+ saveSnapshot(snapshotIndex) {
66
+ const output = {};
67
+ for (const [key, value] of this.map.entries()) {
68
+ const entry = bestEntry(value, snapshotIndex);
69
+ if (entry) {
70
+ const { logIndex: _logIndex, ...rest } = entry;
71
+ output[key] = rest;
72
+ }
73
+ }
74
+ const json = JSON.stringify(output);
75
+ const encoder = new TextEncoder;
76
+ return encoder.encode(json);
77
+ }
78
+ /** Initializes map with content of snapshot */
79
+ loadSnapshot(snapshot) {
80
+ const decoder = new TextDecoder;
81
+ const inputString = decoder.decode(snapshot);
82
+ const input = JSON.parse(inputString);
83
+ if (!input || typeof input !== 'object')
84
+ throw Error("Failed to parse snapshot, root is not an object");
85
+ this.map.clear();
86
+ for (const [key, anyValue] of Object.entries(input)) {
87
+ // Tracer bullet, no validation that value is valid!
88
+ const value = anyValue;
89
+ this.map.set(key, value);
90
+ }
91
+ }
92
+ /** Equivalent to {@link saveSnapshot} followed by {@link loadSnapshot} */
93
+ loadAsSnapshot(src, snapshotIndex) {
94
+ for (const [key, value] of src.map.entries()) {
95
+ const entry = bestEntry(value, snapshotIndex);
96
+ if (entry) {
97
+ const { logIndex: _logIndex, ...rest } = entry;
98
+ this.map.set(key, rest);
99
+ }
100
+ }
101
+ }
102
+ map;
103
+ }
104
+
105
+ function formatName(rowMin, colMin, rowCount, colCount) {
106
+ return `${rowMin}-${colMin}-${rowCount}-${colCount}`;
107
+ }
108
+ /** In-memory representation of snapshot metadata
109
+ * @internal
110
+ */
111
+ class SpreadsheetSnapshot {
112
+ constructor(id, snapshotDir, tileDir) {
113
+ this.id = id;
114
+ this.snapshotDir = snapshotDir;
115
+ this.tileDir = tileDir;
116
+ this.rowCount = 0;
117
+ this.colCount = 0;
118
+ }
119
+ async saveIndex() {
120
+ const meta = { rowCount: this.rowCount, colCount: this.colCount };
121
+ const json = JSON.stringify(meta);
122
+ const encoder = new TextEncoder;
123
+ const blob = encoder.encode(json);
124
+ const blobResult = await this.snapshotDir.writeBlob("index", blob);
125
+ if (blobResult.isErr()) {
126
+ if (blobResult.error.type === "StorageError")
127
+ return err(blobResult.error);
128
+ throw Error("Messed up my blobs", { cause: blobResult.error });
129
+ }
130
+ return ok();
131
+ }
132
+ async loadIndex() {
133
+ const blobResult = await this.snapshotDir.readBlob("index");
134
+ if (blobResult.isErr()) {
135
+ if (blobResult.error.type === "StorageError")
136
+ return err(blobResult.error);
137
+ throw Error("Messed up my blobs", { cause: blobResult.error });
138
+ }
139
+ const decoder = new TextDecoder;
140
+ const inputString = decoder.decode(blobResult.value);
141
+ // Tracer bullet, no validation
142
+ const input = JSON.parse(inputString);
143
+ this.rowCount = input.rowCount;
144
+ this.colCount = input.colCount;
145
+ return ok();
146
+ }
147
+ async saveTile(rowMin, colMin, rowCount, colCount, blob) {
148
+ const blobResult = await this.tileDir.writeBlob(formatName(rowMin, colMin, rowCount, colCount), blob);
149
+ if (blobResult.isErr()) {
150
+ if (blobResult.error.type === "StorageError")
151
+ return err(blobResult.error);
152
+ throw Error("Messed up my blobs", { cause: blobResult.error });
153
+ }
154
+ this.rowCount = Math.max(this.rowCount, rowMin + rowCount);
155
+ this.colCount = Math.max(this.colCount, colMin + colCount);
156
+ return ok();
157
+ }
158
+ async loadTile(rowMin, colMin, rowCount, colCount) {
159
+ const blobResult = await this.tileDir.readBlob(formatName(rowMin, colMin, rowCount, colCount));
160
+ if (blobResult.isErr()) {
161
+ if (blobResult.error.type === "StorageError")
162
+ return err(blobResult.error);
163
+ throw Error("Messed up my blobs", { cause: blobResult.error });
164
+ }
165
+ return ok(blobResult.value);
166
+ }
167
+ id;
168
+ snapshotDir;
169
+ tileDir;
170
+ rowCount;
171
+ colCount;
172
+ }
173
+ async function openSnapshot(rootDir, snapshotId) {
174
+ const snapshotResult = await rootDir.getDir(snapshotId);
175
+ if (snapshotResult.isErr()) {
176
+ if (snapshotResult.error.type == "StorageError")
177
+ return err(snapshotResult.error);
178
+ throw Error("Messed up my blobs", { cause: snapshotResult.error });
179
+ }
180
+ const snapshotDir = snapshotResult.value;
181
+ const tileDirResult = await snapshotDir.getDir("tiles");
182
+ if (tileDirResult.isErr()) {
183
+ if (tileDirResult.error.type == "StorageError")
184
+ return err(tileDirResult.error);
185
+ throw Error("Messed up my blobs", { cause: tileDirResult.error });
186
+ }
187
+ const tileDir = tileDirResult.value;
188
+ return ok(new SpreadsheetSnapshot(snapshotId, snapshotDir, tileDir));
189
+ }
190
+
191
+ /** @internal */
192
+ function forkSegment(segment, cellMap, snapshot) {
193
+ const index = Number(snapshot.sequenceId - segment.startSequenceId);
194
+ if (index < 0 || index >= segment.entries.length)
195
+ throw Error("forkSegment: snapshotId not within segment");
196
+ const newSegment = { startSequenceId: snapshot.sequenceId, entries: segment.entries.slice(index), snapshotId: snapshot.blobId };
197
+ const newMap = new SpreadsheetCellMap;
198
+ newMap.loadAsSnapshot(cellMap, index);
199
+ newMap.addEntries(newSegment.entries, 0);
200
+ return [newSegment, newMap];
201
+ }
202
+ async function cellMapFromSnapshot(segment, blobStore) {
203
+ let snapshot = segment.snapshot;
204
+ if (!snapshot) {
205
+ const dir = await blobStore.getRootDir();
206
+ if (dir.isErr())
207
+ return err(dir.error);
208
+ const result = await openSnapshot(dir.value, segment.snapshotId);
209
+ if (result.isErr())
210
+ return err(result.error);
211
+ snapshot = result.value;
212
+ const index = await snapshot.loadIndex();
213
+ if (index.isErr())
214
+ return err(index.error);
215
+ segment.snapshot = snapshot;
216
+ }
217
+ const blob = await snapshot.loadTile(0, 0, snapshot.rowCount, snapshot.colCount);
218
+ if (blob.isErr())
219
+ return err(blob.error);
220
+ const cellMap = new SpreadsheetCellMap;
221
+ cellMap.loadSnapshot(blob.value);
222
+ cellMap.addEntries(segment.entries, 0);
223
+ return ok(cellMap);
224
+ }
225
+ async function updateContent(curr, value, blobStore) {
226
+ let segment = curr.logSegment;
227
+ let cellMap = curr.cellMap;
228
+ let rowCount = curr.rowCount;
229
+ let colCount = curr.colCount;
230
+ let entries = value.entries;
231
+ const startSequenceId = value.startSequenceId;
232
+ const snapshotId = entries[0].snapshot;
233
+ // Start a new segment and load from snapshot if we've jumped to id past what we currently have
234
+ if (snapshotId && curr.endSequenceId != startSequenceId) {
235
+ segment = { startSequenceId, entries, snapshotId };
236
+ const result = await cellMapFromSnapshot(segment, blobStore);
237
+ if (result.isErr())
238
+ return err(result.error);
239
+ cellMap = result.value;
240
+ rowCount = segment.snapshot.rowCount;
241
+ colCount = segment.snapshot.colCount;
242
+ for (const entry of entries) {
243
+ rowCount = Math.max(rowCount, entry.row + 1);
244
+ colCount = Math.max(colCount, entry.column + 1);
245
+ }
246
+ }
247
+ else {
248
+ if (curr.endSequenceId != startSequenceId) {
249
+ // Shouldn't happen unless we have buggy event log implementation
250
+ throw Error(`Query returned start ${value.startSequenceId}, expected ${curr.endSequenceId}`);
251
+ }
252
+ if (value.lastSnapshot) {
253
+ const { sequenceId } = value.lastSnapshot;
254
+ if (sequenceId < curr.endSequenceId) {
255
+ // Snapshot has completed in entry we already have. Fork segment at that point then process value as normal.
256
+ [segment, cellMap] = forkSegment(segment, cellMap, value.lastSnapshot);
257
+ }
258
+ else if (sequenceId < value.endSequenceId) {
259
+ // Snapshot in returned value. Add entries up to snapshot to current cell map then start new segment from that.
260
+ const indexInValue = Number(sequenceId - startSequenceId);
261
+ const baseIndex = segment.entries.length;
262
+ for (let i = 0; i < indexInValue; i++) {
263
+ const entry = entries[i];
264
+ rowCount = Math.max(rowCount, entry.row + 1);
265
+ colCount = Math.max(colCount, entry.column + 1);
266
+ cellMap.addEntry(entry.row, entry.column, baseIndex + i, entry.value, entry.format);
267
+ }
268
+ entries = entries.slice(indexInValue);
269
+ const oldCellMap = cellMap;
270
+ cellMap = new SpreadsheetCellMap;
271
+ cellMap.loadAsSnapshot(oldCellMap, baseIndex + indexInValue);
272
+ const emptyArray = [];
273
+ segment = { startSequenceId: startSequenceId + BigInt(indexInValue), entries: emptyArray, snapshotId: value.lastSnapshot.blobId };
274
+ // Segment extension code below will add the remaining values
275
+ }
276
+ // Snapshot must be in later page of results. Deal with it when we get there.
277
+ }
278
+ // Extend the current loaded segment.
279
+ cellMap.addEntries(entries, segment.entries.length);
280
+ segment.entries.push(...entries);
281
+ for (const entry of entries) {
282
+ rowCount = Math.max(rowCount, entry.row + 1);
283
+ colCount = Math.max(colCount, entry.column + 1);
284
+ }
285
+ }
286
+ // Create new content based on the new data
287
+ return ok({
288
+ endSequenceId: value.endSequenceId,
289
+ logSegment: segment,
290
+ logLoadStatus: ok(value.isComplete),
291
+ cellMap,
292
+ mapLoadStatus: ok(true),
293
+ rowCount, colCount,
294
+ viewport: curr.viewport
295
+ });
296
+ }
297
+ /**
298
+ * Low level engine for working with spreadsheet data
299
+ * @internal
300
+ */
301
+ class EventSourcedSpreadsheetEngine {
302
+ constructor(eventLog, blobStore, viewport) {
303
+ this.isInSyncLogs = false;
304
+ this.eventLog = eventLog;
305
+ this.blobStore = blobStore;
306
+ this.content = {
307
+ endSequenceId: 0n,
308
+ logSegment: { startSequenceId: 0n, entries: [] },
309
+ logLoadStatus: ok(false),
310
+ cellMap: new SpreadsheetCellMap,
311
+ mapLoadStatus: ok(true), // Empty map is consistent with current state of log
312
+ rowCount: 0,
313
+ colCount: 0,
314
+ viewport
315
+ };
316
+ }
317
+ setViewport(viewport) {
318
+ const curr = this.content;
319
+ if (equalViewports(curr.viewport, viewport))
320
+ return;
321
+ // Take our own copy of viewport to ensure that it's immutable
322
+ const viewportCopy = viewport ? { ...viewport } : undefined;
323
+ this.content = { ...curr, viewport: viewportCopy, mapLoadStatus: ok(false) };
324
+ this.notifyListeners();
325
+ }
326
+ // Sync in-memory representation so that it includes range to endSequenceId (defaults to end of log)
327
+ syncLogs(endSequenceId) {
328
+ this.syncLogsAsync(endSequenceId).catch((reason) => { throw Error("Rejected promise from syncLogsAsync", { cause: reason }); });
329
+ }
330
+ async syncLogsAsync(endSequenceId) {
331
+ if (this.isInSyncLogs)
332
+ return Promise.resolve();
333
+ // Already have everything required?
334
+ if (endSequenceId && endSequenceId <= this.content.endSequenceId)
335
+ return Promise.resolve();
336
+ this.isInSyncLogs = true;
337
+ // Set up load of first batch of entries
338
+ let isComplete = false;
339
+ while (!isComplete) {
340
+ const curr = this.content;
341
+ const initialLoad = (curr.endSequenceId === 0n);
342
+ const start = initialLoad ? 'snapshot' : curr.endSequenceId;
343
+ const end = endSequenceId ? endSequenceId : 'end';
344
+ const segment = curr.logSegment;
345
+ const result = await this.eventLog.query(start, end, initialLoad ? undefined : segment.startSequenceId);
346
+ if (curr != this.content) {
347
+ // Must have had setCellValueAndFormat complete successfully and update content to match.
348
+ // Query result no longer relevant
349
+ break;
350
+ }
351
+ if (!result.isOk()) {
352
+ if (result.error.type == 'InfinisheetRangeError') {
353
+ // Once we have proper snapshot system would expect this if client gets too far behind, for
354
+ // now shouldn't happen.
355
+ throw Error("Query resulted in range error, reload from scratch?", { cause: result.error });
356
+ }
357
+ // Could do some immediate retries of intermittent errors (limited times, jitter and backoff).
358
+ // For now wait for interval timer to try another sync
359
+ // For persistent failures should stop interval timer and have some mechanism for user to trigger
360
+ // manual retry.
361
+ this.content = { ...curr, logLoadStatus: err(result.error) };
362
+ this.notifyListeners();
363
+ break;
364
+ }
365
+ const value = result.value;
366
+ isComplete = value.isComplete;
367
+ // Don't create new snapshot if nothing has changed
368
+ if (value.entries.length > 0) {
369
+ const result = await updateContent(curr, value, this.blobStore);
370
+ this.content = result.isOk() ? result.value : { ...curr, logLoadStatus: err(result.error) };
371
+ this.notifyListeners();
372
+ }
373
+ else if (curr.logLoadStatus.isErr() || curr.logLoadStatus.value != isComplete) {
374
+ // Careful, even if no entries returned, loadStatus may have changed
375
+ this.content = { ...curr, logLoadStatus: ok(isComplete) };
376
+ this.notifyListeners();
377
+ }
378
+ }
379
+ this.isInSyncLogs = false;
380
+ }
381
+ eventLog;
382
+ blobStore;
383
+ content;
384
+ isInSyncLogs;
385
+ }
386
+
387
+ /**
388
+ * Event sourced implementation of spreadsheet {@link EventLog} triggered workflows
389
+ *
390
+ */
391
+ class EventSourcedSpreadsheetWorkflow extends EventSourcedSpreadsheetEngine {
392
+ constructor(eventLog, blobStore, worker) {
393
+ super(eventLog, blobStore);
394
+ this.worker = worker;
395
+ worker.onReceiveMessage = (message) => this.onReceiveMessage(message);
396
+ }
397
+ notifyListeners() { }
398
+ onReceiveMessage(message) {
399
+ if (message.workflow !== 'snapshot')
400
+ throw Error(`Unknown workflow ${message.workflow}`);
401
+ return new ResultAsync(this.onReceiveMessageAsync(message));
402
+ }
403
+ async onReceiveMessageAsync(message) {
404
+ const endSequenceId = message.sequenceId;
405
+ await this.syncLogsAsync(endSequenceId);
406
+ if (this.content.logLoadStatus.isErr())
407
+ return err(this.content.logLoadStatus.error);
408
+ if (this.content.mapLoadStatus.isErr())
409
+ return err(this.content.mapLoadStatus.error);
410
+ if (!this.content.logLoadStatus.value)
411
+ throw Error("Somehow syncLogs() is still in progress despite promise having resolved");
412
+ const { logSegment, cellMap } = this.content;
413
+ const snapshotIndex = Number(endSequenceId - logSegment.startSequenceId);
414
+ const blob = cellMap.saveSnapshot(snapshotIndex);
415
+ const name = message.sequenceId.toString();
416
+ const dir = await this.blobStore.getRootDir();
417
+ if (dir.isErr())
418
+ return err(dir.error);
419
+ const snapshotResult = await openSnapshot(dir.value, name);
420
+ if (snapshotResult.isErr())
421
+ return err(snapshotResult.error);
422
+ const snapshot = snapshotResult.value;
423
+ const blobResult = await snapshot.saveTile(0, 0, this.content.rowCount, this.content.colCount, blob);
424
+ if (blobResult.isErr())
425
+ return err(blobResult.error);
426
+ const indexResult = await snapshot.saveIndex();
427
+ if (indexResult.isErr())
428
+ return err(indexResult.error);
429
+ return this.eventLog.setMetadata(message.sequenceId, { pending: undefined, snapshot: name });
430
+ }
431
+ worker;
432
+ }
433
+
434
+ // How often to check for new event log entries (ms)
435
+ const EVENT_LOG_CHECK_INTERVAL = 10000;
4
436
  /**
5
437
  * Branding Enum. Used by {@link EventSourcedSnapshot} to ensure that
6
438
  * you'll get a type error if you pass some random object where a `EventSourcedSnapshot`
@@ -23,35 +455,37 @@ function asSnapshot(snapshot) {
23
455
  * Event sourced implementation of {@link SpreadsheetData}
24
456
  *
25
457
  */
26
- class EventSourcedSpreadsheetData {
27
- constructor(eventLog) {
28
- this.#intervalId = undefined;
29
- this.#isInSyncLogs = false;
30
- this.#eventLog = eventLog;
31
- this.#listeners = [];
32
- this.#content = {
33
- endSequenceId: 0n,
34
- logSegment: { startSequenceId: 0n, entries: [] },
35
- isComplete: false,
36
- rowCount: 0,
37
- colCount: 0
38
- };
39
- this.#syncLogs();
458
+ class EventSourcedSpreadsheetData extends EventSourcedSpreadsheetEngine {
459
+ constructor(eventLog, blobStore, workerHost, options) {
460
+ super(eventLog, blobStore, options?.viewport);
461
+ this.intervalId = undefined;
462
+ this.workerHost = workerHost;
463
+ this.snapshotInterval = options?.snapshotInterval || 100;
464
+ this.listeners = [];
465
+ this.syncLogs();
40
466
  }
41
467
  subscribe(onDataChange) {
42
- if (!this.#intervalId)
43
- this.#intervalId = setInterval(() => { this.#syncLogs(); }, EVENT_LOG_CHECK_DELAY);
44
- this.#listeners = [...this.#listeners, onDataChange];
468
+ if (!this.intervalId)
469
+ this.intervalId = setInterval(() => { this.syncLogs(); }, EVENT_LOG_CHECK_INTERVAL);
470
+ this.listeners = [...this.listeners, onDataChange];
45
471
  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;
472
+ this.listeners = this.listeners.filter(l => l !== onDataChange);
473
+ if (this.listeners.length == 0 && this.intervalId !== undefined) {
474
+ clearInterval(this.intervalId);
475
+ this.intervalId = undefined;
50
476
  }
51
477
  };
52
478
  }
53
479
  getSnapshot() {
54
- return asSnapshot(this.#content);
480
+ return asSnapshot(this.content);
481
+ }
482
+ getLoadStatus(snapshot) {
483
+ const content = asContent(snapshot);
484
+ if (content.logLoadStatus.isErr())
485
+ return content.logLoadStatus;
486
+ if (content.mapLoadStatus.isErr())
487
+ return content.mapLoadStatus;
488
+ return content.logLoadStatus.value ? content.mapLoadStatus : content.logLoadStatus;
55
489
  }
56
490
  getRowCount(snapshot) {
57
491
  return asContent(snapshot).rowCount;
@@ -66,103 +500,84 @@ class EventSourcedSpreadsheetData {
66
500
  return columnItemOffsetMapping;
67
501
  }
68
502
  getCellValue(snapshot, row, column) {
69
- const entry = this.#getCellValueAndFormatEntry(snapshot, row, column);
503
+ const entry = this.getCellValueAndFormatEntry(snapshot, row, column);
70
504
  return entry?.value;
71
505
  }
72
506
  getCellFormat(snapshot, row, column) {
73
- const entry = this.#getCellValueAndFormatEntry(snapshot, row, column);
507
+ const entry = this.getCellValueAndFormatEntry(snapshot, row, column);
74
508
  return entry?.format;
75
509
  }
76
510
  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) {
511
+ const curr = this.content;
512
+ const entry = { type: 'SetCellValueAndFormat', row, column, value, format };
513
+ return this.addEntry(curr, entry).map((addEntryValue) => {
514
+ if (this.content === curr) {
81
515
  // 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
- curr.logSegment.entries.push({ type: 'SetCellValueAndFormat', row, column, value, format });
516
+ let { logSegment, cellMap } = curr;
517
+ if (addEntryValue.lastSnapshot)
518
+ [logSegment, cellMap] = forkSegment(curr.logSegment, curr.cellMap, addEntryValue.lastSnapshot);
519
+ logSegment.entries.push(entry);
520
+ cellMap.addEntry(row, column, Number(curr.endSequenceId - logSegment.startSequenceId), value, format);
83
521
  // Snapshot semantics preserved by treating EventSourcedSnapshot as an immutable data structure which is
84
522
  // replaced with a modified copy on every update.
85
- this.#content = {
523
+ this.content = {
86
524
  endSequenceId: curr.endSequenceId + 1n,
87
- logSegment: curr.logSegment,
88
- isComplete: true,
525
+ logSegment,
526
+ logLoadStatus: ok(true),
527
+ cellMap,
528
+ mapLoadStatus: ok(true),
89
529
  rowCount: Math.max(curr.rowCount, row + 1),
90
- colCount: Math.max(curr.colCount, column + 1)
530
+ colCount: Math.max(curr.colCount, column + 1),
531
+ viewport: curr.viewport
91
532
  };
92
- this.#notifyListeners();
533
+ this.notifyListeners();
93
534
  }
94
- }).orElse((err) => { throw Error(err.message); });
95
- // Oh no, this method needs to become async ...
96
- return ok();
535
+ }).mapErr((err) => {
536
+ switch (err.type) {
537
+ case 'ConflictError':
538
+ if (this.content == curr) {
539
+ // Out of date wrt to event log, nothing else has updated content since then, so set
540
+ // status for in progress load and trigger sync.
541
+ this.content = { ...curr, logLoadStatus: ok(false) };
542
+ this.syncLogs();
543
+ }
544
+ return storageError("Client out of sync", 409);
545
+ case 'StorageError':
546
+ return err;
547
+ }
548
+ });
97
549
  }
98
550
  isValidCellValueAndFormat(_row, _column, _value, _format) {
99
551
  return ok();
100
552
  }
101
- #notifyListeners() {
102
- for (const listener of this.#listeners)
553
+ getViewport(snapshot) {
554
+ return asContent(snapshot).viewport;
555
+ }
556
+ notifyListeners() {
557
+ for (const listener of this.listeners)
103
558
  listener();
104
559
  }
105
- #getCellValueAndFormatEntry(snapshot, row, column) {
106
- const content = asContent(snapshot);
107
- const endIndex = Number(content.endSequenceId - content.logSegment.startSequenceId);
108
- for (let i = endIndex - 1; i >= 0; i--) {
109
- const entry = content.logSegment.entries[i];
110
- if (entry.row == row && entry.column == column)
111
- return entry;
560
+ addEntry(curr, entry) {
561
+ const segment = curr.logSegment;
562
+ if (this.workerHost) {
563
+ const index = segment.entries.length % this.snapshotInterval;
564
+ if (this.snapshotInterval === index + 1)
565
+ entry.pending = 'snapshot';
566
+ // TODO: Check whether previous snapshot has completed. If not need to wait before
567
+ // doing another. May need to retry previous snapshot.
112
568
  }
113
- return undefined;
114
- }
115
- #syncLogs() {
116
- if (this.#isInSyncLogs)
117
- return;
118
- this.#syncLogsAsync().catch((reason) => { throw Error("Rejected promise from #syncLogsAsync", { cause: reason }); });
569
+ return this.eventLog.addEntry(entry, curr.endSequenceId, segment.snapshotId ? segment.startSequenceId : 0n);
119
570
  }
120
- async #syncLogsAsync() {
121
- this.#isInSyncLogs = true;
122
- // Set up load of first batch of entries
123
- const segment = this.#content.logSegment;
124
- let isComplete = false;
125
- while (!isComplete) {
126
- const curr = this.#content;
127
- const start = (segment.entries.length == 0) ? 'snapshot' : curr.endSequenceId;
128
- const result = await this.#eventLog.query(start, 'end');
129
- 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");
132
- }
133
- // Extend the current loaded segment.
134
- // Once snapshots supported need to look out for new snapshot and start new segment
135
- const value = result.value;
136
- if (segment.entries.length == 0)
137
- segment.startSequenceId = value.startSequenceId;
138
- else if (curr.endSequenceId != value.startSequenceId)
139
- throw Error(`Query returned start ${value.startSequenceId}, expected ${curr.endSequenceId}`);
140
- isComplete = value.isComplete;
141
- if (value.entries.length > 0) {
142
- segment.entries.push(...value.entries);
143
- // Create a new snapshot based on the new data
144
- let rowCount = curr.rowCount;
145
- let colCount = curr.colCount;
146
- for (const entry of value.entries) {
147
- rowCount = Math.max(rowCount, entry.row + 1);
148
- colCount = Math.max(colCount, entry.column + 1);
149
- }
150
- this.#content = {
151
- endSequenceId: value.endSequenceId,
152
- logSegment: segment,
153
- isComplete, rowCount, colCount
154
- };
155
- this.#notifyListeners();
156
- }
157
- }
158
- this.#isInSyncLogs = false;
571
+ getCellValueAndFormatEntry(snapshot, row, column) {
572
+ const content = asContent(snapshot);
573
+ const endIndex = Number(content.endSequenceId - content.logSegment.startSequenceId);
574
+ return content.cellMap.findEntry(row, column, endIndex);
159
575
  }
160
- #intervalId;
161
- #isInSyncLogs;
162
- #eventLog;
163
- #listeners;
164
- #content;
576
+ workerHost;
577
+ snapshotInterval;
578
+ intervalId;
579
+ listeners;
165
580
  }
166
581
 
167
- export { EventSourcedSpreadsheetData, _EventSourcedSnapshotBrand };
582
+ export { EventSourcedSpreadsheetData, EventSourcedSpreadsheetEngine, EventSourcedSpreadsheetWorkflow, SpreadsheetCellMap, SpreadsheetSnapshot, _EventSourcedSnapshotBrand, openSnapshot };
168
583
  //# sourceMappingURL=index.js.map
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/SpreadsheetCellMap.ts","../src/SpreadsheetSnapshot.ts","../src/EventSourcedSpreadsheetEngine.ts","../src/EventSourcedSpreadsheetWorkflow.ts","../src/EventSourcedSpreadsheetData.ts"],"sourcesContent":["import { CellValue, CellData, CellFormat, RowColRef, rowColCoordsToRef, rowColRefToCoords } from \"@candidstartup/infinisheet-types\";\nimport { SetCellValueAndFormatLogEntry } from \"./SpreadsheetLogEntry\";\n\n/**\n * Entry stored in a {@link SpreadsheetCellMap}\n * @internal\n */\nexport interface CellMapEntry extends CellData {\n /** Index of entry within `LogSegment` */\n logIndex?: number | undefined;\n}\n\nfunction bestEntry(entry: CellMapEntry | CellMapEntry[], snapshotIndex: number): CellMapEntry | undefined {\n if (!Array.isArray(entry))\n return (entry.logIndex === undefined || entry.logIndex < snapshotIndex) ? entry : undefined;\n\n // Future optimization: Check 3 entries then switch to binary chop\n for (let i = entry.length-1; i >= 0; i --) {\n const t = entry[i]!;\n if (t.logIndex === undefined || t.logIndex < snapshotIndex)\n return t;\n }\n\n return undefined;\n}\n\n/** @internal */\nexport interface CellMapExtents {\n rowMin: number;\n rowMax: number;\n columnMin: number;\n columnMax: number;\n}\n\n/** @internal */\nexport class SpreadsheetCellMap {\n constructor() {\n this.map = new Map<RowColRef, CellMapEntry | CellMapEntry[]>();\n }\n\n addEntries(entries: SetCellValueAndFormatLogEntry[], baseIndex: number): void {\n entries.forEach((value, index) => {\n this.addEntry(value.row, value.column, baseIndex+index, value.value, value.format);\n })\n }\n\n addEntry(row: number, column: number, logIndex: number, value: CellValue, format?: CellFormat): void {\n const key = rowColCoordsToRef(row, column);\n const newEntry = { value, format, logIndex };\n\n const entry = this.map.get(key);\n if (!entry) {\n this.map.set(key, newEntry)\n return;\n }\n\n if (Array.isArray(entry)) {\n entry.push(newEntry);\n } else {\n this.map.set(key, [ entry, newEntry ]);\n }\n }\n\n /** Return entry with highest index smaller than `snapshotIndex` */\n findEntry(row: number, column: number, snapshotIndex: number): CellMapEntry|undefined {\n const key = rowColCoordsToRef(row, column);\n const entry = this.map.get(key);\n return entry ? bestEntry(entry, snapshotIndex) : undefined;\n }\n\n calcExtents(snapshotIndex: number): CellMapExtents {\n let extents: CellMapExtents | undefined = undefined;\n for (const [key,value] of this.map.entries()) {\n const entry = bestEntry(value,snapshotIndex);\n if (entry) {\n const [row,column] = rowColRefToCoords(key);\n if (extents) {\n extents.rowMin = Math.min(extents.rowMin, row!);\n extents.rowMax = Math.max(extents.rowMax, row! + 1);\n extents.columnMin = Math.min(extents.columnMin, column!);\n extents.columnMax = Math.max(extents.columnMax, column! + 1);\n } else {\n extents = { rowMin: row!, rowMax: row!+1, columnMin: column!, columnMax: column!+1 }\n }\n }\n }\n\n return extents ? extents : { rowMin: 0, columnMin: 0, rowMax: 0, columnMax: 0};\n }\n\n /** Saves snapshot containing highest entry smaller than snapshotIndex for each cell */\n saveSnapshot(snapshotIndex: number): Uint8Array {\n const output: { [index: string]: CellData } = {};\n for (const [key,value] of this.map.entries()) {\n const entry = bestEntry(value,snapshotIndex);\n if (entry) {\n const { logIndex: _logIndex, ...rest } = entry;\n output[key] = rest;\n }\n }\n const json = JSON.stringify(output);\n\n const encoder = new TextEncoder;\n return encoder.encode(json);\n }\n\n /** Initializes map with content of snapshot */\n loadSnapshot(snapshot: Uint8Array): void {\n const decoder = new TextDecoder;\n const inputString = decoder.decode(snapshot);\n const input: unknown = JSON.parse(inputString);\n if (!input || typeof input !== 'object')\n throw Error(\"Failed to parse snapshot, root is not an object\");\n\n this.map.clear();\n for (const [key,anyValue] of Object.entries(input)) {\n // Tracer bullet, no validation that value is valid!\n const value = anyValue as CellData;\n this.map.set(key, value);\n }\n }\n\n /** Equivalent to {@link saveSnapshot} followed by {@link loadSnapshot} */\n loadAsSnapshot(src: SpreadsheetCellMap, snapshotIndex: number) {\n for (const [key,value] of src.map.entries()) {\n const entry = bestEntry(value,snapshotIndex);\n if (entry) {\n const { logIndex: _logIndex, ...rest } = entry;\n this.map.set(key, rest);\n }\n }\n }\n\n private map: Map<RowColRef, CellMapEntry | CellMapEntry[]>\n}","import type { BlobDir, BlobId, Result, StorageError } from \"@candidstartup/infinisheet-types\";\nimport { err, ok } from \"@candidstartup/infinisheet-types\";\n\nfunction formatName(rowMin: number, colMin: number, rowCount: number, colCount: number) {\n return `${rowMin}-${colMin}-${rowCount}-${colCount}`\n}\n\n/** In-memory representation of snapshot metadata\n * @internal\n */\nexport class SpreadsheetSnapshot {\n constructor(id: BlobId, snapshotDir: BlobDir<unknown>, tileDir: BlobDir<unknown>) {\n this.id = id;\n this.snapshotDir = snapshotDir;\n this.tileDir = tileDir;\n\n this.rowCount = 0;\n this.colCount = 0;\n }\n\n async saveIndex(): Promise<Result<void,StorageError>> {\n const meta = { rowCount: this.rowCount, colCount: this.colCount }\n const json = JSON.stringify(meta);\n const encoder = new TextEncoder;\n const blob = encoder.encode(json);\n \n const blobResult = await this.snapshotDir.writeBlob(\"index\", blob);\n if (blobResult.isErr()) {\n if (blobResult.error.type === \"StorageError\")\n return err(blobResult.error);\n throw Error(\"Messed up my blobs\", { cause: blobResult.error });\n }\n\n return ok();\n }\n\n async loadIndex(): Promise<Result<void,StorageError>> {\n const blobResult = await this.snapshotDir.readBlob(\"index\");\n if (blobResult.isErr()) {\n if (blobResult.error.type === \"StorageError\")\n return err(blobResult.error);\n throw Error(\"Messed up my blobs\", { cause: blobResult.error });\n }\n\n const decoder = new TextDecoder;\n const inputString = decoder.decode(blobResult.value);\n\n // Tracer bullet, no validation\n const input = JSON.parse(inputString) as SpreadsheetSnapshot;\n this.rowCount = input.rowCount;\n this.colCount = input.colCount;\n\n return ok();\n }\n\n async saveTile(rowMin: number, colMin: number, rowCount: number, colCount: number, blob: Uint8Array): Promise<Result<void,StorageError>> {\n const blobResult = await this.tileDir.writeBlob(formatName(rowMin,colMin,rowCount,colCount), blob);\n if (blobResult.isErr()) {\n if (blobResult.error.type === \"StorageError\")\n return err(blobResult.error);\n throw Error(\"Messed up my blobs\", { cause: blobResult.error });\n }\n\n this.rowCount = Math.max(this.rowCount, rowMin+rowCount);\n this.colCount = Math.max(this.colCount, colMin+colCount);\n return ok();\n }\n\n async loadTile(rowMin: number, colMin: number, rowCount: number, colCount: number): Promise<Result<Uint8Array,StorageError>> {\n const blobResult = await this.tileDir.readBlob(formatName(rowMin,colMin,rowCount,colCount));\n if (blobResult.isErr()) {\n if (blobResult.error.type === \"StorageError\")\n return err(blobResult.error);\n throw Error(\"Messed up my blobs\", { cause: blobResult.error });\n }\n\n return ok(blobResult.value);\n }\n\n id: BlobId;\n snapshotDir: BlobDir<unknown>;\n tileDir: BlobDir<unknown>;\n rowCount: number;\n colCount: number;\n}\n\nexport async function openSnapshot(rootDir: BlobDir<unknown>, snapshotId: BlobId): Promise<Result<SpreadsheetSnapshot,StorageError>> {\n const snapshotResult = await rootDir.getDir(snapshotId);\n if (snapshotResult.isErr()) {\n if (snapshotResult.error.type == \"StorageError\")\n return err(snapshotResult.error);\n throw Error(\"Messed up my blobs\", { cause: snapshotResult.error });\n }\n const snapshotDir = snapshotResult.value;\n\n const tileDirResult = await snapshotDir.getDir(\"tiles\");\n if (tileDirResult.isErr()) {\n if (tileDirResult.error.type == \"StorageError\")\n return err(tileDirResult.error);\n throw Error(\"Messed up my blobs\", { cause: tileDirResult.error });\n }\n const tileDir = tileDirResult.value;\n\n return ok(new SpreadsheetSnapshot(snapshotId,snapshotDir, tileDir));\n}","import type { Result, StorageError, SequenceId, BlobId, EventLog, BlobStore, QueryValue, SnapshotValue,\n SpreadsheetViewport } from \"@candidstartup/infinisheet-types\";\nimport { ok, err, equalViewports } from \"@candidstartup/infinisheet-types\";\n\nimport type { SpreadsheetLogEntry } from \"./SpreadsheetLogEntry\";\nimport { SpreadsheetCellMap } from \"./SpreadsheetCellMap\"\nimport { openSnapshot, SpreadsheetSnapshot } from \"./SpreadsheetSnapshot\";\n\n/** @internal */\nexport interface LogSegment {\n startSequenceId: SequenceId;\n entries: SpreadsheetLogEntry[];\n snapshotId?: BlobId | undefined;\n snapshot?: SpreadsheetSnapshot | undefined;\n}\n\n/** @internal */\nexport interface EventSourcedSnapshotContent {\n endSequenceId: SequenceId;\n logSegment: LogSegment;\n logLoadStatus: Result<boolean,StorageError>;\n cellMap: SpreadsheetCellMap;\n mapLoadStatus: Result<boolean,StorageError>;\n rowCount: number;\n colCount: number;\n viewport: SpreadsheetViewport | undefined;\n}\n\n/** @internal */\nexport function forkSegment(segment: LogSegment, cellMap: SpreadsheetCellMap, snapshot: SnapshotValue): [LogSegment, SpreadsheetCellMap] {\n const index = Number(snapshot.sequenceId - segment.startSequenceId);\n if (index < 0 || index >= segment.entries.length)\n throw Error(\"forkSegment: snapshotId not within segment\");\n\n const newSegment: LogSegment = \n { startSequenceId: snapshot.sequenceId, entries: segment.entries.slice(index), snapshotId: snapshot.blobId };\n const newMap = new SpreadsheetCellMap;\n newMap.loadAsSnapshot(cellMap, index);\n newMap.addEntries(newSegment.entries, 0);\n\n return [newSegment, newMap];\n}\n\nasync function cellMapFromSnapshot(segment: LogSegment, blobStore: BlobStore<unknown>): Promise<Result<SpreadsheetCellMap,StorageError>> {\n let snapshot = segment.snapshot;\n if (!snapshot) {\n const dir = await blobStore.getRootDir();\n if (dir.isErr())\n return err(dir.error);\n const result = await openSnapshot(dir.value, segment.snapshotId!);\n if (result.isErr())\n return err(result.error);\n snapshot = result.value;\n const index = await snapshot.loadIndex();\n if (index.isErr())\n return err(index.error);\n segment.snapshot = snapshot;\n }\n\n const blob = await snapshot.loadTile(0, 0, snapshot.rowCount, snapshot.colCount);\n if (blob.isErr())\n return err(blob.error);\n\n const cellMap = new SpreadsheetCellMap;\n cellMap.loadSnapshot(blob.value);\n cellMap.addEntries(segment.entries, 0);\n return ok(cellMap);\n}\n\nasync function updateContent(curr: EventSourcedSnapshotContent, value: QueryValue<SpreadsheetLogEntry>,\n blobStore: BlobStore<unknown>): Promise<Result<EventSourcedSnapshotContent,StorageError>> {\n let segment: LogSegment = curr.logSegment;\n let cellMap: SpreadsheetCellMap = curr.cellMap;\n let rowCount = curr.rowCount;\n let colCount = curr.colCount;\n\n let entries = value.entries;\n const startSequenceId = value.startSequenceId;\n const snapshotId = entries[0]!.snapshot;\n\n // Start a new segment and load from snapshot if we've jumped to id past what we currently have\n if (snapshotId && curr.endSequenceId != startSequenceId) {\n segment = { startSequenceId, entries, snapshotId };\n const result = await cellMapFromSnapshot(segment, blobStore);\n if (result.isErr())\n return err(result.error);\n cellMap = result.value;\n\n rowCount = segment.snapshot!.rowCount;\n colCount = segment.snapshot!.colCount;\n for (const entry of entries) {\n rowCount = Math.max(rowCount, entry.row+1);\n colCount = Math.max(colCount, entry.column+1);\n }\n } else {\n if (curr.endSequenceId != 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\n if (value.lastSnapshot) {\n const { sequenceId } = value.lastSnapshot;\n if (sequenceId < curr.endSequenceId) {\n // Snapshot has completed in entry we already have. Fork segment at that point then process value as normal.\n [segment, cellMap] = forkSegment(segment, cellMap, value.lastSnapshot);\n } else if (sequenceId < value.endSequenceId) {\n // Snapshot in returned value. Add entries up to snapshot to current cell map then start new segment from that.\n const indexInValue = Number(sequenceId - startSequenceId);\n const baseIndex = segment.entries.length;\n for (let i = 0; i < indexInValue; i ++) {\n const entry = entries[i]!;\n rowCount = Math.max(rowCount, entry.row+1);\n colCount = Math.max(colCount, entry.column+1);\n cellMap.addEntry(entry.row, entry.column, baseIndex+i, entry.value, entry.format);\n }\n entries = entries.slice(indexInValue);\n const oldCellMap = cellMap;\n cellMap = new SpreadsheetCellMap;\n cellMap.loadAsSnapshot(oldCellMap, baseIndex+indexInValue);\n const emptyArray: SpreadsheetLogEntry[] = [];\n segment = { startSequenceId: startSequenceId + BigInt(indexInValue), entries: emptyArray, snapshotId: value.lastSnapshot.blobId };\n // Segment extension code below will add the remaining values\n }\n // Snapshot must be in later page of results. Deal with it when we get there.\n }\n\n // Extend the current loaded segment.\n cellMap.addEntries(entries, segment.entries.length);\n segment.entries.push(...entries);\n\n for (const entry of entries) {\n rowCount = Math.max(rowCount, entry.row+1);\n colCount = Math.max(colCount, entry.column+1);\n }\n }\n\n // Create new content based on the new data\n return ok({\n endSequenceId: value.endSequenceId,\n logSegment: segment,\n logLoadStatus: ok(value.isComplete),\n cellMap,\n mapLoadStatus: ok(true),\n rowCount, colCount,\n viewport: curr.viewport\n });\n}\n\n/**\n * Low level engine for working with spreadsheet data\n * @internal\n */\nexport abstract class EventSourcedSpreadsheetEngine {\n constructor (eventLog: EventLog<SpreadsheetLogEntry>, blobStore: BlobStore<unknown>, viewport?: SpreadsheetViewport) {\n this.isInSyncLogs = false;\n this.eventLog = eventLog;\n this.blobStore = blobStore;\n this.content = {\n endSequenceId: 0n,\n logSegment: { startSequenceId: 0n, entries: [] },\n logLoadStatus: ok(false),\n cellMap: new SpreadsheetCellMap,\n mapLoadStatus: ok(true), // Empty map is consistent with current state of log\n rowCount: 0,\n colCount: 0,\n viewport\n }\n }\n\n setViewport(viewport: SpreadsheetViewport | undefined): void { \n const curr = this.content;\n if (equalViewports(curr.viewport, viewport))\n return;\n\n // Take our own copy of viewport to ensure that it's immutable\n const viewportCopy = viewport ? { ...viewport } : undefined;\n this.content = { ...curr, viewport: viewportCopy, mapLoadStatus: ok(false) };\n this.notifyListeners();\n }\n\n // Sync in-memory representation so that it includes range to endSequenceId (defaults to end of log)\n protected syncLogs(endSequenceId?: SequenceId): void {\n this.syncLogsAsync(endSequenceId).catch((reason) => { throw Error(\"Rejected promise from syncLogsAsync\", { cause: reason }) });\n }\n\n protected abstract notifyListeners(): void\n\n protected async syncLogsAsync(endSequenceId?: SequenceId): Promise<void> {\n if (this.isInSyncLogs)\n return Promise.resolve();\n\n // Already have everything required?\n if (endSequenceId && endSequenceId <= this.content.endSequenceId)\n return Promise.resolve();\n\n this.isInSyncLogs = true;\n\n // Set up load of first batch of entries\n let isComplete = false;\n\n while (!isComplete) {\n const curr = this.content;\n const initialLoad = (curr.endSequenceId === 0n);\n const start = initialLoad ? 'snapshot' : curr.endSequenceId;\n const end = endSequenceId ? endSequenceId : 'end';\n const segment = curr.logSegment;\n const result = await this.eventLog.query(start, end, initialLoad ? undefined : segment.startSequenceId);\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, logLoadStatus: err(result.error)};\n this.notifyListeners();\n break;\n }\n\n const value = result.value;\n isComplete = value.isComplete;\n\n // Don't create new snapshot if nothing has changed\n if (value.entries.length > 0) {\n const result = await updateContent(curr, value, this.blobStore);\n this.content = result.isOk() ? result.value : { ...curr, logLoadStatus: err(result.error)};\n this.notifyListeners();\n } else if (curr.logLoadStatus.isErr() || curr.logLoadStatus.value != isComplete) {\n // Careful, even if no entries returned, loadStatus may have changed\n this.content = { ...curr, logLoadStatus: ok(isComplete) }\n this.notifyListeners();\n }\n }\n\n this.isInSyncLogs = false;\n }\n\n protected eventLog: EventLog<SpreadsheetLogEntry>;\n protected blobStore: BlobStore<unknown>;\n protected content: EventSourcedSnapshotContent;\n private isInSyncLogs: boolean;\n}\n","import { EventLog, BlobStore, PendingWorkflowMessage, InfiniSheetWorker, \n ResultAsync, err, Result, InfinisheetError } from \"@candidstartup/infinisheet-types\";\n\nimport type { SpreadsheetLogEntry } from \"./SpreadsheetLogEntry\";\nimport { EventSourcedSpreadsheetEngine } from \"./EventSourcedSpreadsheetEngine\"\nimport { openSnapshot } from \"./SpreadsheetSnapshot\";\n\n/**\n * Event sourced implementation of spreadsheet {@link EventLog} triggered workflows\n *\n */\nexport class EventSourcedSpreadsheetWorkflow extends EventSourcedSpreadsheetEngine {\n constructor (eventLog: EventLog<SpreadsheetLogEntry>, blobStore: BlobStore<unknown>, worker: InfiniSheetWorker<PendingWorkflowMessage>) {\n super(eventLog, blobStore);\n\n this.worker = worker;\n\n worker.onReceiveMessage = (message: PendingWorkflowMessage) => this.onReceiveMessage(message);\n }\n\n protected notifyListeners(): void {}\n\n private onReceiveMessage(message: PendingWorkflowMessage): ResultAsync<void,InfinisheetError> {\n if (message.workflow !== 'snapshot')\n throw Error(`Unknown workflow ${message.workflow}`);\n\n return new ResultAsync(this.onReceiveMessageAsync(message));\n }\n\n private async onReceiveMessageAsync(message: PendingWorkflowMessage): Promise<Result<void,InfinisheetError>> {\n const endSequenceId = message.sequenceId;\n await this.syncLogsAsync(endSequenceId);\n if (this.content.logLoadStatus.isErr())\n return err(this.content.logLoadStatus.error);\n if (this.content.mapLoadStatus.isErr())\n return err(this.content.mapLoadStatus.error);\n if (!this.content.logLoadStatus.value)\n throw Error(\"Somehow syncLogs() is still in progress despite promise having resolved\");\n\n const { logSegment, cellMap } = this.content;\n const snapshotIndex = Number(endSequenceId - logSegment.startSequenceId);\n const blob = cellMap.saveSnapshot(snapshotIndex);\n const name = message.sequenceId.toString();\n\n const dir = await this.blobStore.getRootDir();\n if (dir.isErr())\n return err(dir.error);\n\n const snapshotResult = await openSnapshot(dir.value, name);\n if (snapshotResult.isErr())\n return err(snapshotResult.error);\n const snapshot = snapshotResult.value;\n\n const blobResult = await snapshot.saveTile(0, 0, this.content.rowCount, this.content.colCount, blob);\n if (blobResult.isErr())\n return err(blobResult.error);\n\n const indexResult = await snapshot.saveIndex();\n if (indexResult.isErr())\n return err(indexResult.error);\n\n return this.eventLog.setMetadata(message.sequenceId, { pending: undefined, snapshot: name });\n }\n\n protected worker: InfiniSheetWorker<PendingWorkflowMessage>;\n}\n","import type { CellValue, CellFormat, SpreadsheetData, ItemOffsetMapping, Result, ResultAsync, StorageError, AddEntryError, AddEntryValue,\n SpreadsheetDataError, ValidationError, EventLog, BlobStore, WorkerHost, PendingWorkflowMessage,\n SpreadsheetViewport } from \"@candidstartup/infinisheet-types\";\nimport { FixedSizeItemOffsetMapping, ok, storageError } from \"@candidstartup/infinisheet-types\";\n\nimport type { SetCellValueAndFormatLogEntry, SpreadsheetLogEntry } from \"./SpreadsheetLogEntry\";\nimport { EventSourcedSnapshotContent, EventSourcedSpreadsheetEngine, forkSegment } from \"./EventSourcedSpreadsheetEngine\"\nimport { CellMapEntry } from \"./SpreadsheetCellMap\";\n\n// How often to check for new event log entries (ms)\nconst EVENT_LOG_CHECK_INTERVAL = 10000;\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\n/** Additional options for {@link EventSourcedSpreadsheetData} */\nexport interface EventSourcedSpreadsheetDataOptions {\n /** Minimum number of log entries before creation of next snapshot \n * @defaultValue 100\n */\n snapshotInterval?: number | undefined;\n\n /** Should pending workflows be restarted on initial load of event log? \n * @defaultValue false\n */\n restartPendingWorkflowsOnLoad?: boolean | undefined;\n\n /** Initial viewport */\n viewport?: SpreadsheetViewport | undefined;\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 extends EventSourcedSpreadsheetEngine implements SpreadsheetData<EventSourcedSnapshot> {\n constructor (eventLog: EventLog<SpreadsheetLogEntry>, blobStore: BlobStore<unknown>, workerHost?: WorkerHost<PendingWorkflowMessage>,\n options?: EventSourcedSpreadsheetDataOptions) {\n super(eventLog, blobStore, options?.viewport);\n\n this.intervalId = undefined;\n this.workerHost = workerHost;\n this.snapshotInterval = options?.snapshotInterval || 100;\n this.listeners = [];\n\n this.syncLogs();\n }\n\n subscribe(onDataChange: () => void): () => void {\n if (!this.intervalId)\n this.intervalId = setInterval(() => { this.syncLogs() }, EVENT_LOG_CHECK_INTERVAL);\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 const content = asContent(snapshot);\n if (content.logLoadStatus.isErr())\n return content.logLoadStatus;\n if (content.mapLoadStatus.isErr())\n return content.mapLoadStatus;\n\n return content.logLoadStatus.value ? content.mapLoadStatus : content.logLoadStatus;\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): CellFormat {\n const entry = this.getCellValueAndFormatEntry(snapshot, row, column);\n return entry?.format;\n }\n\n setCellValueAndFormat(row: number, column: number, value: CellValue, format: CellFormat): ResultAsync<void,SpreadsheetDataError> {\n const curr = this.content;\n\n const entry: SetCellValueAndFormatLogEntry = { type: 'SetCellValueAndFormat', row, column, value, format };\n return this.addEntry(curr, entry).map((addEntryValue) => {\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 let { logSegment, cellMap } = curr\n if (addEntryValue.lastSnapshot)\n [logSegment, cellMap] = forkSegment(curr.logSegment, curr.cellMap, addEntryValue.lastSnapshot);\n logSegment.entries.push(entry);\n cellMap.addEntry(row, column, Number(curr.endSequenceId-logSegment.startSequenceId), 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,\n logLoadStatus: ok(true),\n cellMap,\n mapLoadStatus: ok(true),\n rowCount: Math.max(curr.rowCount, row+1),\n colCount: Math.max(curr.colCount, column+1),\n viewport: curr.viewport\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, logLoadStatus: 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: CellFormat): Result<void,ValidationError> {\n return ok(); \n }\n\n getViewport(snapshot: EventSourcedSnapshot): SpreadsheetViewport | undefined { \n return asContent(snapshot).viewport; \n }\n\n protected notifyListeners() {\n for (const listener of this.listeners)\n listener();\n }\n\n private addEntry(curr: EventSourcedSnapshotContent, entry: SpreadsheetLogEntry): ResultAsync<AddEntryValue,AddEntryError> {\n const segment = curr.logSegment;\n if (this.workerHost) {\n const index = segment.entries.length % this.snapshotInterval;\n if (this.snapshotInterval === index + 1)\n entry.pending = 'snapshot';\n\n // TODO: Check whether previous snapshot has completed. If not need to wait before\n // doing another. May need to retry previous snapshot.\n }\n\n return this.eventLog.addEntry(entry, curr.endSequenceId, segment.snapshotId ? segment.startSequenceId : 0n);\n }\n\n private getCellValueAndFormatEntry(snapshot: EventSourcedSnapshot, row: number, column: number): CellMapEntry | undefined {\n const content = asContent(snapshot);\n const endIndex = Number(content.endSequenceId-content.logSegment.startSequenceId);\n return content.cellMap.findEntry(row, column, endIndex);\n }\n\n\n protected workerHost?: WorkerHost<PendingWorkflowMessage> | undefined;\n private snapshotInterval: number;\n private intervalId: ReturnType<typeof setInterval> | undefined;\n private listeners: (() => void)[];\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.13.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.13.0"
53
53
  },
54
- "gitHead": "0c72d04996502cb84f8720972bfda453e4989106"
54
+ "gitHead": "ca46dae5575a3011416803e2b159457ecb59eaee"
55
55
  }