@candidstartup/event-sourced-spreadsheet-data 0.12.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,22 +1,113 @@
1
- import { LogEntry, CellValue, SpreadsheetData, EventLog, Result, StorageError, ItemOffsetMapping, ResultAsync, 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
3
  /**
4
4
  * Log entry that captures change from calling {@link SpreadsheetData.setCellValueAndFormat}
5
5
  */
6
- interface SetCellValueAndFormatLogEntry extends LogEntry {
6
+ interface SetCellValueAndFormatLogEntry extends LogEntry, CellData {
7
7
  type: 'SetCellValueAndFormat';
8
8
  /** Row index of cell being modified */
9
9
  row: number;
10
10
  /** Column index of cell being modified */
11
11
  column: number;
12
- /** Value of cell being modified */
13
- value: CellValue;
14
- /** Format of cell being modified */
15
- format?: string | undefined;
16
12
  }
17
13
  /** Union of all types of Spreadsheet related log entries */
18
14
  type SpreadsheetLogEntry = SetCellValueAndFormatLogEntry;
19
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
+
20
111
  /**
21
112
  * Branding Enum. Used by {@link EventSourcedSnapshot} to ensure that
22
113
  * you'll get a type error if you pass some random object where a `EventSourcedSnapshot`
@@ -34,12 +125,25 @@ interface EventSourcedSnapshot {
34
125
  /** @internal */
35
126
  _brand: _EventSourcedSnapshotBrand;
36
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
+ }
37
141
  /**
38
142
  * Event sourced implementation of {@link SpreadsheetData}
39
143
  *
40
144
  */
41
- declare class EventSourcedSpreadsheetData implements SpreadsheetData<EventSourcedSnapshot> {
42
- 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);
43
147
  subscribe(onDataChange: () => void): () => void;
44
148
  getSnapshot(): EventSourcedSnapshot;
45
149
  getLoadStatus(snapshot: EventSourcedSnapshot): Result<boolean, StorageError>;
@@ -48,19 +152,18 @@ declare class EventSourcedSpreadsheetData implements SpreadsheetData<EventSource
48
152
  getColumnCount(snapshot: EventSourcedSnapshot): number;
49
153
  getColumnItemOffsetMapping(_snapshot: EventSourcedSnapshot): ItemOffsetMapping;
50
154
  getCellValue(snapshot: EventSourcedSnapshot, row: number, column: number): CellValue;
51
- getCellFormat(snapshot: EventSourcedSnapshot, row: number, column: number): string | undefined;
52
- setCellValueAndFormat(row: number, column: number, value: CellValue, format: string | undefined): ResultAsync<void, SpreadsheetDataError>;
53
- isValidCellValueAndFormat(_row: number, _column: number, _value: CellValue, _format: string | undefined): Result<void, ValidationError>;
54
- private notifyListeners;
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;
55
161
  private getCellValueAndFormatEntry;
56
- private syncLogs;
57
- private syncLogsAsync;
162
+ protected workerHost?: WorkerHost<PendingWorkflowMessage> | undefined;
163
+ private snapshotInterval;
58
164
  private intervalId;
59
- private isInSyncLogs;
60
- private eventLog;
61
165
  private listeners;
62
- private content;
63
166
  }
64
167
 
65
- export { EventSourcedSpreadsheetData, _EventSourcedSnapshotBrand };
66
- export type { EventSourcedSnapshot, SetCellValueAndFormatLogEntry, SpreadsheetLogEntry };
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, storageError, err } 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,24 +455,18 @@ function asSnapshot(snapshot) {
23
455
  * Event sourced implementation of {@link SpreadsheetData}
24
456
  *
25
457
  */
26
- class EventSourcedSpreadsheetData {
27
- constructor(eventLog) {
458
+ class EventSourcedSpreadsheetData extends EventSourcedSpreadsheetEngine {
459
+ constructor(eventLog, blobStore, workerHost, options) {
460
+ super(eventLog, blobStore, options?.viewport);
28
461
  this.intervalId = undefined;
29
- this.isInSyncLogs = false;
30
- this.eventLog = eventLog;
462
+ this.workerHost = workerHost;
463
+ this.snapshotInterval = options?.snapshotInterval || 100;
31
464
  this.listeners = [];
32
- this.content = {
33
- endSequenceId: 0n,
34
- logSegment: { startSequenceId: 0n, entries: [] },
35
- loadStatus: ok(false),
36
- rowCount: 0,
37
- colCount: 0
38
- };
39
465
  this.syncLogs();
40
466
  }
41
467
  subscribe(onDataChange) {
42
468
  if (!this.intervalId)
43
- this.intervalId = setInterval(() => { this.syncLogs(); }, EVENT_LOG_CHECK_DELAY);
469
+ this.intervalId = setInterval(() => { this.syncLogs(); }, EVENT_LOG_CHECK_INTERVAL);
44
470
  this.listeners = [...this.listeners, onDataChange];
45
471
  return () => {
46
472
  this.listeners = this.listeners.filter(l => l !== onDataChange);
@@ -54,7 +480,12 @@ class EventSourcedSpreadsheetData {
54
480
  return asSnapshot(this.content);
55
481
  }
56
482
  getLoadStatus(snapshot) {
57
- return asContent(snapshot).loadStatus;
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;
58
489
  }
59
490
  getRowCount(snapshot) {
60
491
  return asContent(snapshot).rowCount;
@@ -78,19 +509,26 @@ class EventSourcedSpreadsheetData {
78
509
  }
79
510
  setCellValueAndFormat(row, column, value, format) {
80
511
  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) {
512
+ const entry = { type: 'SetCellValueAndFormat', row, column, value, format };
513
+ return this.addEntry(curr, entry).map((addEntryValue) => {
514
+ if (this.content === curr) {
84
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
85
- 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);
86
521
  // Snapshot semantics preserved by treating EventSourcedSnapshot as an immutable data structure which is
87
522
  // replaced with a modified copy on every update.
88
523
  this.content = {
89
524
  endSequenceId: curr.endSequenceId + 1n,
90
- logSegment: curr.logSegment,
91
- loadStatus: ok(true),
525
+ logSegment,
526
+ logLoadStatus: ok(true),
527
+ cellMap,
528
+ mapLoadStatus: ok(true),
92
529
  rowCount: Math.max(curr.rowCount, row + 1),
93
- colCount: Math.max(curr.colCount, column + 1)
530
+ colCount: Math.max(curr.colCount, column + 1),
531
+ viewport: curr.viewport
94
532
  };
95
533
  this.notifyListeners();
96
534
  }
@@ -100,7 +538,7 @@ class EventSourcedSpreadsheetData {
100
538
  if (this.content == curr) {
101
539
  // Out of date wrt to event log, nothing else has updated content since then, so set
102
540
  // status for in progress load and trigger sync.
103
- this.content = { ...curr, loadStatus: ok(false) };
541
+ this.content = { ...curr, logLoadStatus: ok(false) };
104
542
  this.syncLogs();
105
543
  }
106
544
  return storageError("Client out of sync", 409);
@@ -112,95 +550,34 @@ class EventSourcedSpreadsheetData {
112
550
  isValidCellValueAndFormat(_row, _column, _value, _format) {
113
551
  return ok();
114
552
  }
553
+ getViewport(snapshot) {
554
+ return asContent(snapshot).viewport;
555
+ }
115
556
  notifyListeners() {
116
557
  for (const listener of this.listeners)
117
558
  listener();
118
559
  }
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.
568
+ }
569
+ return this.eventLog.addEntry(entry, curr.endSequenceId, segment.snapshotId ? segment.startSequenceId : 0n);
570
+ }
119
571
  getCellValueAndFormatEntry(snapshot, row, column) {
120
572
  const content = asContent(snapshot);
121
573
  const endIndex = Number(content.endSequenceId - content.logSegment.startSequenceId);
122
- for (let i = endIndex - 1; i >= 0; i--) {
123
- const entry = content.logSegment.entries[i];
124
- if (entry.row == row && entry.column == column)
125
- return entry;
126
- }
127
- return undefined;
128
- }
129
- syncLogs() {
130
- if (this.isInSyncLogs)
131
- return;
132
- this.syncLogsAsync().catch((reason) => { throw Error("Rejected promise from syncLogsAsync", { cause: reason }); });
133
- }
134
- async syncLogsAsync() {
135
- this.isInSyncLogs = true;
136
- // Set up load of first batch of entries
137
- const segment = this.content.logSegment;
138
- let isComplete = false;
139
- while (!isComplete) {
140
- const curr = this.content;
141
- const start = (segment.entries.length == 0) ? 'snapshot' : curr.endSequenceId;
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
- }
148
- if (!result.isOk()) {
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;
161
- }
162
- // Extend the current loaded segment.
163
- // Once snapshots supported need to look out for new snapshot and start new segment
164
- const value = result.value;
165
- if (segment.entries.length == 0)
166
- segment.startSequenceId = value.startSequenceId;
167
- else if (curr.endSequenceId != value.startSequenceId) {
168
- // Shouldn't happen unless we have buggy event log implementation
169
- throw Error(`Query returned start ${value.startSequenceId}, expected ${curr.endSequenceId}`);
170
- }
171
- isComplete = value.isComplete;
172
- // Don't create new snapshot if nothing has changed
173
- if (value.entries.length > 0) {
174
- segment.entries.push(...value.entries);
175
- // Create a new snapshot based on the new data
176
- let rowCount = curr.rowCount;
177
- let colCount = curr.colCount;
178
- for (const entry of value.entries) {
179
- rowCount = Math.max(rowCount, entry.row + 1);
180
- colCount = Math.max(colCount, entry.column + 1);
181
- }
182
- this.content = {
183
- endSequenceId: value.endSequenceId,
184
- logSegment: segment,
185
- loadStatus: ok(isComplete),
186
- rowCount, colCount
187
- };
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();
194
- }
195
- }
196
- this.isInSyncLogs = false;
574
+ return content.cellMap.findEntry(row, column, endIndex);
197
575
  }
576
+ workerHost;
577
+ snapshotInterval;
198
578
  intervalId;
199
- isInSyncLogs;
200
- eventLog;
201
579
  listeners;
202
- content;
203
580
  }
204
581
 
205
- export { EventSourcedSpreadsheetData, _EventSourcedSnapshotBrand };
582
+ export { EventSourcedSpreadsheetData, EventSourcedSpreadsheetEngine, EventSourcedSpreadsheetWorkflow, SpreadsheetCellMap, SpreadsheetSnapshot, _EventSourcedSnapshotBrand, openSnapshot };
206
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, 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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"}
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.12.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.12.0"
52
+ "@candidstartup/infinisheet-types": "^0.13.0"
53
53
  },
54
- "gitHead": "03b639807d367f0c167dc5b6653b3935412cbde8"
54
+ "gitHead": "ca46dae5575a3011416803e2b159457ecb59eaee"
55
55
  }