@spooky-sync/core 0.0.1-canary.220 → 0.0.1-canary.221
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/index.d.ts +14 -0
- package/dist/index.js +42 -6
- package/package.json +3 -3
- package/src/modules/sync/sync.ts +18 -7
- package/src/services/database/remote.ts +46 -0
package/dist/index.d.ts
CHANGED
|
@@ -79,6 +79,20 @@ declare class RemoteDatabaseService extends AbstractDatabaseService {
|
|
|
79
79
|
private authToken;
|
|
80
80
|
constructor(config: Sp00kyConfig<any>['database'], logger: Logger$1);
|
|
81
81
|
getConfig(): Sp00kyConfig<any>['database'];
|
|
82
|
+
/**
|
|
83
|
+
* Send one SurrealQL statement so that it survives the page going away.
|
|
84
|
+
*
|
|
85
|
+
* A WebSocket `send()` during `pagehide` is not guaranteed to flush — the
|
|
86
|
+
* browser may tear the socket down first, and the frame is simply lost.
|
|
87
|
+
* Measured: an unload-time release over the live socket reached the server
|
|
88
|
+
* zero times out of one. `fetch` with `keepalive` is the primitive the
|
|
89
|
+
* platform actually guarantees here, so this goes over SurrealDB's HTTP
|
|
90
|
+
* `/sql` endpoint instead of the RPC socket.
|
|
91
|
+
*
|
|
92
|
+
* Best-effort by design: no await, no retry, errors swallowed. Every caller
|
|
93
|
+
* must have a server-side fallback that makes a lost beacon a non-event.
|
|
94
|
+
*/
|
|
95
|
+
beaconSql(sql: string): void;
|
|
82
96
|
/**
|
|
83
97
|
* Record the token every future connect should authenticate with, or `null`
|
|
84
98
|
* on sign-out. See {@link authToken}.
|
package/dist/index.js
CHANGED
|
@@ -1044,6 +1044,42 @@ var RemoteDatabaseService = class extends AbstractDatabaseService {
|
|
|
1044
1044
|
return this.config;
|
|
1045
1045
|
}
|
|
1046
1046
|
/**
|
|
1047
|
+
* Send one SurrealQL statement so that it survives the page going away.
|
|
1048
|
+
*
|
|
1049
|
+
* A WebSocket `send()` during `pagehide` is not guaranteed to flush — the
|
|
1050
|
+
* browser may tear the socket down first, and the frame is simply lost.
|
|
1051
|
+
* Measured: an unload-time release over the live socket reached the server
|
|
1052
|
+
* zero times out of one. `fetch` with `keepalive` is the primitive the
|
|
1053
|
+
* platform actually guarantees here, so this goes over SurrealDB's HTTP
|
|
1054
|
+
* `/sql` endpoint instead of the RPC socket.
|
|
1055
|
+
*
|
|
1056
|
+
* Best-effort by design: no await, no retry, errors swallowed. Every caller
|
|
1057
|
+
* must have a server-side fallback that makes a lost beacon a non-event.
|
|
1058
|
+
*/
|
|
1059
|
+
beaconSql(sql) {
|
|
1060
|
+
try {
|
|
1061
|
+
const { endpoint, namespace, database } = this.getConfig();
|
|
1062
|
+
if (!endpoint || typeof fetch !== "function") return;
|
|
1063
|
+
const url = new URL(endpoint);
|
|
1064
|
+
url.protocol = url.protocol === "wss:" ? "https:" : url.protocol === "ws:" ? "http:" : url.protocol;
|
|
1065
|
+
url.pathname = url.pathname.replace(/\/rpc\/?$/, "") + "/sql";
|
|
1066
|
+
const headers = {
|
|
1067
|
+
Accept: "application/json",
|
|
1068
|
+
"Content-Type": "text/plain"
|
|
1069
|
+
};
|
|
1070
|
+
if (namespace) headers["surreal-ns"] = namespace;
|
|
1071
|
+
if (database) headers["surreal-db"] = database;
|
|
1072
|
+
if (this.authToken) headers.Authorization = `Bearer ${this.authToken}`;
|
|
1073
|
+
fetch(url.toString(), {
|
|
1074
|
+
method: "POST",
|
|
1075
|
+
headers,
|
|
1076
|
+
body: sql,
|
|
1077
|
+
keepalive: true,
|
|
1078
|
+
credentials: "omit"
|
|
1079
|
+
}).catch(() => {});
|
|
1080
|
+
} catch {}
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1047
1083
|
* Record the token every future connect should authenticate with, or `null`
|
|
1048
1084
|
* on sign-out. See {@link authToken}.
|
|
1049
1085
|
*
|
|
@@ -7246,9 +7282,9 @@ var Sp00kySync = class Sp00kySync {
|
|
|
7246
7282
|
return;
|
|
7247
7283
|
}
|
|
7248
7284
|
if (ids.length === 0) return;
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7285
|
+
const list = ids.map((id) => String(id)).filter((id) => /^_00_query:[0-9a-f]{64}$/.test(id)).join(", ");
|
|
7286
|
+
if (!list) return;
|
|
7287
|
+
this.remote.beaconSql(`FOR $id IN [${list}] { LET $_released = fn::query::unsubscribe($id); };`);
|
|
7252
7288
|
}
|
|
7253
7289
|
async registerQuery(queryHash) {
|
|
7254
7290
|
this.dataModule.beginFetching(queryHash);
|
|
@@ -7699,8 +7735,8 @@ function selfAllowlistedVariant(flag, userId) {
|
|
|
7699
7735
|
|
|
7700
7736
|
//#endregion
|
|
7701
7737
|
//#region src/modules/devtools/index.ts
|
|
7702
|
-
const CORE_VERSION = "0.0.1-canary.
|
|
7703
|
-
const WASM_VERSION = "0.0.1-canary.
|
|
7738
|
+
const CORE_VERSION = "0.0.1-canary.221";
|
|
7739
|
+
const WASM_VERSION = "0.0.1-canary.221";
|
|
7704
7740
|
const SURREAL_VERSION = "3.0.3";
|
|
7705
7741
|
var DevToolsService = class DevToolsService {
|
|
7706
7742
|
eventsHistory = [];
|
|
@@ -12715,7 +12751,7 @@ var Sp00kyClient = class {
|
|
|
12715
12751
|
return new TabsCoordinator({
|
|
12716
12752
|
tabId,
|
|
12717
12753
|
fingerprint: computeTabsFingerprint({
|
|
12718
|
-
coreVersion: "0.0.1-canary.
|
|
12754
|
+
coreVersion: "0.0.1-canary.221",
|
|
12719
12755
|
schemaHash: hash53(this.config.schemaSurql),
|
|
12720
12756
|
endpoint: this.config.database.endpoint ?? "",
|
|
12721
12757
|
namespace: this.config.database.namespace,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spooky-sync/core",
|
|
3
|
-
"version": "0.0.1-canary.
|
|
3
|
+
"version": "0.0.1-canary.221",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@spooky-sync/query-builder": "0.0.1-canary.
|
|
64
|
-
"@spooky-sync/ssp-wasm": "0.0.1-canary.
|
|
63
|
+
"@spooky-sync/query-builder": "0.0.1-canary.221",
|
|
64
|
+
"@spooky-sync/ssp-wasm": "0.0.1-canary.221",
|
|
65
65
|
"@sqlite.org/sqlite-wasm": "3.53.0-build1",
|
|
66
66
|
"@surrealdb/wasm": "^3.0.3",
|
|
67
67
|
"blurhash": "^2.0.5",
|
package/src/modules/sync/sync.ts
CHANGED
|
@@ -1878,13 +1878,24 @@ export class Sp00kySync<S extends SchemaStructure> {
|
|
|
1878
1878
|
}
|
|
1879
1879
|
if (ids.length === 0) return;
|
|
1880
1880
|
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1881
|
+
// Over HTTP with `keepalive`, NOT the live socket. A WebSocket send during
|
|
1882
|
+
// `pagehide` is not guaranteed to flush — the browser may tear the socket
|
|
1883
|
+
// down first and the frame is lost. Measured on staging: releasing over the
|
|
1884
|
+
// socket reached the server zero times; `fetch`/`keepalive` is the only
|
|
1885
|
+
// primitive the platform promises to finish after the page is gone.
|
|
1886
|
+
//
|
|
1887
|
+
// Ids are inlined rather than bound because this is a bare statement, not
|
|
1888
|
+
// an RPC call with a params channel. They are `_00_query:<sha256>` record
|
|
1889
|
+
// ids the client itself derived, so the only interpolation is a hex digest.
|
|
1890
|
+
const list = ids
|
|
1891
|
+
.map((id) => String(id))
|
|
1892
|
+
.filter((id) => /^_00_query:[0-9a-f]{64}$/.test(id))
|
|
1893
|
+
.join(', ');
|
|
1894
|
+
if (!list) return;
|
|
1895
|
+
|
|
1896
|
+
this.remote.beaconSql(
|
|
1897
|
+
`FOR $id IN [${list}] { LET $_released = fn::query::unsubscribe($id); };`
|
|
1898
|
+
);
|
|
1888
1899
|
}
|
|
1889
1900
|
|
|
1890
1901
|
private async registerQuery(queryHash: string) {
|
|
@@ -96,6 +96,52 @@ export class RemoteDatabaseService extends AbstractDatabaseService {
|
|
|
96
96
|
return this.config;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Send one SurrealQL statement so that it survives the page going away.
|
|
101
|
+
*
|
|
102
|
+
* A WebSocket `send()` during `pagehide` is not guaranteed to flush — the
|
|
103
|
+
* browser may tear the socket down first, and the frame is simply lost.
|
|
104
|
+
* Measured: an unload-time release over the live socket reached the server
|
|
105
|
+
* zero times out of one. `fetch` with `keepalive` is the primitive the
|
|
106
|
+
* platform actually guarantees here, so this goes over SurrealDB's HTTP
|
|
107
|
+
* `/sql` endpoint instead of the RPC socket.
|
|
108
|
+
*
|
|
109
|
+
* Best-effort by design: no await, no retry, errors swallowed. Every caller
|
|
110
|
+
* must have a server-side fallback that makes a lost beacon a non-event.
|
|
111
|
+
*/
|
|
112
|
+
beaconSql(sql: string): void {
|
|
113
|
+
try {
|
|
114
|
+
const { endpoint, namespace, database } = this.getConfig();
|
|
115
|
+
if (!endpoint || typeof fetch !== 'function') return;
|
|
116
|
+
|
|
117
|
+
// `ws(s)://host/rpc` is the socket; `http(s)://host/sql` is the same
|
|
118
|
+
// server's statement endpoint.
|
|
119
|
+
const url = new URL(endpoint);
|
|
120
|
+
url.protocol = url.protocol === 'wss:' ? 'https:' : url.protocol === 'ws:' ? 'http:' : url.protocol;
|
|
121
|
+
url.pathname = url.pathname.replace(/\/rpc\/?$/, '') + '/sql';
|
|
122
|
+
|
|
123
|
+
const headers: Record<string, string> = {
|
|
124
|
+
Accept: 'application/json',
|
|
125
|
+
'Content-Type': 'text/plain',
|
|
126
|
+
};
|
|
127
|
+
if (namespace) headers['surreal-ns'] = namespace;
|
|
128
|
+
if (database) headers['surreal-db'] = database;
|
|
129
|
+
// Without this the statement runs unauthenticated and `$auth.id` is NONE,
|
|
130
|
+
// which for a per-user release means it matches nothing.
|
|
131
|
+
if (this.authToken) headers.Authorization = `Bearer ${this.authToken}`;
|
|
132
|
+
|
|
133
|
+
void fetch(url.toString(), {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers,
|
|
136
|
+
body: sql,
|
|
137
|
+
keepalive: true,
|
|
138
|
+
credentials: 'omit',
|
|
139
|
+
}).catch(() => {});
|
|
140
|
+
} catch {
|
|
141
|
+
// Malformed endpoint, no fetch, blocked by CSP: the caller's fallback owns it.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
99
145
|
/**
|
|
100
146
|
* Record the token every future connect should authenticate with, or `null`
|
|
101
147
|
* on sign-out. See {@link authToken}.
|