@basictech/react 0.8.0-beta.1 → 0.8.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
@@ -216,8 +216,809 @@ var init_syncProtocol = __esm({
216
216
  }
217
217
  });
218
218
 
219
+ // package.json
220
+ var version;
221
+ var init_package = __esm({
222
+ "package.json"() {
223
+ version = "0.8.0-beta.2";
224
+ }
225
+ });
226
+
227
+ // src/utils/network.ts
228
+ import semver from "semver";
229
+ function isDevelopment(debug) {
230
+ if (debug === true) return true;
231
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "development") return true;
232
+ if (typeof window === "undefined" || !window.location) return false;
233
+ const host = window.location.hostname;
234
+ return host === "localhost" || host === "127.0.0.1" || host.includes("localhost") || host.includes("127.0.0.1") || host.includes(".local");
235
+ }
236
+ function normalizeVersion(v) {
237
+ if (v == null) return null;
238
+ const t = String(v).trim();
239
+ return t.length ? t : null;
240
+ }
241
+ function versionsMatch(a, b) {
242
+ const na = a.trim();
243
+ const nb = b.trim();
244
+ if (na === nb) return true;
245
+ const va = semver.valid(na);
246
+ const vb = semver.valid(nb);
247
+ if (va && vb) return semver.eq(va, vb);
248
+ return false;
249
+ }
250
+ function usesBetaDistTag(version2) {
251
+ const pre = semver.prerelease(version2);
252
+ const id = pre?.[0];
253
+ return typeof id === "string" && id.toLowerCase() === "beta";
254
+ }
255
+ async function checkForNewVersion() {
256
+ try {
257
+ const currentVersion = normalizeVersion(version);
258
+ if (!currentVersion) {
259
+ return { hasNewVersion: false, latestVersion: null, currentVersion: null };
260
+ }
261
+ const response = await fetch("https://registry.npmjs.org/@basictech/react", {
262
+ headers: { Accept: "application/vnd.npm.install-v1+json" }
263
+ });
264
+ if (!response.ok) {
265
+ throw new Error("Failed to fetch version from npm");
266
+ }
267
+ const data = await response.json();
268
+ const distTags = data["dist-tags"] ?? {};
269
+ const rawRegistry = usesBetaDistTag(currentVersion) ? distTags.beta ?? distTags.latest : distTags.latest;
270
+ const latestVersion = normalizeVersion(rawRegistry ?? null);
271
+ if (!latestVersion) {
272
+ throw new Error("Missing dist-tags from npm registry");
273
+ }
274
+ const same = versionsMatch(currentVersion, latestVersion);
275
+ if (!same && isDevelopment()) {
276
+ log("[basic] version check mismatch:", {
277
+ currentVersion,
278
+ registryVersion: latestVersion,
279
+ channel: usesBetaDistTag(currentVersion) ? "beta" : "latest"
280
+ });
281
+ }
282
+ if (!same) {
283
+ console.warn("[basic] New version available:", latestVersion, `
284
+ run "npm install @basictech/react@${latestVersion}" to update`);
285
+ }
286
+ if (usesBetaDistTag(currentVersion)) {
287
+ log("thank you for being on basictech/react beta :)");
288
+ }
289
+ return {
290
+ hasNewVersion: !same,
291
+ latestVersion,
292
+ currentVersion
293
+ };
294
+ } catch (error) {
295
+ log("Error checking for new version:", error);
296
+ return {
297
+ hasNewVersion: false,
298
+ latestVersion: null,
299
+ currentVersion: null
300
+ };
301
+ }
302
+ }
303
+ function cleanOAuthParamsFromUrl() {
304
+ if (window.location.search.includes("code") || window.location.search.includes("state")) {
305
+ const url = new URL(window.location.href);
306
+ url.searchParams.delete("code");
307
+ url.searchParams.delete("state");
308
+ window.history.pushState({}, document.title, url.pathname + url.search);
309
+ log("Cleaned OAuth parameters from URL");
310
+ }
311
+ }
312
+ function getSyncStatus(statusCode) {
313
+ switch (statusCode) {
314
+ case -1:
315
+ return "ERROR";
316
+ case 0:
317
+ return "OFFLINE";
318
+ case 1:
319
+ return "CONNECTING";
320
+ case 2:
321
+ return "ONLINE";
322
+ case 3:
323
+ return "SYNCING";
324
+ case 4:
325
+ return "ERROR_WILL_RETRY";
326
+ default:
327
+ return "UNKNOWN";
328
+ }
329
+ }
330
+ var init_network = __esm({
331
+ "src/utils/network.ts"() {
332
+ "use strict";
333
+ init_config();
334
+ init_package();
335
+ }
336
+ });
337
+
338
+ // src/context.tsx
339
+ import { createContext, useContext } from "react";
340
+ function useBasic() {
341
+ return useContext(BasicContext);
342
+ }
343
+ var DBStatus, noDb, BasicContext;
344
+ var init_context = __esm({
345
+ "src/context.tsx"() {
346
+ "use strict";
347
+ DBStatus = /* @__PURE__ */ ((DBStatus2) => {
348
+ DBStatus2["LOADING"] = "LOADING";
349
+ DBStatus2["OFFLINE"] = "OFFLINE";
350
+ DBStatus2["CONNECTING"] = "CONNECTING";
351
+ DBStatus2["ONLINE"] = "ONLINE";
352
+ DBStatus2["SYNCING"] = "SYNCING";
353
+ DBStatus2["ERROR"] = "ERROR";
354
+ DBStatus2["ERROR_WILL_RETRY"] = "ERROR_WILL_RETRY";
355
+ DBStatus2["ERROR_TOKEN_EXPIRED"] = "ERROR_TOKEN_EXPIRED";
356
+ return DBStatus2;
357
+ })(DBStatus || {});
358
+ noDb = {
359
+ collection: () => {
360
+ throw new Error("no basicdb found - initialization failed. double check your schema.");
361
+ }
362
+ };
363
+ BasicContext = createContext({
364
+ isReady: false,
365
+ isSignedIn: false,
366
+ user: null,
367
+ did: null,
368
+ scope: null,
369
+ hasScope: () => false,
370
+ missingScopes: () => [],
371
+ signIn: () => Promise.resolve(),
372
+ signInWithHandle: () => Promise.resolve(),
373
+ signOut: () => Promise.resolve(),
374
+ signInWithCode: () => Promise.resolve({ success: false }),
375
+ getToken: (_options) => Promise.reject(new Error("no token")),
376
+ getSignInUrl: () => Promise.resolve(""),
377
+ db: noDb,
378
+ dbStatus: "LOADING" /* LOADING */,
379
+ dbMode: "sync",
380
+ devInfo: null,
381
+ refreshSchemaStatus: async () => {
382
+ },
383
+ isAuthReady: false,
384
+ signin: () => Promise.resolve(),
385
+ signout: () => Promise.resolve(),
386
+ signinWithCode: () => Promise.resolve({ success: false }),
387
+ getSignInLink: () => Promise.resolve("")
388
+ });
389
+ }
390
+ });
391
+
392
+ // src/dev/BasicDevToolbar.tsx
393
+ var BasicDevToolbar_exports = {};
394
+ __export(BasicDevToolbar_exports, {
395
+ BasicDevToolbar: () => BasicDevToolbar
396
+ });
397
+ import { useCallback, useMemo, useState } from "react";
398
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
399
+ function toneForAuth(isReady, isSignedIn) {
400
+ if (!isReady) return "muted";
401
+ if (isSignedIn) return "ok";
402
+ return "warn";
403
+ }
404
+ function toneForDb(dbMode, dbStatus) {
405
+ if (dbMode === "remote") return dbStatus === "ONLINE" /* ONLINE */ ? "ok" : "warn";
406
+ if (dbStatus === "ONLINE" /* ONLINE */ || dbStatus === "SYNCING" /* SYNCING */) return "ok";
407
+ if (dbStatus === "CONNECTING" /* CONNECTING */ || dbStatus === "LOADING" /* LOADING */) return "warn";
408
+ if (dbStatus === "OFFLINE" /* OFFLINE */) return "muted";
409
+ return "bad";
410
+ }
411
+ function toneForSchema(info) {
412
+ if (!info) return "muted";
413
+ if (info.valid && info.status === "current") return "ok";
414
+ if (info.status === "unpublished") return "warn";
415
+ if (info.status === "no_schema") return "muted";
416
+ return "bad";
417
+ }
418
+ function dbStatusLabel(status) {
419
+ switch (status) {
420
+ case "LOADING" /* LOADING */:
421
+ return "Initializing";
422
+ case "OFFLINE" /* OFFLINE */:
423
+ return "Offline";
424
+ case "CONNECTING" /* CONNECTING */:
425
+ return "Connecting";
426
+ case "ONLINE" /* ONLINE */:
427
+ return "Connected";
428
+ case "SYNCING" /* SYNCING */:
429
+ return "Syncing";
430
+ case "ERROR" /* ERROR */:
431
+ return "Error";
432
+ case "ERROR_WILL_RETRY" /* ERROR_WILL_RETRY */:
433
+ return "Retrying";
434
+ case "ERROR_TOKEN_EXPIRED" /* ERROR_TOKEN_EXPIRED */:
435
+ return "Token refresh";
436
+ default:
437
+ return String(status);
438
+ }
439
+ }
440
+ function chipColor(tone) {
441
+ switch (tone) {
442
+ case "ok":
443
+ return "#22c55e";
444
+ case "warn":
445
+ return "#eab308";
446
+ case "bad":
447
+ return "#ef4444";
448
+ default:
449
+ return "#71717a";
450
+ }
451
+ }
452
+ function displayDid(did) {
453
+ return did || "\u2014";
454
+ }
455
+ function displayUserLine(user) {
456
+ const parts = [];
457
+ if (user.sub) parts.push(`sub: ${user.sub}`);
458
+ if (user.email) parts.push(`email: ${user.email}`);
459
+ if (user.name) parts.push(`name: ${user.name}`);
460
+ return parts.length ? parts.join(" \xB7 ") : "\u2014";
461
+ }
462
+ function ClipboardIcon() {
463
+ return /* @__PURE__ */ jsxs(
464
+ "svg",
465
+ {
466
+ width: "14",
467
+ height: "14",
468
+ viewBox: "0 0 24 24",
469
+ fill: "none",
470
+ stroke: "currentColor",
471
+ strokeWidth: "2",
472
+ strokeLinecap: "round",
473
+ strokeLinejoin: "round",
474
+ "aria-hidden": true,
475
+ children: [
476
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
477
+ /* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
478
+ ]
479
+ }
480
+ );
481
+ }
482
+ function SectionHeader({ children }) {
483
+ return /* @__PURE__ */ jsx(
484
+ "div",
485
+ {
486
+ style: {
487
+ fontSize: 10,
488
+ fontWeight: 700,
489
+ color: "#e4e4e7",
490
+ letterSpacing: "0.07em",
491
+ textTransform: "uppercase",
492
+ marginBottom: 8
493
+ },
494
+ children
495
+ }
496
+ );
497
+ }
498
+ function SectionRule() {
499
+ const bleed = PANEL_PAD_X;
500
+ return /* @__PURE__ */ jsx(
501
+ "div",
502
+ {
503
+ role: "separator",
504
+ style: {
505
+ height: 1,
506
+ background: "rgba(255, 255, 255, 0.055)",
507
+ marginLeft: -bleed,
508
+ marginRight: -bleed,
509
+ marginTop: 14,
510
+ marginBottom: 10,
511
+ width: `calc(100% + ${bleed * 2}px)`
512
+ }
513
+ }
514
+ );
515
+ }
516
+ function CopyableRow({
517
+ rowKey,
518
+ label,
519
+ copyText,
520
+ copiedKey,
521
+ onCopied,
522
+ children
523
+ }) {
524
+ const [hover, setHover] = useState(false);
525
+ const canCopy = copyText.length > 0;
526
+ const handleClick = useCallback(
527
+ (e) => {
528
+ e.stopPropagation();
529
+ if (!canCopy) return;
530
+ void navigator.clipboard.writeText(copyText).then(() => onCopied(rowKey));
531
+ },
532
+ [canCopy, copyText, onCopied, rowKey]
533
+ );
534
+ return /* @__PURE__ */ jsxs(
535
+ "div",
536
+ {
537
+ role: canCopy ? "button" : void 0,
538
+ tabIndex: canCopy ? 0 : void 0,
539
+ onClick: canCopy ? handleClick : void 0,
540
+ onKeyDown: canCopy ? (e) => {
541
+ if (e.key === "Enter" || e.key === " ") {
542
+ e.preventDefault();
543
+ handleClick(e);
544
+ }
545
+ } : void 0,
546
+ onMouseEnter: () => setHover(true),
547
+ onMouseLeave: () => setHover(false),
548
+ style: {
549
+ display: "flex",
550
+ gap: 8,
551
+ marginBottom: 6,
552
+ alignItems: "flex-start",
553
+ borderRadius: 6,
554
+ padding: "4px 6px",
555
+ marginLeft: -6,
556
+ marginRight: -6,
557
+ cursor: canCopy ? "pointer" : "default",
558
+ background: hover && canCopy ? "rgba(255,255,255,0.06)" : "transparent",
559
+ transition: "background 0.12s ease"
560
+ },
561
+ children: [
562
+ /* @__PURE__ */ jsx("span", { style: { color: "#a1a1aa", minWidth: 88, flexShrink: 0, paddingTop: 2 }, children: label }),
563
+ /* @__PURE__ */ jsx(
564
+ "span",
565
+ {
566
+ style: {
567
+ flex: 1,
568
+ minWidth: 0,
569
+ wordBreak: "break-all",
570
+ paddingTop: 2,
571
+ lineHeight: 1.35
572
+ },
573
+ children
574
+ }
575
+ ),
576
+ canCopy && /* @__PURE__ */ jsx(
577
+ "span",
578
+ {
579
+ style: {
580
+ flexShrink: 0,
581
+ color: copiedKey === rowKey ? "#22c55e" : "#71717a",
582
+ opacity: hover || copiedKey === rowKey ? 1 : 0,
583
+ transition: "opacity 0.12s ease, color 0.12s ease",
584
+ paddingTop: 2,
585
+ display: "flex",
586
+ alignItems: "flex-start"
587
+ },
588
+ title: "Copy value",
589
+ children: copiedKey === rowKey ? /* @__PURE__ */ jsx("span", { style: { fontSize: 10 }, children: "\u2713" }) : /* @__PURE__ */ jsx(ClipboardIcon, {})
590
+ }
591
+ )
592
+ ]
593
+ }
594
+ );
595
+ }
596
+ function BasicDevToolbar({ enabled = true, debug }) {
597
+ const {
598
+ isReady,
599
+ isSignedIn,
600
+ user,
601
+ did,
602
+ scope,
603
+ missingScopes,
604
+ dbMode,
605
+ dbStatus,
606
+ devInfo,
607
+ refreshSchemaStatus
608
+ } = useBasic();
609
+ const [open, setOpen] = useState(false);
610
+ const [refreshing, setRefreshing] = useState(false);
611
+ const [copied, setCopied] = useState(false);
612
+ const [rowCopied, setRowCopied] = useState(null);
613
+ const show = enabled && typeof window !== "undefined" && isDevelopment(debug);
614
+ const authTone = toneForAuth(isReady, isSignedIn);
615
+ const dbTone = toneForDb(dbMode, dbStatus);
616
+ const schemaTone = toneForSchema(devInfo);
617
+ const syncTone = dbMode === "remote" ? "muted" : dbTone === "ok" || dbStatus === "SYNCING" /* SYNCING */ ? "ok" : dbTone === "warn" ? "warn" : dbTone === "bad" ? "bad" : "muted";
618
+ const handleRefreshSchema = useCallback(async () => {
619
+ setRefreshing(true);
620
+ try {
621
+ await refreshSchemaStatus();
622
+ } finally {
623
+ setRefreshing(false);
624
+ }
625
+ }, [refreshSchemaStatus]);
626
+ const missingList = missingScopes();
627
+ const debugPayload = useMemo(() => {
628
+ return {
629
+ sdkVersion: version,
630
+ isReady,
631
+ isSignedIn,
632
+ did: did ?? null,
633
+ user: user ? {
634
+ sub: user.sub,
635
+ email: user.email,
636
+ name: user.name,
637
+ picture: user.picture
638
+ } : null,
639
+ scope,
640
+ missingScopes: missingList,
641
+ dbMode,
642
+ dbStatus,
643
+ indexedDbName: dbMode === "sync" ? INDEXED_DB_NAME : null,
644
+ schema: devInfo
645
+ };
646
+ }, [isReady, isSignedIn, did, user, scope, dbMode, dbStatus, devInfo, missingList]);
647
+ const handleCopy = useCallback(async () => {
648
+ try {
649
+ await navigator.clipboard.writeText(JSON.stringify(debugPayload, null, 2));
650
+ setCopied(true);
651
+ setTimeout(() => setCopied(false), 2e3);
652
+ } catch {
653
+ }
654
+ }, [debugPayload]);
655
+ const onRowCopied = useCallback((key) => {
656
+ setRowCopied(key);
657
+ setTimeout(() => setRowCopied((k) => k === key ? null : k), 1500);
658
+ }, []);
659
+ if (!show) return null;
660
+ const shell = {
661
+ position: "fixed",
662
+ bottom: 12,
663
+ left: "50%",
664
+ transform: "translateX(-50%)",
665
+ zIndex: 99999,
666
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
667
+ fontSize: 11,
668
+ color: "#e4e4e7",
669
+ pointerEvents: "auto"
670
+ };
671
+ const bar = {
672
+ display: "flex",
673
+ alignItems: "center",
674
+ gap: 8,
675
+ padding: "8px 12px",
676
+ borderRadius: 999,
677
+ background: "rgba(24, 24, 27, 0.92)",
678
+ border: "1px solid rgba(63, 63, 70, 0.9)",
679
+ boxShadow: "0 8px 32px rgba(0,0,0,0.35)",
680
+ cursor: "pointer",
681
+ userSelect: "none"
682
+ };
683
+ const dot = (tone) => /* @__PURE__ */ jsx(
684
+ "span",
685
+ {
686
+ style: {
687
+ display: "block",
688
+ boxSizing: "border-box",
689
+ width: 6,
690
+ height: 6,
691
+ minWidth: 6,
692
+ minHeight: 6,
693
+ maxWidth: 6,
694
+ maxHeight: 6,
695
+ borderRadius: "50%",
696
+ background: chipColor(tone),
697
+ flexShrink: 0
698
+ }
699
+ }
700
+ );
701
+ const dotSlot = (title, tone) => /* @__PURE__ */ jsx(
702
+ "span",
703
+ {
704
+ title,
705
+ style: {
706
+ display: "inline-flex",
707
+ alignItems: "center",
708
+ justifyContent: "center",
709
+ width: 6,
710
+ height: 6,
711
+ flexShrink: 0,
712
+ lineHeight: 0
713
+ },
714
+ children: dot(tone)
715
+ }
716
+ );
717
+ const panel = {
718
+ marginBottom: 8,
719
+ maxHeight: "50vh",
720
+ overflow: "auto",
721
+ padding: PANEL_PAD_X,
722
+ borderRadius: 10,
723
+ background: "rgba(24, 24, 27, 0.96)",
724
+ border: "1px solid rgba(63, 63, 70, 0.9)",
725
+ boxShadow: "0 8px 32px rgba(0,0,0,0.35)",
726
+ minWidth: 300,
727
+ maxWidth: "min(560px, calc(100vw - 24px))"
728
+ };
729
+ const syncStatusText = dbStatusLabel(dbStatus);
730
+ return /* @__PURE__ */ jsxs("div", { style: shell, children: [
731
+ open && /* @__PURE__ */ jsxs("div", { style: panel, children: [
732
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
733
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, fontSize: 12 }, children: "Basic SDK" }),
734
+ /* @__PURE__ */ jsxs("div", { style: { color: "#71717a", fontSize: 10, marginTop: 2 }, children: [
735
+ "v",
736
+ version
737
+ ] })
738
+ ] }),
739
+ /* @__PURE__ */ jsx(SectionHeader, { children: "Auth" }),
740
+ /* @__PURE__ */ jsx(
741
+ CopyableRow,
742
+ {
743
+ rowKey: "ready",
744
+ label: "Ready",
745
+ copyText: String(isReady),
746
+ copiedKey: rowCopied,
747
+ onCopied: onRowCopied,
748
+ children: String(isReady)
749
+ }
750
+ ),
751
+ /* @__PURE__ */ jsx(
752
+ CopyableRow,
753
+ {
754
+ rowKey: "signedIn",
755
+ label: "Signed in",
756
+ copyText: String(isSignedIn),
757
+ copiedKey: rowCopied,
758
+ onCopied: onRowCopied,
759
+ children: String(isSignedIn)
760
+ }
761
+ ),
762
+ /* @__PURE__ */ jsx(
763
+ CopyableRow,
764
+ {
765
+ rowKey: "did",
766
+ label: "DID",
767
+ copyText: did || "",
768
+ copiedKey: rowCopied,
769
+ onCopied: onRowCopied,
770
+ children: displayDid(did)
771
+ }
772
+ ),
773
+ /* @__PURE__ */ jsx(
774
+ CopyableRow,
775
+ {
776
+ rowKey: "user",
777
+ label: "User",
778
+ copyText: user ? displayUserLine(user) : "",
779
+ copiedKey: rowCopied,
780
+ onCopied: onRowCopied,
781
+ children: user ? displayUserLine(user) : "\u2014"
782
+ }
783
+ ),
784
+ /* @__PURE__ */ jsx(
785
+ CopyableRow,
786
+ {
787
+ rowKey: "scopes",
788
+ label: "Scopes",
789
+ copyText: scope || "",
790
+ copiedKey: rowCopied,
791
+ onCopied: onRowCopied,
792
+ children: scope || "\u2014"
793
+ }
794
+ ),
795
+ /* @__PURE__ */ jsx(
796
+ CopyableRow,
797
+ {
798
+ rowKey: "missingScopes",
799
+ label: "Missing scopes",
800
+ copyText: missingList.length ? missingList.join(", ") : "",
801
+ copiedKey: rowCopied,
802
+ onCopied: onRowCopied,
803
+ children: missingList.length ? missingList.join(", ") : "\u2014"
804
+ }
805
+ ),
806
+ /* @__PURE__ */ jsx(SectionRule, {}),
807
+ /* @__PURE__ */ jsx(SectionHeader, { children: "Database" }),
808
+ /* @__PURE__ */ jsx(
809
+ CopyableRow,
810
+ {
811
+ rowKey: "dbMode",
812
+ label: "Mode",
813
+ copyText: dbMode,
814
+ copiedKey: rowCopied,
815
+ onCopied: onRowCopied,
816
+ children: dbMode
817
+ }
818
+ ),
819
+ /* @__PURE__ */ jsx(
820
+ CopyableRow,
821
+ {
822
+ rowKey: "indexedDb",
823
+ label: "IndexedDB",
824
+ copyText: dbMode === "sync" ? INDEXED_DB_NAME : "",
825
+ copiedKey: rowCopied,
826
+ onCopied: onRowCopied,
827
+ children: dbMode === "sync" ? INDEXED_DB_NAME : "\u2014"
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsx(
831
+ CopyableRow,
832
+ {
833
+ rowKey: "syncStatus",
834
+ label: "Sync / status",
835
+ copyText: syncStatusText,
836
+ copiedKey: rowCopied,
837
+ onCopied: onRowCopied,
838
+ children: syncStatusText
839
+ }
840
+ ),
841
+ /* @__PURE__ */ jsx(SectionRule, {}),
842
+ /* @__PURE__ */ jsx(SectionHeader, { children: "Schema" }),
843
+ devInfo ? /* @__PURE__ */ jsxs(Fragment, { children: [
844
+ /* @__PURE__ */ jsx(
845
+ CopyableRow,
846
+ {
847
+ rowKey: "schemaProject",
848
+ label: "Project",
849
+ copyText: devInfo.projectId ?? "",
850
+ copiedKey: rowCopied,
851
+ onCopied: onRowCopied,
852
+ children: devInfo.projectId ?? "\u2014"
853
+ }
854
+ ),
855
+ /* @__PURE__ */ jsx(
856
+ CopyableRow,
857
+ {
858
+ rowKey: "schemaLocalVer",
859
+ label: "Local version",
860
+ copyText: devInfo.localVersion !== void 0 && devInfo.localVersion !== null ? String(devInfo.localVersion) : "",
861
+ copiedKey: rowCopied,
862
+ onCopied: onRowCopied,
863
+ children: devInfo.localVersion ?? "\u2014"
864
+ }
865
+ ),
866
+ /* @__PURE__ */ jsx(
867
+ CopyableRow,
868
+ {
869
+ rowKey: "schemaRemote",
870
+ label: "Remote check",
871
+ copyText: devInfo.status,
872
+ copiedKey: rowCopied,
873
+ onCopied: onRowCopied,
874
+ children: devInfo.status
875
+ }
876
+ ),
877
+ /* @__PURE__ */ jsx(
878
+ CopyableRow,
879
+ {
880
+ rowKey: "schemaValid",
881
+ label: "Valid",
882
+ copyText: String(devInfo.valid),
883
+ copiedKey: rowCopied,
884
+ onCopied: onRowCopied,
885
+ children: String(devInfo.valid)
886
+ }
887
+ ),
888
+ /* @__PURE__ */ jsx(
889
+ CopyableRow,
890
+ {
891
+ rowKey: "schemaChecked",
892
+ label: "Checked",
893
+ copyText: devInfo.lastCheckedAt ? new Date(devInfo.lastCheckedAt).toISOString() : "",
894
+ copiedKey: rowCopied,
895
+ onCopied: onRowCopied,
896
+ children: devInfo.lastCheckedAt ? new Date(devInfo.lastCheckedAt).toLocaleString() : "\u2014"
897
+ }
898
+ ),
899
+ devInfo.error ? /* @__PURE__ */ jsx(
900
+ CopyableRow,
901
+ {
902
+ rowKey: "schemaError",
903
+ label: "Error",
904
+ copyText: devInfo.error,
905
+ copiedKey: rowCopied,
906
+ onCopied: onRowCopied,
907
+ children: devInfo.error
908
+ }
909
+ ) : null
910
+ ] }) : /* @__PURE__ */ jsx(
911
+ CopyableRow,
912
+ {
913
+ rowKey: "schemaStatus",
914
+ label: "Status",
915
+ copyText: "No schema on provider",
916
+ copiedKey: rowCopied,
917
+ onCopied: onRowCopied,
918
+ children: "No schema on provider"
919
+ }
920
+ ),
921
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginTop: 12, flexWrap: "wrap" }, children: [
922
+ /* @__PURE__ */ jsx(
923
+ "button",
924
+ {
925
+ type: "button",
926
+ onClick: (e) => {
927
+ e.stopPropagation();
928
+ void handleRefreshSchema();
929
+ },
930
+ disabled: refreshing,
931
+ style: {
932
+ padding: "6px 10px",
933
+ borderRadius: 6,
934
+ border: "1px solid #3f3f46",
935
+ background: "#27272a",
936
+ color: "#e4e4e7",
937
+ cursor: refreshing ? "wait" : "pointer",
938
+ fontSize: 11,
939
+ fontFamily: "inherit"
940
+ },
941
+ children: refreshing ? "Refreshing\u2026" : "Refresh schema"
942
+ }
943
+ ),
944
+ /* @__PURE__ */ jsx(
945
+ "button",
946
+ {
947
+ type: "button",
948
+ onClick: (e) => {
949
+ e.stopPropagation();
950
+ void handleCopy();
951
+ },
952
+ style: {
953
+ padding: "6px 10px",
954
+ borderRadius: 6,
955
+ border: "1px solid #3f3f46",
956
+ background: "#27272a",
957
+ color: "#e4e4e7",
958
+ cursor: "pointer",
959
+ fontSize: 11,
960
+ fontFamily: "inherit"
961
+ },
962
+ children: copied ? "Copied" : "Copy debug info"
963
+ }
964
+ )
965
+ ] })
966
+ ] }),
967
+ /* @__PURE__ */ jsxs(
968
+ "button",
969
+ {
970
+ type: "button",
971
+ "aria-expanded": open,
972
+ onClick: () => setOpen((o) => !o),
973
+ style: {
974
+ ...bar,
975
+ border: "none",
976
+ width: "100%",
977
+ cursor: "pointer"
978
+ },
979
+ children: [
980
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, letterSpacing: 0.02 }, children: "Basic" }),
981
+ /* @__PURE__ */ jsxs(
982
+ "span",
983
+ {
984
+ style: {
985
+ display: "inline-flex",
986
+ alignItems: "center",
987
+ gap: 6,
988
+ marginLeft: 8,
989
+ height: 6,
990
+ flexShrink: 0,
991
+ lineHeight: 0
992
+ },
993
+ children: [
994
+ dotSlot("Auth", authTone),
995
+ dotSlot("DB", dbTone),
996
+ dotSlot("Sync", syncTone),
997
+ dotSlot("Schema", schemaTone)
998
+ ]
999
+ }
1000
+ ),
1001
+ /* @__PURE__ */ jsx("span", { style: { color: "#71717a", marginLeft: 4 }, children: open ? "\u25BE" : "\u25B4" })
1002
+ ]
1003
+ }
1004
+ )
1005
+ ] });
1006
+ }
1007
+ var INDEXED_DB_NAME, PANEL_PAD_X;
1008
+ var init_BasicDevToolbar = __esm({
1009
+ "src/dev/BasicDevToolbar.tsx"() {
1010
+ "use strict";
1011
+ "use client";
1012
+ init_context();
1013
+ init_package();
1014
+ init_network();
1015
+ INDEXED_DB_NAME = "basicdb";
1016
+ PANEL_PAD_X = 12;
1017
+ }
1018
+ });
1019
+
219
1020
  // src/AuthContext.tsx
220
- import { createContext, useContext, useEffect, useState, useRef } from "react";
1021
+ import { useCallback as useCallback2, useEffect, useRef, useState as useState2, Suspense, lazy } from "react";
221
1022
 
222
1023
  // src/sync/index.ts
223
1024
  init_config();
@@ -808,75 +1609,8 @@ async function resolveHandle(handle) {
808
1609
  return resolved;
809
1610
  }
810
1611
 
811
- // src/utils/network.ts
812
- init_config();
813
-
814
- // package.json
815
- var version = "0.8.0-beta.1";
816
-
817
- // src/utils/network.ts
818
- function isDevelopment(debug) {
819
- return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname.includes("localhost") || window.location.hostname.includes("127.0.0.1") || window.location.hostname.includes(".local") || process.env.NODE_ENV === "development" || debug === true;
820
- }
821
- async function checkForNewVersion() {
822
- try {
823
- const isBeta = version.includes("beta");
824
- const response = await fetch(`https://registry.npmjs.org/@basictech/react/${isBeta ? "beta" : "latest"}`);
825
- if (!response.ok) {
826
- throw new Error("Failed to fetch version from npm");
827
- }
828
- const data = await response.json();
829
- const latestVersion = data.version;
830
- if (latestVersion !== version) {
831
- console.warn("[basic] New version available:", latestVersion, `
832
- run "npm install @basictech/react@${latestVersion}" to update`);
833
- }
834
- if (isBeta) {
835
- log("thank you for being on basictech/react beta :)");
836
- }
837
- return {
838
- hasNewVersion: version !== latestVersion,
839
- latestVersion,
840
- currentVersion: version
841
- };
842
- } catch (error) {
843
- log("Error checking for new version:", error);
844
- return {
845
- hasNewVersion: false,
846
- latestVersion: null,
847
- currentVersion: null
848
- };
849
- }
850
- }
851
- function cleanOAuthParamsFromUrl() {
852
- if (window.location.search.includes("code") || window.location.search.includes("state")) {
853
- const url = new URL(window.location.href);
854
- url.searchParams.delete("code");
855
- url.searchParams.delete("state");
856
- window.history.pushState({}, document.title, url.pathname + url.search);
857
- log("Cleaned OAuth parameters from URL");
858
- }
859
- }
860
- function getSyncStatus(statusCode) {
861
- switch (statusCode) {
862
- case -1:
863
- return "ERROR";
864
- case 0:
865
- return "OFFLINE";
866
- case 1:
867
- return "CONNECTING";
868
- case 2:
869
- return "ONLINE";
870
- case 3:
871
- return "SYNCING";
872
- case 4:
873
- return "ERROR_WILL_RETRY";
874
- default:
875
- return "UNKNOWN";
876
- }
877
- }
878
-
879
1612
  // src/core/auth/AuthManager.ts
1613
+ init_network();
880
1614
  init_config();
881
1615
  function generateCodeVerifier() {
882
1616
  const array = new Uint8Array(32);
@@ -1577,6 +2311,7 @@ var AuthManager = class {
1577
2311
 
1578
2312
  // src/AuthContext.tsx
1579
2313
  init_config();
2314
+ init_package();
1580
2315
 
1581
2316
  // src/updater/versionUpdater.ts
1582
2317
  init_config();
@@ -1701,6 +2436,9 @@ function getMigrations() {
1701
2436
  ];
1702
2437
  }
1703
2438
 
2439
+ // src/AuthContext.tsx
2440
+ init_network();
2441
+
1704
2442
  // src/utils/schema.ts
1705
2443
  init_config();
1706
2444
  import { validateSchema, compareSchemas } from "@basictech/schema";
@@ -1800,57 +2538,18 @@ async function validateAndCheckSchema(schema) {
1800
2538
  }
1801
2539
 
1802
2540
  // src/AuthContext.tsx
1803
- import { jsx, jsxs } from "react/jsx-runtime";
2541
+ init_context();
2542
+ init_context();
2543
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2544
+ var BasicDevToolbar2 = lazy(
2545
+ () => Promise.resolve().then(() => (init_BasicDevToolbar(), BasicDevToolbar_exports)).then((m) => ({ default: m.BasicDevToolbar }))
2546
+ );
1804
2547
  var DEFAULT_AUTH_CONFIG = {
1805
2548
  scopes: "profile,email,app:admin",
1806
2549
  pds_url: "https://pds.basic.id",
1807
2550
  admin_url: "https://api.basic.tech",
1808
2551
  ws_url: "wss://pds.basic.id/ws"
1809
2552
  };
1810
- var DBStatus = /* @__PURE__ */ ((DBStatus2) => {
1811
- DBStatus2["LOADING"] = "LOADING";
1812
- DBStatus2["OFFLINE"] = "OFFLINE";
1813
- DBStatus2["CONNECTING"] = "CONNECTING";
1814
- DBStatus2["ONLINE"] = "ONLINE";
1815
- DBStatus2["SYNCING"] = "SYNCING";
1816
- DBStatus2["ERROR"] = "ERROR";
1817
- DBStatus2["ERROR_WILL_RETRY"] = "ERROR_WILL_RETRY";
1818
- DBStatus2["ERROR_TOKEN_EXPIRED"] = "ERROR_TOKEN_EXPIRED";
1819
- return DBStatus2;
1820
- })(DBStatus || {});
1821
- var noDb = {
1822
- collection: () => {
1823
- throw new Error("no basicdb found - initialization failed. double check your schema.");
1824
- }
1825
- };
1826
- var BasicContext = createContext({
1827
- // Auth state
1828
- isReady: false,
1829
- isSignedIn: false,
1830
- user: null,
1831
- did: null,
1832
- scope: null,
1833
- hasScope: () => false,
1834
- missingScopes: () => [],
1835
- // Auth actions
1836
- signIn: () => Promise.resolve(),
1837
- signInWithHandle: () => Promise.resolve(),
1838
- signOut: () => Promise.resolve(),
1839
- signInWithCode: () => Promise.resolve({ success: false }),
1840
- // Token management
1841
- getToken: (_options) => Promise.reject(new Error("no token")),
1842
- getSignInUrl: () => Promise.resolve(""),
1843
- // DB access
1844
- db: noDb,
1845
- dbStatus: "LOADING" /* LOADING */,
1846
- dbMode: "sync",
1847
- // Legacy aliases
1848
- isAuthReady: false,
1849
- signin: () => Promise.resolve(),
1850
- signout: () => Promise.resolve(),
1851
- signinWithCode: () => Promise.resolve({ success: false }),
1852
- getSignInLink: () => Promise.resolve("")
1853
- });
1854
2553
  function snapshotAuth(mgr) {
1855
2554
  return {
1856
2555
  isSignedIn: mgr.isSignedIn,
@@ -1868,7 +2567,8 @@ function BasicProvider({
1868
2567
  debug = false,
1869
2568
  storage,
1870
2569
  auth,
1871
- dbMode = "sync"
2570
+ dbMode = "sync",
2571
+ devToolbar = false
1872
2572
  }) {
1873
2573
  const project_id = schema?.project_id || project_id_prop;
1874
2574
  if (auth?.server_url && !auth?.pds_url) {
@@ -1883,7 +2583,9 @@ function BasicProvider({
1883
2583
  const scopesString = Array.isArray(authConfig.scopes) ? authConfig.scopes.join(" ") : authConfig.scopes;
1884
2584
  const storageRef = useRef(storage || new LocalStorageAdapter());
1885
2585
  const storageAdapter = storageRef.current;
1886
- const [authState, setAuthState] = useState({
2586
+ const schemaRef = useRef(schema);
2587
+ schemaRef.current = schema;
2588
+ const [authState, setAuthState] = useState2({
1887
2589
  isSignedIn: false,
1888
2590
  hasToken: false,
1889
2591
  isAuthReady: false,
@@ -1907,11 +2609,47 @@ function BasicProvider({
1907
2609
  }
1908
2610
  const syncRef = useRef(null);
1909
2611
  const remoteDbRef = useRef(null);
1910
- const [shouldConnect, setShouldConnect] = useState(false);
1911
- const [dbStatus, setDbStatus] = useState("OFFLINE" /* OFFLINE */);
1912
- const [isReady, setIsReady] = useState(false);
1913
- const [error, setError] = useState(null);
2612
+ const [shouldConnect, setShouldConnect] = useState2(false);
2613
+ const [dbStatus, setDbStatus] = useState2("OFFLINE" /* OFFLINE */);
2614
+ const [isReady, setIsReady] = useState2(false);
2615
+ const [error, setError] = useState2(null);
2616
+ const [schemaDevInfo, setSchemaDevInfo] = useState2(null);
1914
2617
  const isDevMode = () => isDevelopment(debug);
2618
+ const refreshSchemaStatus = useCallback2(async () => {
2619
+ const s = schemaRef.current;
2620
+ if (!s) {
2621
+ setSchemaDevInfo(
2622
+ project_id ? {
2623
+ projectId: project_id,
2624
+ localVersion: void 0,
2625
+ status: "no_schema",
2626
+ valid: false,
2627
+ lastCheckedAt: Date.now()
2628
+ } : null
2629
+ );
2630
+ return;
2631
+ }
2632
+ const result = await validateAndCheckSchema(s);
2633
+ if (!result.isValid) {
2634
+ const errText = result.errors?.map((e) => e.message || "").join("; ") || "invalid";
2635
+ setSchemaDevInfo({
2636
+ projectId: s.project_id ?? null,
2637
+ localVersion: s.version,
2638
+ status: "invalid",
2639
+ valid: false,
2640
+ lastCheckedAt: Date.now(),
2641
+ error: errText
2642
+ });
2643
+ return;
2644
+ }
2645
+ setSchemaDevInfo({
2646
+ projectId: s.project_id ?? null,
2647
+ localVersion: s.version,
2648
+ status: result.schemaStatus.status ?? "unknown",
2649
+ valid: result.schemaStatus.valid,
2650
+ lastCheckedAt: Date.now()
2651
+ });
2652
+ }, [project_id]);
1915
2653
  useEffect(() => {
1916
2654
  const runVersionUpdater = async () => {
1917
2655
  try {
@@ -1985,11 +2723,19 @@ function BasicProvider({
1985
2723
  if (!result.isValid) {
1986
2724
  let errorMessage = "";
1987
2725
  if (result.errors) {
1988
- result.errors.forEach((error2, index) => {
1989
- errorMessage += `${index + 1}: ${error2.message} - at ${error2.instancePath}
2726
+ result.errors.forEach((err, index) => {
2727
+ errorMessage += `${index + 1}: ${err.message} - at ${err.instancePath}
1990
2728
  `;
1991
2729
  });
1992
2730
  }
2731
+ setSchemaDevInfo({
2732
+ projectId: schema?.project_id ?? null,
2733
+ localVersion: schema?.version,
2734
+ status: "invalid",
2735
+ valid: false,
2736
+ lastCheckedAt: Date.now(),
2737
+ error: errorMessage.trim() || void 0
2738
+ });
1993
2739
  setError({
1994
2740
  code: "schema_invalid",
1995
2741
  title: "Basic Schema is invalid!",
@@ -1998,6 +2744,13 @@ function BasicProvider({
1998
2744
  setIsReady(true);
1999
2745
  return null;
2000
2746
  }
2747
+ setSchemaDevInfo({
2748
+ projectId: schema?.project_id ?? null,
2749
+ localVersion: schema?.version,
2750
+ status: result.schemaStatus.status ?? "unknown",
2751
+ valid: result.schemaStatus.valid,
2752
+ lastCheckedAt: Date.now()
2753
+ });
2001
2754
  if (dbMode === "remote") {
2002
2755
  initRemoteDb();
2003
2756
  } else {
@@ -2017,6 +2770,15 @@ function BasicProvider({
2017
2770
  if (schema) {
2018
2771
  checkSchema();
2019
2772
  } else {
2773
+ setSchemaDevInfo(
2774
+ project_id ? {
2775
+ projectId: project_id,
2776
+ localVersion: void 0,
2777
+ status: "no_schema",
2778
+ valid: false,
2779
+ lastCheckedAt: Date.now()
2780
+ } : null
2781
+ );
2020
2782
  if (dbMode === "remote" && project_id) {
2021
2783
  initRemoteDb();
2022
2784
  } else {
@@ -2083,68 +2845,71 @@ function BasicProvider({
2083
2845
  return syncRef.current || noDb;
2084
2846
  };
2085
2847
  const contextValue = {
2086
- // Auth state
2087
2848
  isReady: authState.isAuthReady,
2088
2849
  isSignedIn: authState.isSignedIn,
2089
2850
  user: authState.user,
2090
2851
  did: authState.did,
2091
2852
  scope: authState.tokenScope,
2092
- hasScope: (scope) => authRef.current.hasScope(scope),
2853
+ hasScope: (s) => authRef.current.hasScope(s),
2093
2854
  missingScopes: () => authRef.current.missingScopes(),
2094
- // Auth actions
2095
2855
  signIn: handleSignIn,
2096
2856
  signInWithHandle: handleSignInWithHandle,
2097
2857
  signOut: handleSignOut,
2098
2858
  signInWithCode: (code, state) => authRef.current.signInWithCode(code, state),
2099
- // Token management
2100
2859
  getToken: (opts) => authRef.current.getToken(opts),
2101
2860
  getSignInUrl: (redirectUri) => authRef.current.getSignInUrl(redirectUri),
2102
- // DB access
2103
2861
  db: getCurrentDb(),
2104
2862
  dbStatus,
2105
2863
  dbMode,
2106
- // Legacy aliases (deprecated)
2864
+ devInfo: schemaDevInfo,
2865
+ refreshSchemaStatus,
2107
2866
  isAuthReady: authState.isAuthReady,
2108
2867
  signin: handleSignIn,
2109
2868
  signout: handleSignOut,
2110
2869
  signinWithCode: (code, state) => authRef.current.signInWithCode(code, state),
2111
2870
  getSignInLink: (redirectUri) => authRef.current.getSignInUrl(redirectUri)
2112
2871
  };
2113
- return /* @__PURE__ */ jsxs(BasicContext.Provider, { value: contextValue, children: [
2114
- error && isDevMode() && /* @__PURE__ */ jsx(ErrorDisplay, { error }),
2872
+ return /* @__PURE__ */ jsxs2(BasicContext.Provider, { value: contextValue, children: [
2873
+ error && isDevMode() && /* @__PURE__ */ jsx2(ErrorDisplay, { error }),
2874
+ devToolbar && isDevMode() && /* @__PURE__ */ jsx2(Suspense, { fallback: null, children: /* @__PURE__ */ jsx2(BasicDevToolbar2, { debug }) }),
2115
2875
  isReady && children
2116
2876
  ] });
2117
2877
  }
2118
2878
  function ErrorDisplay({ error }) {
2119
- return /* @__PURE__ */ jsxs("div", { style: {
2120
- position: "absolute",
2121
- top: 20,
2122
- left: 20,
2123
- color: "black",
2124
- backgroundColor: "#f8d7da",
2125
- border: "1px solid #f5c6cb",
2126
- borderRadius: "4px",
2127
- padding: "20px",
2128
- maxWidth: "400px",
2129
- margin: "20px auto",
2130
- boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
2131
- fontFamily: "monospace"
2132
- }, children: [
2133
- /* @__PURE__ */ jsxs("h3", { style: { fontSize: "0.8rem", opacity: 0.8 }, children: [
2134
- "code: ",
2135
- error.code
2136
- ] }),
2137
- /* @__PURE__ */ jsx("h1", { style: { fontSize: "1.2rem", lineHeight: "1.5" }, children: error.title }),
2138
- /* @__PURE__ */ jsx("p", { children: error.message })
2139
- ] });
2140
- }
2141
- function useBasic() {
2142
- return useContext(BasicContext);
2879
+ return /* @__PURE__ */ jsxs2(
2880
+ "div",
2881
+ {
2882
+ style: {
2883
+ position: "absolute",
2884
+ top: 20,
2885
+ left: 20,
2886
+ color: "black",
2887
+ backgroundColor: "#f8d7da",
2888
+ border: "1px solid #f5c6cb",
2889
+ borderRadius: "4px",
2890
+ padding: "20px",
2891
+ maxWidth: "400px",
2892
+ margin: "20px auto",
2893
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
2894
+ fontFamily: "monospace"
2895
+ },
2896
+ children: [
2897
+ /* @__PURE__ */ jsxs2("h3", { style: { fontSize: "0.8rem", opacity: 0.8 }, children: [
2898
+ "code: ",
2899
+ error.code
2900
+ ] }),
2901
+ /* @__PURE__ */ jsx2("h1", { style: { fontSize: "1.2rem", lineHeight: 1.5 }, children: error.title }),
2902
+ /* @__PURE__ */ jsx2("p", { children: error.message })
2903
+ ]
2904
+ }
2905
+ );
2143
2906
  }
2144
2907
 
2145
2908
  // src/index.ts
2909
+ init_BasicDevToolbar();
2146
2910
  import { useLiveQuery as useQuery } from "dexie-react-hooks";
2147
2911
  export {
2912
+ BasicDevToolbar,
2148
2913
  BasicProvider,
2149
2914
  DBStatus,
2150
2915
  NotAuthenticatedError,