@basictech/react 0.7.0-beta.0 → 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/dist/index.js CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ BasicDBSDK: () => BasicDBSDK,
23
24
  BasicProvider: () => BasicProvider,
24
25
  useBasic: () => useBasic,
25
26
  useQuery: () => import_dexie_react_hooks.useLiveQuery
@@ -278,78 +279,607 @@ var BasicSync = class extends import_dexie2.Dexie {
278
279
  }
279
280
  };
280
281
 
281
- // src/db.ts
282
- var baseUrl = "https://api.basic.tech";
283
- async function get({ projectId, accountId, tableName, token }) {
284
- const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}`;
285
- const response = await fetch(url, {
286
- headers: {
287
- "Authorization": `Bearer ${token}`
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
+ }
288
306
  }
289
- });
290
- return response.json();
307
+ }
308
+ // Validate operator based on field type
309
+ validateOperator(field, operator, value) {
310
+ if (!this.tableSchema || this.reservedFields.includes(field)) {
311
+ return;
312
+ }
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
+ };
636
+
637
+ // package.json
638
+ var version = "0.7.0-beta.1";
639
+
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);
291
748
  }
292
- async function add({ projectId, accountId, tableName, value, token }) {
293
- const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}`;
294
- const response = await fetch(url, {
295
- method: "POST",
296
- headers: {
297
- "Content-Type": "application/json",
298
- "Authorization": `Bearer ${token}`
299
- },
300
- body: JSON.stringify({ "value": value })
301
- });
302
- return response.json();
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
+ ];
303
763
  }
304
- async function update({ projectId, accountId, tableName, id, value, token }) {
305
- const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}/${id}`;
306
- const response = await fetch(url, {
307
- method: "PATCH",
308
- headers: {
309
- "Content-Type": "application/json",
310
- "Authorization": `Bearer ${token}`
311
- },
312
- body: JSON.stringify({ id, value })
313
- });
314
- return response.json();
764
+
765
+ // src/utils/storage.ts
766
+ var LocalStorageAdapter = class {
767
+ async get(key) {
768
+ return localStorage.getItem(key);
769
+ }
770
+ async set(key, value) {
771
+ localStorage.setItem(key, value);
772
+ }
773
+ async remove(key) {
774
+ localStorage.removeItem(key);
775
+ }
776
+ };
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;
315
820
  }
316
- async function deleteRecord({ projectId, accountId, tableName, id, token }) {
317
- const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}/${id}`;
318
- const response = await fetch(url, {
319
- method: "DELETE",
320
- headers: {
321
- "Authorization": `Bearer ${token}`
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");
322
827
  }
323
- });
324
- return response.json();
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
+ }
325
877
  }
326
878
 
327
- // src/AuthContext.tsx
879
+ // src/utils/schema.ts
328
880
  var import_schema2 = require("@basictech/schema");
329
-
330
- // package.json
331
- var version = "0.6.0";
332
-
333
- // src/AuthContext.tsx
334
- var import_jsx_runtime = require("react/jsx-runtime");
335
- var BasicContext = (0, import_react.createContext)({
336
- unicorn: "\u{1F984}",
337
- isAuthReady: false,
338
- isSignedIn: false,
339
- user: null,
340
- signout: () => {
341
- },
342
- signin: () => {
343
- },
344
- getToken: () => new Promise(() => {
345
- }),
346
- getSignInLink: () => "",
347
- db: {},
348
- dbStatus: "LOADING" /* LOADING */
349
- });
350
881
  async function getSchemaStatus(schema) {
351
882
  const projectId = schema.project_id;
352
- let status = "";
353
883
  const valid = (0, import_schema2.validateSchema)(schema);
354
884
  if (!valid.valid) {
355
885
  console.warn("BasicDB Error: your local schema is invalid. Please fix errors and try again - sync is disabled");
@@ -412,55 +942,62 @@ async function getSchemaStatus(schema) {
412
942
  };
413
943
  }
414
944
  }
415
- function getSyncStatus(statusCode) {
416
- switch (statusCode) {
417
- case -1:
418
- return "ERROR";
419
- case 0:
420
- return "OFFLINE";
421
- case 1:
422
- return "CONNECTING";
423
- case 2:
424
- return "ONLINE";
425
- case 3:
426
- return "SYNCING";
427
- case 4:
428
- return "ERROR_WILL_RETRY";
429
- default:
430
- return "UNKNOWN";
431
- }
432
- }
433
- async function checkForNewVersion() {
434
- try {
435
- const isBeta = version.includes("beta");
436
- const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
437
- if (!response.ok) {
438
- throw new Error("Failed to fetch version from npm");
439
- }
440
- const data = await response.json();
441
- const latestVersion = data.version;
442
- if (latestVersion !== version) {
443
- console.warn("[basic] New version available:", latestVersion, `
444
- run "npm install @basictech/react@${latestVersion}" to update`);
445
- }
446
- if (isBeta) {
447
- log("thank you for being on basictech/react beta :)");
448
- }
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();
449
957
  return {
450
- hasNewVersion: version !== latestVersion,
451
- latestVersion,
452
- currentVersion: version
453
- };
454
- } catch (error) {
455
- log("Error checking for new version:", error);
456
- return {
457
- hasNewVersion: false,
458
- latestVersion: null,
459
- currentVersion: null
958
+ isValid: false,
959
+ schemaStatus: { valid: false },
960
+ errors: valid.errors
460
961
  };
461
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
+ };
462
974
  }
463
- function BasicProvider({ children, project_id, schema, debug = false }) {
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
+ });
994
+ function BasicProvider({
995
+ children,
996
+ project_id,
997
+ schema,
998
+ debug = false,
999
+ storage
1000
+ }) {
464
1001
  const [isAuthReady, setIsAuthReady] = (0, import_react.useState)(false);
465
1002
  const [isSignedIn, setIsSignedIn] = (0, import_react.useState)(false);
466
1003
  const [token, setToken] = (0, import_react.useState)(null);
@@ -469,7 +1006,41 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
469
1006
  const [isReady, setIsReady] = (0, import_react.useState)(false);
470
1007
  const [dbStatus, setDbStatus] = (0, import_react.useState)("OFFLINE" /* OFFLINE */);
471
1008
  const [error, setError] = (0, import_react.useState)(null);
1009
+ const [isOnline, setIsOnline] = (0, import_react.useState)(navigator.onLine);
1010
+ const [pendingRefresh, setPendingRefresh] = (0, import_react.useState)(false);
472
1011
  const syncRef = (0, import_react.useRef)(null);
1012
+ const remoteDbRef = (0, import_react.useRef)(null);
1013
+ const storageAdapter = storage || new LocalStorageAdapter();
1014
+ const isDevMode = () => isDevelopment(debug);
1015
+ const cleanOAuthParams = () => cleanOAuthParamsFromUrl();
1016
+ (0, import_react.useEffect)(() => {
1017
+ const handleOnline = () => {
1018
+ log("Network came back online");
1019
+ setIsOnline(true);
1020
+ if (pendingRefresh) {
1021
+ log("Retrying pending token refresh");
1022
+ setPendingRefresh(false);
1023
+ if (token) {
1024
+ const refreshToken = token.refresh_token || localStorage.getItem("basic_refresh_token");
1025
+ if (refreshToken) {
1026
+ fetchToken(refreshToken).catch((error2) => {
1027
+ log("Retry refresh failed:", error2);
1028
+ });
1029
+ }
1030
+ }
1031
+ }
1032
+ };
1033
+ const handleOffline = () => {
1034
+ log("Network went offline");
1035
+ setIsOnline(false);
1036
+ };
1037
+ window.addEventListener("online", handleOnline);
1038
+ window.addEventListener("offline", handleOffline);
1039
+ return () => {
1040
+ window.removeEventListener("online", handleOnline);
1041
+ window.removeEventListener("offline", handleOffline);
1042
+ };
1043
+ }, [pendingRefresh, token]);
473
1044
  (0, import_react.useEffect)(() => {
474
1045
  function initDb(options) {
475
1046
  if (!syncRef.current) {
@@ -478,9 +1049,6 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
478
1049
  syncRef.current.syncable.on("statusChanged", (status, url) => {
479
1050
  setDbStatus(getSyncStatus(status));
480
1051
  });
481
- syncRef.current.syncable.getStatus().then((status) => {
482
- setDbStatus(getSyncStatus(status));
483
- });
484
1052
  if (options.shouldConnect) {
485
1053
  setShouldConnect(true);
486
1054
  } else {
@@ -490,17 +1058,15 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
490
1058
  }
491
1059
  }
492
1060
  async function checkSchema() {
493
- const valid = (0, import_schema2.validateSchema)(schema);
494
- if (!valid.valid) {
495
- log("Basic Schema is invalid!", valid.errors);
496
- console.group("Schema Errors");
1061
+ const result = await validateAndCheckSchema(schema);
1062
+ if (!result.isValid) {
497
1063
  let errorMessage = "";
498
- valid.errors.forEach((error2, index) => {
499
- log(`${index + 1}:`, error2.message, ` - at ${error2.instancePath}`);
500
- errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
1064
+ if (result.errors) {
1065
+ result.errors.forEach((error2, index) => {
1066
+ errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
501
1067
  `;
502
- });
503
- console.groupEnd("Schema Errors");
1068
+ });
1069
+ }
504
1070
  setError({
505
1071
  code: "schema_invalid",
506
1072
  title: "Basic Schema is invalid!",
@@ -509,17 +1075,10 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
509
1075
  setIsReady(true);
510
1076
  return null;
511
1077
  }
512
- let schemaStatus = { valid: false };
513
- if (schema.version !== 0) {
514
- schemaStatus = await getSchemaStatus(schema);
515
- log("schemaStatus", schemaStatus);
516
- } else {
517
- log("schema not published - at version 0");
518
- }
519
- if (schemaStatus.valid) {
1078
+ if (result.schemaStatus.valid) {
520
1079
  initDb({ shouldConnect: true });
521
1080
  } else {
522
- log("Schema is invalid!", schemaStatus);
1081
+ log("Schema is invalid!", result.schemaStatus);
523
1082
  initDb({ shouldConnect: false });
524
1083
  }
525
1084
  checkForNewVersion();
@@ -531,36 +1090,101 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
531
1090
  }
532
1091
  }, []);
533
1092
  (0, import_react.useEffect)(() => {
534
- if (token && syncRef.current && isSignedIn && shouldConnect) {
535
- connectToDb();
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
+ }
536
1105
  }
1106
+ connectToDb();
537
1107
  }, [isSignedIn, shouldConnect]);
538
1108
  (0, import_react.useEffect)(() => {
539
- localStorage.setItem("basic_debug", debug ? "true" : "false");
540
- try {
541
- if (window.location.search.includes("code")) {
542
- let code = window.location?.search?.split("code=")[1].split("&")[0];
543
- const state = localStorage.getItem("basic_auth_state");
544
- if (!state || state !== window.location.search.split("state=")[1].split("&")[0]) {
545
- log("error: auth state does not match");
546
- setIsAuthReady(true);
547
- localStorage.removeItem("basic_auth_state");
548
- window.history.pushState({}, document.title, "/");
549
- return;
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
+ });
1117
+ }
1118
+ }, [token, project_id, schema]);
1119
+ (0, import_react.useEffect)(() => {
1120
+ const initializeAuth = async () => {
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`);
550
1129
  }
551
- localStorage.removeItem("basic_auth_state");
552
- fetchToken(code);
553
- } else {
554
- let cookie_token = getCookie("basic_token");
555
- if (cookie_token !== "") {
556
- setToken(JSON.parse(cookie_token));
1130
+ } catch (error2) {
1131
+ log("Version update failed:", error2);
1132
+ }
1133
+ try {
1134
+ if (window.location.search.includes("code")) {
1135
+ let code = window.location?.search?.split("code=")[1]?.split("&")[0];
1136
+ if (!code)
1137
+ return;
1138
+ const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
1139
+ const urlState = window.location.search.split("state=")[1]?.split("&")[0];
1140
+ if (!state || state !== urlState) {
1141
+ log("error: auth state does not match");
1142
+ setIsAuthReady(true);
1143
+ await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
1144
+ cleanOAuthParams();
1145
+ return;
1146
+ }
1147
+ await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
1148
+ cleanOAuthParams();
1149
+ fetchToken(code).catch((error2) => {
1150
+ log("Error fetching token:", error2);
1151
+ });
557
1152
  } else {
558
- setIsAuthReady(true);
1153
+ const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
1154
+ if (refreshToken) {
1155
+ log("Found refresh token in storage, attempting to refresh access token");
1156
+ fetchToken(refreshToken).catch((error2) => {
1157
+ log("Error fetching refresh token:", error2);
1158
+ });
1159
+ } else {
1160
+ let cookie_token = getCookie("basic_token");
1161
+ if (cookie_token !== "") {
1162
+ const tokenData = JSON.parse(cookie_token);
1163
+ setToken(tokenData);
1164
+ if (tokenData.refresh_token) {
1165
+ await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token);
1166
+ }
1167
+ } else {
1168
+ const cachedUserInfo = await storageAdapter.get(STORAGE_KEYS.USER_INFO);
1169
+ if (cachedUserInfo) {
1170
+ try {
1171
+ const userData = JSON.parse(cachedUserInfo);
1172
+ setUser(userData);
1173
+ setIsSignedIn(true);
1174
+ log("Loaded cached user info for offline mode");
1175
+ } catch (error2) {
1176
+ log("Error parsing cached user info:", error2);
1177
+ }
1178
+ }
1179
+ setIsAuthReady(true);
1180
+ }
1181
+ }
559
1182
  }
1183
+ } catch (e) {
1184
+ log("error getting token", e);
560
1185
  }
561
- } catch (e) {
562
- log("error getting cookie", e);
563
- }
1186
+ };
1187
+ initializeAuth();
564
1188
  }, []);
565
1189
  (0, import_react.useEffect)(() => {
566
1190
  async function fetchUser(acc_token) {
@@ -575,10 +1199,13 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
575
1199
  log("error fetching user", user2.error);
576
1200
  return;
577
1201
  } else {
578
- document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
579
- if (window.location.search.includes("code")) {
580
- window.history.pushState({}, document.title, "/");
1202
+ if (token?.refresh_token) {
1203
+ await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
581
1204
  }
1205
+ await storageAdapter.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user2));
1206
+ log("Cached user info in storage");
1207
+ setCookie("basic_access_token", token?.access_token || "", { httpOnly: false });
1208
+ setCookie("basic_token", JSON.stringify(token));
582
1209
  setUser(user2);
583
1210
  setIsSignedIn(true);
584
1211
  setIsAuthReady(true);
@@ -594,56 +1221,122 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
594
1221
  const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
595
1222
  if (isExpired) {
596
1223
  log("token is expired - refreshing ...");
597
- const newToken = await fetchToken(token?.refresh);
598
- fetchUser(newToken.access_token);
1224
+ try {
1225
+ const newToken = await fetchToken(token?.refresh_token || "");
1226
+ fetchUser(newToken?.access_token || "");
1227
+ } catch (error2) {
1228
+ log("Failed to refresh token in checkToken:", error2);
1229
+ if (error2.message.includes("offline") || error2.message.includes("Network")) {
1230
+ log("Network issue - continuing with expired token until online");
1231
+ fetchUser(token?.access_token || "");
1232
+ } else {
1233
+ setIsAuthReady(true);
1234
+ }
1235
+ }
599
1236
  } else {
600
- fetchUser(token.access_token);
1237
+ fetchUser(token?.access_token || "");
601
1238
  }
602
1239
  }
603
1240
  if (token) {
604
1241
  checkToken();
605
1242
  }
606
1243
  }, [token]);
607
- const connectToDb = async () => {
608
- const tok = await getToken();
609
- if (!tok) {
610
- log("no token found");
611
- return;
1244
+ const getSignInLink = async (redirectUri) => {
1245
+ try {
1246
+ log("getting sign in link...");
1247
+ if (!project_id) {
1248
+ throw new Error("Project ID is required to generate sign-in link");
1249
+ }
1250
+ const randomState = Math.random().toString(36).substring(6);
1251
+ await storageAdapter.set(STORAGE_KEYS.AUTH_STATE, randomState);
1252
+ const redirectUrl = redirectUri || window.location.href;
1253
+ if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
1254
+ throw new Error("Invalid redirect URI provided");
1255
+ }
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}`;
1262
+ log("Generated sign-in link successfully");
1263
+ return baseUrl;
1264
+ } catch (error2) {
1265
+ log("Error generating sign-in link:", error2);
1266
+ throw error2;
612
1267
  }
613
- log("connecting to db...");
614
- syncRef.current.connect({ access_token: tok }).catch((e) => {
615
- log("error connecting to db", e);
616
- });
617
1268
  };
618
- const getSignInLink = () => {
619
- log("getting sign in link...");
620
- const randomState = Math.random().toString(36).substring(6);
621
- localStorage.setItem("basic_auth_state", randomState);
622
- let baseUrl2 = "https://api.basic.tech/auth/authorize";
623
- baseUrl2 += `?client_id=${project_id}`;
624
- baseUrl2 += `&redirect_uri=${encodeURIComponent(window.location.href)}`;
625
- baseUrl2 += `&response_type=code`;
626
- baseUrl2 += `&scope=profile`;
627
- baseUrl2 += `&state=${randomState}`;
628
- return baseUrl2;
1269
+ const signin = async () => {
1270
+ try {
1271
+ log("signing in...");
1272
+ if (!project_id) {
1273
+ log("Error: project_id is required for sign-in");
1274
+ throw new Error("Project ID is required for authentication");
1275
+ }
1276
+ const signInLink = await getSignInLink();
1277
+ log("Generated sign-in link:", signInLink);
1278
+ if (!signInLink || !signInLink.startsWith("https://")) {
1279
+ log("Error: Invalid sign-in link generated");
1280
+ throw new Error("Failed to generate valid sign-in URL");
1281
+ }
1282
+ window.location.href = signInLink;
1283
+ } catch (error2) {
1284
+ log("Error during sign-in:", error2);
1285
+ if (isDevMode()) {
1286
+ setError({
1287
+ code: "signin_error",
1288
+ title: "Sign-in Failed",
1289
+ message: error2.message || "An error occurred during sign-in. Please try again."
1290
+ });
1291
+ }
1292
+ throw error2;
1293
+ }
629
1294
  };
630
- const signin = () => {
631
- log("signing in: ", getSignInLink());
632
- const signInLink = getSignInLink();
633
- window.location.href = signInLink;
1295
+ const signinWithCode = async (code, state) => {
1296
+ try {
1297
+ log("signinWithCode called with code:", code);
1298
+ if (!code || typeof code !== "string") {
1299
+ return { success: false, error: "Invalid authorization code" };
1300
+ }
1301
+ if (state) {
1302
+ const storedState = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
1303
+ if (storedState && storedState !== state) {
1304
+ log("State parameter mismatch:", { provided: state, stored: storedState });
1305
+ return { success: false, error: "State parameter mismatch" };
1306
+ }
1307
+ }
1308
+ await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
1309
+ cleanOAuthParams();
1310
+ const token2 = await fetchToken(code);
1311
+ if (token2) {
1312
+ log("signinWithCode successful");
1313
+ return { success: true };
1314
+ } else {
1315
+ return { success: false, error: "Failed to exchange code for token" };
1316
+ }
1317
+ } catch (error2) {
1318
+ log("signinWithCode error:", error2);
1319
+ return {
1320
+ success: false,
1321
+ error: error2.message || "Authentication failed"
1322
+ };
1323
+ }
634
1324
  };
635
- const signout = () => {
1325
+ const signout = async () => {
636
1326
  log("signing out!");
637
1327
  setUser({});
638
1328
  setIsSignedIn(false);
639
1329
  setToken(null);
640
- document.cookie = `basic_token=; Secure; SameSite=Strict`;
641
- localStorage.removeItem("basic_auth_state");
1330
+ clearCookie("basic_token");
1331
+ clearCookie("basic_access_token");
1332
+ await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
1333
+ await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
1334
+ await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
642
1335
  if (syncRef.current) {
643
1336
  (async () => {
644
1337
  try {
645
- await syncRef.current.close();
646
- await syncRef.current.delete({ disableAutoOpen: false });
1338
+ await syncRef.current?.close();
1339
+ await syncRef.current?.delete({ disableAutoOpen: false });
647
1340
  syncRef.current = null;
648
1341
  window?.location?.reload();
649
1342
  } catch (error2) {
@@ -655,6 +1348,27 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
655
1348
  const getToken = async () => {
656
1349
  log("getting token...");
657
1350
  if (!token) {
1351
+ const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
1352
+ if (refreshToken) {
1353
+ log("No token in memory, attempting to refresh from storage");
1354
+ try {
1355
+ const newToken = await fetchToken(refreshToken);
1356
+ if (newToken?.access_token) {
1357
+ return newToken.access_token;
1358
+ }
1359
+ } catch (error2) {
1360
+ log("Failed to refresh token from storage:", error2);
1361
+ if (error2.message.includes("offline") || error2.message.includes("Network")) {
1362
+ log("Network issue - continuing with potentially expired token");
1363
+ const lastToken = localStorage.getItem("basic_access_token");
1364
+ if (lastToken) {
1365
+ return lastToken;
1366
+ }
1367
+ throw new Error("Network offline - authentication will be retried when online");
1368
+ }
1369
+ throw new Error("Authentication expired. Please sign in again.");
1370
+ }
1371
+ }
658
1372
  log("no token found");
659
1373
  throw new Error("no token found");
660
1374
  }
@@ -662,69 +1376,86 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
662
1376
  const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
663
1377
  if (isExpired) {
664
1378
  log("token is expired - refreshing ...");
665
- const newToken = await fetchToken(token?.refresh);
666
- return newToken?.access_token || "";
667
- }
668
- return token?.access_token || "";
669
- };
670
- function getCookie(name) {
671
- let cookieValue = "";
672
- if (document.cookie && document.cookie !== "") {
673
- const cookies = document.cookie.split(";");
674
- for (let i = 0; i < cookies.length; i++) {
675
- const cookie = cookies[i].trim();
676
- if (cookie.substring(0, name.length + 1) === name + "=") {
677
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
678
- break;
1379
+ const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
1380
+ if (refreshToken) {
1381
+ try {
1382
+ const newToken = await fetchToken(refreshToken);
1383
+ return newToken?.access_token || "";
1384
+ } catch (error2) {
1385
+ log("Failed to refresh expired token:", error2);
1386
+ if (error2.message.includes("offline") || error2.message.includes("Network")) {
1387
+ log("Network issue - using expired token until network is restored");
1388
+ return token.access_token;
1389
+ }
1390
+ throw new Error("Authentication expired. Please sign in again.");
679
1391
  }
1392
+ } else {
1393
+ throw new Error("no refresh token available");
680
1394
  }
681
1395
  }
682
- return cookieValue;
683
- }
684
- const fetchToken = async (code) => {
685
- const token2 = await fetch("https://api.basic.tech/auth/token", {
686
- method: "POST",
687
- headers: {
688
- "Content-Type": "application/json"
689
- },
690
- body: JSON.stringify({ code })
691
- }).then((response) => response.json()).catch((error2) => log("Error:", error2));
692
- if (token2.error) {
693
- log("error fetching token", token2.error);
694
- return;
695
- } else {
696
- setToken(token2);
697
- }
698
- return token2;
1396
+ return token?.access_token || "";
699
1397
  };
700
- const db_ = (tableName) => {
701
- const checkSignIn = () => {
702
- if (!isSignedIn) {
703
- throw new Error("cannot use db. user not logged in.");
1398
+ const fetchToken = async (code) => {
1399
+ try {
1400
+ if (!isOnline) {
1401
+ log("Network is offline, marking refresh as pending");
1402
+ setPendingRefresh(true);
1403
+ throw new Error("Network offline - refresh will be retried when online");
704
1404
  }
705
- };
706
- return {
707
- get: async () => {
708
- checkSignIn();
709
- const tok = await getToken();
710
- return get({ projectId: project_id, accountId: user.id, tableName, token: tok });
711
- },
712
- add: async (value) => {
713
- checkSignIn();
714
- const tok = await getToken();
715
- return add({ projectId: project_id, accountId: user.id, tableName, value, token: tok });
716
- },
717
- update: async (id, value) => {
718
- checkSignIn();
719
- const tok = await getToken();
720
- return update({ projectId: project_id, accountId: user.id, tableName, id, value, token: tok });
721
- },
722
- delete: async (id) => {
723
- checkSignIn();
724
- const tok = await getToken();
725
- return deleteRecord({ projectId: project_id, accountId: user.id, tableName, id, token: tok });
1405
+ const token2 = await fetch("https://api.basic.tech/auth/token", {
1406
+ method: "POST",
1407
+ headers: {
1408
+ "Content-Type": "application/json"
1409
+ },
1410
+ body: JSON.stringify({ code })
1411
+ }).then((response) => response.json()).catch((error2) => {
1412
+ log("Network error fetching token:", error2);
1413
+ if (!isOnline) {
1414
+ setPendingRefresh(true);
1415
+ throw new Error("Network offline - refresh will be retried when online");
1416
+ }
1417
+ throw new Error("Network error during token refresh");
1418
+ });
1419
+ if (token2.error) {
1420
+ log("error fetching token", token2.error);
1421
+ if (token2.error.includes("network") || token2.error.includes("timeout")) {
1422
+ setPendingRefresh(true);
1423
+ throw new Error("Network issue - refresh will be retried when online");
1424
+ }
1425
+ await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
1426
+ await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
1427
+ clearCookie("basic_token");
1428
+ clearCookie("basic_access_token");
1429
+ setUser({});
1430
+ setIsSignedIn(false);
1431
+ setToken(null);
1432
+ setIsAuthReady(true);
1433
+ throw new Error(`Token refresh failed: ${token2.error}`);
1434
+ } else {
1435
+ setToken(token2);
1436
+ setPendingRefresh(false);
1437
+ if (token2.refresh_token) {
1438
+ await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
1439
+ log("Updated refresh token in storage");
1440
+ }
1441
+ setCookie("basic_access_token", token2.access_token, { httpOnly: false });
1442
+ log("Updated access token in cookie");
726
1443
  }
727
- };
1444
+ return token2;
1445
+ } catch (error2) {
1446
+ log("Token refresh error:", error2);
1447
+ if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
1448
+ await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
1449
+ await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
1450
+ clearCookie("basic_token");
1451
+ clearCookie("basic_access_token");
1452
+ setUser({});
1453
+ setIsSignedIn(false);
1454
+ setToken(null);
1455
+ setIsAuthReady(true);
1456
+ }
1457
+ throw error2;
1458
+ }
728
1459
  };
729
1460
  const noDb = {
730
1461
  collection: () => {
@@ -738,12 +1469,14 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
738
1469
  user,
739
1470
  signout,
740
1471
  signin,
1472
+ signinWithCode,
741
1473
  getToken,
742
1474
  getSignInLink,
743
1475
  db: syncRef.current ? syncRef.current : noDb,
1476
+ remoteDb: remoteDbRef.current ? remoteDbRef.current : noDb,
744
1477
  dbStatus
745
1478
  }, children: [
746
- error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
1479
+ error && isDevMode() && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorDisplay, { error }),
747
1480
  isReady && children
748
1481
  ] });
749
1482
  }
@@ -778,6 +1511,7 @@ function useBasic() {
778
1511
  var import_dexie_react_hooks = require("dexie-react-hooks");
779
1512
  // Annotate the CommonJS export names for ESM import in node:
780
1513
  0 && (module.exports = {
1514
+ BasicDBSDK,
781
1515
  BasicProvider,
782
1516
  useBasic,
783
1517
  useQuery