@rxferrix/client 0.0.0-stage → 0.2.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/api.d.ts +60 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +22 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.d.ts +256 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +541 -0
- package/dist/auth.js.map +1 -0
- package/dist/bytes.d.ts +21 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +51 -0
- package/dist/bytes.js.map +1 -0
- package/dist/canonical.d.ts +13 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +66 -0
- package/dist/canonical.js.map +1 -0
- package/dist/client.d.ts +170 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +638 -0
- package/dist/client.js.map +1 -0
- package/dist/diff.d.ts +20 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +100 -0
- package/dist/diff.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +34 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/local_query_set.d.ts +56 -0
- package/dist/local_query_set.d.ts.map +1 -0
- package/dist/local_query_set.js +108 -0
- package/dist/local_query_set.js.map +1 -0
- package/dist/logging.d.ts +15 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +17 -0
- package/dist/logging.js.map +1 -0
- package/dist/optimistic_updates.d.ts +48 -0
- package/dist/optimistic_updates.d.ts.map +1 -0
- package/dist/optimistic_updates.js +113 -0
- package/dist/optimistic_updates.js.map +1 -0
- package/dist/paginated_query.d.ts +118 -0
- package/dist/paginated_query.d.ts.map +1 -0
- package/dist/paginated_query.js +199 -0
- package/dist/paginated_query.js.map +1 -0
- package/dist/protocol.d.ts +136 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +11 -0
- package/dist/protocol.js.map +1 -0
- package/dist/query_state.d.ts +16 -0
- package/dist/query_state.d.ts.map +1 -0
- package/dist/query_state.js +2 -0
- package/dist/query_state.js.map +1 -0
- package/dist/remote_query_set.d.ts +37 -0
- package/dist/remote_query_set.d.ts.map +1 -0
- package/dist/remote_query_set.js +87 -0
- package/dist/remote_query_set.js.map +1 -0
- package/dist/request_manager.d.ts +57 -0
- package/dist/request_manager.d.ts.map +1 -0
- package/dist/request_manager.js +163 -0
- package/dist/request_manager.js.map +1 -0
- package/dist/storage.d.ts +45 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +120 -0
- package/dist/storage.js.map +1 -0
- package/dist/testing.d.ts +68 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +97 -0
- package/dist/testing.js.map +1 -0
- package/dist/web_socket_manager.d.ts +88 -0
- package/dist/web_socket_manager.d.ts.map +1 -0
- package/dist/web_socket_manager.js +291 -0
- package/dist/web_socket_manager.js.map +1 -0
- package/package.json +50 -4
- package/src/api.ts +86 -0
- package/src/auth.ts +646 -0
- package/src/bytes.ts +57 -0
- package/src/canonical.ts +61 -0
- package/src/client.ts +740 -0
- package/src/diff.ts +87 -0
- package/src/errors.ts +36 -0
- package/src/index.ts +60 -0
- package/src/local_query_set.ts +130 -0
- package/src/logging.ts +26 -0
- package/src/optimistic_updates.ts +137 -0
- package/src/paginated_query.ts +299 -0
- package/src/protocol.ts +152 -0
- package/src/query_state.ts +12 -0
- package/src/remote_query_set.ts +105 -0
- package/src/request_manager.ts +199 -0
- package/src/storage.ts +171 -0
- package/src/testing.ts +120 -0
- package/src/web_socket_manager.ts +322 -0
- package/README.md +0 -3
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { applyDiff, DiffMismatch } from "./diff.js";
|
|
2
|
+
import { RxFerrixError } from "./errors.js";
|
|
3
|
+
import { formatVersion, versionsEqual, } from "./protocol.js";
|
|
4
|
+
export class TransitionVersionMismatch extends Error {
|
|
5
|
+
constructor(expected, got) {
|
|
6
|
+
super(`Transition startVersion ${formatVersion(got)} does not match client version ${formatVersion(expected)}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The server's view of the world for the current socket: the StateVersion and the latest result for every query.
|
|
11
|
+
* Transitions are validated (`startVersion` must equal the current version) and then applied atomically.
|
|
12
|
+
*/
|
|
13
|
+
export class RemoteQuerySet {
|
|
14
|
+
_version = { querySet: 0, identity: 0, ts: 0 };
|
|
15
|
+
results = new Map();
|
|
16
|
+
get version() {
|
|
17
|
+
return this._version;
|
|
18
|
+
}
|
|
19
|
+
result(queryId) {
|
|
20
|
+
return this.results.get(queryId);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Applies a transition. Modifications for queries that `isKnown` rejects (removed locally while the transition was
|
|
24
|
+
* in flight) are ignored. Throws {@link TransitionVersionMismatch} without changing anything when the transition
|
|
25
|
+
* does not start at the current version, and {@link DiffMismatch} when a result diff doesn't fit the result held.
|
|
26
|
+
*/
|
|
27
|
+
transition(message, isKnown) {
|
|
28
|
+
if (!versionsEqual(message.startVersion, this._version)) {
|
|
29
|
+
throw new TransitionVersionMismatch(this._version, message.startVersion);
|
|
30
|
+
}
|
|
31
|
+
// Diffs are applied first, against the results as they were: if one doesn't fit, nothing changes.
|
|
32
|
+
const diffed = new Map();
|
|
33
|
+
for (const modification of message.modifications ?? []) {
|
|
34
|
+
if (modification.type !== "QueryUpdated" || !("diff" in modification) || !isKnown(modification.queryId))
|
|
35
|
+
continue;
|
|
36
|
+
const current = this.results.get(modification.queryId);
|
|
37
|
+
if (current?.status !== "success") {
|
|
38
|
+
throw new DiffMismatch(`query ${modification.queryId} has no result to apply a diff to`);
|
|
39
|
+
}
|
|
40
|
+
diffed.set(modification, applyDiff(current.value, modification.diff));
|
|
41
|
+
}
|
|
42
|
+
const changed = [];
|
|
43
|
+
const logLines = [];
|
|
44
|
+
for (const modification of message.modifications ?? []) {
|
|
45
|
+
const queryId = modification.queryId;
|
|
46
|
+
if (!isKnown(queryId))
|
|
47
|
+
continue;
|
|
48
|
+
switch (modification.type) {
|
|
49
|
+
case "QueryUpdated":
|
|
50
|
+
this.results.set(queryId, {
|
|
51
|
+
status: "success",
|
|
52
|
+
value: "diff" in modification ? diffed.get(modification) : modification.value,
|
|
53
|
+
});
|
|
54
|
+
break;
|
|
55
|
+
case "QueryFailed":
|
|
56
|
+
this.results.set(queryId, {
|
|
57
|
+
status: "error",
|
|
58
|
+
error: RxFerrixError.fromServer(modification.errorMessage, modification.errorData),
|
|
59
|
+
});
|
|
60
|
+
break;
|
|
61
|
+
case "QueryRemoved":
|
|
62
|
+
this.results.delete(queryId);
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
changed.push(queryId);
|
|
68
|
+
if ("logLines" in modification && modification.logLines && modification.logLines.length > 0) {
|
|
69
|
+
logLines.push({ queryId, lines: modification.logLines });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
this._version = { ...message.endVersion };
|
|
73
|
+
return { changed, logLines };
|
|
74
|
+
}
|
|
75
|
+
/** Forgets a query's result (it was removed from the local query set). */
|
|
76
|
+
remove(queryId) {
|
|
77
|
+
this.results.delete(queryId);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A new socket starts from a fresh server state (all versions 0). Results are kept so the UI keeps showing the
|
|
81
|
+
* last known data until the re-subscribed queries report again.
|
|
82
|
+
*/
|
|
83
|
+
resetVersion() {
|
|
84
|
+
this._version = { querySet: 0, identity: 0, ts: 0 };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=remote_query_set.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote_query_set.js","sourceRoot":"","sources":["../src/remote_query_set.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { RxFerrixError } from "./errors.js";
|
|
2
|
+
import type { ActionMessage, ActionResponseMessage, MutationMessage, MutationResponseMessage, RequestId } from "./protocol.js";
|
|
3
|
+
export interface SettledMutation {
|
|
4
|
+
requestId: RequestId;
|
|
5
|
+
settle: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Tracks in-flight mutations and actions.
|
|
9
|
+
*
|
|
10
|
+
* A mutation's promise resolves only once BOTH its successful `MutationResponse { ts: M }` has arrived AND the client
|
|
11
|
+
* has applied a Transition with `endVersion.ts >= M` (read-your-writes). Failures reject immediately.
|
|
12
|
+
*
|
|
13
|
+
* Settling is split in two (`settle` callbacks are returned rather than invoked) so the client can update its query
|
|
14
|
+
* view (dropping optimistic updates) before promise continuations run.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RequestManager {
|
|
17
|
+
private nextRequestId;
|
|
18
|
+
private readonly requests;
|
|
19
|
+
allocateRequestId(): RequestId;
|
|
20
|
+
trackMutation(message: MutationMessage): Promise<unknown>;
|
|
21
|
+
trackAction(message: ActionMessage): Promise<unknown>;
|
|
22
|
+
/** Messages that still have to go out on the current socket, in request order. */
|
|
23
|
+
unsent(): (MutationMessage | ActionMessage)[];
|
|
24
|
+
/** The message went out on the current socket. */
|
|
25
|
+
markSent(requestId: RequestId): void;
|
|
26
|
+
/**
|
|
27
|
+
* Handles a MutationResponse. `observedTs` is the ts of the last applied Transition, or null when the client's view
|
|
28
|
+
* is not currently consistent with the server (right after a reconnect).
|
|
29
|
+
*
|
|
30
|
+
* Returns the settled mutation (whose optimistic update must be dropped), or null if it is still waiting for its
|
|
31
|
+
* ts to be observed (or the requestId is unknown).
|
|
32
|
+
*/
|
|
33
|
+
onMutationResponse(response: MutationResponseMessage, observedTs: number | null): SettledMutation | null;
|
|
34
|
+
/** Handles an ActionResponse. Returns the settle callback, or null for an unknown request. */
|
|
35
|
+
onActionResponse(response: ActionResponseMessage): (() => void) | null;
|
|
36
|
+
/** A Transition with `endVersion.ts = ts` was applied: completes every mutation with a commit ts <= ts. */
|
|
37
|
+
removeCompleted(ts: number): SettledMutation[];
|
|
38
|
+
/**
|
|
39
|
+
* The socket dropped. Mutations without a response will be re-sent on the next socket. Actions that were already
|
|
40
|
+
* sent are rejected because they are not idempotent; actions that never left the client stay queued.
|
|
41
|
+
*/
|
|
42
|
+
onSocketClosed(): {
|
|
43
|
+
settle: () => void;
|
|
44
|
+
requestId: RequestId;
|
|
45
|
+
}[];
|
|
46
|
+
/** Rejects everything (client closed). */
|
|
47
|
+
rejectAll(error: RxFerrixError): void;
|
|
48
|
+
hasInflight(): boolean;
|
|
49
|
+
hasInflightMutations(): boolean;
|
|
50
|
+
counts(): {
|
|
51
|
+
mutations: number;
|
|
52
|
+
actions: number;
|
|
53
|
+
oldestStartedAt: number | null;
|
|
54
|
+
};
|
|
55
|
+
udfPathOf(requestId: RequestId): string | undefined;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=request_manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request_manager.d.ts","sourceRoot":"","sources":["../src/request_manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EACV,aAAa,EACb,qBAAqB,EACrB,eAAe,EACf,uBAAuB,EACvB,SAAS,EACV,MAAM,eAAe,CAAC;AAuBvB,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAE1D,iBAAiB,IAAI,SAAS;IAI9B,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IAczD,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAarD,kFAAkF;IAClF,MAAM,IAAI,CAAC,eAAe,GAAG,aAAa,CAAC,EAAE;IAU7C,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAKpC;;;;;;OAMG;IACH,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,eAAe,GAAG,IAAI;IAiBxG,8FAA8F;IAC9F,gBAAgB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI;IAYtE,2GAA2G;IAC3G,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,EAAE;IAW9C;;;OAGG;IACH,cAAc,IAAI;QAAE,MAAM,EAAE,MAAM,IAAI,CAAC;QAAC,SAAS,EAAE,SAAS,CAAA;KAAE,EAAE;IAkBhE,0CAA0C;IAC1C,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAMrC,WAAW,IAAI,OAAO;IAItB,oBAAoB,IAAI,OAAO;IAK/B,MAAM,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;IAYhF,SAAS,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;CAGpD"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { CONNECTION_LOST, RxFerrixError } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tracks in-flight mutations and actions.
|
|
4
|
+
*
|
|
5
|
+
* A mutation's promise resolves only once BOTH its successful `MutationResponse { ts: M }` has arrived AND the client
|
|
6
|
+
* has applied a Transition with `endVersion.ts >= M` (read-your-writes). Failures reject immediately.
|
|
7
|
+
*
|
|
8
|
+
* Settling is split in two (`settle` callbacks are returned rather than invoked) so the client can update its query
|
|
9
|
+
* view (dropping optimistic updates) before promise continuations run.
|
|
10
|
+
*/
|
|
11
|
+
export class RequestManager {
|
|
12
|
+
nextRequestId = 0;
|
|
13
|
+
requests = new Map();
|
|
14
|
+
allocateRequestId() {
|
|
15
|
+
return this.nextRequestId++;
|
|
16
|
+
}
|
|
17
|
+
trackMutation(message) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
this.requests.set(message.requestId, {
|
|
20
|
+
kind: "mutation",
|
|
21
|
+
message,
|
|
22
|
+
sent: false,
|
|
23
|
+
completed: null,
|
|
24
|
+
startedAt: Date.now(),
|
|
25
|
+
resolve,
|
|
26
|
+
reject,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
trackAction(message) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
this.requests.set(message.requestId, {
|
|
33
|
+
kind: "action",
|
|
34
|
+
message,
|
|
35
|
+
sent: false,
|
|
36
|
+
startedAt: Date.now(),
|
|
37
|
+
resolve,
|
|
38
|
+
reject,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Messages that still have to go out on the current socket, in request order. */
|
|
43
|
+
unsent() {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const request of this.requests.values()) {
|
|
46
|
+
if (request.sent)
|
|
47
|
+
continue;
|
|
48
|
+
if (request.kind === "mutation" && request.completed)
|
|
49
|
+
continue;
|
|
50
|
+
out.push(request.message);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/** The message went out on the current socket. */
|
|
55
|
+
markSent(requestId) {
|
|
56
|
+
const request = this.requests.get(requestId);
|
|
57
|
+
if (request)
|
|
58
|
+
request.sent = true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Handles a MutationResponse. `observedTs` is the ts of the last applied Transition, or null when the client's view
|
|
62
|
+
* is not currently consistent with the server (right after a reconnect).
|
|
63
|
+
*
|
|
64
|
+
* Returns the settled mutation (whose optimistic update must be dropped), or null if it is still waiting for its
|
|
65
|
+
* ts to be observed (or the requestId is unknown).
|
|
66
|
+
*/
|
|
67
|
+
onMutationResponse(response, observedTs) {
|
|
68
|
+
const request = this.requests.get(response.requestId);
|
|
69
|
+
if (!request || request.kind !== "mutation" || request.completed)
|
|
70
|
+
return null;
|
|
71
|
+
if (!response.success) {
|
|
72
|
+
this.requests.delete(response.requestId);
|
|
73
|
+
const error = RxFerrixError.fromServer(response.errorMessage, response.errorData);
|
|
74
|
+
return { requestId: response.requestId, settle: () => request.reject(error) };
|
|
75
|
+
}
|
|
76
|
+
request.completed = { ts: response.ts, result: response.result };
|
|
77
|
+
if (observedTs !== null && observedTs >= response.ts) {
|
|
78
|
+
this.requests.delete(response.requestId);
|
|
79
|
+
const result = response.result;
|
|
80
|
+
return { requestId: response.requestId, settle: () => request.resolve(result) };
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/** Handles an ActionResponse. Returns the settle callback, or null for an unknown request. */
|
|
85
|
+
onActionResponse(response) {
|
|
86
|
+
const request = this.requests.get(response.requestId);
|
|
87
|
+
if (!request || request.kind !== "action")
|
|
88
|
+
return null;
|
|
89
|
+
this.requests.delete(response.requestId);
|
|
90
|
+
if (response.success) {
|
|
91
|
+
const result = response.result;
|
|
92
|
+
return () => request.resolve(result);
|
|
93
|
+
}
|
|
94
|
+
const error = RxFerrixError.fromServer(response.errorMessage, response.errorData);
|
|
95
|
+
return () => request.reject(error);
|
|
96
|
+
}
|
|
97
|
+
/** A Transition with `endVersion.ts = ts` was applied: completes every mutation with a commit ts <= ts. */
|
|
98
|
+
removeCompleted(ts) {
|
|
99
|
+
const settled = [];
|
|
100
|
+
for (const [requestId, request] of this.requests) {
|
|
101
|
+
if (request.kind !== "mutation" || !request.completed || request.completed.ts > ts)
|
|
102
|
+
continue;
|
|
103
|
+
this.requests.delete(requestId);
|
|
104
|
+
const result = request.completed.result;
|
|
105
|
+
settled.push({ requestId, settle: () => request.resolve(result) });
|
|
106
|
+
}
|
|
107
|
+
return settled;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The socket dropped. Mutations without a response will be re-sent on the next socket. Actions that were already
|
|
111
|
+
* sent are rejected because they are not idempotent; actions that never left the client stay queued.
|
|
112
|
+
*/
|
|
113
|
+
onSocketClosed() {
|
|
114
|
+
const rejected = [];
|
|
115
|
+
for (const [requestId, request] of this.requests) {
|
|
116
|
+
if (request.kind === "mutation") {
|
|
117
|
+
if (!request.completed)
|
|
118
|
+
request.sent = false;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!request.sent)
|
|
122
|
+
continue;
|
|
123
|
+
this.requests.delete(requestId);
|
|
124
|
+
const error = new RxFerrixError(`Connection lost while action "${request.message.udfPath}" was in flight`, { code: CONNECTION_LOST });
|
|
125
|
+
rejected.push({ requestId, settle: () => request.reject(error) });
|
|
126
|
+
}
|
|
127
|
+
return rejected;
|
|
128
|
+
}
|
|
129
|
+
/** Rejects everything (client closed). */
|
|
130
|
+
rejectAll(error) {
|
|
131
|
+
const all = [...this.requests.values()];
|
|
132
|
+
this.requests.clear();
|
|
133
|
+
for (const request of all)
|
|
134
|
+
request.reject(error);
|
|
135
|
+
}
|
|
136
|
+
hasInflight() {
|
|
137
|
+
return this.requests.size > 0;
|
|
138
|
+
}
|
|
139
|
+
hasInflightMutations() {
|
|
140
|
+
for (const request of this.requests.values())
|
|
141
|
+
if (request.kind === "mutation")
|
|
142
|
+
return true;
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
counts() {
|
|
146
|
+
let mutations = 0;
|
|
147
|
+
let actions = 0;
|
|
148
|
+
let oldest = null;
|
|
149
|
+
for (const request of this.requests.values()) {
|
|
150
|
+
if (request.kind === "mutation")
|
|
151
|
+
mutations++;
|
|
152
|
+
else
|
|
153
|
+
actions++;
|
|
154
|
+
if (oldest === null || request.startedAt < oldest)
|
|
155
|
+
oldest = request.startedAt;
|
|
156
|
+
}
|
|
157
|
+
return { mutations, actions, oldestStartedAt: oldest };
|
|
158
|
+
}
|
|
159
|
+
udfPathOf(requestId) {
|
|
160
|
+
return this.requests.get(requestId)?.message.udfPath;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=request_manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request_manager.js","sourceRoot":"","sources":["../src/request_manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmC7D;;;;;;;;GAQG;AACH,MAAM,OAAO,cAAc;IACjB,aAAa,GAAG,CAAC,CAAC;IACT,QAAQ,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE1D,iBAAiB;QACf,OAAO,IAAI,CAAC,aAAa,EAAE,CAAC;IAC9B,CAAC;IAED,aAAa,CAAC,OAAwB;QACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE;gBACnC,IAAI,EAAE,UAAU;gBAChB,OAAO;gBACP,IAAI,EAAE,KAAK;gBACX,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,OAAO;gBACP,MAAM;aACP,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,WAAW,CAAC,OAAsB;QAChC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE;gBACnC,IAAI,EAAE,QAAQ;gBACd,OAAO;gBACP,IAAI,EAAE,KAAK;gBACX,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,OAAO;gBACP,MAAM;aACP,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,kFAAkF;IAClF,MAAM;QACJ,MAAM,GAAG,GAAwC,EAAE,CAAC;QACpD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IAAI,OAAO,CAAC,IAAI;gBAAE,SAAS;YAC3B,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,SAAS;gBAAE,SAAS;YAC/D,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,kDAAkD;IAClD,QAAQ,CAAC,SAAoB;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,OAAO;YAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IACnC,CAAC;IAED;;;;;;OAMG;IACH,kBAAkB,CAAC,QAAiC,EAAE,UAAyB;QAC7E,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QAC9E,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YACzC,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;YAClF,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAChF,CAAC;QACD,OAAO,CAAC,SAAS,GAAG,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjE,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YACrD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;YAC/B,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClF,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8FAA8F;IAC9F,gBAAgB,CAAC,QAA+B;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACvD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;YAC/B,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QAClF,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,2GAA2G;IAC3G,eAAe,CAAC,EAAU;QACxB,MAAM,OAAO,GAAsB,EAAE,CAAC;QACtC,KAAK,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjD,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE;gBAAE,SAAS;YAC7F,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAChC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,cAAc;QACZ,MAAM,QAAQ,GAAmD,EAAE,CAAC;QACpE,KAAK,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACjD,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBAChC,IAAI,CAAC,OAAO,CAAC,SAAS;oBAAE,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC;gBAC7C,SAAS;YACX,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,IAAI;gBAAE,SAAS;YAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAChC,MAAM,KAAK,GAAG,IAAI,aAAa,CAC7B,iCAAiC,OAAO,CAAC,OAAO,CAAC,OAAO,iBAAiB,EACzE,EAAE,IAAI,EAAE,eAAe,EAAE,CAC1B,CAAC;YACF,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,0CAA0C;IAC1C,SAAS,CAAC,KAAoB;QAC5B,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,KAAK,MAAM,OAAO,IAAI,GAAG;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,oBAAoB;QAClB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;YAAE,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU;gBAAE,OAAO,IAAI,CAAC;QAC3F,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM;QACJ,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,MAAM,GAAkB,IAAI,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU;gBAAE,SAAS,EAAE,CAAC;;gBACxC,OAAO,EAAE,CAAC;YACf,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,SAAS,GAAG,MAAM;gBAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;QAChF,CAAC;QACD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;IACzD,CAAC;IAED,SAAS,CAAC,SAAoB;QAC5B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IACvD,CAAC;CACF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File uploads to a RxFerrix server.
|
|
3
|
+
*
|
|
4
|
+
* The flow: a mutation returns `ctx.storage.generate_upload_url()`, the client `POST`s the file to it
|
|
5
|
+
* with {@link uploadFile}, and a second mutation stores the returned `Id<"_storage">` in a document. Serve it with
|
|
6
|
+
* the URL from `ctx.storage.get_url(id)` (`/api/storage/<id>`).
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const url = await client.mutation(api.files.generateUploadUrl, {});
|
|
10
|
+
* const storageId = await uploadFile(url, file, { onProgress: ({ loaded, total }) => (pct = loaded / total) });
|
|
11
|
+
* await client.mutation(api.files.attach, { storageId });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
import type { Id } from "./api.js";
|
|
15
|
+
export interface UploadProgress {
|
|
16
|
+
/** Bytes sent so far. */
|
|
17
|
+
loaded: number;
|
|
18
|
+
/** Total bytes to send. */
|
|
19
|
+
total: number;
|
|
20
|
+
}
|
|
21
|
+
export interface UploadFileOptions {
|
|
22
|
+
/** Called as the upload progresses (with `XMLHttpRequest`; with `fetch`, only at the start and the end). */
|
|
23
|
+
onProgress?: (progress: UploadProgress) => void;
|
|
24
|
+
/** Aborts the upload; the promise rejects with an `AbortError`. */
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
/** The `Content-Type` to store. Default: the Blob's `type` (none if empty). */
|
|
27
|
+
contentType?: string;
|
|
28
|
+
/** The original file name to store. Default: `File.name` for a `File`; `null` sends none. */
|
|
29
|
+
filename?: string | null;
|
|
30
|
+
/** Base for a root-relative upload URL (`/api/storage/upload?…`). Default: the page's `location`. */
|
|
31
|
+
baseUrl?: string;
|
|
32
|
+
/** `fetch` to use when `XMLHttpRequest` is unavailable (or `transport: "fetch"`). Default: `globalThis.fetch`. */
|
|
33
|
+
fetch?: typeof globalThis.fetch;
|
|
34
|
+
/** Force a transport. Default: `XMLHttpRequest` when available (browsers, for progress events), else `fetch`. */
|
|
35
|
+
transport?: "xhr" | "fetch";
|
|
36
|
+
}
|
|
37
|
+
/** The final URL: `uploadUrl` resolved against `baseUrl`, with `filename` appended. */
|
|
38
|
+
export declare function uploadRequestUrl(uploadUrl: string, filename: string | null | undefined, baseUrl?: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Upload `file` to an upload URL from `ctx.storage.generate_upload_url()` and return the new file's
|
|
41
|
+
* `Id<"_storage">`. Rejects with a {@link RxFerrixError} whose `code` is the server's (`file_too_large`,
|
|
42
|
+
* `upload_url_expired`, `upload_url_used`, `invalid_upload_url`, `rate_limited`, ...), or `network_error`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function uploadFile(uploadUrl: string, file: Blob, options?: UploadFileOptions): Promise<Id<"_storage">>;
|
|
45
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAInC,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,4GAA4G;IAC5G,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD,mEAAmE;IACnE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,qGAAqG;IACrG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kHAAkH;IAClH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,iHAAiH;IACjH,SAAS,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;CAC7B;AAOD,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAYjH;AAyBD;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAYxH"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { RxFerrixError } from "./errors.js";
|
|
2
|
+
/** The final URL: `uploadUrl` resolved against `baseUrl`, with `filename` appended. */
|
|
3
|
+
export function uploadRequestUrl(uploadUrl, filename, baseUrl) {
|
|
4
|
+
const base = baseUrl ?? (typeof location !== "undefined" ? location.href : undefined);
|
|
5
|
+
let url;
|
|
6
|
+
try {
|
|
7
|
+
url = base === undefined ? new URL(uploadUrl) : new URL(uploadUrl, base);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
throw new RxFerrixError(`Invalid upload URL ${JSON.stringify(uploadUrl)} (pass options.baseUrl for relative URLs)`, {
|
|
11
|
+
code: "invalid_upload_url",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
if (filename)
|
|
15
|
+
url.searchParams.set("filename", filename);
|
|
16
|
+
return url.toString();
|
|
17
|
+
}
|
|
18
|
+
function parseResult(status, text) {
|
|
19
|
+
let body = null;
|
|
20
|
+
try {
|
|
21
|
+
body = text ? JSON.parse(text) : null;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Not JSON (e.g. a proxy's error page).
|
|
25
|
+
}
|
|
26
|
+
if (status >= 200 && status < 300) {
|
|
27
|
+
const id = body?.storageId;
|
|
28
|
+
if (typeof id !== "string")
|
|
29
|
+
throw new RxFerrixError(`Unexpected upload response: ${text.slice(0, 200)}`);
|
|
30
|
+
return id;
|
|
31
|
+
}
|
|
32
|
+
const err = body;
|
|
33
|
+
if (err && typeof err === "object" && (err.errorMessage || err.errorData)) {
|
|
34
|
+
throw RxFerrixError.fromServer(err.errorMessage, err.errorData ?? null);
|
|
35
|
+
}
|
|
36
|
+
throw new RxFerrixError(`Upload failed with HTTP ${status}`, { code: status === 413 ? "file_too_large" : "http_error" });
|
|
37
|
+
}
|
|
38
|
+
function abortError(signal) {
|
|
39
|
+
return signal?.reason ?? new DOMException("The upload was aborted", "AbortError");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Upload `file` to an upload URL from `ctx.storage.generate_upload_url()` and return the new file's
|
|
43
|
+
* `Id<"_storage">`. Rejects with a {@link RxFerrixError} whose `code` is the server's (`file_too_large`,
|
|
44
|
+
* `upload_url_expired`, `upload_url_used`, `invalid_upload_url`, `rate_limited`, ...), or `network_error`.
|
|
45
|
+
*/
|
|
46
|
+
export async function uploadFile(uploadUrl, file, options = {}) {
|
|
47
|
+
const filename = options.filename !== undefined
|
|
48
|
+
? options.filename
|
|
49
|
+
: typeof File !== "undefined" && file instanceof File && file.name
|
|
50
|
+
? file.name
|
|
51
|
+
: null;
|
|
52
|
+
const url = uploadRequestUrl(uploadUrl, filename, options.baseUrl);
|
|
53
|
+
const contentType = options.contentType ?? (file.type || undefined);
|
|
54
|
+
if (options.signal?.aborted)
|
|
55
|
+
throw abortError(options.signal);
|
|
56
|
+
const useXhr = options.transport ? options.transport === "xhr" : typeof XMLHttpRequest === "function";
|
|
57
|
+
return useXhr ? uploadWithXhr(url, file, contentType, options) : uploadWithFetch(url, file, contentType, options);
|
|
58
|
+
}
|
|
59
|
+
function uploadWithXhr(url, file, contentType, options) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const xhr = new XMLHttpRequest();
|
|
62
|
+
const { signal, onProgress } = options;
|
|
63
|
+
const onAbort = () => xhr.abort();
|
|
64
|
+
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
65
|
+
xhr.open("POST", url);
|
|
66
|
+
if (contentType)
|
|
67
|
+
xhr.setRequestHeader("Content-Type", contentType);
|
|
68
|
+
if (onProgress) {
|
|
69
|
+
xhr.upload.onprogress = (e) => {
|
|
70
|
+
onProgress({ loaded: e.loaded, total: e.lengthComputable ? e.total : file.size });
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
xhr.onload = () => {
|
|
74
|
+
cleanup();
|
|
75
|
+
try {
|
|
76
|
+
const id = parseResult(xhr.status, xhr.responseText);
|
|
77
|
+
onProgress?.({ loaded: file.size, total: file.size });
|
|
78
|
+
resolve(id);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
reject(e);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
xhr.onerror = () => {
|
|
85
|
+
cleanup();
|
|
86
|
+
reject(new RxFerrixError("The upload failed: network error", { code: "network_error" }));
|
|
87
|
+
};
|
|
88
|
+
xhr.onabort = () => {
|
|
89
|
+
cleanup();
|
|
90
|
+
reject(abortError(signal));
|
|
91
|
+
};
|
|
92
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
93
|
+
xhr.send(file);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async function uploadWithFetch(url, file, contentType, options) {
|
|
97
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
98
|
+
options.onProgress?.({ loaded: 0, total: file.size });
|
|
99
|
+
let res;
|
|
100
|
+
try {
|
|
101
|
+
res = await fetchFn(url, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
body: file,
|
|
104
|
+
headers: contentType ? { "Content-Type": contentType } : {},
|
|
105
|
+
signal: options.signal,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
if (options.signal?.aborted)
|
|
110
|
+
throw abortError(options.signal);
|
|
111
|
+
throw new RxFerrixError(`The upload failed: ${e instanceof Error ? e.message : String(e)}`, {
|
|
112
|
+
code: "network_error",
|
|
113
|
+
cause: e,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const id = parseResult(res.status, await res.text());
|
|
117
|
+
options.onProgress?.({ loaded: file.size, total: file.size });
|
|
118
|
+
return id;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test utilities: a scriptable fake of the RxFerrix sync endpoint (`@rxferrix/client/testing`).
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* const server = new FakeServer();
|
|
6
|
+
* const client = new RxFerrixClient("http://localhost", { webSocketConstructor: server.WebSocket });
|
|
7
|
+
* server.open(); // the socket connects
|
|
8
|
+
* await flushMicrotasks(); // batched ModifyQuerySet goes out
|
|
9
|
+
* server.sent; // what the client sent on this socket
|
|
10
|
+
* server.transition({ querySet: 1, ts: 1 }, [{ type: "QueryUpdated", queryId: 0, value: [] }]);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import type { ClientMessage, ServerMessage, StateVersion } from "./protocol.js";
|
|
14
|
+
type Modification = Extract<ServerMessage, {
|
|
15
|
+
type: "Transition";
|
|
16
|
+
}>["modifications"][number];
|
|
17
|
+
export declare class FakeSocket {
|
|
18
|
+
readonly url: string;
|
|
19
|
+
static readonly CONNECTING = 0;
|
|
20
|
+
static readonly OPEN = 1;
|
|
21
|
+
static readonly CLOSING = 2;
|
|
22
|
+
static readonly CLOSED = 3;
|
|
23
|
+
readyState: number;
|
|
24
|
+
readonly sent: ClientMessage[];
|
|
25
|
+
closedByClient: boolean;
|
|
26
|
+
/** The server-side StateVersion of this socket, advanced by `transition`. */
|
|
27
|
+
version: StateVersion;
|
|
28
|
+
onopen: ((ev: unknown) => void) | null;
|
|
29
|
+
onmessage: ((ev: {
|
|
30
|
+
data: string;
|
|
31
|
+
}) => void) | null;
|
|
32
|
+
onclose: ((ev: {
|
|
33
|
+
code: number;
|
|
34
|
+
reason: string;
|
|
35
|
+
}) => void) | null;
|
|
36
|
+
onerror: ((ev: unknown) => void) | null;
|
|
37
|
+
constructor(url: string);
|
|
38
|
+
send(data: string): void;
|
|
39
|
+
close(): void;
|
|
40
|
+
}
|
|
41
|
+
export declare class FakeServer {
|
|
42
|
+
readonly sockets: FakeSocket[];
|
|
43
|
+
readonly WebSocket: typeof WebSocket;
|
|
44
|
+
constructor();
|
|
45
|
+
/** The most recently created socket. */
|
|
46
|
+
get socket(): FakeSocket;
|
|
47
|
+
/** Everything the client sent on the current socket. */
|
|
48
|
+
get sent(): ClientMessage[];
|
|
49
|
+
/** Messages of one type sent on the current socket. */
|
|
50
|
+
sentOfType<T extends ClientMessage["type"]>(type: T): Extract<ClientMessage, {
|
|
51
|
+
type: T;
|
|
52
|
+
}>[];
|
|
53
|
+
/** Returns and forgets what the client sent on the current socket so far. */
|
|
54
|
+
drain(): ClientMessage[];
|
|
55
|
+
open(): void;
|
|
56
|
+
push(message: ServerMessage | Record<string, unknown>): void;
|
|
57
|
+
/**
|
|
58
|
+
* Pushes a Transition from the socket's current version. `end` overrides fields of the end version; unspecified
|
|
59
|
+
* fields keep their current value.
|
|
60
|
+
*/
|
|
61
|
+
transition(end: Partial<StateVersion>, modifications?: Modification[]): void;
|
|
62
|
+
/** The server closes the current socket. */
|
|
63
|
+
close(code?: number, reason?: string): void;
|
|
64
|
+
}
|
|
65
|
+
/** Lets queued microtasks (query-set flush, promise continuations) run. */
|
|
66
|
+
export declare function flushMicrotasks(): Promise<void>;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEhF,KAAK,YAAY,GAAG,OAAO,CAAC,aAAa,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5F,qBAAa,UAAU;IAiBT,QAAQ,CAAC,GAAG,EAAE,MAAM;IAhBhC,MAAM,CAAC,QAAQ,CAAC,UAAU,KAAK;IAC/B,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK;IACzB,MAAM,CAAC,QAAQ,CAAC,OAAO,KAAK;IAC5B,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK;IAE3B,UAAU,SAAyB;IACnC,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,CAAM;IACpC,cAAc,UAAS;IACvB,6EAA6E;IAC7E,OAAO,EAAE,YAAY,CAAuC;IAE5D,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC9C,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC1D,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IACxE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;gBAE1B,GAAG,EAAE,MAAM;IAEhC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,KAAK,IAAI,IAAI;CAKd;AAED,qBAAa,UAAU;IACrB,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,CAAM;IACpC,QAAQ,CAAC,SAAS,EAAE,OAAO,SAAS,CAAC;;IAarC,wCAAwC;IACxC,IAAI,MAAM,IAAI,UAAU,CAIvB;IAED,wDAAwD;IACxD,IAAI,IAAI,IAAI,aAAa,EAAE,CAE1B;IAED,uDAAuD;IACvD,UAAU,CAAC,CAAC,SAAS,aAAa,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,aAAa,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,EAAE;IAI3F,6EAA6E;IAC7E,KAAK,IAAI,aAAa,EAAE;IAIxB,IAAI,IAAI,IAAI;IAMZ,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAM5D;;;OAGG;IACH,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,aAAa,GAAE,YAAY,EAAO,GAAG,IAAI;IAQhF,4CAA4C;IAC5C,KAAK,CAAC,IAAI,SAAO,EAAE,MAAM,SAAkB,GAAG,IAAI;CAKnD;AAED,2EAA2E;AAC3E,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAErD"}
|