@streamscloud/kit 0.55.1 → 0.56.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/core/data-loaders/cursor-data-loader-with-search.svelte.d.ts +7 -5
- package/dist/core/data-loaders/cursor-data-loader-with-search.svelte.js +31 -21
- package/dist/core/data-loaders/cursor-data-loader.svelte.d.ts +5 -3
- package/dist/core/data-loaders/cursor-data-loader.svelte.js +24 -14
- package/dist/core/data-loaders/data-loader.d.ts +2 -0
- package/dist/core/data-loaders/index.d.ts +1 -0
- package/dist/core/data-loaders/index.js +1 -0
- package/dist/core/data-loaders/keyed-cursor-data-loader.svelte.d.ts +28 -0
- package/dist/core/data-loaders/keyed-cursor-data-loader.svelte.js +66 -0
- package/dist/core/data-loaders/page-data-loader.svelte.d.ts +7 -5
- package/dist/core/data-loaders/page-data-loader.svelte.js +25 -17
- package/package.json +1 -1
|
@@ -2,13 +2,14 @@ import { ContinuationToken, type CursorResult } from '..';
|
|
|
2
2
|
import type { IDataLoader } from './data-loader';
|
|
3
3
|
export declare class CursorDataLoaderWithSearch<T> implements IDataLoader<T> {
|
|
4
4
|
items: T[];
|
|
5
|
-
private
|
|
5
|
+
private _continuationToken;
|
|
6
6
|
private _searchString;
|
|
7
|
-
private
|
|
8
|
-
private
|
|
9
|
-
private
|
|
10
|
-
private
|
|
7
|
+
private _loadPage;
|
|
8
|
+
private _pending;
|
|
9
|
+
private _generation;
|
|
10
|
+
private _searchStringMinLength;
|
|
11
11
|
private _loading;
|
|
12
|
+
private _failed;
|
|
12
13
|
constructor(init: {
|
|
13
14
|
loadPage: (continuationToken: ContinuationToken, searchString: string) => Promise<CursorResult<T> | null>;
|
|
14
15
|
searchStringMinLength?: number;
|
|
@@ -16,6 +17,7 @@ export declare class CursorDataLoaderWithSearch<T> implements IDataLoader<T> {
|
|
|
16
17
|
get searchString(): string;
|
|
17
18
|
get loading(): boolean;
|
|
18
19
|
get initialLoading(): boolean;
|
|
20
|
+
get failed(): boolean;
|
|
19
21
|
loadMore: () => Promise<T[]>;
|
|
20
22
|
reset(): Promise<void>;
|
|
21
23
|
updateSearchString: (searchString: string | null) => void;
|
|
@@ -2,17 +2,18 @@ import { ContinuationToken } from '..';
|
|
|
2
2
|
import { Utils } from '../utils';
|
|
3
3
|
export class CursorDataLoaderWithSearch {
|
|
4
4
|
items = $state.raw([]);
|
|
5
|
-
|
|
5
|
+
_continuationToken = $state.raw(ContinuationToken.init());
|
|
6
6
|
_searchString = $state.raw('');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
_loadPage;
|
|
8
|
+
_pending = null;
|
|
9
|
+
_generation = 0;
|
|
10
|
+
_searchStringMinLength = 1;
|
|
11
11
|
_loading = $state(false);
|
|
12
|
+
_failed = $state(false);
|
|
12
13
|
constructor(init) {
|
|
13
|
-
this.
|
|
14
|
+
this._loadPage = init.loadPage;
|
|
14
15
|
if (init.searchStringMinLength !== undefined) {
|
|
15
|
-
this.
|
|
16
|
+
this._searchStringMinLength = init.searchStringMinLength;
|
|
16
17
|
}
|
|
17
18
|
this.updateSearchString = Utils.debounce(this.updateSearchString, 400);
|
|
18
19
|
}
|
|
@@ -25,33 +26,36 @@ export class CursorDataLoaderWithSearch {
|
|
|
25
26
|
get initialLoading() {
|
|
26
27
|
return this._loading && this.items.length === 0;
|
|
27
28
|
}
|
|
29
|
+
get failed() {
|
|
30
|
+
return this._failed;
|
|
31
|
+
}
|
|
28
32
|
loadMore = async () => {
|
|
29
|
-
if (this.
|
|
30
|
-
return this.
|
|
33
|
+
if (this._pending) {
|
|
34
|
+
return this._pending;
|
|
31
35
|
}
|
|
32
|
-
if (!this.
|
|
36
|
+
if (!this._continuationToken.canLoadMore) {
|
|
33
37
|
return [];
|
|
34
38
|
}
|
|
35
39
|
this._loading = true;
|
|
36
40
|
const pending = this.runLoad();
|
|
37
|
-
this.
|
|
41
|
+
this._pending = pending;
|
|
38
42
|
try {
|
|
39
43
|
return await pending;
|
|
40
44
|
}
|
|
41
45
|
finally {
|
|
42
46
|
// a concurrent reset() may have installed a newer load — leave that one alone
|
|
43
|
-
if (this.
|
|
44
|
-
this.
|
|
47
|
+
if (this._pending === pending) {
|
|
48
|
+
this._pending = null;
|
|
45
49
|
this._loading = false;
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
};
|
|
49
53
|
async reset() {
|
|
50
|
-
this.
|
|
51
|
-
this.
|
|
54
|
+
this._generation++;
|
|
55
|
+
this._pending = null;
|
|
52
56
|
this._loading = false;
|
|
53
57
|
this.items = [];
|
|
54
|
-
this.
|
|
58
|
+
this._continuationToken = ContinuationToken.init();
|
|
55
59
|
await this.loadMore();
|
|
56
60
|
}
|
|
57
61
|
updateSearchString = (searchString) => {
|
|
@@ -67,22 +71,28 @@ export class CursorDataLoaderWithSearch {
|
|
|
67
71
|
}
|
|
68
72
|
};
|
|
69
73
|
runLoad = async () => {
|
|
70
|
-
const generation = this.
|
|
74
|
+
const generation = this._generation;
|
|
71
75
|
const search = this.isSearchStringEffective(this._searchString) ? this._searchString : '';
|
|
76
|
+
this._failed = false;
|
|
72
77
|
try {
|
|
73
|
-
const
|
|
78
|
+
const page = await this._loadPage(this._continuationToken, search);
|
|
74
79
|
// a reset() mid-load bumps the generation — drop the stale page instead of appending it to the cleared list
|
|
75
|
-
if (generation !== this.
|
|
80
|
+
if (generation !== this._generation) {
|
|
76
81
|
return [];
|
|
77
82
|
}
|
|
78
|
-
|
|
83
|
+
const result = page ?? { items: [], continuationToken: ContinuationToken.preventLoading() };
|
|
84
|
+
this._failed = page === null;
|
|
85
|
+
this._continuationToken = result.continuationToken;
|
|
79
86
|
this.items = [...this.items, ...result.items];
|
|
80
87
|
return result.items;
|
|
81
88
|
}
|
|
82
89
|
catch (error) {
|
|
83
90
|
console.error('CursorDataLoaderWithSearch: failed to load page', error);
|
|
91
|
+
if (generation === this._generation) {
|
|
92
|
+
this._failed = true;
|
|
93
|
+
}
|
|
84
94
|
return [];
|
|
85
95
|
}
|
|
86
96
|
};
|
|
87
|
-
isSearchStringEffective = (searchString) => searchString && searchString.length >= this.
|
|
97
|
+
isSearchStringEffective = (searchString) => searchString && searchString.length >= this._searchStringMinLength;
|
|
88
98
|
}
|
|
@@ -3,15 +3,17 @@ import type { IDataLoader } from './data-loader';
|
|
|
3
3
|
export declare class CursorDataLoader<T> implements IDataLoader<T> {
|
|
4
4
|
items: T[];
|
|
5
5
|
continuationToken: ContinuationToken;
|
|
6
|
-
private
|
|
7
|
-
private
|
|
8
|
-
private
|
|
6
|
+
private _loadPage;
|
|
7
|
+
private _pending;
|
|
8
|
+
private _generation;
|
|
9
9
|
private _loading;
|
|
10
|
+
private _failed;
|
|
10
11
|
constructor(init: {
|
|
11
12
|
loadPage: (continuationToken: ContinuationToken) => Promise<CursorResult<T> | null>;
|
|
12
13
|
});
|
|
13
14
|
get loading(): boolean;
|
|
14
15
|
get initialLoading(): boolean;
|
|
16
|
+
get failed(): boolean;
|
|
15
17
|
loadMore: () => Promise<T[]>;
|
|
16
18
|
reset(): Promise<void>;
|
|
17
19
|
private runLoad;
|
|
@@ -2,12 +2,13 @@ import { ContinuationToken } from '..';
|
|
|
2
2
|
export class CursorDataLoader {
|
|
3
3
|
items = $state.raw([]);
|
|
4
4
|
continuationToken = $state.raw(ContinuationToken.init());
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
_loadPage;
|
|
6
|
+
_pending = null;
|
|
7
|
+
_generation = 0;
|
|
8
8
|
_loading = $state(false);
|
|
9
|
+
_failed = $state(false);
|
|
9
10
|
constructor(init) {
|
|
10
|
-
this.
|
|
11
|
+
this._loadPage = init.loadPage;
|
|
11
12
|
}
|
|
12
13
|
get loading() {
|
|
13
14
|
return this._loading;
|
|
@@ -15,49 +16,58 @@ export class CursorDataLoader {
|
|
|
15
16
|
get initialLoading() {
|
|
16
17
|
return this._loading && this.items.length === 0;
|
|
17
18
|
}
|
|
19
|
+
get failed() {
|
|
20
|
+
return this._failed;
|
|
21
|
+
}
|
|
18
22
|
loadMore = async () => {
|
|
19
|
-
if (this.
|
|
20
|
-
return this.
|
|
23
|
+
if (this._pending) {
|
|
24
|
+
return this._pending;
|
|
21
25
|
}
|
|
22
26
|
if (!this.continuationToken.canLoadMore) {
|
|
23
27
|
return [];
|
|
24
28
|
}
|
|
25
29
|
this._loading = true;
|
|
26
30
|
const pending = this.runLoad();
|
|
27
|
-
this.
|
|
31
|
+
this._pending = pending;
|
|
28
32
|
try {
|
|
29
33
|
return await pending;
|
|
30
34
|
}
|
|
31
35
|
finally {
|
|
32
36
|
// a concurrent reset() may have installed a newer load — leave that one alone
|
|
33
|
-
if (this.
|
|
34
|
-
this.
|
|
37
|
+
if (this._pending === pending) {
|
|
38
|
+
this._pending = null;
|
|
35
39
|
this._loading = false;
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
};
|
|
39
43
|
async reset() {
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
44
|
+
this._generation++;
|
|
45
|
+
this._pending = null;
|
|
42
46
|
this._loading = false;
|
|
43
47
|
this.items = [];
|
|
44
48
|
this.continuationToken = ContinuationToken.init();
|
|
45
49
|
await this.loadMore();
|
|
46
50
|
}
|
|
47
51
|
runLoad = async () => {
|
|
48
|
-
const generation = this.
|
|
52
|
+
const generation = this._generation;
|
|
53
|
+
this._failed = false;
|
|
49
54
|
try {
|
|
50
|
-
const
|
|
55
|
+
const page = await this._loadPage(this.continuationToken);
|
|
51
56
|
// a reset() mid-load bumps the generation — drop the stale page instead of appending it to the cleared list
|
|
52
|
-
if (generation !== this.
|
|
57
|
+
if (generation !== this._generation) {
|
|
53
58
|
return [];
|
|
54
59
|
}
|
|
60
|
+
const result = page ?? { items: [], continuationToken: ContinuationToken.preventLoading() };
|
|
61
|
+
this._failed = page === null;
|
|
55
62
|
this.continuationToken = result.continuationToken;
|
|
56
63
|
this.items = [...this.items, ...result.items];
|
|
57
64
|
return result.items;
|
|
58
65
|
}
|
|
59
66
|
catch (error) {
|
|
60
67
|
console.error('CursorDataLoader: failed to load page', error);
|
|
68
|
+
if (generation === this._generation) {
|
|
69
|
+
this._failed = true;
|
|
70
|
+
}
|
|
61
71
|
return [];
|
|
62
72
|
}
|
|
63
73
|
};
|
|
@@ -3,5 +3,7 @@ export interface IDataLoader<T> {
|
|
|
3
3
|
loading: boolean;
|
|
4
4
|
/** Loading with nothing to show yet — first load, or a reload after the list was cleared by a reset. */
|
|
5
5
|
initialLoading: boolean;
|
|
6
|
+
/** The last attempt did not deliver a page, whether it returned nothing or threw; cleared when the next attempt starts. */
|
|
7
|
+
failed: boolean;
|
|
6
8
|
loadMore: () => Promise<T[]>;
|
|
7
9
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type { IDataLoader } from './data-loader';
|
|
2
2
|
export { CursorDataLoader } from './cursor-data-loader.svelte';
|
|
3
3
|
export { CursorDataLoaderWithSearch } from './cursor-data-loader-with-search.svelte';
|
|
4
|
+
export { KeyedCursorDataLoader } from './keyed-cursor-data-loader.svelte';
|
|
4
5
|
export { PageDataLoader } from './page-data-loader.svelte';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Data loaders
|
|
2
2
|
export { CursorDataLoader } from './cursor-data-loader.svelte';
|
|
3
3
|
export { CursorDataLoaderWithSearch } from './cursor-data-loader-with-search.svelte';
|
|
4
|
+
export { KeyedCursorDataLoader } from './keyed-cursor-data-loader.svelte';
|
|
4
5
|
export { PageDataLoader } from './page-data-loader.svelte';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ContinuationToken, type CursorResult } from '..';
|
|
2
|
+
export declare class KeyedCursorDataLoader<T> {
|
|
3
|
+
private _loaders;
|
|
4
|
+
private _loaded;
|
|
5
|
+
private _pending;
|
|
6
|
+
private _loadPage;
|
|
7
|
+
private _loadAlongside;
|
|
8
|
+
constructor(init: {
|
|
9
|
+
loadPage: (key: string, continuationToken: ContinuationToken) => Promise<CursorResult<T> | null>;
|
|
10
|
+
/** Started with the key's first page and awaited by `ensureLoaded`; its outcome never reaches `isLoaded`. */
|
|
11
|
+
loadAlongside?: (key: string) => Promise<void>;
|
|
12
|
+
});
|
|
13
|
+
items: (key?: string) => T[];
|
|
14
|
+
loading: (key?: string) => boolean;
|
|
15
|
+
initialLoading: (key?: string) => boolean;
|
|
16
|
+
canLoadMore: (key?: string) => boolean;
|
|
17
|
+
/** False until the key's first page loaded without failing, so a failed load can be retried; a later failed page never clears it. */
|
|
18
|
+
isLoaded: (key?: string) => boolean;
|
|
19
|
+
/** Loads the key's first page once; concurrent and repeat calls share that load's promise. */
|
|
20
|
+
ensureLoaded: (key?: string) => Promise<void>;
|
|
21
|
+
loadMore: (key?: string) => Promise<T[]>;
|
|
22
|
+
/** Drops the key's pages and loads them again from the first one. */
|
|
23
|
+
reload: (key?: string) => Promise<void>;
|
|
24
|
+
private startLoad;
|
|
25
|
+
private runLoad;
|
|
26
|
+
private runAlongside;
|
|
27
|
+
private loaderFor;
|
|
28
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ContinuationToken } from '..';
|
|
2
|
+
import { CursorDataLoader } from './cursor-data-loader.svelte';
|
|
3
|
+
export class KeyedCursorDataLoader {
|
|
4
|
+
_loaders = {};
|
|
5
|
+
_loaded = $state.raw({});
|
|
6
|
+
_pending = {};
|
|
7
|
+
_loadPage;
|
|
8
|
+
_loadAlongside;
|
|
9
|
+
constructor(init) {
|
|
10
|
+
this._loadPage = init.loadPage;
|
|
11
|
+
this._loadAlongside = init.loadAlongside;
|
|
12
|
+
}
|
|
13
|
+
items = (key = '') => this.loaderFor(key).items;
|
|
14
|
+
loading = (key = '') => this.loaderFor(key).loading;
|
|
15
|
+
initialLoading = (key = '') => this.loaderFor(key).initialLoading;
|
|
16
|
+
canLoadMore = (key = '') => this.loaderFor(key).continuationToken.canLoadMore;
|
|
17
|
+
/** False until the key's first page loaded without failing, so a failed load can be retried; a later failed page never clears it. */
|
|
18
|
+
isLoaded = (key = '') => this._loaded[key] === true;
|
|
19
|
+
/** Loads the key's first page once; concurrent and repeat calls share that load's promise. */
|
|
20
|
+
ensureLoaded = (key = '') => {
|
|
21
|
+
if (this._loaded[key]) {
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
}
|
|
24
|
+
return this._pending[key] ?? this.startLoad(key);
|
|
25
|
+
};
|
|
26
|
+
loadMore = (key = '') => this.loaderFor(key).loadMore();
|
|
27
|
+
/** Drops the key's pages and loads them again from the first one. */
|
|
28
|
+
reload = (key = '') => {
|
|
29
|
+
this._loaded = { ...this._loaded, [key]: false };
|
|
30
|
+
return this.startLoad(key);
|
|
31
|
+
};
|
|
32
|
+
startLoad = async (key) => {
|
|
33
|
+
const pending = this.runLoad(key);
|
|
34
|
+
this._pending[key] = pending;
|
|
35
|
+
try {
|
|
36
|
+
await pending;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
// a concurrent reload() may have installed a newer load — leave that one alone
|
|
40
|
+
if (this._pending[key] === pending) {
|
|
41
|
+
this._pending[key] = undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
runLoad = async (key) => {
|
|
46
|
+
const loader = this.loaderFor(key);
|
|
47
|
+
const alongside = this.runAlongside(key);
|
|
48
|
+
await loader.reset();
|
|
49
|
+
this._loaded = { ...this._loaded, [key]: !loader.failed };
|
|
50
|
+
await alongside;
|
|
51
|
+
};
|
|
52
|
+
runAlongside = async (key) => {
|
|
53
|
+
try {
|
|
54
|
+
await this._loadAlongside?.(key);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('KeyedCursorDataLoader: failed to load alongside the page', error);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
loaderFor = (key) => {
|
|
61
|
+
if (!this._loaders[key]) {
|
|
62
|
+
this._loaders[key] = new CursorDataLoader({ loadPage: (continuationToken) => this._loadPage(key, continuationToken) });
|
|
63
|
+
}
|
|
64
|
+
return this._loaders[key];
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -2,11 +2,12 @@ import type { IDataLoader } from './data-loader';
|
|
|
2
2
|
export declare class PageDataLoader<T> implements IDataLoader<T> {
|
|
3
3
|
private _items;
|
|
4
4
|
private _loading;
|
|
5
|
-
private
|
|
6
|
-
private
|
|
7
|
-
private
|
|
8
|
-
private
|
|
9
|
-
private
|
|
5
|
+
private _failed;
|
|
6
|
+
private _canLoadMore;
|
|
7
|
+
private _perPage;
|
|
8
|
+
private _loadPage;
|
|
9
|
+
private _pageNumber;
|
|
10
|
+
private _generation;
|
|
10
11
|
constructor(init: {
|
|
11
12
|
loadPage: (page: number, perPage: number) => Promise<T[]>;
|
|
12
13
|
perPage?: number;
|
|
@@ -14,6 +15,7 @@ export declare class PageDataLoader<T> implements IDataLoader<T> {
|
|
|
14
15
|
get items(): T[];
|
|
15
16
|
get loading(): boolean;
|
|
16
17
|
get initialLoading(): boolean;
|
|
18
|
+
get failed(): boolean;
|
|
17
19
|
loadMore: () => Promise<T[]>;
|
|
18
20
|
reset: () => Promise<void>;
|
|
19
21
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
export class PageDataLoader {
|
|
2
2
|
_items = $state.raw([]);
|
|
3
3
|
_loading = $state(false);
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
_failed = $state(false);
|
|
5
|
+
_canLoadMore = $state(true);
|
|
6
|
+
_perPage = 20;
|
|
7
|
+
_loadPage;
|
|
8
|
+
_pageNumber = 1;
|
|
9
|
+
_generation = 0;
|
|
9
10
|
constructor(init) {
|
|
10
|
-
this.
|
|
11
|
+
this._loadPage = init.loadPage;
|
|
11
12
|
if (init.perPage) {
|
|
12
|
-
this.
|
|
13
|
+
this._perPage = init.perPage;
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
get items() {
|
|
@@ -21,39 +22,46 @@ export class PageDataLoader {
|
|
|
21
22
|
get initialLoading() {
|
|
22
23
|
return this._loading && this._items.length === 0;
|
|
23
24
|
}
|
|
25
|
+
get failed() {
|
|
26
|
+
return this._failed;
|
|
27
|
+
}
|
|
24
28
|
loadMore = async () => {
|
|
25
|
-
if (this._loading || !this.
|
|
29
|
+
if (this._loading || !this._canLoadMore) {
|
|
26
30
|
return [];
|
|
27
31
|
}
|
|
28
|
-
const generation = this.
|
|
32
|
+
const generation = this._generation;
|
|
29
33
|
this._loading = true;
|
|
34
|
+
this._failed = false;
|
|
30
35
|
try {
|
|
31
|
-
const items = await this.
|
|
36
|
+
const items = await this._loadPage(this._pageNumber, this._perPage);
|
|
32
37
|
// a reset() mid-load bumps the generation — drop the stale page instead of appending it to the cleared list
|
|
33
|
-
if (generation !== this.
|
|
38
|
+
if (generation !== this._generation) {
|
|
34
39
|
return [];
|
|
35
40
|
}
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
41
|
+
this._pageNumber++;
|
|
42
|
+
this._canLoadMore = items.length === this._perPage;
|
|
38
43
|
this._items = [...this._items, ...items];
|
|
39
44
|
return items;
|
|
40
45
|
}
|
|
41
46
|
catch (error) {
|
|
42
47
|
console.error('PageDataLoader: failed to load page', error);
|
|
48
|
+
if (generation === this._generation) {
|
|
49
|
+
this._failed = true;
|
|
50
|
+
}
|
|
43
51
|
return [];
|
|
44
52
|
}
|
|
45
53
|
finally {
|
|
46
|
-
if (generation === this.
|
|
54
|
+
if (generation === this._generation) {
|
|
47
55
|
this._loading = false;
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
};
|
|
51
59
|
reset = async () => {
|
|
52
|
-
this.
|
|
60
|
+
this._generation++;
|
|
53
61
|
this._loading = false;
|
|
54
|
-
this.
|
|
62
|
+
this._pageNumber = 1;
|
|
55
63
|
this._items = [];
|
|
56
|
-
this.
|
|
64
|
+
this._canLoadMore = true;
|
|
57
65
|
await this.loadMore();
|
|
58
66
|
};
|
|
59
67
|
}
|