@reatomized/convex 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/README.md +110 -2
- package/dist/browser/logging.d.ts +39 -0
- package/dist/browser/logging.js +117 -0
- package/dist/browser/reatom/auth.d.ts +75 -0
- package/dist/browser/reatom/auth.js +329 -0
- package/dist/browser/reatom/client.d.ts +348 -0
- package/dist/browser/reatom/client.js +1138 -0
- package/dist/browser/reatom/connection_hooks.d.ts +11 -0
- package/dist/browser/reatom/connection_hooks.js +15 -0
- package/dist/browser/reatom/errors.d.ts +3 -0
- package/dist/browser/reatom/errors.js +8 -0
- package/dist/browser/reatom/index.d.ts +6 -0
- package/dist/browser/reatom/index.js +2 -0
- package/dist/browser/reatom/optimistic.d.ts +44 -0
- package/dist/browser/reatom/optimistic.js +70 -0
- package/dist/browser/reatom/paginated.d.ts +56 -0
- package/dist/browser/reatom/paginated.js +162 -0
- package/dist/browser/reatom/socket.d.ts +52 -0
- package/dist/browser/reatom/socket.js +523 -0
- package/dist/browser/sync/function_result.d.ts +21 -0
- package/dist/browser/sync/function_result.js +1 -0
- package/dist/browser/sync/metrics.d.ts +9 -0
- package/dist/browser/sync/metrics.js +43 -0
- package/dist/browser/sync/pagination.d.ts +22 -0
- package/dist/browser/sync/pagination.js +20 -0
- package/dist/browser/sync/protocol.d.ts +206 -0
- package/dist/browser/sync/protocol.js +75 -0
- package/dist/browser/sync/session.d.ts +1 -0
- package/dist/browser/sync/session.js +10 -0
- package/dist/browser/sync/udf_path_utils.d.ts +30 -0
- package/dist/browser/sync/udf_path_utils.js +38 -0
- package/dist/common/index.d.ts +11 -0
- package/dist/common/index.js +52 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/vendor/jwt-decode/LICENSE +21 -0
- package/dist/vendor/jwt-decode/index.d.ts +23 -0
- package/dist/vendor/jwt-decode/index.js +57 -0
- package/dist/vendor/long.d.ts +16 -0
- package/dist/vendor/long.js +297 -0
- package/package.json +59 -4
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AtomLike, Ext } from "@reatom/core";
|
|
2
|
+
/**
|
|
3
|
+
* Calls `connect` when `target` gains its first subscriber and `disconnect`
|
|
4
|
+
* when it loses the last one. `target` must have no other connect hook.
|
|
5
|
+
*
|
|
6
|
+
* Sets Reatom's internal `onConnect` slot to plain functions: Reatom's queue
|
|
7
|
+
* calls them in the context's root frame. Unlike `withConnectHook` this makes
|
|
8
|
+
* no actions, AbortController or AbortError per connection, which matters
|
|
9
|
+
* with thousands of queries.
|
|
10
|
+
*/
|
|
11
|
+
export declare const withConnectionHooks: <Target extends AtomLike<any, any[], any>>(connect: () => void, disconnect: () => void) => Ext<Target, Target>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calls `connect` when `target` gains its first subscriber and `disconnect`
|
|
3
|
+
* when it loses the last one. `target` must have no other connect hook.
|
|
4
|
+
*
|
|
5
|
+
* Sets Reatom's internal `onConnect` slot to plain functions: Reatom's queue
|
|
6
|
+
* calls them in the context's root frame. Unlike `withConnectHook` this makes
|
|
7
|
+
* no actions, AbortController or AbortError per connection, which matters
|
|
8
|
+
* with thousands of queries.
|
|
9
|
+
*/
|
|
10
|
+
export const withConnectionHooks = (connect, disconnect) => (target) => {
|
|
11
|
+
target.__reatom.onConnect = Object.assign(() => connect(), {
|
|
12
|
+
abort: () => disconnect(),
|
|
13
|
+
});
|
|
14
|
+
return target;
|
|
15
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { FunctionFailure } from "../sync/function_result.js";
|
|
2
|
+
/** The error a failed function call surfaces as: `ConvexError` when it carries data. */
|
|
3
|
+
export declare function functionError(type: "query" | "mutation" | "action", udfPath: string, result: FunctionFailure): Error;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ConvexError } from "convex/values";
|
|
2
|
+
import { createHybridErrorStacktrace, forwardData } from "../logging.js";
|
|
3
|
+
/** The error a failed function call surfaces as: `ConvexError` when it carries data. */
|
|
4
|
+
export function functionError(type, udfPath, result) {
|
|
5
|
+
return result.errorData !== undefined
|
|
6
|
+
? forwardData(result, new ConvexError(createHybridErrorStacktrace(type, udfPath, result)))
|
|
7
|
+
: new Error(createHybridErrorStacktrace(type, udfPath, result));
|
|
8
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { reatomConvexClient } from "./client.js";
|
|
2
|
+
export type { BaseConvexClientOptions as ReatomConvexClientOptions, ConnectionState, MutationOptions, QueryModification, QueryStatus, ReatomConvexClient, ReatomQuery, ReatomQueryOptions, Transition, } from "./client.js";
|
|
3
|
+
export type { AuthTokenFetcher } from "./auth.js";
|
|
4
|
+
export type { PaginatedQueryState, ReatomPaginatedQuery } from "./paginated.js";
|
|
5
|
+
export type { SocketState } from "./socket.js";
|
|
6
|
+
export type { OptimisticLocalStore } from "convex/browser";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimistic updates over server results. The overlay holds only the results
|
|
3
|
+
* updates have set; everything else reads through to the server results of
|
|
4
|
+
* subscribed queries.
|
|
5
|
+
*/
|
|
6
|
+
import { FunctionArgs, FunctionReference, FunctionReference_future, FunctionReturnType, OptionalRestArgs } from "convex/server";
|
|
7
|
+
import { Value } from "convex/values";
|
|
8
|
+
import { FunctionResult } from "../sync/function_result.js";
|
|
9
|
+
import { OptimisticLocalStore } from "convex/browser";
|
|
10
|
+
import { RequestId } from "../sync/protocol.js";
|
|
11
|
+
import { QueryToken } from "../sync/udf_path_utils.js";
|
|
12
|
+
export type OverlayQuery = {
|
|
13
|
+
udfPath: string;
|
|
14
|
+
args: Record<string, Value>;
|
|
15
|
+
/** `undefined` is a query an update set to loading. */
|
|
16
|
+
result: FunctionResult | undefined;
|
|
17
|
+
};
|
|
18
|
+
export type Overlay = Map<QueryToken, OverlayQuery>;
|
|
19
|
+
/** Subscribed queries with a server result, in subscription order. */
|
|
20
|
+
export type ServerResults = {
|
|
21
|
+
get(token: QueryToken): FunctionResult | undefined;
|
|
22
|
+
all(): Iterable<OverlayQuery & {
|
|
23
|
+
token: QueryToken;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
/** An optimistic update curried over its mutation's arguments. */
|
|
27
|
+
export type WrappedOptimisticUpdate = (store: OptimisticLocalStore) => void;
|
|
28
|
+
export type OptimisticUpdateAndId = {
|
|
29
|
+
update: WrappedOptimisticUpdate;
|
|
30
|
+
mutationId: RequestId;
|
|
31
|
+
};
|
|
32
|
+
/** A store writing into `overlay`; `modifiedQueries` collects the tokens it sets. */
|
|
33
|
+
export declare class OptimisticLocalStoreImpl implements OptimisticLocalStore {
|
|
34
|
+
private readonly overlay;
|
|
35
|
+
private readonly server;
|
|
36
|
+
readonly modifiedQueries: QueryToken[];
|
|
37
|
+
constructor(overlay: Overlay, server: ServerResults);
|
|
38
|
+
getQuery<Query extends FunctionReference<"query"> | FunctionReference_future<"query">>(query: Query, ...args: OptionalRestArgs<Query>): undefined | FunctionReturnType<Query>;
|
|
39
|
+
getAllQueries<Query extends FunctionReference<"query"> | FunctionReference_future<"query">>(query: Query): {
|
|
40
|
+
args: FunctionArgs<Query>;
|
|
41
|
+
value: undefined | FunctionReturnType<Query>;
|
|
42
|
+
}[];
|
|
43
|
+
setQuery<Query extends FunctionReference<"query"> | FunctionReference_future<"query">>(queryReference: Query, args: FunctionArgs<Query>, value: undefined | FunctionReturnType<Query>): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimistic updates over server results. The overlay holds only the results
|
|
3
|
+
* updates have set; everything else reads through to the server results of
|
|
4
|
+
* subscribed queries.
|
|
5
|
+
*/
|
|
6
|
+
import { getFunctionName, } from "convex/server";
|
|
7
|
+
import { parseArgs } from "../../common/index.js";
|
|
8
|
+
import { canonicalizeUdfPath, serializePathAndArgs, } from "../sync/udf_path_utils.js";
|
|
9
|
+
// An errored query reads as loading, so one failed query doesn't break an
|
|
10
|
+
// update that reads it.
|
|
11
|
+
function queryValue(result) {
|
|
12
|
+
if (result === undefined || !result.success)
|
|
13
|
+
return undefined;
|
|
14
|
+
return result.value;
|
|
15
|
+
}
|
|
16
|
+
/** A store writing into `overlay`; `modifiedQueries` collects the tokens it sets. */
|
|
17
|
+
export class OptimisticLocalStoreImpl {
|
|
18
|
+
overlay;
|
|
19
|
+
server;
|
|
20
|
+
modifiedQueries = [];
|
|
21
|
+
constructor(overlay, server) {
|
|
22
|
+
this.overlay = overlay;
|
|
23
|
+
this.server = server;
|
|
24
|
+
}
|
|
25
|
+
getQuery(query, ...args) {
|
|
26
|
+
const token = serializePathAndArgs(getFunctionName(query), parseArgs(args[0]));
|
|
27
|
+
const local = this.overlay.get(token);
|
|
28
|
+
return queryValue(local ? local.result : this.server.get(token));
|
|
29
|
+
}
|
|
30
|
+
getAllQueries(query) {
|
|
31
|
+
const udfPath = canonicalizeUdfPath(getFunctionName(query));
|
|
32
|
+
const queries = [];
|
|
33
|
+
// Server results keep their position when an update overrides them;
|
|
34
|
+
// queries only updates know about come after.
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
for (const subscribed of this.server.all()) {
|
|
37
|
+
seen.add(subscribed.token);
|
|
38
|
+
if (subscribed.udfPath !== udfPath)
|
|
39
|
+
continue;
|
|
40
|
+
const local = this.overlay.get(subscribed.token);
|
|
41
|
+
queries.push({
|
|
42
|
+
args: subscribed.args,
|
|
43
|
+
value: queryValue(local ? local.result : subscribed.result),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
for (const [token, local] of this.overlay) {
|
|
47
|
+
if (seen.has(token) || local.udfPath !== udfPath)
|
|
48
|
+
continue;
|
|
49
|
+
queries.push({
|
|
50
|
+
args: local.args,
|
|
51
|
+
value: queryValue(local.result),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return queries;
|
|
55
|
+
}
|
|
56
|
+
setQuery(queryReference, args, value) {
|
|
57
|
+
const queryArgs = parseArgs(args);
|
|
58
|
+
const name = getFunctionName(queryReference);
|
|
59
|
+
const token = serializePathAndArgs(name, queryArgs);
|
|
60
|
+
this.overlay.set(token, {
|
|
61
|
+
udfPath: canonicalizeUdfPath(name),
|
|
62
|
+
args: queryArgs,
|
|
63
|
+
// An optimistic update has no function logs to show.
|
|
64
|
+
result: value === undefined
|
|
65
|
+
? undefined
|
|
66
|
+
: { success: true, value, logLines: [] },
|
|
67
|
+
});
|
|
68
|
+
this.modifiedQueries.push(token);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive pagination over page queries. Loaded pages stay subscribed and
|
|
3
|
+
* keep their boundaries; a page the server asks to split is replaced by two
|
|
4
|
+
* once both halves have loaded. The page list derives itself from its
|
|
5
|
+
* previous value and the page results it reads, so subscribing it subscribes
|
|
6
|
+
* exactly the pages in use.
|
|
7
|
+
*/
|
|
8
|
+
import { type Action, type AtomLike, type Computed } from "@reatom/core";
|
|
9
|
+
import { Value } from "convex/values";
|
|
10
|
+
import { FunctionResult } from "../sync/function_result.js";
|
|
11
|
+
import { PaginationStatus } from "../sync/pagination.js";
|
|
12
|
+
import { PaginatedQueryToken, QueryToken } from "../sync/udf_path_utils.js";
|
|
13
|
+
/** What a paginated query needs of a page query. */
|
|
14
|
+
export type PageQuery = {
|
|
15
|
+
readonly token: QueryToken;
|
|
16
|
+
readonly result: AtomLike<FunctionResult | undefined, []>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The state of a {@link ReatomPaginatedQuery}.
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export type PaginatedQueryState<Item> = {
|
|
24
|
+
results: Item[];
|
|
25
|
+
status: PaginationStatus;
|
|
26
|
+
/** The error a page failed with. */
|
|
27
|
+
error?: Error;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A paginated query of a {@link ReatomConvexClient}.
|
|
31
|
+
*
|
|
32
|
+
* Reading its atoms inside a subscribed Reatom graph subscribes its pages.
|
|
33
|
+
* Once nothing reads it, it drops its pages and starts over from the first
|
|
34
|
+
* one.
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export interface ReatomPaginatedQuery<Item = Value> {
|
|
39
|
+
readonly token: PaginatedQueryToken;
|
|
40
|
+
readonly state: Computed<PaginatedQueryState<Item>>;
|
|
41
|
+
readonly results: Computed<Item[]>;
|
|
42
|
+
readonly status: Computed<PaginationStatus>;
|
|
43
|
+
readonly error: Computed<Error | undefined>;
|
|
44
|
+
/**
|
|
45
|
+
* Load `numItems` more items. Returns whether a page load started: not
|
|
46
|
+
* while a page loads or once the query is exhausted.
|
|
47
|
+
*/
|
|
48
|
+
readonly loadMore: Action<[numItems: number], boolean>;
|
|
49
|
+
}
|
|
50
|
+
export type PaginatedOptions = {
|
|
51
|
+
initialNumItems: number;
|
|
52
|
+
id: number;
|
|
53
|
+
};
|
|
54
|
+
type PageFactory = (udfPath: string, args: Record<string, Value>, paginationOpts: Record<string, Value>) => PageQuery;
|
|
55
|
+
export declare function reatomPaginatedQuery(page: PageFactory, udfPath: string, args: Record<string, Value>, options: PaginatedOptions, name: string): ReatomPaginatedQuery;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive pagination over page queries. Loaded pages stay subscribed and
|
|
3
|
+
* keep their boundaries; a page the server asks to split is replaced by two
|
|
4
|
+
* once both halves have loaded. The page list derives itself from its
|
|
5
|
+
* previous value and the page results it reads, so subscribing it subscribes
|
|
6
|
+
* exactly the pages in use.
|
|
7
|
+
*/
|
|
8
|
+
import { action, atom, computed, noop, withComputed, } from "@reatom/core";
|
|
9
|
+
import { asPaginationResult } from "../sync/pagination.js";
|
|
10
|
+
import { serializePaginatedPathAndArgs, } from "../sync/udf_path_utils.js";
|
|
11
|
+
import { withConnectionHooks } from "./connection_hooks.js";
|
|
12
|
+
import { functionError } from "./errors.js";
|
|
13
|
+
const loadedPage = (query) => {
|
|
14
|
+
const result = query.result();
|
|
15
|
+
return result?.success ? asPaginationResult(result.value) : undefined;
|
|
16
|
+
};
|
|
17
|
+
export function reatomPaginatedQuery(page, udfPath, args, options, name) {
|
|
18
|
+
const { initialNumItems, id } = options;
|
|
19
|
+
const token = serializePaginatedPathAndArgs(udfPath, args, options);
|
|
20
|
+
const pageAt = (paginationOpts, cursor) => ({
|
|
21
|
+
query: page(udfPath, args, paginationOpts),
|
|
22
|
+
cursor,
|
|
23
|
+
});
|
|
24
|
+
const firstPage = () => ({
|
|
25
|
+
keys: [0],
|
|
26
|
+
pages: new Map([
|
|
27
|
+
[0, pageAt({ cursor: null, numItems: initialNumItems, id }, null)],
|
|
28
|
+
]),
|
|
29
|
+
splits: new Map(),
|
|
30
|
+
nextKey: 1,
|
|
31
|
+
});
|
|
32
|
+
/** Swaps in the halves of each split once both have loaded. */
|
|
33
|
+
const completeSplits = (list) => {
|
|
34
|
+
let next = list;
|
|
35
|
+
for (const [key, [first, second]] of list.splits) {
|
|
36
|
+
const halvesLoaded = list.pages.get(first).query.result() !== undefined &&
|
|
37
|
+
list.pages.get(second).query.result() !== undefined;
|
|
38
|
+
if (!halvesLoaded)
|
|
39
|
+
continue;
|
|
40
|
+
const pages = new Map(next.pages);
|
|
41
|
+
pages.delete(key);
|
|
42
|
+
const splits = new Map(next.splits);
|
|
43
|
+
splits.delete(key);
|
|
44
|
+
next = {
|
|
45
|
+
...next,
|
|
46
|
+
keys: next.keys.flatMap((current) => current === key ? [first, second] : [current]),
|
|
47
|
+
pages,
|
|
48
|
+
splits,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return next;
|
|
52
|
+
};
|
|
53
|
+
/** Starts splitting each active page the server or its size asks to split. */
|
|
54
|
+
const startSplits = (list) => {
|
|
55
|
+
let next = list;
|
|
56
|
+
for (const key of list.keys) {
|
|
57
|
+
if (next.splits.has(key))
|
|
58
|
+
continue;
|
|
59
|
+
const current = list.pages.get(key);
|
|
60
|
+
const result = loadedPage(current.query);
|
|
61
|
+
if (!result)
|
|
62
|
+
continue;
|
|
63
|
+
const shouldSplit = result.splitCursor &&
|
|
64
|
+
(result.pageStatus === "SplitRecommended" ||
|
|
65
|
+
result.pageStatus === "SplitRequired" ||
|
|
66
|
+
// This client-driven page splitting condition may change.
|
|
67
|
+
result.page.length > initialNumItems * 2);
|
|
68
|
+
if (!shouldSplit)
|
|
69
|
+
continue;
|
|
70
|
+
const splitCursor = result.splitCursor;
|
|
71
|
+
const first = next.nextKey;
|
|
72
|
+
const second = first + 1;
|
|
73
|
+
const paginationOpts = { numItems: initialNumItems, id };
|
|
74
|
+
const pages = new Map(next.pages);
|
|
75
|
+
pages.set(first, pageAt({ ...paginationOpts, cursor: current.cursor, endCursor: splitCursor }, current.cursor));
|
|
76
|
+
pages.set(second, pageAt({
|
|
77
|
+
...paginationOpts,
|
|
78
|
+
cursor: splitCursor,
|
|
79
|
+
endCursor: result.continueCursor,
|
|
80
|
+
}, splitCursor));
|
|
81
|
+
// Read now, so the halves loading derives the list again.
|
|
82
|
+
pages.get(first).query.result();
|
|
83
|
+
pages.get(second).query.result();
|
|
84
|
+
next = {
|
|
85
|
+
...next,
|
|
86
|
+
pages,
|
|
87
|
+
splits: new Map(next.splits).set(key, [first, second]),
|
|
88
|
+
nextKey: second + 1,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return next;
|
|
92
|
+
};
|
|
93
|
+
const pages = atom(null, `${name}.pages`).extend(withComputed((current) => {
|
|
94
|
+
const next = startSplits(completeSplits(current ?? firstPage()));
|
|
95
|
+
// Exactly the pages in the list are read, so a replaced page drops.
|
|
96
|
+
for (const { query } of next.pages.values())
|
|
97
|
+
query.result();
|
|
98
|
+
return next;
|
|
99
|
+
}),
|
|
100
|
+
// Like `PaginatedQueryClient`, forget loaded pages once unused.
|
|
101
|
+
withConnectionHooks(noop, () => {
|
|
102
|
+
pages.set(null);
|
|
103
|
+
}));
|
|
104
|
+
const state = computed(() => {
|
|
105
|
+
const list = pages();
|
|
106
|
+
let results = [];
|
|
107
|
+
// A page is loading; only the last one is supposed to.
|
|
108
|
+
let hasUndefined = false;
|
|
109
|
+
let isDone = false;
|
|
110
|
+
for (const key of list.keys) {
|
|
111
|
+
const result = list.pages.get(key).query.result();
|
|
112
|
+
if (result === undefined) {
|
|
113
|
+
hasUndefined = true;
|
|
114
|
+
isDone = false;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (!result.success) {
|
|
118
|
+
return {
|
|
119
|
+
results: [],
|
|
120
|
+
status: "LoadingFirstPage",
|
|
121
|
+
error: functionError("query", udfPath, result),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const paginationResult = asPaginationResult(result.value);
|
|
125
|
+
results = results.concat(paginationResult.page);
|
|
126
|
+
isDone = !!paginationResult.isDone;
|
|
127
|
+
}
|
|
128
|
+
const status = hasUndefined
|
|
129
|
+
? results.length === 0
|
|
130
|
+
? "LoadingFirstPage"
|
|
131
|
+
: "LoadingMore"
|
|
132
|
+
: isDone
|
|
133
|
+
? "Exhausted"
|
|
134
|
+
: "CanLoadMore";
|
|
135
|
+
return { results, status };
|
|
136
|
+
}, `${name}.state`);
|
|
137
|
+
const loadMore = action((numItems) => {
|
|
138
|
+
const list = pages();
|
|
139
|
+
const last = list.pages.get(list.keys[list.keys.length - 1]);
|
|
140
|
+
const result = loadedPage(last.query);
|
|
141
|
+
// Still loading a page, or no more pages.
|
|
142
|
+
if (!result || result.isDone)
|
|
143
|
+
return false;
|
|
144
|
+
const key = list.nextKey;
|
|
145
|
+
pages.set({
|
|
146
|
+
...list,
|
|
147
|
+
keys: [...list.keys, key],
|
|
148
|
+
pages: new Map(list.pages).set(key, pageAt({ cursor: result.continueCursor, numItems, id }, result.continueCursor)),
|
|
149
|
+
nextKey: key + 1,
|
|
150
|
+
});
|
|
151
|
+
return true;
|
|
152
|
+
}, `${name}.loadMore`);
|
|
153
|
+
return {
|
|
154
|
+
token,
|
|
155
|
+
state,
|
|
156
|
+
results: computed(() => state().results, `${name}.results`),
|
|
157
|
+
status: computed(() => state().status, `${name}.status`),
|
|
158
|
+
error: computed(() => state().error, `${name}.error`),
|
|
159
|
+
loadMore,
|
|
160
|
+
pageTokens: () => Array.from(pages().pages.values(), ({ query }) => query.token),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Logger } from "../logging.js";
|
|
2
|
+
import { ClientMessage, ServerMessage } from "../sync/protocol.js";
|
|
3
|
+
export type SocketState = {
|
|
4
|
+
state: "disconnected";
|
|
5
|
+
} | {
|
|
6
|
+
state: "connecting";
|
|
7
|
+
paused: "yes" | "no";
|
|
8
|
+
} | {
|
|
9
|
+
state: "ready";
|
|
10
|
+
paused: "yes" | "no" | "uninitialized";
|
|
11
|
+
} | {
|
|
12
|
+
state: "stopped";
|
|
13
|
+
} | {
|
|
14
|
+
state: "terminated";
|
|
15
|
+
};
|
|
16
|
+
export type ReconnectMetadata = {
|
|
17
|
+
connectionCount: number;
|
|
18
|
+
lastCloseReason: string | null;
|
|
19
|
+
clientTs: number;
|
|
20
|
+
};
|
|
21
|
+
export type SocketCallbacks = {
|
|
22
|
+
onOpen: (reconnectMetadata: ReconnectMetadata) => void;
|
|
23
|
+
onResume: () => void;
|
|
24
|
+
/** Returns whether the client has synced past the last reconnect. */
|
|
25
|
+
onMessage: (message: ServerMessage) => boolean;
|
|
26
|
+
onServerDisconnectError?: ((message: string) => void) | undefined;
|
|
27
|
+
};
|
|
28
|
+
export declare function monotonicMillis(): number;
|
|
29
|
+
export declare function reatomSocket(uri: string, options: {
|
|
30
|
+
webSocketConstructor: typeof WebSocket;
|
|
31
|
+
logger: Logger;
|
|
32
|
+
debug: boolean;
|
|
33
|
+
}, callbacks: SocketCallbacks, name: string): {
|
|
34
|
+
/** The state machine position, without the socket handle. */
|
|
35
|
+
state: import("@reatom/core").Atom<SocketState, [newState: SocketState]>;
|
|
36
|
+
/** Times a socket reached `ready`. */
|
|
37
|
+
connectionCount: import("@reatom/core").Atom<number, [newState: number]>;
|
|
38
|
+
retries: import("@reatom/core").Atom<number, [newState: number]>;
|
|
39
|
+
hasEverConnected: import("@reatom/core").Atom<boolean, [newState: boolean]>;
|
|
40
|
+
send: (message: ClientMessage) => boolean;
|
|
41
|
+
/** Opens the first socket. */
|
|
42
|
+
start(): void;
|
|
43
|
+
/** Closes the socket until `tryRestart`. */
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
/** Reconnects after `stop`, unless terminated meanwhile. */
|
|
46
|
+
tryRestart(): void;
|
|
47
|
+
pause(): void;
|
|
48
|
+
resume(): void;
|
|
49
|
+
/** Closes the socket for good. */
|
|
50
|
+
terminate(): Promise<void>;
|
|
51
|
+
};
|
|
52
|
+
export type ReatomSocket = ReturnType<typeof reatomSocket>;
|