@basictech/react 0.7.0-beta.1 → 0.7.0-beta.2
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/changelog.md +6 -0
- package/dist/index.d.mts +107 -1
- package/dist/index.d.ts +107 -1
- package/dist/index.js +719 -356
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +716 -344
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/AuthContext.tsx +178 -402
- package/src/index.ts +6 -31
- package/src/sync/index.ts +1 -1
- package/src/updater/updateMigrations.ts +22 -0
- package/src/updater/versionUpdater.ts +160 -0
- package/src/utils/network.ts +82 -0
- package/src/utils/schema.ts +120 -0
- package/src/utils/storage.ts +62 -0
- package/src/schema.ts +0 -159
package/dist/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,19 +15,12 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/index.ts
|
|
31
21
|
var src_exports = {};
|
|
32
22
|
__export(src_exports, {
|
|
23
|
+
BasicDBSDK: () => BasicDBSDK,
|
|
33
24
|
BasicProvider: () => BasicProvider,
|
|
34
25
|
useBasic: () => useBasic,
|
|
35
26
|
useQuery: () => import_dexie_react_hooks.useLiveQuery
|
|
@@ -165,116 +156,8 @@ var syncProtocol = function() {
|
|
|
165
156
|
});
|
|
166
157
|
};
|
|
167
158
|
|
|
168
|
-
// src/schema.ts
|
|
169
|
-
var import_ajv = __toESM(require("ajv"));
|
|
170
|
-
var basicJsonSchema = {
|
|
171
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
172
|
-
"type": "object",
|
|
173
|
-
"properties": {
|
|
174
|
-
"project_id": {
|
|
175
|
-
"type": "string"
|
|
176
|
-
},
|
|
177
|
-
"namespace": {
|
|
178
|
-
"type": "string"
|
|
179
|
-
},
|
|
180
|
-
"version": {
|
|
181
|
-
"type": "integer",
|
|
182
|
-
"minimum": 0
|
|
183
|
-
},
|
|
184
|
-
"tables": {
|
|
185
|
-
"type": "object",
|
|
186
|
-
"patternProperties": {
|
|
187
|
-
"^[a-zA-Z0-9_]+$": {
|
|
188
|
-
"type": "object",
|
|
189
|
-
"properties": {
|
|
190
|
-
"name": {
|
|
191
|
-
"type": "string"
|
|
192
|
-
},
|
|
193
|
-
"type": {
|
|
194
|
-
"type": "string",
|
|
195
|
-
"enum": ["collection"]
|
|
196
|
-
},
|
|
197
|
-
"fields": {
|
|
198
|
-
"type": "object",
|
|
199
|
-
"patternProperties": {
|
|
200
|
-
"^[a-zA-Z0-9_]+$": {
|
|
201
|
-
"type": "object",
|
|
202
|
-
"properties": {
|
|
203
|
-
"type": {
|
|
204
|
-
"type": "string",
|
|
205
|
-
"enum": ["string", "boolean", "number", "json"]
|
|
206
|
-
},
|
|
207
|
-
"indexed": {
|
|
208
|
-
"type": "boolean"
|
|
209
|
-
},
|
|
210
|
-
"required": {
|
|
211
|
-
"type": "boolean"
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
"required": ["type"]
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
"additionalProperties": true
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
"required": ["fields"]
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
"additionalProperties": true
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
"required": ["project_id", "version", "tables"]
|
|
227
|
-
};
|
|
228
|
-
var ajv = new import_ajv.default();
|
|
229
|
-
var validator = ajv.compile(basicJsonSchema);
|
|
230
|
-
function validateSchema(schema) {
|
|
231
|
-
const v = validator(schema);
|
|
232
|
-
return {
|
|
233
|
-
valid: v,
|
|
234
|
-
errors: validator.errors || []
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
function validateData(schema, table, data, checkRequired = true) {
|
|
238
|
-
const valid = validateSchema(schema);
|
|
239
|
-
if (!valid.valid) {
|
|
240
|
-
return { valid: false, errors: valid.errors, message: "Schema is invalid" };
|
|
241
|
-
}
|
|
242
|
-
const tableSchema = schema.tables[table];
|
|
243
|
-
if (!tableSchema) {
|
|
244
|
-
return { valid: false, errors: [{ message: `Table ${table} not found in schema` }], message: "Table not found" };
|
|
245
|
-
}
|
|
246
|
-
for (const [fieldName, fieldValue] of Object.entries(data)) {
|
|
247
|
-
const fieldSchema = tableSchema.fields[fieldName];
|
|
248
|
-
if (!fieldSchema) {
|
|
249
|
-
return {
|
|
250
|
-
valid: false,
|
|
251
|
-
errors: [{ message: `Field ${fieldName} not found in schema` }],
|
|
252
|
-
message: "Invalid field"
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
const schemaType = fieldSchema.type;
|
|
256
|
-
const valueType = typeof fieldValue;
|
|
257
|
-
if (schemaType === "string" && valueType !== "string" || schemaType === "number" && valueType !== "number" || schemaType === "boolean" && valueType !== "boolean" || schemaType === "json" && valueType !== "object") {
|
|
258
|
-
return {
|
|
259
|
-
valid: false,
|
|
260
|
-
errors: [{
|
|
261
|
-
message: `Field ${fieldName} should be type ${schemaType}, got ${valueType}`
|
|
262
|
-
}],
|
|
263
|
-
message: "invalid type"
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if (checkRequired) {
|
|
268
|
-
for (const [fieldName, fieldSchema] of Object.entries(tableSchema.fields)) {
|
|
269
|
-
if (fieldSchema.required && !data[fieldName]) {
|
|
270
|
-
return { valid: false, errors: [{ message: `Field ${fieldName} is required` }], message: "Required field missing" };
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return { valid: true, errors: [] };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
159
|
// src/sync/index.ts
|
|
160
|
+
var import_schema = require("@basictech/schema");
|
|
278
161
|
syncProtocol();
|
|
279
162
|
var BasicSync = class extends import_dexie2.Dexie {
|
|
280
163
|
basic_schema;
|
|
@@ -349,7 +232,7 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
349
232
|
ref: this.table(name),
|
|
350
233
|
// --- WRITE ---- //
|
|
351
234
|
add: (data) => {
|
|
352
|
-
const valid = validateData(this.basic_schema, name, data);
|
|
235
|
+
const valid = (0, import_schema.validateData)(this.basic_schema, name, data);
|
|
353
236
|
if (!valid.valid) {
|
|
354
237
|
log("Invalid data", valid);
|
|
355
238
|
return Promise.reject({ ...valid });
|
|
@@ -360,7 +243,7 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
360
243
|
});
|
|
361
244
|
},
|
|
362
245
|
put: (data) => {
|
|
363
|
-
const valid = validateData(this.basic_schema, name, data);
|
|
246
|
+
const valid = (0, import_schema.validateData)(this.basic_schema, name, data);
|
|
364
247
|
if (!valid.valid) {
|
|
365
248
|
log("Invalid data", valid);
|
|
366
249
|
return Promise.reject({ ...valid });
|
|
@@ -371,7 +254,7 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
371
254
|
});
|
|
372
255
|
},
|
|
373
256
|
update: (id, data) => {
|
|
374
|
-
const valid = validateData(this.basic_schema, name, data, false);
|
|
257
|
+
const valid = (0, import_schema.validateData)(this.basic_schema, name, data, false);
|
|
375
258
|
if (!valid.valid) {
|
|
376
259
|
log("Invalid data", valid);
|
|
377
260
|
return Promise.reject({ ...valid });
|
|
@@ -396,60 +279,490 @@ var BasicSync = class extends import_dexie2.Dexie {
|
|
|
396
279
|
}
|
|
397
280
|
};
|
|
398
281
|
|
|
399
|
-
// src/
|
|
400
|
-
var
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
282
|
+
// src/db_ts.ts
|
|
283
|
+
var DBError = class extends Error {
|
|
284
|
+
constructor(message, status, response, originalError) {
|
|
285
|
+
super(message);
|
|
286
|
+
this.status = status;
|
|
287
|
+
this.response = response;
|
|
288
|
+
this.originalError = originalError;
|
|
289
|
+
this.name = "DBError";
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var QueryBuilder = class {
|
|
293
|
+
constructor(tableClient, tableSchema) {
|
|
294
|
+
this.tableClient = tableClient;
|
|
295
|
+
this.tableSchema = tableSchema;
|
|
296
|
+
}
|
|
297
|
+
params = {};
|
|
298
|
+
// Reserved fields that are always allowed
|
|
299
|
+
reservedFields = ["created_at", "updated_at", "id"];
|
|
300
|
+
// Validate field existence in schema
|
|
301
|
+
validateField(field) {
|
|
302
|
+
if (this.tableSchema && !this.reservedFields.includes(field)) {
|
|
303
|
+
if (!this.tableSchema.fields || !(field in this.tableSchema.fields)) {
|
|
304
|
+
throw new Error(`Invalid field: "${field}". Field does not exist in table schema.`);
|
|
305
|
+
}
|
|
406
306
|
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const response = await fetch(url, {
|
|
413
|
-
method: "POST",
|
|
414
|
-
headers: {
|
|
415
|
-
"Content-Type": "application/json",
|
|
416
|
-
"Authorization": `Bearer ${token}`
|
|
417
|
-
},
|
|
418
|
-
body: JSON.stringify({ "value": value })
|
|
419
|
-
});
|
|
420
|
-
return response.json();
|
|
421
|
-
}
|
|
422
|
-
async function update({ projectId, accountId, tableName, id, value, token }) {
|
|
423
|
-
const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}/${id}`;
|
|
424
|
-
const response = await fetch(url, {
|
|
425
|
-
method: "PATCH",
|
|
426
|
-
headers: {
|
|
427
|
-
"Content-Type": "application/json",
|
|
428
|
-
"Authorization": `Bearer ${token}`
|
|
429
|
-
},
|
|
430
|
-
body: JSON.stringify({ id, value })
|
|
431
|
-
});
|
|
432
|
-
return response.json();
|
|
433
|
-
}
|
|
434
|
-
async function deleteRecord({ projectId, accountId, tableName, id, token }) {
|
|
435
|
-
const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}/${id}`;
|
|
436
|
-
const response = await fetch(url, {
|
|
437
|
-
method: "DELETE",
|
|
438
|
-
headers: {
|
|
439
|
-
"Authorization": `Bearer ${token}`
|
|
307
|
+
}
|
|
308
|
+
// Validate operator based on field type
|
|
309
|
+
validateOperator(field, operator, value) {
|
|
310
|
+
if (!this.tableSchema || this.reservedFields.includes(field)) {
|
|
311
|
+
return;
|
|
440
312
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
313
|
+
const fieldInfo = this.tableSchema.fields[field];
|
|
314
|
+
if (!fieldInfo)
|
|
315
|
+
return;
|
|
316
|
+
switch (operator) {
|
|
317
|
+
case "gt":
|
|
318
|
+
case "gte":
|
|
319
|
+
case "lt":
|
|
320
|
+
case "lte":
|
|
321
|
+
if (fieldInfo.type !== "number" && fieldInfo.type !== "string") {
|
|
322
|
+
throw new Error(`Operator "${operator}" can only be used with number or string fields. Field "${field}" is type "${fieldInfo.type}".`);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
case "like":
|
|
326
|
+
case "ilike":
|
|
327
|
+
if (fieldInfo.type !== "string") {
|
|
328
|
+
throw new Error(`Operator "${operator}" can only be used with string fields. Field "${field}" is type "${fieldInfo.type}".`);
|
|
329
|
+
}
|
|
330
|
+
if (typeof value !== "string") {
|
|
331
|
+
throw new Error(`Operator "${operator}" requires a string value. Received: ${typeof value}`);
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
case "in":
|
|
335
|
+
if (!Array.isArray(value)) {
|
|
336
|
+
throw new Error(`Operator "in" requires an array value. Received: ${typeof value}`);
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
case "is":
|
|
340
|
+
if (value !== null && typeof value !== "boolean") {
|
|
341
|
+
throw new Error(`Operator "is" requires null or boolean. Received: ${typeof value}`);
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Add ordering to query with schema validation
|
|
347
|
+
order(field, direction = "asc") {
|
|
348
|
+
this.validateField(field);
|
|
349
|
+
this.params.order = `${field}.${direction}`;
|
|
350
|
+
return this;
|
|
351
|
+
}
|
|
352
|
+
// Add filtering to query
|
|
353
|
+
filter(conditions) {
|
|
354
|
+
if (!this.params.filters) {
|
|
355
|
+
this.params.filters = {};
|
|
356
|
+
}
|
|
357
|
+
for (const [field, condition] of Object.entries(conditions)) {
|
|
358
|
+
this.validateField(field);
|
|
359
|
+
if (condition === null || typeof condition !== "object") {
|
|
360
|
+
this.params.filters[field] = condition;
|
|
361
|
+
} else {
|
|
362
|
+
this.params.filters[field] = condition;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return this;
|
|
366
|
+
}
|
|
367
|
+
// Add limit to query
|
|
368
|
+
limit(count) {
|
|
369
|
+
this.params.limit = count;
|
|
370
|
+
return this;
|
|
371
|
+
}
|
|
372
|
+
// Add offset to query for pagination
|
|
373
|
+
offset(count) {
|
|
374
|
+
this.params.offset = count;
|
|
375
|
+
return this;
|
|
376
|
+
}
|
|
377
|
+
// Auto-execute when awaited
|
|
378
|
+
then(onfulfilled, onrejected) {
|
|
379
|
+
return this.tableClient.executeQuery(this.params).then(onfulfilled, onrejected);
|
|
380
|
+
}
|
|
381
|
+
// Auto-execute when awaited with catch
|
|
382
|
+
catch(onrejected) {
|
|
383
|
+
return this.tableClient.executeQuery(this.params).catch(onrejected);
|
|
384
|
+
}
|
|
385
|
+
// Auto-execute when awaited with finally
|
|
386
|
+
finally(onfinally) {
|
|
387
|
+
return this.tableClient.executeQuery(this.params).finally(onfinally);
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
var TableClient = class {
|
|
391
|
+
constructor(baseUrl, projectId, token, table, getToken, schema) {
|
|
392
|
+
this.baseUrl = baseUrl;
|
|
393
|
+
this.projectId = projectId;
|
|
394
|
+
this.token = token;
|
|
395
|
+
this.table = table;
|
|
396
|
+
this.getToken = getToken;
|
|
397
|
+
this.schema = schema;
|
|
398
|
+
if (schema && schema.tables && schema.tables[table]) {
|
|
399
|
+
this.tableSchema = schema.tables[table];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
tableSchema;
|
|
403
|
+
async headers() {
|
|
404
|
+
const token = await this.getToken();
|
|
405
|
+
return {
|
|
406
|
+
Authorization: `Bearer ${token}`,
|
|
407
|
+
"Content-Type": "application/json"
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
async handleRequest(request) {
|
|
411
|
+
try {
|
|
412
|
+
const res = await request;
|
|
413
|
+
if (!res.ok) {
|
|
414
|
+
let errorMessage = `Request failed with status ${res.status}`;
|
|
415
|
+
let errorData;
|
|
416
|
+
try {
|
|
417
|
+
const json2 = await res.json();
|
|
418
|
+
errorData = json2;
|
|
419
|
+
if (json2.error || json2.message) {
|
|
420
|
+
const errorDetails = typeof json2.error === "object" ? JSON.stringify(json2.error) : json2.error;
|
|
421
|
+
const messageDetails = typeof json2.message === "object" ? JSON.stringify(json2.message) : json2.message;
|
|
422
|
+
errorMessage = `${res.status} ${res.statusText}: ${messageDetails || errorDetails || "Unknown error"}`;
|
|
423
|
+
}
|
|
424
|
+
} catch (e) {
|
|
425
|
+
console.log("Failed to parse error response:", e);
|
|
426
|
+
errorMessage = `${res.status} ${res.statusText}`;
|
|
427
|
+
}
|
|
428
|
+
throw new DBError(
|
|
429
|
+
errorMessage,
|
|
430
|
+
res.status,
|
|
431
|
+
errorData
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
const json = await res.json();
|
|
435
|
+
return json.data;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
console.log("Caught error:", error);
|
|
438
|
+
if (error instanceof Error) {
|
|
439
|
+
console.log("Error type:", error.constructor.name);
|
|
440
|
+
console.log("Error stack:", error.stack);
|
|
441
|
+
}
|
|
442
|
+
if (error instanceof DBError) {
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
if (error instanceof TypeError && error.message === "Network request failed") {
|
|
446
|
+
throw new DBError(
|
|
447
|
+
"Network request failed. Please check your internet connection and try again.",
|
|
448
|
+
void 0,
|
|
449
|
+
void 0,
|
|
450
|
+
error
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
throw new DBError(
|
|
454
|
+
`Unexpected error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
455
|
+
void 0,
|
|
456
|
+
void 0,
|
|
457
|
+
error instanceof Error ? error : void 0
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Build query string from query options
|
|
462
|
+
buildQueryParams(query) {
|
|
463
|
+
if (!query)
|
|
464
|
+
return "";
|
|
465
|
+
const params = [];
|
|
466
|
+
if (query.id) {
|
|
467
|
+
params.push(`id=${query.id}`);
|
|
468
|
+
}
|
|
469
|
+
if (query.filters) {
|
|
470
|
+
for (const [field, condition] of Object.entries(query.filters)) {
|
|
471
|
+
this.addFilterParam(params, field, condition);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (query.order) {
|
|
475
|
+
params.push(`order=${query.order}`);
|
|
476
|
+
}
|
|
477
|
+
if (query.limit !== void 0 && query.limit >= 0) {
|
|
478
|
+
params.push(`limit=${query.limit}`);
|
|
479
|
+
}
|
|
480
|
+
if (query.offset !== void 0 && query.offset >= 0) {
|
|
481
|
+
params.push(`offset=${query.offset}`);
|
|
482
|
+
}
|
|
483
|
+
return params.length > 0 ? `?${params.join("&")}` : "";
|
|
484
|
+
}
|
|
485
|
+
// Helper method to build filter parameters
|
|
486
|
+
addFilterParam(params, field, condition, negate = false) {
|
|
487
|
+
if (condition === null || typeof condition !== "object") {
|
|
488
|
+
if (condition === null) {
|
|
489
|
+
params.push(`${field}=${negate ? "not." : ""}is.null`);
|
|
490
|
+
} else if (typeof condition === "boolean") {
|
|
491
|
+
params.push(`${field}=${negate ? "not." : ""}is.${condition}`);
|
|
492
|
+
} else if (typeof condition === "number") {
|
|
493
|
+
params.push(`${field}=${negate ? "not." : ""}eq.${condition}`);
|
|
494
|
+
} else {
|
|
495
|
+
params.push(`${field}=${negate ? "not." : ""}eq.${encodeURIComponent(String(condition))}`);
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const operatorObj = condition;
|
|
500
|
+
if (operatorObj.not) {
|
|
501
|
+
this.addFilterParam(params, field, operatorObj.not, true);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
for (const [op, value] of Object.entries(operatorObj)) {
|
|
505
|
+
if (op === "not")
|
|
506
|
+
continue;
|
|
507
|
+
const operator = op;
|
|
508
|
+
if (value === null) {
|
|
509
|
+
params.push(`${field}=${negate ? "not." : ""}is.null`);
|
|
510
|
+
} else if (operator === "in" && Array.isArray(value)) {
|
|
511
|
+
params.push(`${field}=${negate ? "not." : ""}in.${value.join(",")}`);
|
|
512
|
+
} else if (operator === "is") {
|
|
513
|
+
if (typeof value === "boolean") {
|
|
514
|
+
params.push(`${field}=${negate ? "not." : ""}is.${value}`);
|
|
515
|
+
} else {
|
|
516
|
+
params.push(`${field}=${negate ? "not." : ""}is.null`);
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
const paramValue = typeof value === "string" ? encodeURIComponent(value) : String(value);
|
|
520
|
+
params.push(`${field}=${negate ? "not." : ""}${operator}.${paramValue}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// Internal method to execute a query with options
|
|
525
|
+
async executeQuery(options) {
|
|
526
|
+
const params = this.buildQueryParams(options);
|
|
527
|
+
const headers = await this.headers();
|
|
528
|
+
return this.handleRequest(
|
|
529
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}${params}`, {
|
|
530
|
+
headers
|
|
531
|
+
})
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
// Public method to start building a query
|
|
535
|
+
getAll() {
|
|
536
|
+
return new QueryBuilder(this, this.tableSchema);
|
|
537
|
+
}
|
|
538
|
+
// Get a specific item by ID
|
|
539
|
+
async get(id) {
|
|
540
|
+
const headers = await this.headers();
|
|
541
|
+
return this.handleRequest(
|
|
542
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
543
|
+
headers
|
|
544
|
+
})
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
async create(value) {
|
|
548
|
+
const headers = await this.headers();
|
|
549
|
+
return this.handleRequest(
|
|
550
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}`, {
|
|
551
|
+
method: "POST",
|
|
552
|
+
headers,
|
|
553
|
+
body: JSON.stringify({ value })
|
|
554
|
+
})
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
async update(id, value) {
|
|
558
|
+
const headers = await this.headers();
|
|
559
|
+
return this.handleRequest(
|
|
560
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
561
|
+
method: "PATCH",
|
|
562
|
+
headers,
|
|
563
|
+
body: JSON.stringify({ value })
|
|
564
|
+
})
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
async replace(id, value) {
|
|
568
|
+
const headers = await this.headers();
|
|
569
|
+
return this.handleRequest(
|
|
570
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
571
|
+
method: "PUT",
|
|
572
|
+
headers,
|
|
573
|
+
body: JSON.stringify({ value })
|
|
574
|
+
})
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
async delete(id) {
|
|
578
|
+
const token = await this.getToken();
|
|
579
|
+
const headers = {
|
|
580
|
+
Authorization: `Bearer ${token}`
|
|
581
|
+
};
|
|
582
|
+
return this.handleRequest(
|
|
583
|
+
fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
|
|
584
|
+
method: "DELETE",
|
|
585
|
+
headers
|
|
586
|
+
})
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
var BasicDBSDK = class {
|
|
591
|
+
projectId;
|
|
592
|
+
getToken;
|
|
593
|
+
baseUrl;
|
|
594
|
+
schema;
|
|
595
|
+
tableNames;
|
|
596
|
+
constructor(config) {
|
|
597
|
+
this.projectId = config.project_id;
|
|
598
|
+
if (config.getToken) {
|
|
599
|
+
this.getToken = config.getToken;
|
|
600
|
+
} else if (config.token) {
|
|
601
|
+
this.getToken = async () => config.token;
|
|
602
|
+
} else {
|
|
603
|
+
throw new Error("Either token or getToken must be provided");
|
|
604
|
+
}
|
|
605
|
+
this.baseUrl = config.baseUrl || "https://api.basic.tech";
|
|
606
|
+
this.schema = config.schema;
|
|
607
|
+
this.tableNames = Object.keys(this.schema.tables);
|
|
608
|
+
}
|
|
609
|
+
// Primary method - table access
|
|
610
|
+
table(name) {
|
|
611
|
+
if (!this.tableNames.includes(name)) {
|
|
612
|
+
throw new Error(`Table '${name}' not found in schema. Available tables: ${this.tableNames.join(", ")}`);
|
|
613
|
+
}
|
|
614
|
+
return new TableClient(
|
|
615
|
+
this.baseUrl,
|
|
616
|
+
this.projectId,
|
|
617
|
+
"",
|
|
618
|
+
// Empty placeholder, will be replaced in headers() method
|
|
619
|
+
name,
|
|
620
|
+
this.getToken,
|
|
621
|
+
this.schema
|
|
622
|
+
// Pass the entire schema to the TableClient
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
get tables() {
|
|
626
|
+
return {};
|
|
627
|
+
}
|
|
628
|
+
fields(table) {
|
|
629
|
+
const tableSchema = this.schema.tables[table];
|
|
630
|
+
if (!tableSchema) {
|
|
631
|
+
throw new Error(`Table '${table}' not found in schema`);
|
|
632
|
+
}
|
|
633
|
+
return Object.keys(tableSchema.fields);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
447
636
|
|
|
448
637
|
// package.json
|
|
449
|
-
var version = "0.
|
|
638
|
+
var version = "0.7.0-beta.1";
|
|
450
639
|
|
|
451
|
-
// src/
|
|
452
|
-
var
|
|
640
|
+
// src/updater/versionUpdater.ts
|
|
641
|
+
var VersionUpdater = class {
|
|
642
|
+
storage;
|
|
643
|
+
currentVersion;
|
|
644
|
+
migrations;
|
|
645
|
+
versionKey = "basic_app_version";
|
|
646
|
+
constructor(storage, currentVersion, migrations = []) {
|
|
647
|
+
this.storage = storage;
|
|
648
|
+
this.currentVersion = currentVersion;
|
|
649
|
+
this.migrations = migrations.sort((a, b) => this.compareVersions(a.fromVersion, b.fromVersion));
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Check current stored version and run migrations if needed
|
|
653
|
+
* Only compares major.minor versions, ignoring beta/prerelease parts
|
|
654
|
+
* Example: "0.7.0-beta.1" and "0.7.0" are treated as the same version
|
|
655
|
+
*/
|
|
656
|
+
async checkAndUpdate() {
|
|
657
|
+
const storedVersion = await this.getStoredVersion();
|
|
658
|
+
if (!storedVersion) {
|
|
659
|
+
await this.setStoredVersion(this.currentVersion);
|
|
660
|
+
return { updated: false, toVersion: this.currentVersion };
|
|
661
|
+
}
|
|
662
|
+
if (storedVersion === this.currentVersion) {
|
|
663
|
+
return { updated: false, toVersion: this.currentVersion };
|
|
664
|
+
}
|
|
665
|
+
const migrationsToRun = this.getMigrationsToRun(storedVersion, this.currentVersion);
|
|
666
|
+
if (migrationsToRun.length === 0) {
|
|
667
|
+
await this.setStoredVersion(this.currentVersion);
|
|
668
|
+
return { updated: true, fromVersion: storedVersion, toVersion: this.currentVersion };
|
|
669
|
+
}
|
|
670
|
+
for (const migration of migrationsToRun) {
|
|
671
|
+
try {
|
|
672
|
+
console.log(`Running migration from ${migration.fromVersion} to ${migration.toVersion}`);
|
|
673
|
+
await migration.migrate(this.storage);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
console.error(`Migration failed from ${migration.fromVersion} to ${migration.toVersion}:`, error);
|
|
676
|
+
throw new Error(`Migration failed: ${error}`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
await this.setStoredVersion(this.currentVersion);
|
|
680
|
+
return { updated: true, fromVersion: storedVersion, toVersion: this.currentVersion };
|
|
681
|
+
}
|
|
682
|
+
async getStoredVersion() {
|
|
683
|
+
try {
|
|
684
|
+
const versionData = await this.storage.get(this.versionKey);
|
|
685
|
+
if (!versionData)
|
|
686
|
+
return null;
|
|
687
|
+
const versionInfo = JSON.parse(versionData);
|
|
688
|
+
return versionInfo.version;
|
|
689
|
+
} catch (error) {
|
|
690
|
+
console.warn("Failed to get stored version:", error);
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
async setStoredVersion(version2) {
|
|
695
|
+
const versionInfo = {
|
|
696
|
+
version: version2,
|
|
697
|
+
lastUpdated: Date.now()
|
|
698
|
+
};
|
|
699
|
+
await this.storage.set(this.versionKey, JSON.stringify(versionInfo));
|
|
700
|
+
}
|
|
701
|
+
getMigrationsToRun(fromVersion, toVersion) {
|
|
702
|
+
return this.migrations.filter((migration) => {
|
|
703
|
+
const storedLessThanMigrationTo = this.compareVersions(fromVersion, migration.toVersion) < 0;
|
|
704
|
+
const currentGreaterThanOrEqualMigrationTo = this.compareVersions(toVersion, migration.toVersion) >= 0;
|
|
705
|
+
console.log(`Checking migration ${migration.fromVersion} \u2192 ${migration.toVersion}:`);
|
|
706
|
+
console.log(` stored ${fromVersion} < migration.to ${migration.toVersion}: ${storedLessThanMigrationTo}`);
|
|
707
|
+
console.log(` current ${toVersion} >= migration.to ${migration.toVersion}: ${currentGreaterThanOrEqualMigrationTo}`);
|
|
708
|
+
const shouldRun = storedLessThanMigrationTo && currentGreaterThanOrEqualMigrationTo;
|
|
709
|
+
console.log(` Should run: ${shouldRun}`);
|
|
710
|
+
return shouldRun;
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Simple semantic version comparison (major.minor only, ignoring beta/prerelease)
|
|
715
|
+
* Returns: -1 if a < b, 0 if a === b, 1 if a > b
|
|
716
|
+
*/
|
|
717
|
+
compareVersions(a, b) {
|
|
718
|
+
const aMajorMinor = this.extractMajorMinor(a);
|
|
719
|
+
const bMajorMinor = this.extractMajorMinor(b);
|
|
720
|
+
if (aMajorMinor.major !== bMajorMinor.major) {
|
|
721
|
+
return aMajorMinor.major - bMajorMinor.major;
|
|
722
|
+
}
|
|
723
|
+
return aMajorMinor.minor - bMajorMinor.minor;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Extract major.minor from version string, ignoring beta/prerelease
|
|
727
|
+
* Examples: "0.7.0-beta.1" -> {major: 0, minor: 7}
|
|
728
|
+
* "1.2.3" -> {major: 1, minor: 2}
|
|
729
|
+
*/
|
|
730
|
+
extractMajorMinor(version2) {
|
|
731
|
+
const cleanVersion = version2.split("-")[0]?.split("+")[0] || version2;
|
|
732
|
+
const parts = cleanVersion.split(".").map(Number);
|
|
733
|
+
return {
|
|
734
|
+
major: parts[0] || 0,
|
|
735
|
+
minor: parts[1] || 0
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Add a migration to the updater
|
|
740
|
+
*/
|
|
741
|
+
addMigration(migration) {
|
|
742
|
+
this.migrations.push(migration);
|
|
743
|
+
this.migrations.sort((a, b) => this.compareVersions(a.fromVersion, b.fromVersion));
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
function createVersionUpdater(storage, currentVersion, migrations = []) {
|
|
747
|
+
return new VersionUpdater(storage, currentVersion, migrations);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/updater/updateMigrations.ts
|
|
751
|
+
var addMigrationTimestamp = {
|
|
752
|
+
fromVersion: "0.6.0",
|
|
753
|
+
toVersion: "0.7.0",
|
|
754
|
+
async migrate(storage) {
|
|
755
|
+
console.log("Running test migration");
|
|
756
|
+
storage.set("test_migration", "true");
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
function getMigrations() {
|
|
760
|
+
return [
|
|
761
|
+
addMigrationTimestamp
|
|
762
|
+
];
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/utils/storage.ts
|
|
453
766
|
var LocalStorageAdapter = class {
|
|
454
767
|
async get(key) {
|
|
455
768
|
return localStorage.getItem(key);
|
|
@@ -461,24 +774,112 @@ var LocalStorageAdapter = class {
|
|
|
461
774
|
localStorage.removeItem(key);
|
|
462
775
|
}
|
|
463
776
|
};
|
|
464
|
-
var
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
777
|
+
var STORAGE_KEYS = {
|
|
778
|
+
REFRESH_TOKEN: "basic_refresh_token",
|
|
779
|
+
USER_INFO: "basic_user_info",
|
|
780
|
+
AUTH_STATE: "basic_auth_state",
|
|
781
|
+
DEBUG: "basic_debug"
|
|
782
|
+
};
|
|
783
|
+
function getCookie(name) {
|
|
784
|
+
let cookieValue = "";
|
|
785
|
+
if (document.cookie && document.cookie !== "") {
|
|
786
|
+
const cookies = document.cookie.split(";");
|
|
787
|
+
for (let i = 0; i < cookies.length; i++) {
|
|
788
|
+
const cookie = cookies[i]?.trim();
|
|
789
|
+
if (cookie && cookie.substring(0, name.length + 1) === name + "=") {
|
|
790
|
+
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return cookieValue;
|
|
796
|
+
}
|
|
797
|
+
function setCookie(name, value, options) {
|
|
798
|
+
const opts = {
|
|
799
|
+
secure: true,
|
|
800
|
+
sameSite: "Strict",
|
|
801
|
+
httpOnly: false,
|
|
802
|
+
...options
|
|
803
|
+
};
|
|
804
|
+
let cookieString = `${name}=${value}`;
|
|
805
|
+
if (opts.secure)
|
|
806
|
+
cookieString += "; Secure";
|
|
807
|
+
if (opts.sameSite)
|
|
808
|
+
cookieString += `; SameSite=${opts.sameSite}`;
|
|
809
|
+
if (opts.httpOnly)
|
|
810
|
+
cookieString += "; HttpOnly";
|
|
811
|
+
document.cookie = cookieString;
|
|
812
|
+
}
|
|
813
|
+
function clearCookie(name) {
|
|
814
|
+
document.cookie = `${name}=; Secure; SameSite=Strict`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/utils/network.ts
|
|
818
|
+
function isDevelopment(debug) {
|
|
819
|
+
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname.includes("localhost") || window.location.hostname.includes("127.0.0.1") || window.location.hostname.includes(".local") || process.env.NODE_ENV === "development" || debug === true;
|
|
820
|
+
}
|
|
821
|
+
async function checkForNewVersion() {
|
|
822
|
+
try {
|
|
823
|
+
const isBeta = version.includes("beta");
|
|
824
|
+
const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
|
|
825
|
+
if (!response.ok) {
|
|
826
|
+
throw new Error("Failed to fetch version from npm");
|
|
827
|
+
}
|
|
828
|
+
const data = await response.json();
|
|
829
|
+
const latestVersion = data.version;
|
|
830
|
+
if (latestVersion !== version) {
|
|
831
|
+
console.warn("[basic] New version available:", latestVersion, `
|
|
832
|
+
run "npm install @basictech/react@${latestVersion}" to update`);
|
|
833
|
+
}
|
|
834
|
+
if (isBeta) {
|
|
835
|
+
log("thank you for being on basictech/react beta :)");
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
hasNewVersion: version !== latestVersion,
|
|
839
|
+
latestVersion,
|
|
840
|
+
currentVersion: version
|
|
841
|
+
};
|
|
842
|
+
} catch (error) {
|
|
843
|
+
log("Error checking for new version:", error);
|
|
844
|
+
return {
|
|
845
|
+
hasNewVersion: false,
|
|
846
|
+
latestVersion: null,
|
|
847
|
+
currentVersion: null
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function cleanOAuthParamsFromUrl() {
|
|
852
|
+
if (window.location.search.includes("code") || window.location.search.includes("state")) {
|
|
853
|
+
const url = new URL(window.location.href);
|
|
854
|
+
url.searchParams.delete("code");
|
|
855
|
+
url.searchParams.delete("state");
|
|
856
|
+
window.history.pushState({}, document.title, url.pathname + url.search);
|
|
857
|
+
log("Cleaned OAuth parameters from URL");
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function getSyncStatus(statusCode) {
|
|
861
|
+
switch (statusCode) {
|
|
862
|
+
case -1:
|
|
863
|
+
return "ERROR";
|
|
864
|
+
case 0:
|
|
865
|
+
return "OFFLINE";
|
|
866
|
+
case 1:
|
|
867
|
+
return "CONNECTING";
|
|
868
|
+
case 2:
|
|
869
|
+
return "ONLINE";
|
|
870
|
+
case 3:
|
|
871
|
+
return "SYNCING";
|
|
872
|
+
case 4:
|
|
873
|
+
return "ERROR_WILL_RETRY";
|
|
874
|
+
default:
|
|
875
|
+
return "UNKNOWN";
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/utils/schema.ts
|
|
880
|
+
var import_schema2 = require("@basictech/schema");
|
|
479
881
|
async function getSchemaStatus(schema) {
|
|
480
882
|
const projectId = schema.project_id;
|
|
481
|
-
let status = "";
|
|
482
883
|
const valid = (0, import_schema2.validateSchema)(schema);
|
|
483
884
|
if (!valid.valid) {
|
|
484
885
|
console.warn("BasicDB Error: your local schema is invalid. Please fix errors and try again - sync is disabled");
|
|
@@ -541,54 +942,55 @@ async function getSchemaStatus(schema) {
|
|
|
541
942
|
};
|
|
542
943
|
}
|
|
543
944
|
}
|
|
544
|
-
function
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
case 4:
|
|
557
|
-
return "ERROR_WILL_RETRY";
|
|
558
|
-
default:
|
|
559
|
-
return "UNKNOWN";
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
async function checkForNewVersion() {
|
|
563
|
-
try {
|
|
564
|
-
const isBeta = version.includes("beta");
|
|
565
|
-
const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
|
|
566
|
-
if (!response.ok) {
|
|
567
|
-
throw new Error("Failed to fetch version from npm");
|
|
568
|
-
}
|
|
569
|
-
const data = await response.json();
|
|
570
|
-
const latestVersion = data.version;
|
|
571
|
-
if (latestVersion !== version) {
|
|
572
|
-
console.warn("[basic] New version available:", latestVersion, `
|
|
573
|
-
run "npm install @basictech/react@${latestVersion}" to update`);
|
|
574
|
-
}
|
|
575
|
-
if (isBeta) {
|
|
576
|
-
log("thank you for being on basictech/react beta :)");
|
|
577
|
-
}
|
|
578
|
-
return {
|
|
579
|
-
hasNewVersion: version !== latestVersion,
|
|
580
|
-
latestVersion,
|
|
581
|
-
currentVersion: version
|
|
582
|
-
};
|
|
583
|
-
} catch (error) {
|
|
584
|
-
log("Error checking for new version:", error);
|
|
945
|
+
async function validateAndCheckSchema(schema) {
|
|
946
|
+
const valid = (0, import_schema2.validateSchema)(schema);
|
|
947
|
+
if (!valid.valid) {
|
|
948
|
+
log("Basic Schema is invalid!", valid.errors);
|
|
949
|
+
console.group("Schema Errors");
|
|
950
|
+
let errorMessage = "";
|
|
951
|
+
valid.errors.forEach((error, index) => {
|
|
952
|
+
log(`${index + 1}:`, error.message, ` - at ${error.instancePath}`);
|
|
953
|
+
errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}
|
|
954
|
+
`;
|
|
955
|
+
});
|
|
956
|
+
console.groupEnd();
|
|
585
957
|
return {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
958
|
+
isValid: false,
|
|
959
|
+
schemaStatus: { valid: false },
|
|
960
|
+
errors: valid.errors
|
|
589
961
|
};
|
|
590
962
|
}
|
|
963
|
+
let schemaStatus = { valid: false };
|
|
964
|
+
if (schema.version !== 0) {
|
|
965
|
+
schemaStatus = await getSchemaStatus(schema);
|
|
966
|
+
log("schemaStatus", schemaStatus);
|
|
967
|
+
} else {
|
|
968
|
+
log("schema not published - at version 0");
|
|
969
|
+
}
|
|
970
|
+
return {
|
|
971
|
+
isValid: true,
|
|
972
|
+
schemaStatus
|
|
973
|
+
};
|
|
591
974
|
}
|
|
975
|
+
|
|
976
|
+
// src/AuthContext.tsx
|
|
977
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
978
|
+
var BasicContext = (0, import_react.createContext)({
|
|
979
|
+
unicorn: "\u{1F984}",
|
|
980
|
+
isAuthReady: false,
|
|
981
|
+
isSignedIn: false,
|
|
982
|
+
user: null,
|
|
983
|
+
signout: () => Promise.resolve(),
|
|
984
|
+
signin: () => Promise.resolve(),
|
|
985
|
+
signinWithCode: () => new Promise(() => {
|
|
986
|
+
}),
|
|
987
|
+
getToken: () => new Promise(() => {
|
|
988
|
+
}),
|
|
989
|
+
getSignInLink: () => Promise.resolve(""),
|
|
990
|
+
db: {},
|
|
991
|
+
remoteDb: {},
|
|
992
|
+
dbStatus: "LOADING" /* LOADING */
|
|
993
|
+
});
|
|
592
994
|
function BasicProvider({
|
|
593
995
|
children,
|
|
594
996
|
project_id,
|
|
@@ -607,25 +1009,10 @@ function BasicProvider({
|
|
|
607
1009
|
const [isOnline, setIsOnline] = (0, import_react.useState)(navigator.onLine);
|
|
608
1010
|
const [pendingRefresh, setPendingRefresh] = (0, import_react.useState)(false);
|
|
609
1011
|
const syncRef = (0, import_react.useRef)(null);
|
|
1012
|
+
const remoteDbRef = (0, import_react.useRef)(null);
|
|
610
1013
|
const storageAdapter = storage || new LocalStorageAdapter();
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
USER_INFO: "basic_user_info",
|
|
614
|
-
AUTH_STATE: "basic_auth_state",
|
|
615
|
-
DEBUG: "basic_debug"
|
|
616
|
-
};
|
|
617
|
-
const isDevelopment = () => {
|
|
618
|
-
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname.includes("localhost") || window.location.hostname.includes("127.0.0.1") || window.location.hostname.includes(".local") || process.env.NODE_ENV === "development" || debug === true;
|
|
619
|
-
};
|
|
620
|
-
const cleanOAuthParamsFromUrl = () => {
|
|
621
|
-
if (window.location.search.includes("code") || window.location.search.includes("state")) {
|
|
622
|
-
const url = new URL(window.location.href);
|
|
623
|
-
url.searchParams.delete("code");
|
|
624
|
-
url.searchParams.delete("state");
|
|
625
|
-
window.history.pushState({}, document.title, url.pathname + url.search);
|
|
626
|
-
log("Cleaned OAuth parameters from URL");
|
|
627
|
-
}
|
|
628
|
-
};
|
|
1014
|
+
const isDevMode = () => isDevelopment(debug);
|
|
1015
|
+
const cleanOAuthParams = () => cleanOAuthParamsFromUrl();
|
|
629
1016
|
(0, import_react.useEffect)(() => {
|
|
630
1017
|
const handleOnline = () => {
|
|
631
1018
|
log("Network came back online");
|
|
@@ -662,9 +1049,6 @@ function BasicProvider({
|
|
|
662
1049
|
syncRef.current.syncable.on("statusChanged", (status, url) => {
|
|
663
1050
|
setDbStatus(getSyncStatus(status));
|
|
664
1051
|
});
|
|
665
|
-
syncRef.current.syncable.getStatus().then((status) => {
|
|
666
|
-
setDbStatus(getSyncStatus(status));
|
|
667
|
-
});
|
|
668
1052
|
if (options.shouldConnect) {
|
|
669
1053
|
setShouldConnect(true);
|
|
670
1054
|
} else {
|
|
@@ -674,17 +1058,15 @@ function BasicProvider({
|
|
|
674
1058
|
}
|
|
675
1059
|
}
|
|
676
1060
|
async function checkSchema() {
|
|
677
|
-
const
|
|
678
|
-
if (!
|
|
679
|
-
log("Basic Schema is invalid!", valid.errors);
|
|
680
|
-
console.group("Schema Errors");
|
|
1061
|
+
const result = await validateAndCheckSchema(schema);
|
|
1062
|
+
if (!result.isValid) {
|
|
681
1063
|
let errorMessage = "";
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
1064
|
+
if (result.errors) {
|
|
1065
|
+
result.errors.forEach((error2, index) => {
|
|
1066
|
+
errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
|
|
685
1067
|
`;
|
|
686
|
-
|
|
687
|
-
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
688
1070
|
setError({
|
|
689
1071
|
code: "schema_invalid",
|
|
690
1072
|
title: "Basic Schema is invalid!",
|
|
@@ -693,17 +1075,10 @@ function BasicProvider({
|
|
|
693
1075
|
setIsReady(true);
|
|
694
1076
|
return null;
|
|
695
1077
|
}
|
|
696
|
-
|
|
697
|
-
if (schema.version !== 0) {
|
|
698
|
-
schemaStatus = await getSchemaStatus(schema);
|
|
699
|
-
log("schemaStatus", schemaStatus);
|
|
700
|
-
} else {
|
|
701
|
-
log("schema not published - at version 0");
|
|
702
|
-
}
|
|
703
|
-
if (schemaStatus.valid) {
|
|
1078
|
+
if (result.schemaStatus.valid) {
|
|
704
1079
|
initDb({ shouldConnect: true });
|
|
705
1080
|
} else {
|
|
706
|
-
log("Schema is invalid!", schemaStatus);
|
|
1081
|
+
log("Schema is invalid!", result.schemaStatus);
|
|
707
1082
|
initDb({ shouldConnect: false });
|
|
708
1083
|
}
|
|
709
1084
|
checkForNewVersion();
|
|
@@ -715,43 +1090,72 @@ function BasicProvider({
|
|
|
715
1090
|
}
|
|
716
1091
|
}, []);
|
|
717
1092
|
(0, import_react.useEffect)(() => {
|
|
718
|
-
|
|
719
|
-
|
|
1093
|
+
async function connectToDb() {
|
|
1094
|
+
if (token && syncRef.current && isSignedIn && shouldConnect) {
|
|
1095
|
+
const tok = await getToken();
|
|
1096
|
+
if (!tok) {
|
|
1097
|
+
log("no token found");
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
log("connecting to db...");
|
|
1101
|
+
syncRef.current?.connect({ access_token: tok }).catch((e) => {
|
|
1102
|
+
log("error connecting to db", e);
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
720
1105
|
}
|
|
1106
|
+
connectToDb();
|
|
721
1107
|
}, [isSignedIn, shouldConnect]);
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
1108
|
+
(0, import_react.useEffect)(() => {
|
|
1109
|
+
if (project_id && schema && token?.access_token && !remoteDbRef.current) {
|
|
1110
|
+
log("Initializing Remote DB SDK");
|
|
1111
|
+
remoteDbRef.current = new BasicDBSDK({
|
|
1112
|
+
project_id,
|
|
1113
|
+
schema,
|
|
1114
|
+
getToken: () => getToken(),
|
|
1115
|
+
baseUrl: "https://api.basic.tech"
|
|
1116
|
+
});
|
|
727
1117
|
}
|
|
728
|
-
|
|
729
|
-
syncRef.current.connect({ access_token: tok }).catch((e) => {
|
|
730
|
-
log("error connecting to db", e);
|
|
731
|
-
});
|
|
732
|
-
};
|
|
1118
|
+
}, [token, project_id, schema]);
|
|
733
1119
|
(0, import_react.useEffect)(() => {
|
|
734
1120
|
const initializeAuth = async () => {
|
|
735
1121
|
await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
|
|
1122
|
+
try {
|
|
1123
|
+
const versionUpdater = createVersionUpdater(storageAdapter, version, getMigrations());
|
|
1124
|
+
const updateResult = await versionUpdater.checkAndUpdate();
|
|
1125
|
+
if (updateResult.updated) {
|
|
1126
|
+
log(`App updated from ${updateResult.fromVersion} to ${updateResult.toVersion}`);
|
|
1127
|
+
} else {
|
|
1128
|
+
log(`App version ${updateResult.toVersion} is current`);
|
|
1129
|
+
}
|
|
1130
|
+
} catch (error2) {
|
|
1131
|
+
log("Version update failed:", error2);
|
|
1132
|
+
}
|
|
736
1133
|
try {
|
|
737
1134
|
if (window.location.search.includes("code")) {
|
|
738
|
-
let code = window.location?.search?.split("code=")[1]
|
|
1135
|
+
let code = window.location?.search?.split("code=")[1]?.split("&")[0];
|
|
1136
|
+
if (!code)
|
|
1137
|
+
return;
|
|
739
1138
|
const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
|
|
740
|
-
|
|
1139
|
+
const urlState = window.location.search.split("state=")[1]?.split("&")[0];
|
|
1140
|
+
if (!state || state !== urlState) {
|
|
741
1141
|
log("error: auth state does not match");
|
|
742
1142
|
setIsAuthReady(true);
|
|
743
1143
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
744
|
-
|
|
1144
|
+
cleanOAuthParams();
|
|
745
1145
|
return;
|
|
746
1146
|
}
|
|
747
1147
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
748
|
-
|
|
749
|
-
fetchToken(code)
|
|
1148
|
+
cleanOAuthParams();
|
|
1149
|
+
fetchToken(code).catch((error2) => {
|
|
1150
|
+
log("Error fetching token:", error2);
|
|
1151
|
+
});
|
|
750
1152
|
} else {
|
|
751
1153
|
const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
|
|
752
1154
|
if (refreshToken) {
|
|
753
1155
|
log("Found refresh token in storage, attempting to refresh access token");
|
|
754
|
-
fetchToken(refreshToken)
|
|
1156
|
+
fetchToken(refreshToken).catch((error2) => {
|
|
1157
|
+
log("Error fetching refresh token:", error2);
|
|
1158
|
+
});
|
|
755
1159
|
} else {
|
|
756
1160
|
let cookie_token = getCookie("basic_token");
|
|
757
1161
|
if (cookie_token !== "") {
|
|
@@ -800,8 +1204,8 @@ function BasicProvider({
|
|
|
800
1204
|
}
|
|
801
1205
|
await storageAdapter.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user2));
|
|
802
1206
|
log("Cached user info in storage");
|
|
803
|
-
|
|
804
|
-
|
|
1207
|
+
setCookie("basic_access_token", token?.access_token || "", { httpOnly: false });
|
|
1208
|
+
setCookie("basic_token", JSON.stringify(token));
|
|
805
1209
|
setUser(user2);
|
|
806
1210
|
setIsSignedIn(true);
|
|
807
1211
|
setIsAuthReady(true);
|
|
@@ -818,19 +1222,19 @@ function BasicProvider({
|
|
|
818
1222
|
if (isExpired) {
|
|
819
1223
|
log("token is expired - refreshing ...");
|
|
820
1224
|
try {
|
|
821
|
-
const newToken = await fetchToken(token?.refresh_token);
|
|
822
|
-
fetchUser(newToken
|
|
1225
|
+
const newToken = await fetchToken(token?.refresh_token || "");
|
|
1226
|
+
fetchUser(newToken?.access_token || "");
|
|
823
1227
|
} catch (error2) {
|
|
824
1228
|
log("Failed to refresh token in checkToken:", error2);
|
|
825
1229
|
if (error2.message.includes("offline") || error2.message.includes("Network")) {
|
|
826
1230
|
log("Network issue - continuing with expired token until online");
|
|
827
|
-
fetchUser(token
|
|
1231
|
+
fetchUser(token?.access_token || "");
|
|
828
1232
|
} else {
|
|
829
1233
|
setIsAuthReady(true);
|
|
830
1234
|
}
|
|
831
1235
|
}
|
|
832
1236
|
} else {
|
|
833
|
-
fetchUser(token
|
|
1237
|
+
fetchUser(token?.access_token || "");
|
|
834
1238
|
}
|
|
835
1239
|
}
|
|
836
1240
|
if (token) {
|
|
@@ -849,14 +1253,14 @@ function BasicProvider({
|
|
|
849
1253
|
if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
|
|
850
1254
|
throw new Error("Invalid redirect URI provided");
|
|
851
1255
|
}
|
|
852
|
-
let
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1256
|
+
let baseUrl = "https://api.basic.tech/auth/authorize";
|
|
1257
|
+
baseUrl += `?client_id=${project_id}`;
|
|
1258
|
+
baseUrl += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
|
|
1259
|
+
baseUrl += `&response_type=code`;
|
|
1260
|
+
baseUrl += `&scope=profile`;
|
|
1261
|
+
baseUrl += `&state=${randomState}`;
|
|
858
1262
|
log("Generated sign-in link successfully");
|
|
859
|
-
return
|
|
1263
|
+
return baseUrl;
|
|
860
1264
|
} catch (error2) {
|
|
861
1265
|
log("Error generating sign-in link:", error2);
|
|
862
1266
|
throw error2;
|
|
@@ -878,7 +1282,7 @@ function BasicProvider({
|
|
|
878
1282
|
window.location.href = signInLink;
|
|
879
1283
|
} catch (error2) {
|
|
880
1284
|
log("Error during sign-in:", error2);
|
|
881
|
-
if (
|
|
1285
|
+
if (isDevMode()) {
|
|
882
1286
|
setError({
|
|
883
1287
|
code: "signin_error",
|
|
884
1288
|
title: "Sign-in Failed",
|
|
@@ -902,7 +1306,7 @@ function BasicProvider({
|
|
|
902
1306
|
}
|
|
903
1307
|
}
|
|
904
1308
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
905
|
-
|
|
1309
|
+
cleanOAuthParams();
|
|
906
1310
|
const token2 = await fetchToken(code);
|
|
907
1311
|
if (token2) {
|
|
908
1312
|
log("signinWithCode successful");
|
|
@@ -923,16 +1327,16 @@ function BasicProvider({
|
|
|
923
1327
|
setUser({});
|
|
924
1328
|
setIsSignedIn(false);
|
|
925
1329
|
setToken(null);
|
|
926
|
-
|
|
927
|
-
|
|
1330
|
+
clearCookie("basic_token");
|
|
1331
|
+
clearCookie("basic_access_token");
|
|
928
1332
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
|
|
929
1333
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
930
1334
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
931
1335
|
if (syncRef.current) {
|
|
932
1336
|
(async () => {
|
|
933
1337
|
try {
|
|
934
|
-
await syncRef.current
|
|
935
|
-
await syncRef.current
|
|
1338
|
+
await syncRef.current?.close();
|
|
1339
|
+
await syncRef.current?.delete({ disableAutoOpen: false });
|
|
936
1340
|
syncRef.current = null;
|
|
937
1341
|
window?.location?.reload();
|
|
938
1342
|
} catch (error2) {
|
|
@@ -991,20 +1395,6 @@ function BasicProvider({
|
|
|
991
1395
|
}
|
|
992
1396
|
return token?.access_token || "";
|
|
993
1397
|
};
|
|
994
|
-
function getCookie(name) {
|
|
995
|
-
let cookieValue = "";
|
|
996
|
-
if (document.cookie && document.cookie !== "") {
|
|
997
|
-
const cookies = document.cookie.split(";");
|
|
998
|
-
for (let i = 0; i < cookies.length; i++) {
|
|
999
|
-
const cookie = cookies[i].trim();
|
|
1000
|
-
if (cookie.substring(0, name.length + 1) === name + "=") {
|
|
1001
|
-
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
1002
|
-
break;
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
return cookieValue;
|
|
1007
|
-
}
|
|
1008
1398
|
const fetchToken = async (code) => {
|
|
1009
1399
|
try {
|
|
1010
1400
|
if (!isOnline) {
|
|
@@ -1034,8 +1424,8 @@ function BasicProvider({
|
|
|
1034
1424
|
}
|
|
1035
1425
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1036
1426
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1037
|
-
|
|
1038
|
-
|
|
1427
|
+
clearCookie("basic_token");
|
|
1428
|
+
clearCookie("basic_access_token");
|
|
1039
1429
|
setUser({});
|
|
1040
1430
|
setIsSignedIn(false);
|
|
1041
1431
|
setToken(null);
|
|
@@ -1048,7 +1438,7 @@ function BasicProvider({
|
|
|
1048
1438
|
await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
|
|
1049
1439
|
log("Updated refresh token in storage");
|
|
1050
1440
|
}
|
|
1051
|
-
|
|
1441
|
+
setCookie("basic_access_token", token2.access_token, { httpOnly: false });
|
|
1052
1442
|
log("Updated access token in cookie");
|
|
1053
1443
|
}
|
|
1054
1444
|
return token2;
|
|
@@ -1057,8 +1447,8 @@ function BasicProvider({
|
|
|
1057
1447
|
if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
|
|
1058
1448
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
|
|
1059
1449
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
|
|
1060
|
-
|
|
1061
|
-
|
|
1450
|
+
clearCookie("basic_token");
|
|
1451
|
+
clearCookie("basic_access_token");
|
|
1062
1452
|
setUser({});
|
|
1063
1453
|
setIsSignedIn(false);
|
|
1064
1454
|
setToken(null);
|
|
@@ -1067,35 +1457,6 @@ function BasicProvider({
|
|
|
1067
1457
|
throw error2;
|
|
1068
1458
|
}
|
|
1069
1459
|
};
|
|
1070
|
-
const db_ = (tableName) => {
|
|
1071
|
-
const checkSignIn = () => {
|
|
1072
|
-
if (!isSignedIn) {
|
|
1073
|
-
throw new Error("cannot use db. user not logged in.");
|
|
1074
|
-
}
|
|
1075
|
-
};
|
|
1076
|
-
return {
|
|
1077
|
-
get: async () => {
|
|
1078
|
-
checkSignIn();
|
|
1079
|
-
const tok = await getToken();
|
|
1080
|
-
return get({ projectId: project_id, accountId: user.id, tableName, token: tok });
|
|
1081
|
-
},
|
|
1082
|
-
add: async (value) => {
|
|
1083
|
-
checkSignIn();
|
|
1084
|
-
const tok = await getToken();
|
|
1085
|
-
return add({ projectId: project_id, accountId: user.id, tableName, value, token: tok });
|
|
1086
|
-
},
|
|
1087
|
-
update: async (id, value) => {
|
|
1088
|
-
checkSignIn();
|
|
1089
|
-
const tok = await getToken();
|
|
1090
|
-
return update({ projectId: project_id, accountId: user.id, tableName, id, value, token: tok });
|
|
1091
|
-
},
|
|
1092
|
-
delete: async (id) => {
|
|
1093
|
-
checkSignIn();
|
|
1094
|
-
const tok = await getToken();
|
|
1095
|
-
return deleteRecord({ projectId: project_id, accountId: user.id, tableName, id, token: tok });
|
|
1096
|
-
}
|
|
1097
|
-
};
|
|
1098
|
-
};
|
|
1099
1460
|
const noDb = {
|
|
1100
1461
|
collection: () => {
|
|
1101
1462
|
throw new Error("no basicdb found - initialization failed. double check your schema.");
|
|
@@ -1112,9 +1473,10 @@ function BasicProvider({
|
|
|
1112
1473
|
getToken,
|
|
1113
1474
|
getSignInLink,
|
|
1114
1475
|
db: syncRef.current ? syncRef.current : noDb,
|
|
1476
|
+
remoteDb: remoteDbRef.current ? remoteDbRef.current : noDb,
|
|
1115
1477
|
dbStatus
|
|
1116
1478
|
}, children: [
|
|
1117
|
-
error &&
|
|
1479
|
+
error && isDevMode() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
|
|
1118
1480
|
isReady && children
|
|
1119
1481
|
] });
|
|
1120
1482
|
}
|
|
@@ -1149,6 +1511,7 @@ function useBasic() {
|
|
|
1149
1511
|
var import_dexie_react_hooks = require("dexie-react-hooks");
|
|
1150
1512
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1151
1513
|
0 && (module.exports = {
|
|
1514
|
+
BasicDBSDK,
|
|
1152
1515
|
BasicProvider,
|
|
1153
1516
|
useBasic,
|
|
1154
1517
|
useQuery
|