@archlast/server 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -407,12 +407,12 @@ export const processImage = action({
|
|
|
407
407
|
Explicit HTTP endpoints with full control over request/response.
|
|
408
408
|
|
|
409
409
|
```ts
|
|
410
|
-
import { http } from "@archlast/server/
|
|
410
|
+
import { http } from "@archlast/server/http/definition";
|
|
411
411
|
|
|
412
412
|
// GET route
|
|
413
413
|
export const health = http.get({
|
|
414
414
|
path: "/health",
|
|
415
|
-
handler: async () => {
|
|
415
|
+
handler: async (ctx) => {
|
|
416
416
|
return new Response("ok", { status: 200 });
|
|
417
417
|
},
|
|
418
418
|
});
|
|
@@ -420,8 +420,8 @@ export const health = http.get({
|
|
|
420
420
|
// POST route with body parsing
|
|
421
421
|
export const createWebhook = http.post({
|
|
422
422
|
path: "/api/webhooks",
|
|
423
|
-
handler: async (ctx
|
|
424
|
-
const body = await
|
|
423
|
+
handler: async (ctx) => {
|
|
424
|
+
const body = await ctx.req.json();
|
|
425
425
|
// Process webhook...
|
|
426
426
|
return Response.json({ received: true });
|
|
427
427
|
},
|
|
@@ -430,8 +430,9 @@ export const createWebhook = http.post({
|
|
|
430
430
|
// Route with path parameters
|
|
431
431
|
export const getUser = http.get({
|
|
432
432
|
path: "/api/users/:id",
|
|
433
|
-
handler: async (ctx
|
|
434
|
-
const
|
|
433
|
+
handler: async (ctx) => {
|
|
434
|
+
const userId = ctx.req.params.id;
|
|
435
|
+
const user = await ctx.server.db.get("users", userId);
|
|
435
436
|
if (!user) {
|
|
436
437
|
return new Response("Not found", { status: 404 });
|
|
437
438
|
}
|
|
@@ -440,41 +441,76 @@ export const getUser = http.get({
|
|
|
440
441
|
});
|
|
441
442
|
|
|
442
443
|
// Other methods
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
export const updateResource = http.put({
|
|
445
|
+
path: "/api/resource/:id",
|
|
446
|
+
handler: async (ctx) => {
|
|
447
|
+
const id = ctx.req.params.id;
|
|
448
|
+
const data = await ctx.req.json();
|
|
449
|
+
// Update resource...
|
|
450
|
+
return Response.json({ success: true });
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
export const patchResource = http.patch({
|
|
455
|
+
path: "/api/resource/:id",
|
|
456
|
+
handler: async (ctx) => { /* ... */ return Response.json({ success: true }); },
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
export const deleteResource = http.delete({
|
|
460
|
+
path: "/api/resource/:id",
|
|
461
|
+
handler: async (ctx) => { /* ... */ return new Response(null, { status: 204 }); },
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
export const optionsResource = http.options({
|
|
465
|
+
path: "/api/resource",
|
|
466
|
+
handler: async (ctx) => { return new Response(null, { status: 204 }); },
|
|
467
|
+
});
|
|
447
468
|
```
|
|
448
469
|
|
|
449
470
|
### Webhooks
|
|
450
471
|
|
|
451
|
-
|
|
472
|
+
Webhook handlers with custom security guards.
|
|
452
473
|
|
|
453
474
|
```ts
|
|
454
|
-
import { webhook } from "@archlast/server/
|
|
475
|
+
import { webhook } from "@archlast/server/webhook/definition";
|
|
476
|
+
import { createSignatureGuard } from "@archlast/server/webhook/guard";
|
|
455
477
|
|
|
478
|
+
// Stripe webhook with signature verification
|
|
456
479
|
export const stripeWebhook = webhook({
|
|
457
480
|
path: "/webhooks/stripe",
|
|
458
|
-
|
|
481
|
+
method: "POST",
|
|
482
|
+
guards: [
|
|
483
|
+
createSignatureGuard({
|
|
484
|
+
secret: process.env.STRIPE_WEBHOOK_SECRET!,
|
|
485
|
+
header: "stripe-signature",
|
|
486
|
+
algorithm: "sha256",
|
|
487
|
+
}),
|
|
488
|
+
],
|
|
459
489
|
handler: async (ctx, event) => {
|
|
460
490
|
switch (event.type) {
|
|
461
491
|
case "checkout.session.completed":
|
|
462
|
-
await
|
|
492
|
+
await ctx.db.insert("orders", { userId: event.data.customer });
|
|
463
493
|
break;
|
|
464
494
|
case "customer.subscription.deleted":
|
|
465
|
-
await
|
|
495
|
+
await ctx.db.update("users", event.data.customer, { subscribed: false });
|
|
466
496
|
break;
|
|
467
497
|
}
|
|
468
498
|
return { received: true };
|
|
469
499
|
},
|
|
470
500
|
});
|
|
471
501
|
|
|
502
|
+
// GitHub webhook with signature verification
|
|
472
503
|
export const githubWebhook = webhook({
|
|
473
504
|
path: "/webhooks/github",
|
|
474
|
-
|
|
475
|
-
|
|
505
|
+
guards: [
|
|
506
|
+
createSignatureGuard({
|
|
507
|
+
secret: process.env.GITHUB_WEBHOOK_SECRET!,
|
|
508
|
+
header: "X-Hub-Signature-256",
|
|
509
|
+
algorithm: "sha256",
|
|
510
|
+
}),
|
|
511
|
+
],
|
|
476
512
|
handler: async (ctx, payload) => {
|
|
477
|
-
|
|
513
|
+
ctx.logger.info("GitHub event received", { action: payload.action });
|
|
478
514
|
return { ok: true };
|
|
479
515
|
},
|
|
480
516
|
});
|
|
@@ -486,22 +522,29 @@ Type-safe RPC procedures with automatic router generation.
|
|
|
486
522
|
|
|
487
523
|
```ts
|
|
488
524
|
import { rpc } from "@archlast/server/functions/definition";
|
|
525
|
+
import { z } from "zod";
|
|
489
526
|
|
|
490
527
|
// RPC query
|
|
491
528
|
export const getTasks = rpc.query({
|
|
492
|
-
args:
|
|
529
|
+
args: z.object({
|
|
530
|
+
completed: z.boolean().optional(),
|
|
531
|
+
}),
|
|
493
532
|
handler: async (ctx, args) => {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
.
|
|
533
|
+
const query = ctx.db.query("tasks");
|
|
534
|
+
if (args.completed !== undefined) {
|
|
535
|
+
query.where({ completed: args.completed });
|
|
536
|
+
}
|
|
537
|
+
return query.findMany();
|
|
497
538
|
},
|
|
498
539
|
});
|
|
499
540
|
|
|
500
541
|
// RPC mutation
|
|
501
542
|
export const createTask = rpc.mutation({
|
|
502
|
-
args:
|
|
543
|
+
args: z.object({
|
|
544
|
+
text: z.string(),
|
|
545
|
+
}),
|
|
503
546
|
handler: async (ctx, args) => {
|
|
504
|
-
return ctx.db.insert("tasks", { text: args.text });
|
|
547
|
+
return ctx.db.insert("tasks", { text: args.text, completed: false });
|
|
505
548
|
},
|
|
506
549
|
});
|
|
507
550
|
```
|
|
@@ -542,15 +585,49 @@ interface ActionCtx extends MutationCtx {
|
|
|
542
585
|
|
|
543
586
|
### AuthContext
|
|
544
587
|
|
|
588
|
+
The auth context is populated from Better-Auth session data:
|
|
589
|
+
|
|
545
590
|
```ts
|
|
546
591
|
interface AuthContext {
|
|
592
|
+
// User ID from Better-Auth session
|
|
547
593
|
userId: string | null;
|
|
594
|
+
|
|
595
|
+
// Session ID from Better-Auth
|
|
548
596
|
sessionId: string | null;
|
|
597
|
+
|
|
598
|
+
// Whether the request is authenticated
|
|
549
599
|
isAuthenticated: boolean;
|
|
550
|
-
|
|
600
|
+
|
|
601
|
+
// Better-Auth user object
|
|
602
|
+
user: {
|
|
603
|
+
id: string;
|
|
604
|
+
email: string;
|
|
605
|
+
emailVerified: boolean;
|
|
606
|
+
name?: string;
|
|
607
|
+
image?: string;
|
|
608
|
+
role?: string; // "user", "admin", "super-admin"
|
|
609
|
+
banned?: boolean;
|
|
610
|
+
createdAt: Date;
|
|
611
|
+
updatedAt: Date;
|
|
612
|
+
} | null;
|
|
613
|
+
|
|
614
|
+
// Tenant/Organization info (if using organizations)
|
|
615
|
+
tenant?: {
|
|
616
|
+
id: string;
|
|
617
|
+
name: string;
|
|
618
|
+
ownerId: string;
|
|
619
|
+
} | null;
|
|
620
|
+
|
|
621
|
+
// Membership info
|
|
622
|
+
membership?: {
|
|
623
|
+
role: string;
|
|
624
|
+
permissions: string[];
|
|
625
|
+
} | null;
|
|
551
626
|
}
|
|
552
627
|
```
|
|
553
628
|
|
|
629
|
+
**Note:** Better-Auth stores users in `system_auth_user` collection with the `id` field (not `_id`). The adapter handles mapping automatically.
|
|
630
|
+
|
|
554
631
|
### Usage Examples
|
|
555
632
|
|
|
556
633
|
```ts
|
|
@@ -596,15 +673,49 @@ export const myAction = action(async (ctx) => {
|
|
|
596
673
|
|
|
597
674
|
## Authentication & Permissions
|
|
598
675
|
|
|
676
|
+
Archlast uses [Better-Auth](https://www.better-auth.com/) for authentication. The server mounts Better-Auth at `/api/auth/*` and includes these plugins:
|
|
677
|
+
|
|
678
|
+
- **Email/Password**: Standard credential authentication via `signIn.email()`
|
|
679
|
+
- **Username**: Username-based sign-in via `signIn.username()`
|
|
680
|
+
- **Anonymous**: Guest user support via `signIn.anonymous()`
|
|
681
|
+
- **Admin**: User management, roles, bans, impersonation
|
|
682
|
+
- **API Key**: Programmatic access with `arch_` prefixed keys
|
|
683
|
+
- **Organization**: Multi-tenancy support
|
|
684
|
+
|
|
685
|
+
### Sign-In Methods
|
|
686
|
+
|
|
687
|
+
```ts
|
|
688
|
+
// Email-based sign-in (web app pattern)
|
|
689
|
+
await authClient.signIn.email({ email: "user@example.com", password: "..." });
|
|
690
|
+
|
|
691
|
+
// Username-based sign-in (dashboard pattern)
|
|
692
|
+
await authClient.signIn.username({ username: "admin", password: "..." });
|
|
693
|
+
|
|
694
|
+
// Anonymous/guest sign-in
|
|
695
|
+
await authClient.signIn.anonymous();
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Better-Auth Configuration
|
|
699
|
+
|
|
700
|
+
Better-Auth is configured in the server with these features:
|
|
701
|
+
|
|
702
|
+
```ts
|
|
703
|
+
// Environment variables for Better-Auth
|
|
704
|
+
BETTER_AUTH_SECRET=your-32-char-secret // Required in production
|
|
705
|
+
APP_URL=https://your-app.com // Base URL for redirects
|
|
706
|
+
ARCHLAST_ALLOWED_ORIGINS=http://localhost:3000 // Trusted origins (CSV)
|
|
707
|
+
```
|
|
708
|
+
|
|
599
709
|
### Auth Modes
|
|
600
710
|
|
|
601
711
|
All functions default to requiring authentication. Override with `auth` option:
|
|
602
712
|
|
|
603
713
|
```ts
|
|
604
|
-
// Required (default) - must be authenticated
|
|
714
|
+
// Required (default) - must be authenticated via Better-Auth
|
|
605
715
|
export const privateQuery = query({
|
|
606
716
|
handler: async (ctx) => {
|
|
607
717
|
// ctx.auth.userId is guaranteed to exist
|
|
718
|
+
// ctx.auth.user contains Better-Auth user object
|
|
608
719
|
},
|
|
609
720
|
});
|
|
610
721
|
|
|
@@ -613,9 +724,9 @@ export const optionalAuth = query({
|
|
|
613
724
|
auth: "optional",
|
|
614
725
|
handler: async (ctx) => {
|
|
615
726
|
if (ctx.auth.isAuthenticated) {
|
|
616
|
-
// Authenticated user
|
|
727
|
+
// Authenticated user via Better-Auth
|
|
617
728
|
} else {
|
|
618
|
-
// Anonymous user
|
|
729
|
+
// Anonymous/guest user
|
|
619
730
|
}
|
|
620
731
|
},
|
|
621
732
|
});
|
|
@@ -666,13 +777,20 @@ export const scheduleCleanup = mutation(async (ctx) => {
|
|
|
666
777
|
payload: { olderThanDays: 30 },
|
|
667
778
|
});
|
|
668
779
|
|
|
669
|
-
// Run
|
|
780
|
+
// Run every 5 minutes
|
|
670
781
|
await ctx.scheduler.schedule({
|
|
671
782
|
name: "sendReminder",
|
|
672
|
-
preset: SchedulePreset.
|
|
783
|
+
preset: SchedulePreset.Every5Minutes,
|
|
673
784
|
payload: { userId: ctx.auth.userId },
|
|
674
785
|
});
|
|
675
786
|
|
|
787
|
+
// Run every 10 minutes
|
|
788
|
+
await ctx.scheduler.schedule({
|
|
789
|
+
name: "syncData",
|
|
790
|
+
preset: SchedulePreset.Every10Minutes,
|
|
791
|
+
payload: {},
|
|
792
|
+
});
|
|
793
|
+
|
|
676
794
|
// Recurring with cron
|
|
677
795
|
await ctx.scheduler.schedule({
|
|
678
796
|
name: "dailyReport",
|
|
@@ -684,23 +802,30 @@ export const scheduleCleanup = mutation(async (ctx) => {
|
|
|
684
802
|
|
|
685
803
|
### Schedule Presets
|
|
686
804
|
|
|
687
|
-
| Preset | Description |
|
|
688
|
-
|
|
689
|
-
| `RunNow` | Execute immediately |
|
|
690
|
-
| `EveryMinute` | Run every minute |
|
|
691
|
-
| `Every5Minutes` | Run every 5 minutes |
|
|
692
|
-
| `
|
|
693
|
-
| `Every30Minutes` | Run every 30 minutes |
|
|
694
|
-
| `Hourly` | Run every hour |
|
|
695
|
-
| `
|
|
696
|
-
| `
|
|
697
|
-
| `DailyMidnight` | Daily at midnight UTC |
|
|
698
|
-
| `
|
|
699
|
-
| `
|
|
700
|
-
| `
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
805
|
+
| Preset | Cron Expression | Description |
|
|
806
|
+
|--------|-----------------|-------------|
|
|
807
|
+
| `RunNow` | (immediate) | Execute immediately |
|
|
808
|
+
| `EveryMinute` | `* * * * *` | Run every minute |
|
|
809
|
+
| `Every5Minutes` | `*/5 * * * *` | Run every 5 minutes |
|
|
810
|
+
| `Every10Minutes` | `*/10 * * * *` | Run every 10 minutes |
|
|
811
|
+
| `Every30Minutes` | `*/30 * * * *` | Run every 30 minutes |
|
|
812
|
+
| `Hourly` | `0 * * * *` | Run every hour |
|
|
813
|
+
| `Every8Hours` | `0 */8 * * *` | Run every 8 hours |
|
|
814
|
+
| `Every16Hours` | `0 */16 * * *` | Run every 16 hours |
|
|
815
|
+
| `DailyMidnight` | `0 0 * * *` | Daily at midnight UTC |
|
|
816
|
+
| `Weekly` | `0 0 * * 0` | Weekly on Sunday midnight |
|
|
817
|
+
| `Monthly` | `0 0 1 * *` | Monthly on the 1st |
|
|
818
|
+
| `Quarterly` | `0 0 1 */3 *` | Quarterly (every 3 months) |
|
|
819
|
+
|
|
820
|
+
You can also use custom cron expressions:
|
|
821
|
+
|
|
822
|
+
```ts
|
|
823
|
+
await ctx.scheduler.schedule({
|
|
824
|
+
name: "customJob",
|
|
825
|
+
cron: "0 9 * * 1-5", // Weekdays at 9 AM
|
|
826
|
+
payload: {},
|
|
827
|
+
});
|
|
828
|
+
```
|
|
704
829
|
|
|
705
830
|
---
|
|
706
831
|
|
|
@@ -740,14 +865,28 @@ Import only what you need:
|
|
|
740
865
|
|--------|-------------|
|
|
741
866
|
| `@archlast/server/schema/definition` | `defineSchema`, `defineTable`, relationship helpers |
|
|
742
867
|
| `@archlast/server/schema/validators` | `v` validator object |
|
|
743
|
-
| `@archlast/server/functions/definition` | `query`, `mutation`, `action`, `
|
|
868
|
+
| `@archlast/server/functions/definition` | `query`, `mutation`, `action`, `rpc` |
|
|
744
869
|
| `@archlast/server/functions/types` | Context types, function types |
|
|
745
|
-
| `@archlast/server/http` |
|
|
746
|
-
| `@archlast/server/
|
|
747
|
-
| `@archlast/server/
|
|
870
|
+
| `@archlast/server/http/definition` | `http` route builder |
|
|
871
|
+
| `@archlast/server/http/router` | HTTP router utilities |
|
|
872
|
+
| `@archlast/server/http` | HTTP utilities |
|
|
873
|
+
| `@archlast/server/webhook/definition` | `webhook` function builder |
|
|
874
|
+
| `@archlast/server/webhook/guard` | Webhook guard utilities |
|
|
875
|
+
| `@archlast/server/webhook/verifier` | Signature verification |
|
|
876
|
+
| `@archlast/server/webhook` | Webhook utilities |
|
|
877
|
+
| `@archlast/server/jobs` | `SchedulePreset`, `Scheduler`, `JobQueue` |
|
|
878
|
+
| `@archlast/server/jobs/scheduler` | Scheduler class and presets |
|
|
879
|
+
| `@archlast/server/jobs/queue` | Job queue implementation |
|
|
748
880
|
| `@archlast/server/storage/types` | Storage type definitions |
|
|
749
|
-
| `@archlast/server/context` |
|
|
750
|
-
| `@archlast/server/
|
|
881
|
+
| `@archlast/server/context` | Server context exports |
|
|
882
|
+
| `@archlast/server/db/interfaces` | Database interface types |
|
|
883
|
+
| `@archlast/server/auth/interfaces` | Auth interface types |
|
|
884
|
+
| `@archlast/server/repository/interfaces` | Repository interface types |
|
|
885
|
+
| `@archlast/server/repository/factory` | Repository factory |
|
|
886
|
+
| `@archlast/server/di/decorators` | DI decorators |
|
|
887
|
+
| `@archlast/server/di/container` | DI container |
|
|
888
|
+
| `@archlast/server/logging/logger` | Logger service |
|
|
889
|
+
| `@archlast/server/docker` | Docker utilities |
|
|
751
890
|
|
|
752
891
|
---
|
|
753
892
|
|
|
@@ -755,6 +894,16 @@ Import only what you need:
|
|
|
755
894
|
|
|
756
895
|
Key variables used by the server runtime:
|
|
757
896
|
|
|
897
|
+
### Better-Auth Configuration
|
|
898
|
+
|
|
899
|
+
| Variable | Default | Description |
|
|
900
|
+
|----------|---------|-------------|
|
|
901
|
+
| `BETTER_AUTH_SECRET` | - | **Required in production.** Secret for signing tokens |
|
|
902
|
+
| `APP_URL` | `http://localhost:4000` | Base URL for auth redirects |
|
|
903
|
+
| `BETTER_AUTH_DEBUG` | `false` | Enable debug logging |
|
|
904
|
+
|
|
905
|
+
### Server Configuration
|
|
906
|
+
|
|
758
907
|
| Variable | Default | Description |
|
|
759
908
|
|----------|---------|-------------|
|
|
760
909
|
| `PORT` | `4000` | HTTP server port |
|
|
@@ -762,6 +911,11 @@ Key variables used by the server runtime:
|
|
|
762
911
|
| `ARCHLAST_DB_ROOT` | `./data` | Database file directory |
|
|
763
912
|
| `ARCHLAST_ALLOWED_ORIGINS` | - | CORS allowed origins (CSV) |
|
|
764
913
|
| `ARCHLAST_CORS_ALLOW_CREDENTIALS` | `false` | Allow credentials in CORS |
|
|
914
|
+
|
|
915
|
+
### Storage Configuration
|
|
916
|
+
|
|
917
|
+
| Variable | Default | Description |
|
|
918
|
+
|----------|---------|-------------|
|
|
765
919
|
| `STORAGE_ROOT` | `./storage` | Local file storage directory |
|
|
766
920
|
| `STORAGE_SIGNING_SECRET` | - | Secret for signed URLs |
|
|
767
921
|
| `S3_ENABLED` | `false` | Enable S3 storage |
|
|
@@ -769,7 +923,11 @@ Key variables used by the server runtime:
|
|
|
769
923
|
| `S3_REGION` | `us-east-1` | S3 region |
|
|
770
924
|
| `AWS_ACCESS_KEY_ID` | - | AWS access key |
|
|
771
925
|
| `AWS_SECRET_ACCESS_KEY` | - | AWS secret key |
|
|
772
|
-
|
|
926
|
+
|
|
927
|
+
### Dashboard & Store Configuration
|
|
928
|
+
|
|
929
|
+
| Variable | Default | Description |
|
|
930
|
+
|----------|---------|-------------|
|
|
773
931
|
| `ARCHLAST_DASHBOARD_DIR` | - | Dashboard static files path |
|
|
774
932
|
| `ARCHLAST_DASHBOARD_URL` | - | Dashboard proxy URL |
|
|
775
933
|
| `ARCHLAST_STORE_PORT` | `7001` | Document store port |
|
|
@@ -792,9 +950,9 @@ The CLI uses these templates when running `archlast start`.
|
|
|
792
950
|
|
|
793
951
|
---
|
|
794
952
|
|
|
795
|
-
##
|
|
953
|
+
## Keywords
|
|
796
954
|
|
|
797
|
-
|
|
955
|
+
archlast, server, backend, reactive, real-time, websocket, typescript, api, baas
|
|
798
956
|
|
|
799
957
|
## License
|
|
800
958
|
|