|
@@ -29,6 +29,8 @@ interface Generation {
|
|
|
readonly hold?: boolean
|
|
readonly hold?: boolean
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+type PageSource = Page | Promise<Page> | ((signal: AbortSignal) => Promise<Page>)
|
|
|
|
|
+
|
|
|
const AVAILABLE_CONNECTION = {
|
|
const AVAILABLE_CONNECTION = {
|
|
|
hostDescription: {
|
|
hostDescription: {
|
|
|
getSnapshot: () => ({
|
|
getSnapshot: () => ({
|
|
@@ -55,7 +57,7 @@ const STREAM_FACTORY = {
|
|
|
class FixtureJournal extends RemoteJournalStream<Page, Entry, number, PageRequest> {
|
|
class FixtureJournal extends RemoteJournalStream<Page, Entry, number, PageRequest> {
|
|
|
constructor(
|
|
constructor(
|
|
|
private readonly generations: Generation[],
|
|
private readonly generations: Generation[],
|
|
|
- private readonly pages: (Page | Promise<Page>)[],
|
|
|
|
|
|
|
+ private readonly pages: PageSource[],
|
|
|
private readonly calls: string[],
|
|
private readonly calls: string[],
|
|
|
private readonly pageRequests: PageRequest[],
|
|
private readonly pageRequests: PageRequest[],
|
|
|
private readonly pageCursors: number[],
|
|
private readonly pageCursors: number[],
|
|
@@ -95,13 +97,17 @@ class FixtureJournal extends RemoteJournalStream<Page, Entry, number, PageReques
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** @inheritdoc */
|
|
/** @inheritdoc */
|
|
|
- protected override readPage(request: PageRequest, through: number): Promise<Page> {
|
|
|
|
|
|
|
+ protected override readPage(
|
|
|
|
|
+ request: PageRequest,
|
|
|
|
|
+ through: number,
|
|
|
|
|
+ signal: AbortSignal,
|
|
|
|
|
+ ): Promise<Page> {
|
|
|
this.calls.push('page')
|
|
this.calls.push('page')
|
|
|
this.pageRequests.push(request)
|
|
this.pageRequests.push(request)
|
|
|
this.pageCursors.push(through)
|
|
this.pageCursors.push(through)
|
|
|
const value = this.pages.shift()
|
|
const value = this.pages.shift()
|
|
|
if (value === undefined) throw new Error('no scripted journal page')
|
|
if (value === undefined) throw new Error('no scripted journal page')
|
|
|
- return Promise.resolve(value)
|
|
|
|
|
|
|
+ return typeof value === 'function' ? value(signal) : Promise.resolve(value)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** @inheritdoc */
|
|
/** @inheritdoc */
|
|
@@ -112,7 +118,7 @@ class FixtureJournal extends RemoteJournalStream<Page, Entry, number, PageReques
|
|
|
|
|
|
|
|
function journalFixture(
|
|
function journalFixture(
|
|
|
generations: Generation[],
|
|
generations: Generation[],
|
|
|
- pages: (Page | Promise<Page>)[],
|
|
|
|
|
|
|
+ pages: PageSource[],
|
|
|
): {
|
|
): {
|
|
|
readonly journal: RemoteJournalStream<Page, Entry, number, PageRequest>
|
|
readonly journal: RemoteJournalStream<Page, Entry, number, PageRequest>
|
|
|
readonly changes: RemoteJournalChange<Page, Entry>[]
|
|
readonly changes: RemoteJournalChange<Page, Entry>[]
|
|
@@ -241,6 +247,42 @@ describe('RemoteJournalStream', () => {
|
|
|
await fixture.journal.dispose()
|
|
await fixture.journal.dispose()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ it('restarts a page aborted with its carrier generation', async () => {
|
|
|
|
|
+ const fixture = journalFixture(
|
|
|
|
|
+ [
|
|
|
|
|
+ {
|
|
|
|
|
+ frames: [{ type: 'opened', cursor: 1 }],
|
|
|
|
|
+ terminal: new RemoteStreamCarrierError('carrier lost during page'),
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ frames: [{ type: 'opened', cursor: 2 }],
|
|
|
|
|
+ hold: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ [
|
|
|
|
|
+ signal => new Promise<Page>((_resolve, reject) => {
|
|
|
|
|
+ const aborted = (): void => { reject(signal.reason) }
|
|
|
|
|
+ signal.addEventListener('abort', aborted, { once: true })
|
|
|
|
|
+ if (signal.aborted) aborted()
|
|
|
|
|
+ }),
|
|
|
|
|
+ page('replacement', [0, 1, 2]),
|
|
|
|
|
+ ],
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ await fixture.journal.open({ limit: 3 })
|
|
|
|
|
+
|
|
|
|
|
+ expect(fixture.changes).toEqual([{
|
|
|
|
|
+ type: 'replace',
|
|
|
|
|
+ page: page('replacement', [0, 1, 2]),
|
|
|
|
|
+ entries: entries(0, 1, 2),
|
|
|
|
|
+ hasMore: false,
|
|
|
|
|
+ }])
|
|
|
|
|
+ expect(fixture.pageCursors).toEqual([1, 2])
|
|
|
|
|
+ expect(fixture.followCursors).toEqual([undefined, 1])
|
|
|
|
|
+ expect(fixture.failed).not.toHaveBeenCalled()
|
|
|
|
|
+ await fixture.journal.dispose()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
it('repairs a live gap before publishing another change', async () => {
|
|
it('repairs a live gap before publishing another change', async () => {
|
|
|
const fixture = journalFixture(
|
|
const fixture = journalFixture(
|
|
|
[{
|
|
[{
|