suparisma 1.2.1 → 1.2.3

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/README.md CHANGED
@@ -16,6 +16,7 @@ A powerful, typesafe React hook generator for Supabase, driven by your Prisma sc
16
16
  - [Why Suparisma?](#why-suparisma)
17
17
  - [Features](#features)
18
18
  - [Installation](#installation)
19
+ - [React Native / Expo Setup](#react-native--expo-setup)
19
20
  - [Quick Start](#quick-start)
20
21
  - [Detailed Usage](#detailed-usage)
21
22
  - [Basic CRUD Operations](#basic-crud-operations)
@@ -25,6 +26,8 @@ A powerful, typesafe React hook generator for Supabase, driven by your Prisma sc
25
26
  - [Array Filtering](#array-filtering)
26
27
  - [Sorting Data](#sorting-data)
27
28
  - [Pagination](#pagination)
29
+ - [Field Selection (select)](#field-selection-select)
30
+ - [Including Relations (include)](#including-relations-include)
28
31
  - [Search Functionality](#search-functionality)
29
32
  - [Enabling Search](#enabling-search)
30
33
  - [Search Methods](#search-methods)
@@ -60,7 +63,7 @@ Suparisma bridges this gap by:
60
63
  - Enabling easy **pagination, filtering, and search** on your data
61
64
  - Leveraging both **Prisma** and **Supabase** official SDKs
62
65
  - Respecting **Supabase's auth rules** for secure database access
63
- - Working seamlessly with any React environment (Next.js, Remix, Tanstack Router, etc.)
66
+ - Working seamlessly with any React environment (Next.js, Remix, Tanstack Router, React Native, etc.)
64
67
 
65
68
  ## Features
66
69
 
@@ -70,7 +73,7 @@ Suparisma bridges this gap by:
70
73
  - 🔍 **Full-text search** with configurable annotations *(currently under maintenance)*
71
74
  - 🔢 **Pagination and sorting** built into every hook
72
75
  - 🧩 **Prisma-like API** that feels familiar if you already use Prisma
73
- - 📱 **Works with any React framework** including Next.js, Remix, etc.
76
+ - 📱 **Works with any React framework** including Next.js, Remix, React Native, and Expo
74
77
  - 🛠️ **Simple CLI** to generate hooks with a single command
75
78
 
76
79
  ## Installation
@@ -86,6 +89,110 @@ yarn add suparisma
86
89
  pnpm install suparisma
87
90
  ```
88
91
 
92
+ ## React Native / Expo Setup
93
+
94
+ Suparisma fully supports React Native and Expo projects. Follow these additional steps for mobile development:
95
+
96
+ ### 1. Install Dependencies
97
+
98
+ ```bash
99
+ # Install Suparisma and required dependencies
100
+ pnpm install suparisma @supabase/supabase-js @react-native-async-storage/async-storage react-native-url-polyfill
101
+
102
+ # For UUID generation support (recommended)
103
+ pnpm install react-native-get-random-values
104
+ ```
105
+
106
+ ### 2. Add Polyfills
107
+
108
+ Add these imports at the very top of your app's entry point (e.g., `App.tsx` or `index.js`):
109
+
110
+ ```tsx
111
+ // App.tsx or index.js - Add these at the VERY TOP before any other imports
112
+ import 'react-native-get-random-values'; // Required for UUID generation
113
+ import 'react-native-url-polyfill/auto'; // Required for Supabase
114
+ ```
115
+
116
+ ### 3. Set Environment Variables
117
+
118
+ For **Expo** projects, use the `EXPO_PUBLIC_` prefix in your `.env` file:
119
+
120
+ ```bash
121
+ EXPO_PUBLIC_SUPABASE_URL="https://your-project.supabase.co"
122
+ EXPO_PUBLIC_SUPABASE_ANON_KEY="your-anon-key"
123
+ ```
124
+
125
+ For **bare React Native** projects, use `react-native-dotenv` or similar.
126
+
127
+ ### 4. Generate Hooks for React Native
128
+
129
+ Set the `SUPARISMA_PLATFORM` environment variable when generating:
130
+
131
+ ```bash
132
+ # Generate hooks for React Native / Expo
133
+ SUPARISMA_PLATFORM=react-native npx suparisma generate
134
+ ```
135
+
136
+ Or add it to your `package.json` scripts:
137
+
138
+ ```json
139
+ {
140
+ "scripts": {
141
+ "suparisma:generate": "SUPARISMA_PLATFORM=react-native npx suparisma generate"
142
+ }
143
+ }
144
+ ```
145
+
146
+ ### 5. Use the Hooks
147
+
148
+ The hooks work exactly the same as in web projects:
149
+
150
+ ```tsx
151
+ import React from 'react';
152
+ import { View, Text, FlatList, TouchableOpacity } from 'react-native';
153
+ import useSuparisma from './src/suparisma/generated';
154
+
155
+ function ThingList() {
156
+ const {
157
+ data: things,
158
+ loading,
159
+ error,
160
+ create: createThing
161
+ } = useSuparisma.thing();
162
+
163
+ if (loading) return <Text>Loading...</Text>;
164
+ if (error) return <Text>Error: {error.message}</Text>;
165
+
166
+ return (
167
+ <View>
168
+ <FlatList
169
+ data={things}
170
+ keyExtractor={(item) => item.id}
171
+ renderItem={({ item }) => (
172
+ <Text>{item.name} (Number: {item.someNumber})</Text>
173
+ )}
174
+ />
175
+
176
+ <TouchableOpacity
177
+ onPress={() => createThing({
178
+ name: "New Thing",
179
+ someNumber: Math.floor(Math.random() * 100)
180
+ })}
181
+ >
182
+ <Text>Add Thing</Text>
183
+ </TouchableOpacity>
184
+ </View>
185
+ );
186
+ }
187
+ ```
188
+
189
+ ### Platform Detection
190
+
191
+ The generated Supabase client automatically configures itself for React Native with:
192
+ - **AsyncStorage** for auth persistence
193
+ - **Session detection** disabled (not applicable in mobile)
194
+ - **Auto refresh token** enabled
195
+
89
196
  ## Quick Start
90
197
 
91
198
  1. **Add a Prisma schema**: Ensure you have a valid `prisma/schema.prisma` file in your project
@@ -139,6 +246,7 @@ Note: you can adjust the prisma schema path and the generated files output path
139
246
  ```bash
140
247
  SUPARISMA_PRISMA_SCHEMA_PATH="./prisma/schema.prisma"
141
248
  SUPARISMA_OUTPUT_DIR="./src/suparisma/generated"
249
+ SUPARISMA_PLATFORM="web" # or "react-native" for React Native/Expo projects
142
250
  ```
143
251
  Also make sure to not change any of these generated files directly as **they will always be overwritten**
144
252
 
@@ -627,6 +735,53 @@ const { data: page2 } = useSuparisma.thing({
627
735
  const { data, count } = useSuparisma.thing();
628
736
  ```
629
737
 
738
+ ### Field Selection (select)
739
+
740
+ Use the `select` option to only return specific fields, reducing payload size:
741
+
742
+ ```tsx
743
+ // Only get id and name fields
744
+ const { data: things } = useSuparisma.thing({
745
+ select: { id: true, name: true }
746
+ });
747
+ // Returns: [{ id: "123", name: "Thing 1" }, ...]
748
+
749
+ // Combine with filtering
750
+ const { data: activeThings } = useSuparisma.thing({
751
+ where: { someEnum: "ONE" },
752
+ select: { id: true, name: true, someNumber: true }
753
+ });
754
+ ```
755
+
756
+ ### Including Relations (include)
757
+
758
+ Use the `include` option to fetch related records (foreign key relations):
759
+
760
+ ```tsx
761
+ // Include all fields from a related model
762
+ const { data: posts } = useSuparisma.post({
763
+ include: { author: true }
764
+ });
765
+ // Returns: [{ id: "123", title: "...", author: { id: "456", name: "John" } }, ...]
766
+
767
+ // Include specific fields from a relation
768
+ const { data: posts } = useSuparisma.post({
769
+ include: {
770
+ author: {
771
+ select: { id: true, name: true }
772
+ }
773
+ }
774
+ });
775
+
776
+ // Combine select and include
777
+ const { data: posts } = useSuparisma.post({
778
+ select: { id: true, title: true },
779
+ include: { author: true, comments: true }
780
+ });
781
+ ```
782
+
783
+ **Note:** The relation names in `include` should match your Prisma schema relation field names.
784
+
630
785
  ### Search Functionality
631
786
 
632
787
  Suparisma provides powerful PostgreSQL full-text search capabilities with automatic RPC function generation and type-safe search methods. Search is enabled per field using annotations in your Prisma schema.
@@ -1291,10 +1446,13 @@ export default function ThingTable() {
1291
1446
  |----------|----------|-------------|---------|
1292
1447
  | `DATABASE_URL` | Yes | Postgres database URL used by Prisma | `postgresql://user:pass@host:port/db` |
1293
1448
  | `DIRECT_URL` | Yes | Direct URL to Postgres DB for realtime setup | `postgresql://user:pass@host:port/db` |
1294
- | `NEXT_PUBLIC_SUPABASE_URL` | Yes | Your Supabase project URL | `https://xyz.supabase.co` |
1295
- | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Yes | Supabase anonymous key | `eyJh...` |
1449
+ | `NEXT_PUBLIC_SUPABASE_URL` | Yes (Web) | Your Supabase project URL (Next.js) | `https://xyz.supabase.co` |
1450
+ | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Yes (Web) | Supabase anonymous key (Next.js) | `eyJh...` |
1451
+ | `EXPO_PUBLIC_SUPABASE_URL` | Yes (RN) | Your Supabase project URL (Expo) | `https://xyz.supabase.co` |
1452
+ | `EXPO_PUBLIC_SUPABASE_ANON_KEY` | Yes (RN) | Supabase anonymous key (Expo) | `eyJh...` |
1296
1453
  | `SUPARISMA_OUTPUT_DIR` | No | Custom output directory | `src/lib/suparisma` |
1297
1454
  | `SUPARISMA_PRISMA_SCHEMA_PATH` | No | Custom schema path | `db/schema.prisma` |
1455
+ | `SUPARISMA_PLATFORM` | No | Target platform: `web` or `react-native` | `react-native` |
1298
1456
 
1299
1457
  ### CLI Commands
1300
1458
 
@@ -1416,8 +1574,8 @@ const { data } = useSuparisma.thing({
1416
1574
  | `limit` | `number` | Maximum number of records to return |
1417
1575
  | `offset` | `number` | Number of records to skip for pagination |
1418
1576
  | `realtime` | `boolean` | Enable/disable real-time updates |
1419
- | `select` | `object` | Fields to include in the response |
1420
- | `include` | `object` | Related records to include |
1577
+ | `select` | `object` | Fields to include in the response. Use `{ fieldName: true }` syntax |
1578
+ | `include` | `object` | Related records to include. Use `{ relationName: true }` or `{ relationName: { select: {...} } }` |
1421
1579
  | `search` | `object` | Full-text search configuration |
1422
1580
 
1423
1581
  ### Hook Return Value
@@ -1464,6 +1622,12 @@ GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO anon;
1464
1622
  ALTER DEFAULT PRIVILEGES IN SCHEMA public
1465
1623
  GRANT USAGE, SELECT ON SEQUENCES TO anon;
1466
1624
  ```
1625
+
1626
+ You could also try debugging on a table, the following is NOT recommended but you can debug permissions given to anon, service_account and give all access to anon key to make sure that's not the issue:
1627
+ ```sql
1628
+ GRANT ALL ON TABLE "(tableName)" TO anon;
1629
+ GRANT ALL ON TABLE "(tableName)" TO authenticated;
1630
+ ```
1467
1631
 
1468
1632
  **"Unknown command: undefined"**
1469
1633
 
package/dist/config.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.HOOK_NAME_PREFIX = exports.UTILS_DIR = exports.HOOKS_DIR = exports.TYPES_DIR = exports.OUTPUT_DIR = exports.PRISMA_SCHEMA_PATH = void 0;
6
+ exports.PLATFORM = exports.HOOK_NAME_PREFIX = exports.UTILS_DIR = exports.HOOKS_DIR = exports.TYPES_DIR = exports.OUTPUT_DIR = exports.PRISMA_SCHEMA_PATH = void 0;
7
7
  // Configuration
8
8
  const path_1 = __importDefault(require("path"));
9
9
  // Use current working directory for all paths
@@ -14,3 +14,4 @@ exports.TYPES_DIR = `${exports.OUTPUT_DIR}/types`;
14
14
  exports.HOOKS_DIR = `${exports.OUTPUT_DIR}/hooks`;
15
15
  exports.UTILS_DIR = `${exports.OUTPUT_DIR}/utils`;
16
16
  exports.HOOK_NAME_PREFIX = 'useSuparisma';
17
+ exports.PLATFORM = process.env.SUPARISMA_PLATFORM || 'web';
@@ -47,6 +47,42 @@ export function escapeRegexCharacters(str: string): string {
47
47
  return str.replace(/[()\\[\\]{}+*?^$|.\\\\]/g, '\\\\\\\\$&');
48
48
  }
49
49
 
50
+ /**
51
+ * Generate a UUID v4, with fallback for environments without crypto.randomUUID()
52
+ * Works in: browsers, Node.js, and React Native (with react-native-get-random-values polyfill)
53
+ *
54
+ * For React Native, ensure you have installed and imported the polyfill:
55
+ * - pnpm install react-native-get-random-values
56
+ * - Import at app entry point: import 'react-native-get-random-values';
57
+ */
58
+ export function generateUUID(): string {
59
+ // Try native crypto.randomUUID() first (modern browsers & Node.js 16.7+)
60
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
61
+ return crypto.randomUUID();
62
+ }
63
+
64
+ // Fallback using crypto.getRandomValues() (works with react-native-get-random-values polyfill)
65
+ if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
66
+ const bytes = new Uint8Array(16);
67
+ crypto.getRandomValues(bytes);
68
+
69
+ // Set version (4) and variant (RFC 4122)
70
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
71
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant RFC 4122
72
+
73
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
74
+ return \`\${hex.slice(0, 8)}-\${hex.slice(8, 12)}-\${hex.slice(12, 16)}-\${hex.slice(16, 20)}-\${hex.slice(20)}\`;
75
+ }
76
+
77
+ // Last resort fallback using Math.random() (not cryptographically secure)
78
+ console.warn('[Suparisma] crypto API not available, using Math.random() fallback for UUID generation');
79
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
80
+ const r = (Math.random() * 16) | 0;
81
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
82
+ return v.toString(16);
83
+ });
84
+ }
85
+
50
86
  /**
51
87
  * Advanced filter operators for complex queries
52
88
  * @example
@@ -133,7 +169,36 @@ export type AdvancedWhereInput<T> = {
133
169
  * limit: 10
134
170
  * });
135
171
  */
136
- export type SuparismaOptions<TWhereInput, TOrderByInput> = {
172
+ /**
173
+ * Select input type - specify which fields to return
174
+ * Use true to include a field, or use an object for relations
175
+ * @example
176
+ * // Select specific fields
177
+ * { id: true, name: true, email: true }
178
+ *
179
+ * @example
180
+ * // Select fields with relations
181
+ * { id: true, name: true, posts: true }
182
+ */
183
+ export type SelectInput<T> = {
184
+ [K in keyof T]?: boolean;
185
+ };
186
+
187
+ /**
188
+ * Include input type - specify which relations to include
189
+ * @example
190
+ * // Include a relation with all fields
191
+ * { posts: true }
192
+ *
193
+ * @example
194
+ * // Include a relation with specific fields
195
+ * { posts: { select: { id: true, title: true } } }
196
+ */
197
+ export type IncludeInput = {
198
+ [key: string]: boolean | { select?: Record<string, boolean> };
199
+ };
200
+
201
+ export type SuparismaOptions<TWhereInput, TOrderByInput, TSelectInput = Record<string, boolean>> = {
137
202
  /** Whether to enable realtime updates (default: true) */
138
203
  realtime?: boolean;
139
204
  /** Custom channel name for realtime subscription */
@@ -148,6 +213,16 @@ export type SuparismaOptions<TWhereInput, TOrderByInput> = {
148
213
  limit?: number;
149
214
  /** Offset for pagination (skip records) */
150
215
  offset?: number;
216
+ /**
217
+ * Select specific fields to return. Reduces payload size.
218
+ * @example { id: true, name: true, email: true }
219
+ */
220
+ select?: TSelectInput;
221
+ /**
222
+ * Include related records (foreign key relations).
223
+ * @example { posts: true } or { posts: { select: { id: true, title: true } } }
224
+ */
225
+ include?: IncludeInput;
151
226
  };
152
227
 
153
228
  /**
@@ -697,6 +772,82 @@ function matchesFilter<T>(record: any, filter: T): boolean {
697
772
  return conditions.every(condition => condition);
698
773
  }
699
774
 
775
+ /**
776
+ * Build a Supabase select string from select and include options.
777
+ *
778
+ * @param select - Object specifying which fields to select { field: true }
779
+ * @param include - Object specifying which relations to include { relation: true }
780
+ * @returns A Supabase-compatible select string
781
+ *
782
+ * @example
783
+ * // Select specific fields
784
+ * buildSelectString({ id: true, name: true }) // Returns "id,name"
785
+ *
786
+ * @example
787
+ * // Include relations
788
+ * buildSelectString(undefined, { posts: true }) // Returns "*,posts(*)"
789
+ *
790
+ * @example
791
+ * // Select fields and include relations with specific fields
792
+ * buildSelectString({ id: true, name: true }, { posts: { select: { id: true, title: true } } })
793
+ * // Returns "id,name,posts(id,title)"
794
+ */
795
+ export function buildSelectString<TSelect, TInclude>(
796
+ select?: TSelect,
797
+ include?: TInclude
798
+ ): string {
799
+ const parts: string[] = [];
800
+
801
+ // Handle select - if provided, only return specified fields
802
+ if (select && typeof select === 'object') {
803
+ const selectedFields = Object.entries(select)
804
+ .filter(([_, value]) => value === true)
805
+ .map(([key]) => key);
806
+
807
+ if (selectedFields.length > 0) {
808
+ parts.push(...selectedFields);
809
+ }
810
+ }
811
+
812
+ // Handle include - add related records
813
+ if (include && typeof include === 'object') {
814
+ for (const [relationName, relationValue] of Object.entries(include)) {
815
+ if (relationValue === true) {
816
+ // Include all fields from the relation
817
+ parts.push(\`\${relationName}(*)\`);
818
+ } else if (typeof relationValue === 'object' && relationValue !== null) {
819
+ // Include specific fields from the relation
820
+ const relationOptions = relationValue as { select?: Record<string, boolean> };
821
+ if (relationOptions.select) {
822
+ const relationFields = Object.entries(relationOptions.select)
823
+ .filter(([_, value]) => value === true)
824
+ .map(([key]) => key);
825
+
826
+ if (relationFields.length > 0) {
827
+ parts.push(\`\${relationName}(\${relationFields.join(',')})\`);
828
+ } else {
829
+ parts.push(\`\${relationName}(*)\`);
830
+ }
831
+ } else {
832
+ parts.push(\`\${relationName}(*)\`);
833
+ }
834
+ }
835
+ }
836
+ }
837
+
838
+ // If no select specified but include is, we need to include base table fields too
839
+ if (parts.length === 0) {
840
+ return '*';
841
+ }
842
+
843
+ // If only include was specified (no select), we need all base fields plus relations
844
+ if (!select && include) {
845
+ return '*,' + parts.join(',');
846
+ }
847
+
848
+ return parts.join(',');
849
+ }
850
+
700
851
  /**
701
852
  * Apply order by to the query builder
702
853
  */
@@ -792,13 +943,19 @@ export function createSuparismaHook<
792
943
  orderBy,
793
944
  limit,
794
945
  offset,
946
+ select,
947
+ include,
795
948
  } = options;
796
949
 
950
+ // Build the select string once for reuse
951
+ const selectString = buildSelectString(select, include);
952
+
797
953
  // Refs to store the latest options for realtime handlers
798
954
  const whereRef = useRef(where);
799
955
  const orderByRef = useRef(orderBy);
800
956
  const limitRef = useRef(limit);
801
957
  const offsetRef = useRef(offset);
958
+ const selectStringRef = useRef(selectString);
802
959
 
803
960
  // Update refs whenever options change
804
961
  useEffect(() => {
@@ -817,6 +974,10 @@ export function createSuparismaHook<
817
974
  offsetRef.current = offset;
818
975
  }, [offset]);
819
976
 
977
+ useEffect(() => {
978
+ selectStringRef.current = selectString;
979
+ }, [selectString]);
980
+
820
981
  // Single data collection for holding results
821
982
  const [data, setData] = useState<TWithRelations[]>([]);
822
983
  const [error, setError] = useState<Error | null>(null);
@@ -1162,7 +1323,8 @@ export function createSuparismaHook<
1162
1323
  setLoading(true);
1163
1324
  setError(null);
1164
1325
 
1165
- let query = supabase.from(tableName).select('*');
1326
+ // Use selectString for field selection (includes relations if specified)
1327
+ let query = supabase.from(tableName).select(selectString);
1166
1328
 
1167
1329
  // Apply where conditions if provided
1168
1330
  if (params?.where) {
@@ -1249,7 +1411,7 @@ export function createSuparismaHook<
1249
1411
 
1250
1412
  const { data, error } = await supabase
1251
1413
  .from(tableName)
1252
- .select('*')
1414
+ .select(selectString)
1253
1415
  .eq(primaryKey, value)
1254
1416
  .maybeSingle();
1255
1417
 
@@ -1572,7 +1734,7 @@ export function createSuparismaHook<
1572
1734
  }, [realtime, channelName, tableName]); // NEVER include 'where' - subscription should persist
1573
1735
 
1574
1736
  // Create a memoized options object to prevent unnecessary re-renders
1575
- const optionsRef = useRef({ where, orderBy, limit, offset });
1737
+ const optionsRef = useRef({ where, orderBy, limit, offset, selectString });
1576
1738
 
1577
1739
  // Compare current options with previous options
1578
1740
  const optionsChanged = useCallback(() => {
@@ -1587,16 +1749,17 @@ export function createSuparismaHook<
1587
1749
  whereStr !== prevWhereStr ||
1588
1750
  orderByStr !== prevOrderByStr ||
1589
1751
  limit !== optionsRef.current.limit ||
1590
- offset !== optionsRef.current.offset;
1752
+ offset !== optionsRef.current.offset ||
1753
+ selectString !== optionsRef.current.selectString;
1591
1754
 
1592
1755
  if (hasChanged) {
1593
1756
  // Update the ref with the new values
1594
- optionsRef.current = { where, orderBy, limit, offset };
1757
+ optionsRef.current = { where, orderBy, limit, offset, selectString };
1595
1758
  return true;
1596
1759
  }
1597
1760
 
1598
1761
  return false;
1599
- }, [where, orderBy, limit, offset]);
1762
+ }, [where, orderBy, limit, offset, selectString]);
1600
1763
 
1601
1764
  // Load initial data and refetch when options change (BUT NEVER TOUCH SUBSCRIPTION)
1602
1765
  useEffect(() => {
@@ -1690,7 +1853,7 @@ export function createSuparismaHook<
1690
1853
  if (defaultValue.includes('now()') || defaultValue.includes('now')) {
1691
1854
  appliedDefaults[field] = now.toISOString(); // Database expects ISO string
1692
1855
  } else if (defaultValue.includes('uuid()') || defaultValue.includes('uuid')) {
1693
- appliedDefaults[field] = crypto.randomUUID();
1856
+ appliedDefaults[field] = generateUUID();
1694
1857
  } else if (defaultValue.includes('cuid()') || defaultValue.includes('cuid')) {
1695
1858
  // Simple cuid-like implementation for client-side
1696
1859
  appliedDefaults[field] = 'c' + Math.random().toString(36).substring(2, 15);
@@ -1720,7 +1883,7 @@ export function createSuparismaHook<
1720
1883
  const { data: result, error } = await supabase
1721
1884
  .from(tableName)
1722
1885
  .insert([itemWithDefaults])
1723
- .select();
1886
+ .select(selectString);
1724
1887
 
1725
1888
  if (error) throw error;
1726
1889
 
@@ -1812,7 +1975,7 @@ export function createSuparismaHook<
1812
1975
  .from(tableName)
1813
1976
  .update(itemWithDefaults)
1814
1977
  .eq(primaryKey, value)
1815
- .select();
1978
+ .select(selectString);
1816
1979
 
1817
1980
  if (error) throw error;
1818
1981
 
@@ -1867,7 +2030,7 @@ export function createSuparismaHook<
1867
2030
  // First fetch the record to return it
1868
2031
  const { data: recordToDelete } = await supabase
1869
2032
  .from(tableName)
1870
- .select('*')
2033
+ .select(selectString)
1871
2034
  .eq(primaryKey, value)
1872
2035
  .maybeSingle();
1873
2036
 
@@ -42,6 +42,8 @@ import type {
42
42
  ${modelName}WhereInput,
43
43
  ${modelName}WhereUniqueInput,
44
44
  ${modelName}OrderByInput,
45
+ ${modelName}SelectInput,
46
+ ${modelName}IncludeInput,
45
47
  ${modelName}HookApi,
46
48
  Use${modelName}Options
47
49
  } from '../types/${modelName}Types';
@@ -92,6 +94,18 @@ import type {
92
94
  * orderBy: { // ordering },
93
95
  * take: 20 // limit
94
96
  * });
97
+ *
98
+ * @example
99
+ * // Select specific fields only
100
+ * const ${modelName.toLowerCase()} = ${config_1.HOOK_NAME_PREFIX}${modelName}({
101
+ * select: { id: true, name: true }
102
+ * });
103
+ *
104
+ * @example
105
+ * // Include related records
106
+ * const ${modelName.toLowerCase()} = ${config_1.HOOK_NAME_PREFIX}${modelName}({
107
+ * include: { relatedModel: true }
108
+ * });
95
109
  */
96
110
  export const ${config_1.HOOK_NAME_PREFIX}${modelName} = createSuparismaHook<
97
111
  ${modelName}WithRelations,
@@ -6,21 +6,83 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateSupabaseClientFile = generateSupabaseClientFile;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
- const config_1 = require("../config"); // Ensure this is UTILS_DIR
9
+ const config_1 = require("../config");
10
+ /**
11
+ * Generate the Supabase client file based on the target platform.
12
+ * Supports both web (Next.js, etc.) and React Native/Expo.
13
+ */
10
14
  function generateSupabaseClientFile() {
11
- const supabaseClientContent = `// THIS FILE IS AUTO-GENERATED - DO NOT EDIT DIRECTLY
15
+ let supabaseClientContent;
16
+ if (config_1.PLATFORM === 'react-native') {
17
+ // React Native / Expo compatible client
18
+ supabaseClientContent = `// THIS FILE IS AUTO-GENERATED - DO NOT EDIT DIRECTLY
19
+ // Platform: React Native / Expo
20
+ //
21
+ // IMPORTANT: Before using Suparisma in React Native, ensure you have:
22
+ // 1. Installed required dependencies:
23
+ // pnpm install @supabase/supabase-js @react-native-async-storage/async-storage react-native-url-polyfill
24
+ //
25
+ // 2. Added polyfills at your app's entry point (e.g., App.tsx or index.js):
26
+ // import 'react-native-url-polyfill/auto';
27
+ //
28
+ // 3. Set your Supabase credentials below or via environment variables
29
+
30
+ import AsyncStorage from '@react-native-async-storage/async-storage';
12
31
  import { createClient } from '@supabase/supabase-js';
13
32
 
14
- export const supabase = createClient(
15
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
16
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
17
- );
33
+ // Option 1: Set your Supabase credentials directly (for quick setup)
34
+ // const SUPABASE_URL = 'https://your-project.supabase.co';
35
+ // const SUPABASE_ANON_KEY = 'your-anon-key';
36
+
37
+ // Option 2: Use environment variables (recommended for production)
38
+ // With Expo, use expo-constants or babel-plugin-inline-dotenv
39
+ // With bare React Native, use react-native-dotenv
40
+ const SUPABASE_URL = process.env.EXPO_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || '';
41
+ const SUPABASE_ANON_KEY = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY || '';
42
+
43
+ if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
44
+ console.warn(
45
+ '[Suparisma] Supabase credentials not found. Please set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY ' +
46
+ 'in your environment variables, or update the credentials directly in this file.'
47
+ );
48
+ }
49
+
50
+ export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
51
+ auth: {
52
+ storage: AsyncStorage,
53
+ autoRefreshToken: true,
54
+ persistSession: true,
55
+ detectSessionInUrl: false, // Important for React Native
56
+ },
57
+ });
18
58
  `;
59
+ }
60
+ else {
61
+ // Web platform (Next.js, Remix, etc.)
62
+ supabaseClientContent = `// THIS FILE IS AUTO-GENERATED - DO NOT EDIT DIRECTLY
63
+ // Platform: Web (Next.js, Remix, etc.)
64
+ import { createClient } from '@supabase/supabase-js';
65
+
66
+ // For Next.js, use NEXT_PUBLIC_ prefix
67
+ // For other frameworks, adjust the environment variable names as needed
68
+ const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || '';
69
+ const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY || '';
70
+
71
+ if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
72
+ console.warn(
73
+ '[Suparisma] Supabase credentials not found. Please set NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY ' +
74
+ '(or SUPABASE_URL and SUPABASE_ANON_KEY) in your environment variables.'
75
+ );
76
+ }
77
+
78
+ export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
79
+ `;
80
+ }
19
81
  // Output to the UTILS_DIR
20
82
  const outputPath = path_1.default.join(config_1.UTILS_DIR, 'supabase-client.ts');
21
83
  if (!fs_1.default.existsSync(config_1.UTILS_DIR)) {
22
84
  fs_1.default.mkdirSync(config_1.UTILS_DIR, { recursive: true });
23
85
  }
24
86
  fs_1.default.writeFileSync(outputPath, supabaseClientContent);
25
- console.log(`🚀 Generated Supabase client file at: ${outputPath}`);
87
+ console.log(`🚀 Generated Supabase client file at: ${outputPath} (platform: ${config_1.PLATFORM})`);
26
88
  }
@@ -352,6 +352,40 @@ export type ${modelName}OrderByInput = {
352
352
  [key in keyof ${modelName}WithRelations]?: 'asc' | 'desc';
353
353
  };
354
354
 
355
+ /**
356
+ * Select specific fields to return from ${modelName} queries.
357
+ * Set fields to \`true\` to include them in the response.
358
+ *
359
+ * @example
360
+ * // Only return id and name
361
+ * ${modelName.toLowerCase()}.findMany({
362
+ * select: { id: true, name: true }
363
+ * });
364
+ */
365
+ export type ${modelName}SelectInput = {
366
+ [key in keyof ${modelName}WithRelations]?: boolean;
367
+ };
368
+
369
+ /**
370
+ * Include related records when querying ${modelName}.
371
+ * Set relation names to \`true\` to include all fields, or use an object to select specific fields.
372
+ *
373
+ * @example
374
+ * // Include all fields from a relation
375
+ * ${modelName.toLowerCase()}.findMany({
376
+ * include: { relatedModel: true }
377
+ * });
378
+ *
379
+ * @example
380
+ * // Include specific fields from a relation
381
+ * ${modelName.toLowerCase()}.findMany({
382
+ * include: { relatedModel: { select: { id: true, name: true } } }
383
+ * });
384
+ */
385
+ export type ${modelName}IncludeInput = {
386
+ [key: string]: boolean | { select?: Record<string, boolean> };
387
+ };
388
+
355
389
  /**
356
390
  * Result type for operations that return a single ${modelName} record.
357
391
  */
@@ -364,8 +398,12 @@ export type ${modelName}ManyResult = ModelResult<${modelName}WithRelations[]>;
364
398
 
365
399
  /**
366
400
  * Configuration options for the ${modelName} hook.
401
+ * Includes where filters, ordering, pagination, and field selection.
367
402
  */
368
- export type Use${modelName}Options = SuparismaOptions<${modelName}WhereInput, ${modelName}OrderByInput>;
403
+ export type Use${modelName}Options = SuparismaOptions<${modelName}WhereInput, ${modelName}OrderByInput, ${modelName}SelectInput> & {
404
+ /** Include related records (foreign key relations) */
405
+ include?: ${modelName}IncludeInput;
406
+ };
369
407
 
370
408
  /**
371
409
  * The complete API for interacting with ${modelName} records.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "suparisma",
3
- "version": "1.2.1",
4
- "description": "Opinionated typesafe React realtime CRUD hooks generator for all your Supabase tables, powered by Prisma.",
3
+ "version": "1.2.3",
4
+ "description": "Opinionated typesafe React realtime CRUD hooks generator for all your Supabase tables, powered by Prisma. Works with Next.js, Remix, React Native, and Expo.",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
7
7
  "type": "git",
@@ -20,12 +20,15 @@
20
20
  },
21
21
  "keywords": [
22
22
  "react",
23
+ "react-native",
24
+ "expo",
23
25
  "hooks",
24
26
  "supabase",
25
27
  "prisma",
26
28
  "typescript",
27
29
  "nextjs",
28
- "realtime"
30
+ "realtime",
31
+ "mobile"
29
32
  ],
30
33
  "dependencies": {
31
34
  "@supabase/supabase-js": "^2.49.4",
@@ -41,5 +44,21 @@
41
44
  "@types/react": "^19.1.4",
42
45
  "prisma": "^6.8.2",
43
46
  "supabase-js": "link:@types/@supabase/supabase-js"
47
+ },
48
+ "peerDependencies": {
49
+ "@react-native-async-storage/async-storage": ">=1.19.0",
50
+ "react-native-get-random-values": ">=1.9.0",
51
+ "react-native-url-polyfill": ">=2.0.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@react-native-async-storage/async-storage": {
55
+ "optional": true
56
+ },
57
+ "react-native-get-random-values": {
58
+ "optional": true
59
+ },
60
+ "react-native-url-polyfill": {
61
+ "optional": true
62
+ }
44
63
  }
45
64
  }