silosdk 0.0.0 → 0.0.1

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.
Files changed (85) hide show
  1. package/README.md +3 -1
  2. package/dist/_virtual/rolldown_runtime.cjs +29 -0
  3. package/dist/cli/d1.cjs +93 -0
  4. package/dist/cli/d1.mjs +92 -0
  5. package/dist/cli/index.cjs +93 -0
  6. package/dist/cli/index.d.cts +1 -0
  7. package/dist/cli/index.d.mts +1 -0
  8. package/dist/cli/index.mjs +94 -0
  9. package/dist/cli/init.cjs +134 -0
  10. package/dist/cli/init.mjs +133 -0
  11. package/dist/cli/kv.cjs +63 -0
  12. package/dist/cli/kv.mjs +60 -0
  13. package/dist/cli/r2.cjs +83 -0
  14. package/dist/cli/r2.mjs +82 -0
  15. package/dist/cli/wrangler.cjs +93 -0
  16. package/dist/cli/wrangler.mjs +89 -0
  17. package/dist/local/adapters/cloudflare.cjs +200 -0
  18. package/dist/local/adapters/cloudflare.d.cts +50 -0
  19. package/dist/local/adapters/cloudflare.d.mts +50 -0
  20. package/dist/local/adapters/cloudflare.mjs +200 -0
  21. package/dist/local/auth-context.cjs +14 -0
  22. package/dist/local/auth-context.d.cts +7 -0
  23. package/dist/local/auth-context.d.mts +7 -0
  24. package/dist/local/auth-context.mjs +12 -0
  25. package/dist/local/auth.cjs +109 -0
  26. package/dist/local/auth.d.cts +26 -0
  27. package/dist/local/auth.d.mts +26 -0
  28. package/dist/local/auth.mjs +99 -0
  29. package/dist/local/commit.cjs +350 -0
  30. package/dist/local/commit.d.cts +59 -0
  31. package/dist/local/commit.d.mts +59 -0
  32. package/dist/local/commit.mjs +349 -0
  33. package/dist/local/config.cjs +17 -0
  34. package/dist/local/config.mjs +15 -0
  35. package/dist/local/index.cjs +16 -0
  36. package/dist/local/index.d.cts +10 -0
  37. package/dist/local/index.d.mts +10 -0
  38. package/dist/local/index.mjs +9 -0
  39. package/dist/local/provider.cjs +204 -0
  40. package/dist/local/provider.d.cts +25 -0
  41. package/dist/local/provider.d.mts +25 -0
  42. package/dist/local/provider.mjs +203 -0
  43. package/dist/local/query-store.cjs +276 -0
  44. package/dist/local/query-store.mjs +274 -0
  45. package/dist/local/storage.cjs +71 -0
  46. package/dist/local/storage.d.cts +7 -0
  47. package/dist/local/storage.d.mts +7 -0
  48. package/dist/local/storage.mjs +68 -0
  49. package/dist/local/sync.cjs +124 -0
  50. package/dist/local/sync.d.cts +36 -0
  51. package/dist/local/sync.d.mts +36 -0
  52. package/dist/local/sync.mjs +122 -0
  53. package/dist/local/view.cjs +257 -0
  54. package/dist/local/view.d.cts +24 -0
  55. package/dist/local/view.d.mts +24 -0
  56. package/dist/local/view.mjs +254 -0
  57. package/dist/package.cjs +11 -0
  58. package/dist/package.mjs +5 -0
  59. package/dist/schema/index.cjs +276 -0
  60. package/dist/schema/index.d.cts +207 -0
  61. package/dist/schema/index.d.mts +207 -0
  62. package/dist/schema/index.mjs +265 -0
  63. package/dist/server/auth.cjs +132 -0
  64. package/dist/server/auth.d.cts +49 -0
  65. package/dist/server/auth.d.mts +49 -0
  66. package/dist/server/auth.mjs +122 -0
  67. package/dist/server/d1.cjs +120 -0
  68. package/dist/server/d1.mjs +116 -0
  69. package/dist/server/do.cjs +132 -0
  70. package/dist/server/do.d.cts +21 -0
  71. package/dist/server/do.d.mts +21 -0
  72. package/dist/server/do.mjs +131 -0
  73. package/dist/server/index.cjs +355 -0
  74. package/dist/server/index.d.cts +65 -0
  75. package/dist/server/index.d.mts +65 -0
  76. package/dist/server/index.mjs +348 -0
  77. package/dist/server/protect.cjs +34 -0
  78. package/dist/server/protect.d.cts +32 -0
  79. package/dist/server/protect.d.mts +32 -0
  80. package/dist/server/protect.mjs +33 -0
  81. package/dist/server/r2.cjs +58 -0
  82. package/dist/server/r2.d.cts +4 -0
  83. package/dist/server/r2.d.mts +4 -0
  84. package/dist/server/r2.mjs +53 -0
  85. package/package.json +55 -2
@@ -0,0 +1,50 @@
1
+ import { MergeFn, SyncAdapter, SyncPayload } from "../sync.cjs";
2
+ import { SQLiteDatabase } from "expo-sqlite";
3
+
4
+ //#region src/local/adapters/cloudflare.d.ts
5
+ type CloudflareAdapterOptions = {
6
+ url: string;
7
+ getToken?: () => Promise<string | null>;
8
+ db: SQLiteDatabase;
9
+ subscribers: Map<string, {
10
+ update: () => void;
11
+ }>;
12
+ merge?: MergeFn;
13
+ };
14
+ declare class CloudflareSyncAdapter extends SyncAdapter {
15
+ private url;
16
+ private getToken?;
17
+ private db;
18
+ private subscribers;
19
+ private ws;
20
+ private receiveHandler?;
21
+ private reconnectTimer;
22
+ private backoff;
23
+ private alive;
24
+ private registeredViews;
25
+ private failureCount;
26
+ private firstFailureAt;
27
+ private blocked;
28
+ private warned;
29
+ private ignoreClose;
30
+ private pendingCommits;
31
+ constructor(options: CloudflareAdapterOptions);
32
+ /** Register the view names this adapter should subscribe to on connect. */
33
+ registerViews(names: string[]): void;
34
+ connect(): Promise<void>;
35
+ private handleMessage;
36
+ private scheduleReconnect;
37
+ private recordFailure;
38
+ resetConnection(): void;
39
+ send(payload: SyncPayload): Promise<void>;
40
+ onReceive(handler: (payload: SyncPayload) => Promise<void>): void;
41
+ fetchInitialState(): Promise<SyncPayload | null>;
42
+ disconnect(): void;
43
+ /**
44
+ * Reset backoff and force an immediate reconnect.
45
+ * Used after token changes (e.g. elevation from anonymous to authenticated).
46
+ */
47
+ forceReconnect(): void;
48
+ }
49
+ //#endregion
50
+ export { CloudflareSyncAdapter };
@@ -0,0 +1,50 @@
1
+ import { MergeFn, SyncAdapter, SyncPayload } from "../sync.mjs";
2
+ import { SQLiteDatabase } from "expo-sqlite";
3
+
4
+ //#region src/local/adapters/cloudflare.d.ts
5
+ type CloudflareAdapterOptions = {
6
+ url: string;
7
+ getToken?: () => Promise<string | null>;
8
+ db: SQLiteDatabase;
9
+ subscribers: Map<string, {
10
+ update: () => void;
11
+ }>;
12
+ merge?: MergeFn;
13
+ };
14
+ declare class CloudflareSyncAdapter extends SyncAdapter {
15
+ private url;
16
+ private getToken?;
17
+ private db;
18
+ private subscribers;
19
+ private ws;
20
+ private receiveHandler?;
21
+ private reconnectTimer;
22
+ private backoff;
23
+ private alive;
24
+ private registeredViews;
25
+ private failureCount;
26
+ private firstFailureAt;
27
+ private blocked;
28
+ private warned;
29
+ private ignoreClose;
30
+ private pendingCommits;
31
+ constructor(options: CloudflareAdapterOptions);
32
+ /** Register the view names this adapter should subscribe to on connect. */
33
+ registerViews(names: string[]): void;
34
+ connect(): Promise<void>;
35
+ private handleMessage;
36
+ private scheduleReconnect;
37
+ private recordFailure;
38
+ resetConnection(): void;
39
+ send(payload: SyncPayload): Promise<void>;
40
+ onReceive(handler: (payload: SyncPayload) => Promise<void>): void;
41
+ fetchInitialState(): Promise<SyncPayload | null>;
42
+ disconnect(): void;
43
+ /**
44
+ * Reset backoff and force an immediate reconnect.
45
+ * Used after token changes (e.g. elevation from anonymous to authenticated).
46
+ */
47
+ forceReconnect(): void;
48
+ }
49
+ //#endregion
50
+ export { CloudflareSyncAdapter };
@@ -0,0 +1,200 @@
1
+ import { SyncAdapter, applyRemoteOps } from "../sync.mjs";
2
+
3
+ //#region src/local/adapters/cloudflare.ts
4
+ const INITIAL_BACKOFF = 1e3;
5
+ const MAX_BACKOFF = 3e4;
6
+ const SYNC_RETRY_WINDOW_MS = 3e4;
7
+ const SYNC_MAX_RETRIES = 5;
8
+ var CloudflareSyncAdapter = class extends SyncAdapter {
9
+ constructor(options) {
10
+ super({ merge: options.merge });
11
+ this.ws = null;
12
+ this.reconnectTimer = null;
13
+ this.backoff = INITIAL_BACKOFF;
14
+ this.alive = true;
15
+ this.registeredViews = [];
16
+ this.failureCount = 0;
17
+ this.firstFailureAt = 0;
18
+ this.blocked = false;
19
+ this.warned = false;
20
+ this.ignoreClose = false;
21
+ this.pendingCommits = /* @__PURE__ */ new Map();
22
+ this.url = options.url;
23
+ this.getToken = options.getToken;
24
+ this.db = options.db;
25
+ this.subscribers = options.subscribers;
26
+ }
27
+ /** Register the view names this adapter should subscribe to on connect. */
28
+ registerViews(names) {
29
+ this.registeredViews = names;
30
+ }
31
+ async connect() {
32
+ if (!this.alive) return;
33
+ if (this.blocked) return;
34
+ try {
35
+ const wsUrl = this.url.replace(/^https?/, (m) => m === "https" ? "wss" : "ws");
36
+ const token = this.getToken ? await this.getToken() : null;
37
+ if (this.getToken && !token) return;
38
+ const url = token ? `${wsUrl}/sync?token=${encodeURIComponent(token)}` : `${wsUrl}/sync`;
39
+ this.ws = new WebSocket(url);
40
+ this.ws.onopen = () => {
41
+ this.backoff = INITIAL_BACKOFF;
42
+ if (this.registeredViews.length > 0) this.ws.send(JSON.stringify({
43
+ type: "subscribe",
44
+ views: this.registeredViews
45
+ }));
46
+ };
47
+ this.ws.onmessage = async (event) => {
48
+ const msg = JSON.parse(event.data);
49
+ await this.handleMessage(msg);
50
+ };
51
+ this.ws.onclose = () => {
52
+ if (this.ignoreClose) {
53
+ this.ignoreClose = false;
54
+ return;
55
+ }
56
+ if (this.alive) this.scheduleReconnect();
57
+ };
58
+ this.ws.onerror = () => {
59
+ this.ws?.close();
60
+ };
61
+ } catch {
62
+ if (this.alive) this.scheduleReconnect();
63
+ }
64
+ }
65
+ async handleMessage(msg) {
66
+ switch (msg.type) {
67
+ case "hydrate": {
68
+ const ops = (msg.rows ?? []).map((row) => ({
69
+ kind: "add",
70
+ view: { name: msg.view },
71
+ id: row.id,
72
+ value: row.data,
73
+ version: row.version ?? 1,
74
+ timestamp: row.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
75
+ }));
76
+ if (ops.length > 0) await applyRemoteOps(this.db, ops, this.subscribers, this.merge);
77
+ break;
78
+ }
79
+ case "push": {
80
+ const ops = msg.ops ?? [];
81
+ if (ops.length > 0) await applyRemoteOps(this.db, ops, this.subscribers, this.merge);
82
+ break;
83
+ }
84
+ case "committed": {
85
+ const pending = this.pendingCommits.get(msg.requestId);
86
+ if (pending) {
87
+ pending.resolve();
88
+ this.pendingCommits.delete(msg.requestId);
89
+ }
90
+ break;
91
+ }
92
+ case "rejected": {
93
+ const pending = this.pendingCommits.get(msg.requestId);
94
+ if (pending) {
95
+ pending.reject(msg.reason ?? "Rejected by server");
96
+ this.pendingCommits.delete(msg.requestId);
97
+ }
98
+ break;
99
+ }
100
+ case "error":
101
+ console.error("[CloudflareSyncAdapter] server error:", msg.message);
102
+ break;
103
+ }
104
+ if (msg.type === "push" && this.receiveHandler) await this.receiveHandler({ ops: msg.ops ?? [] });
105
+ }
106
+ scheduleReconnect() {
107
+ this.recordFailure();
108
+ if (this.blocked) return;
109
+ if (this.reconnectTimer) return;
110
+ this.reconnectTimer = setTimeout(() => {
111
+ this.reconnectTimer = null;
112
+ this.backoff = Math.min(this.backoff * 2, MAX_BACKOFF);
113
+ this.connect();
114
+ }, this.backoff);
115
+ }
116
+ recordFailure() {
117
+ const now = Date.now();
118
+ if (!this.firstFailureAt || now - this.firstFailureAt > SYNC_RETRY_WINDOW_MS) {
119
+ this.firstFailureAt = now;
120
+ this.failureCount = 0;
121
+ }
122
+ this.failureCount += 1;
123
+ if (this.failureCount > SYNC_MAX_RETRIES) {
124
+ this.blocked = true;
125
+ if (!this.warned) {
126
+ this.warned = true;
127
+ console.warn("[CloudflareSyncAdapter] Retry budget exceeded, blocking reconnects");
128
+ }
129
+ }
130
+ }
131
+ resetConnection() {
132
+ this.blocked = false;
133
+ this.warned = false;
134
+ this.failureCount = 0;
135
+ this.firstFailureAt = 0;
136
+ this.backoff = INITIAL_BACKOFF;
137
+ if (this.reconnectTimer) {
138
+ clearTimeout(this.reconnectTimer);
139
+ this.reconnectTimer = null;
140
+ }
141
+ if (this.ws) {
142
+ this.ignoreClose = true;
143
+ this.ws.close();
144
+ this.ws = null;
145
+ }
146
+ this.connect();
147
+ }
148
+ async send(payload) {
149
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
150
+ const requestId = Math.random().toString(36).slice(2);
151
+ return new Promise((resolve, reject) => {
152
+ this.pendingCommits.set(requestId, {
153
+ resolve,
154
+ reject
155
+ });
156
+ this.ws.send(JSON.stringify({
157
+ type: "commit",
158
+ ops: payload.ops,
159
+ requestId
160
+ }));
161
+ setTimeout(() => {
162
+ if (this.pendingCommits.has(requestId)) {
163
+ this.pendingCommits.delete(requestId);
164
+ resolve();
165
+ }
166
+ }, 1e4);
167
+ });
168
+ }
169
+ onReceive(handler) {
170
+ this.receiveHandler = handler;
171
+ }
172
+ async fetchInitialState() {
173
+ return null;
174
+ }
175
+ disconnect() {
176
+ this.alive = false;
177
+ if (this.reconnectTimer) {
178
+ clearTimeout(this.reconnectTimer);
179
+ this.reconnectTimer = null;
180
+ }
181
+ this.ws?.close();
182
+ this.ws = null;
183
+ }
184
+ /**
185
+ * Reset backoff and force an immediate reconnect.
186
+ * Used after token changes (e.g. elevation from anonymous to authenticated).
187
+ */
188
+ forceReconnect() {
189
+ if (this.reconnectTimer) {
190
+ clearTimeout(this.reconnectTimer);
191
+ this.reconnectTimer = null;
192
+ }
193
+ this.backoff = INITIAL_BACKOFF;
194
+ if (this.ws) this.ws.close();
195
+ else this.connect();
196
+ }
197
+ };
198
+
199
+ //#endregion
200
+ export { CloudflareSyncAdapter };
@@ -0,0 +1,14 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ let react = require("react");
3
+
4
+ //#region src/local/auth-context.ts
5
+ const AuthContext = (0, react.createContext)(null);
6
+ function useAuth() {
7
+ const ctx = (0, react.useContext)(AuthContext);
8
+ if (!ctx) throw new Error("silosdk: useAuth() must be used within <Provider>");
9
+ return ctx;
10
+ }
11
+
12
+ //#endregion
13
+ exports.AuthContext = AuthContext;
14
+ exports.useAuth = useAuth;
@@ -0,0 +1,7 @@
1
+ import { AuthState } from "./auth.cjs";
2
+ import "react";
3
+
4
+ //#region src/local/auth-context.d.ts
5
+ declare function useAuth(): AuthState;
6
+ //#endregion
7
+ export { useAuth };
@@ -0,0 +1,7 @@
1
+ import { AuthState } from "./auth.mjs";
2
+ import "react";
3
+
4
+ //#region src/local/auth-context.d.ts
5
+ declare function useAuth(): AuthState;
6
+ //#endregion
7
+ export { useAuth };
@@ -0,0 +1,12 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ //#region src/local/auth-context.ts
4
+ const AuthContext = createContext(null);
5
+ function useAuth() {
6
+ const ctx = useContext(AuthContext);
7
+ if (!ctx) throw new Error("silosdk: useAuth() must be used within <Provider>");
8
+ return ctx;
9
+ }
10
+
11
+ //#endregion
12
+ export { AuthContext, useAuth };
@@ -0,0 +1,109 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ let expo_secure_store = require("expo-secure-store");
3
+ expo_secure_store = require_rolldown_runtime.__toESM(expo_secure_store);
4
+
5
+ //#region src/local/auth.ts
6
+ const SESSION_KEY = "silosdk_session_token";
7
+ let prodUrl = null;
8
+ function setAuthProdUrl(url) {
9
+ prodUrl = url;
10
+ }
11
+ function getSessionKey(url) {
12
+ if (!url) return SESSION_KEY;
13
+ return url === prodUrl ? `${SESSION_KEY}_prod` : `${SESSION_KEY}_dev`;
14
+ }
15
+ var AuthEventBus = class {
16
+ constructor() {
17
+ this.listeners = /* @__PURE__ */ new Map();
18
+ }
19
+ on(event, listener) {
20
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
21
+ this.listeners.get(event).add(listener);
22
+ }
23
+ off(event, listener) {
24
+ this.listeners.get(event)?.delete(listener);
25
+ }
26
+ emit(event) {
27
+ this.listeners.get(event)?.forEach((l) => l());
28
+ }
29
+ };
30
+ const authEvents = new AuthEventBus();
31
+ function createAuthUser(data) {
32
+ return {
33
+ id: data.id,
34
+ email: data.email,
35
+ role: data.role,
36
+ isAnonymous: data.isAnonymous
37
+ };
38
+ }
39
+ async function getStoredSessionToken(url) {
40
+ try {
41
+ return await expo_secure_store.getItemAsync(getSessionKey(url));
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ async function clearStoredSessionToken(url) {
47
+ try {
48
+ await expo_secure_store.deleteItemAsync(getSessionKey(url));
49
+ } catch {}
50
+ }
51
+ async function requestOtp(url, email) {
52
+ const res = await fetch(`${url}/auth/request`, {
53
+ method: "POST",
54
+ headers: { "Content-Type": "application/json" },
55
+ body: JSON.stringify({ email })
56
+ });
57
+ if (!res.ok) throw new Error(`OTP request failed: ${res.status}`);
58
+ }
59
+ async function verifyOtpWithServer(url, opts) {
60
+ const res = await fetch(`${url}/auth/verify`, {
61
+ method: "POST",
62
+ headers: { "Content-Type": "application/json" },
63
+ body: JSON.stringify(opts)
64
+ });
65
+ if (!res.ok) throw new Error(`OTP verification failed: ${res.status}`);
66
+ const { token, user: userData } = await res.json();
67
+ await expo_secure_store.setItemAsync(getSessionKey(url), token);
68
+ return {
69
+ token,
70
+ user: createAuthUser(userData)
71
+ };
72
+ }
73
+ async function signOutFromServer(url, token) {
74
+ if (token) await fetch(`${url}/auth/signout`, {
75
+ method: "POST",
76
+ headers: { Authorization: `Bearer ${token}` }
77
+ });
78
+ await clearStoredSessionToken(url);
79
+ }
80
+ async function fetchCurrentUser(url, token) {
81
+ const res = await fetch(`${url}/auth/me`, { headers: { Authorization: `Bearer ${token}` } });
82
+ if (res.status === 401) return {
83
+ user: null,
84
+ unauthorized: true
85
+ };
86
+ if (!res.ok) return {
87
+ user: null,
88
+ unauthorized: false
89
+ };
90
+ return {
91
+ user: createAuthUser(await res.json()),
92
+ unauthorized: false
93
+ };
94
+ }
95
+ async function handleUnauthorized(url) {
96
+ await clearStoredSessionToken(url);
97
+ authEvents.emit("unauthorized");
98
+ }
99
+
100
+ //#endregion
101
+ exports.authEvents = authEvents;
102
+ exports.clearStoredSessionToken = clearStoredSessionToken;
103
+ exports.fetchCurrentUser = fetchCurrentUser;
104
+ exports.getStoredSessionToken = getStoredSessionToken;
105
+ exports.handleUnauthorized = handleUnauthorized;
106
+ exports.requestOtp = requestOtp;
107
+ exports.setAuthProdUrl = setAuthProdUrl;
108
+ exports.signOutFromServer = signOutFromServer;
109
+ exports.verifyOtpWithServer = verifyOtpWithServer;
@@ -0,0 +1,26 @@
1
+ //#region src/local/auth.d.ts
2
+ type AuthUser = {
3
+ id: string;
4
+ email: string | null;
5
+ role: string;
6
+ isAnonymous: boolean;
7
+ };
8
+ type AuthState = {
9
+ /** Resolved user — null for public-only access or while loading. */
10
+ user: AuthUser | null;
11
+ /** True while restoring session on first mount. */
12
+ isLoading: boolean;
13
+ /** Request an OTP to be sent to the given email address. */
14
+ signIn: (opts: {
15
+ email: string;
16
+ }) => Promise<void>;
17
+ /** Verify the OTP code sent to the email and establish an authenticated session. */
18
+ verifyOtp: (opts: {
19
+ email: string;
20
+ otp: string;
21
+ }) => Promise<void>;
22
+ /** Sign out and clear session. */
23
+ signOut: () => Promise<void>;
24
+ };
25
+ //#endregion
26
+ export { AuthState, AuthUser };
@@ -0,0 +1,26 @@
1
+ //#region src/local/auth.d.ts
2
+ type AuthUser = {
3
+ id: string;
4
+ email: string | null;
5
+ role: string;
6
+ isAnonymous: boolean;
7
+ };
8
+ type AuthState = {
9
+ /** Resolved user — null for public-only access or while loading. */
10
+ user: AuthUser | null;
11
+ /** True while restoring session on first mount. */
12
+ isLoading: boolean;
13
+ /** Request an OTP to be sent to the given email address. */
14
+ signIn: (opts: {
15
+ email: string;
16
+ }) => Promise<void>;
17
+ /** Verify the OTP code sent to the email and establish an authenticated session. */
18
+ verifyOtp: (opts: {
19
+ email: string;
20
+ otp: string;
21
+ }) => Promise<void>;
22
+ /** Sign out and clear session. */
23
+ signOut: () => Promise<void>;
24
+ };
25
+ //#endregion
26
+ export { AuthState, AuthUser };
@@ -0,0 +1,99 @@
1
+ import * as SecureStore from "expo-secure-store";
2
+
3
+ //#region src/local/auth.ts
4
+ const SESSION_KEY = "silosdk_session_token";
5
+ let prodUrl = null;
6
+ function setAuthProdUrl(url) {
7
+ prodUrl = url;
8
+ }
9
+ function getSessionKey(url) {
10
+ if (!url) return SESSION_KEY;
11
+ return url === prodUrl ? `${SESSION_KEY}_prod` : `${SESSION_KEY}_dev`;
12
+ }
13
+ var AuthEventBus = class {
14
+ constructor() {
15
+ this.listeners = /* @__PURE__ */ new Map();
16
+ }
17
+ on(event, listener) {
18
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
19
+ this.listeners.get(event).add(listener);
20
+ }
21
+ off(event, listener) {
22
+ this.listeners.get(event)?.delete(listener);
23
+ }
24
+ emit(event) {
25
+ this.listeners.get(event)?.forEach((l) => l());
26
+ }
27
+ };
28
+ const authEvents = new AuthEventBus();
29
+ function createAuthUser(data) {
30
+ return {
31
+ id: data.id,
32
+ email: data.email,
33
+ role: data.role,
34
+ isAnonymous: data.isAnonymous
35
+ };
36
+ }
37
+ async function getStoredSessionToken(url) {
38
+ try {
39
+ return await SecureStore.getItemAsync(getSessionKey(url));
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ async function clearStoredSessionToken(url) {
45
+ try {
46
+ await SecureStore.deleteItemAsync(getSessionKey(url));
47
+ } catch {}
48
+ }
49
+ async function requestOtp(url, email) {
50
+ const res = await fetch(`${url}/auth/request`, {
51
+ method: "POST",
52
+ headers: { "Content-Type": "application/json" },
53
+ body: JSON.stringify({ email })
54
+ });
55
+ if (!res.ok) throw new Error(`OTP request failed: ${res.status}`);
56
+ }
57
+ async function verifyOtpWithServer(url, opts) {
58
+ const res = await fetch(`${url}/auth/verify`, {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: JSON.stringify(opts)
62
+ });
63
+ if (!res.ok) throw new Error(`OTP verification failed: ${res.status}`);
64
+ const { token, user: userData } = await res.json();
65
+ await SecureStore.setItemAsync(getSessionKey(url), token);
66
+ return {
67
+ token,
68
+ user: createAuthUser(userData)
69
+ };
70
+ }
71
+ async function signOutFromServer(url, token) {
72
+ if (token) await fetch(`${url}/auth/signout`, {
73
+ method: "POST",
74
+ headers: { Authorization: `Bearer ${token}` }
75
+ });
76
+ await clearStoredSessionToken(url);
77
+ }
78
+ async function fetchCurrentUser(url, token) {
79
+ const res = await fetch(`${url}/auth/me`, { headers: { Authorization: `Bearer ${token}` } });
80
+ if (res.status === 401) return {
81
+ user: null,
82
+ unauthorized: true
83
+ };
84
+ if (!res.ok) return {
85
+ user: null,
86
+ unauthorized: false
87
+ };
88
+ return {
89
+ user: createAuthUser(await res.json()),
90
+ unauthorized: false
91
+ };
92
+ }
93
+ async function handleUnauthorized(url) {
94
+ await clearStoredSessionToken(url);
95
+ authEvents.emit("unauthorized");
96
+ }
97
+
98
+ //#endregion
99
+ export { authEvents, clearStoredSessionToken, fetchCurrentUser, getStoredSessionToken, handleUnauthorized, requestOtp, setAuthProdUrl, signOutFromServer, verifyOtpWithServer };