@koolbase/react-native 9.1.0 → 9.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/README.md +125 -9
- package/dist/auth-errors.d.ts +2 -2
- package/dist/auth-errors.js +3 -3
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +16 -0
- package/dist/cache-store.d.ts +42 -3
- package/dist/cache-store.js +62 -1
- package/dist/conflict.d.ts +80 -0
- package/dist/conflict.js +84 -0
- package/dist/database-errors.d.ts +9 -3
- package/dist/database-errors.js +42 -15
- package/dist/database.d.ts +91 -1
- package/dist/database.js +438 -94
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +85 -0
- package/dist/function-errors.d.ts +51 -0
- package/dist/function-errors.js +103 -0
- package/dist/functions.d.ts +8 -1
- package/dist/functions.js +18 -5
- package/dist/index.d.ts +4 -0
- package/dist/index.js +15 -4
- package/dist/offline-state.d.ts +97 -0
- package/dist/offline-state.js +200 -0
- package/dist/pending-write.d.ts +47 -0
- package/dist/pending-write.js +22 -0
- package/dist/realtime.d.ts +26 -1
- package/dist/realtime.js +50 -3
- package/dist/record.js +3 -0
- package/dist/storage-errors.d.ts +2 -2
- package/dist/storage-errors.js +7 -3
- package/dist/storage.d.ts +15 -1
- package/dist/storage.js +23 -10
- package/dist/sync-engine.d.ts +15 -1
- package/dist/sync-engine.js +226 -22
- package/dist/types.d.ts +18 -1
- package/package.json +13 -7
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The root of every error the SDK raises.
|
|
3
|
+
*
|
|
4
|
+
* Each subsystem has its own family beneath this — data, storage, auth — so an
|
|
5
|
+
* application can catch narrowly where it wants to and broadly where it does
|
|
6
|
+
* not:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* try {
|
|
10
|
+
* await Koolbase.storage.upload(...);
|
|
11
|
+
* } catch (e) {
|
|
12
|
+
* if (e instanceof KoolbaseUnauthenticatedError) return goToLogin();
|
|
13
|
+
* if (e instanceof KoolbaseStorageError) return showError(e.message);
|
|
14
|
+
* throw e;
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* The families used to be unrelated roots, which meant a failure belonging to no
|
|
19
|
+
* single subsystem — a rejected credential, discovered by whichever call
|
|
20
|
+
* happened to make it — had to be redefined in each one.
|
|
21
|
+
*/
|
|
22
|
+
export declare class KoolbaseError extends Error {
|
|
23
|
+
code?: string;
|
|
24
|
+
constructor(message: string, code?: string);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The server would not accept the caller's credentials.
|
|
28
|
+
*
|
|
29
|
+
* Raised by any surface — a query, an upload, a Function invoke — because a
|
|
30
|
+
* session stops working for the whole SDK at once, not one subsystem at a time.
|
|
31
|
+
*
|
|
32
|
+
* Named for what the server actually reports. A 401 covers an expired session, a
|
|
33
|
+
* revoked key, a malformed header, and no credentials at all, and the server
|
|
34
|
+
* does not distinguish them: calling this "session expired" would claim a
|
|
35
|
+
* precision that does not exist, and an app that signed a user out on a revoked
|
|
36
|
+
* API key would be acting on it.
|
|
37
|
+
*
|
|
38
|
+
* When the SDK holds a session it clears it before throwing, so by the time an
|
|
39
|
+
* application catches this the user is already signed out.
|
|
40
|
+
*/
|
|
41
|
+
export declare class KoolbaseUnauthenticatedError extends KoolbaseError {
|
|
42
|
+
constructor(message: string);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* An offline update or delete could not be queued, because the SDK has no
|
|
46
|
+
* record of what the change was composed against.
|
|
47
|
+
*
|
|
48
|
+
* Replaying a mutation without knowing the state it was based on means applying
|
|
49
|
+
* it blindly: whatever changed on the server in the meantime is overwritten,
|
|
50
|
+
* silently, with nobody able to tell it happened.
|
|
51
|
+
*
|
|
52
|
+
* A baseline is available when the record is in the local cache — read through
|
|
53
|
+
* a query, a single fetch, or seen over the socket — or when it was created
|
|
54
|
+
* offline and its insert is still queued. It is unavailable when the record has
|
|
55
|
+
* never been seen on this device, so read it first, or make the change while
|
|
56
|
+
* online where the server arbitrates directly.
|
|
57
|
+
*
|
|
58
|
+
* Deliberate rather than lenient. Queueing these anyway would mean most offline
|
|
59
|
+
* updates are conflict-safe and some quietly are not, which is a worse guarantee
|
|
60
|
+
* than a clear refusal.
|
|
61
|
+
*/
|
|
62
|
+
export declare class KoolbaseOfflineBaselineUnavailableError extends KoolbaseError {
|
|
63
|
+
constructor(message: string);
|
|
64
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KoolbaseOfflineBaselineUnavailableError = exports.KoolbaseUnauthenticatedError = exports.KoolbaseError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The root of every error the SDK raises.
|
|
6
|
+
*
|
|
7
|
+
* Each subsystem has its own family beneath this — data, storage, auth — so an
|
|
8
|
+
* application can catch narrowly where it wants to and broadly where it does
|
|
9
|
+
* not:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* try {
|
|
13
|
+
* await Koolbase.storage.upload(...);
|
|
14
|
+
* } catch (e) {
|
|
15
|
+
* if (e instanceof KoolbaseUnauthenticatedError) return goToLogin();
|
|
16
|
+
* if (e instanceof KoolbaseStorageError) return showError(e.message);
|
|
17
|
+
* throw e;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The families used to be unrelated roots, which meant a failure belonging to no
|
|
22
|
+
* single subsystem — a rejected credential, discovered by whichever call
|
|
23
|
+
* happened to make it — had to be redefined in each one.
|
|
24
|
+
*/
|
|
25
|
+
class KoolbaseError extends Error {
|
|
26
|
+
constructor(message, code) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.name = 'KoolbaseError';
|
|
30
|
+
// Required for `instanceof` to work across the prototype chain when
|
|
31
|
+
// targeting ES5-era output, which TypeScript's class extension otherwise
|
|
32
|
+
// breaks. Every subclass repeats it for the same reason.
|
|
33
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.KoolbaseError = KoolbaseError;
|
|
37
|
+
/**
|
|
38
|
+
* The server would not accept the caller's credentials.
|
|
39
|
+
*
|
|
40
|
+
* Raised by any surface — a query, an upload, a Function invoke — because a
|
|
41
|
+
* session stops working for the whole SDK at once, not one subsystem at a time.
|
|
42
|
+
*
|
|
43
|
+
* Named for what the server actually reports. A 401 covers an expired session, a
|
|
44
|
+
* revoked key, a malformed header, and no credentials at all, and the server
|
|
45
|
+
* does not distinguish them: calling this "session expired" would claim a
|
|
46
|
+
* precision that does not exist, and an app that signed a user out on a revoked
|
|
47
|
+
* API key would be acting on it.
|
|
48
|
+
*
|
|
49
|
+
* When the SDK holds a session it clears it before throwing, so by the time an
|
|
50
|
+
* application catches this the user is already signed out.
|
|
51
|
+
*/
|
|
52
|
+
class KoolbaseUnauthenticatedError extends KoolbaseError {
|
|
53
|
+
constructor(message) {
|
|
54
|
+
super(message, 'unauthenticated');
|
|
55
|
+
this.name = 'KoolbaseUnauthenticatedError';
|
|
56
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.KoolbaseUnauthenticatedError = KoolbaseUnauthenticatedError;
|
|
60
|
+
/**
|
|
61
|
+
* An offline update or delete could not be queued, because the SDK has no
|
|
62
|
+
* record of what the change was composed against.
|
|
63
|
+
*
|
|
64
|
+
* Replaying a mutation without knowing the state it was based on means applying
|
|
65
|
+
* it blindly: whatever changed on the server in the meantime is overwritten,
|
|
66
|
+
* silently, with nobody able to tell it happened.
|
|
67
|
+
*
|
|
68
|
+
* A baseline is available when the record is in the local cache — read through
|
|
69
|
+
* a query, a single fetch, or seen over the socket — or when it was created
|
|
70
|
+
* offline and its insert is still queued. It is unavailable when the record has
|
|
71
|
+
* never been seen on this device, so read it first, or make the change while
|
|
72
|
+
* online where the server arbitrates directly.
|
|
73
|
+
*
|
|
74
|
+
* Deliberate rather than lenient. Queueing these anyway would mean most offline
|
|
75
|
+
* updates are conflict-safe and some quietly are not, which is a worse guarantee
|
|
76
|
+
* than a clear refusal.
|
|
77
|
+
*/
|
|
78
|
+
class KoolbaseOfflineBaselineUnavailableError extends KoolbaseError {
|
|
79
|
+
constructor(message) {
|
|
80
|
+
super(message, 'offline_baseline_unavailable');
|
|
81
|
+
this.name = 'KoolbaseOfflineBaselineUnavailableError';
|
|
82
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.KoolbaseOfflineBaselineUnavailableError = KoolbaseOfflineBaselineUnavailableError;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { KoolbaseError } from './errors';
|
|
2
|
+
/**
|
|
3
|
+
* A Function call did not succeed.
|
|
4
|
+
*
|
|
5
|
+
* Every failure used to be a bare `Error`, so an application could only match on
|
|
6
|
+
* message text — and a missing Function, a caller without permission, a Function
|
|
7
|
+
* that threw, and an exhausted plan limit all looked alike, though they call for
|
|
8
|
+
* entirely different responses.
|
|
9
|
+
*/
|
|
10
|
+
export declare class FunctionInvokeError extends KoolbaseError {
|
|
11
|
+
statusCode?: number;
|
|
12
|
+
constructor(message: string, statusCode?: number, code?: string);
|
|
13
|
+
}
|
|
14
|
+
/** No Function by that name is deployed to this project. */
|
|
15
|
+
export declare class FunctionNotFoundError extends FunctionInvokeError {
|
|
16
|
+
constructor(message: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The caller may not invoke this Function.
|
|
20
|
+
*
|
|
21
|
+
* Distinct from an authentication failure: the credentials were accepted and
|
|
22
|
+
* this caller is not permitted. Retrying will not help, and signing the user out
|
|
23
|
+
* would be wrong.
|
|
24
|
+
*/
|
|
25
|
+
export declare class FunctionPermissionError extends FunctionInvokeError {
|
|
26
|
+
constructor(message: string);
|
|
27
|
+
}
|
|
28
|
+
/** The Function rejected its arguments. */
|
|
29
|
+
export declare class FunctionValidationError extends FunctionInvokeError {
|
|
30
|
+
constructor(message: string);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The project's Function invocations are used up.
|
|
34
|
+
*
|
|
35
|
+
* Nothing about the call is wrong — retrying will not help until the plan
|
|
36
|
+
* allows it. The one failure here fixed by changing a plan rather than code.
|
|
37
|
+
*/
|
|
38
|
+
export declare class FunctionQuotaExceededError extends FunctionInvokeError {
|
|
39
|
+
constructor(message: string);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The Function ran and threw.
|
|
43
|
+
*
|
|
44
|
+
* The message is the Function's own, not the platform's — it comes from the code
|
|
45
|
+
* that was deployed.
|
|
46
|
+
*/
|
|
47
|
+
export declare class FunctionExecutionError extends FunctionInvokeError {
|
|
48
|
+
constructor(message: string, statusCode?: number);
|
|
49
|
+
}
|
|
50
|
+
/** Builds the right error for a failed invocation. */
|
|
51
|
+
export declare function functionInvokeError(status: number, message: string): KoolbaseError;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FunctionExecutionError = exports.FunctionQuotaExceededError = exports.FunctionValidationError = exports.FunctionPermissionError = exports.FunctionNotFoundError = exports.FunctionInvokeError = void 0;
|
|
4
|
+
exports.functionInvokeError = functionInvokeError;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
/**
|
|
7
|
+
* A Function call did not succeed.
|
|
8
|
+
*
|
|
9
|
+
* Every failure used to be a bare `Error`, so an application could only match on
|
|
10
|
+
* message text — and a missing Function, a caller without permission, a Function
|
|
11
|
+
* that threw, and an exhausted plan limit all looked alike, though they call for
|
|
12
|
+
* entirely different responses.
|
|
13
|
+
*/
|
|
14
|
+
class FunctionInvokeError extends errors_1.KoolbaseError {
|
|
15
|
+
constructor(message, statusCode, code) {
|
|
16
|
+
super(message, code);
|
|
17
|
+
this.statusCode = statusCode;
|
|
18
|
+
this.name = 'FunctionInvokeError';
|
|
19
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.FunctionInvokeError = FunctionInvokeError;
|
|
23
|
+
/** No Function by that name is deployed to this project. */
|
|
24
|
+
class FunctionNotFoundError extends FunctionInvokeError {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message, 404, 'not_found');
|
|
27
|
+
this.name = 'FunctionNotFoundError';
|
|
28
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.FunctionNotFoundError = FunctionNotFoundError;
|
|
32
|
+
/**
|
|
33
|
+
* The caller may not invoke this Function.
|
|
34
|
+
*
|
|
35
|
+
* Distinct from an authentication failure: the credentials were accepted and
|
|
36
|
+
* this caller is not permitted. Retrying will not help, and signing the user out
|
|
37
|
+
* would be wrong.
|
|
38
|
+
*/
|
|
39
|
+
class FunctionPermissionError extends FunctionInvokeError {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message, 403, 'permission_denied');
|
|
42
|
+
this.name = 'FunctionPermissionError';
|
|
43
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.FunctionPermissionError = FunctionPermissionError;
|
|
47
|
+
/** The Function rejected its arguments. */
|
|
48
|
+
class FunctionValidationError extends FunctionInvokeError {
|
|
49
|
+
constructor(message) {
|
|
50
|
+
super(message, 400, 'validation_error');
|
|
51
|
+
this.name = 'FunctionValidationError';
|
|
52
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.FunctionValidationError = FunctionValidationError;
|
|
56
|
+
/**
|
|
57
|
+
* The project's Function invocations are used up.
|
|
58
|
+
*
|
|
59
|
+
* Nothing about the call is wrong — retrying will not help until the plan
|
|
60
|
+
* allows it. The one failure here fixed by changing a plan rather than code.
|
|
61
|
+
*/
|
|
62
|
+
class FunctionQuotaExceededError extends FunctionInvokeError {
|
|
63
|
+
constructor(message) {
|
|
64
|
+
super(message, 402, 'limit_reached');
|
|
65
|
+
this.name = 'FunctionQuotaExceededError';
|
|
66
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.FunctionQuotaExceededError = FunctionQuotaExceededError;
|
|
70
|
+
/**
|
|
71
|
+
* The Function ran and threw.
|
|
72
|
+
*
|
|
73
|
+
* The message is the Function's own, not the platform's — it comes from the code
|
|
74
|
+
* that was deployed.
|
|
75
|
+
*/
|
|
76
|
+
class FunctionExecutionError extends FunctionInvokeError {
|
|
77
|
+
constructor(message, statusCode) {
|
|
78
|
+
super(message, statusCode, 'execution_failed');
|
|
79
|
+
this.name = 'FunctionExecutionError';
|
|
80
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.FunctionExecutionError = FunctionExecutionError;
|
|
84
|
+
/** Builds the right error for a failed invocation. */
|
|
85
|
+
function functionInvokeError(status, message) {
|
|
86
|
+
switch (status) {
|
|
87
|
+
case 401:
|
|
88
|
+
// Not a Function failure. A rejected credential stops the whole SDK
|
|
89
|
+
// working, so it raises the shared type.
|
|
90
|
+
return new errors_1.KoolbaseUnauthenticatedError(message);
|
|
91
|
+
case 403:
|
|
92
|
+
return new FunctionPermissionError(message);
|
|
93
|
+
case 404:
|
|
94
|
+
return new FunctionNotFoundError(message);
|
|
95
|
+
case 400:
|
|
96
|
+
return new FunctionValidationError(message);
|
|
97
|
+
case 402:
|
|
98
|
+
return new FunctionQuotaExceededError(message);
|
|
99
|
+
}
|
|
100
|
+
if (status >= 500)
|
|
101
|
+
return new FunctionExecutionError(message, status);
|
|
102
|
+
return new FunctionInvokeError(message, status);
|
|
103
|
+
}
|
package/dist/functions.d.ts
CHANGED
|
@@ -2,7 +2,14 @@ import { KoolbaseConfig, FunctionInvokeResult, DeployOptions, DeployResult } fro
|
|
|
2
2
|
export declare class KoolbaseFunctions {
|
|
3
3
|
private config;
|
|
4
4
|
private getUserAccessToken?;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Called when the server rejects the caller's credentials.
|
|
7
|
+
*
|
|
8
|
+
* A session stops working for the whole SDK at once, so an app whose failing
|
|
9
|
+
* call happens to be a Function invoke must not keep believing it is signed in.
|
|
10
|
+
*/
|
|
11
|
+
private onSessionExpired?;
|
|
12
|
+
constructor(config: KoolbaseConfig, getUserAccessToken?: () => Promise<string | null>, onSessionExpired?: () => Promise<void>);
|
|
6
13
|
deploy(options: DeployOptions): Promise<DeployResult>;
|
|
7
14
|
invoke(name: string, body?: Record<string, unknown>): Promise<FunctionInvokeResult>;
|
|
8
15
|
}
|
package/dist/functions.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.KoolbaseFunctions = void 0;
|
|
4
|
+
const function_errors_1 = require("./function-errors");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
4
6
|
const types_1 = require("./types");
|
|
5
7
|
class KoolbaseFunctions {
|
|
6
|
-
constructor(config, getUserAccessToken) {
|
|
8
|
+
constructor(config, getUserAccessToken, onSessionExpired) {
|
|
7
9
|
this.config = config;
|
|
10
|
+
this.onSessionExpired = onSessionExpired;
|
|
8
11
|
this.getUserAccessToken = getUserAccessToken;
|
|
9
12
|
}
|
|
10
13
|
// ─── Deploy ────────────────────────────────────────────────────────────────
|
|
@@ -25,8 +28,13 @@ class KoolbaseFunctions {
|
|
|
25
28
|
});
|
|
26
29
|
const data = await res.json().catch(() => null);
|
|
27
30
|
if (!res.ok) {
|
|
28
|
-
|
|
29
|
-
'Function deploy failed'
|
|
31
|
+
const message = data?.error ??
|
|
32
|
+
'Function deploy failed';
|
|
33
|
+
const err = (0, function_errors_1.functionInvokeError)(res.status, message);
|
|
34
|
+
if (err instanceof errors_1.KoolbaseUnauthenticatedError) {
|
|
35
|
+
await this.onSessionExpired?.();
|
|
36
|
+
}
|
|
37
|
+
throw err;
|
|
30
38
|
}
|
|
31
39
|
const d = data;
|
|
32
40
|
return {
|
|
@@ -57,8 +65,13 @@ class KoolbaseFunctions {
|
|
|
57
65
|
const data = await res.json().catch(() => null);
|
|
58
66
|
const success = res.status >= 200 && res.status < 300;
|
|
59
67
|
if (!success) {
|
|
60
|
-
|
|
61
|
-
'Function invocation failed'
|
|
68
|
+
const message = data?.error ??
|
|
69
|
+
'Function invocation failed';
|
|
70
|
+
const err = (0, function_errors_1.functionInvokeError)(res.status, message);
|
|
71
|
+
if (err instanceof errors_1.KoolbaseUnauthenticatedError) {
|
|
72
|
+
await this.onSessionExpired?.();
|
|
73
|
+
}
|
|
74
|
+
throw err;
|
|
62
75
|
}
|
|
63
76
|
return {
|
|
64
77
|
statusCode: res.status,
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ import { KoolbaseRealtime } from './realtime';
|
|
|
17
17
|
import { KoolbaseStorage } from './storage';
|
|
18
18
|
import { KoolbaseConfig, VersionCheckResult } from './types';
|
|
19
19
|
export * from './types';
|
|
20
|
+
export * from './errors';
|
|
21
|
+
export * from './conflict';
|
|
22
|
+
export * from './pending-write';
|
|
23
|
+
export * from './function-errors';
|
|
20
24
|
export * from './auth-errors';
|
|
21
25
|
export * from './database-errors';
|
|
22
26
|
export * from './storage-errors';
|
package/dist/index.js
CHANGED
|
@@ -41,6 +41,13 @@ const storage_1 = require("./storage");
|
|
|
41
41
|
Object.defineProperty(exports, "KoolbaseStorage", { enumerable: true, get: function () { return storage_1.KoolbaseStorage; } });
|
|
42
42
|
const device_id_1 = require("./device-id");
|
|
43
43
|
__exportStar(require("./types"), exports);
|
|
44
|
+
// The root, and the authentication failure any surface can raise. Listed
|
|
45
|
+
// first: an application catching broadly needs these more than it needs any
|
|
46
|
+
// single subsystem's types.
|
|
47
|
+
__exportStar(require("./errors"), exports);
|
|
48
|
+
__exportStar(require("./conflict"), exports);
|
|
49
|
+
__exportStar(require("./pending-write"), exports);
|
|
50
|
+
__exportStar(require("./function-errors"), exports);
|
|
44
51
|
__exportStar(require("./auth-errors"), exports);
|
|
45
52
|
__exportStar(require("./database-errors"), exports);
|
|
46
53
|
__exportStar(require("./storage-errors"), exports);
|
|
@@ -65,10 +72,14 @@ exports.Koolbase = {
|
|
|
65
72
|
if (_initialized)
|
|
66
73
|
return;
|
|
67
74
|
_auth = new auth_1.KoolbaseAuth(config);
|
|
68
|
-
_db = new database_1.KoolbaseDatabase(config, () => _auth?.currentUser?.id ?? null, () => _auth?.validAccessToken() ?? Promise.resolve(null)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
_db = new database_1.KoolbaseDatabase(config, () => _auth?.currentUser?.id ?? null, () => _auth?.validAccessToken() ?? Promise.resolve(null),
|
|
76
|
+
// A session the server refuses is not a session. Clearing it here means an
|
|
77
|
+
// app catching KoolbaseUnauthenticatedError is already signed out and can
|
|
78
|
+
// route to login, rather than looping on a dead token.
|
|
79
|
+
async () => { await _auth?.clearStoredSession(); });
|
|
80
|
+
_storage = new storage_1.KoolbaseStorage(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
|
|
81
|
+
_realtime = new realtime_1.KoolbaseRealtime(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), () => _auth?.currentUser?.id ?? null);
|
|
82
|
+
_functions = new functions_1.KoolbaseFunctions(config, () => _auth?.validAccessToken() ?? Promise.resolve(null), async () => { await _auth?.clearStoredSession(); });
|
|
72
83
|
// One anonymous device id for the whole SDK — bucketing (flags), targeting
|
|
73
84
|
// (code push), and registration keying (messaging) must all agree on it.
|
|
74
85
|
const deviceId = await (0, device_id_1.getOrCreateDeviceId)();
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export declare function recordKey(userId: string, collection: string, recordId: string): string;
|
|
2
|
+
/** A write waiting to be sent. */
|
|
3
|
+
export interface QueuedWrite {
|
|
4
|
+
id: string;
|
|
5
|
+
operation: 'insert' | 'update' | 'delete';
|
|
6
|
+
collection: string;
|
|
7
|
+
recordId?: string;
|
|
8
|
+
data?: Record<string, unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* The record as the client last saw it, for update and delete.
|
|
11
|
+
*
|
|
12
|
+
* Copied in at enqueue time rather than looked up at replay: the record cache
|
|
13
|
+
* can be evicted or invalidated in between, and a write whose baseline
|
|
14
|
+
* depends on something else surviving is not durable. From here on the write
|
|
15
|
+
* is self-contained.
|
|
16
|
+
*/
|
|
17
|
+
baseline?: Record<string, unknown>;
|
|
18
|
+
/** The revision that baseline carried, sent so the server can refuse atomically. */
|
|
19
|
+
baseRevision?: number;
|
|
20
|
+
retries: number;
|
|
21
|
+
enqueuedAt: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Why a write is waiting for a decision.
|
|
25
|
+
*
|
|
26
|
+
* Kept distinct because they are different situations and an app showing them
|
|
27
|
+
* to someone should say different things. One means two people changed the same
|
|
28
|
+
* thing; the other means we never knew what the change was based on, so there is
|
|
29
|
+
* nothing to compare it against.
|
|
30
|
+
*/
|
|
31
|
+
export type ConflictReason =
|
|
32
|
+
/** The record moved between the change being made and the queue reaching it. */
|
|
33
|
+
'concurrent_modification'
|
|
34
|
+
/**
|
|
35
|
+
* Queued by a version of this SDK that did not record what the change was
|
|
36
|
+
* composed against. It cannot be replayed safely — there is nothing to check
|
|
37
|
+
* it against — so it waits rather than overwriting whatever is there now.
|
|
38
|
+
*/
|
|
39
|
+
| 'baseline_unavailable'
|
|
40
|
+
/**
|
|
41
|
+
* The server refused the write for a reason retrying cannot change — the data
|
|
42
|
+
* no longer satisfies the collection's rules, the record is gone, the caller
|
|
43
|
+
* is not permitted, a unique value is taken.
|
|
44
|
+
*
|
|
45
|
+
* Held rather than retried or dropped. Retrying sends identical bytes to
|
|
46
|
+
* identical rules; dropping loses a change the user believes is saved. Neither
|
|
47
|
+
* tells them anything.
|
|
48
|
+
*/
|
|
49
|
+
| 'rejected';
|
|
50
|
+
/** A write the server would not apply, held until someone decides. */
|
|
51
|
+
export interface QueuedConflict {
|
|
52
|
+
reason: ConflictReason;
|
|
53
|
+
id: string;
|
|
54
|
+
operation: 'insert' | 'update' | 'delete';
|
|
55
|
+
collection: string;
|
|
56
|
+
recordId: string;
|
|
57
|
+
local?: Record<string, unknown>;
|
|
58
|
+
baseline?: Record<string, unknown>;
|
|
59
|
+
server?: Record<string, unknown>;
|
|
60
|
+
baseRevision?: number;
|
|
61
|
+
serverRevision?: number;
|
|
62
|
+
/** What the server said, when it refused for a terminal reason. */
|
|
63
|
+
message?: string;
|
|
64
|
+
createdAt: string;
|
|
65
|
+
}
|
|
66
|
+
export interface OfflineState {
|
|
67
|
+
pending: QueuedWrite[];
|
|
68
|
+
conflicts: QueuedConflict[];
|
|
69
|
+
}
|
|
70
|
+
export declare class OfflineStateTooLargeError extends Error {
|
|
71
|
+
constructor(bytes: number);
|
|
72
|
+
}
|
|
73
|
+
export declare function readOfflineState(userId: string): Promise<OfflineState>;
|
|
74
|
+
/**
|
|
75
|
+
* Reads, mutates, and writes the state under the user's lock.
|
|
76
|
+
*
|
|
77
|
+
* Every mutation goes through here. A caller that reads and writes separately
|
|
78
|
+
* reintroduces the race this exists to prevent.
|
|
79
|
+
*/
|
|
80
|
+
export declare function mutateOfflineState(userId: string, mutate: (state: OfflineState) => void): Promise<void>;
|
|
81
|
+
/** Adds a write to the queue, under the user's lock. */
|
|
82
|
+
export declare function queueWrite(userId: string, write: Omit<QueuedWrite, 'retries' | 'enqueuedAt'>): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Moves writes queued by an earlier version into the current state.
|
|
85
|
+
*
|
|
86
|
+
* Runs once, before any replay, and never contacts the network. Migration that
|
|
87
|
+
* depended on connectivity would give the same input two different outcomes
|
|
88
|
+
* depending on whether the device happened to be online at startup — which is
|
|
89
|
+
* how a bug becomes unreproducible.
|
|
90
|
+
*
|
|
91
|
+
* Inserts carry everything they need and simply move across. Updates and
|
|
92
|
+
* deletes do not: they were queued before baselines were recorded, so replaying
|
|
93
|
+
* one would apply it blindly and overwrite whatever changed in the meantime.
|
|
94
|
+
* They are preserved as waiting for a decision instead — the change is not lost,
|
|
95
|
+
* and nothing is written on a guess.
|
|
96
|
+
*/
|
|
97
|
+
export declare function migrateLegacyQueue(userId: string): Promise<void>;
|