suparisma 0.0.1 → 0.0.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 +156 -3
- package/dist/config.js +12 -3
- package/dist/generators/coreGenerator.js +122 -40
- package/dist/generators/hookGenerator.js +16 -7
- package/dist/generators/indexGenerator.js +12 -7
- package/dist/generators/supabaseClientGenerator.js +5 -5
- package/dist/generators/typeGenerator.js +22 -22
- package/dist/index.js +103 -25
- package/{src/suparisma/generated/useSuparismaUser.ts → dist/suparisma/generated/hooks/useSuparismaUser.js} +19 -35
- package/dist/suparisma/generated/index.js +33 -0
- package/dist/suparisma/generated/types/UserTypes.js +4 -0
- package/dist/suparisma/generated/utils/core.js +1090 -0
- package/dist/suparisma/generated/utils/supabase-client.js +8 -0
- package/package.json +12 -2
- package/prisma/schema.prisma +19 -3
- package/tsconfig.json +1 -1
- package/src/config.ts +0 -7
- package/src/generated/hooks/useSuparismaUser.ts +0 -77
- package/src/generated/index.ts +0 -50
- package/src/generated/types/UserTypes.ts +0 -400
- package/src/generated/utils/core.ts +0 -1413
- package/src/generated/utils/supabase-client.ts +0 -7
- package/src/generators/coreGenerator.ts +0 -1426
- package/src/generators/hookGenerator.ts +0 -110
- package/src/generators/indexGenerator.ts +0 -117
- package/src/generators/supabaseClientGenerator.ts +0 -24
- package/src/generators/typeGenerator.ts +0 -587
- package/src/index.ts +0 -339
- package/src/parser.ts +0 -134
- package/src/suparisma/generated/UserTypes.ts +0 -400
- package/src/suparisma/generated/core.ts +0 -1413
- package/src/suparisma/generated/hooks/useSuparismaUser.ts +0 -77
- package/src/suparisma/generated/index.ts +0 -50
- package/src/suparisma/generated/supabase-client-generated.ts +0 -9
- package/src/suparisma/generated/types/UserTypes.ts +0 -400
- package/src/suparisma/generated/utils/core.ts +0 -1413
- package/src/suparisma/generated/utils/supabase-client.ts +0 -7
- package/src/types.ts +0 -57
package/README.md
CHANGED
|
@@ -1,4 +1,157 @@
|
|
|
1
|
-
|
|
1
|
+
# Suparisma
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
A React hook generator for Supabase that is driven by your Prisma schema, giving you type-safe, real-time enabled hooks to interact with your Supabase database.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
CRUD typesafetey with Supabase should be easy, currently it is not with a lot of issues that can rise easily espeically if you're not using Prisma.
|
|
7
|
+
|
|
8
|
+
Prisma solved supabase typesafely issue on the server, and TRPC helped making the client even more type safe but realtime capabilites from the DB to the browser was still lacking and that lead to a lot of unnecessary GET, POST requests if you just simply need to have realtime support.
|
|
9
|
+
|
|
10
|
+
Supabase's rules are also powerful and if you're using TRPC or any server solution you're easily missing out on them.
|
|
11
|
+
|
|
12
|
+
This package solves all this focusing, it lets you:
|
|
13
|
+
- Create typesafe CRUD hooks for all your supabase tables.
|
|
14
|
+
- Enables you to easily paginate, search and query in each table.
|
|
15
|
+
- Uses Prisma and Supabase official SDKs.
|
|
16
|
+
- Respects Supabase's auth rules enabling an easy way to secure your DB.
|
|
17
|
+
- Works with any React env. like NextJS/Remix/Tanstack Start/Router/etc..
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- 🚀 **Auto-generated React hooks** based on your Prisma schema
|
|
22
|
+
- 🔄 **Real-time updates by default** for all tables (opt-out available)
|
|
23
|
+
- 🔒 **Type-safe interfaces** for all database operations
|
|
24
|
+
- 🔍 **Full-text search** capabilities with optional annotations
|
|
25
|
+
- 🔄 **Prisma-like API** that feels familiar if you use Prisma
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install suparisma
|
|
31
|
+
# or
|
|
32
|
+
yarn add suparisma
|
|
33
|
+
# or
|
|
34
|
+
pnpm install suparisma
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
1. **Add a Prisma schema**: Make sure you have a valid `prisma/schema.prisma` file in your project.
|
|
40
|
+
|
|
41
|
+
2. **Set up required environment variables** in a `.env` file:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
# Required for Prisma and Supabase
|
|
45
|
+
DATABASE_URL="postgresql://user:password@host:port/database"
|
|
46
|
+
DIRECT_URL="postgresql://user:password@host:port/database" # Direct Postgres connection
|
|
47
|
+
NEXT_PUBLIC_SUPABASE_URL="https://your-project.supabase.co"
|
|
48
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY="your-anon-key"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
3. **Generate hooks** with a single command:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx suparisma generate
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This will:
|
|
58
|
+
- Read your Prisma schema from the current directory
|
|
59
|
+
- Configure your database for realtime functionality and search
|
|
60
|
+
- Generate type-safe React hooks in `src/suparisma/generated` (configurable)
|
|
61
|
+
|
|
62
|
+
4. **Use the hooks** in your React components:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import useSuparisma from './src/suparisma/generated';
|
|
66
|
+
|
|
67
|
+
function UserList() {
|
|
68
|
+
const users = useSuparisma.user();
|
|
69
|
+
|
|
70
|
+
if (users.loading) return <div>Loading...</div>;
|
|
71
|
+
if (users.error) return <div>Error: {users.error.message}</div>;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
<h1>Users</h1>
|
|
76
|
+
<ul>
|
|
77
|
+
{users.data?.map(user => (
|
|
78
|
+
<li key={user.id}>{user.name}</li>
|
|
79
|
+
))}
|
|
80
|
+
</ul>
|
|
81
|
+
|
|
82
|
+
<button onClick={() => users.create({ name: "New User" })}>
|
|
83
|
+
Add User
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Annotations in Your Prisma Schema
|
|
91
|
+
|
|
92
|
+
Add annotations directly in your Prisma schema as comments:
|
|
93
|
+
|
|
94
|
+
```prisma
|
|
95
|
+
// Realtime is enabled by default for this model
|
|
96
|
+
model User {
|
|
97
|
+
id String @id @default(uuid())
|
|
98
|
+
email String @unique
|
|
99
|
+
name String? // @enableSearch
|
|
100
|
+
createdAt DateTime @default(now())
|
|
101
|
+
updatedAt DateTime @updatedAt
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// @disableRealtime - Opt out of realtime for this model
|
|
105
|
+
model AuditLog {
|
|
106
|
+
id String @id @default(uuid())
|
|
107
|
+
action String
|
|
108
|
+
details String?
|
|
109
|
+
createdAt DateTime @default(now())
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Stale Models Cleanup
|
|
114
|
+
|
|
115
|
+
When you delete a model from your Prisma schema and run the generation command, Suparisma automatically:
|
|
116
|
+
- Detects changes to your schema
|
|
117
|
+
- Deletes the entire generated directory
|
|
118
|
+
- Regenerates all hooks and types based on your current schema
|
|
119
|
+
|
|
120
|
+
This ensures you don't have stale files lingering around for models that no longer exist in your schema.
|
|
121
|
+
|
|
122
|
+
## CLI Commands
|
|
123
|
+
|
|
124
|
+
Suparisma provides a simple CLI with the following commands:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Generate hooks based on your Prisma schema
|
|
128
|
+
npx suparisma generate
|
|
129
|
+
|
|
130
|
+
# Show help information
|
|
131
|
+
npx suparisma help
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Configuration
|
|
135
|
+
|
|
136
|
+
You can customize the behavior using environment variables:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
# Optional: Customize output directory
|
|
140
|
+
SUPARISMA_OUTPUT_DIR="src/hooks/generated"
|
|
141
|
+
|
|
142
|
+
# Optional: Specify custom schema path
|
|
143
|
+
SUPARISMA_PRISMA_SCHEMA_PATH="path/to/schema.prisma"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Environment Variables
|
|
147
|
+
|
|
148
|
+
The following environment variables are required:
|
|
149
|
+
|
|
150
|
+
- `DATABASE_URL` - Your Postgres database URL (used by Prisma)
|
|
151
|
+
- `DIRECT_URL` - Direct URL to your Postgres database (for setting up realtime)
|
|
152
|
+
- `NEXT_PUBLIC_SUPABASE_URL` - Your Supabase project URL
|
|
153
|
+
- `NEXT_PUBLIC_SUPABASE_ANON_KEY` - Your Supabase anonymous key
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HOOK_NAME_PREFIX = exports.OUTPUT_DIR = exports.PRISMA_SCHEMA_PATH = void 0;
|
|
6
|
+
exports.HOOK_NAME_PREFIX = exports.UTILS_DIR = exports.HOOKS_DIR = exports.TYPES_DIR = exports.OUTPUT_DIR = exports.PRISMA_SCHEMA_PATH = void 0;
|
|
4
7
|
// Configuration
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
// Use current working directory for all paths
|
|
10
|
+
const CWD = process.cwd();
|
|
11
|
+
exports.PRISMA_SCHEMA_PATH = process.env.SUPARISMA_PRISMA_SCHEMA_PATH || path_1.default.join(CWD, 'prisma/schema.prisma');
|
|
12
|
+
exports.OUTPUT_DIR = process.env.SUPARISMA_OUTPUT_DIR || path_1.default.join(CWD, 'src/suparisma/generated');
|
|
13
|
+
exports.TYPES_DIR = `${exports.OUTPUT_DIR}/types`;
|
|
14
|
+
exports.HOOKS_DIR = `${exports.OUTPUT_DIR}/hooks`;
|
|
15
|
+
exports.UTILS_DIR = `${exports.OUTPUT_DIR}/utils`;
|
|
7
16
|
exports.HOOK_NAME_PREFIX = 'useSuparisma';
|
|
@@ -15,7 +15,8 @@ function generateCoreFile() {
|
|
|
15
15
|
// Edit the generator script instead: scripts/generate-realtime-hooks.ts
|
|
16
16
|
|
|
17
17
|
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
18
|
-
import
|
|
18
|
+
// This import should be relative to its new location in utils/
|
|
19
|
+
import { supabase } from './supabase-client';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Represents a single search query against a field
|
|
@@ -301,13 +302,14 @@ export function applyFilter<T>(
|
|
|
301
302
|
export function applyOrderBy<T>(
|
|
302
303
|
query: SupabaseQueryBuilder,
|
|
303
304
|
orderBy?: T,
|
|
304
|
-
hasCreatedAt?: boolean
|
|
305
|
+
hasCreatedAt?: boolean,
|
|
306
|
+
createdAtField: string = 'createdAt'
|
|
305
307
|
): SupabaseQueryBuilder {
|
|
306
308
|
if (!orderBy) {
|
|
307
|
-
// By default, sort by
|
|
309
|
+
// By default, sort by createdAt if available, using the actual field name from Prisma
|
|
308
310
|
if (hasCreatedAt) {
|
|
309
311
|
// @ts-ignore: Supabase typing issue
|
|
310
|
-
return query.order(
|
|
312
|
+
return query.order(createdAtField, { ascending: false });
|
|
311
313
|
}
|
|
312
314
|
return query;
|
|
313
315
|
}
|
|
@@ -341,9 +343,19 @@ export function createSuparismaHook<
|
|
|
341
343
|
hasCreatedAt: boolean;
|
|
342
344
|
hasUpdatedAt: boolean;
|
|
343
345
|
searchFields?: string[];
|
|
344
|
-
defaultValues?: Record<string, string>;
|
|
346
|
+
defaultValues?: Record<string, string>;
|
|
347
|
+
createdAtField?: string;
|
|
348
|
+
updatedAtField?: string;
|
|
345
349
|
}) {
|
|
346
|
-
const {
|
|
350
|
+
const {
|
|
351
|
+
tableName,
|
|
352
|
+
hasCreatedAt,
|
|
353
|
+
hasUpdatedAt,
|
|
354
|
+
searchFields = [],
|
|
355
|
+
defaultValues = {},
|
|
356
|
+
createdAtField = 'createdAt',
|
|
357
|
+
updatedAtField = 'updatedAt'
|
|
358
|
+
} = config;
|
|
347
359
|
|
|
348
360
|
/**
|
|
349
361
|
* The main hook function that provides all data access methods for a model.
|
|
@@ -383,6 +395,9 @@ export function createSuparismaHook<
|
|
|
383
395
|
const [error, setError] = useState<Error | null>(null);
|
|
384
396
|
const [loading, setLoading] = useState<boolean>(false);
|
|
385
397
|
|
|
398
|
+
// This is the total count, unaffected by pagination limits
|
|
399
|
+
const [count, setCount] = useState<number>(0);
|
|
400
|
+
|
|
386
401
|
// Search state
|
|
387
402
|
const [searchQueries, setSearchQueries] = useState<SearchQuery[]>([]);
|
|
388
403
|
const [searchLoading, setSearchLoading] = useState<boolean>(false);
|
|
@@ -391,6 +406,34 @@ export function createSuparismaHook<
|
|
|
391
406
|
const channelRef = useRef<ReturnType<typeof supabase.channel> | null>(null);
|
|
392
407
|
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
393
408
|
const isSearchingRef = useRef<boolean>(false);
|
|
409
|
+
|
|
410
|
+
// Function to fetch the total count from Supabase with current filters
|
|
411
|
+
const fetchTotalCount = useCallback(async () => {
|
|
412
|
+
try {
|
|
413
|
+
// Skip count updates during search
|
|
414
|
+
if (isSearchingRef.current) return;
|
|
415
|
+
|
|
416
|
+
let countQuery = supabase.from(tableName).select('*', { count: 'exact', head: true });
|
|
417
|
+
|
|
418
|
+
// Apply where conditions if provided
|
|
419
|
+
if (where) {
|
|
420
|
+
countQuery = applyFilter(countQuery, where);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const { count: totalCount, error: countError } = await countQuery;
|
|
424
|
+
|
|
425
|
+
if (!countError) {
|
|
426
|
+
setCount(totalCount || 0);
|
|
427
|
+
}
|
|
428
|
+
} catch (err) {
|
|
429
|
+
console.error(\`Error fetching count for \${tableName}:\`, err);
|
|
430
|
+
}
|
|
431
|
+
}, [where, tableName]);
|
|
432
|
+
|
|
433
|
+
// Update total count whenever where filter changes
|
|
434
|
+
useEffect(() => {
|
|
435
|
+
fetchTotalCount();
|
|
436
|
+
}, [fetchTotalCount]);
|
|
394
437
|
|
|
395
438
|
// Create the search state object with all required methods
|
|
396
439
|
const search: SearchState = {
|
|
@@ -537,6 +580,9 @@ export function createSuparismaHook<
|
|
|
537
580
|
});
|
|
538
581
|
}
|
|
539
582
|
|
|
583
|
+
// Set count directly for search results
|
|
584
|
+
setCount(results.length);
|
|
585
|
+
|
|
540
586
|
// Apply ordering if needed
|
|
541
587
|
if (orderBy) {
|
|
542
588
|
const orderEntries = Object.entries(orderBy);
|
|
@@ -556,16 +602,17 @@ export function createSuparismaHook<
|
|
|
556
602
|
}
|
|
557
603
|
|
|
558
604
|
// Apply pagination if needed
|
|
605
|
+
let paginatedResults = results;
|
|
559
606
|
if (limit && limit > 0) {
|
|
560
|
-
|
|
607
|
+
paginatedResults = results.slice(0, limit);
|
|
561
608
|
}
|
|
562
609
|
|
|
563
610
|
if (offset && offset > 0) {
|
|
564
|
-
|
|
611
|
+
paginatedResults = paginatedResults.slice(offset);
|
|
565
612
|
}
|
|
566
613
|
|
|
567
614
|
// Update data with search results
|
|
568
|
-
setData(
|
|
615
|
+
setData(paginatedResults);
|
|
569
616
|
} catch (err) {
|
|
570
617
|
console.error('Search error:', err);
|
|
571
618
|
setError(err as Error);
|
|
@@ -620,11 +667,11 @@ export function createSuparismaHook<
|
|
|
620
667
|
|
|
621
668
|
// Apply order by if provided
|
|
622
669
|
if (params?.orderBy) {
|
|
623
|
-
query = applyOrderBy(query, params.orderBy, hasCreatedAt);
|
|
670
|
+
query = applyOrderBy(query, params.orderBy, hasCreatedAt, createdAtField);
|
|
624
671
|
} else if (hasCreatedAt) {
|
|
625
|
-
//
|
|
672
|
+
// Use the actual createdAt field name from Prisma
|
|
626
673
|
// @ts-ignore: Supabase typing issue
|
|
627
|
-
query = query.order(
|
|
674
|
+
query = query.order(createdAtField, { ascending: false });
|
|
628
675
|
}
|
|
629
676
|
|
|
630
677
|
// Apply limit if provided
|
|
@@ -646,6 +693,12 @@ export function createSuparismaHook<
|
|
|
646
693
|
// Only update data if not currently searching
|
|
647
694
|
if (!isSearchingRef.current) {
|
|
648
695
|
setData(typedData);
|
|
696
|
+
|
|
697
|
+
// If the where filter changed, update the total count
|
|
698
|
+
if (JSON.stringify(params?.where) !== JSON.stringify(where)) {
|
|
699
|
+
// Use our standard count fetching function instead of duplicating logic
|
|
700
|
+
setTimeout(() => fetchTotalCount(), 0);
|
|
701
|
+
}
|
|
649
702
|
}
|
|
650
703
|
|
|
651
704
|
return { data: typedData, error: null };
|
|
@@ -656,7 +709,7 @@ export function createSuparismaHook<
|
|
|
656
709
|
} finally {
|
|
657
710
|
setLoading(false);
|
|
658
711
|
}
|
|
659
|
-
}, []);
|
|
712
|
+
}, [fetchTotalCount, where, tableName, hasCreatedAt, createdAtField]);
|
|
660
713
|
|
|
661
714
|
/**
|
|
662
715
|
* Find a single record by its unique identifier (usually ID).
|
|
@@ -809,6 +862,9 @@ export function createSuparismaHook<
|
|
|
809
862
|
newData = newData.slice(0, currentLimit);
|
|
810
863
|
}
|
|
811
864
|
|
|
865
|
+
// Fetch the updated count after the data changes
|
|
866
|
+
setTimeout(() => fetchTotalCount(), 0);
|
|
867
|
+
|
|
812
868
|
return newData;
|
|
813
869
|
} catch (error) {
|
|
814
870
|
console.error('Error processing INSERT event:', error);
|
|
@@ -816,19 +872,25 @@ export function createSuparismaHook<
|
|
|
816
872
|
}
|
|
817
873
|
});
|
|
818
874
|
} else if (payload.eventType === 'UPDATE') {
|
|
819
|
-
// Process update event
|
|
875
|
+
// Process update event
|
|
820
876
|
setData((prev) => {
|
|
821
877
|
// Skip if search is active
|
|
822
878
|
if (isSearchingRef.current) {
|
|
823
879
|
return prev;
|
|
824
880
|
}
|
|
825
881
|
|
|
826
|
-
|
|
882
|
+
const newData = prev.map((item) =>
|
|
827
883
|
// @ts-ignore: Supabase typing issue
|
|
828
884
|
'id' in item && 'id' in payload.new && item.id === payload.new.id
|
|
829
885
|
? (payload.new as TWithRelations)
|
|
830
886
|
: item
|
|
831
887
|
);
|
|
888
|
+
|
|
889
|
+
// Fetch the updated count after the data changes
|
|
890
|
+
// For updates, the count might not change but we fetch anyway to be consistent
|
|
891
|
+
setTimeout(() => fetchTotalCount(), 0);
|
|
892
|
+
|
|
893
|
+
return newData;
|
|
832
894
|
});
|
|
833
895
|
} else if (payload.eventType === 'DELETE') {
|
|
834
896
|
// Process delete event
|
|
@@ -847,6 +909,9 @@ export function createSuparismaHook<
|
|
|
847
909
|
return !('id' in item && 'id' in payload.old && item.id === payload.old.id);
|
|
848
910
|
});
|
|
849
911
|
|
|
912
|
+
// Fetch the updated count after the data changes
|
|
913
|
+
setTimeout(() => fetchTotalCount(), 0);
|
|
914
|
+
|
|
850
915
|
// If we need to maintain the size with a limit
|
|
851
916
|
if (currentLimit && currentLimit > 0 && filteredData.length < currentSize && currentSize === currentLimit) {
|
|
852
917
|
console.log(\`Record deleted with limit \${currentLimit}, will fetch additional record to maintain size\`);
|
|
@@ -931,6 +996,9 @@ export function createSuparismaHook<
|
|
|
931
996
|
take: limit,
|
|
932
997
|
skip: offset
|
|
933
998
|
});
|
|
999
|
+
|
|
1000
|
+
// Also update the total count
|
|
1001
|
+
fetchTotalCount();
|
|
934
1002
|
}
|
|
935
1003
|
return;
|
|
936
1004
|
}
|
|
@@ -943,7 +1011,10 @@ export function createSuparismaHook<
|
|
|
943
1011
|
take: limit,
|
|
944
1012
|
skip: offset
|
|
945
1013
|
});
|
|
946
|
-
|
|
1014
|
+
|
|
1015
|
+
// Initial count fetch
|
|
1016
|
+
fetchTotalCount();
|
|
1017
|
+
}, [findMany, where, orderBy, limit, offset, optionsChanged, fetchTotalCount]);
|
|
947
1018
|
|
|
948
1019
|
/**
|
|
949
1020
|
* Create a new record with the provided data.
|
|
@@ -1008,8 +1079,9 @@ export function createSuparismaHook<
|
|
|
1008
1079
|
const itemWithDefaults = {
|
|
1009
1080
|
...appliedDefaults, // Apply schema defaults first
|
|
1010
1081
|
...data, // Then user data (overrides defaults)
|
|
1011
|
-
|
|
1012
|
-
...(
|
|
1082
|
+
// Use the actual field names from Prisma
|
|
1083
|
+
...(hasCreatedAt ? { [createdAtField]: now } : {}),
|
|
1084
|
+
...(hasUpdatedAt ? { [updatedAtField]: now } : {})
|
|
1013
1085
|
};
|
|
1014
1086
|
|
|
1015
1087
|
const { data: result, error } = await supabase
|
|
@@ -1019,6 +1091,9 @@ export function createSuparismaHook<
|
|
|
1019
1091
|
|
|
1020
1092
|
if (error) throw error;
|
|
1021
1093
|
|
|
1094
|
+
// Update the total count after a successful creation
|
|
1095
|
+
setTimeout(() => fetchTotalCount(), 0);
|
|
1096
|
+
|
|
1022
1097
|
// Return created record
|
|
1023
1098
|
return { data: result?.[0] as TWithRelations, error: null };
|
|
1024
1099
|
} catch (err: any) {
|
|
@@ -1028,7 +1103,7 @@ export function createSuparismaHook<
|
|
|
1028
1103
|
} finally {
|
|
1029
1104
|
setLoading(false);
|
|
1030
1105
|
}
|
|
1031
|
-
}, []);
|
|
1106
|
+
}, [fetchTotalCount]);
|
|
1032
1107
|
|
|
1033
1108
|
/**
|
|
1034
1109
|
* Update an existing record identified by a unique identifier.
|
|
@@ -1081,7 +1156,8 @@ export function createSuparismaHook<
|
|
|
1081
1156
|
|
|
1082
1157
|
const itemWithDefaults = {
|
|
1083
1158
|
...params.data,
|
|
1084
|
-
|
|
1159
|
+
// Use the actual updatedAt field name from Prisma
|
|
1160
|
+
...(hasUpdatedAt ? { [updatedAtField]: now } : {})
|
|
1085
1161
|
};
|
|
1086
1162
|
|
|
1087
1163
|
const { data, error } = await supabase
|
|
@@ -1092,6 +1168,11 @@ export function createSuparismaHook<
|
|
|
1092
1168
|
|
|
1093
1169
|
if (error) throw error;
|
|
1094
1170
|
|
|
1171
|
+
// Update the total count after a successful update
|
|
1172
|
+
// This is for consistency with other operations, and because
|
|
1173
|
+
// updates can sometimes affect filtering based on updated values
|
|
1174
|
+
setTimeout(() => fetchTotalCount(), 0);
|
|
1175
|
+
|
|
1095
1176
|
// Return updated record
|
|
1096
1177
|
return { data: data?.[0] as TWithRelations, error: null };
|
|
1097
1178
|
} catch (err: any) {
|
|
@@ -1101,7 +1182,7 @@ export function createSuparismaHook<
|
|
|
1101
1182
|
} finally {
|
|
1102
1183
|
setLoading(false);
|
|
1103
1184
|
}
|
|
1104
|
-
}, []);
|
|
1185
|
+
}, [fetchTotalCount]);
|
|
1105
1186
|
|
|
1106
1187
|
/**
|
|
1107
1188
|
* Delete a record by its unique identifier.
|
|
@@ -1154,6 +1235,9 @@ export function createSuparismaHook<
|
|
|
1154
1235
|
|
|
1155
1236
|
if (error) throw error;
|
|
1156
1237
|
|
|
1238
|
+
// Update the total count after a successful deletion
|
|
1239
|
+
setTimeout(() => fetchTotalCount(), 0);
|
|
1240
|
+
|
|
1157
1241
|
// Return the deleted record
|
|
1158
1242
|
return { data: recordToDelete as TWithRelations, error: null };
|
|
1159
1243
|
} catch (err: any) {
|
|
@@ -1163,7 +1247,7 @@ export function createSuparismaHook<
|
|
|
1163
1247
|
} finally {
|
|
1164
1248
|
setLoading(false);
|
|
1165
1249
|
}
|
|
1166
|
-
}, []);
|
|
1250
|
+
}, [fetchTotalCount]);
|
|
1167
1251
|
|
|
1168
1252
|
/**
|
|
1169
1253
|
* Delete multiple records matching the filter criteria.
|
|
@@ -1220,6 +1304,9 @@ export function createSuparismaHook<
|
|
|
1220
1304
|
|
|
1221
1305
|
if (deleteError) throw deleteError;
|
|
1222
1306
|
|
|
1307
|
+
// Update the total count after a successful bulk deletion
|
|
1308
|
+
setTimeout(() => fetchTotalCount(), 0);
|
|
1309
|
+
|
|
1223
1310
|
// Return the count of deleted records
|
|
1224
1311
|
return { count: recordsToDelete.length, error: null };
|
|
1225
1312
|
} catch (err: any) {
|
|
@@ -1229,7 +1316,7 @@ export function createSuparismaHook<
|
|
|
1229
1316
|
} finally {
|
|
1230
1317
|
setLoading(false);
|
|
1231
1318
|
}
|
|
1232
|
-
}, []);
|
|
1319
|
+
}, [fetchTotalCount]);
|
|
1233
1320
|
|
|
1234
1321
|
/**
|
|
1235
1322
|
* Find the first record matching the filter criteria.
|
|
@@ -1312,21 +1399,13 @@ export function createSuparismaHook<
|
|
|
1312
1399
|
|
|
1313
1400
|
/**
|
|
1314
1401
|
* Count the number of records matching the filter criteria.
|
|
1402
|
+
* This is a manual method to get the count with a different filter
|
|
1403
|
+
* than the main hook's filter.
|
|
1315
1404
|
*
|
|
1316
1405
|
* @param params - Query parameters for filtering
|
|
1317
1406
|
* @returns A promise with the count of matching records
|
|
1318
|
-
*
|
|
1319
|
-
* @example
|
|
1320
|
-
* // Count all users
|
|
1321
|
-
* const count = await users.count();
|
|
1322
|
-
*
|
|
1323
|
-
* @example
|
|
1324
|
-
* // Count active users
|
|
1325
|
-
* const activeCount = await users.count({
|
|
1326
|
-
* where: { active: true }
|
|
1327
|
-
* });
|
|
1328
1407
|
*/
|
|
1329
|
-
const
|
|
1408
|
+
const countFn = useCallback(async (params?: {
|
|
1330
1409
|
where?: TWhereInput;
|
|
1331
1410
|
}): Promise<number> => {
|
|
1332
1411
|
try {
|
|
@@ -1395,6 +1474,7 @@ export function createSuparismaHook<
|
|
|
1395
1474
|
data,
|
|
1396
1475
|
error,
|
|
1397
1476
|
loading,
|
|
1477
|
+
count, // Now including count as a reactive state value
|
|
1398
1478
|
|
|
1399
1479
|
// Finder methods
|
|
1400
1480
|
findUnique,
|
|
@@ -1408,9 +1488,6 @@ export function createSuparismaHook<
|
|
|
1408
1488
|
deleteMany,
|
|
1409
1489
|
upsert,
|
|
1410
1490
|
|
|
1411
|
-
// Utilities
|
|
1412
|
-
count,
|
|
1413
|
-
|
|
1414
1491
|
// Manual refresh
|
|
1415
1492
|
refresh
|
|
1416
1493
|
};
|
|
@@ -1423,8 +1500,13 @@ export function createSuparismaHook<
|
|
|
1423
1500
|
}
|
|
1424
1501
|
: api;
|
|
1425
1502
|
};
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1503
|
+
}
|
|
1504
|
+
`; // Ensure template literal is closed
|
|
1505
|
+
// Output to the UTILS_DIR
|
|
1506
|
+
const outputPath = path_1.default.join(config_1.UTILS_DIR, 'core.ts');
|
|
1507
|
+
if (!fs_1.default.existsSync(config_1.UTILS_DIR)) {
|
|
1508
|
+
fs_1.default.mkdirSync(config_1.UTILS_DIR, { recursive: true });
|
|
1509
|
+
}
|
|
1428
1510
|
fs_1.default.writeFileSync(outputPath, coreContent);
|
|
1429
|
-
console.log(`
|
|
1511
|
+
console.log(`Generated core utility file at ${outputPath}`);
|
|
1430
1512
|
}
|
|
@@ -16,7 +16,9 @@ const config_1 = require("../config");
|
|
|
16
16
|
* @param modelInfo - Processed model information with metadata
|
|
17
17
|
*/
|
|
18
18
|
function generateModelHookFile(modelInfo) {
|
|
19
|
-
const { modelName, tableName, hasCreatedAt, hasUpdatedAt, searchFields, defaultValues
|
|
19
|
+
const { modelName, tableName, hasCreatedAt, hasUpdatedAt, searchFields, defaultValues, createdAtField = 'createdAt', // Default to camelCase but use actual field name if provided
|
|
20
|
+
updatedAtField = 'updatedAt' // Default to camelCase but use actual field name if provided
|
|
21
|
+
} = modelInfo;
|
|
20
22
|
// Configure search fields if available
|
|
21
23
|
const searchConfig = searchFields && searchFields.length > 0
|
|
22
24
|
? `,\n // Configure search for fields with @enableSearch annotation\n searchFields: ${JSON.stringify(searchFields)}`
|
|
@@ -25,11 +27,14 @@ function generateModelHookFile(modelInfo) {
|
|
|
25
27
|
const defaultValuesConfig = defaultValues
|
|
26
28
|
? `,\n // Default values from schema\n defaultValues: ${JSON.stringify(defaultValues)}`
|
|
27
29
|
: '';
|
|
30
|
+
// Add createdAt/updatedAt field name config
|
|
31
|
+
const fieldNamesConfig = `${hasCreatedAt ? `,\n // Field name for createdAt from Prisma schema\n createdAtField: "${createdAtField}"` : ''}${hasUpdatedAt ? `,\n // Field name for updatedAt from Prisma schema\n updatedAtField: "${updatedAtField}"` : ''}`;
|
|
28
32
|
// Generate the hook content
|
|
29
33
|
const hookContent = `// THIS FILE IS AUTO-GENERATED - DO NOT EDIT DIRECTLY
|
|
30
34
|
// Edit the generator script instead: scripts/generate-realtime-hooks.ts
|
|
31
35
|
|
|
32
|
-
import
|
|
36
|
+
// Corrected import for core hook factory
|
|
37
|
+
import { createSuparismaHook } from '../utils/core';
|
|
33
38
|
import type {
|
|
34
39
|
${modelName}WithRelations,
|
|
35
40
|
${modelName}CreateInput,
|
|
@@ -39,7 +44,7 @@ import type {
|
|
|
39
44
|
${modelName}OrderByInput,
|
|
40
45
|
${modelName}HookApi,
|
|
41
46
|
Use${modelName}Options
|
|
42
|
-
} from '
|
|
47
|
+
} from '../types/${modelName}Types';
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
50
|
* A Prisma-like hook for interacting with ${modelName} records with real-time capabilities.
|
|
@@ -58,7 +63,7 @@ import type {
|
|
|
58
63
|
* // With filtering and ordering
|
|
59
64
|
* const ${modelName.toLowerCase()} = ${config_1.HOOK_NAME_PREFIX}${modelName}({
|
|
60
65
|
* where: { active: true },
|
|
61
|
-
* orderBy: {
|
|
66
|
+
* orderBy: { createdAt: 'desc' }, // Note: Using actual Prisma field name
|
|
62
67
|
* limit: 10
|
|
63
68
|
* });
|
|
64
69
|
*
|
|
@@ -99,10 +104,14 @@ export const ${config_1.HOOK_NAME_PREFIX}${modelName} = createSuparismaHook<
|
|
|
99
104
|
>({
|
|
100
105
|
tableName: '${tableName}',
|
|
101
106
|
hasCreatedAt: ${hasCreatedAt},
|
|
102
|
-
hasUpdatedAt: ${hasUpdatedAt}${searchConfig}${defaultValuesConfig}
|
|
103
|
-
}) as (options?: Use${modelName}Options) => ${modelName}HookApi;
|
|
107
|
+
hasUpdatedAt: ${hasUpdatedAt}${searchConfig}${defaultValuesConfig}${fieldNamesConfig}
|
|
108
|
+
}) as unknown as (options?: Use${modelName}Options) => ${modelName}HookApi;
|
|
104
109
|
`;
|
|
105
|
-
|
|
110
|
+
// Output to the HOOKS_DIR
|
|
111
|
+
const outputPath = path_1.default.join(config_1.HOOKS_DIR, `${config_1.HOOK_NAME_PREFIX}${modelName}.ts`);
|
|
112
|
+
if (!fs_1.default.existsSync(config_1.HOOKS_DIR)) {
|
|
113
|
+
fs_1.default.mkdirSync(config_1.HOOKS_DIR, { recursive: true });
|
|
114
|
+
}
|
|
106
115
|
fs_1.default.writeFileSync(outputPath, hookContent);
|
|
107
116
|
console.log(`Generated hook for ${modelName} at ${outputPath}`);
|
|
108
117
|
}
|
|
@@ -17,22 +17,22 @@ const config_1 = require("../config");
|
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
19
|
* // The generated index allows imports like:
|
|
20
|
-
* import useSuparisma from './
|
|
20
|
+
* import useSuparisma from './your-output-dir'; // e.g., from './suparisma/generated'
|
|
21
21
|
* // or
|
|
22
|
-
* import { useSuparismaUser } from './
|
|
22
|
+
* import { useSuparismaUser } from './your-output-dir';
|
|
23
23
|
*/
|
|
24
24
|
function generateMainIndexFile(modelInfos) {
|
|
25
25
|
// Import statements for hooks
|
|
26
26
|
const imports = modelInfos
|
|
27
|
-
.map((info) => `import { ${config_1.HOOK_NAME_PREFIX}${info.modelName} } from '
|
|
27
|
+
.map((info) => `import { ${config_1.HOOK_NAME_PREFIX}${info.modelName} } from './hooks/${config_1.HOOK_NAME_PREFIX}${info.modelName}';`)
|
|
28
28
|
.join('\n');
|
|
29
29
|
// Import all required types
|
|
30
30
|
const typeImports = modelInfos
|
|
31
|
-
.map((info) => `import type { Use${info.modelName}Options, ${info.modelName}HookApi } from '
|
|
31
|
+
.map((info) => `import type { Use${info.modelName}Options, ${info.modelName}HookApi } from './types/${info.modelName}Types';`)
|
|
32
32
|
.join('\n');
|
|
33
33
|
// Model-specific type exports
|
|
34
34
|
const modelTypeExports = modelInfos
|
|
35
|
-
.map((info) => `export type { ${info.modelName}WithRelations, ${info.modelName}CreateInput, ${info.modelName}UpdateInput, ${info.modelName}WhereInput, ${info.modelName}WhereUniqueInput, ${info.modelName}OrderByInput, ${info.modelName}HookApi, Use${info.modelName}Options } from '
|
|
35
|
+
.map((info) => `export type { ${info.modelName}WithRelations, ${info.modelName}CreateInput, ${info.modelName}UpdateInput, ${info.modelName}WhereInput, ${info.modelName}WhereUniqueInput, ${info.modelName}OrderByInput, ${info.modelName}HookApi, Use${info.modelName}Options } from './types/${info.modelName}Types';`)
|
|
36
36
|
.join('\n');
|
|
37
37
|
// Create hook interface properties
|
|
38
38
|
const hookProperties = modelInfos
|
|
@@ -48,7 +48,7 @@ function generateMainIndexFile(modelInfos) {
|
|
|
48
48
|
|
|
49
49
|
${imports}
|
|
50
50
|
${typeImports}
|
|
51
|
-
export type { SuparismaOptions, SearchQuery, SearchState, FilterOperators } from './core';
|
|
51
|
+
export type { SuparismaOptions, SearchQuery, SearchState, FilterOperators } from './utils/core';
|
|
52
52
|
${modelTypeExports}
|
|
53
53
|
|
|
54
54
|
/**
|
|
@@ -69,7 +69,7 @@ ${hookProperties}
|
|
|
69
69
|
*
|
|
70
70
|
* @example
|
|
71
71
|
* // Get hooks for different models
|
|
72
|
-
* import useSuparisma from './
|
|
72
|
+
* import useSuparisma from './your-output-dir'; // e.g., from './suparisma/generated'
|
|
73
73
|
*
|
|
74
74
|
* // Access user model with all hook methods
|
|
75
75
|
* const users = useSuparisma.user();
|
|
@@ -94,7 +94,12 @@ ${hookAssignments}
|
|
|
94
94
|
|
|
95
95
|
export default useSuparisma;
|
|
96
96
|
`;
|
|
97
|
+
// Output to OUTPUT_DIR (root of generated files)
|
|
97
98
|
const outputPath = path_1.default.join(config_1.OUTPUT_DIR, 'index.ts');
|
|
99
|
+
// Ensure the main output directory exists (it should have been created by other generators for subdirs too)
|
|
100
|
+
if (!fs_1.default.existsSync(config_1.OUTPUT_DIR)) {
|
|
101
|
+
fs_1.default.mkdirSync(config_1.OUTPUT_DIR, { recursive: true });
|
|
102
|
+
}
|
|
98
103
|
fs_1.default.writeFileSync(outputPath, content);
|
|
99
104
|
console.log(`Generated main module file at ${outputPath}`);
|
|
100
105
|
}
|