naystack 1.1.16 → 1.1.17

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 (2) hide show
  1. package/README.md +600 -0
  2. package/package.json +7 -4
package/README.md ADDED
@@ -0,0 +1,600 @@
1
+ # Naystack
2
+
3
+ > A stack built with tight **Next.js + Drizzle ORM + GraphQL** integration
4
+
5
+ [![npm version](https://img.shields.io/npm/v/naystack.svg)](https://www.npmjs.com/package/naystack)
6
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install naystack
12
+ # or
13
+ pnpm add naystack
14
+ # or
15
+ yarn add naystack
16
+ ```
17
+
18
+ ## Modules
19
+
20
+ Naystack provides the following modules, each accessible via its own import path:
21
+
22
+ | Module | Import Path | Description |
23
+ | ----------- | ------------------ | ----------------------------------------------- |
24
+ | **Auth** | `naystack/auth` | Email, Google, and Instagram authentication |
25
+ | **GraphQL** | `naystack/graphql` | GraphQL server initialization with type-graphql |
26
+ | **Client** | `naystack/client` | Client-side hooks and utilities |
27
+ | **File** | `naystack/file` | File upload to AWS S3 |
28
+ | **Socials** | `naystack/socials` | Instagram and Threads API integration |
29
+
30
+ ---
31
+
32
+ ## Auth Module
33
+
34
+ ```typescript
35
+ import {
36
+ getEmailAuthRoutes,
37
+ initGoogleAuth,
38
+ initInstagramAuth,
39
+ } from "naystack/auth";
40
+ ```
41
+
42
+ ### Email Authentication
43
+
44
+ Setup email-based authentication with JWT tokens and optional Turnstile captcha verification.
45
+
46
+ ```typescript
47
+ const emailAuth = getEmailAuthRoutes({
48
+ getUser: async (email: string) => { /* fetch user by email */ },
49
+ createUser: async (user: UserInput) => { /* create new user */ },
50
+ signingKey: process.env.JWT_SIGNING_KEY!,
51
+ refreshKey: process.env.JWT_REFRESH_KEY!,
52
+ turnstileKey?: string, // Optional: Cloudflare Turnstile secret key
53
+ onSignUp: (user) => { /* callback on signup */ },
54
+ onLogout?: (body) => { /* callback on logout */ },
55
+ onError?: (error) => { /* custom error handler */ },
56
+ });
57
+
58
+ // Export in Next.js route handler
59
+ export const { GET, POST, PUT, DELETE, getUserIdFromRequest } = emailAuth;
60
+ ```
61
+
62
+ #### Options
63
+
64
+ | Option | Type | Required | Description |
65
+ | -------------- | ------------------------------------------------------- | -------- | ------------------------------- |
66
+ | `getUser` | `(email: string) => Promise<UserOutput \| undefined>` | ✅ | Fetch user by email |
67
+ | `createUser` | `(user: UserInput) => Promise<UserOutput \| undefined>` | ✅ | Create new user |
68
+ | `signingKey` | `string` | ✅ | JWT signing key |
69
+ | `refreshKey` | `string` | ✅ | JWT refresh key |
70
+ | `turnstileKey` | `string` | ❌ | Cloudflare Turnstile secret key |
71
+ | `onSignUp` | `(user: UserOutput) => void` | ✅ | Callback when user signs up |
72
+ | `onLogout` | `(body: string) => Promise<void>` | ❌ | Callback on logout |
73
+ | `onError` | `ErrorHandler` | ❌ | Custom error handler |
74
+
75
+ #### Types
76
+
77
+ ```typescript
78
+ interface UserInput {
79
+ email: string;
80
+ password: string;
81
+ [key: string]: unknown;
82
+ }
83
+
84
+ interface UserOutput {
85
+ id: number;
86
+ email: string;
87
+ password: string | null;
88
+ [key: string]: unknown;
89
+ }
90
+ ```
91
+
92
+ ---
93
+
94
+ ### Google Authentication
95
+
96
+ ```typescript
97
+ const googleAuth = initGoogleAuth({
98
+ clientId: process.env.GOOGLE_CLIENT_ID!,
99
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
100
+ authRoute: "/api/auth/google",
101
+ successRedirectURL: "/dashboard",
102
+ errorRedirectURL: "/login?error=google",
103
+ refreshKey: process.env.JWT_REFRESH_KEY!,
104
+ getUserIdFromEmail: async (userInfo) => {
105
+ /* return user ID or null */
106
+ },
107
+ });
108
+
109
+ export const { GET } = googleAuth;
110
+ ```
111
+
112
+ #### Options
113
+
114
+ | Option | Type | Required | Description |
115
+ | -------------------- | ----------------------------------------------------- | -------- | --------------------------------- |
116
+ | `clientId` | `string` | ✅ | Google OAuth client ID |
117
+ | `clientSecret` | `string` | ✅ | Google OAuth client secret |
118
+ | `authRoute` | `string` | ✅ | OAuth callback route |
119
+ | `successRedirectURL` | `string` | ✅ | Redirect URL on success |
120
+ | `errorRedirectURL` | `string` | ✅ | Redirect URL on error |
121
+ | `refreshKey` | `string` | ✅ | JWT refresh key |
122
+ | `getUserIdFromEmail` | `(email: Schema$Userinfo) => Promise<number \| null>` | ✅ | Get user ID from Google user info |
123
+
124
+ ---
125
+
126
+ ### Instagram Authentication
127
+
128
+ ```typescript
129
+ const instagramAuth = initInstagramAuth({
130
+ clientId: process.env.INSTAGRAM_CLIENT_ID!,
131
+ clientSecret: process.env.INSTAGRAM_CLIENT_SECRET!,
132
+ authRoute: "/api/auth/instagram",
133
+ successRedirectURL: "/dashboard",
134
+ errorRedirectURL: "/login?error=instagram",
135
+ refreshKey: process.env.JWT_REFRESH_KEY!,
136
+ onUser: async (data, userId, accessToken) => {
137
+ /* handle Instagram user */
138
+ },
139
+ });
140
+
141
+ export const { GET, getRefreshedAccessToken } = instagramAuth;
142
+ ```
143
+
144
+ #### Options
145
+
146
+ | Option | Type | Required | Description |
147
+ | -------------------- | ------------------------------------------------------------------------------------------- | -------- | --------------------------------- |
148
+ | `clientId` | `string` | ✅ | Instagram app client ID |
149
+ | `clientSecret` | `string` | ✅ | Instagram app client secret |
150
+ | `authRoute` | `string` | ✅ | OAuth callback route |
151
+ | `successRedirectURL` | `string` | ✅ | Redirect URL on success |
152
+ | `errorRedirectURL` | `string` | ✅ | Redirect URL on error |
153
+ | `refreshKey` | `string` | ✅ | JWT refresh key |
154
+ | `onUser` | `(data: InstagramUser, id: number \| null, accessToken: string) => Promise<string \| void>` | ✅ | Callback with Instagram user data |
155
+
156
+ ---
157
+
158
+ ## GraphQL Module
159
+
160
+ ```typescript
161
+ import {
162
+ initGraphQLServer,
163
+ GQLError,
164
+ query,
165
+ field,
166
+ QueryLibrary,
167
+ FieldLibrary,
168
+ } from "naystack/graphql";
169
+ import type { Context, AuthorizedContext } from "naystack/graphql";
170
+ ```
171
+
172
+ ### Initialize GraphQL Server
173
+
174
+ ```typescript
175
+ const { GET, POST } = await initGraphQLServer({
176
+ resolvers: [UserResolver, PostResolver],
177
+ authChecker: ({ context }) => !!context.userId,
178
+ plugins: [], // Optional Apollo plugins
179
+ context: async (req) => ({
180
+ // Custom context builder
181
+ userId: await getUserIdFromRequest(req),
182
+ }),
183
+ });
184
+
185
+ export { GET, POST };
186
+ ```
187
+
188
+ #### Options
189
+
190
+ | Option | Type | Required | Description |
191
+ | ------------- | ------------------------------------ | -------- | -------------------------------- |
192
+ | `resolvers` | `NonEmptyArray<Function>` | ✅ | Array of TypeGraphQL resolvers |
193
+ | `authChecker` | `AuthChecker<any>` | ❌ | Custom auth checker function |
194
+ | `plugins` | `ApolloServerPlugin[]` | ❌ | Additional Apollo Server plugins |
195
+ | `context` | `(req: NextRequest) => Promise<any>` | ❌ | Context builder function |
196
+
197
+ ### Error Handling
198
+
199
+ ```typescript
200
+ import { GQLError } from "naystack/graphql";
201
+
202
+ // Usage in resolvers
203
+ throw GQLError(404, "User not found");
204
+ throw GQLError(403); // "You are not allowed to perform this action"
205
+ throw GQLError(400); // "Please provide all required inputs"
206
+ throw GQLError(); // "Server Error" (500)
207
+ ```
208
+
209
+ ### Query & Field Helpers
210
+
211
+ Build resolvers functionally using `query`, `field`, `QueryLibrary`, and `FieldLibrary`:
212
+
213
+ ```typescript
214
+ import { query, QueryLibrary, field, FieldLibrary } from "naystack/graphql";
215
+
216
+ // Define queries/mutations
217
+ const queries = {
218
+ getUser: query(
219
+ async (ctx, input) => {
220
+ return await db.query.users.findFirst({ where: eq(users.id, input) });
221
+ },
222
+ {
223
+ output: User,
224
+ input: Number,
225
+ authorized: true,
226
+ }
227
+ ),
228
+ createUser: query(
229
+ async (ctx, input) => {
230
+ /* ... */
231
+ },
232
+ {
233
+ output: User,
234
+ input: CreateUserInput,
235
+ mutation: true, // Makes this a mutation instead of query
236
+ }
237
+ ),
238
+ };
239
+
240
+ // Generate resolver class
241
+ const UserResolver = QueryLibrary(queries);
242
+
243
+ // Define field resolvers
244
+ const fields = {
245
+ posts: field(
246
+ async (root, ctx) => {
247
+ return await db.query.posts.findMany({
248
+ where: eq(posts.userId, root.id),
249
+ });
250
+ },
251
+ { output: [Post] }
252
+ ),
253
+ };
254
+
255
+ const UserFieldResolver = FieldLibrary(User, fields);
256
+ ```
257
+
258
+ ### Types
259
+
260
+ ```typescript
261
+ interface Context {
262
+ userId: number | null;
263
+ }
264
+
265
+ interface AuthorizedContext {
266
+ userId: number;
267
+ }
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Client Module
273
+
274
+ ```typescript
275
+ import {
276
+ useVisibility,
277
+ useBreakpoint,
278
+ setupSEO,
279
+ getHandleImageUpload,
280
+ getInstagramAuthorizationURLSetup,
281
+ } from "naystack/client";
282
+ ```
283
+
284
+ ### Hooks
285
+
286
+ #### `useVisibility`
287
+
288
+ Observe element visibility using Intersection Observer.
289
+
290
+ ```typescript
291
+ function Component() {
292
+ const ref = useVisibility(() => {
293
+ console.log("Element is visible!");
294
+ });
295
+
296
+ return <div ref={ref}>Watch me!</div>;
297
+ }
298
+ ```
299
+
300
+ #### `useBreakpoint`
301
+
302
+ React to media query changes.
303
+
304
+ ```typescript
305
+ function Component() {
306
+ const isMobile = useBreakpoint("(max-width: 768px)");
307
+
308
+ return <div>{isMobile ? "Mobile" : "Desktop"}</div>;
309
+ }
310
+ ```
311
+
312
+ ### SEO Helper
313
+
314
+ ```typescript
315
+ const getSEO = setupSEO({
316
+ title: "My App",
317
+ description: "Default description",
318
+ siteName: "MyApp",
319
+ themeColor: "#000000",
320
+ });
321
+
322
+ // In page
323
+ export const metadata = getSEO(
324
+ "Page Title",
325
+ "Page description",
326
+ "/og-image.png"
327
+ );
328
+ ```
329
+
330
+ ### Instagram Authorization URL
331
+
332
+ ```typescript
333
+ const getInstagramAuthURL = getInstagramAuthorizationURLSetup(
334
+ process.env.INSTAGRAM_CLIENT_ID!,
335
+ "https://myapp.com/api/auth/instagram"
336
+ );
337
+
338
+ // Usage
339
+ const authURL = getInstagramAuthURL(userToken);
340
+ ```
341
+
342
+ ### Image Upload Client
343
+
344
+ ```typescript
345
+ const uploadImage = getHandleImageUpload("/api/upload");
346
+
347
+ const result = await uploadImage({
348
+ file: imageFile,
349
+ token: authToken,
350
+ type: "avatar",
351
+ data: { userId: 123 }, // Optional additional data
352
+ sync: true, // Optional: wait for processing
353
+ });
354
+ ```
355
+
356
+ ---
357
+
358
+ ## File Module
359
+
360
+ ```typescript
361
+ import { setupFileUpload } from "naystack/file";
362
+ ```
363
+
364
+ ### Setup File Upload
365
+
366
+ ```typescript
367
+ const fileUpload = setupFileUpload({
368
+ refreshKey: process.env.JWT_REFRESH_KEY!,
369
+ signingKey: process.env.JWT_SIGNING_KEY!,
370
+ region: "us-east-1",
371
+ bucket: "my-bucket",
372
+ awsKey: process.env.AWS_ACCESS_KEY_ID!,
373
+ awsSecret: process.env.AWS_SECRET_ACCESS_KEY!,
374
+ processFile: async ({ url, type, userId, data }) => {
375
+ // Process uploaded file
376
+ return {
377
+ deleteURL: url, // URL to delete if needed
378
+ response: { success: true },
379
+ };
380
+ },
381
+ });
382
+
383
+ // Export route handler
384
+ export const { PUT } = fileUpload;
385
+
386
+ // Server-side utilities
387
+ const { getUploadFileURL, uploadImage, deleteImage, getFileURL, uploadFile } =
388
+ fileUpload;
389
+ ```
390
+
391
+ #### Options
392
+
393
+ | Option | Type | Required | Description |
394
+ | ------------- | ---------- | -------- | ------------------------ |
395
+ | `refreshKey` | `string` | ✅ | JWT refresh key |
396
+ | `signingKey` | `string` | ✅ | JWT signing key |
397
+ | `region` | `string` | ✅ | AWS S3 region |
398
+ | `bucket` | `string` | ✅ | AWS S3 bucket name |
399
+ | `awsKey` | `string` | ✅ | AWS access key ID |
400
+ | `awsSecret` | `string` | ✅ | AWS secret access key |
401
+ | `processFile` | `Function` | ✅ | File processing callback |
402
+
403
+ #### Returned Utilities
404
+
405
+ | Utility | Description |
406
+ | ------------------ | ------------------------------ |
407
+ | `PUT` | Route handler for file uploads |
408
+ | `getUploadFileURL` | Get presigned URL for upload |
409
+ | `uploadImage` | Upload image to S3 |
410
+ | `deleteImage` | Delete image from S3 |
411
+ | `getFileURL` | Get public URL for a file |
412
+ | `uploadFile` | Upload any file to S3 |
413
+
414
+ ---
415
+
416
+ ## Socials Module
417
+
418
+ ```typescript
419
+ import {
420
+ // Instagram
421
+ getInstagramUser,
422
+ getInstagramMedia,
423
+ getInstagramConversations,
424
+ getInstagramConversationsByUser,
425
+ getInstagramConversationByUser,
426
+ getInstagramConversation,
427
+ getInstagramMessage,
428
+ sendInstagramMessage,
429
+ setupInstagramWebhook,
430
+ // Threads
431
+ getThread,
432
+ getThreads,
433
+ getThreadsReplies,
434
+ createThread,
435
+ createThreadsPost,
436
+ setupThreadsWebhook,
437
+ } from "naystack/socials";
438
+ ```
439
+
440
+ ### Instagram API
441
+
442
+ #### Get User Data
443
+
444
+ ```typescript
445
+ const user = await getInstagramUser(accessToken);
446
+ const user = await getInstagramUser(accessToken, "user_id");
447
+ const user = await getInstagramUser(accessToken, "me", [
448
+ "username",
449
+ "followers_count",
450
+ ]);
451
+ ```
452
+
453
+ #### Get Media
454
+
455
+ ```typescript
456
+ const media = await getInstagramMedia(accessToken);
457
+ const media = await getInstagramMedia(
458
+ accessToken,
459
+ ["like_count", "comments_count"],
460
+ 24
461
+ );
462
+ ```
463
+
464
+ #### Conversations
465
+
466
+ ```typescript
467
+ // Get all conversations
468
+ const { data, fetchMore } = await getInstagramConversations(accessToken);
469
+
470
+ // Get conversations by user
471
+ const conversations = await getInstagramConversationsByUser(
472
+ accessToken,
473
+ userId
474
+ );
475
+
476
+ // Get single conversation
477
+ const conversation = await getInstagramConversationByUser(accessToken, userId);
478
+
479
+ // Get conversation with messages
480
+ const { messages, participants, fetchMore } = await getInstagramConversation(
481
+ accessToken,
482
+ conversationId
483
+ );
484
+ ```
485
+
486
+ #### Messages
487
+
488
+ ```typescript
489
+ // Get message details
490
+ const message = await getInstagramMessage(accessToken, messageId);
491
+
492
+ // Send message
493
+ const result = await sendInstagramMessage(accessToken, recipientId, "Hello!");
494
+ ```
495
+
496
+ #### Webhook
497
+
498
+ ```typescript
499
+ const instagramWebhook = setupInstagramWebhook({
500
+ secret: process.env.INSTAGRAM_WEBHOOK_SECRET!,
501
+ callback: async (type, value, id) => {
502
+ // Handle webhook events
503
+ },
504
+ });
505
+
506
+ export const { GET, POST } = instagramWebhook;
507
+ ```
508
+
509
+ ---
510
+
511
+ ### Threads API
512
+
513
+ #### Get Threads
514
+
515
+ ```typescript
516
+ // Get single thread
517
+ const thread = await getThread(accessToken, threadId);
518
+ const thread = await getThread(accessToken, threadId, ["text", "permalink"]);
519
+
520
+ // Get all threads
521
+ const threads = await getThreads(accessToken);
522
+
523
+ // Get thread replies
524
+ const replies = await getThreadsReplies(accessToken, threadId);
525
+ ```
526
+
527
+ #### Create Threads
528
+
529
+ ```typescript
530
+ // Create single post
531
+ const postId = await createThreadsPost(accessToken, "Hello, Threads!");
532
+
533
+ // Reply to a thread
534
+ const replyId = await createThreadsPost(
535
+ accessToken,
536
+ "Reply text",
537
+ parentThreadId
538
+ );
539
+
540
+ // Create threaded posts (carousel)
541
+ const firstPostId = await createThread(accessToken, [
542
+ "First post",
543
+ "Second post in thread",
544
+ "Third post in thread",
545
+ ]);
546
+ ```
547
+
548
+ #### Webhook
549
+
550
+ ```typescript
551
+ const threadsWebhook = setupThreadsWebhook({
552
+ secret: process.env.THREADS_WEBHOOK_SECRET!,
553
+ callback: async (type, value) => {
554
+ // Handle webhook events
555
+ return true; // Return false to indicate failure
556
+ },
557
+ });
558
+
559
+ export const { GET, POST } = threadsWebhook;
560
+ ```
561
+
562
+ ---
563
+
564
+ ## Environment Variables
565
+
566
+ Here's a summary of common environment variables used:
567
+
568
+ ```env
569
+ # JWT Keys
570
+ JWT_SIGNING_KEY=your-signing-key
571
+ JWT_REFRESH_KEY=your-refresh-key
572
+
573
+ # Google OAuth
574
+ GOOGLE_CLIENT_ID=your-google-client-id
575
+ GOOGLE_CLIENT_SECRET=your-google-client-secret
576
+
577
+ # Instagram
578
+ INSTAGRAM_CLIENT_ID=your-instagram-client-id
579
+ INSTAGRAM_CLIENT_SECRET=your-instagram-client-secret
580
+ INSTAGRAM_WEBHOOK_SECRET=your-instagram-webhook-secret
581
+
582
+ # Threads
583
+ THREADS_WEBHOOK_SECRET=your-threads-webhook-secret
584
+
585
+ # AWS S3
586
+ AWS_ACCESS_KEY_ID=your-aws-key
587
+ AWS_SECRET_ACCESS_KEY=your-aws-secret
588
+
589
+ # Cloudflare Turnstile (optional)
590
+ TURNSTILE_SECRET_KEY=your-turnstile-key
591
+
592
+ # App
593
+ NEXT_PUBLIC_BASE_URL=https://your-app.com
594
+ ```
595
+
596
+ ---
597
+
598
+ ## License
599
+
600
+ ISC © [Abhinay Pandey](mailto:abhinaypandey02@gmail.com)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "naystack",
3
- "version": "1.1.16",
4
- "description": "A stack built with tight Next + Drizzle + GraphQL",
3
+ "version": "1.1.17",
4
+ "description": "A stack built with Next + GraphQL + S3 + Auth",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
7
7
  "types": "dist/index.d.ts",
@@ -9,8 +9,7 @@
9
9
  "dist"
10
10
  ],
11
11
  "scripts": {
12
- "build": "rm -rf dist .tsbuildinfo node_modules/.cache && tsup",
13
- "test": "tsx tests/node.ts"
12
+ "build": "tsup"
14
13
  },
15
14
  "exports": {
16
15
  "./auth": {
@@ -100,5 +99,9 @@
100
99
  "tsx": "^4.20.5",
101
100
  "typescript": "^5.9.2",
102
101
  "typescript-eslint": "^8.31.1"
102
+ },
103
+ "repository": {
104
+ "type": "git",
105
+ "url": "https://github.com/abhinaypandey02/naystack.git"
103
106
  }
104
107
  }