@thinkingdifferently/core 1.2.0 → 1.3.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.d.mts CHANGED
@@ -1,15 +1,55 @@
1
1
  interface SDKConfig {
2
2
  apiKey: string;
3
+ securityKey?: string;
4
+ publicKey?: string;
3
5
  }
4
6
  interface GetOptions {
5
7
  limit?: number;
6
8
  }
9
+ interface CredentialParams {
10
+ adminEmail?: string;
11
+ password?: string;
12
+ }
13
+ interface LoginResponse {
14
+ message: string;
15
+ token: string;
16
+ admin: {
17
+ id: string;
18
+ adminName: string;
19
+ adminEmail: string;
20
+ projectId: string;
21
+ projectName: string;
22
+ };
23
+ }
7
24
 
8
25
  declare class TDClient {
9
26
  private api;
10
27
  private apikey;
11
- constructor(apiKey: string);
12
- request(method: "POST" | "GET" | "PATCH" | "DELETE", body?: any): Promise<any>;
28
+ private securityKey?;
29
+ private publicKey?;
30
+ private adminToken;
31
+ constructor(apiKey: string, securityKey?: string, publicKey?: string);
32
+ setSecurityKey(key: string): void;
33
+ setPublicKey(key: string): void;
34
+ setAdminToken(token: string): void;
35
+ getAdminToken(): string | null;
36
+ getPublicKey(): string | undefined;
37
+ /**
38
+ * Reusable private error formatting utility
39
+ */
40
+ private handleError;
41
+ /**
42
+ * Handles authentication HTTP requests targeting /auth/admin/login
43
+ */
44
+ adminLogin(adminEmail?: string, password?: string): Promise<LoginResponse>;
45
+ /**
46
+ * Determines if an API request is a database write operation.
47
+ */
48
+ private isWriteOperation;
49
+ /**
50
+ * Executes queries and mutations against /data endpoint.
51
+ */
52
+ sendDataRequest(method: "POST" | "GET" | "PATCH" | "DELETE", body?: any): Promise<any>;
13
53
  }
14
54
 
15
55
  type Operator = "=" | "!=" | ">" | "<" | ">=" | "<=" | "in" | "contains";
@@ -48,13 +88,53 @@ declare class QueryBuilder {
48
88
  deleteMany(): Promise<any>;
49
89
  }
50
90
 
91
+ declare class AdminAuth {
92
+ private client;
93
+ constructor(client: TDClient);
94
+ /**
95
+ * Authenticate an administrator using credentials.
96
+ * The token returned will be automatically stored in the SDK client.
97
+ */
98
+ credentials(adminEmail?: string, password?: string): Promise<LoginResponse>;
99
+ /**
100
+ * Get the currently active administrator JWT token.
101
+ */
102
+ getToken(): string | null;
103
+ /**
104
+ * Manually set the administrator JWT token (e.g. for server-side requests).
105
+ */
106
+ setToken(token: string): void;
107
+ /**
108
+ * Offline verification of Ed25519 (EdDSA) JWT admin token using public key.
109
+ * Supported in Node.js / Server-side environments.
110
+ */
111
+ verifyJwt(token: string, publicKeyPem?: string): any;
112
+ }
113
+ declare class AuthModule {
114
+ admin: AdminAuth;
115
+ constructor(client: TDClient);
116
+ }
117
+
51
118
  declare class ThinkingDifferently {
52
119
  private client;
120
+ auth: AuthModule;
53
121
  constructor(config: SDKConfig);
122
+ /**
123
+ * Dynamically update the security key (useful for backend projects).
124
+ */
125
+ setSecurityKey(key: string): void;
126
+ /**
127
+ * Dynamically update the public key used for offline verification.
128
+ */
129
+ setPublicKey(key: string): void;
130
+ /**
131
+ * Helper to verify Ed25519 admin JWT token offline.
132
+ */
133
+ verifyJwt(token: string, publicKey?: string): any;
54
134
  collection(name: string): QueryBuilder;
55
135
  insert(key: string, data: any): Promise<any>;
56
136
  update(key: string, id: string, data: Record<string, any>): Promise<any>;
57
137
  delete(key: string, id: string): Promise<any>;
58
138
  }
59
139
 
60
- export { type GetOptions, type SDKConfig, TDClient, ThinkingDifferently };
140
+ export { AdminAuth, AuthModule, type CredentialParams, type GetOptions, type LoginResponse, type SDKConfig, TDClient, ThinkingDifferently };
package/dist/index.d.ts CHANGED
@@ -1,15 +1,55 @@
1
1
  interface SDKConfig {
2
2
  apiKey: string;
3
+ securityKey?: string;
4
+ publicKey?: string;
3
5
  }
4
6
  interface GetOptions {
5
7
  limit?: number;
6
8
  }
9
+ interface CredentialParams {
10
+ adminEmail?: string;
11
+ password?: string;
12
+ }
13
+ interface LoginResponse {
14
+ message: string;
15
+ token: string;
16
+ admin: {
17
+ id: string;
18
+ adminName: string;
19
+ adminEmail: string;
20
+ projectId: string;
21
+ projectName: string;
22
+ };
23
+ }
7
24
 
8
25
  declare class TDClient {
9
26
  private api;
10
27
  private apikey;
11
- constructor(apiKey: string);
12
- request(method: "POST" | "GET" | "PATCH" | "DELETE", body?: any): Promise<any>;
28
+ private securityKey?;
29
+ private publicKey?;
30
+ private adminToken;
31
+ constructor(apiKey: string, securityKey?: string, publicKey?: string);
32
+ setSecurityKey(key: string): void;
33
+ setPublicKey(key: string): void;
34
+ setAdminToken(token: string): void;
35
+ getAdminToken(): string | null;
36
+ getPublicKey(): string | undefined;
37
+ /**
38
+ * Reusable private error formatting utility
39
+ */
40
+ private handleError;
41
+ /**
42
+ * Handles authentication HTTP requests targeting /auth/admin/login
43
+ */
44
+ adminLogin(adminEmail?: string, password?: string): Promise<LoginResponse>;
45
+ /**
46
+ * Determines if an API request is a database write operation.
47
+ */
48
+ private isWriteOperation;
49
+ /**
50
+ * Executes queries and mutations against /data endpoint.
51
+ */
52
+ sendDataRequest(method: "POST" | "GET" | "PATCH" | "DELETE", body?: any): Promise<any>;
13
53
  }
14
54
 
15
55
  type Operator = "=" | "!=" | ">" | "<" | ">=" | "<=" | "in" | "contains";
@@ -48,13 +88,53 @@ declare class QueryBuilder {
48
88
  deleteMany(): Promise<any>;
49
89
  }
50
90
 
91
+ declare class AdminAuth {
92
+ private client;
93
+ constructor(client: TDClient);
94
+ /**
95
+ * Authenticate an administrator using credentials.
96
+ * The token returned will be automatically stored in the SDK client.
97
+ */
98
+ credentials(adminEmail?: string, password?: string): Promise<LoginResponse>;
99
+ /**
100
+ * Get the currently active administrator JWT token.
101
+ */
102
+ getToken(): string | null;
103
+ /**
104
+ * Manually set the administrator JWT token (e.g. for server-side requests).
105
+ */
106
+ setToken(token: string): void;
107
+ /**
108
+ * Offline verification of Ed25519 (EdDSA) JWT admin token using public key.
109
+ * Supported in Node.js / Server-side environments.
110
+ */
111
+ verifyJwt(token: string, publicKeyPem?: string): any;
112
+ }
113
+ declare class AuthModule {
114
+ admin: AdminAuth;
115
+ constructor(client: TDClient);
116
+ }
117
+
51
118
  declare class ThinkingDifferently {
52
119
  private client;
120
+ auth: AuthModule;
53
121
  constructor(config: SDKConfig);
122
+ /**
123
+ * Dynamically update the security key (useful for backend projects).
124
+ */
125
+ setSecurityKey(key: string): void;
126
+ /**
127
+ * Dynamically update the public key used for offline verification.
128
+ */
129
+ setPublicKey(key: string): void;
130
+ /**
131
+ * Helper to verify Ed25519 admin JWT token offline.
132
+ */
133
+ verifyJwt(token: string, publicKey?: string): any;
54
134
  collection(name: string): QueryBuilder;
55
135
  insert(key: string, data: any): Promise<any>;
56
136
  update(key: string, id: string, data: Record<string, any>): Promise<any>;
57
137
  delete(key: string, id: string): Promise<any>;
58
138
  }
59
139
 
60
- export { type GetOptions, type SDKConfig, TDClient, ThinkingDifferently };
140
+ export { AdminAuth, AuthModule, type CredentialParams, type GetOptions, type LoginResponse, type SDKConfig, TDClient, ThinkingDifferently };
package/dist/index.js CHANGED
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AdminAuth: () => AdminAuth,
34
+ AuthModule: () => AuthModule,
33
35
  TDClient: () => TDClient,
34
36
  ThinkingDifferently: () => ThinkingDifferently
35
37
  });
@@ -38,19 +40,155 @@ module.exports = __toCommonJS(index_exports);
38
40
  // src/client.ts
39
41
  var import_axios = __toESM(require("axios"));
40
42
  var TDClient = class {
41
- constructor(apiKey) {
43
+ constructor(apiKey, securityKey, publicKey) {
44
+ this.adminToken = null;
42
45
  this.apikey = apiKey;
46
+ this.securityKey = securityKey;
47
+ this.publicKey = publicKey;
43
48
  this.api = import_axios.default.create({
49
+ // baseURL: "http://localhost:3000/api/v1",
44
50
  baseURL: "https://www.thinkingdifferently.dev/api/v1",
45
51
  headers: {
46
- "x-api-key": apiKey,
52
+ "X-API-Key": apiKey,
47
53
  "Content-Type": "application/json"
48
- }
54
+ },
55
+ withCredentials: true
56
+ // Enable browser cookie sharing automatically
49
57
  });
50
58
  }
51
- async request(method, body) {
59
+ //what is hte use of withCredentials: true in axios config?
60
+ //The withCredentials: true option in the Axios configuration is used to indicate that cross-site Access-Control requests should be made using credentials such as cookies,
61
+ // authorization headers, or TLS client certificates. When this option is set to true, it allows the browser to include cookies and other credentials in requests made to
62
+ // a different domain than the one serving the web page. This is particularly important for maintaining sessions and authentication when making API calls to a backend server
63
+ // that is on a different domain than the frontend application.
64
+ setSecurityKey(key) {
65
+ this.securityKey = key;
66
+ }
67
+ setPublicKey(key) {
68
+ this.publicKey = key;
69
+ }
70
+ setAdminToken(token) {
71
+ this.adminToken = token;
72
+ }
73
+ getAdminToken() {
74
+ return this.adminToken;
75
+ }
76
+ getPublicKey() {
77
+ return this.publicKey;
78
+ }
79
+ /**
80
+ * Reusable private error formatting utility
81
+ */
82
+ // private handleError(error: any): Error {
83
+ // if (error.response && error.response.data && error.response.data.error) {
84
+ // const backendError = error.response.data.error;
85
+ //
86
+ // let errorMessage = `[ThinkingDifferently SDK Error] ${backendError.code || error.response.status}: ${backendError.message}`;
87
+ //
88
+ // if (backendError.details) {
89
+ // errorMessage += `\nDetails: ${JSON.stringify(backendError.details, null, 2)}`;
90
+ // }
91
+ //
92
+ // return new Error(errorMessage);
93
+ // }
94
+ //
95
+ // return new Error(
96
+ // `[ThinkingDifferently Network Error] ${error.message || "Could not connect to the API."}`
97
+ // );
98
+ // }
99
+ //why if everything fails then we say that could not connect to the api? is it because we assume that if it's not a structured error from the backend, then it's likely a network or connectivity issue?
100
+ //explain: Yes, that's correct. The rationale behind returning a generic "Could not connect to the API" message when the error doesn't match the expected structured format from the backend is based on the assumption that if the error isn't coming from the API in a recognizable way, it's likely due to a network issue or some other problem preventing communication with the server.
101
+ // In a well-designed API, errors should be returned in a consistent format (e.g., with an "error" object containing "code" and "message").
102
+ // If the error doesn't conform to this structure, it suggests that the request may not have reached the server at all, or there was a failure in the network layer (like DNS issues, server downtime, CORS errors, etc.). Therefore, providing a generic message helps guide developers towards checking their network connection or server status when they encounter such unstructured errors.
103
+ //what if the developer of server forgets to send the error in this structured format? then the sdk will always say could not connect to the api even if the api is working fine but the error is not structured properly. so how can we improve this error handling to be more robust and informative?
104
+ // To improve the error handling and make it more robust and informative, we can implement a few strategies:
105
+ // 1. **Log the Raw Error**: Always log the raw error object to the console or a logging service. This way, developers can see the full context of the error, even if it's not structured.
106
+ // 2. **Check for Common Error Patterns**: Instead of only checking for a specific structured format, we can look for common patterns in error responses (like status codes, message fields, etc.) to provide more context.
107
+ // 3. **Fallback Messages**: If the error doesn't match the expected structure, we can provide a fallback message that includes any available information from the error object (like status code or message) instead of just saying "Could not connect to the API."
108
+ // 4. **Documentation and Guidelines**: Encourage backend developers to follow a consistent error response format through documentation and guidelines. This can help ensure that errors are structured properly, making it easier for the SDK to handle them effectively.
109
+ // Here's an improved version of the error handling method:
110
+ handleError(error) {
111
+ console.error("[ThinkingDifferently SDK] Raw Error:", error);
112
+ if (error.response) {
113
+ const status = error.response.status;
114
+ const data = error.response.data;
115
+ if (data && data.error) {
116
+ const backendError = data.error;
117
+ let errorMessage = `[ThinkingDifferently SDK Error] ${backendError.code || status}: ${backendError.message}`;
118
+ if (backendError.details) {
119
+ errorMessage += `
120
+ Details: ${JSON.stringify(backendError.details, null, 2)}`;
121
+ }
122
+ return new Error(errorMessage);
123
+ }
124
+ return new Error(`[ThinkingDifferently SDK Error] HTTP ${status}: ${data.message || "An error occurred."}`);
125
+ }
126
+ return new Error(
127
+ `[ThinkingDifferently Network Error] ${error.message || "Could not connect to the API."}`
128
+ );
129
+ }
130
+ /**
131
+ * Handles authentication HTTP requests targeting /auth/admin/login
132
+ */
133
+ async adminLogin(adminEmail, password) {
52
134
  try {
53
- const isFormData = body instanceof FormData;
135
+ const response = await this.api.post("/auth/admin/login", {
136
+ adminEmail,
137
+ password
138
+ });
139
+ const { token } = response.data;
140
+ if (token) {
141
+ this.setAdminToken(token);
142
+ }
143
+ return response.data;
144
+ } catch (error) {
145
+ throw this.handleError(error);
146
+ }
147
+ }
148
+ /**
149
+ * Determines if an API request is a database write operation.
150
+ */
151
+ isWriteOperation(method, body) {
152
+ if (method === "GET") return false;
153
+ if (method === "PATCH" || method === "DELETE") return true;
154
+ if (body) {
155
+ if (typeof FormData !== "undefined" && body instanceof FormData) {
156
+ return true;
157
+ }
158
+ const query = body.query;
159
+ if (query) {
160
+ const parsed = typeof query === "string" ? JSON.parse(query) : query;
161
+ if (parsed && parsed.operation === "find") {
162
+ return false;
163
+ }
164
+ }
165
+ }
166
+ return true;
167
+ }
168
+ /**
169
+ * Executes queries and mutations against /data endpoint.
170
+ */
171
+ async sendDataRequest(method, body) {
172
+ if (this.isWriteOperation(method, body)) {
173
+ const isBrowser = typeof window !== "undefined";
174
+ if (!isBrowser && !this.securityKey && !this.adminToken) {
175
+ throw new Error(
176
+ "[ThinkingDifferently SDK Error] Write operations require either a valid Security Key or an Admin Session Token."
177
+ );
178
+ }
179
+ }
180
+ try {
181
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
182
+ const headers = {};
183
+ if (!isFormData) {
184
+ headers["Content-Type"] = "application/json";
185
+ }
186
+ if (this.securityKey) {
187
+ headers["x-security-key"] = this.securityKey;
188
+ }
189
+ if (this.adminToken) {
190
+ headers["x-admin-token"] = this.adminToken;
191
+ }
54
192
  if (isFormData) {
55
193
  body.append("key", this.apikey);
56
194
  } else {
@@ -62,14 +200,12 @@ var TDClient = class {
62
200
  const response = await this.api.request({
63
201
  url: "/data",
64
202
  method,
65
- headers: isFormData ? {} : { "Content-Type": "application/json" },
203
+ headers,
66
204
  ...method === "GET" ? { params: body } : { data: body }
67
205
  });
68
206
  return response.data;
69
207
  } catch (error) {
70
- throw new Error(
71
- error?.response?.data?.message || "Something went wrong"
72
- );
208
+ throw this.handleError(error);
73
209
  }
74
210
  }
75
211
  };
@@ -121,7 +257,7 @@ var QueryBuilder = class {
121
257
  console.log("\n================ COUNT REQUEST ================");
122
258
  console.log("[SDK] Final Query:", this.query);
123
259
  try {
124
- const response = await this.client.request(
260
+ const response = await this.client.sendDataRequest(
125
261
  "POST",
126
262
  this.query
127
263
  );
@@ -136,11 +272,16 @@ var QueryBuilder = class {
136
272
  return structuredClone(this.query);
137
273
  }
138
274
  async get() {
275
+ if (arguments.length > 0) {
276
+ throw new Error(
277
+ "[ThinkingDifferently SDK Error] The .get() method takes no arguments. Please use chainable methods like .where() and .limit()."
278
+ );
279
+ }
139
280
  this.query.operation = "find";
140
281
  console.log("\n================ GET REQUEST ================");
141
282
  console.log("[SDK] Final Query:", this.query);
142
283
  try {
143
- const response = await this.client.request(
284
+ const response = await this.client.sendDataRequest(
144
285
  "POST",
145
286
  this.query
146
287
  );
@@ -181,14 +322,14 @@ var QueryBuilder = class {
181
322
  JSON.stringify(this.query)
182
323
  );
183
324
  console.log("[SDK] Query:", this.query);
184
- return await this.client.request(
325
+ return await this.client.sendDataRequest(
185
326
  "POST",
186
327
  payload
187
328
  );
188
329
  }
189
330
  this.query.data = data;
190
331
  console.log("[SDK] Final Query:", this.query);
191
- return await this.client.request(
332
+ return await this.client.sendDataRequest(
192
333
  "POST",
193
334
  this.query
194
335
  );
@@ -205,7 +346,7 @@ var QueryBuilder = class {
205
346
  console.log("\n================ UPDATE REQUEST ================");
206
347
  console.log("[SDK] Final Query:", this.query);
207
348
  try {
208
- const response = await this.client.request(
349
+ const response = await this.client.sendDataRequest(
209
350
  "PATCH",
210
351
  this.query
211
352
  );
@@ -226,7 +367,7 @@ var QueryBuilder = class {
226
367
  console.log("\n================ UPDATE MANY REQUEST ================");
227
368
  console.log("[SDK] Final Query:", this.query);
228
369
  try {
229
- const response = await this.client.request(
370
+ const response = await this.client.sendDataRequest(
230
371
  "PATCH",
231
372
  this.query
232
373
  );
@@ -244,7 +385,7 @@ var QueryBuilder = class {
244
385
  console.log("\n================ DELETE REQUEST ================");
245
386
  console.log("[SDK] Final Query:", this.query);
246
387
  try {
247
- const response = await this.client.request(
388
+ const response = await this.client.sendDataRequest(
248
389
  "DELETE",
249
390
  this.query
250
391
  );
@@ -264,7 +405,7 @@ var QueryBuilder = class {
264
405
  console.log("\n================ DELETE MANY REQUEST ================");
265
406
  console.log("[SDK] Final Query:", this.query);
266
407
  try {
267
- const response = await this.client.request(
408
+ const response = await this.client.sendDataRequest(
268
409
  "DELETE",
269
410
  this.query
270
411
  );
@@ -278,11 +419,139 @@ var QueryBuilder = class {
278
419
  }
279
420
  };
280
421
 
422
+ // src/auth.ts
423
+ var crypto = __toESM(require("crypto"));
424
+ var AdminAuth = class {
425
+ constructor(client) {
426
+ this.client = client;
427
+ }
428
+ /**
429
+ * Authenticate an administrator using credentials.
430
+ * The token returned will be automatically stored in the SDK client.
431
+ */
432
+ async credentials(adminEmail, password) {
433
+ return this.client.adminLogin(adminEmail, password);
434
+ }
435
+ /**
436
+ * Get the currently active administrator JWT token.
437
+ */
438
+ getToken() {
439
+ return this.client.getAdminToken();
440
+ }
441
+ /**
442
+ * Manually set the administrator JWT token (e.g. for server-side requests).
443
+ */
444
+ setToken(token) {
445
+ this.client.setAdminToken(token);
446
+ }
447
+ /**
448
+ * Offline verification of Ed25519 (EdDSA) JWT admin token using public key.
449
+ * Supported in Node.js / Server-side environments.
450
+ */
451
+ //detailed explaination of the verifyJwt function:
452
+ // The verifyJwt function is designed to validate a JWT (JSON Web Token) using
453
+ // the Ed25519 signature algorithm (EdDSA). It takes a JWT token as input and an optional public key in PEM format.
454
+ // The function performs several steps to ensure the token's integrity and authenticity:
455
+ // 1. It first determines which public key to use for verification, either from the argument or from the client's configuration.
456
+ // 2. It checks if the runtime environment supports the necessary crypto capabilities for JWT verification.
457
+ // 3. It splits the JWT into its three components: header, payload, and signature.
458
+ // 4. It decodes the signature from base64url format into a Buffer.
459
+ // 5. It imports the public key using Node.js's crypto module.
460
+ // 6. It verifies the signature against the message (header + payload) using the Ed25519 algorithm.
461
+ // 7. If verification succeeds, it decodes and parses the payload JSON.
462
+ // 8. It checks for token expiration based on the "exp" claim in the payload.
463
+ // 9. Finally, it returns the decoded payload if all checks pass,
464
+ // or throws descriptive errors if any step fails.
465
+ verifyJwt(token, publicKeyPem) {
466
+ const keyToUse = publicKeyPem || this.client.getPublicKey();
467
+ if (!keyToUse) {
468
+ throw new Error(
469
+ "[ThinkingDifferently SDK Error] Public key is required for JWT verification. Please configure it in SDKConfig or pass it as an argument."
470
+ );
471
+ }
472
+ if (typeof crypto === "undefined" || !crypto.createPublicKey || !crypto.verify) {
473
+ throw new Error(
474
+ "[ThinkingDifferently SDK Error] JWT verification is only supported in Node.js / Server-side environments."
475
+ );
476
+ }
477
+ const parts = token.split(".");
478
+ if (parts.length !== 3) {
479
+ throw new Error("Invalid JWT format");
480
+ }
481
+ const [headerB64, payloadB64, signatureB64] = parts;
482
+ const message = `${headerB64}.${payloadB64}`;
483
+ let signature;
484
+ try {
485
+ signature = Buffer.from(signatureB64, "base64url");
486
+ } catch (err) {
487
+ throw new Error("Invalid JWT signature encoding");
488
+ }
489
+ let publicKey;
490
+ try {
491
+ publicKey = crypto.createPublicKey({
492
+ key: keyToUse,
493
+ format: "pem",
494
+ type: "spki"
495
+ });
496
+ } catch (err) {
497
+ throw new Error(`Failed to import public key: ${err.message}`);
498
+ }
499
+ const verified = crypto.verify(
500
+ void 0,
501
+ // Algorithm must be undefined for Ed25519 (EdDSA) in Node
502
+ Buffer.from(message),
503
+ publicKey,
504
+ signature
505
+ );
506
+ if (!verified) {
507
+ throw new Error("Invalid JWT signature");
508
+ }
509
+ let payload;
510
+ try {
511
+ const payloadJson = Buffer.from(payloadB64, "base64url").toString("utf8");
512
+ payload = JSON.parse(payloadJson);
513
+ } catch (err) {
514
+ throw new Error("Invalid JWT payload JSON");
515
+ }
516
+ if (payload.exp && typeof payload.exp === "number") {
517
+ const currentTime = Math.floor(Date.now() / 1e3);
518
+ if (payload.exp < currentTime) {
519
+ throw new Error("JWT has expired");
520
+ }
521
+ }
522
+ return payload;
523
+ }
524
+ };
525
+ var AuthModule = class {
526
+ constructor(client) {
527
+ this.admin = new AdminAuth(client);
528
+ }
529
+ };
530
+
281
531
  // src/index.ts
282
532
  var ThinkingDifferently = class {
283
533
  constructor(config) {
284
534
  console.log("[ThinkingDifferently SDK] Initializing SDK");
285
- this.client = new TDClient(config.apiKey);
535
+ this.client = new TDClient(config.apiKey, config.securityKey, config.publicKey);
536
+ this.auth = new AuthModule(this.client);
537
+ }
538
+ /**
539
+ * Dynamically update the security key (useful for backend projects).
540
+ */
541
+ setSecurityKey(key) {
542
+ this.client.setSecurityKey(key);
543
+ }
544
+ /**
545
+ * Dynamically update the public key used for offline verification.
546
+ */
547
+ setPublicKey(key) {
548
+ this.client.setPublicKey(key);
549
+ }
550
+ /**
551
+ * Helper to verify Ed25519 admin JWT token offline.
552
+ */
553
+ verifyJwt(token, publicKey) {
554
+ return this.auth.admin.verifyJwt(token, publicKey);
286
555
  }
287
556
  collection(name) {
288
557
  return new QueryBuilder(
@@ -316,7 +585,7 @@ var ThinkingDifferently = class {
316
585
  for (const [k, v] of payload.entries()) {
317
586
  console.log(k, v);
318
587
  }
319
- return this.client.request(
588
+ return this.client.sendDataRequest(
320
589
  "POST",
321
590
  payload
322
591
  );
@@ -326,7 +595,7 @@ var ThinkingDifferently = class {
326
595
  key,
327
596
  data
328
597
  });
329
- const response = await this.client.request(
598
+ const response = await this.client.sendDataRequest(
330
599
  "POST",
331
600
  {
332
601
  key,
@@ -347,7 +616,7 @@ var ThinkingDifferently = class {
347
616
  console.log("[SDK] Document ID:", id);
348
617
  console.log("[SDK] Update Data:", data);
349
618
  try {
350
- const response = await this.client.request(
619
+ const response = await this.client.sendDataRequest(
351
620
  "PATCH",
352
621
  {
353
622
  key,
@@ -368,7 +637,7 @@ var ThinkingDifferently = class {
368
637
  console.log("[SDK] Collection Key:", key);
369
638
  console.log("[SDK] Document ID:", id);
370
639
  try {
371
- const response = await this.client.request(
640
+ const response = await this.client.sendDataRequest(
372
641
  "DELETE",
373
642
  {
374
643
  key,
@@ -386,6 +655,8 @@ var ThinkingDifferently = class {
386
655
  };
387
656
  // Annotate the CommonJS export names for ESM import in node:
388
657
  0 && (module.exports = {
658
+ AdminAuth,
659
+ AuthModule,
389
660
  TDClient,
390
661
  ThinkingDifferently
391
662
  });
package/dist/index.mjs CHANGED
@@ -1,19 +1,155 @@
1
1
  // src/client.ts
2
2
  import axios from "axios";
3
3
  var TDClient = class {
4
- constructor(apiKey) {
4
+ constructor(apiKey, securityKey, publicKey) {
5
+ this.adminToken = null;
5
6
  this.apikey = apiKey;
7
+ this.securityKey = securityKey;
8
+ this.publicKey = publicKey;
6
9
  this.api = axios.create({
10
+ // baseURL: "http://localhost:3000/api/v1",
7
11
  baseURL: "https://www.thinkingdifferently.dev/api/v1",
8
12
  headers: {
9
- "x-api-key": apiKey,
13
+ "X-API-Key": apiKey,
10
14
  "Content-Type": "application/json"
11
- }
15
+ },
16
+ withCredentials: true
17
+ // Enable browser cookie sharing automatically
12
18
  });
13
19
  }
14
- async request(method, body) {
20
+ //what is hte use of withCredentials: true in axios config?
21
+ //The withCredentials: true option in the Axios configuration is used to indicate that cross-site Access-Control requests should be made using credentials such as cookies,
22
+ // authorization headers, or TLS client certificates. When this option is set to true, it allows the browser to include cookies and other credentials in requests made to
23
+ // a different domain than the one serving the web page. This is particularly important for maintaining sessions and authentication when making API calls to a backend server
24
+ // that is on a different domain than the frontend application.
25
+ setSecurityKey(key) {
26
+ this.securityKey = key;
27
+ }
28
+ setPublicKey(key) {
29
+ this.publicKey = key;
30
+ }
31
+ setAdminToken(token) {
32
+ this.adminToken = token;
33
+ }
34
+ getAdminToken() {
35
+ return this.adminToken;
36
+ }
37
+ getPublicKey() {
38
+ return this.publicKey;
39
+ }
40
+ /**
41
+ * Reusable private error formatting utility
42
+ */
43
+ // private handleError(error: any): Error {
44
+ // if (error.response && error.response.data && error.response.data.error) {
45
+ // const backendError = error.response.data.error;
46
+ //
47
+ // let errorMessage = `[ThinkingDifferently SDK Error] ${backendError.code || error.response.status}: ${backendError.message}`;
48
+ //
49
+ // if (backendError.details) {
50
+ // errorMessage += `\nDetails: ${JSON.stringify(backendError.details, null, 2)}`;
51
+ // }
52
+ //
53
+ // return new Error(errorMessage);
54
+ // }
55
+ //
56
+ // return new Error(
57
+ // `[ThinkingDifferently Network Error] ${error.message || "Could not connect to the API."}`
58
+ // );
59
+ // }
60
+ //why if everything fails then we say that could not connect to the api? is it because we assume that if it's not a structured error from the backend, then it's likely a network or connectivity issue?
61
+ //explain: Yes, that's correct. The rationale behind returning a generic "Could not connect to the API" message when the error doesn't match the expected structured format from the backend is based on the assumption that if the error isn't coming from the API in a recognizable way, it's likely due to a network issue or some other problem preventing communication with the server.
62
+ // In a well-designed API, errors should be returned in a consistent format (e.g., with an "error" object containing "code" and "message").
63
+ // If the error doesn't conform to this structure, it suggests that the request may not have reached the server at all, or there was a failure in the network layer (like DNS issues, server downtime, CORS errors, etc.). Therefore, providing a generic message helps guide developers towards checking their network connection or server status when they encounter such unstructured errors.
64
+ //what if the developer of server forgets to send the error in this structured format? then the sdk will always say could not connect to the api even if the api is working fine but the error is not structured properly. so how can we improve this error handling to be more robust and informative?
65
+ // To improve the error handling and make it more robust and informative, we can implement a few strategies:
66
+ // 1. **Log the Raw Error**: Always log the raw error object to the console or a logging service. This way, developers can see the full context of the error, even if it's not structured.
67
+ // 2. **Check for Common Error Patterns**: Instead of only checking for a specific structured format, we can look for common patterns in error responses (like status codes, message fields, etc.) to provide more context.
68
+ // 3. **Fallback Messages**: If the error doesn't match the expected structure, we can provide a fallback message that includes any available information from the error object (like status code or message) instead of just saying "Could not connect to the API."
69
+ // 4. **Documentation and Guidelines**: Encourage backend developers to follow a consistent error response format through documentation and guidelines. This can help ensure that errors are structured properly, making it easier for the SDK to handle them effectively.
70
+ // Here's an improved version of the error handling method:
71
+ handleError(error) {
72
+ console.error("[ThinkingDifferently SDK] Raw Error:", error);
73
+ if (error.response) {
74
+ const status = error.response.status;
75
+ const data = error.response.data;
76
+ if (data && data.error) {
77
+ const backendError = data.error;
78
+ let errorMessage = `[ThinkingDifferently SDK Error] ${backendError.code || status}: ${backendError.message}`;
79
+ if (backendError.details) {
80
+ errorMessage += `
81
+ Details: ${JSON.stringify(backendError.details, null, 2)}`;
82
+ }
83
+ return new Error(errorMessage);
84
+ }
85
+ return new Error(`[ThinkingDifferently SDK Error] HTTP ${status}: ${data.message || "An error occurred."}`);
86
+ }
87
+ return new Error(
88
+ `[ThinkingDifferently Network Error] ${error.message || "Could not connect to the API."}`
89
+ );
90
+ }
91
+ /**
92
+ * Handles authentication HTTP requests targeting /auth/admin/login
93
+ */
94
+ async adminLogin(adminEmail, password) {
15
95
  try {
16
- const isFormData = body instanceof FormData;
96
+ const response = await this.api.post("/auth/admin/login", {
97
+ adminEmail,
98
+ password
99
+ });
100
+ const { token } = response.data;
101
+ if (token) {
102
+ this.setAdminToken(token);
103
+ }
104
+ return response.data;
105
+ } catch (error) {
106
+ throw this.handleError(error);
107
+ }
108
+ }
109
+ /**
110
+ * Determines if an API request is a database write operation.
111
+ */
112
+ isWriteOperation(method, body) {
113
+ if (method === "GET") return false;
114
+ if (method === "PATCH" || method === "DELETE") return true;
115
+ if (body) {
116
+ if (typeof FormData !== "undefined" && body instanceof FormData) {
117
+ return true;
118
+ }
119
+ const query = body.query;
120
+ if (query) {
121
+ const parsed = typeof query === "string" ? JSON.parse(query) : query;
122
+ if (parsed && parsed.operation === "find") {
123
+ return false;
124
+ }
125
+ }
126
+ }
127
+ return true;
128
+ }
129
+ /**
130
+ * Executes queries and mutations against /data endpoint.
131
+ */
132
+ async sendDataRequest(method, body) {
133
+ if (this.isWriteOperation(method, body)) {
134
+ const isBrowser = typeof window !== "undefined";
135
+ if (!isBrowser && !this.securityKey && !this.adminToken) {
136
+ throw new Error(
137
+ "[ThinkingDifferently SDK Error] Write operations require either a valid Security Key or an Admin Session Token."
138
+ );
139
+ }
140
+ }
141
+ try {
142
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
143
+ const headers = {};
144
+ if (!isFormData) {
145
+ headers["Content-Type"] = "application/json";
146
+ }
147
+ if (this.securityKey) {
148
+ headers["x-security-key"] = this.securityKey;
149
+ }
150
+ if (this.adminToken) {
151
+ headers["x-admin-token"] = this.adminToken;
152
+ }
17
153
  if (isFormData) {
18
154
  body.append("key", this.apikey);
19
155
  } else {
@@ -25,14 +161,12 @@ var TDClient = class {
25
161
  const response = await this.api.request({
26
162
  url: "/data",
27
163
  method,
28
- headers: isFormData ? {} : { "Content-Type": "application/json" },
164
+ headers,
29
165
  ...method === "GET" ? { params: body } : { data: body }
30
166
  });
31
167
  return response.data;
32
168
  } catch (error) {
33
- throw new Error(
34
- error?.response?.data?.message || "Something went wrong"
35
- );
169
+ throw this.handleError(error);
36
170
  }
37
171
  }
38
172
  };
@@ -84,7 +218,7 @@ var QueryBuilder = class {
84
218
  console.log("\n================ COUNT REQUEST ================");
85
219
  console.log("[SDK] Final Query:", this.query);
86
220
  try {
87
- const response = await this.client.request(
221
+ const response = await this.client.sendDataRequest(
88
222
  "POST",
89
223
  this.query
90
224
  );
@@ -99,11 +233,16 @@ var QueryBuilder = class {
99
233
  return structuredClone(this.query);
100
234
  }
101
235
  async get() {
236
+ if (arguments.length > 0) {
237
+ throw new Error(
238
+ "[ThinkingDifferently SDK Error] The .get() method takes no arguments. Please use chainable methods like .where() and .limit()."
239
+ );
240
+ }
102
241
  this.query.operation = "find";
103
242
  console.log("\n================ GET REQUEST ================");
104
243
  console.log("[SDK] Final Query:", this.query);
105
244
  try {
106
- const response = await this.client.request(
245
+ const response = await this.client.sendDataRequest(
107
246
  "POST",
108
247
  this.query
109
248
  );
@@ -144,14 +283,14 @@ var QueryBuilder = class {
144
283
  JSON.stringify(this.query)
145
284
  );
146
285
  console.log("[SDK] Query:", this.query);
147
- return await this.client.request(
286
+ return await this.client.sendDataRequest(
148
287
  "POST",
149
288
  payload
150
289
  );
151
290
  }
152
291
  this.query.data = data;
153
292
  console.log("[SDK] Final Query:", this.query);
154
- return await this.client.request(
293
+ return await this.client.sendDataRequest(
155
294
  "POST",
156
295
  this.query
157
296
  );
@@ -168,7 +307,7 @@ var QueryBuilder = class {
168
307
  console.log("\n================ UPDATE REQUEST ================");
169
308
  console.log("[SDK] Final Query:", this.query);
170
309
  try {
171
- const response = await this.client.request(
310
+ const response = await this.client.sendDataRequest(
172
311
  "PATCH",
173
312
  this.query
174
313
  );
@@ -189,7 +328,7 @@ var QueryBuilder = class {
189
328
  console.log("\n================ UPDATE MANY REQUEST ================");
190
329
  console.log("[SDK] Final Query:", this.query);
191
330
  try {
192
- const response = await this.client.request(
331
+ const response = await this.client.sendDataRequest(
193
332
  "PATCH",
194
333
  this.query
195
334
  );
@@ -207,7 +346,7 @@ var QueryBuilder = class {
207
346
  console.log("\n================ DELETE REQUEST ================");
208
347
  console.log("[SDK] Final Query:", this.query);
209
348
  try {
210
- const response = await this.client.request(
349
+ const response = await this.client.sendDataRequest(
211
350
  "DELETE",
212
351
  this.query
213
352
  );
@@ -227,7 +366,7 @@ var QueryBuilder = class {
227
366
  console.log("\n================ DELETE MANY REQUEST ================");
228
367
  console.log("[SDK] Final Query:", this.query);
229
368
  try {
230
- const response = await this.client.request(
369
+ const response = await this.client.sendDataRequest(
231
370
  "DELETE",
232
371
  this.query
233
372
  );
@@ -241,11 +380,139 @@ var QueryBuilder = class {
241
380
  }
242
381
  };
243
382
 
383
+ // src/auth.ts
384
+ import * as crypto from "crypto";
385
+ var AdminAuth = class {
386
+ constructor(client) {
387
+ this.client = client;
388
+ }
389
+ /**
390
+ * Authenticate an administrator using credentials.
391
+ * The token returned will be automatically stored in the SDK client.
392
+ */
393
+ async credentials(adminEmail, password) {
394
+ return this.client.adminLogin(adminEmail, password);
395
+ }
396
+ /**
397
+ * Get the currently active administrator JWT token.
398
+ */
399
+ getToken() {
400
+ return this.client.getAdminToken();
401
+ }
402
+ /**
403
+ * Manually set the administrator JWT token (e.g. for server-side requests).
404
+ */
405
+ setToken(token) {
406
+ this.client.setAdminToken(token);
407
+ }
408
+ /**
409
+ * Offline verification of Ed25519 (EdDSA) JWT admin token using public key.
410
+ * Supported in Node.js / Server-side environments.
411
+ */
412
+ //detailed explaination of the verifyJwt function:
413
+ // The verifyJwt function is designed to validate a JWT (JSON Web Token) using
414
+ // the Ed25519 signature algorithm (EdDSA). It takes a JWT token as input and an optional public key in PEM format.
415
+ // The function performs several steps to ensure the token's integrity and authenticity:
416
+ // 1. It first determines which public key to use for verification, either from the argument or from the client's configuration.
417
+ // 2. It checks if the runtime environment supports the necessary crypto capabilities for JWT verification.
418
+ // 3. It splits the JWT into its three components: header, payload, and signature.
419
+ // 4. It decodes the signature from base64url format into a Buffer.
420
+ // 5. It imports the public key using Node.js's crypto module.
421
+ // 6. It verifies the signature against the message (header + payload) using the Ed25519 algorithm.
422
+ // 7. If verification succeeds, it decodes and parses the payload JSON.
423
+ // 8. It checks for token expiration based on the "exp" claim in the payload.
424
+ // 9. Finally, it returns the decoded payload if all checks pass,
425
+ // or throws descriptive errors if any step fails.
426
+ verifyJwt(token, publicKeyPem) {
427
+ const keyToUse = publicKeyPem || this.client.getPublicKey();
428
+ if (!keyToUse) {
429
+ throw new Error(
430
+ "[ThinkingDifferently SDK Error] Public key is required for JWT verification. Please configure it in SDKConfig or pass it as an argument."
431
+ );
432
+ }
433
+ if (typeof crypto === "undefined" || !crypto.createPublicKey || !crypto.verify) {
434
+ throw new Error(
435
+ "[ThinkingDifferently SDK Error] JWT verification is only supported in Node.js / Server-side environments."
436
+ );
437
+ }
438
+ const parts = token.split(".");
439
+ if (parts.length !== 3) {
440
+ throw new Error("Invalid JWT format");
441
+ }
442
+ const [headerB64, payloadB64, signatureB64] = parts;
443
+ const message = `${headerB64}.${payloadB64}`;
444
+ let signature;
445
+ try {
446
+ signature = Buffer.from(signatureB64, "base64url");
447
+ } catch (err) {
448
+ throw new Error("Invalid JWT signature encoding");
449
+ }
450
+ let publicKey;
451
+ try {
452
+ publicKey = crypto.createPublicKey({
453
+ key: keyToUse,
454
+ format: "pem",
455
+ type: "spki"
456
+ });
457
+ } catch (err) {
458
+ throw new Error(`Failed to import public key: ${err.message}`);
459
+ }
460
+ const verified = crypto.verify(
461
+ void 0,
462
+ // Algorithm must be undefined for Ed25519 (EdDSA) in Node
463
+ Buffer.from(message),
464
+ publicKey,
465
+ signature
466
+ );
467
+ if (!verified) {
468
+ throw new Error("Invalid JWT signature");
469
+ }
470
+ let payload;
471
+ try {
472
+ const payloadJson = Buffer.from(payloadB64, "base64url").toString("utf8");
473
+ payload = JSON.parse(payloadJson);
474
+ } catch (err) {
475
+ throw new Error("Invalid JWT payload JSON");
476
+ }
477
+ if (payload.exp && typeof payload.exp === "number") {
478
+ const currentTime = Math.floor(Date.now() / 1e3);
479
+ if (payload.exp < currentTime) {
480
+ throw new Error("JWT has expired");
481
+ }
482
+ }
483
+ return payload;
484
+ }
485
+ };
486
+ var AuthModule = class {
487
+ constructor(client) {
488
+ this.admin = new AdminAuth(client);
489
+ }
490
+ };
491
+
244
492
  // src/index.ts
245
493
  var ThinkingDifferently = class {
246
494
  constructor(config) {
247
495
  console.log("[ThinkingDifferently SDK] Initializing SDK");
248
- this.client = new TDClient(config.apiKey);
496
+ this.client = new TDClient(config.apiKey, config.securityKey, config.publicKey);
497
+ this.auth = new AuthModule(this.client);
498
+ }
499
+ /**
500
+ * Dynamically update the security key (useful for backend projects).
501
+ */
502
+ setSecurityKey(key) {
503
+ this.client.setSecurityKey(key);
504
+ }
505
+ /**
506
+ * Dynamically update the public key used for offline verification.
507
+ */
508
+ setPublicKey(key) {
509
+ this.client.setPublicKey(key);
510
+ }
511
+ /**
512
+ * Helper to verify Ed25519 admin JWT token offline.
513
+ */
514
+ verifyJwt(token, publicKey) {
515
+ return this.auth.admin.verifyJwt(token, publicKey);
249
516
  }
250
517
  collection(name) {
251
518
  return new QueryBuilder(
@@ -279,7 +546,7 @@ var ThinkingDifferently = class {
279
546
  for (const [k, v] of payload.entries()) {
280
547
  console.log(k, v);
281
548
  }
282
- return this.client.request(
549
+ return this.client.sendDataRequest(
283
550
  "POST",
284
551
  payload
285
552
  );
@@ -289,7 +556,7 @@ var ThinkingDifferently = class {
289
556
  key,
290
557
  data
291
558
  });
292
- const response = await this.client.request(
559
+ const response = await this.client.sendDataRequest(
293
560
  "POST",
294
561
  {
295
562
  key,
@@ -310,7 +577,7 @@ var ThinkingDifferently = class {
310
577
  console.log("[SDK] Document ID:", id);
311
578
  console.log("[SDK] Update Data:", data);
312
579
  try {
313
- const response = await this.client.request(
580
+ const response = await this.client.sendDataRequest(
314
581
  "PATCH",
315
582
  {
316
583
  key,
@@ -331,7 +598,7 @@ var ThinkingDifferently = class {
331
598
  console.log("[SDK] Collection Key:", key);
332
599
  console.log("[SDK] Document ID:", id);
333
600
  try {
334
- const response = await this.client.request(
601
+ const response = await this.client.sendDataRequest(
335
602
  "DELETE",
336
603
  {
337
604
  key,
@@ -348,6 +615,8 @@ var ThinkingDifferently = class {
348
615
  }
349
616
  };
350
617
  export {
618
+ AdminAuth,
619
+ AuthModule,
351
620
  TDClient,
352
621
  ThinkingDifferently
353
622
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkingdifferently/core",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Official SDK for Thinking Differently API",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -21,7 +21,8 @@
21
21
  "author": "Krrish Savlani",
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
- "axios": "^1.16.0"
24
+ "axios": "^1.16.0",
25
+ "crypto-js": "^4.2.0"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@types/node": "^25.6.2",