@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.mjs CHANGED
@@ -250,78 +250,607 @@ var BasicSync = class extends Dexie2 {
250
250
  }
251
251
  };
252
252
 
253
- // src/db.ts
254
- var baseUrl = "https://api.basic.tech";
255
- async function get({ projectId, accountId, tableName, token }) {
256
- const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}`;
257
- const response = await fetch(url, {
258
- headers: {
259
- "Authorization": `Bearer ${token}`
253
+ // src/db_ts.ts
254
+ var DBError = class extends Error {
255
+ constructor(message, status, response, originalError) {
256
+ super(message);
257
+ this.status = status;
258
+ this.response = response;
259
+ this.originalError = originalError;
260
+ this.name = "DBError";
261
+ }
262
+ };
263
+ var QueryBuilder = class {
264
+ constructor(tableClient, tableSchema) {
265
+ this.tableClient = tableClient;
266
+ this.tableSchema = tableSchema;
267
+ }
268
+ params = {};
269
+ // Reserved fields that are always allowed
270
+ reservedFields = ["created_at", "updated_at", "id"];
271
+ // Validate field existence in schema
272
+ validateField(field) {
273
+ if (this.tableSchema && !this.reservedFields.includes(field)) {
274
+ if (!this.tableSchema.fields || !(field in this.tableSchema.fields)) {
275
+ throw new Error(`Invalid field: "${field}". Field does not exist in table schema.`);
276
+ }
260
277
  }
261
- });
262
- return response.json();
278
+ }
279
+ // Validate operator based on field type
280
+ validateOperator(field, operator, value) {
281
+ if (!this.tableSchema || this.reservedFields.includes(field)) {
282
+ return;
283
+ }
284
+ const fieldInfo = this.tableSchema.fields[field];
285
+ if (!fieldInfo)
286
+ return;
287
+ switch (operator) {
288
+ case "gt":
289
+ case "gte":
290
+ case "lt":
291
+ case "lte":
292
+ if (fieldInfo.type !== "number" && fieldInfo.type !== "string") {
293
+ throw new Error(`Operator "${operator}" can only be used with number or string fields. Field "${field}" is type "${fieldInfo.type}".`);
294
+ }
295
+ break;
296
+ case "like":
297
+ case "ilike":
298
+ if (fieldInfo.type !== "string") {
299
+ throw new Error(`Operator "${operator}" can only be used with string fields. Field "${field}" is type "${fieldInfo.type}".`);
300
+ }
301
+ if (typeof value !== "string") {
302
+ throw new Error(`Operator "${operator}" requires a string value. Received: ${typeof value}`);
303
+ }
304
+ break;
305
+ case "in":
306
+ if (!Array.isArray(value)) {
307
+ throw new Error(`Operator "in" requires an array value. Received: ${typeof value}`);
308
+ }
309
+ break;
310
+ case "is":
311
+ if (value !== null && typeof value !== "boolean") {
312
+ throw new Error(`Operator "is" requires null or boolean. Received: ${typeof value}`);
313
+ }
314
+ break;
315
+ }
316
+ }
317
+ // Add ordering to query with schema validation
318
+ order(field, direction = "asc") {
319
+ this.validateField(field);
320
+ this.params.order = `${field}.${direction}`;
321
+ return this;
322
+ }
323
+ // Add filtering to query
324
+ filter(conditions) {
325
+ if (!this.params.filters) {
326
+ this.params.filters = {};
327
+ }
328
+ for (const [field, condition] of Object.entries(conditions)) {
329
+ this.validateField(field);
330
+ if (condition === null || typeof condition !== "object") {
331
+ this.params.filters[field] = condition;
332
+ } else {
333
+ this.params.filters[field] = condition;
334
+ }
335
+ }
336
+ return this;
337
+ }
338
+ // Add limit to query
339
+ limit(count) {
340
+ this.params.limit = count;
341
+ return this;
342
+ }
343
+ // Add offset to query for pagination
344
+ offset(count) {
345
+ this.params.offset = count;
346
+ return this;
347
+ }
348
+ // Auto-execute when awaited
349
+ then(onfulfilled, onrejected) {
350
+ return this.tableClient.executeQuery(this.params).then(onfulfilled, onrejected);
351
+ }
352
+ // Auto-execute when awaited with catch
353
+ catch(onrejected) {
354
+ return this.tableClient.executeQuery(this.params).catch(onrejected);
355
+ }
356
+ // Auto-execute when awaited with finally
357
+ finally(onfinally) {
358
+ return this.tableClient.executeQuery(this.params).finally(onfinally);
359
+ }
360
+ };
361
+ var TableClient = class {
362
+ constructor(baseUrl, projectId, token, table, getToken, schema) {
363
+ this.baseUrl = baseUrl;
364
+ this.projectId = projectId;
365
+ this.token = token;
366
+ this.table = table;
367
+ this.getToken = getToken;
368
+ this.schema = schema;
369
+ if (schema && schema.tables && schema.tables[table]) {
370
+ this.tableSchema = schema.tables[table];
371
+ }
372
+ }
373
+ tableSchema;
374
+ async headers() {
375
+ const token = await this.getToken();
376
+ return {
377
+ Authorization: `Bearer ${token}`,
378
+ "Content-Type": "application/json"
379
+ };
380
+ }
381
+ async handleRequest(request) {
382
+ try {
383
+ const res = await request;
384
+ if (!res.ok) {
385
+ let errorMessage = `Request failed with status ${res.status}`;
386
+ let errorData;
387
+ try {
388
+ const json2 = await res.json();
389
+ errorData = json2;
390
+ if (json2.error || json2.message) {
391
+ const errorDetails = typeof json2.error === "object" ? JSON.stringify(json2.error) : json2.error;
392
+ const messageDetails = typeof json2.message === "object" ? JSON.stringify(json2.message) : json2.message;
393
+ errorMessage = `${res.status} ${res.statusText}: ${messageDetails || errorDetails || "Unknown error"}`;
394
+ }
395
+ } catch (e) {
396
+ console.log("Failed to parse error response:", e);
397
+ errorMessage = `${res.status} ${res.statusText}`;
398
+ }
399
+ throw new DBError(
400
+ errorMessage,
401
+ res.status,
402
+ errorData
403
+ );
404
+ }
405
+ const json = await res.json();
406
+ return json.data;
407
+ } catch (error) {
408
+ console.log("Caught error:", error);
409
+ if (error instanceof Error) {
410
+ console.log("Error type:", error.constructor.name);
411
+ console.log("Error stack:", error.stack);
412
+ }
413
+ if (error instanceof DBError) {
414
+ throw error;
415
+ }
416
+ if (error instanceof TypeError && error.message === "Network request failed") {
417
+ throw new DBError(
418
+ "Network request failed. Please check your internet connection and try again.",
419
+ void 0,
420
+ void 0,
421
+ error
422
+ );
423
+ }
424
+ throw new DBError(
425
+ `Unexpected error: ${error instanceof Error ? error.message : "Unknown error"}`,
426
+ void 0,
427
+ void 0,
428
+ error instanceof Error ? error : void 0
429
+ );
430
+ }
431
+ }
432
+ // Build query string from query options
433
+ buildQueryParams(query) {
434
+ if (!query)
435
+ return "";
436
+ const params = [];
437
+ if (query.id) {
438
+ params.push(`id=${query.id}`);
439
+ }
440
+ if (query.filters) {
441
+ for (const [field, condition] of Object.entries(query.filters)) {
442
+ this.addFilterParam(params, field, condition);
443
+ }
444
+ }
445
+ if (query.order) {
446
+ params.push(`order=${query.order}`);
447
+ }
448
+ if (query.limit !== void 0 && query.limit >= 0) {
449
+ params.push(`limit=${query.limit}`);
450
+ }
451
+ if (query.offset !== void 0 && query.offset >= 0) {
452
+ params.push(`offset=${query.offset}`);
453
+ }
454
+ return params.length > 0 ? `?${params.join("&")}` : "";
455
+ }
456
+ // Helper method to build filter parameters
457
+ addFilterParam(params, field, condition, negate = false) {
458
+ if (condition === null || typeof condition !== "object") {
459
+ if (condition === null) {
460
+ params.push(`${field}=${negate ? "not." : ""}is.null`);
461
+ } else if (typeof condition === "boolean") {
462
+ params.push(`${field}=${negate ? "not." : ""}is.${condition}`);
463
+ } else if (typeof condition === "number") {
464
+ params.push(`${field}=${negate ? "not." : ""}eq.${condition}`);
465
+ } else {
466
+ params.push(`${field}=${negate ? "not." : ""}eq.${encodeURIComponent(String(condition))}`);
467
+ }
468
+ return;
469
+ }
470
+ const operatorObj = condition;
471
+ if (operatorObj.not) {
472
+ this.addFilterParam(params, field, operatorObj.not, true);
473
+ return;
474
+ }
475
+ for (const [op, value] of Object.entries(operatorObj)) {
476
+ if (op === "not")
477
+ continue;
478
+ const operator = op;
479
+ if (value === null) {
480
+ params.push(`${field}=${negate ? "not." : ""}is.null`);
481
+ } else if (operator === "in" && Array.isArray(value)) {
482
+ params.push(`${field}=${negate ? "not." : ""}in.${value.join(",")}`);
483
+ } else if (operator === "is") {
484
+ if (typeof value === "boolean") {
485
+ params.push(`${field}=${negate ? "not." : ""}is.${value}`);
486
+ } else {
487
+ params.push(`${field}=${negate ? "not." : ""}is.null`);
488
+ }
489
+ } else {
490
+ const paramValue = typeof value === "string" ? encodeURIComponent(value) : String(value);
491
+ params.push(`${field}=${negate ? "not." : ""}${operator}.${paramValue}`);
492
+ }
493
+ }
494
+ }
495
+ // Internal method to execute a query with options
496
+ async executeQuery(options) {
497
+ const params = this.buildQueryParams(options);
498
+ const headers = await this.headers();
499
+ return this.handleRequest(
500
+ fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}${params}`, {
501
+ headers
502
+ })
503
+ );
504
+ }
505
+ // Public method to start building a query
506
+ getAll() {
507
+ return new QueryBuilder(this, this.tableSchema);
508
+ }
509
+ // Get a specific item by ID
510
+ async get(id) {
511
+ const headers = await this.headers();
512
+ return this.handleRequest(
513
+ fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
514
+ headers
515
+ })
516
+ );
517
+ }
518
+ async create(value) {
519
+ const headers = await this.headers();
520
+ return this.handleRequest(
521
+ fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}`, {
522
+ method: "POST",
523
+ headers,
524
+ body: JSON.stringify({ value })
525
+ })
526
+ );
527
+ }
528
+ async update(id, value) {
529
+ const headers = await this.headers();
530
+ return this.handleRequest(
531
+ fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
532
+ method: "PATCH",
533
+ headers,
534
+ body: JSON.stringify({ value })
535
+ })
536
+ );
537
+ }
538
+ async replace(id, value) {
539
+ const headers = await this.headers();
540
+ return this.handleRequest(
541
+ fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
542
+ method: "PUT",
543
+ headers,
544
+ body: JSON.stringify({ value })
545
+ })
546
+ );
547
+ }
548
+ async delete(id) {
549
+ const token = await this.getToken();
550
+ const headers = {
551
+ Authorization: `Bearer ${token}`
552
+ };
553
+ return this.handleRequest(
554
+ fetch(`${this.baseUrl}/account/${this.projectId}/db/${this.table}/${id}`, {
555
+ method: "DELETE",
556
+ headers
557
+ })
558
+ );
559
+ }
560
+ };
561
+ var BasicDBSDK = class {
562
+ projectId;
563
+ getToken;
564
+ baseUrl;
565
+ schema;
566
+ tableNames;
567
+ constructor(config) {
568
+ this.projectId = config.project_id;
569
+ if (config.getToken) {
570
+ this.getToken = config.getToken;
571
+ } else if (config.token) {
572
+ this.getToken = async () => config.token;
573
+ } else {
574
+ throw new Error("Either token or getToken must be provided");
575
+ }
576
+ this.baseUrl = config.baseUrl || "https://api.basic.tech";
577
+ this.schema = config.schema;
578
+ this.tableNames = Object.keys(this.schema.tables);
579
+ }
580
+ // Primary method - table access
581
+ table(name) {
582
+ if (!this.tableNames.includes(name)) {
583
+ throw new Error(`Table '${name}' not found in schema. Available tables: ${this.tableNames.join(", ")}`);
584
+ }
585
+ return new TableClient(
586
+ this.baseUrl,
587
+ this.projectId,
588
+ "",
589
+ // Empty placeholder, will be replaced in headers() method
590
+ name,
591
+ this.getToken,
592
+ this.schema
593
+ // Pass the entire schema to the TableClient
594
+ );
595
+ }
596
+ get tables() {
597
+ return {};
598
+ }
599
+ fields(table) {
600
+ const tableSchema = this.schema.tables[table];
601
+ if (!tableSchema) {
602
+ throw new Error(`Table '${table}' not found in schema`);
603
+ }
604
+ return Object.keys(tableSchema.fields);
605
+ }
606
+ };
607
+
608
+ // package.json
609
+ var version = "0.7.0-beta.1";
610
+
611
+ // src/updater/versionUpdater.ts
612
+ var VersionUpdater = class {
613
+ storage;
614
+ currentVersion;
615
+ migrations;
616
+ versionKey = "basic_app_version";
617
+ constructor(storage, currentVersion, migrations = []) {
618
+ this.storage = storage;
619
+ this.currentVersion = currentVersion;
620
+ this.migrations = migrations.sort((a, b) => this.compareVersions(a.fromVersion, b.fromVersion));
621
+ }
622
+ /**
623
+ * Check current stored version and run migrations if needed
624
+ * Only compares major.minor versions, ignoring beta/prerelease parts
625
+ * Example: "0.7.0-beta.1" and "0.7.0" are treated as the same version
626
+ */
627
+ async checkAndUpdate() {
628
+ const storedVersion = await this.getStoredVersion();
629
+ if (!storedVersion) {
630
+ await this.setStoredVersion(this.currentVersion);
631
+ return { updated: false, toVersion: this.currentVersion };
632
+ }
633
+ if (storedVersion === this.currentVersion) {
634
+ return { updated: false, toVersion: this.currentVersion };
635
+ }
636
+ const migrationsToRun = this.getMigrationsToRun(storedVersion, this.currentVersion);
637
+ if (migrationsToRun.length === 0) {
638
+ await this.setStoredVersion(this.currentVersion);
639
+ return { updated: true, fromVersion: storedVersion, toVersion: this.currentVersion };
640
+ }
641
+ for (const migration of migrationsToRun) {
642
+ try {
643
+ console.log(`Running migration from ${migration.fromVersion} to ${migration.toVersion}`);
644
+ await migration.migrate(this.storage);
645
+ } catch (error) {
646
+ console.error(`Migration failed from ${migration.fromVersion} to ${migration.toVersion}:`, error);
647
+ throw new Error(`Migration failed: ${error}`);
648
+ }
649
+ }
650
+ await this.setStoredVersion(this.currentVersion);
651
+ return { updated: true, fromVersion: storedVersion, toVersion: this.currentVersion };
652
+ }
653
+ async getStoredVersion() {
654
+ try {
655
+ const versionData = await this.storage.get(this.versionKey);
656
+ if (!versionData)
657
+ return null;
658
+ const versionInfo = JSON.parse(versionData);
659
+ return versionInfo.version;
660
+ } catch (error) {
661
+ console.warn("Failed to get stored version:", error);
662
+ return null;
663
+ }
664
+ }
665
+ async setStoredVersion(version2) {
666
+ const versionInfo = {
667
+ version: version2,
668
+ lastUpdated: Date.now()
669
+ };
670
+ await this.storage.set(this.versionKey, JSON.stringify(versionInfo));
671
+ }
672
+ getMigrationsToRun(fromVersion, toVersion) {
673
+ return this.migrations.filter((migration) => {
674
+ const storedLessThanMigrationTo = this.compareVersions(fromVersion, migration.toVersion) < 0;
675
+ const currentGreaterThanOrEqualMigrationTo = this.compareVersions(toVersion, migration.toVersion) >= 0;
676
+ console.log(`Checking migration ${migration.fromVersion} \u2192 ${migration.toVersion}:`);
677
+ console.log(` stored ${fromVersion} < migration.to ${migration.toVersion}: ${storedLessThanMigrationTo}`);
678
+ console.log(` current ${toVersion} >= migration.to ${migration.toVersion}: ${currentGreaterThanOrEqualMigrationTo}`);
679
+ const shouldRun = storedLessThanMigrationTo && currentGreaterThanOrEqualMigrationTo;
680
+ console.log(` Should run: ${shouldRun}`);
681
+ return shouldRun;
682
+ });
683
+ }
684
+ /**
685
+ * Simple semantic version comparison (major.minor only, ignoring beta/prerelease)
686
+ * Returns: -1 if a < b, 0 if a === b, 1 if a > b
687
+ */
688
+ compareVersions(a, b) {
689
+ const aMajorMinor = this.extractMajorMinor(a);
690
+ const bMajorMinor = this.extractMajorMinor(b);
691
+ if (aMajorMinor.major !== bMajorMinor.major) {
692
+ return aMajorMinor.major - bMajorMinor.major;
693
+ }
694
+ return aMajorMinor.minor - bMajorMinor.minor;
695
+ }
696
+ /**
697
+ * Extract major.minor from version string, ignoring beta/prerelease
698
+ * Examples: "0.7.0-beta.1" -> {major: 0, minor: 7}
699
+ * "1.2.3" -> {major: 1, minor: 2}
700
+ */
701
+ extractMajorMinor(version2) {
702
+ const cleanVersion = version2.split("-")[0]?.split("+")[0] || version2;
703
+ const parts = cleanVersion.split(".").map(Number);
704
+ return {
705
+ major: parts[0] || 0,
706
+ minor: parts[1] || 0
707
+ };
708
+ }
709
+ /**
710
+ * Add a migration to the updater
711
+ */
712
+ addMigration(migration) {
713
+ this.migrations.push(migration);
714
+ this.migrations.sort((a, b) => this.compareVersions(a.fromVersion, b.fromVersion));
715
+ }
716
+ };
717
+ function createVersionUpdater(storage, currentVersion, migrations = []) {
718
+ return new VersionUpdater(storage, currentVersion, migrations);
263
719
  }
264
- async function add({ projectId, accountId, tableName, value, token }) {
265
- const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}`;
266
- const response = await fetch(url, {
267
- method: "POST",
268
- headers: {
269
- "Content-Type": "application/json",
270
- "Authorization": `Bearer ${token}`
271
- },
272
- body: JSON.stringify({ "value": value })
273
- });
274
- return response.json();
720
+
721
+ // src/updater/updateMigrations.ts
722
+ var addMigrationTimestamp = {
723
+ fromVersion: "0.6.0",
724
+ toVersion: "0.7.0",
725
+ async migrate(storage) {
726
+ console.log("Running test migration");
727
+ storage.set("test_migration", "true");
728
+ }
729
+ };
730
+ function getMigrations() {
731
+ return [
732
+ addMigrationTimestamp
733
+ ];
275
734
  }
276
- async function update({ projectId, accountId, tableName, id, value, token }) {
277
- const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}/${id}`;
278
- const response = await fetch(url, {
279
- method: "PATCH",
280
- headers: {
281
- "Content-Type": "application/json",
282
- "Authorization": `Bearer ${token}`
283
- },
284
- body: JSON.stringify({ id, value })
285
- });
286
- return response.json();
735
+
736
+ // src/utils/storage.ts
737
+ var LocalStorageAdapter = class {
738
+ async get(key) {
739
+ return localStorage.getItem(key);
740
+ }
741
+ async set(key, value) {
742
+ localStorage.setItem(key, value);
743
+ }
744
+ async remove(key) {
745
+ localStorage.removeItem(key);
746
+ }
747
+ };
748
+ var STORAGE_KEYS = {
749
+ REFRESH_TOKEN: "basic_refresh_token",
750
+ USER_INFO: "basic_user_info",
751
+ AUTH_STATE: "basic_auth_state",
752
+ DEBUG: "basic_debug"
753
+ };
754
+ function getCookie(name) {
755
+ let cookieValue = "";
756
+ if (document.cookie && document.cookie !== "") {
757
+ const cookies = document.cookie.split(";");
758
+ for (let i = 0; i < cookies.length; i++) {
759
+ const cookie = cookies[i]?.trim();
760
+ if (cookie && cookie.substring(0, name.length + 1) === name + "=") {
761
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
762
+ break;
763
+ }
764
+ }
765
+ }
766
+ return cookieValue;
767
+ }
768
+ function setCookie(name, value, options) {
769
+ const opts = {
770
+ secure: true,
771
+ sameSite: "Strict",
772
+ httpOnly: false,
773
+ ...options
774
+ };
775
+ let cookieString = `${name}=${value}`;
776
+ if (opts.secure)
777
+ cookieString += "; Secure";
778
+ if (opts.sameSite)
779
+ cookieString += `; SameSite=${opts.sameSite}`;
780
+ if (opts.httpOnly)
781
+ cookieString += "; HttpOnly";
782
+ document.cookie = cookieString;
783
+ }
784
+ function clearCookie(name) {
785
+ document.cookie = `${name}=; Secure; SameSite=Strict`;
786
+ }
787
+
788
+ // src/utils/network.ts
789
+ function isDevelopment(debug) {
790
+ 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;
287
791
  }
288
- async function deleteRecord({ projectId, accountId, tableName, id, token }) {
289
- const url = `${baseUrl}/project/${projectId}/db/${accountId}/${tableName}/${id}`;
290
- const response = await fetch(url, {
291
- method: "DELETE",
292
- headers: {
293
- "Authorization": `Bearer ${token}`
792
+ async function checkForNewVersion() {
793
+ try {
794
+ const isBeta = version.includes("beta");
795
+ const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
796
+ if (!response.ok) {
797
+ throw new Error("Failed to fetch version from npm");
294
798
  }
295
- });
296
- return response.json();
799
+ const data = await response.json();
800
+ const latestVersion = data.version;
801
+ if (latestVersion !== version) {
802
+ console.warn("[basic] New version available:", latestVersion, `
803
+ run "npm install @basictech/react@${latestVersion}" to update`);
804
+ }
805
+ if (isBeta) {
806
+ log("thank you for being on basictech/react beta :)");
807
+ }
808
+ return {
809
+ hasNewVersion: version !== latestVersion,
810
+ latestVersion,
811
+ currentVersion: version
812
+ };
813
+ } catch (error) {
814
+ log("Error checking for new version:", error);
815
+ return {
816
+ hasNewVersion: false,
817
+ latestVersion: null,
818
+ currentVersion: null
819
+ };
820
+ }
821
+ }
822
+ function cleanOAuthParamsFromUrl() {
823
+ if (window.location.search.includes("code") || window.location.search.includes("state")) {
824
+ const url = new URL(window.location.href);
825
+ url.searchParams.delete("code");
826
+ url.searchParams.delete("state");
827
+ window.history.pushState({}, document.title, url.pathname + url.search);
828
+ log("Cleaned OAuth parameters from URL");
829
+ }
830
+ }
831
+ function getSyncStatus(statusCode) {
832
+ switch (statusCode) {
833
+ case -1:
834
+ return "ERROR";
835
+ case 0:
836
+ return "OFFLINE";
837
+ case 1:
838
+ return "CONNECTING";
839
+ case 2:
840
+ return "ONLINE";
841
+ case 3:
842
+ return "SYNCING";
843
+ case 4:
844
+ return "ERROR_WILL_RETRY";
845
+ default:
846
+ return "UNKNOWN";
847
+ }
297
848
  }
298
849
 
299
- // src/AuthContext.tsx
850
+ // src/utils/schema.ts
300
851
  import { validateSchema as validateSchema2, compareSchemas } from "@basictech/schema";
301
-
302
- // package.json
303
- var version = "0.6.0";
304
-
305
- // src/AuthContext.tsx
306
- import { jsx, jsxs } from "react/jsx-runtime";
307
- var BasicContext = createContext({
308
- unicorn: "\u{1F984}",
309
- isAuthReady: false,
310
- isSignedIn: false,
311
- user: null,
312
- signout: () => {
313
- },
314
- signin: () => {
315
- },
316
- getToken: () => new Promise(() => {
317
- }),
318
- getSignInLink: () => "",
319
- db: {},
320
- dbStatus: "LOADING" /* LOADING */
321
- });
322
852
  async function getSchemaStatus(schema) {
323
853
  const projectId = schema.project_id;
324
- let status = "";
325
854
  const valid = validateSchema2(schema);
326
855
  if (!valid.valid) {
327
856
  console.warn("BasicDB Error: your local schema is invalid. Please fix errors and try again - sync is disabled");
@@ -384,55 +913,62 @@ async function getSchemaStatus(schema) {
384
913
  };
385
914
  }
386
915
  }
387
- function getSyncStatus(statusCode) {
388
- switch (statusCode) {
389
- case -1:
390
- return "ERROR";
391
- case 0:
392
- return "OFFLINE";
393
- case 1:
394
- return "CONNECTING";
395
- case 2:
396
- return "ONLINE";
397
- case 3:
398
- return "SYNCING";
399
- case 4:
400
- return "ERROR_WILL_RETRY";
401
- default:
402
- return "UNKNOWN";
403
- }
404
- }
405
- async function checkForNewVersion() {
406
- try {
407
- const isBeta = version.includes("beta");
408
- const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
409
- if (!response.ok) {
410
- throw new Error("Failed to fetch version from npm");
411
- }
412
- const data = await response.json();
413
- const latestVersion = data.version;
414
- if (latestVersion !== version) {
415
- console.warn("[basic] New version available:", latestVersion, `
416
- run "npm install @basictech/react@${latestVersion}" to update`);
417
- }
418
- if (isBeta) {
419
- log("thank you for being on basictech/react beta :)");
420
- }
916
+ async function validateAndCheckSchema(schema) {
917
+ const valid = validateSchema2(schema);
918
+ if (!valid.valid) {
919
+ log("Basic Schema is invalid!", valid.errors);
920
+ console.group("Schema Errors");
921
+ let errorMessage = "";
922
+ valid.errors.forEach((error, index) => {
923
+ log(`${index + 1}:`, error.message, ` - at ${error.instancePath}`);
924
+ errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}
925
+ `;
926
+ });
927
+ console.groupEnd();
421
928
  return {
422
- hasNewVersion: version !== latestVersion,
423
- latestVersion,
424
- currentVersion: version
425
- };
426
- } catch (error) {
427
- log("Error checking for new version:", error);
428
- return {
429
- hasNewVersion: false,
430
- latestVersion: null,
431
- currentVersion: null
929
+ isValid: false,
930
+ schemaStatus: { valid: false },
931
+ errors: valid.errors
432
932
  };
433
933
  }
934
+ let schemaStatus = { valid: false };
935
+ if (schema.version !== 0) {
936
+ schemaStatus = await getSchemaStatus(schema);
937
+ log("schemaStatus", schemaStatus);
938
+ } else {
939
+ log("schema not published - at version 0");
940
+ }
941
+ return {
942
+ isValid: true,
943
+ schemaStatus
944
+ };
434
945
  }
435
- function BasicProvider({ children, project_id, schema, debug = false }) {
946
+
947
+ // src/AuthContext.tsx
948
+ import { jsx, jsxs } from "react/jsx-runtime";
949
+ var BasicContext = createContext({
950
+ unicorn: "\u{1F984}",
951
+ isAuthReady: false,
952
+ isSignedIn: false,
953
+ user: null,
954
+ signout: () => Promise.resolve(),
955
+ signin: () => Promise.resolve(),
956
+ signinWithCode: () => new Promise(() => {
957
+ }),
958
+ getToken: () => new Promise(() => {
959
+ }),
960
+ getSignInLink: () => Promise.resolve(""),
961
+ db: {},
962
+ remoteDb: {},
963
+ dbStatus: "LOADING" /* LOADING */
964
+ });
965
+ function BasicProvider({
966
+ children,
967
+ project_id,
968
+ schema,
969
+ debug = false,
970
+ storage
971
+ }) {
436
972
  const [isAuthReady, setIsAuthReady] = useState(false);
437
973
  const [isSignedIn, setIsSignedIn] = useState(false);
438
974
  const [token, setToken] = useState(null);
@@ -441,7 +977,41 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
441
977
  const [isReady, setIsReady] = useState(false);
442
978
  const [dbStatus, setDbStatus] = useState("OFFLINE" /* OFFLINE */);
443
979
  const [error, setError] = useState(null);
980
+ const [isOnline, setIsOnline] = useState(navigator.onLine);
981
+ const [pendingRefresh, setPendingRefresh] = useState(false);
444
982
  const syncRef = useRef(null);
983
+ const remoteDbRef = useRef(null);
984
+ const storageAdapter = storage || new LocalStorageAdapter();
985
+ const isDevMode = () => isDevelopment(debug);
986
+ const cleanOAuthParams = () => cleanOAuthParamsFromUrl();
987
+ useEffect(() => {
988
+ const handleOnline = () => {
989
+ log("Network came back online");
990
+ setIsOnline(true);
991
+ if (pendingRefresh) {
992
+ log("Retrying pending token refresh");
993
+ setPendingRefresh(false);
994
+ if (token) {
995
+ const refreshToken = token.refresh_token || localStorage.getItem("basic_refresh_token");
996
+ if (refreshToken) {
997
+ fetchToken(refreshToken).catch((error2) => {
998
+ log("Retry refresh failed:", error2);
999
+ });
1000
+ }
1001
+ }
1002
+ }
1003
+ };
1004
+ const handleOffline = () => {
1005
+ log("Network went offline");
1006
+ setIsOnline(false);
1007
+ };
1008
+ window.addEventListener("online", handleOnline);
1009
+ window.addEventListener("offline", handleOffline);
1010
+ return () => {
1011
+ window.removeEventListener("online", handleOnline);
1012
+ window.removeEventListener("offline", handleOffline);
1013
+ };
1014
+ }, [pendingRefresh, token]);
445
1015
  useEffect(() => {
446
1016
  function initDb(options) {
447
1017
  if (!syncRef.current) {
@@ -450,9 +1020,6 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
450
1020
  syncRef.current.syncable.on("statusChanged", (status, url) => {
451
1021
  setDbStatus(getSyncStatus(status));
452
1022
  });
453
- syncRef.current.syncable.getStatus().then((status) => {
454
- setDbStatus(getSyncStatus(status));
455
- });
456
1023
  if (options.shouldConnect) {
457
1024
  setShouldConnect(true);
458
1025
  } else {
@@ -462,17 +1029,15 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
462
1029
  }
463
1030
  }
464
1031
  async function checkSchema() {
465
- const valid = validateSchema2(schema);
466
- if (!valid.valid) {
467
- log("Basic Schema is invalid!", valid.errors);
468
- console.group("Schema Errors");
1032
+ const result = await validateAndCheckSchema(schema);
1033
+ if (!result.isValid) {
469
1034
  let errorMessage = "";
470
- valid.errors.forEach((error2, index) => {
471
- log(`${index + 1}:`, error2.message, ` - at ${error2.instancePath}`);
472
- errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
1035
+ if (result.errors) {
1036
+ result.errors.forEach((error2, index) => {
1037
+ errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
473
1038
  `;
474
- });
475
- console.groupEnd("Schema Errors");
1039
+ });
1040
+ }
476
1041
  setError({
477
1042
  code: "schema_invalid",
478
1043
  title: "Basic Schema is invalid!",
@@ -481,17 +1046,10 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
481
1046
  setIsReady(true);
482
1047
  return null;
483
1048
  }
484
- let schemaStatus = { valid: false };
485
- if (schema.version !== 0) {
486
- schemaStatus = await getSchemaStatus(schema);
487
- log("schemaStatus", schemaStatus);
488
- } else {
489
- log("schema not published - at version 0");
490
- }
491
- if (schemaStatus.valid) {
1049
+ if (result.schemaStatus.valid) {
492
1050
  initDb({ shouldConnect: true });
493
1051
  } else {
494
- log("Schema is invalid!", schemaStatus);
1052
+ log("Schema is invalid!", result.schemaStatus);
495
1053
  initDb({ shouldConnect: false });
496
1054
  }
497
1055
  checkForNewVersion();
@@ -503,36 +1061,101 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
503
1061
  }
504
1062
  }, []);
505
1063
  useEffect(() => {
506
- if (token && syncRef.current && isSignedIn && shouldConnect) {
507
- connectToDb();
1064
+ async function connectToDb() {
1065
+ if (token && syncRef.current && isSignedIn && shouldConnect) {
1066
+ const tok = await getToken();
1067
+ if (!tok) {
1068
+ log("no token found");
1069
+ return;
1070
+ }
1071
+ log("connecting to db...");
1072
+ syncRef.current?.connect({ access_token: tok }).catch((e) => {
1073
+ log("error connecting to db", e);
1074
+ });
1075
+ }
508
1076
  }
1077
+ connectToDb();
509
1078
  }, [isSignedIn, shouldConnect]);
510
1079
  useEffect(() => {
511
- localStorage.setItem("basic_debug", debug ? "true" : "false");
512
- try {
513
- if (window.location.search.includes("code")) {
514
- let code = window.location?.search?.split("code=")[1].split("&")[0];
515
- const state = localStorage.getItem("basic_auth_state");
516
- if (!state || state !== window.location.search.split("state=")[1].split("&")[0]) {
517
- log("error: auth state does not match");
518
- setIsAuthReady(true);
519
- localStorage.removeItem("basic_auth_state");
520
- window.history.pushState({}, document.title, "/");
521
- return;
1080
+ if (project_id && schema && token?.access_token && !remoteDbRef.current) {
1081
+ log("Initializing Remote DB SDK");
1082
+ remoteDbRef.current = new BasicDBSDK({
1083
+ project_id,
1084
+ schema,
1085
+ getToken: () => getToken(),
1086
+ baseUrl: "https://api.basic.tech"
1087
+ });
1088
+ }
1089
+ }, [token, project_id, schema]);
1090
+ useEffect(() => {
1091
+ const initializeAuth = async () => {
1092
+ await storageAdapter.set(STORAGE_KEYS.DEBUG, debug ? "true" : "false");
1093
+ try {
1094
+ const versionUpdater = createVersionUpdater(storageAdapter, version, getMigrations());
1095
+ const updateResult = await versionUpdater.checkAndUpdate();
1096
+ if (updateResult.updated) {
1097
+ log(`App updated from ${updateResult.fromVersion} to ${updateResult.toVersion}`);
1098
+ } else {
1099
+ log(`App version ${updateResult.toVersion} is current`);
522
1100
  }
523
- localStorage.removeItem("basic_auth_state");
524
- fetchToken(code);
525
- } else {
526
- let cookie_token = getCookie("basic_token");
527
- if (cookie_token !== "") {
528
- setToken(JSON.parse(cookie_token));
1101
+ } catch (error2) {
1102
+ log("Version update failed:", error2);
1103
+ }
1104
+ try {
1105
+ if (window.location.search.includes("code")) {
1106
+ let code = window.location?.search?.split("code=")[1]?.split("&")[0];
1107
+ if (!code)
1108
+ return;
1109
+ const state = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
1110
+ const urlState = window.location.search.split("state=")[1]?.split("&")[0];
1111
+ if (!state || state !== urlState) {
1112
+ log("error: auth state does not match");
1113
+ setIsAuthReady(true);
1114
+ await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
1115
+ cleanOAuthParams();
1116
+ return;
1117
+ }
1118
+ await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
1119
+ cleanOAuthParams();
1120
+ fetchToken(code).catch((error2) => {
1121
+ log("Error fetching token:", error2);
1122
+ });
529
1123
  } else {
530
- setIsAuthReady(true);
1124
+ const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
1125
+ if (refreshToken) {
1126
+ log("Found refresh token in storage, attempting to refresh access token");
1127
+ fetchToken(refreshToken).catch((error2) => {
1128
+ log("Error fetching refresh token:", error2);
1129
+ });
1130
+ } else {
1131
+ let cookie_token = getCookie("basic_token");
1132
+ if (cookie_token !== "") {
1133
+ const tokenData = JSON.parse(cookie_token);
1134
+ setToken(tokenData);
1135
+ if (tokenData.refresh_token) {
1136
+ await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, tokenData.refresh_token);
1137
+ }
1138
+ } else {
1139
+ const cachedUserInfo = await storageAdapter.get(STORAGE_KEYS.USER_INFO);
1140
+ if (cachedUserInfo) {
1141
+ try {
1142
+ const userData = JSON.parse(cachedUserInfo);
1143
+ setUser(userData);
1144
+ setIsSignedIn(true);
1145
+ log("Loaded cached user info for offline mode");
1146
+ } catch (error2) {
1147
+ log("Error parsing cached user info:", error2);
1148
+ }
1149
+ }
1150
+ setIsAuthReady(true);
1151
+ }
1152
+ }
531
1153
  }
1154
+ } catch (e) {
1155
+ log("error getting token", e);
532
1156
  }
533
- } catch (e) {
534
- log("error getting cookie", e);
535
- }
1157
+ };
1158
+ initializeAuth();
536
1159
  }, []);
537
1160
  useEffect(() => {
538
1161
  async function fetchUser(acc_token) {
@@ -547,10 +1170,13 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
547
1170
  log("error fetching user", user2.error);
548
1171
  return;
549
1172
  } else {
550
- document.cookie = `basic_token=${JSON.stringify(token)}; Secure; SameSite=Strict`;
551
- if (window.location.search.includes("code")) {
552
- window.history.pushState({}, document.title, "/");
1173
+ if (token?.refresh_token) {
1174
+ await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token.refresh_token);
553
1175
  }
1176
+ await storageAdapter.set(STORAGE_KEYS.USER_INFO, JSON.stringify(user2));
1177
+ log("Cached user info in storage");
1178
+ setCookie("basic_access_token", token?.access_token || "", { httpOnly: false });
1179
+ setCookie("basic_token", JSON.stringify(token));
554
1180
  setUser(user2);
555
1181
  setIsSignedIn(true);
556
1182
  setIsAuthReady(true);
@@ -566,56 +1192,122 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
566
1192
  const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
567
1193
  if (isExpired) {
568
1194
  log("token is expired - refreshing ...");
569
- const newToken = await fetchToken(token?.refresh);
570
- fetchUser(newToken.access_token);
1195
+ try {
1196
+ const newToken = await fetchToken(token?.refresh_token || "");
1197
+ fetchUser(newToken?.access_token || "");
1198
+ } catch (error2) {
1199
+ log("Failed to refresh token in checkToken:", error2);
1200
+ if (error2.message.includes("offline") || error2.message.includes("Network")) {
1201
+ log("Network issue - continuing with expired token until online");
1202
+ fetchUser(token?.access_token || "");
1203
+ } else {
1204
+ setIsAuthReady(true);
1205
+ }
1206
+ }
571
1207
  } else {
572
- fetchUser(token.access_token);
1208
+ fetchUser(token?.access_token || "");
573
1209
  }
574
1210
  }
575
1211
  if (token) {
576
1212
  checkToken();
577
1213
  }
578
1214
  }, [token]);
579
- const connectToDb = async () => {
580
- const tok = await getToken();
581
- if (!tok) {
582
- log("no token found");
583
- return;
1215
+ const getSignInLink = async (redirectUri) => {
1216
+ try {
1217
+ log("getting sign in link...");
1218
+ if (!project_id) {
1219
+ throw new Error("Project ID is required to generate sign-in link");
1220
+ }
1221
+ const randomState = Math.random().toString(36).substring(6);
1222
+ await storageAdapter.set(STORAGE_KEYS.AUTH_STATE, randomState);
1223
+ const redirectUrl = redirectUri || window.location.href;
1224
+ if (!redirectUrl || !redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
1225
+ throw new Error("Invalid redirect URI provided");
1226
+ }
1227
+ let baseUrl = "https://api.basic.tech/auth/authorize";
1228
+ baseUrl += `?client_id=${project_id}`;
1229
+ baseUrl += `&redirect_uri=${encodeURIComponent(redirectUrl)}`;
1230
+ baseUrl += `&response_type=code`;
1231
+ baseUrl += `&scope=profile`;
1232
+ baseUrl += `&state=${randomState}`;
1233
+ log("Generated sign-in link successfully");
1234
+ return baseUrl;
1235
+ } catch (error2) {
1236
+ log("Error generating sign-in link:", error2);
1237
+ throw error2;
584
1238
  }
585
- log("connecting to db...");
586
- syncRef.current.connect({ access_token: tok }).catch((e) => {
587
- log("error connecting to db", e);
588
- });
589
1239
  };
590
- const getSignInLink = () => {
591
- log("getting sign in link...");
592
- const randomState = Math.random().toString(36).substring(6);
593
- localStorage.setItem("basic_auth_state", randomState);
594
- let baseUrl2 = "https://api.basic.tech/auth/authorize";
595
- baseUrl2 += `?client_id=${project_id}`;
596
- baseUrl2 += `&redirect_uri=${encodeURIComponent(window.location.href)}`;
597
- baseUrl2 += `&response_type=code`;
598
- baseUrl2 += `&scope=profile`;
599
- baseUrl2 += `&state=${randomState}`;
600
- return baseUrl2;
1240
+ const signin = async () => {
1241
+ try {
1242
+ log("signing in...");
1243
+ if (!project_id) {
1244
+ log("Error: project_id is required for sign-in");
1245
+ throw new Error("Project ID is required for authentication");
1246
+ }
1247
+ const signInLink = await getSignInLink();
1248
+ log("Generated sign-in link:", signInLink);
1249
+ if (!signInLink || !signInLink.startsWith("https://")) {
1250
+ log("Error: Invalid sign-in link generated");
1251
+ throw new Error("Failed to generate valid sign-in URL");
1252
+ }
1253
+ window.location.href = signInLink;
1254
+ } catch (error2) {
1255
+ log("Error during sign-in:", error2);
1256
+ if (isDevMode()) {
1257
+ setError({
1258
+ code: "signin_error",
1259
+ title: "Sign-in Failed",
1260
+ message: error2.message || "An error occurred during sign-in. Please try again."
1261
+ });
1262
+ }
1263
+ throw error2;
1264
+ }
601
1265
  };
602
- const signin = () => {
603
- log("signing in: ", getSignInLink());
604
- const signInLink = getSignInLink();
605
- window.location.href = signInLink;
1266
+ const signinWithCode = async (code, state) => {
1267
+ try {
1268
+ log("signinWithCode called with code:", code);
1269
+ if (!code || typeof code !== "string") {
1270
+ return { success: false, error: "Invalid authorization code" };
1271
+ }
1272
+ if (state) {
1273
+ const storedState = await storageAdapter.get(STORAGE_KEYS.AUTH_STATE);
1274
+ if (storedState && storedState !== state) {
1275
+ log("State parameter mismatch:", { provided: state, stored: storedState });
1276
+ return { success: false, error: "State parameter mismatch" };
1277
+ }
1278
+ }
1279
+ await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
1280
+ cleanOAuthParams();
1281
+ const token2 = await fetchToken(code);
1282
+ if (token2) {
1283
+ log("signinWithCode successful");
1284
+ return { success: true };
1285
+ } else {
1286
+ return { success: false, error: "Failed to exchange code for token" };
1287
+ }
1288
+ } catch (error2) {
1289
+ log("signinWithCode error:", error2);
1290
+ return {
1291
+ success: false,
1292
+ error: error2.message || "Authentication failed"
1293
+ };
1294
+ }
606
1295
  };
607
- const signout = () => {
1296
+ const signout = async () => {
608
1297
  log("signing out!");
609
1298
  setUser({});
610
1299
  setIsSignedIn(false);
611
1300
  setToken(null);
612
- document.cookie = `basic_token=; Secure; SameSite=Strict`;
613
- localStorage.removeItem("basic_auth_state");
1301
+ clearCookie("basic_token");
1302
+ clearCookie("basic_access_token");
1303
+ await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE);
1304
+ await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
1305
+ await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
614
1306
  if (syncRef.current) {
615
1307
  (async () => {
616
1308
  try {
617
- await syncRef.current.close();
618
- await syncRef.current.delete({ disableAutoOpen: false });
1309
+ await syncRef.current?.close();
1310
+ await syncRef.current?.delete({ disableAutoOpen: false });
619
1311
  syncRef.current = null;
620
1312
  window?.location?.reload();
621
1313
  } catch (error2) {
@@ -627,6 +1319,27 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
627
1319
  const getToken = async () => {
628
1320
  log("getting token...");
629
1321
  if (!token) {
1322
+ const refreshToken = await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
1323
+ if (refreshToken) {
1324
+ log("No token in memory, attempting to refresh from storage");
1325
+ try {
1326
+ const newToken = await fetchToken(refreshToken);
1327
+ if (newToken?.access_token) {
1328
+ return newToken.access_token;
1329
+ }
1330
+ } catch (error2) {
1331
+ log("Failed to refresh token from storage:", error2);
1332
+ if (error2.message.includes("offline") || error2.message.includes("Network")) {
1333
+ log("Network issue - continuing with potentially expired token");
1334
+ const lastToken = localStorage.getItem("basic_access_token");
1335
+ if (lastToken) {
1336
+ return lastToken;
1337
+ }
1338
+ throw new Error("Network offline - authentication will be retried when online");
1339
+ }
1340
+ throw new Error("Authentication expired. Please sign in again.");
1341
+ }
1342
+ }
630
1343
  log("no token found");
631
1344
  throw new Error("no token found");
632
1345
  }
@@ -634,69 +1347,86 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
634
1347
  const isExpired = decoded.exp && decoded.exp < Date.now() / 1e3;
635
1348
  if (isExpired) {
636
1349
  log("token is expired - refreshing ...");
637
- const newToken = await fetchToken(token?.refresh);
638
- return newToken?.access_token || "";
639
- }
640
- return token?.access_token || "";
641
- };
642
- function getCookie(name) {
643
- let cookieValue = "";
644
- if (document.cookie && document.cookie !== "") {
645
- const cookies = document.cookie.split(";");
646
- for (let i = 0; i < cookies.length; i++) {
647
- const cookie = cookies[i].trim();
648
- if (cookie.substring(0, name.length + 1) === name + "=") {
649
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
650
- break;
1350
+ const refreshToken = token?.refresh_token || await storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
1351
+ if (refreshToken) {
1352
+ try {
1353
+ const newToken = await fetchToken(refreshToken);
1354
+ return newToken?.access_token || "";
1355
+ } catch (error2) {
1356
+ log("Failed to refresh expired token:", error2);
1357
+ if (error2.message.includes("offline") || error2.message.includes("Network")) {
1358
+ log("Network issue - using expired token until network is restored");
1359
+ return token.access_token;
1360
+ }
1361
+ throw new Error("Authentication expired. Please sign in again.");
651
1362
  }
1363
+ } else {
1364
+ throw new Error("no refresh token available");
652
1365
  }
653
1366
  }
654
- return cookieValue;
655
- }
656
- const fetchToken = async (code) => {
657
- const token2 = await fetch("https://api.basic.tech/auth/token", {
658
- method: "POST",
659
- headers: {
660
- "Content-Type": "application/json"
661
- },
662
- body: JSON.stringify({ code })
663
- }).then((response) => response.json()).catch((error2) => log("Error:", error2));
664
- if (token2.error) {
665
- log("error fetching token", token2.error);
666
- return;
667
- } else {
668
- setToken(token2);
669
- }
670
- return token2;
1367
+ return token?.access_token || "";
671
1368
  };
672
- const db_ = (tableName) => {
673
- const checkSignIn = () => {
674
- if (!isSignedIn) {
675
- throw new Error("cannot use db. user not logged in.");
1369
+ const fetchToken = async (code) => {
1370
+ try {
1371
+ if (!isOnline) {
1372
+ log("Network is offline, marking refresh as pending");
1373
+ setPendingRefresh(true);
1374
+ throw new Error("Network offline - refresh will be retried when online");
676
1375
  }
677
- };
678
- return {
679
- get: async () => {
680
- checkSignIn();
681
- const tok = await getToken();
682
- return get({ projectId: project_id, accountId: user.id, tableName, token: tok });
683
- },
684
- add: async (value) => {
685
- checkSignIn();
686
- const tok = await getToken();
687
- return add({ projectId: project_id, accountId: user.id, tableName, value, token: tok });
688
- },
689
- update: async (id, value) => {
690
- checkSignIn();
691
- const tok = await getToken();
692
- return update({ projectId: project_id, accountId: user.id, tableName, id, value, token: tok });
693
- },
694
- delete: async (id) => {
695
- checkSignIn();
696
- const tok = await getToken();
697
- return deleteRecord({ projectId: project_id, accountId: user.id, tableName, id, token: tok });
1376
+ const token2 = await fetch("https://api.basic.tech/auth/token", {
1377
+ method: "POST",
1378
+ headers: {
1379
+ "Content-Type": "application/json"
1380
+ },
1381
+ body: JSON.stringify({ code })
1382
+ }).then((response) => response.json()).catch((error2) => {
1383
+ log("Network error fetching token:", error2);
1384
+ if (!isOnline) {
1385
+ setPendingRefresh(true);
1386
+ throw new Error("Network offline - refresh will be retried when online");
1387
+ }
1388
+ throw new Error("Network error during token refresh");
1389
+ });
1390
+ if (token2.error) {
1391
+ log("error fetching token", token2.error);
1392
+ if (token2.error.includes("network") || token2.error.includes("timeout")) {
1393
+ setPendingRefresh(true);
1394
+ throw new Error("Network issue - refresh will be retried when online");
1395
+ }
1396
+ await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
1397
+ await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
1398
+ clearCookie("basic_token");
1399
+ clearCookie("basic_access_token");
1400
+ setUser({});
1401
+ setIsSignedIn(false);
1402
+ setToken(null);
1403
+ setIsAuthReady(true);
1404
+ throw new Error(`Token refresh failed: ${token2.error}`);
1405
+ } else {
1406
+ setToken(token2);
1407
+ setPendingRefresh(false);
1408
+ if (token2.refresh_token) {
1409
+ await storageAdapter.set(STORAGE_KEYS.REFRESH_TOKEN, token2.refresh_token);
1410
+ log("Updated refresh token in storage");
1411
+ }
1412
+ setCookie("basic_access_token", token2.access_token, { httpOnly: false });
1413
+ log("Updated access token in cookie");
698
1414
  }
699
- };
1415
+ return token2;
1416
+ } catch (error2) {
1417
+ log("Token refresh error:", error2);
1418
+ if (!error2.message.includes("offline") && !error2.message.includes("Network")) {
1419
+ await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN);
1420
+ await storageAdapter.remove(STORAGE_KEYS.USER_INFO);
1421
+ clearCookie("basic_token");
1422
+ clearCookie("basic_access_token");
1423
+ setUser({});
1424
+ setIsSignedIn(false);
1425
+ setToken(null);
1426
+ setIsAuthReady(true);
1427
+ }
1428
+ throw error2;
1429
+ }
700
1430
  };
701
1431
  const noDb = {
702
1432
  collection: () => {
@@ -710,12 +1440,14 @@ function BasicProvider({ children, project_id, schema, debug = false }) {
710
1440
  user,
711
1441
  signout,
712
1442
  signin,
1443
+ signinWithCode,
713
1444
  getToken,
714
1445
  getSignInLink,
715
1446
  db: syncRef.current ? syncRef.current : noDb,
1447
+ remoteDb: remoteDbRef.current ? remoteDbRef.current : noDb,
716
1448
  dbStatus
717
1449
  }, children: [
718
- error && /* @__PURE__ */ jsx(ErrorDisplay, { error }),
1450
+ error && isDevMode() && /* @__PURE__ */ jsx(ErrorDisplay, { error }),
719
1451
  isReady && children
720
1452
  ] });
721
1453
  }
@@ -749,6 +1481,7 @@ function useBasic() {
749
1481
  // src/index.ts
750
1482
  import { useLiveQuery as useQuery } from "dexie-react-hooks";
751
1483
  export {
1484
+ BasicDBSDK,
752
1485
  BasicProvider,
753
1486
  useBasic,
754
1487
  useQuery