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