@scarletdb/sdk 0.1.2 → 0.2.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 +55 -65
- package/dist/index.d.mts +50 -73
- package/dist/index.d.ts +50 -73
- package/dist/index.js +234 -168
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +234 -169
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -79,19 +79,23 @@ var HttpClient = class {
|
|
|
79
79
|
url += `?${queryString}`;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
const authHeaders = await this.config.getAuthHeaders({ method, url });
|
|
82
83
|
const requestHeaders = {
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
"Accept": "application/json",
|
|
84
|
+
Accept: "application/json",
|
|
85
|
+
...authHeaders,
|
|
86
86
|
...headers
|
|
87
87
|
};
|
|
88
|
+
const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
|
|
89
|
+
if (body !== void 0 && !isFormData) {
|
|
90
|
+
requestHeaders["Content-Type"] = requestHeaders["Content-Type"] || "application/json";
|
|
91
|
+
}
|
|
88
92
|
const controller = new AbortController();
|
|
89
93
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
90
94
|
try {
|
|
91
95
|
const response = await fetch(url, {
|
|
92
96
|
method,
|
|
93
97
|
headers: requestHeaders,
|
|
94
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
98
|
+
body: body ? isFormData ? body : JSON.stringify(body) : void 0,
|
|
95
99
|
signal: controller.signal
|
|
96
100
|
});
|
|
97
101
|
clearTimeout(timeoutId);
|
|
@@ -139,54 +143,29 @@ var HttpClient = class {
|
|
|
139
143
|
}
|
|
140
144
|
}
|
|
141
145
|
// Convenience methods
|
|
142
|
-
async get(path, params) {
|
|
143
|
-
const response = await this.request({ method: "GET", path, params });
|
|
146
|
+
async get(path, params, headers) {
|
|
147
|
+
const response = await this.request({ method: "GET", path, params, headers });
|
|
144
148
|
return response.data;
|
|
145
149
|
}
|
|
146
|
-
async post(path, body) {
|
|
147
|
-
const response = await this.request({ method: "POST", path, body });
|
|
150
|
+
async post(path, body, headers) {
|
|
151
|
+
const response = await this.request({ method: "POST", path, body, headers });
|
|
148
152
|
return response.data;
|
|
149
153
|
}
|
|
150
|
-
async patch(path, body) {
|
|
151
|
-
const response = await this.request({ method: "PATCH", path, body });
|
|
154
|
+
async patch(path, body, headers) {
|
|
155
|
+
const response = await this.request({ method: "PATCH", path, body, headers });
|
|
152
156
|
return response.data;
|
|
153
157
|
}
|
|
154
|
-
async put(path, body) {
|
|
155
|
-
const response = await this.request({ method: "PUT", path, body });
|
|
158
|
+
async put(path, body, headers) {
|
|
159
|
+
const response = await this.request({ method: "PUT", path, body, headers });
|
|
156
160
|
return response.data;
|
|
157
161
|
}
|
|
158
|
-
async delete(path, body) {
|
|
159
|
-
const response = await this.request({ method: "DELETE", path, body });
|
|
162
|
+
async delete(path, body, headers) {
|
|
163
|
+
const response = await this.request({ method: "DELETE", path, body, headers });
|
|
160
164
|
return response.data;
|
|
161
165
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
async upload(path, file, fileName) {
|
|
166
|
-
const formData = new FormData();
|
|
167
|
-
formData.append("file", file, fileName);
|
|
168
|
-
const controller = new AbortController();
|
|
169
|
-
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout * 2);
|
|
170
|
-
try {
|
|
171
|
-
const response = await fetch(`${this.config.baseUrl}${path}`, {
|
|
172
|
-
method: "POST",
|
|
173
|
-
headers: {
|
|
174
|
-
"Authorization": `Bearer ${this.config.apiKey}`
|
|
175
|
-
},
|
|
176
|
-
body: formData,
|
|
177
|
-
signal: controller.signal
|
|
178
|
-
});
|
|
179
|
-
clearTimeout(timeoutId);
|
|
180
|
-
const data = await response.json();
|
|
181
|
-
if (!response.ok) {
|
|
182
|
-
this.handleErrorResponse(response.status, data);
|
|
183
|
-
}
|
|
184
|
-
return data;
|
|
185
|
-
} catch (error) {
|
|
186
|
-
clearTimeout(timeoutId);
|
|
187
|
-
if (error instanceof ScarletError) throw error;
|
|
188
|
-
throw new NetworkError("Upload failed", error);
|
|
189
|
-
}
|
|
166
|
+
async postForm(path, form) {
|
|
167
|
+
const response = await this.request({ method: "POST", path, body: form });
|
|
168
|
+
return response.data;
|
|
190
169
|
}
|
|
191
170
|
};
|
|
192
171
|
|
|
@@ -207,37 +186,41 @@ var QueryBuilder = class {
|
|
|
207
186
|
this.client = client;
|
|
208
187
|
this.table = table;
|
|
209
188
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
189
|
+
_fields = [];
|
|
190
|
+
_filters = {};
|
|
191
|
+
_sort;
|
|
192
|
+
_page;
|
|
213
193
|
_limit;
|
|
214
|
-
_offset;
|
|
215
194
|
/**
|
|
216
195
|
* Select specific columns (default: all)
|
|
217
196
|
*/
|
|
218
197
|
select(...columns) {
|
|
219
|
-
this.
|
|
198
|
+
this._fields = columns;
|
|
220
199
|
return this;
|
|
221
200
|
}
|
|
222
201
|
/**
|
|
223
202
|
* Add where conditions
|
|
224
203
|
*/
|
|
225
204
|
where(conditions) {
|
|
226
|
-
this.
|
|
205
|
+
this._filters = { ...this._filters, ...conditions };
|
|
227
206
|
return this;
|
|
228
207
|
}
|
|
229
208
|
/**
|
|
230
209
|
* Add equality condition
|
|
231
210
|
*/
|
|
232
211
|
eq(column, value) {
|
|
233
|
-
this.
|
|
212
|
+
this._filters[column] = value;
|
|
234
213
|
return this;
|
|
235
214
|
}
|
|
236
215
|
/**
|
|
237
216
|
* Order results
|
|
238
217
|
*/
|
|
239
218
|
orderBy(column, direction = "asc") {
|
|
240
|
-
this.
|
|
219
|
+
this._sort = { column, direction };
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
page(page) {
|
|
223
|
+
this._page = page;
|
|
241
224
|
return this;
|
|
242
225
|
}
|
|
243
226
|
/**
|
|
@@ -247,31 +230,47 @@ var QueryBuilder = class {
|
|
|
247
230
|
this._limit = count;
|
|
248
231
|
return this;
|
|
249
232
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
233
|
+
buildQueryParams() {
|
|
234
|
+
const params = {};
|
|
235
|
+
const page = this._page ?? 1;
|
|
236
|
+
const limit = this._limit ?? void 0;
|
|
237
|
+
params.page = page;
|
|
238
|
+
if (limit !== void 0) params.limit = limit;
|
|
239
|
+
if (this._sort) {
|
|
240
|
+
params.sort = this._sort.direction === "desc" ? `-${this._sort.column}` : this._sort.column;
|
|
241
|
+
}
|
|
242
|
+
if (this._fields.length > 0) {
|
|
243
|
+
params.fields = this._fields.join(",");
|
|
244
|
+
}
|
|
245
|
+
for (const [field, value] of Object.entries(this._filters)) {
|
|
246
|
+
if (value === void 0) continue;
|
|
247
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
248
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
249
|
+
params[`filter[${field}][${op}]`] = String(opValue);
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
params[`filter[${field}]`] = String(value);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return params;
|
|
256
256
|
}
|
|
257
257
|
/**
|
|
258
258
|
* Execute SELECT query
|
|
259
259
|
*/
|
|
260
260
|
async execute() {
|
|
261
|
-
const params =
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (this._orderBy) {
|
|
265
|
-
params.sortBy = this._orderBy.column;
|
|
266
|
-
params.sortDir = this._orderBy.direction;
|
|
267
|
-
}
|
|
268
|
-
if (Object.keys(this._where).length > 0) {
|
|
269
|
-
params.search = JSON.stringify(this._where);
|
|
270
|
-
}
|
|
271
|
-
return this.client.get(
|
|
272
|
-
`/tables/${this.table}/rows`,
|
|
261
|
+
const params = this.buildQueryParams();
|
|
262
|
+
const res = await this.client.get(
|
|
263
|
+
`/${this.table}`,
|
|
273
264
|
params
|
|
274
265
|
);
|
|
266
|
+
const offset = ((res.meta.page || 1) - 1) * (res.meta.limit || 0);
|
|
267
|
+
return {
|
|
268
|
+
rows: res.data,
|
|
269
|
+
total: res.meta.total,
|
|
270
|
+
limit: res.meta.limit,
|
|
271
|
+
offset,
|
|
272
|
+
hasMore: res.meta.page < res.meta.totalPages
|
|
273
|
+
};
|
|
275
274
|
}
|
|
276
275
|
/**
|
|
277
276
|
* Execute and return all rows
|
|
@@ -286,6 +285,7 @@ var QueryBuilder = class {
|
|
|
286
285
|
*/
|
|
287
286
|
async single() {
|
|
288
287
|
this._limit = 1;
|
|
288
|
+
this._page = 1;
|
|
289
289
|
const result = await this.execute();
|
|
290
290
|
return result.rows[0] ?? null;
|
|
291
291
|
}
|
|
@@ -293,49 +293,51 @@ var QueryBuilder = class {
|
|
|
293
293
|
* Insert data
|
|
294
294
|
*/
|
|
295
295
|
async insert(data) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
296
|
+
if (Array.isArray(data)) {
|
|
297
|
+
const created2 = await Promise.all(data.map((row) => this.client.post(`/${this.table}`, row)));
|
|
298
|
+
return { success: true, rows: created2.map((r) => r.data) };
|
|
299
|
+
}
|
|
300
|
+
const created = await this.client.post(`/${this.table}`, data);
|
|
301
|
+
return { success: true, row: created.data };
|
|
300
302
|
}
|
|
301
303
|
/**
|
|
302
304
|
* Update data (requires where clause)
|
|
303
305
|
*/
|
|
304
306
|
async update(data) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
+
const id = this._filters["id"];
|
|
308
|
+
if (!id) {
|
|
309
|
+
throw new Error("Update requires an id filter");
|
|
307
310
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
+
await this.client.patch(
|
|
312
|
+
`/${this.table}/${encodeURIComponent(String(id))}`,
|
|
313
|
+
data
|
|
311
314
|
);
|
|
315
|
+
return { success: true, count: 1 };
|
|
312
316
|
}
|
|
313
317
|
/**
|
|
314
318
|
* Delete data (requires where clause)
|
|
315
319
|
*/
|
|
316
320
|
async delete() {
|
|
317
|
-
|
|
318
|
-
|
|
321
|
+
const id = this._filters["id"];
|
|
322
|
+
if (!id) {
|
|
323
|
+
throw new Error("Delete requires an id filter");
|
|
319
324
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
{ filters: this._where }
|
|
323
|
-
);
|
|
325
|
+
await this.client.delete(`/${this.table}/${encodeURIComponent(String(id))}`);
|
|
326
|
+
return { success: true, count: 1 };
|
|
324
327
|
}
|
|
325
328
|
};
|
|
326
329
|
|
|
327
330
|
// src/modules/storage.ts
|
|
328
331
|
var StorageModule = class {
|
|
329
|
-
constructor(client,
|
|
332
|
+
constructor(client, projectId) {
|
|
330
333
|
this.client = client;
|
|
331
|
-
this.
|
|
334
|
+
this.projectId = projectId;
|
|
332
335
|
}
|
|
333
|
-
baseUrl;
|
|
334
336
|
/**
|
|
335
337
|
* Create a new storage bucket
|
|
336
338
|
*/
|
|
337
339
|
async createBucket(name, options) {
|
|
338
|
-
return this.client.post(
|
|
340
|
+
return this.client.post(`/storage/${this.projectId}/buckets`, {
|
|
339
341
|
name,
|
|
340
342
|
publicAccess: options?.publicAccess ?? false
|
|
341
343
|
});
|
|
@@ -344,17 +346,20 @@ var StorageModule = class {
|
|
|
344
346
|
* List all buckets
|
|
345
347
|
*/
|
|
346
348
|
async listBuckets() {
|
|
347
|
-
return this.client.get(
|
|
349
|
+
return this.client.get(`/storage/${this.projectId}/buckets`);
|
|
348
350
|
}
|
|
349
351
|
/**
|
|
350
352
|
* Upload a file to a bucket
|
|
351
353
|
*/
|
|
352
354
|
async upload(bucket, file, options) {
|
|
355
|
+
const form = new FormData();
|
|
353
356
|
const fileName = options?.fileName || (file instanceof File ? file.name : "file");
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
357
|
+
form.append("file", file, fileName);
|
|
358
|
+
if (options?.randomId !== void 0) form.append("randomId", String(options.randomId));
|
|
359
|
+
if (options?.cacheDuration !== void 0) form.append("cacheDuration", String(options.cacheDuration));
|
|
360
|
+
return this.client.postForm(
|
|
361
|
+
`/storage/${this.projectId}/buckets/${bucket}/upload`,
|
|
362
|
+
form
|
|
358
363
|
);
|
|
359
364
|
}
|
|
360
365
|
/**
|
|
@@ -365,62 +370,63 @@ var StorageModule = class {
|
|
|
365
370
|
if (options?.limit) params.limit = options.limit;
|
|
366
371
|
if (options?.offset) params.offset = options.offset;
|
|
367
372
|
if (options?.prefix) params.prefix = options.prefix;
|
|
368
|
-
return this.client.get(`/storage/buckets/${bucket}/files`, params);
|
|
373
|
+
return this.client.get(`/storage/${this.projectId}/buckets/${bucket}/files`, params);
|
|
369
374
|
}
|
|
370
375
|
/**
|
|
371
376
|
* Get a signed URL for private file access
|
|
372
377
|
*/
|
|
373
378
|
async getSignedUrl(bucket, fileName) {
|
|
374
379
|
const result = await this.client.get(
|
|
375
|
-
`/storage/buckets/${bucket}/files/${encodeURIComponent(fileName)}/url`
|
|
380
|
+
`/storage/${this.projectId}/buckets/${bucket}/files/${encodeURIComponent(fileName)}/url`
|
|
376
381
|
);
|
|
377
382
|
return result.url;
|
|
378
383
|
}
|
|
379
384
|
/**
|
|
380
385
|
* Get public URL for a file (bucket must be public)
|
|
381
386
|
*/
|
|
382
|
-
getPublicUrl(bucket, fileName) {
|
|
383
|
-
return `${this.baseUrl}/storage/buckets/${bucket}/files/${encodeURIComponent(fileName)}`;
|
|
384
|
-
}
|
|
385
387
|
/**
|
|
386
388
|
* Delete a file
|
|
387
389
|
*/
|
|
388
390
|
async delete(bucket, fileName) {
|
|
389
391
|
return this.client.delete(
|
|
390
|
-
`/storage/buckets/${bucket}/files/${encodeURIComponent(fileName)}`
|
|
392
|
+
`/storage/${this.projectId}/buckets/${bucket}/files/${encodeURIComponent(fileName)}`
|
|
391
393
|
);
|
|
392
394
|
}
|
|
393
395
|
/**
|
|
394
396
|
* Get storage statistics
|
|
395
397
|
*/
|
|
396
398
|
async getStats() {
|
|
397
|
-
return this.client.get(
|
|
399
|
+
return this.client.get(`/storage/${this.projectId}/stats`);
|
|
398
400
|
}
|
|
399
401
|
};
|
|
400
402
|
|
|
401
403
|
// src/modules/email.ts
|
|
402
404
|
var EmailModule = class {
|
|
403
|
-
constructor(client) {
|
|
405
|
+
constructor(client, projectId) {
|
|
404
406
|
this.client = client;
|
|
407
|
+
this.projectId = projectId;
|
|
408
|
+
}
|
|
409
|
+
headers() {
|
|
410
|
+
return { "x-project-id": this.projectId };
|
|
405
411
|
}
|
|
406
412
|
/**
|
|
407
413
|
* Send a transactional email
|
|
408
414
|
*/
|
|
409
415
|
async send(options) {
|
|
410
|
-
return this.client.post("/email/send", options);
|
|
416
|
+
return this.client.post("/email/send", options, this.headers());
|
|
411
417
|
}
|
|
412
418
|
/**
|
|
413
419
|
* Add a custom email domain
|
|
414
420
|
*/
|
|
415
421
|
async addDomain(domain) {
|
|
416
|
-
const result = await this.client.post("/email/domains", { domain });
|
|
422
|
+
const result = await this.client.post("/email/domains", { domain }, this.headers());
|
|
417
423
|
return result.domain;
|
|
418
424
|
}
|
|
419
425
|
/**
|
|
420
426
|
* List configured email domains
|
|
421
427
|
*/
|
|
422
428
|
async listDomains() {
|
|
423
|
-
const result = await this.client.get("/email/domains");
|
|
429
|
+
const result = await this.client.get("/email/domains", void 0, this.headers());
|
|
424
430
|
return result.domains;
|
|
425
431
|
}
|
|
426
432
|
/**
|
|
@@ -428,7 +434,9 @@ var EmailModule = class {
|
|
|
428
434
|
*/
|
|
429
435
|
async verifyDomain(domainId) {
|
|
430
436
|
return this.client.post(
|
|
431
|
-
`/email/domains/${domainId}/verify
|
|
437
|
+
`/email/domains/${domainId}/verify`,
|
|
438
|
+
{},
|
|
439
|
+
this.headers()
|
|
432
440
|
);
|
|
433
441
|
}
|
|
434
442
|
/**
|
|
@@ -436,15 +444,16 @@ var EmailModule = class {
|
|
|
436
444
|
*/
|
|
437
445
|
async getLogs(options) {
|
|
438
446
|
const params = options?.limit ? { limit: options.limit } : void 0;
|
|
439
|
-
const result = await this.client.get("/email/logs", params);
|
|
447
|
+
const result = await this.client.get("/email/logs", params, this.headers());
|
|
440
448
|
return result.logs;
|
|
441
449
|
}
|
|
442
450
|
};
|
|
443
451
|
|
|
444
452
|
// src/modules/ai.ts
|
|
445
453
|
var AIModule = class {
|
|
446
|
-
constructor(client) {
|
|
454
|
+
constructor(client, projectId) {
|
|
447
455
|
this.client = client;
|
|
456
|
+
this.projectId = projectId;
|
|
448
457
|
}
|
|
449
458
|
/**
|
|
450
459
|
* Execute a natural language query
|
|
@@ -456,61 +465,72 @@ var AIModule = class {
|
|
|
456
465
|
* console.log(result.rows); // Query results
|
|
457
466
|
*/
|
|
458
467
|
async query(prompt) {
|
|
459
|
-
return this.client.post(
|
|
468
|
+
return this.client.post(`/ai/${this.projectId}/query`, { prompt });
|
|
460
469
|
}
|
|
461
|
-
/**
|
|
462
|
-
* Generate SQL from natural language without executing
|
|
463
|
-
*/
|
|
464
470
|
async generateSQL(prompt) {
|
|
465
|
-
|
|
471
|
+
const result = await this.query(prompt);
|
|
472
|
+
return { sql: result.sql, explanation: result.explanation };
|
|
466
473
|
}
|
|
467
474
|
};
|
|
468
475
|
|
|
469
|
-
// src/
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
476
|
+
// src/dpop.ts
|
|
477
|
+
function base64urlEncode(data) {
|
|
478
|
+
let str = "";
|
|
479
|
+
for (let i = 0; i < data.length; i++) str += String.fromCharCode(data[i]);
|
|
480
|
+
const b64 = typeof btoa !== "undefined" ? btoa(str) : Buffer.from(data).toString("base64");
|
|
481
|
+
return b64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
482
|
+
}
|
|
483
|
+
function base64urlEncodeJson(value) {
|
|
484
|
+
const json = JSON.stringify(value);
|
|
485
|
+
const bytes = new TextEncoder().encode(json);
|
|
486
|
+
return base64urlEncode(bytes);
|
|
487
|
+
}
|
|
488
|
+
function randomId() {
|
|
489
|
+
const bytes = new Uint8Array(16);
|
|
490
|
+
crypto.getRandomValues(bytes);
|
|
491
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
492
|
+
}
|
|
493
|
+
async function createDpopKey() {
|
|
494
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
495
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
496
|
+
true,
|
|
497
|
+
["sign", "verify"]
|
|
498
|
+
);
|
|
499
|
+
const publicJwk = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
|
|
500
|
+
return { keyPair, publicJwk };
|
|
501
|
+
}
|
|
502
|
+
async function createDpopProof(params) {
|
|
503
|
+
const header = {
|
|
504
|
+
typ: "dpop+jwt",
|
|
505
|
+
alg: "ES256",
|
|
506
|
+
jwk: await crypto.subtle.exportKey("jwk", params.keyPair.publicKey)
|
|
507
|
+
};
|
|
508
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
509
|
+
const payload = {
|
|
510
|
+
htm: params.method.toUpperCase(),
|
|
511
|
+
htu: new URL(params.url).toString(),
|
|
512
|
+
iat: now,
|
|
513
|
+
jti: randomId(),
|
|
514
|
+
iss: "scarlet-dpop",
|
|
515
|
+
aud: "scarlet-sdk"
|
|
506
516
|
};
|
|
517
|
+
const encodedHeader = base64urlEncodeJson(header);
|
|
518
|
+
const encodedPayload = base64urlEncodeJson(payload);
|
|
519
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
520
|
+
const signature = await crypto.subtle.sign(
|
|
521
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
522
|
+
params.keyPair.privateKey,
|
|
523
|
+
new TextEncoder().encode(signingInput)
|
|
524
|
+
);
|
|
525
|
+
return `${signingInput}.${base64urlEncode(new Uint8Array(signature))}`;
|
|
507
526
|
}
|
|
508
527
|
|
|
509
528
|
// src/index.ts
|
|
510
|
-
var
|
|
529
|
+
var DEFAULT_URL = "https://api.scarletdb.space";
|
|
511
530
|
var DEFAULT_TIMEOUT = 3e4;
|
|
512
531
|
var Scarlet = class {
|
|
513
|
-
|
|
532
|
+
engineClient;
|
|
533
|
+
coreClient;
|
|
514
534
|
_data;
|
|
515
535
|
/** Storage module for file operations */
|
|
516
536
|
storage;
|
|
@@ -518,26 +538,68 @@ var Scarlet = class {
|
|
|
518
538
|
email;
|
|
519
539
|
/** AI module for natural language queries */
|
|
520
540
|
ai;
|
|
521
|
-
|
|
522
|
-
sql;
|
|
541
|
+
projectId;
|
|
523
542
|
constructor(config) {
|
|
524
|
-
if (!config.
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const
|
|
543
|
+
if (!config.projectId) throw new Error("projectId is required");
|
|
544
|
+
this.projectId = config.projectId;
|
|
545
|
+
const url = config.url || DEFAULT_URL;
|
|
546
|
+
const coreBaseUrl = `${(config.coreUrl || url).replace(/\/$/, "")}/api/v1`;
|
|
547
|
+
const engineBaseUrl = `${(config.engineUrl || url).replace(/\/$/, "")}/api/db/${this.projectId}`;
|
|
528
548
|
const timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
549
|
+
const tokenState = { value: "", expiresAt: 0 };
|
|
550
|
+
let dpopKey = null;
|
|
551
|
+
const getToken = async () => {
|
|
552
|
+
if (config.token) return config.token;
|
|
553
|
+
if (config.getToken) return config.getToken();
|
|
554
|
+
if (config.tokenEndpoint || config.publishableKey || !config.apiKey) {
|
|
555
|
+
const endpoint = config.tokenEndpoint || (config.publishableKey ? "/auth/publishable-token" : "/auth/sdk-token");
|
|
556
|
+
const now = Date.now();
|
|
557
|
+
if (tokenState.value && tokenState.expiresAt > now + 1e4) {
|
|
558
|
+
return tokenState.value;
|
|
559
|
+
}
|
|
560
|
+
if (config.enableDpop && !dpopKey) {
|
|
561
|
+
dpopKey = await createDpopKey();
|
|
562
|
+
}
|
|
563
|
+
const res = await fetch(`${coreBaseUrl}${endpoint}`, {
|
|
564
|
+
method: "POST",
|
|
565
|
+
headers: {
|
|
566
|
+
"Content-Type": "application/json",
|
|
567
|
+
...config.publishableKey ? { "X-Publishable-Key": config.publishableKey } : {}
|
|
568
|
+
},
|
|
569
|
+
credentials: config.publishableKey ? "omit" : "include",
|
|
570
|
+
body: JSON.stringify({
|
|
571
|
+
projectId: this.projectId,
|
|
572
|
+
dpopJwk: dpopKey?.publicJwk
|
|
573
|
+
})
|
|
574
|
+
});
|
|
575
|
+
const json = await res.json().catch(() => ({}));
|
|
576
|
+
if (!res.ok) {
|
|
577
|
+
throw new Error((typeof json.error === "string" ? json.error : "") || "Failed to fetch token");
|
|
578
|
+
}
|
|
579
|
+
tokenState.value = String(json.token || "");
|
|
580
|
+
tokenState.expiresAt = Date.now() + Number(json.expiresIn || 0) * 1e3;
|
|
581
|
+
return tokenState.value;
|
|
582
|
+
}
|
|
583
|
+
throw new Error("Missing apiKey or token configuration");
|
|
584
|
+
};
|
|
585
|
+
const getAuthHeaders = async (params) => {
|
|
586
|
+
if (config.apiKey) {
|
|
587
|
+
return { "X-API-Key": config.apiKey };
|
|
588
|
+
}
|
|
589
|
+
const token = await getToken();
|
|
590
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
591
|
+
if (config.enableDpop) {
|
|
592
|
+
if (!dpopKey) dpopKey = await createDpopKey();
|
|
593
|
+
headers["DPoP"] = await createDpopProof({ keyPair: dpopKey.keyPair, method: params.method, url: params.url });
|
|
594
|
+
}
|
|
595
|
+
return headers;
|
|
596
|
+
};
|
|
597
|
+
this.engineClient = new HttpClient({ baseUrl: engineBaseUrl, timeout, getAuthHeaders });
|
|
598
|
+
this.coreClient = new HttpClient({ baseUrl: coreBaseUrl, timeout, getAuthHeaders });
|
|
599
|
+
this._data = new DataModule(this.engineClient);
|
|
600
|
+
this.storage = new StorageModule(this.coreClient, this.projectId);
|
|
601
|
+
this.email = new EmailModule(this.coreClient, this.projectId);
|
|
602
|
+
this.ai = new AIModule(this.coreClient, this.projectId);
|
|
541
603
|
}
|
|
542
604
|
/**
|
|
543
605
|
* Start a query on a table
|
|
@@ -549,6 +611,9 @@ var Scarlet = class {
|
|
|
549
611
|
return this._data.from(table);
|
|
550
612
|
}
|
|
551
613
|
};
|
|
614
|
+
function createScarlet(config) {
|
|
615
|
+
return new Scarlet(config);
|
|
616
|
+
}
|
|
552
617
|
|
|
553
618
|
exports.AuthenticationError = AuthenticationError;
|
|
554
619
|
exports.NetworkError = NetworkError;
|
|
@@ -558,5 +623,6 @@ exports.RateLimitError = RateLimitError;
|
|
|
558
623
|
exports.Scarlet = Scarlet;
|
|
559
624
|
exports.ScarletError = ScarletError;
|
|
560
625
|
exports.ValidationError = ValidationError;
|
|
626
|
+
exports.createScarlet = createScarlet;
|
|
561
627
|
//# sourceMappingURL=index.js.map
|
|
562
628
|
//# sourceMappingURL=index.js.map
|