@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/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
+ }