@weirdscience/based-client 0.1.0

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.
@@ -0,0 +1,3 @@
1
+ import type { BasedClientOptions, BasedClient } from "./types";
2
+ export declare function createClient(options: BasedClientOptions): BasedClient;
3
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAuB,WAAW,EAAE,MAAM,SAAS,CAAC;AAEpF,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAsJrE"}
@@ -0,0 +1,5 @@
1
+ import type { MutationResult, DefaultTables } from "../types";
2
+ type MutationOperation = "create" | "update" | "delete";
3
+ export declare function useMutation<T extends Record<string, Record<string, unknown>> = DefaultTables, K extends string & keyof T = string & keyof T>(table: K, operation: MutationOperation): MutationResult<T[K]>;
4
+ export {};
5
+ //# sourceMappingURL=use-mutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-mutation.d.ts","sourceRoot":"","sources":["../../src/hooks/use-mutation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9D,KAAK,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExD,wBAAgB,WAAW,CACzB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,aAAa,EACjE,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,EAE7C,KAAK,EAAE,CAAC,EACR,SAAS,EAAE,iBAAiB,GAC3B,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CA0DtB"}
@@ -0,0 +1,3 @@
1
+ import type { QueryOptions, QueryResult, DefaultTables } from "../types";
2
+ export declare function useQuery<T extends Record<string, Record<string, unknown>> = DefaultTables, K extends string & keyof T = string & keyof T>(table: K, options?: QueryOptions<T[K]>): QueryResult<T[K]>;
3
+ //# sourceMappingURL=use-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-query.d.ts","sourceRoot":"","sources":["../../src/hooks/use-query.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzE,wBAAgB,QAAQ,CACtB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,aAAa,EACjE,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,EAE7C,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC3B,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAiDnB"}
@@ -0,0 +1,7 @@
1
+ import type { AuthUser } from "../types";
2
+ export declare function useUser(): {
3
+ user: AuthUser | null;
4
+ isLoading: boolean;
5
+ error: Error | null;
6
+ };
7
+ //# sourceMappingURL=use-user.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-user.d.ts","sourceRoot":"","sources":["../../src/hooks/use-user.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC,wBAAgB,OAAO,IAAI;IACzB,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB,CAcA"}
@@ -0,0 +1,7 @@
1
+ export { createClient } from "./client";
2
+ export { BasedProvider, useBasedClient } from "./provider";
3
+ export { useUser } from "./hooks/use-user";
4
+ export { useQuery } from "./hooks/use-query";
5
+ export { useMutation } from "./hooks/use-mutation";
6
+ export type { BasedClient, BasedClientOptions, AuthUser, AuthState, QueryOptions, QueryResult, MutationResult, DefaultTables, } from "./types";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,cAAc,EACd,aAAa,GACd,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,264 @@
1
+ // src/client.ts
2
+ function createClient(options) {
3
+ const { url, anonKey } = options;
4
+ const baseUrl = url.replace(/\/$/, "");
5
+ let state = {
6
+ user: null,
7
+ accessToken: null,
8
+ refreshToken: null
9
+ };
10
+ const listeners = new Set;
11
+ function notify() {
12
+ for (const listener of listeners)
13
+ listener();
14
+ }
15
+ function subscribe(listener) {
16
+ listeners.add(listener);
17
+ return () => listeners.delete(listener);
18
+ }
19
+ function getState() {
20
+ return state;
21
+ }
22
+ async function fetchWithAuth(path, init = {}) {
23
+ const headers = new Headers(init.headers);
24
+ if (state.accessToken) {
25
+ headers.set("Authorization", `Bearer ${state.accessToken}`);
26
+ } else {
27
+ headers.set("apikey", anonKey);
28
+ }
29
+ if (!headers.has("Content-Type") && init.body) {
30
+ headers.set("Content-Type", "application/json");
31
+ }
32
+ let res = await fetch(`${baseUrl}${path}`, { ...init, headers });
33
+ if (res.status === 401 && state.refreshToken) {
34
+ const refreshed = await refreshSession();
35
+ if (refreshed) {
36
+ headers.set("Authorization", `Bearer ${state.accessToken}`);
37
+ res = await fetch(`${baseUrl}${path}`, { ...init, headers });
38
+ }
39
+ }
40
+ return res;
41
+ }
42
+ async function signUp(email, password) {
43
+ const res = await fetch(`${baseUrl}/auth/signup`, {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/json" },
46
+ body: JSON.stringify({ email, password })
47
+ });
48
+ if (!res.ok) {
49
+ const json = await res.json();
50
+ throw new Error(json.error?.message || "Sign up failed");
51
+ }
52
+ const { data } = await res.json();
53
+ state = {
54
+ user: data.user,
55
+ accessToken: data.accessToken,
56
+ refreshToken: data.refreshToken
57
+ };
58
+ notify();
59
+ return data.user;
60
+ }
61
+ async function signIn(email, password) {
62
+ const res = await fetch(`${baseUrl}/auth/signin`, {
63
+ method: "POST",
64
+ headers: { "Content-Type": "application/json" },
65
+ body: JSON.stringify({ email, password })
66
+ });
67
+ if (!res.ok) {
68
+ const json = await res.json();
69
+ throw new Error(json.error?.message || "Sign in failed");
70
+ }
71
+ const { data } = await res.json();
72
+ state = {
73
+ user: data.user,
74
+ accessToken: data.accessToken,
75
+ refreshToken: data.refreshToken
76
+ };
77
+ notify();
78
+ return data.user;
79
+ }
80
+ async function signOut() {
81
+ if (state.accessToken) {
82
+ try {
83
+ await fetch(`${baseUrl}/auth/signout`, {
84
+ method: "POST",
85
+ headers: { Authorization: `Bearer ${state.accessToken}` }
86
+ });
87
+ } catch {}
88
+ }
89
+ state = { user: null, accessToken: null, refreshToken: null };
90
+ notify();
91
+ }
92
+ async function refreshSession() {
93
+ if (!state.refreshToken)
94
+ return false;
95
+ try {
96
+ const res = await fetch(`${baseUrl}/auth/refresh`, {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify({ refreshToken: state.refreshToken })
100
+ });
101
+ if (!res.ok) {
102
+ state = { user: null, accessToken: null, refreshToken: null };
103
+ notify();
104
+ return false;
105
+ }
106
+ const { data } = await res.json();
107
+ state = {
108
+ ...state,
109
+ accessToken: data.accessToken,
110
+ refreshToken: data.refreshToken
111
+ };
112
+ notify();
113
+ return true;
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+ return {
119
+ auth: { signUp, signIn, signOut, refreshSession },
120
+ fetch: fetchWithAuth,
121
+ subscribe,
122
+ getState
123
+ };
124
+ }
125
+ // src/provider.tsx
126
+ import { createContext, useContext } from "react";
127
+ import { jsxDEV } from "react/jsx-dev-runtime";
128
+ var BasedContext = createContext(null);
129
+ function BasedProvider({
130
+ client,
131
+ children
132
+ }) {
133
+ return /* @__PURE__ */ jsxDEV(BasedContext.Provider, {
134
+ value: client,
135
+ children
136
+ }, undefined, false, undefined, this);
137
+ }
138
+ function useBasedClient() {
139
+ const client = useContext(BasedContext);
140
+ if (!client) {
141
+ throw new Error("useBasedClient must be used within a BasedProvider");
142
+ }
143
+ return client;
144
+ }
145
+ // src/hooks/use-user.ts
146
+ import { useSyncExternalStore } from "react";
147
+ function useUser() {
148
+ const client = useBasedClient();
149
+ const state = useSyncExternalStore(client.subscribe, () => client.getState(), () => client.getState());
150
+ return {
151
+ user: state.user,
152
+ isLoading: false,
153
+ error: null
154
+ };
155
+ }
156
+ // src/hooks/use-query.ts
157
+ import { useState as useState2, useEffect as useEffect2, useCallback } from "react";
158
+ function useQuery(table, options) {
159
+ const client = useBasedClient();
160
+ const [data, setData] = useState2(null);
161
+ const [total, setTotal] = useState2(0);
162
+ const [isLoading, setIsLoading] = useState2(true);
163
+ const [error, setError] = useState2(null);
164
+ const fetchData = useCallback(async () => {
165
+ setIsLoading(true);
166
+ setError(null);
167
+ const params = new URLSearchParams;
168
+ if (options?.limit)
169
+ params.set("limit", String(options.limit));
170
+ if (options?.offset)
171
+ params.set("offset", String(options.offset));
172
+ if (options?.filter) {
173
+ for (const [key, value] of Object.entries(options.filter)) {
174
+ if (value !== undefined)
175
+ params.set(key, String(value));
176
+ }
177
+ }
178
+ const query = params.toString();
179
+ const path = `/api/${table}${query ? `?${query}` : ""}`;
180
+ try {
181
+ const res = await client.fetch(path);
182
+ const json = await res.json();
183
+ if (!res.ok) {
184
+ setError(json.error || { code: "UNKNOWN", message: "Request failed" });
185
+ setData(null);
186
+ setTotal(0);
187
+ } else {
188
+ setData(json.data);
189
+ setTotal(json.total ?? 0);
190
+ }
191
+ } catch (err) {
192
+ setError({ code: "NETWORK_ERROR", message: err.message });
193
+ setData(null);
194
+ setTotal(0);
195
+ } finally {
196
+ setIsLoading(false);
197
+ }
198
+ }, [client, table, JSON.stringify(options)]);
199
+ useEffect2(() => {
200
+ fetchData();
201
+ }, [fetchData]);
202
+ return { data, total, isLoading, error, refetch: fetchData };
203
+ }
204
+ // src/hooks/use-mutation.ts
205
+ import { useState as useState3, useCallback as useCallback2 } from "react";
206
+ function useMutation(table, operation) {
207
+ const client = useBasedClient();
208
+ const [isLoading, setIsLoading] = useState3(false);
209
+ const [error, setError] = useState3(null);
210
+ const mutate = useCallback2(async (data) => {
211
+ setIsLoading(true);
212
+ setError(null);
213
+ try {
214
+ let path;
215
+ let method;
216
+ let body;
217
+ switch (operation) {
218
+ case "create":
219
+ path = `/api/${table}`;
220
+ method = "POST";
221
+ body = JSON.stringify(data);
222
+ break;
223
+ case "update":
224
+ if (!data.id)
225
+ throw new Error("Update requires an 'id' field");
226
+ path = `/api/${table}/${data.id}`;
227
+ method = "PUT";
228
+ const { id, ...rest } = data;
229
+ body = JSON.stringify(rest);
230
+ break;
231
+ case "delete":
232
+ if (!data.id)
233
+ throw new Error("Delete requires an 'id' field");
234
+ path = `/api/${table}/${data.id}`;
235
+ method = "DELETE";
236
+ break;
237
+ }
238
+ const res = await client.fetch(path, { method, body });
239
+ const json = await res.json();
240
+ if (!res.ok) {
241
+ const err = json.error || { code: "UNKNOWN", message: "Mutation failed" };
242
+ setError(err);
243
+ throw new Error(err.message);
244
+ }
245
+ return json.data;
246
+ } catch (err) {
247
+ if (!error) {
248
+ setError({ code: "MUTATION_ERROR", message: err.message });
249
+ }
250
+ throw err;
251
+ } finally {
252
+ setIsLoading(false);
253
+ }
254
+ }, [client, table, operation]);
255
+ return { mutate, isLoading, error };
256
+ }
257
+ export {
258
+ useUser,
259
+ useQuery,
260
+ useMutation,
261
+ useBasedClient,
262
+ createClient,
263
+ BasedProvider
264
+ };
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from "react";
2
+ import type { BasedClient } from "./types";
3
+ export declare function BasedProvider({ client, children, }: {
4
+ client: BasedClient;
5
+ children: ReactNode;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ export declare function useBasedClient(): BasedClient;
8
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,2CAIA;AAED,wBAAgB,cAAc,IAAI,WAAW,CAM5C"}
@@ -0,0 +1,51 @@
1
+ export interface BasedClientOptions {
2
+ url: string;
3
+ anonKey: string;
4
+ }
5
+ export interface AuthUser {
6
+ id: string;
7
+ email: string;
8
+ }
9
+ export interface AuthState {
10
+ user: AuthUser | null;
11
+ accessToken: string | null;
12
+ refreshToken: string | null;
13
+ }
14
+ export interface BasedClient {
15
+ auth: {
16
+ signUp(email: string, password: string): Promise<AuthUser>;
17
+ signIn(email: string, password: string): Promise<AuthUser>;
18
+ signOut(): Promise<void>;
19
+ refreshSession(): Promise<boolean>;
20
+ };
21
+ fetch(path: string, init?: RequestInit): Promise<Response>;
22
+ subscribe(listener: () => void): () => void;
23
+ getState(): AuthState;
24
+ }
25
+ export interface QueryOptions<T = Record<string, unknown>> {
26
+ filter?: Partial<Record<keyof T, string | number | boolean>>;
27
+ limit?: number;
28
+ offset?: number;
29
+ }
30
+ export interface QueryResult<T = unknown> {
31
+ data: T[] | null;
32
+ total: number;
33
+ isLoading: boolean;
34
+ error: {
35
+ code: string;
36
+ message: string;
37
+ } | null;
38
+ refetch: () => void;
39
+ }
40
+ export interface MutationResult<T = unknown> {
41
+ mutate: (data: Partial<T> & Record<string, unknown>) => Promise<T>;
42
+ isLoading: boolean;
43
+ error: {
44
+ code: string;
45
+ message: string;
46
+ } | null;
47
+ }
48
+ export interface DefaultTables {
49
+ [table: string]: Record<string, unknown>;
50
+ }
51
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE;QACJ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACzB,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;KACpC,CAAC;IACF,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3D,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,QAAQ,IAAI,SAAS,CAAC;CACvB;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvD,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IACzC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACnE,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACjD;AAID,MAAM,WAAW,aAAa;IAC5B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@weirdscience/based-client",
3
+ "version": "0.1.0",
4
+ "description": "React SDK for Based — a minimal self-hosted BaaS",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/WeirdScience-dev/based.git",
9
+ "directory": "packages/client"
10
+ },
11
+ "homepage": "https://based.weirdscience.dev",
12
+ "type": "module",
13
+ "main": "./dist/index.js",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "rm -rf dist && bun build src/index.ts --outdir dist --target browser --format esm --external react && bun x tsc --emitDeclarationOnly --declaration --outDir dist",
28
+ "prepublishOnly": "bun run build",
29
+ "test": "bun test"
30
+ },
31
+ "peerDependencies": {
32
+ "react": "^18.0.0 || ^19.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^19.0.0",
36
+ "typescript": "^5.0.0"
37
+ }
38
+ }