honertia 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +610 -0
  3. package/dist/auth.d.ts +10 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +11 -0
  6. package/dist/effect/action.d.ts +107 -0
  7. package/dist/effect/action.d.ts.map +1 -0
  8. package/dist/effect/action.js +150 -0
  9. package/dist/effect/auth.d.ts +94 -0
  10. package/dist/effect/auth.d.ts.map +1 -0
  11. package/dist/effect/auth.js +204 -0
  12. package/dist/effect/bridge.d.ts +40 -0
  13. package/dist/effect/bridge.d.ts.map +1 -0
  14. package/dist/effect/bridge.js +103 -0
  15. package/dist/effect/errors.d.ts +78 -0
  16. package/dist/effect/errors.d.ts.map +1 -0
  17. package/dist/effect/errors.js +37 -0
  18. package/dist/effect/handler.d.ts +25 -0
  19. package/dist/effect/handler.d.ts.map +1 -0
  20. package/dist/effect/handler.js +120 -0
  21. package/dist/effect/index.d.ts +16 -0
  22. package/dist/effect/index.d.ts.map +1 -0
  23. package/dist/effect/index.js +25 -0
  24. package/dist/effect/responses.d.ts +73 -0
  25. package/dist/effect/responses.d.ts.map +1 -0
  26. package/dist/effect/responses.js +104 -0
  27. package/dist/effect/routing.d.ts +90 -0
  28. package/dist/effect/routing.d.ts.map +1 -0
  29. package/dist/effect/routing.js +124 -0
  30. package/dist/effect/schema.d.ts +263 -0
  31. package/dist/effect/schema.d.ts.map +1 -0
  32. package/dist/effect/schema.js +586 -0
  33. package/dist/effect/services.d.ts +85 -0
  34. package/dist/effect/services.d.ts.map +1 -0
  35. package/dist/effect/services.js +24 -0
  36. package/dist/effect/validation.d.ts +38 -0
  37. package/dist/effect/validation.d.ts.map +1 -0
  38. package/dist/effect/validation.js +69 -0
  39. package/dist/helpers.d.ts +65 -0
  40. package/dist/helpers.d.ts.map +1 -0
  41. package/dist/helpers.js +116 -0
  42. package/dist/index.d.ts +14 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +26 -0
  45. package/dist/middleware.d.ts +14 -0
  46. package/dist/middleware.d.ts.map +1 -0
  47. package/dist/middleware.js +113 -0
  48. package/dist/react.d.ts +17 -0
  49. package/dist/react.d.ts.map +1 -0
  50. package/dist/react.js +4 -0
  51. package/dist/schema.d.ts +9 -0
  52. package/dist/schema.d.ts.map +1 -0
  53. package/dist/schema.js +34 -0
  54. package/dist/setup.d.ts +113 -0
  55. package/dist/setup.d.ts.map +1 -0
  56. package/dist/setup.js +96 -0
  57. package/dist/test-utils.d.ts +105 -0
  58. package/dist/test-utils.d.ts.map +1 -0
  59. package/dist/test-utils.js +210 -0
  60. package/dist/types.d.ts +37 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/dist/types.js +11 -0
  63. package/package.json +71 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Effect Action Factories
3
+ *
4
+ * Pure function factories for creating Effect-based request handlers.
5
+ */
6
+ import { Effect, Schema as S } from 'effect';
7
+ import { DatabaseService, AuthUserService, RequestService, type AuthUser } from './services.js';
8
+ import { ValidationError, UnauthorizedError } from './errors.js';
9
+ import { Redirect } from './errors.js';
10
+ /**
11
+ * Create an Effect action with schema validation.
12
+ *
13
+ * @example
14
+ * const createProject = effectAction(
15
+ * S.Struct({ name: requiredString }),
16
+ * (input) => Effect.gen(function* () {
17
+ * const db = yield* DatabaseService
18
+ * yield* Effect.tryPromise(() => db.insert(projects).values(input))
19
+ * return new Redirect({ url: '/projects', status: 303 })
20
+ * })
21
+ * )
22
+ */
23
+ export declare function effectAction<A, I, R, E>(schema: S.Schema<A, I>, handler: (input: A) => Effect.Effect<Response | Redirect, E, R>, options?: {
24
+ errorComponent?: string;
25
+ messages?: Record<string, string>;
26
+ attributes?: Record<string, string>;
27
+ }): Effect.Effect<Response | Redirect, E | ValidationError, R | RequestService>;
28
+ /**
29
+ * Create an Effect action that requires authentication and database access.
30
+ *
31
+ * @example
32
+ * const createProject = dbAction(
33
+ * S.Struct({ name: requiredString }),
34
+ * (input, { db, user }) => Effect.gen(function* () {
35
+ * yield* Effect.tryPromise(() =>
36
+ * db.insert(projects).values({ ...input, userId: user.user.id })
37
+ * )
38
+ * return new Redirect({ url: '/projects', status: 303 })
39
+ * })
40
+ * )
41
+ */
42
+ export declare function dbAction<A, I, E>(schema: S.Schema<A, I>, handler: (input: A, deps: {
43
+ db: unknown;
44
+ user: AuthUser;
45
+ }) => Effect.Effect<Response | Redirect, E, never>, options?: {
46
+ errorComponent?: string;
47
+ messages?: Record<string, string>;
48
+ attributes?: Record<string, string>;
49
+ }): Effect.Effect<Response | Redirect, E | ValidationError | UnauthorizedError, RequestService | DatabaseService | AuthUserService>;
50
+ /**
51
+ * Create an Effect action that requires authentication.
52
+ *
53
+ * @example
54
+ * const showDashboard = authAction(() => Effect.gen(function* () {
55
+ * const user = yield* AuthUserService
56
+ * const honertia = yield* HonertiaService
57
+ * return yield* Effect.tryPromise(() => honertia.render('Dashboard', { user: user.user }))
58
+ * }))
59
+ */
60
+ export declare function authAction<R, E>(handler: (user: AuthUser) => Effect.Effect<Response | Redirect, E, R>): Effect.Effect<Response | Redirect, E | UnauthorizedError, R | AuthUserService>;
61
+ /**
62
+ * Create a simple Effect action without validation.
63
+ *
64
+ * @example
65
+ * const listProjects = simpleAction(() => Effect.gen(function* () {
66
+ * const db = yield* DatabaseService
67
+ * const user = yield* AuthUserService
68
+ * const projects = yield* Effect.tryPromise(() => db.query.projects.findMany())
69
+ * const honertia = yield* HonertiaService
70
+ * return yield* Effect.tryPromise(() => honertia.render('Projects', { projects }))
71
+ * }))
72
+ */
73
+ export declare function simpleAction<R, E>(handler: () => Effect.Effect<Response | Redirect, E, R>): Effect.Effect<Response | Redirect, E, R>;
74
+ /**
75
+ * Inject additional data into the validated input.
76
+ *
77
+ * @example
78
+ * const createProject = effectAction(
79
+ * S.Struct({ name: requiredString }),
80
+ * (input) => pipe(
81
+ * injectUser(input),
82
+ * Effect.flatMap(({ name, userId }) =>
83
+ * Effect.tryPromise(() => db.insert(projects).values({ name, userId }))
84
+ * )
85
+ * )
86
+ * )
87
+ */
88
+ export declare function injectUser<T extends Record<string, unknown>>(input: T): Effect.Effect<T & {
89
+ userId: string;
90
+ }, UnauthorizedError, AuthUserService>;
91
+ /**
92
+ * Run a database operation wrapped in Effect.
93
+ */
94
+ export declare function dbOperation<T>(operation: (db: unknown) => Promise<T>): Effect.Effect<T, Error, DatabaseService>;
95
+ /**
96
+ * Prepare validation data by transforming it before validation.
97
+ */
98
+ export declare function prepareData<T extends Record<string, unknown>>(transform: (data: Record<string, unknown>) => T | Promise<T>): Effect.Effect<T, never, RequestService>;
99
+ /**
100
+ * Create an action with custom data preparation.
101
+ */
102
+ export declare function preparedAction<A, I, R, E>(schema: S.Schema<A, I>, prepare: (data: Record<string, unknown>) => Record<string, unknown> | Promise<Record<string, unknown>>, handler: (input: A) => Effect.Effect<Response | Redirect, E, R>, options?: {
103
+ errorComponent?: string;
104
+ messages?: Record<string, string>;
105
+ attributes?: Record<string, string>;
106
+ }): Effect.Effect<Response | Redirect, E | ValidationError, R | RequestService>;
107
+ //# sourceMappingURL=action.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../src/effect/action.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Effect Action Factories
3
+ *
4
+ * Pure function factories for creating Effect-based request handlers.
5
+ */
6
+ import { Effect } from 'effect';
7
+ import { DatabaseService, AuthUserService, } from './services.js';
8
+ import { validateRequest, getValidationData, validate } from './validation.js';
9
+ /**
10
+ * Create an Effect action with schema validation.
11
+ *
12
+ * @example
13
+ * const createProject = effectAction(
14
+ * S.Struct({ name: requiredString }),
15
+ * (input) => Effect.gen(function* () {
16
+ * const db = yield* DatabaseService
17
+ * yield* Effect.tryPromise(() => db.insert(projects).values(input))
18
+ * return new Redirect({ url: '/projects', status: 303 })
19
+ * })
20
+ * )
21
+ */
22
+ export function effectAction(schema, handler, options) {
23
+ return Effect.gen(function* () {
24
+ const input = yield* validateRequest(schema, {
25
+ errorComponent: options?.errorComponent,
26
+ messages: options?.messages,
27
+ attributes: options?.attributes,
28
+ });
29
+ return yield* handler(input);
30
+ });
31
+ }
32
+ /**
33
+ * Create an Effect action that requires authentication and database access.
34
+ *
35
+ * @example
36
+ * const createProject = dbAction(
37
+ * S.Struct({ name: requiredString }),
38
+ * (input, { db, user }) => Effect.gen(function* () {
39
+ * yield* Effect.tryPromise(() =>
40
+ * db.insert(projects).values({ ...input, userId: user.user.id })
41
+ * )
42
+ * return new Redirect({ url: '/projects', status: 303 })
43
+ * })
44
+ * )
45
+ */
46
+ export function dbAction(schema, handler, options) {
47
+ return Effect.gen(function* () {
48
+ const db = yield* DatabaseService;
49
+ const user = yield* AuthUserService;
50
+ const input = yield* validateRequest(schema, {
51
+ errorComponent: options?.errorComponent,
52
+ messages: options?.messages,
53
+ attributes: options?.attributes,
54
+ });
55
+ return yield* handler(input, { db, user });
56
+ });
57
+ }
58
+ /**
59
+ * Create an Effect action that requires authentication.
60
+ *
61
+ * @example
62
+ * const showDashboard = authAction(() => Effect.gen(function* () {
63
+ * const user = yield* AuthUserService
64
+ * const honertia = yield* HonertiaService
65
+ * return yield* Effect.tryPromise(() => honertia.render('Dashboard', { user: user.user }))
66
+ * }))
67
+ */
68
+ export function authAction(handler) {
69
+ return Effect.gen(function* () {
70
+ const user = yield* AuthUserService;
71
+ return yield* handler(user);
72
+ });
73
+ }
74
+ /**
75
+ * Create a simple Effect action without validation.
76
+ *
77
+ * @example
78
+ * const listProjects = simpleAction(() => Effect.gen(function* () {
79
+ * const db = yield* DatabaseService
80
+ * const user = yield* AuthUserService
81
+ * const projects = yield* Effect.tryPromise(() => db.query.projects.findMany())
82
+ * const honertia = yield* HonertiaService
83
+ * return yield* Effect.tryPromise(() => honertia.render('Projects', { projects }))
84
+ * }))
85
+ */
86
+ export function simpleAction(handler) {
87
+ return handler();
88
+ }
89
+ /**
90
+ * Inject additional data into the validated input.
91
+ *
92
+ * @example
93
+ * const createProject = effectAction(
94
+ * S.Struct({ name: requiredString }),
95
+ * (input) => pipe(
96
+ * injectUser(input),
97
+ * Effect.flatMap(({ name, userId }) =>
98
+ * Effect.tryPromise(() => db.insert(projects).values({ name, userId }))
99
+ * )
100
+ * )
101
+ * )
102
+ */
103
+ export function injectUser(input) {
104
+ return Effect.gen(function* () {
105
+ const authUser = yield* AuthUserService;
106
+ return { ...input, userId: authUser.user.id };
107
+ });
108
+ }
109
+ /**
110
+ * Run a database operation wrapped in Effect.
111
+ */
112
+ export function dbOperation(operation) {
113
+ return Effect.gen(function* () {
114
+ const db = yield* DatabaseService;
115
+ return yield* Effect.tryPromise({
116
+ try: () => operation(db),
117
+ catch: (error) => error instanceof Error ? error : new Error(String(error)),
118
+ });
119
+ });
120
+ }
121
+ /**
122
+ * Prepare validation data by transforming it before validation.
123
+ */
124
+ export function prepareData(transform) {
125
+ return Effect.gen(function* () {
126
+ const data = yield* getValidationData;
127
+ return yield* Effect.tryPromise({
128
+ try: () => Promise.resolve(transform(data)),
129
+ catch: () => new Error('Transform failed'),
130
+ }).pipe(Effect.catchAll(() => Effect.succeed(data)));
131
+ });
132
+ }
133
+ /**
134
+ * Create an action with custom data preparation.
135
+ */
136
+ export function preparedAction(schema, prepare, handler, options) {
137
+ return Effect.gen(function* () {
138
+ const rawData = yield* getValidationData;
139
+ const preparedData = yield* Effect.tryPromise({
140
+ try: () => Promise.resolve(prepare(rawData)),
141
+ catch: () => new Error('Prepare failed'),
142
+ }).pipe(Effect.catchAll(() => Effect.succeed(rawData)));
143
+ const input = yield* validate(schema, {
144
+ errorComponent: options?.errorComponent,
145
+ messages: options?.messages,
146
+ attributes: options?.attributes,
147
+ })(preparedData);
148
+ return yield* handler(input);
149
+ });
150
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Effect Auth Layers and Helpers
3
+ *
4
+ * Authentication and authorization via Effect Layers.
5
+ */
6
+ import { Effect, Layer } from 'effect';
7
+ import type { Hono, MiddlewareHandler, Env } from 'hono';
8
+ import { AuthUserService, HonertiaService, type AuthUser } from './services.js';
9
+ import { UnauthorizedError } from './errors.js';
10
+ /**
11
+ * Layer that requires an authenticated user.
12
+ * Fails with UnauthorizedError if no user is present.
13
+ *
14
+ * @example
15
+ * effectRoutes(app)
16
+ * .provide(RequireAuthLayer)
17
+ * .get('/dashboard', showDashboard)
18
+ */
19
+ export declare const RequireAuthLayer: Layer.Layer<AuthUserService, UnauthorizedError, never>;
20
+ /**
21
+ * Layer that requires no authenticated user (guest only).
22
+ * Fails if a user is present, succeeds (as a no-op) if no user.
23
+ *
24
+ * @example
25
+ * effectRoutes(app)
26
+ * .provide(RequireGuestLayer)
27
+ * .get('/login', showLogin)
28
+ */
29
+ export declare const RequireGuestLayer: Layer.Layer<never, UnauthorizedError, never>;
30
+ /**
31
+ * Check if user is authenticated without failing.
32
+ */
33
+ export declare const isAuthenticated: Effect.Effect<boolean, never, never>;
34
+ /**
35
+ * Get the current user if authenticated.
36
+ */
37
+ export declare const currentUser: Effect.Effect<AuthUser | null, never, never>;
38
+ /**
39
+ * Require authentication or redirect.
40
+ */
41
+ export declare const requireAuth: (redirectTo?: string) => Effect.Effect<AuthUser, UnauthorizedError, never>;
42
+ /**
43
+ * Require guest status or redirect.
44
+ */
45
+ export declare const requireGuest: (redirectTo?: string) => Effect.Effect<void, UnauthorizedError, never>;
46
+ /**
47
+ * Share auth state with Honertia.
48
+ */
49
+ export declare const shareAuth: Effect.Effect<void, never, HonertiaService>;
50
+ /**
51
+ * Middleware version of shareAuth for use with app.use().
52
+ */
53
+ export declare function shareAuthMiddleware<E extends Env>(): MiddlewareHandler<E>;
54
+ /**
55
+ * Configuration for auth routes.
56
+ */
57
+ export interface AuthRoutesConfig<E extends Env> {
58
+ loginPath?: string;
59
+ registerPath?: string;
60
+ logoutPath?: string;
61
+ apiPath?: string;
62
+ logoutRedirect?: string;
63
+ loginRedirect?: string;
64
+ loginComponent?: string;
65
+ registerComponent?: string;
66
+ sessionCookie?: string;
67
+ /**
68
+ * CORS configuration for auth API routes.
69
+ * If provided, adds CORS headers to `/api/auth/*` routes.
70
+ */
71
+ cors?: {
72
+ origin: string | string[] | ((origin: string) => string | undefined | null);
73
+ credentials?: boolean;
74
+ };
75
+ }
76
+ /**
77
+ * Register standard auth routes.
78
+ *
79
+ * @example
80
+ * effectAuthRoutes(app, {
81
+ * loginComponent: 'Auth/Login',
82
+ * registerComponent: 'Auth/Register',
83
+ * })
84
+ */
85
+ export declare function effectAuthRoutes<E extends Env>(app: Hono<E>, config?: AuthRoutesConfig<E>): void;
86
+ /**
87
+ * Middleware to load the authenticated user.
88
+ * This should be used early in the middleware chain.
89
+ */
90
+ export declare function loadUser<E extends Env>(config?: {
91
+ userKey?: string;
92
+ sessionCookie?: string;
93
+ }): MiddlewareHandler<E>;
94
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/effect/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAA;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AACxD,OAAO,EAAE,eAAe,EAAe,eAAe,EAAkB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC5G,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAI/C;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,wDAiB5B,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,8CAe7B,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CACM,CAAA;AAEvE;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAGlE,CAAA;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW,GACtB,mBAAqB,KACpB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,CAQhD,CAAA;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY,GACvB,mBAAgB,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,iBAAiB,EAAE,KAAK,CAQ5C,CAAA;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,eAAe,CAK9D,CAAA;AAEJ;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,GAAG,KAAK,iBAAiB,CAAC,CAAC,CAAC,CASzE;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,GAAG;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,CAAA;QAC3E,WAAW,CAAC,EAAE,OAAO,CAAA;KACtB,CAAA;CACF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,GAAG,EAC5C,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,GAAE,gBAAgB,CAAC,CAAC,CAAM,GAC/B,IAAI,CAoGN;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,GAAG,EACpC,MAAM,GAAE;IACN,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;CAClB,GACL,iBAAiB,CAAC,CAAC,CAAC,CAwBtB"}
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Effect Auth Layers and Helpers
3
+ *
4
+ * Authentication and authorization via Effect Layers.
5
+ */
6
+ import { Effect, Layer, Option } from 'effect';
7
+ import { AuthUserService, AuthService, HonertiaService, RequestService } from './services.js';
8
+ import { UnauthorizedError } from './errors.js';
9
+ import { effectRoutes } from './routing.js';
10
+ import { render } from './responses.js';
11
+ /**
12
+ * Layer that requires an authenticated user.
13
+ * Fails with UnauthorizedError if no user is present.
14
+ *
15
+ * @example
16
+ * effectRoutes(app)
17
+ * .provide(RequireAuthLayer)
18
+ * .get('/dashboard', showDashboard)
19
+ */
20
+ export const RequireAuthLayer = Layer.effect(AuthUserService, Effect.gen(function* () {
21
+ // Try to get existing AuthUserService
22
+ const maybeUser = yield* Effect.serviceOption(AuthUserService);
23
+ if (Option.isNone(maybeUser)) {
24
+ return yield* Effect.fail(new UnauthorizedError({
25
+ message: 'Authentication required',
26
+ redirectTo: '/login',
27
+ }));
28
+ }
29
+ return maybeUser.value;
30
+ }));
31
+ /**
32
+ * Layer that requires no authenticated user (guest only).
33
+ * Fails if a user is present, succeeds (as a no-op) if no user.
34
+ *
35
+ * @example
36
+ * effectRoutes(app)
37
+ * .provide(RequireGuestLayer)
38
+ * .get('/login', showLogin)
39
+ */
40
+ export const RequireGuestLayer = Layer.effectDiscard(Effect.gen(function* () {
41
+ const maybeUser = yield* Effect.serviceOption(AuthUserService);
42
+ if (Option.isSome(maybeUser)) {
43
+ return yield* Effect.fail(new UnauthorizedError({
44
+ message: 'Already authenticated',
45
+ redirectTo: '/',
46
+ }));
47
+ }
48
+ // Guest confirmed - no user present, succeed silently
49
+ }));
50
+ /**
51
+ * Check if user is authenticated without failing.
52
+ */
53
+ export const isAuthenticated = Effect.serviceOption(AuthUserService).pipe(Effect.map(Option.isSome));
54
+ /**
55
+ * Get the current user if authenticated.
56
+ */
57
+ export const currentUser = Effect.serviceOption(AuthUserService).pipe(Effect.map((option) => (Option.isSome(option) ? option.value : null)));
58
+ /**
59
+ * Require authentication or redirect.
60
+ */
61
+ export const requireAuth = (redirectTo = '/login') => Effect.serviceOption(AuthUserService).pipe(Effect.flatMap((option) => {
62
+ if (Option.isNone(option)) {
63
+ return Effect.fail(new UnauthorizedError({ message: 'Unauthenticated', redirectTo }));
64
+ }
65
+ return Effect.succeed(option.value);
66
+ }));
67
+ /**
68
+ * Require guest status or redirect.
69
+ */
70
+ export const requireGuest = (redirectTo = '/') => Effect.serviceOption(AuthUserService).pipe(Effect.flatMap((option) => {
71
+ if (Option.isSome(option)) {
72
+ return Effect.fail(new UnauthorizedError({ message: 'Already authenticated', redirectTo }));
73
+ }
74
+ return Effect.void;
75
+ }));
76
+ /**
77
+ * Share auth state with Honertia.
78
+ */
79
+ export const shareAuth = Effect.gen(function* () {
80
+ const honertia = yield* HonertiaService;
81
+ const user = yield* currentUser;
82
+ honertia.share('auth', { user: user?.user ?? null });
83
+ });
84
+ /**
85
+ * Middleware version of shareAuth for use with app.use().
86
+ */
87
+ export function shareAuthMiddleware() {
88
+ return async (c, next) => {
89
+ const honertia = c.var?.honertia;
90
+ const authUser = c.var?.authUser;
91
+ if (honertia) {
92
+ honertia.share('auth', { user: authUser?.user ?? null });
93
+ }
94
+ await next();
95
+ };
96
+ }
97
+ /**
98
+ * Register standard auth routes.
99
+ *
100
+ * @example
101
+ * effectAuthRoutes(app, {
102
+ * loginComponent: 'Auth/Login',
103
+ * registerComponent: 'Auth/Register',
104
+ * })
105
+ */
106
+ export function effectAuthRoutes(app, config = {}) {
107
+ const { loginPath = '/login', registerPath = '/register', logoutPath = '/logout', apiPath = '/api/auth', logoutRedirect = '/login', loginComponent = 'Auth/Login', registerComponent = 'Auth/Register', } = config;
108
+ const routes = effectRoutes(app);
109
+ // Login page (guest only)
110
+ routes.get(loginPath, Effect.gen(function* () {
111
+ yield* requireGuest(loginPath === '/login' ? '/' : loginPath);
112
+ return yield* render(loginComponent);
113
+ }));
114
+ // Register page (guest only)
115
+ routes.get(registerPath, Effect.gen(function* () {
116
+ yield* requireGuest(registerPath === '/register' ? '/' : registerPath);
117
+ return yield* render(registerComponent);
118
+ }));
119
+ // Logout (POST)
120
+ routes.post(logoutPath, Effect.gen(function* () {
121
+ const auth = yield* AuthService;
122
+ const request = yield* RequestService;
123
+ // Revoke session server-side
124
+ yield* Effect.tryPromise(() => auth.api.signOut({
125
+ headers: request.headers,
126
+ }));
127
+ // Clear cookie and redirect
128
+ const sessionCookie = config.sessionCookie ?? 'better-auth.session_token';
129
+ return new Response(null, {
130
+ status: 303,
131
+ headers: {
132
+ 'Location': logoutRedirect,
133
+ 'Set-Cookie': `${sessionCookie}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=Lax`,
134
+ },
135
+ });
136
+ }));
137
+ // Better-auth API handler (handles sign-in, sign-up, etc.)
138
+ // Apply CORS if configured
139
+ if (config.cors) {
140
+ const corsConfig = config.cors;
141
+ app.use(`${apiPath}/*`, async (c, next) => {
142
+ const origin = c.req.header('Origin');
143
+ // Determine allowed origin
144
+ let allowedOrigin = null;
145
+ if (typeof corsConfig.origin === 'function') {
146
+ allowedOrigin = origin ? corsConfig.origin(origin) ?? null : null;
147
+ }
148
+ else if (Array.isArray(corsConfig.origin)) {
149
+ allowedOrigin = origin && corsConfig.origin.includes(origin) ? origin : null;
150
+ }
151
+ else {
152
+ allowedOrigin = corsConfig.origin;
153
+ }
154
+ if (allowedOrigin) {
155
+ c.header('Access-Control-Allow-Origin', allowedOrigin);
156
+ if (corsConfig.credentials) {
157
+ c.header('Access-Control-Allow-Credentials', 'true');
158
+ }
159
+ }
160
+ // Handle preflight
161
+ if (c.req.method === 'OPTIONS') {
162
+ c.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
163
+ c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
164
+ c.header('Access-Control-Max-Age', '86400');
165
+ return c.body(null, 204);
166
+ }
167
+ await next();
168
+ });
169
+ }
170
+ app.all(`${apiPath}/*`, async (c) => {
171
+ const auth = c.var?.auth;
172
+ if (!auth) {
173
+ return c.json({ error: 'Auth not configured' }, 500);
174
+ }
175
+ return auth.handler(c.req.raw);
176
+ });
177
+ }
178
+ /**
179
+ * Middleware to load the authenticated user.
180
+ * This should be used early in the middleware chain.
181
+ */
182
+ export function loadUser(config = {}) {
183
+ const { userKey = 'authUser' } = config;
184
+ return async (c, next) => {
185
+ const auth = c.var?.auth;
186
+ if (!auth) {
187
+ await next();
188
+ return;
189
+ }
190
+ try {
191
+ const session = await auth.api.getSession({ headers: c.req.raw.headers });
192
+ if (session) {
193
+ c.set(userKey, {
194
+ user: session.user,
195
+ session: session.session,
196
+ });
197
+ }
198
+ }
199
+ catch {
200
+ // Session fetch failed, continue without user
201
+ }
202
+ await next();
203
+ };
204
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Hono-Effect Bridge
3
+ *
4
+ * Middleware that connects Hono's request handling to Effect's runtime.
5
+ */
6
+ import { Layer, ManagedRuntime } from 'effect';
7
+ import type { Context as HonoContext, MiddlewareHandler, Env } from 'hono';
8
+ import { DatabaseService, AuthService, AuthUserService, HonertiaService, RequestService, ResponseFactoryService } from './services.js';
9
+ /**
10
+ * Configuration for the Effect bridge.
11
+ */
12
+ export interface EffectBridgeConfig<E extends Env> {
13
+ database?: (c: HonoContext<E>) => unknown;
14
+ }
15
+ /**
16
+ * Symbol for storing Effect runtime in Hono context.
17
+ */
18
+ declare const EFFECT_RUNTIME: unique symbol;
19
+ /**
20
+ * Extend Hono context with Effect runtime.
21
+ */
22
+ declare module 'hono' {
23
+ interface ContextVariableMap {
24
+ [EFFECT_RUNTIME]?: ManagedRuntime.ManagedRuntime<DatabaseService | AuthService | AuthUserService | HonertiaService | RequestService | ResponseFactoryService, never>;
25
+ }
26
+ }
27
+ /**
28
+ * Build the Effect layer from Hono context.
29
+ */
30
+ export declare function buildContextLayer<E extends Env>(c: HonoContext<E>, config?: EffectBridgeConfig<E>): Layer.Layer<RequestService | ResponseFactoryService | HonertiaService | DatabaseService | AuthService | AuthUserService, never, never>;
31
+ /**
32
+ * Get the Effect runtime from Hono context.
33
+ */
34
+ export declare function getEffectRuntime<E extends Env>(c: HonoContext<E>): ManagedRuntime.ManagedRuntime<any, never> | undefined;
35
+ /**
36
+ * Middleware that sets up the Effect runtime for each request.
37
+ */
38
+ export declare function effectBridge<E extends Env>(config?: EffectBridgeConfig<E>): MiddlewareHandler<E>;
39
+ export {};
40
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/effect/bridge.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAU,KAAK,EAAE,cAAc,EAAW,MAAM,QAAQ,CAAA;AAC/D,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAC1E,OAAO,EACL,eAAe,EACf,WAAW,EACX,eAAe,EACf,eAAe,EACf,cAAc,EACd,sBAAsB,EAKvB,MAAM,eAAe,CAAA;AAEtB;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,GAAG;IAC/C,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,OAAO,CAAA;CAC1C;AAED;;GAEG;AACH,QAAA,MAAM,cAAc,eAA0B,CAAA;AAE9C;;GAEG;AACH,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,kBAAkB;QAC1B,CAAC,cAAc,CAAC,CAAC,EAAE,cAAc,CAAC,cAAc,CAC5C,eAAe,GACf,WAAW,GACX,eAAe,GACf,eAAe,GACf,cAAc,GACd,sBAAsB,EACxB,KAAK,CACN,CAAA;KACF;CACF;AAqDD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,GAAG,EAC7C,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,EACjB,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC7B,KAAK,CAAC,KAAK,CACV,cAAc,GACd,sBAAsB,GACtB,eAAe,GACf,eAAe,GACf,WAAW,GACX,eAAe,EACjB,KAAK,EACL,KAAK,CACN,CAmCA;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,GAAG,EAC5C,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GAChB,cAAc,CAAC,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,SAAS,CAEvD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,GAAG,EACxC,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC7B,iBAAiB,CAAC,CAAC,CAAC,CAetB"}