krisspy-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -0
- package/dist/index.d.mts +605 -0
- package/dist/index.d.ts +605 -0
- package/dist/index.js +921 -0
- package/dist/index.mjs +890 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,921 @@
|
|
|
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
|
+
HttpClient: () => HttpClient,
|
|
24
|
+
KrisspyAuth: () => KrisspyAuth,
|
|
25
|
+
KrisspyClient: () => KrisspyClient,
|
|
26
|
+
QueryBuilder: () => QueryBuilder,
|
|
27
|
+
createClient: () => createClient
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/http.ts
|
|
32
|
+
var HttpClient = class {
|
|
33
|
+
constructor(options) {
|
|
34
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
35
|
+
this.headers = {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
...options.headers
|
|
38
|
+
};
|
|
39
|
+
this.debug = options.debug ?? false;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Set authorization header
|
|
43
|
+
*/
|
|
44
|
+
setAuth(token) {
|
|
45
|
+
this.headers["Authorization"] = `Bearer ${token}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Remove authorization header
|
|
49
|
+
*/
|
|
50
|
+
clearAuth() {
|
|
51
|
+
delete this.headers["Authorization"];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Update headers
|
|
55
|
+
*/
|
|
56
|
+
setHeaders(headers) {
|
|
57
|
+
this.headers = { ...this.headers, ...headers };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build query string from params
|
|
61
|
+
*/
|
|
62
|
+
buildQueryString(params) {
|
|
63
|
+
const parts = [];
|
|
64
|
+
for (const [key, value] of Object.entries(params)) {
|
|
65
|
+
if (value === void 0 || value === null) continue;
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.join(","))}`);
|
|
68
|
+
} else {
|
|
69
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Make HTTP request
|
|
76
|
+
*/
|
|
77
|
+
async request(method, path, options) {
|
|
78
|
+
const url = `${this.baseUrl}${path}${options?.params ? this.buildQueryString(options.params) : ""}`;
|
|
79
|
+
const requestHeaders = {
|
|
80
|
+
...this.headers,
|
|
81
|
+
...options?.headers
|
|
82
|
+
};
|
|
83
|
+
if (this.debug) {
|
|
84
|
+
console.log(`[Krisspy SDK] ${method} ${url}`);
|
|
85
|
+
if (options?.body) {
|
|
86
|
+
console.log("[Krisspy SDK] Body:", JSON.stringify(options.body, null, 2));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch(url, {
|
|
91
|
+
method,
|
|
92
|
+
headers: requestHeaders,
|
|
93
|
+
body: options?.body ? JSON.stringify(options.body) : void 0
|
|
94
|
+
});
|
|
95
|
+
const contentType = response.headers.get("content-type");
|
|
96
|
+
let data = null;
|
|
97
|
+
if (contentType?.includes("application/json")) {
|
|
98
|
+
data = await response.json();
|
|
99
|
+
} else {
|
|
100
|
+
data = await response.text();
|
|
101
|
+
}
|
|
102
|
+
if (this.debug) {
|
|
103
|
+
console.log(`[Krisspy SDK] Response ${response.status}:`, data);
|
|
104
|
+
}
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const error = {
|
|
107
|
+
message: data?.error || data?.message || "Request failed",
|
|
108
|
+
code: data?.code,
|
|
109
|
+
details: data?.details,
|
|
110
|
+
hint: data?.hint,
|
|
111
|
+
status: response.status
|
|
112
|
+
};
|
|
113
|
+
return { data: null, error, status: response.status };
|
|
114
|
+
}
|
|
115
|
+
return { data, error: null, status: response.status };
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (this.debug) {
|
|
118
|
+
console.error("[Krisspy SDK] Error:", err);
|
|
119
|
+
}
|
|
120
|
+
const error = {
|
|
121
|
+
message: err.message || "Network error",
|
|
122
|
+
code: "NETWORK_ERROR",
|
|
123
|
+
status: 0
|
|
124
|
+
};
|
|
125
|
+
return { data: null, error, status: 0 };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* GET request
|
|
130
|
+
*/
|
|
131
|
+
async get(path, params) {
|
|
132
|
+
return this.request("GET", path, { params });
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* POST request
|
|
136
|
+
*/
|
|
137
|
+
async post(path, body, params) {
|
|
138
|
+
return this.request("POST", path, { body, params });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* PATCH request
|
|
142
|
+
*/
|
|
143
|
+
async patch(path, body, params) {
|
|
144
|
+
return this.request("PATCH", path, { body, params });
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* PUT request
|
|
148
|
+
*/
|
|
149
|
+
async put(path, body, params) {
|
|
150
|
+
return this.request("PUT", path, { body, params });
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* DELETE request
|
|
154
|
+
*/
|
|
155
|
+
async delete(path, params) {
|
|
156
|
+
return this.request("DELETE", path, { params });
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/auth.ts
|
|
161
|
+
var STORAGE_KEY = "krisspy-auth-session";
|
|
162
|
+
var KrisspyAuth = class {
|
|
163
|
+
constructor(http, backendId) {
|
|
164
|
+
this.currentSession = null;
|
|
165
|
+
this.currentUser = null;
|
|
166
|
+
this.listeners = [];
|
|
167
|
+
this.http = http;
|
|
168
|
+
this.backendId = backendId;
|
|
169
|
+
this.restoreSession();
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get current session from storage
|
|
173
|
+
*/
|
|
174
|
+
restoreSession() {
|
|
175
|
+
if (typeof window === "undefined") return;
|
|
176
|
+
try {
|
|
177
|
+
const stored = localStorage.getItem(`${STORAGE_KEY}-${this.backendId}`);
|
|
178
|
+
if (stored) {
|
|
179
|
+
const session = JSON.parse(stored);
|
|
180
|
+
if (session.expires_at && Date.now() > session.expires_at * 1e3) {
|
|
181
|
+
this.clearSession();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
this.setSession(session);
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.warn("[Krisspy Auth] Failed to restore session:", err);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Store session
|
|
192
|
+
*/
|
|
193
|
+
saveSession(session) {
|
|
194
|
+
if (typeof window === "undefined") return;
|
|
195
|
+
try {
|
|
196
|
+
localStorage.setItem(`${STORAGE_KEY}-${this.backendId}`, JSON.stringify(session));
|
|
197
|
+
} catch (err) {
|
|
198
|
+
console.warn("[Krisspy Auth] Failed to save session:", err);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Clear session from storage
|
|
203
|
+
*/
|
|
204
|
+
clearSession() {
|
|
205
|
+
this.currentSession = null;
|
|
206
|
+
this.currentUser = null;
|
|
207
|
+
this.http.clearAuth();
|
|
208
|
+
if (this.refreshInterval) {
|
|
209
|
+
clearInterval(this.refreshInterval);
|
|
210
|
+
this.refreshInterval = void 0;
|
|
211
|
+
}
|
|
212
|
+
if (typeof window !== "undefined") {
|
|
213
|
+
localStorage.removeItem(`${STORAGE_KEY}-${this.backendId}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Set current session
|
|
218
|
+
*/
|
|
219
|
+
setSession(session) {
|
|
220
|
+
this.currentSession = session;
|
|
221
|
+
this.currentUser = session.user;
|
|
222
|
+
this.http.setAuth(session.access_token);
|
|
223
|
+
this.saveSession(session);
|
|
224
|
+
this.setupAutoRefresh(session);
|
|
225
|
+
this.notifyListeners("SIGNED_IN");
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Setup auto-refresh of token
|
|
229
|
+
*/
|
|
230
|
+
setupAutoRefresh(session) {
|
|
231
|
+
if (this.refreshInterval) {
|
|
232
|
+
clearInterval(this.refreshInterval);
|
|
233
|
+
}
|
|
234
|
+
if (!session.refresh_token || !session.expires_in) return;
|
|
235
|
+
const refreshIn = (session.expires_in - 300) * 1e3;
|
|
236
|
+
if (refreshIn > 0) {
|
|
237
|
+
this.refreshInterval = setInterval(() => {
|
|
238
|
+
this.refreshSession();
|
|
239
|
+
}, refreshIn);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Notify auth state change listeners
|
|
244
|
+
*/
|
|
245
|
+
notifyListeners(event) {
|
|
246
|
+
for (const listener of this.listeners) {
|
|
247
|
+
try {
|
|
248
|
+
listener(event);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error("[Krisspy Auth] Listener error:", err);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Sign up with email and password
|
|
256
|
+
*/
|
|
257
|
+
async signUp(credentials) {
|
|
258
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/auth/signup`;
|
|
259
|
+
const response = await this.http.post(path, {
|
|
260
|
+
email: credentials.email,
|
|
261
|
+
password: credentials.password,
|
|
262
|
+
metadata: credentials.metadata
|
|
263
|
+
});
|
|
264
|
+
if (response.error) {
|
|
265
|
+
return {
|
|
266
|
+
data: { user: null, session: null },
|
|
267
|
+
error: response.error
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (response.data?.session) {
|
|
271
|
+
this.setSession(response.data.session);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
data: {
|
|
275
|
+
user: response.data?.user ?? null,
|
|
276
|
+
session: response.data?.session ?? null
|
|
277
|
+
},
|
|
278
|
+
error: null
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Sign in with email and password
|
|
283
|
+
*/
|
|
284
|
+
async signInWithPassword(credentials) {
|
|
285
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/auth/login`;
|
|
286
|
+
const response = await this.http.post(path, {
|
|
287
|
+
email: credentials.email,
|
|
288
|
+
password: credentials.password
|
|
289
|
+
});
|
|
290
|
+
if (response.error) {
|
|
291
|
+
return {
|
|
292
|
+
data: { user: null, session: null },
|
|
293
|
+
error: response.error
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (response.data?.session) {
|
|
297
|
+
this.setSession(response.data.session);
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
data: {
|
|
301
|
+
user: response.data?.user ?? null,
|
|
302
|
+
session: response.data?.session ?? null
|
|
303
|
+
},
|
|
304
|
+
error: null
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Sign in with OAuth provider
|
|
309
|
+
*/
|
|
310
|
+
async signInWithOAuth(options) {
|
|
311
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/auth/oauth/${options.provider}`;
|
|
312
|
+
const response = await this.http.post(path, {
|
|
313
|
+
redirect_to: options.redirectTo
|
|
314
|
+
});
|
|
315
|
+
if (response.error) {
|
|
316
|
+
return { data: null, error: response.error };
|
|
317
|
+
}
|
|
318
|
+
if (typeof window !== "undefined" && response.data?.url) {
|
|
319
|
+
window.location.href = response.data.url;
|
|
320
|
+
}
|
|
321
|
+
return { data: response.data, error: null };
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Sign out
|
|
325
|
+
*/
|
|
326
|
+
async signOut() {
|
|
327
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/auth/logout`;
|
|
328
|
+
await this.http.post(path);
|
|
329
|
+
this.clearSession();
|
|
330
|
+
this.notifyListeners("SIGNED_OUT");
|
|
331
|
+
return { error: null };
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Refresh the session token
|
|
335
|
+
*/
|
|
336
|
+
async refreshSession() {
|
|
337
|
+
if (!this.currentSession?.refresh_token) {
|
|
338
|
+
return {
|
|
339
|
+
data: { user: null, session: null },
|
|
340
|
+
error: { message: "No refresh token available" }
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/auth/refresh`;
|
|
344
|
+
const response = await this.http.post(path, {
|
|
345
|
+
refresh_token: this.currentSession.refresh_token
|
|
346
|
+
});
|
|
347
|
+
if (response.error) {
|
|
348
|
+
this.clearSession();
|
|
349
|
+
this.notifyListeners("SIGNED_OUT");
|
|
350
|
+
return {
|
|
351
|
+
data: { user: null, session: null },
|
|
352
|
+
error: response.error
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
if (response.data?.session) {
|
|
356
|
+
this.setSession(response.data.session);
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
data: {
|
|
360
|
+
user: response.data?.session?.user ?? null,
|
|
361
|
+
session: response.data?.session ?? null
|
|
362
|
+
},
|
|
363
|
+
error: null
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Get current session
|
|
368
|
+
*/
|
|
369
|
+
async getSession() {
|
|
370
|
+
return {
|
|
371
|
+
data: { session: this.currentSession },
|
|
372
|
+
error: null
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Get current user
|
|
377
|
+
*/
|
|
378
|
+
async getUser() {
|
|
379
|
+
return {
|
|
380
|
+
data: { user: this.currentUser },
|
|
381
|
+
error: null
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get current user synchronously
|
|
386
|
+
*/
|
|
387
|
+
user() {
|
|
388
|
+
return this.currentUser;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Get current session synchronously
|
|
392
|
+
*/
|
|
393
|
+
session() {
|
|
394
|
+
return this.currentSession;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Update user metadata
|
|
398
|
+
*/
|
|
399
|
+
async updateUser(data) {
|
|
400
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/auth/user`;
|
|
401
|
+
const response = await this.http.patch(path, data);
|
|
402
|
+
if (response.error) {
|
|
403
|
+
return { data: { user: null }, error: response.error };
|
|
404
|
+
}
|
|
405
|
+
if (response.data?.user) {
|
|
406
|
+
this.currentUser = response.data.user;
|
|
407
|
+
if (this.currentSession) {
|
|
408
|
+
this.currentSession.user = response.data.user;
|
|
409
|
+
this.saveSession(this.currentSession);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return { data: { user: response.data?.user ?? null }, error: null };
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Request password reset
|
|
416
|
+
*/
|
|
417
|
+
async resetPasswordForEmail(email) {
|
|
418
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/auth/reset-password`;
|
|
419
|
+
const response = await this.http.post(path, { email });
|
|
420
|
+
return { error: response.error };
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Listen for auth state changes
|
|
424
|
+
*/
|
|
425
|
+
onAuthStateChange(callback) {
|
|
426
|
+
this.listeners.push(callback);
|
|
427
|
+
if (this.currentSession) {
|
|
428
|
+
callback("SIGNED_IN");
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
unsubscribe: () => {
|
|
432
|
+
const index = this.listeners.indexOf(callback);
|
|
433
|
+
if (index > -1) {
|
|
434
|
+
this.listeners.splice(index, 1);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Set session from external source (e.g., OAuth callback)
|
|
441
|
+
*/
|
|
442
|
+
async setSessionFromUrl() {
|
|
443
|
+
if (typeof window === "undefined") {
|
|
444
|
+
return { data: { user: null, session: null }, error: { message: "Not in browser" } };
|
|
445
|
+
}
|
|
446
|
+
const hash = window.location.hash.substring(1);
|
|
447
|
+
const params = new URLSearchParams(hash);
|
|
448
|
+
const accessToken = params.get("access_token");
|
|
449
|
+
const refreshToken = params.get("refresh_token");
|
|
450
|
+
if (!accessToken) {
|
|
451
|
+
return { data: { user: null, session: null }, error: { message: "No access token in URL" } };
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
const payload = JSON.parse(atob(accessToken.split(".")[1]));
|
|
455
|
+
const session = {
|
|
456
|
+
access_token: accessToken,
|
|
457
|
+
refresh_token: refreshToken ?? void 0,
|
|
458
|
+
expires_at: payload.exp,
|
|
459
|
+
expires_in: payload.exp ? payload.exp - Math.floor(Date.now() / 1e3) : 3600,
|
|
460
|
+
user: {
|
|
461
|
+
id: payload.sub,
|
|
462
|
+
email: payload.email,
|
|
463
|
+
metadata: payload.metadata
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
this.setSession(session);
|
|
467
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
468
|
+
return {
|
|
469
|
+
data: { user: session.user, session },
|
|
470
|
+
error: null
|
|
471
|
+
};
|
|
472
|
+
} catch (err) {
|
|
473
|
+
return {
|
|
474
|
+
data: { user: null, session: null },
|
|
475
|
+
error: { message: "Failed to parse token" }
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// src/query-builder.ts
|
|
482
|
+
var QueryBuilder = class {
|
|
483
|
+
constructor(http, backendId, tableName, useRLS = true) {
|
|
484
|
+
this.queryParams = {};
|
|
485
|
+
this.selectColumns = [];
|
|
486
|
+
this.orderClauses = [];
|
|
487
|
+
this.isSingle = false;
|
|
488
|
+
this.http = http;
|
|
489
|
+
this.backendId = backendId;
|
|
490
|
+
this.tableName = tableName;
|
|
491
|
+
this.useRLS = useRLS;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Get the base path for data endpoints
|
|
495
|
+
* Uses /rls/data for RLS-enabled queries (requires auth)
|
|
496
|
+
* Uses /data for legacy queries (admin/owner only)
|
|
497
|
+
*/
|
|
498
|
+
getBasePath() {
|
|
499
|
+
if (this.useRLS) {
|
|
500
|
+
return `/api/v1/cloud-backends/${this.backendId}/rls/data/${this.tableName}`;
|
|
501
|
+
}
|
|
502
|
+
return `/api/v1/cloud-backends/${this.backendId}/data/${this.tableName}`;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Select specific columns
|
|
506
|
+
* @example .select('id, name, price')
|
|
507
|
+
* @example .select('*')
|
|
508
|
+
*/
|
|
509
|
+
select(columns = "*") {
|
|
510
|
+
this.selectColumns = columns.split(",").map((c) => c.trim());
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Filter: equals
|
|
515
|
+
* @example .eq('id', 123)
|
|
516
|
+
*/
|
|
517
|
+
eq(column, value) {
|
|
518
|
+
this.queryParams[column] = `eq.${value}`;
|
|
519
|
+
return this;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Filter: not equals
|
|
523
|
+
* @example .neq('status', 'deleted')
|
|
524
|
+
*/
|
|
525
|
+
neq(column, value) {
|
|
526
|
+
this.queryParams[column] = `neq.${value}`;
|
|
527
|
+
return this;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Filter: greater than
|
|
531
|
+
* @example .gt('price', 100)
|
|
532
|
+
*/
|
|
533
|
+
gt(column, value) {
|
|
534
|
+
this.queryParams[column] = `gt.${value}`;
|
|
535
|
+
return this;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Filter: greater than or equal
|
|
539
|
+
* @example .gte('age', 18)
|
|
540
|
+
*/
|
|
541
|
+
gte(column, value) {
|
|
542
|
+
this.queryParams[column] = `gte.${value}`;
|
|
543
|
+
return this;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Filter: less than
|
|
547
|
+
* @example .lt('stock', 10)
|
|
548
|
+
*/
|
|
549
|
+
lt(column, value) {
|
|
550
|
+
this.queryParams[column] = `lt.${value}`;
|
|
551
|
+
return this;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Filter: less than or equal
|
|
555
|
+
* @example .lte('price', 1000)
|
|
556
|
+
*/
|
|
557
|
+
lte(column, value) {
|
|
558
|
+
this.queryParams[column] = `lte.${value}`;
|
|
559
|
+
return this;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Filter: pattern matching (case sensitive)
|
|
563
|
+
* @example .like('name', '%iPhone%')
|
|
564
|
+
*/
|
|
565
|
+
like(column, pattern) {
|
|
566
|
+
this.queryParams[column] = `like.${pattern}`;
|
|
567
|
+
return this;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Filter: pattern matching (case insensitive)
|
|
571
|
+
* @example .ilike('name', '%iphone%')
|
|
572
|
+
*/
|
|
573
|
+
ilike(column, pattern) {
|
|
574
|
+
this.queryParams[column] = `ilike.${pattern}`;
|
|
575
|
+
return this;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Filter: value in list
|
|
579
|
+
* @example .in('status', ['active', 'pending'])
|
|
580
|
+
*/
|
|
581
|
+
in(column, values) {
|
|
582
|
+
this.queryParams[column] = `in.${values.join(",")}`;
|
|
583
|
+
return this;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Filter: is null/true/false
|
|
587
|
+
* @example .is('deleted_at', null)
|
|
588
|
+
*/
|
|
589
|
+
is(column, value) {
|
|
590
|
+
const strValue = value === null ? "null" : value ? "true" : "false";
|
|
591
|
+
this.queryParams[column] = `is.${strValue}`;
|
|
592
|
+
return this;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Order by column
|
|
596
|
+
* @example .order('created_at', { ascending: false })
|
|
597
|
+
*/
|
|
598
|
+
order(column, options) {
|
|
599
|
+
const direction = options?.ascending === false ? "desc" : "asc";
|
|
600
|
+
this.orderClauses.push(`${column}.${direction}`);
|
|
601
|
+
return this;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Limit number of rows
|
|
605
|
+
* @example .limit(10)
|
|
606
|
+
*/
|
|
607
|
+
limit(count) {
|
|
608
|
+
this.limitValue = count;
|
|
609
|
+
return this;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Skip rows (for pagination)
|
|
613
|
+
* @example .range(0, 9) // First 10 rows
|
|
614
|
+
*/
|
|
615
|
+
range(from, to) {
|
|
616
|
+
this.offsetValue = from;
|
|
617
|
+
this.limitValue = to - from + 1;
|
|
618
|
+
return this;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Return single row (throws if multiple or none)
|
|
622
|
+
*/
|
|
623
|
+
single() {
|
|
624
|
+
this.isSingle = true;
|
|
625
|
+
this.limitValue = 1;
|
|
626
|
+
return this;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Return first row or null
|
|
630
|
+
*/
|
|
631
|
+
maybeSingle() {
|
|
632
|
+
this.isSingle = true;
|
|
633
|
+
this.limitValue = 1;
|
|
634
|
+
return this;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Build query params for the request
|
|
638
|
+
*/
|
|
639
|
+
buildParams() {
|
|
640
|
+
const params = { ...this.queryParams };
|
|
641
|
+
if (this.selectColumns.length > 0 && !this.selectColumns.includes("*")) {
|
|
642
|
+
params["select"] = this.selectColumns.join(",");
|
|
643
|
+
}
|
|
644
|
+
if (this.orderClauses.length > 0) {
|
|
645
|
+
params["order"] = this.orderClauses.join(",");
|
|
646
|
+
}
|
|
647
|
+
if (this.limitValue !== void 0) {
|
|
648
|
+
params["limit"] = String(this.limitValue);
|
|
649
|
+
}
|
|
650
|
+
if (this.offsetValue !== void 0) {
|
|
651
|
+
params["offset"] = String(this.offsetValue);
|
|
652
|
+
}
|
|
653
|
+
return params;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Execute SELECT query
|
|
657
|
+
*/
|
|
658
|
+
async then(resolve, reject) {
|
|
659
|
+
try {
|
|
660
|
+
const result = await this.execute();
|
|
661
|
+
resolve(result);
|
|
662
|
+
} catch (err) {
|
|
663
|
+
if (reject) reject(err);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Execute the query
|
|
668
|
+
*/
|
|
669
|
+
async execute() {
|
|
670
|
+
const path = this.getBasePath();
|
|
671
|
+
const params = this.buildParams();
|
|
672
|
+
const response = await this.http.get(path, params);
|
|
673
|
+
if (response.error) {
|
|
674
|
+
if (this.isSingle) {
|
|
675
|
+
return { data: null, error: response.error };
|
|
676
|
+
}
|
|
677
|
+
return { data: null, error: response.error, count: null };
|
|
678
|
+
}
|
|
679
|
+
if (this.isSingle) {
|
|
680
|
+
const item = response.data?.data?.[0] ?? null;
|
|
681
|
+
return { data: item, error: null };
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
data: response.data?.data ?? [],
|
|
685
|
+
error: null,
|
|
686
|
+
count: response.data?.count ?? 0
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Insert rows
|
|
691
|
+
* @example .insert({ name: 'iPhone', price: 999 })
|
|
692
|
+
* @example .insert([{ name: 'iPhone' }, { name: 'iPad' }])
|
|
693
|
+
*/
|
|
694
|
+
async insert(data) {
|
|
695
|
+
const path = this.getBasePath();
|
|
696
|
+
const response = await this.http.post(path, data);
|
|
697
|
+
if (response.error) {
|
|
698
|
+
return { data: null, error: response.error, count: 0 };
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
data: response.data?.data ?? [],
|
|
702
|
+
error: null,
|
|
703
|
+
count: response.data?.count ?? 0
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Update rows (requires filters)
|
|
708
|
+
* @example .update({ price: 899 }).eq('id', 123)
|
|
709
|
+
*/
|
|
710
|
+
async update(data) {
|
|
711
|
+
const path = this.getBasePath();
|
|
712
|
+
const params = this.buildParams();
|
|
713
|
+
delete params["select"];
|
|
714
|
+
delete params["order"];
|
|
715
|
+
delete params["limit"];
|
|
716
|
+
delete params["offset"];
|
|
717
|
+
const response = await this.http.patch(path, data, params);
|
|
718
|
+
if (response.error) {
|
|
719
|
+
return { data: null, error: response.error, count: 0 };
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
data: response.data?.data ?? [],
|
|
723
|
+
error: null,
|
|
724
|
+
count: response.data?.count ?? 0
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Delete rows (requires filters)
|
|
729
|
+
* @example .delete().eq('id', 123)
|
|
730
|
+
*/
|
|
731
|
+
async delete() {
|
|
732
|
+
const path = this.getBasePath();
|
|
733
|
+
const params = this.buildParams();
|
|
734
|
+
delete params["select"];
|
|
735
|
+
delete params["order"];
|
|
736
|
+
delete params["limit"];
|
|
737
|
+
delete params["offset"];
|
|
738
|
+
const response = await this.http.delete(path, params);
|
|
739
|
+
if (response.error) {
|
|
740
|
+
return { data: null, error: response.error, count: 0 };
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
data: response.data?.data ?? [],
|
|
744
|
+
error: null,
|
|
745
|
+
count: response.data?.count ?? 0
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Upsert rows (insert or update on conflict)
|
|
750
|
+
* @example .upsert({ id: 1, name: 'iPhone' })
|
|
751
|
+
*/
|
|
752
|
+
async upsert(data, options) {
|
|
753
|
+
return this.insert(data);
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// src/client.ts
|
|
758
|
+
var KrisspyClient = class {
|
|
759
|
+
constructor(options) {
|
|
760
|
+
const baseUrl = options.url || "https://api.krisspy.ai";
|
|
761
|
+
this.backendId = options.backendId;
|
|
762
|
+
this.useRLS = options.useRLS !== false;
|
|
763
|
+
this.http = new HttpClient({
|
|
764
|
+
baseUrl,
|
|
765
|
+
headers: {
|
|
766
|
+
...options.headers,
|
|
767
|
+
...options.apiKey ? { "Authorization": `Bearer ${options.apiKey}` } : {}
|
|
768
|
+
},
|
|
769
|
+
debug: options.debug
|
|
770
|
+
});
|
|
771
|
+
this._auth = new KrisspyAuth(this.http, this.backendId);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Auth module for user authentication
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* // Sign up
|
|
778
|
+
* const { data, error } = await krisspy.auth.signUp({
|
|
779
|
+
* email: 'user@example.com',
|
|
780
|
+
* password: 'secret123'
|
|
781
|
+
* })
|
|
782
|
+
*
|
|
783
|
+
* // Sign in
|
|
784
|
+
* const { data, error } = await krisspy.auth.signInWithPassword({
|
|
785
|
+
* email: 'user@example.com',
|
|
786
|
+
* password: 'secret123'
|
|
787
|
+
* })
|
|
788
|
+
*
|
|
789
|
+
* // Get current user
|
|
790
|
+
* const user = krisspy.auth.user()
|
|
791
|
+
*
|
|
792
|
+
* // Sign out
|
|
793
|
+
* await krisspy.auth.signOut()
|
|
794
|
+
*/
|
|
795
|
+
get auth() {
|
|
796
|
+
return this._auth;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Create a query builder for a table
|
|
800
|
+
*
|
|
801
|
+
* @example
|
|
802
|
+
* // Select all rows
|
|
803
|
+
* const { data, error } = await krisspy.from('products').select('*')
|
|
804
|
+
*
|
|
805
|
+
* // Select with filters
|
|
806
|
+
* const { data, error } = await krisspy
|
|
807
|
+
* .from('products')
|
|
808
|
+
* .select('id, name, price')
|
|
809
|
+
* .eq('active', true)
|
|
810
|
+
* .order('created_at', { ascending: false })
|
|
811
|
+
* .limit(10)
|
|
812
|
+
*
|
|
813
|
+
* // Insert
|
|
814
|
+
* const { data, error } = await krisspy
|
|
815
|
+
* .from('products')
|
|
816
|
+
* .insert({ name: 'iPhone', price: 999 })
|
|
817
|
+
*
|
|
818
|
+
* // Update
|
|
819
|
+
* const { data, error } = await krisspy
|
|
820
|
+
* .from('products')
|
|
821
|
+
* .update({ price: 899 })
|
|
822
|
+
* .eq('id', 123)
|
|
823
|
+
*
|
|
824
|
+
* // Delete
|
|
825
|
+
* const { data, error } = await krisspy
|
|
826
|
+
* .from('products')
|
|
827
|
+
* .delete()
|
|
828
|
+
* .eq('id', 123)
|
|
829
|
+
*/
|
|
830
|
+
from(table) {
|
|
831
|
+
return new QueryBuilder(this.http, this.backendId, table, this.useRLS);
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Execute raw SQL query
|
|
835
|
+
*
|
|
836
|
+
* @example
|
|
837
|
+
* const { data, error } = await krisspy.rpc('SELECT * FROM products WHERE price > $1', [100])
|
|
838
|
+
*/
|
|
839
|
+
async rpc(sql, params) {
|
|
840
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/sql`;
|
|
841
|
+
const response = await this.http.post(path, { sql, params });
|
|
842
|
+
if (response.error) {
|
|
843
|
+
return { data: null, error: response.error };
|
|
844
|
+
}
|
|
845
|
+
return { data: response.data?.result ?? null, error: null };
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Invoke a serverless function
|
|
849
|
+
*
|
|
850
|
+
* @example
|
|
851
|
+
* const { data, error } = await krisspy.functions.invoke('hello-world', {
|
|
852
|
+
* body: { name: 'John' }
|
|
853
|
+
* })
|
|
854
|
+
*/
|
|
855
|
+
get functions() {
|
|
856
|
+
return {
|
|
857
|
+
invoke: async (functionName, options) => {
|
|
858
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/functions/${functionName}/invoke`;
|
|
859
|
+
const response = await this.http.post(path, options?.body, void 0);
|
|
860
|
+
if (response.error) {
|
|
861
|
+
return { data: null, error: response.error };
|
|
862
|
+
}
|
|
863
|
+
return { data: response.data, error: null };
|
|
864
|
+
},
|
|
865
|
+
list: async () => {
|
|
866
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/functions`;
|
|
867
|
+
const response = await this.http.get(path);
|
|
868
|
+
if (response.error) {
|
|
869
|
+
return { data: null, error: response.error };
|
|
870
|
+
}
|
|
871
|
+
return { data: response.data?.functions ?? [], error: null };
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Get backend info
|
|
877
|
+
*/
|
|
878
|
+
async getBackendInfo() {
|
|
879
|
+
const path = `/api/v1/cloud-backends/${this.backendId}`;
|
|
880
|
+
const response = await this.http.get(path);
|
|
881
|
+
if (response.error) {
|
|
882
|
+
return { data: null, error: response.error };
|
|
883
|
+
}
|
|
884
|
+
return { data: response.data, error: null };
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* List tables in the backend
|
|
888
|
+
*/
|
|
889
|
+
async listTables() {
|
|
890
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/tables`;
|
|
891
|
+
const response = await this.http.get(path);
|
|
892
|
+
if (response.error) {
|
|
893
|
+
return { data: null, error: response.error };
|
|
894
|
+
}
|
|
895
|
+
return { data: response.data?.tables ?? [], error: null };
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Get table schema (columns)
|
|
899
|
+
*/
|
|
900
|
+
async getTableSchema(table) {
|
|
901
|
+
const path = `/api/v1/cloud-backends/${this.backendId}/tables/${table}`;
|
|
902
|
+
const response = await this.http.get(path);
|
|
903
|
+
if (response.error) {
|
|
904
|
+
return { data: null, error: response.error };
|
|
905
|
+
}
|
|
906
|
+
return { data: response.data?.columns ?? [], error: null };
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
// src/index.ts
|
|
911
|
+
function createClient(options) {
|
|
912
|
+
return new KrisspyClient(options);
|
|
913
|
+
}
|
|
914
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
915
|
+
0 && (module.exports = {
|
|
916
|
+
HttpClient,
|
|
917
|
+
KrisspyAuth,
|
|
918
|
+
KrisspyClient,
|
|
919
|
+
QueryBuilder,
|
|
920
|
+
createClient
|
|
921
|
+
});
|