@qm-hub/sync-client-types 0.2.1
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 +83 -0
- package/dist/index.d.mts +227 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.js +317 -0
- package/dist/index.mjs +285 -0
- package/package.json +38 -0
- package/src/client.ts +350 -0
- package/src/index.ts +243 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
QmSyncClient: () => QmSyncClient,
|
|
24
|
+
createAuthHeaders: () => createAuthHeaders,
|
|
25
|
+
createSyncClientConfig: () => createSyncClientConfig,
|
|
26
|
+
fetchHttpClient: () => fetchHttpClient,
|
|
27
|
+
initialCheckpoint: () => initialCheckpoint,
|
|
28
|
+
withBearer: () => withBearer
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/client.ts
|
|
33
|
+
async function fetchHttpClient(request) {
|
|
34
|
+
const headers = {
|
|
35
|
+
"Content-Type": request.headers.contentType,
|
|
36
|
+
"X-API-Key": request.headers.apiKey,
|
|
37
|
+
"X-App-Id": request.headers.appId
|
|
38
|
+
};
|
|
39
|
+
if (request.headers.authorization) {
|
|
40
|
+
headers["Authorization"] = request.headers.authorization;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch(request.url, {
|
|
44
|
+
method: request.method,
|
|
45
|
+
headers,
|
|
46
|
+
body: request.body
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
status: response.status,
|
|
50
|
+
body: await response.text()
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`HTTP request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
var QmSyncClient = class {
|
|
59
|
+
constructor(config, http = fetchHttpClient) {
|
|
60
|
+
this.config = config;
|
|
61
|
+
this.http = http;
|
|
62
|
+
this._accessToken = null;
|
|
63
|
+
this._refreshToken = null;
|
|
64
|
+
this._userId = null;
|
|
65
|
+
}
|
|
66
|
+
// =========================================================================
|
|
67
|
+
// Authentication
|
|
68
|
+
// =========================================================================
|
|
69
|
+
/**
|
|
70
|
+
* Register a new user.
|
|
71
|
+
*/
|
|
72
|
+
async register(username, email, password) {
|
|
73
|
+
const url = `${this.config.serverUrl}/api/v1/auth/register`;
|
|
74
|
+
const headers = this.buildHeaders();
|
|
75
|
+
const body = JSON.stringify({ username, email, password });
|
|
76
|
+
const response = await this.http({ method: "POST", url, headers, body });
|
|
77
|
+
if (!this.isSuccess(response)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Registration failed: ${response.status} - ${response.body}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const auth = JSON.parse(response.body);
|
|
83
|
+
this.storeTokens(auth);
|
|
84
|
+
console.log("Registered user:", auth.userId);
|
|
85
|
+
return auth;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Login with email and password.
|
|
89
|
+
*/
|
|
90
|
+
async login(email, password) {
|
|
91
|
+
const url = `${this.config.serverUrl}/api/v1/auth/login`;
|
|
92
|
+
const headers = this.buildHeaders();
|
|
93
|
+
const body = JSON.stringify({ email, password });
|
|
94
|
+
const response = await this.http({ method: "POST", url, headers, body });
|
|
95
|
+
if (!this.isSuccess(response)) {
|
|
96
|
+
throw new Error(`Login failed: ${response.status} - ${response.body}`);
|
|
97
|
+
}
|
|
98
|
+
const auth = JSON.parse(response.body);
|
|
99
|
+
this.storeTokens(auth);
|
|
100
|
+
console.log("Logged in user:", auth.userId);
|
|
101
|
+
return auth;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Refresh the access token.
|
|
105
|
+
* Note: Named `refreshToken` to match Rust API (singular).
|
|
106
|
+
*/
|
|
107
|
+
async refreshToken() {
|
|
108
|
+
if (!this._refreshToken) {
|
|
109
|
+
throw new Error("Not authenticated - please login first");
|
|
110
|
+
}
|
|
111
|
+
const url = `${this.config.serverUrl}/api/v1/auth/refresh`;
|
|
112
|
+
const headers = this.buildHeaders();
|
|
113
|
+
const body = JSON.stringify({ refreshToken: this._refreshToken });
|
|
114
|
+
const response = await this.http({ method: "POST", url, headers, body });
|
|
115
|
+
if (!this.isSuccess(response)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Token refresh failed: ${response.status} - ${response.body}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const refresh = JSON.parse(response.body);
|
|
121
|
+
this._accessToken = refresh.accessToken;
|
|
122
|
+
this._refreshToken = refresh.refreshToken;
|
|
123
|
+
console.log("Token refreshed successfully");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Logout and clear tokens.
|
|
127
|
+
*/
|
|
128
|
+
logout() {
|
|
129
|
+
this._accessToken = null;
|
|
130
|
+
this._refreshToken = null;
|
|
131
|
+
this._userId = null;
|
|
132
|
+
console.log("Logged out");
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Check if the client is authenticated.
|
|
136
|
+
*/
|
|
137
|
+
isAuthenticated() {
|
|
138
|
+
return this._accessToken !== null;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Set tokens directly (for restoring from storage).
|
|
142
|
+
*/
|
|
143
|
+
setTokens(accessToken, refreshToken, userId) {
|
|
144
|
+
this._accessToken = accessToken;
|
|
145
|
+
this._refreshToken = refreshToken;
|
|
146
|
+
this._userId = userId ?? null;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get current tokens (for persisting to storage).
|
|
150
|
+
*/
|
|
151
|
+
getTokens() {
|
|
152
|
+
return { accessToken: this._accessToken, refreshToken: this._refreshToken };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get current user ID.
|
|
156
|
+
*/
|
|
157
|
+
getUserId() {
|
|
158
|
+
return this._userId;
|
|
159
|
+
}
|
|
160
|
+
// =========================================================================
|
|
161
|
+
// Sync Operations
|
|
162
|
+
// =========================================================================
|
|
163
|
+
/**
|
|
164
|
+
* Push local changes to the server.
|
|
165
|
+
*/
|
|
166
|
+
async push(records) {
|
|
167
|
+
const request = {
|
|
168
|
+
records: records.map(this.syncToPushRecord),
|
|
169
|
+
clientTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
170
|
+
};
|
|
171
|
+
const url = `${this.config.serverUrl}/api/v1/sync/${this.config.appId}/push`;
|
|
172
|
+
return this.authenticatedPost(url, request);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Pull changes from the server.
|
|
176
|
+
*/
|
|
177
|
+
async pull(checkpoint, batchSize) {
|
|
178
|
+
const request = {
|
|
179
|
+
checkpoint,
|
|
180
|
+
batchSize: batchSize ?? this.config.defaultBatchSize
|
|
181
|
+
};
|
|
182
|
+
const url = `${this.config.serverUrl}/api/v1/sync/${this.config.appId}/pull`;
|
|
183
|
+
return this.authenticatedPost(url, request);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Perform a delta sync (push + pull in one request).
|
|
187
|
+
*/
|
|
188
|
+
async delta(records, checkpoint) {
|
|
189
|
+
const request = {
|
|
190
|
+
push: records.length > 0 ? {
|
|
191
|
+
records: records.map(this.syncToPushRecord),
|
|
192
|
+
clientTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
193
|
+
} : void 0,
|
|
194
|
+
pull: {
|
|
195
|
+
checkpoint,
|
|
196
|
+
batchSize: this.config.defaultBatchSize
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const url = `${this.config.serverUrl}/api/v1/sync/${this.config.appId}/delta`;
|
|
200
|
+
return this.authenticatedPost(url, request);
|
|
201
|
+
}
|
|
202
|
+
// =========================================================================
|
|
203
|
+
// Internal Helpers
|
|
204
|
+
// =========================================================================
|
|
205
|
+
buildHeaders(accessToken) {
|
|
206
|
+
const headers = {
|
|
207
|
+
apiKey: this.config.apiKey,
|
|
208
|
+
appId: this.config.appId,
|
|
209
|
+
contentType: "application/json"
|
|
210
|
+
};
|
|
211
|
+
if (accessToken) {
|
|
212
|
+
headers.authorization = `Bearer ${accessToken}`;
|
|
213
|
+
}
|
|
214
|
+
return headers;
|
|
215
|
+
}
|
|
216
|
+
storeTokens(auth) {
|
|
217
|
+
this._accessToken = auth.accessToken;
|
|
218
|
+
this._refreshToken = auth.refreshToken;
|
|
219
|
+
this._userId = auth.userId;
|
|
220
|
+
}
|
|
221
|
+
isSuccess(response) {
|
|
222
|
+
return response.status >= 200 && response.status < 300;
|
|
223
|
+
}
|
|
224
|
+
isUnauthorized(response) {
|
|
225
|
+
return response.status === 401;
|
|
226
|
+
}
|
|
227
|
+
async authenticatedPost(url, body) {
|
|
228
|
+
if (!this._accessToken) {
|
|
229
|
+
throw new Error("Not authenticated - please login first");
|
|
230
|
+
}
|
|
231
|
+
const headers = this.buildHeaders(this._accessToken);
|
|
232
|
+
const bodyJson = JSON.stringify(body);
|
|
233
|
+
let response = await this.http({
|
|
234
|
+
method: "POST",
|
|
235
|
+
url,
|
|
236
|
+
headers,
|
|
237
|
+
body: bodyJson
|
|
238
|
+
});
|
|
239
|
+
if (this.isUnauthorized(response)) {
|
|
240
|
+
console.warn("Access token expired, attempting refresh");
|
|
241
|
+
await this.refreshToken();
|
|
242
|
+
const newHeaders = this.buildHeaders(this._accessToken);
|
|
243
|
+
response = await this.http({
|
|
244
|
+
method: "POST",
|
|
245
|
+
url,
|
|
246
|
+
headers: newHeaders,
|
|
247
|
+
body: bodyJson
|
|
248
|
+
});
|
|
249
|
+
if (!this.isSuccess(response)) {
|
|
250
|
+
console.error(
|
|
251
|
+
"Request failed after token refresh:",
|
|
252
|
+
response.status,
|
|
253
|
+
response.body
|
|
254
|
+
);
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Request failed: ${response.status} - ${response.body}`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return JSON.parse(response.body);
|
|
260
|
+
}
|
|
261
|
+
if (!this.isSuccess(response)) {
|
|
262
|
+
console.error("Request failed:", response.status, response.body);
|
|
263
|
+
throw new Error(`Request failed: ${response.status} - ${response.body}`);
|
|
264
|
+
}
|
|
265
|
+
return JSON.parse(response.body);
|
|
266
|
+
}
|
|
267
|
+
syncToPushRecord(record) {
|
|
268
|
+
return {
|
|
269
|
+
tableName: record.tableName,
|
|
270
|
+
rowId: record.rowId,
|
|
271
|
+
data: record.data,
|
|
272
|
+
version: record.version,
|
|
273
|
+
deleted: record.deleted,
|
|
274
|
+
assumedServerVersion: void 0
|
|
275
|
+
// Match Rust: always None for now
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/index.ts
|
|
281
|
+
function createAuthHeaders(apiKey, appId) {
|
|
282
|
+
return {
|
|
283
|
+
apiKey,
|
|
284
|
+
appId,
|
|
285
|
+
contentType: "application/json"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function withBearer(headers, token) {
|
|
289
|
+
return {
|
|
290
|
+
...headers,
|
|
291
|
+
authorization: `Bearer ${token}`
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function createSyncClientConfig(serverUrl, appId, apiKey, options) {
|
|
295
|
+
return {
|
|
296
|
+
serverUrl,
|
|
297
|
+
appId,
|
|
298
|
+
apiKey,
|
|
299
|
+
defaultBatchSize: options?.defaultBatchSize ?? 100,
|
|
300
|
+
timeoutMs: options?.timeoutMs ?? 3e4
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function initialCheckpoint() {
|
|
304
|
+
return {
|
|
305
|
+
updatedAt: "1970-01-01T00:00:00Z",
|
|
306
|
+
id: ""
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
310
|
+
0 && (module.exports = {
|
|
311
|
+
QmSyncClient,
|
|
312
|
+
createAuthHeaders,
|
|
313
|
+
createSyncClientConfig,
|
|
314
|
+
fetchHttpClient,
|
|
315
|
+
initialCheckpoint,
|
|
316
|
+
withBearer
|
|
317
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
async function fetchHttpClient(request) {
|
|
3
|
+
const headers = {
|
|
4
|
+
"Content-Type": request.headers.contentType,
|
|
5
|
+
"X-API-Key": request.headers.apiKey,
|
|
6
|
+
"X-App-Id": request.headers.appId
|
|
7
|
+
};
|
|
8
|
+
if (request.headers.authorization) {
|
|
9
|
+
headers["Authorization"] = request.headers.authorization;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(request.url, {
|
|
13
|
+
method: request.method,
|
|
14
|
+
headers,
|
|
15
|
+
body: request.body
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
status: response.status,
|
|
19
|
+
body: await response.text()
|
|
20
|
+
};
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`HTTP request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
var QmSyncClient = class {
|
|
28
|
+
constructor(config, http = fetchHttpClient) {
|
|
29
|
+
this.config = config;
|
|
30
|
+
this.http = http;
|
|
31
|
+
this._accessToken = null;
|
|
32
|
+
this._refreshToken = null;
|
|
33
|
+
this._userId = null;
|
|
34
|
+
}
|
|
35
|
+
// =========================================================================
|
|
36
|
+
// Authentication
|
|
37
|
+
// =========================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Register a new user.
|
|
40
|
+
*/
|
|
41
|
+
async register(username, email, password) {
|
|
42
|
+
const url = `${this.config.serverUrl}/api/v1/auth/register`;
|
|
43
|
+
const headers = this.buildHeaders();
|
|
44
|
+
const body = JSON.stringify({ username, email, password });
|
|
45
|
+
const response = await this.http({ method: "POST", url, headers, body });
|
|
46
|
+
if (!this.isSuccess(response)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Registration failed: ${response.status} - ${response.body}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const auth = JSON.parse(response.body);
|
|
52
|
+
this.storeTokens(auth);
|
|
53
|
+
console.log("Registered user:", auth.userId);
|
|
54
|
+
return auth;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Login with email and password.
|
|
58
|
+
*/
|
|
59
|
+
async login(email, password) {
|
|
60
|
+
const url = `${this.config.serverUrl}/api/v1/auth/login`;
|
|
61
|
+
const headers = this.buildHeaders();
|
|
62
|
+
const body = JSON.stringify({ email, password });
|
|
63
|
+
const response = await this.http({ method: "POST", url, headers, body });
|
|
64
|
+
if (!this.isSuccess(response)) {
|
|
65
|
+
throw new Error(`Login failed: ${response.status} - ${response.body}`);
|
|
66
|
+
}
|
|
67
|
+
const auth = JSON.parse(response.body);
|
|
68
|
+
this.storeTokens(auth);
|
|
69
|
+
console.log("Logged in user:", auth.userId);
|
|
70
|
+
return auth;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Refresh the access token.
|
|
74
|
+
* Note: Named `refreshToken` to match Rust API (singular).
|
|
75
|
+
*/
|
|
76
|
+
async refreshToken() {
|
|
77
|
+
if (!this._refreshToken) {
|
|
78
|
+
throw new Error("Not authenticated - please login first");
|
|
79
|
+
}
|
|
80
|
+
const url = `${this.config.serverUrl}/api/v1/auth/refresh`;
|
|
81
|
+
const headers = this.buildHeaders();
|
|
82
|
+
const body = JSON.stringify({ refreshToken: this._refreshToken });
|
|
83
|
+
const response = await this.http({ method: "POST", url, headers, body });
|
|
84
|
+
if (!this.isSuccess(response)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Token refresh failed: ${response.status} - ${response.body}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const refresh = JSON.parse(response.body);
|
|
90
|
+
this._accessToken = refresh.accessToken;
|
|
91
|
+
this._refreshToken = refresh.refreshToken;
|
|
92
|
+
console.log("Token refreshed successfully");
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Logout and clear tokens.
|
|
96
|
+
*/
|
|
97
|
+
logout() {
|
|
98
|
+
this._accessToken = null;
|
|
99
|
+
this._refreshToken = null;
|
|
100
|
+
this._userId = null;
|
|
101
|
+
console.log("Logged out");
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if the client is authenticated.
|
|
105
|
+
*/
|
|
106
|
+
isAuthenticated() {
|
|
107
|
+
return this._accessToken !== null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Set tokens directly (for restoring from storage).
|
|
111
|
+
*/
|
|
112
|
+
setTokens(accessToken, refreshToken, userId) {
|
|
113
|
+
this._accessToken = accessToken;
|
|
114
|
+
this._refreshToken = refreshToken;
|
|
115
|
+
this._userId = userId ?? null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get current tokens (for persisting to storage).
|
|
119
|
+
*/
|
|
120
|
+
getTokens() {
|
|
121
|
+
return { accessToken: this._accessToken, refreshToken: this._refreshToken };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get current user ID.
|
|
125
|
+
*/
|
|
126
|
+
getUserId() {
|
|
127
|
+
return this._userId;
|
|
128
|
+
}
|
|
129
|
+
// =========================================================================
|
|
130
|
+
// Sync Operations
|
|
131
|
+
// =========================================================================
|
|
132
|
+
/**
|
|
133
|
+
* Push local changes to the server.
|
|
134
|
+
*/
|
|
135
|
+
async push(records) {
|
|
136
|
+
const request = {
|
|
137
|
+
records: records.map(this.syncToPushRecord),
|
|
138
|
+
clientTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
139
|
+
};
|
|
140
|
+
const url = `${this.config.serverUrl}/api/v1/sync/${this.config.appId}/push`;
|
|
141
|
+
return this.authenticatedPost(url, request);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Pull changes from the server.
|
|
145
|
+
*/
|
|
146
|
+
async pull(checkpoint, batchSize) {
|
|
147
|
+
const request = {
|
|
148
|
+
checkpoint,
|
|
149
|
+
batchSize: batchSize ?? this.config.defaultBatchSize
|
|
150
|
+
};
|
|
151
|
+
const url = `${this.config.serverUrl}/api/v1/sync/${this.config.appId}/pull`;
|
|
152
|
+
return this.authenticatedPost(url, request);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Perform a delta sync (push + pull in one request).
|
|
156
|
+
*/
|
|
157
|
+
async delta(records, checkpoint) {
|
|
158
|
+
const request = {
|
|
159
|
+
push: records.length > 0 ? {
|
|
160
|
+
records: records.map(this.syncToPushRecord),
|
|
161
|
+
clientTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
162
|
+
} : void 0,
|
|
163
|
+
pull: {
|
|
164
|
+
checkpoint,
|
|
165
|
+
batchSize: this.config.defaultBatchSize
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const url = `${this.config.serverUrl}/api/v1/sync/${this.config.appId}/delta`;
|
|
169
|
+
return this.authenticatedPost(url, request);
|
|
170
|
+
}
|
|
171
|
+
// =========================================================================
|
|
172
|
+
// Internal Helpers
|
|
173
|
+
// =========================================================================
|
|
174
|
+
buildHeaders(accessToken) {
|
|
175
|
+
const headers = {
|
|
176
|
+
apiKey: this.config.apiKey,
|
|
177
|
+
appId: this.config.appId,
|
|
178
|
+
contentType: "application/json"
|
|
179
|
+
};
|
|
180
|
+
if (accessToken) {
|
|
181
|
+
headers.authorization = `Bearer ${accessToken}`;
|
|
182
|
+
}
|
|
183
|
+
return headers;
|
|
184
|
+
}
|
|
185
|
+
storeTokens(auth) {
|
|
186
|
+
this._accessToken = auth.accessToken;
|
|
187
|
+
this._refreshToken = auth.refreshToken;
|
|
188
|
+
this._userId = auth.userId;
|
|
189
|
+
}
|
|
190
|
+
isSuccess(response) {
|
|
191
|
+
return response.status >= 200 && response.status < 300;
|
|
192
|
+
}
|
|
193
|
+
isUnauthorized(response) {
|
|
194
|
+
return response.status === 401;
|
|
195
|
+
}
|
|
196
|
+
async authenticatedPost(url, body) {
|
|
197
|
+
if (!this._accessToken) {
|
|
198
|
+
throw new Error("Not authenticated - please login first");
|
|
199
|
+
}
|
|
200
|
+
const headers = this.buildHeaders(this._accessToken);
|
|
201
|
+
const bodyJson = JSON.stringify(body);
|
|
202
|
+
let response = await this.http({
|
|
203
|
+
method: "POST",
|
|
204
|
+
url,
|
|
205
|
+
headers,
|
|
206
|
+
body: bodyJson
|
|
207
|
+
});
|
|
208
|
+
if (this.isUnauthorized(response)) {
|
|
209
|
+
console.warn("Access token expired, attempting refresh");
|
|
210
|
+
await this.refreshToken();
|
|
211
|
+
const newHeaders = this.buildHeaders(this._accessToken);
|
|
212
|
+
response = await this.http({
|
|
213
|
+
method: "POST",
|
|
214
|
+
url,
|
|
215
|
+
headers: newHeaders,
|
|
216
|
+
body: bodyJson
|
|
217
|
+
});
|
|
218
|
+
if (!this.isSuccess(response)) {
|
|
219
|
+
console.error(
|
|
220
|
+
"Request failed after token refresh:",
|
|
221
|
+
response.status,
|
|
222
|
+
response.body
|
|
223
|
+
);
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Request failed: ${response.status} - ${response.body}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return JSON.parse(response.body);
|
|
229
|
+
}
|
|
230
|
+
if (!this.isSuccess(response)) {
|
|
231
|
+
console.error("Request failed:", response.status, response.body);
|
|
232
|
+
throw new Error(`Request failed: ${response.status} - ${response.body}`);
|
|
233
|
+
}
|
|
234
|
+
return JSON.parse(response.body);
|
|
235
|
+
}
|
|
236
|
+
syncToPushRecord(record) {
|
|
237
|
+
return {
|
|
238
|
+
tableName: record.tableName,
|
|
239
|
+
rowId: record.rowId,
|
|
240
|
+
data: record.data,
|
|
241
|
+
version: record.version,
|
|
242
|
+
deleted: record.deleted,
|
|
243
|
+
assumedServerVersion: void 0
|
|
244
|
+
// Match Rust: always None for now
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/index.ts
|
|
250
|
+
function createAuthHeaders(apiKey, appId) {
|
|
251
|
+
return {
|
|
252
|
+
apiKey,
|
|
253
|
+
appId,
|
|
254
|
+
contentType: "application/json"
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function withBearer(headers, token) {
|
|
258
|
+
return {
|
|
259
|
+
...headers,
|
|
260
|
+
authorization: `Bearer ${token}`
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function createSyncClientConfig(serverUrl, appId, apiKey, options) {
|
|
264
|
+
return {
|
|
265
|
+
serverUrl,
|
|
266
|
+
appId,
|
|
267
|
+
apiKey,
|
|
268
|
+
defaultBatchSize: options?.defaultBatchSize ?? 100,
|
|
269
|
+
timeoutMs: options?.timeoutMs ?? 3e4
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function initialCheckpoint() {
|
|
273
|
+
return {
|
|
274
|
+
updatedAt: "1970-01-01T00:00:00Z",
|
|
275
|
+
id: ""
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
export {
|
|
279
|
+
QmSyncClient,
|
|
280
|
+
createAuthHeaders,
|
|
281
|
+
createSyncClientConfig,
|
|
282
|
+
fetchHttpClient,
|
|
283
|
+
initialCheckpoint,
|
|
284
|
+
withBearer
|
|
285
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qm-hub/sync-client-types",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "TypeScript types for qm-sync-client - generated from Rust with Specta",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"typescript": "^5.0.0"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://https://github.com/loidinhm31/qm-hub.git"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"keywords": [
|
|
33
|
+
"qm-sync",
|
|
34
|
+
"sync",
|
|
35
|
+
"typescript",
|
|
36
|
+
"types"
|
|
37
|
+
]
|
|
38
|
+
}
|