@hypersonic-js/core 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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/Readme.md +52 -0
  3. package/dist/auth/middleware.d.ts +8 -0
  4. package/dist/auth/middleware.d.ts.map +1 -0
  5. package/dist/auth/middleware.js +10 -0
  6. package/dist/auth/middleware.js.map +1 -0
  7. package/dist/auth/setup.d.ts +9 -0
  8. package/dist/auth/setup.d.ts.map +1 -0
  9. package/dist/auth/setup.js +27 -0
  10. package/dist/auth/setup.js.map +1 -0
  11. package/dist/auth/types.d.ts +16 -0
  12. package/dist/auth/types.d.ts.map +1 -0
  13. package/dist/auth/types.js +2 -0
  14. package/dist/auth/types.js.map +1 -0
  15. package/dist/config/define-config.d.ts +7 -0
  16. package/dist/config/define-config.d.ts.map +1 -0
  17. package/dist/config/define-config.js +8 -0
  18. package/dist/config/define-config.js.map +1 -0
  19. package/dist/config/env.d.ts +22 -0
  20. package/dist/config/env.d.ts.map +1 -0
  21. package/dist/config/env.js +48 -0
  22. package/dist/config/env.js.map +1 -0
  23. package/dist/config/loader.d.ts +24 -0
  24. package/dist/config/loader.d.ts.map +1 -0
  25. package/dist/config/loader.js +37 -0
  26. package/dist/config/loader.js.map +1 -0
  27. package/dist/config/types.d.ts +22 -0
  28. package/dist/config/types.d.ts.map +1 -0
  29. package/dist/config/types.js +2 -0
  30. package/dist/config/types.js.map +1 -0
  31. package/dist/database/client.d.ts +19 -0
  32. package/dist/database/client.d.ts.map +1 -0
  33. package/dist/database/client.js +30 -0
  34. package/dist/database/client.js.map +1 -0
  35. package/dist/index.d.ts +20 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +18 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/inertia/middleware.d.ts +24 -0
  40. package/dist/inertia/middleware.d.ts.map +1 -0
  41. package/dist/inertia/middleware.js +94 -0
  42. package/dist/inertia/middleware.js.map +1 -0
  43. package/dist/inertia/types.d.ts +26 -0
  44. package/dist/inertia/types.d.ts.map +1 -0
  45. package/dist/inertia/types.js +2 -0
  46. package/dist/inertia/types.js.map +1 -0
  47. package/dist/inertia/vite.d.ts +7 -0
  48. package/dist/inertia/vite.d.ts.map +1 -0
  49. package/dist/inertia/vite.js +50 -0
  50. package/dist/inertia/vite.js.map +1 -0
  51. package/dist/server/app.d.ts +19 -0
  52. package/dist/server/app.d.ts.map +1 -0
  53. package/dist/server/app.js +61 -0
  54. package/dist/server/app.js.map +1 -0
  55. package/dist/server/lifecycle.d.ts +8 -0
  56. package/dist/server/lifecycle.d.ts.map +1 -0
  57. package/dist/server/lifecycle.js +33 -0
  58. package/dist/server/lifecycle.js.map +1 -0
  59. package/dist/server/types.d.ts +17 -0
  60. package/dist/server/types.d.ts.map +1 -0
  61. package/dist/server/types.js +2 -0
  62. package/dist/server/types.js.map +1 -0
  63. package/dist/utils/detect-provider.d.ts +14 -0
  64. package/dist/utils/detect-provider.d.ts.map +1 -0
  65. package/dist/utils/detect-provider.js +31 -0
  66. package/dist/utils/detect-provider.js.map +1 -0
  67. package/dist/utils/errors.d.ts +20 -0
  68. package/dist/utils/errors.d.ts.map +1 -0
  69. package/dist/utils/errors.js +36 -0
  70. package/dist/utils/errors.js.map +1 -0
  71. package/package.json +66 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joaquim Dalton-Pereira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Readme.md ADDED
@@ -0,0 +1,52 @@
1
+ # @hypersonic/core
2
+
3
+ The core of **Hypersonic.js** — a modern Django-inspired full-stack TypeScript framework. One install gives you Express, Inertia + React + Vite + Tailwind, Prisma, and Better Auth, all pre-wired together.
4
+
5
+ 📖 **[hypersonic-js.com](https://hypersonic-js.com)**
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @hypersonic/core
11
+ npm install --save-dev prisma @prisma/client
12
+ ```
13
+
14
+ For a single-package install of everything, use [`@hypersonic/complete`](https://www.npmjs.com/package/@hypersonic/complete) instead.
15
+
16
+ ## Quick start
17
+
18
+ **`hypersonic.config.ts`** at your project root:
19
+
20
+ ```ts
21
+ import { defineConfig } from '@hypersonic/core'
22
+
23
+ export default defineConfig({
24
+ server: { port: 3000, host: 'localhost' },
25
+ auth: { trustedOrigins: ['http://localhost:3000'] },
26
+ inertia: { ssr: true },
27
+ })
28
+ ```
29
+
30
+ **`.env`**:
31
+
32
+ ```bash
33
+ DATABASE_URL="postgresql://localhost:5432/myapp"
34
+ BETTER_AUTH_SECRET="your-secret-at-least-32-characters-long"
35
+ ```
36
+
37
+ **`server.ts`**:
38
+
39
+ ```ts
40
+ import { PrismaClient } from '@prisma/client'
41
+ import { createApp, loadConfig } from '@hypersonic/core'
42
+
43
+ const { config, env } = await loadConfig()
44
+ const app = await createApp({ config, env, prisma: new PrismaClient() })
45
+ await app.start()
46
+ ```
47
+
48
+ Full documentation at **[hypersonic-js.com](https://hypersonic-js.com)**.
49
+
50
+ ## License
51
+
52
+ MIT © [Zesuperaker](https://github.com/Zesuperaker)
@@ -0,0 +1,8 @@
1
+ import type { Application } from 'express';
2
+ import type { AuthInstance } from './setup.js';
3
+ /**
4
+ * Mounts the Better Auth request handler on all /api/auth/* routes.
5
+ * Must be called before Inertia middleware so auth routes are handled first.
6
+ */
7
+ export declare function mountAuth(app: Application, auth: AuthInstance): void;
8
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAGpE"}
@@ -0,0 +1,10 @@
1
+ import { toNodeHandler } from 'better-auth/node';
2
+ /**
3
+ * Mounts the Better Auth request handler on all /api/auth/* routes.
4
+ * Must be called before Inertia middleware so auth routes are handled first.
5
+ */
6
+ export function mountAuth(app, auth) {
7
+ // Express 5 uses named wildcard params — *splat captures everything after /api/auth/
8
+ app.all('/api/auth/*splat', toNodeHandler(auth));
9
+ }
10
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAIhD;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,GAAgB,EAAE,IAAkB;IAC5D,qFAAqF;IACrF,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;AAClD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { betterAuth } from 'better-auth';
2
+ import type { AuthSetupOptions } from './types.js';
3
+ export type AuthInstance = ReturnType<typeof betterAuth>;
4
+ /**
5
+ * Creates and returns a configured Better Auth instance.
6
+ * OAuth social providers are only wired in when credentials are supplied.
7
+ */
8
+ export declare function createAuth(options: AuthSetupOptions): AuthInstance;
9
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/auth/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAKxC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;AAExD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CA2BlE"}
@@ -0,0 +1,27 @@
1
+ import { betterAuth } from 'better-auth';
2
+ import { prismaAdapter } from 'better-auth/adapters/prisma';
3
+ import { detectProvider } from '../utils/detect-provider.js';
4
+ /**
5
+ * Creates and returns a configured Better Auth instance.
6
+ * OAuth social providers are only wired in when credentials are supplied.
7
+ */
8
+ export function createAuth(options) {
9
+ const provider = detectProvider(options.databaseUrl);
10
+ const socialProviders = {};
11
+ if (options.providers?.github !== undefined) {
12
+ socialProviders.github = options.providers.github;
13
+ }
14
+ if (options.providers?.google !== undefined) {
15
+ socialProviders.google = options.providers.google;
16
+ }
17
+ const hasSocialProviders = Object.keys(socialProviders).length > 0;
18
+ const authOptions = {
19
+ secret: options.secret,
20
+ trustedOrigins: options.trustedOrigins,
21
+ database: prismaAdapter(options.prisma, { provider }),
22
+ emailAndPassword: { enabled: true },
23
+ ...(hasSocialProviders ? { socialProviders } : {}),
24
+ };
25
+ return betterAuth(authOptions);
26
+ }
27
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/auth/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAG3D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAK5D;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,OAAyB;IAClD,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAEpD,MAAM,eAAe,GAAoB,EAAE,CAAA;IAE3C,IAAI,OAAO,CAAC,SAAS,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;QAC5C,eAAe,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,MAAmC,CAAA;IAChF,CAAC;IAED,IAAI,OAAO,CAAC,SAAS,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;QAC5C,eAAe,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,MAAmC,CAAA;IAChF,CAAC;IAED,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;IAElE,MAAM,WAAW,GAAsB;QACrC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,QAAQ,EAAE,aAAa,CACrB,OAAO,CAAC,MAA6C,EACrD,EAAE,QAAQ,EAAE,CACb;QACD,gBAAgB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;QACnC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnD,CAAA;IAED,OAAO,UAAU,CAAC,WAAW,CAAC,CAAA;AAChC,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface SocialProviderCredentials {
2
+ clientId: string;
3
+ clientSecret: string;
4
+ }
5
+ export interface AuthSetupOptions {
6
+ secret: string;
7
+ trustedOrigins: string[];
8
+ databaseUrl: string;
9
+ prisma: unknown;
10
+ providers?: {
11
+ github?: SocialProviderCredentials;
12
+ google?: SocialProviderCredentials;
13
+ };
14
+ }
15
+ export type { betterAuth as BetterAuth } from 'better-auth';
16
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,yBAAyB,CAAA;QAClC,MAAM,CAAC,EAAE,yBAAyB,CAAA;KACnC,CAAA;CACF;AAGD,YAAY,EAAE,UAAU,IAAI,UAAU,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import type { HypersonicConfig } from './types.js';
2
+ /**
3
+ * Type-safe helper for defining your Hypersonic configuration.
4
+ * Use this as the default export in hypersonic.config.ts.
5
+ */
6
+ export declare function defineConfig(config: HypersonicConfig): HypersonicConfig;
7
+ //# sourceMappingURL=define-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-config.d.ts","sourceRoot":"","sources":["../../src/config/define-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAEvE"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Type-safe helper for defining your Hypersonic configuration.
3
+ * Use this as the default export in hypersonic.config.ts.
4
+ */
5
+ export function defineConfig(config) {
6
+ return config;
7
+ }
8
+ //# sourceMappingURL=define-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-config.js","sourceRoot":"","sources":["../../src/config/define-config.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAwB;IACnD,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ import type { HypersonicConfig } from './types.js';
3
+ /**
4
+ * Builds a Zod schema for environment variables.
5
+ * Required vars are derived from what is enabled in the config —
6
+ * e.g. enabling GitHub OAuth makes GITHUB_CLIENT_ID required.
7
+ */
8
+ export declare function buildEnvSchema(config: HypersonicConfig): z.ZodObject<z.ZodRawShape>;
9
+ export type Env = {
10
+ DATABASE_URL: string;
11
+ BETTER_AUTH_SECRET: string;
12
+ GITHUB_CLIENT_ID?: string;
13
+ GITHUB_CLIENT_SECRET?: string;
14
+ GOOGLE_CLIENT_ID?: string;
15
+ GOOGLE_CLIENT_SECRET?: string;
16
+ };
17
+ /**
18
+ * Validates process.env against the config-derived schema.
19
+ * Throws a descriptive error listing every missing or invalid variable.
20
+ */
21
+ export declare function validateEnv(config: HypersonicConfig, rawEnv?: NodeJS.ProcessEnv): Env;
22
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AASlD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAsBnF;AAED,MAAM,MAAM,GAAG,GAAG;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,gBAAgB,EACxB,MAAM,GAAE,MAAM,CAAC,UAAwB,GACtC,GAAG,CAYL"}
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod';
2
+ const baseShape = {
3
+ DATABASE_URL: z.string().min(1, 'DATABASE_URL is required'),
4
+ BETTER_AUTH_SECRET: z
5
+ .string()
6
+ .min(32, 'BETTER_AUTH_SECRET must be at least 32 characters'),
7
+ };
8
+ /**
9
+ * Builds a Zod schema for environment variables.
10
+ * Required vars are derived from what is enabled in the config —
11
+ * e.g. enabling GitHub OAuth makes GITHUB_CLIENT_ID required.
12
+ */
13
+ export function buildEnvSchema(config) {
14
+ const shape = { ...baseShape };
15
+ if (config.auth.providers?.github === true) {
16
+ shape['GITHUB_CLIENT_ID'] = z
17
+ .string()
18
+ .min(1, 'GITHUB_CLIENT_ID is required when GitHub provider is enabled');
19
+ shape['GITHUB_CLIENT_SECRET'] = z
20
+ .string()
21
+ .min(1, 'GITHUB_CLIENT_SECRET is required when GitHub provider is enabled');
22
+ }
23
+ if (config.auth.providers?.google === true) {
24
+ shape['GOOGLE_CLIENT_ID'] = z
25
+ .string()
26
+ .min(1, 'GOOGLE_CLIENT_ID is required when Google provider is enabled');
27
+ shape['GOOGLE_CLIENT_SECRET'] = z
28
+ .string()
29
+ .min(1, 'GOOGLE_CLIENT_SECRET is required when Google provider is enabled');
30
+ }
31
+ return z.object(shape);
32
+ }
33
+ /**
34
+ * Validates process.env against the config-derived schema.
35
+ * Throws a descriptive error listing every missing or invalid variable.
36
+ */
37
+ export function validateEnv(config, rawEnv = process.env) {
38
+ const schema = buildEnvSchema(config);
39
+ const result = schema.safeParse(rawEnv);
40
+ if (!result.success) {
41
+ const messages = result.error.issues
42
+ .map((i) => ` - ${i.path.join('.')}: ${i.message}`)
43
+ .join('\n');
44
+ throw new Error(`Hypersonic: Environment validation failed:\n${messages}`);
45
+ }
46
+ return result.data;
47
+ }
48
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,MAAM,SAAS,GAAG;IAChB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,0BAA0B,CAAC;IAC3D,kBAAkB,EAAE,CAAC;SAClB,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,EAAE,mDAAmD,CAAC;CAChE,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAwB;IACrD,MAAM,KAAK,GAA8B,EAAE,GAAG,SAAS,EAAE,CAAA;IAEzD,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3C,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC;aAC1B,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,EAAE,8DAA8D,CAAC,CAAA;QACzE,KAAK,CAAC,sBAAsB,CAAC,GAAG,CAAC;aAC9B,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,EAAE,kEAAkE,CAAC,CAAA;IAC/E,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3C,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC;aAC1B,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,EAAE,8DAA8D,CAAC,CAAA;QACzE,KAAK,CAAC,sBAAsB,CAAC,GAAG,CAAC;aAC9B,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,EAAE,kEAAkE,CAAC,CAAA;IAC/E,CAAC;IAED,OAAO,CAAC,CAAC,MAAM,CAAC,KAAsB,CAAC,CAAA;AACzC,CAAC;AAWD;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,MAAwB,EACxB,SAA4B,OAAO,CAAC,GAAG;IAEvC,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IAEvC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aACnD,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,MAAM,IAAI,KAAK,CAAC,+CAA+C,QAAQ,EAAE,CAAC,CAAA;IAC5E,CAAC;IAED,OAAO,MAAM,CAAC,IAAW,CAAA;AAC3B,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { HypersonicConfig } from './types.js';
2
+ import { type Env } from './env.js';
3
+ export interface LoadedConfig {
4
+ config: HypersonicConfig;
5
+ env: Env;
6
+ }
7
+ /**
8
+ * Isolated so tests can inject a mock importer without touching the filesystem.
9
+ */
10
+ export declare function importConfigFile(configUrl: string): Promise<{
11
+ default?: HypersonicConfig;
12
+ }>;
13
+ /**
14
+ * Loads hypersonic.config.ts from the project root, validates all required
15
+ * environment variables, and returns the resolved config + env.
16
+ *
17
+ * @param cwd - project root (defaults to process.cwd())
18
+ * @param rawEnv - environment variables (defaults to process.env)
19
+ * @param importer - override the dynamic import for testing
20
+ */
21
+ export declare function loadConfig(cwd?: string, rawEnv?: NodeJS.ProcessEnv, importer?: (url: string) => Promise<{
22
+ default?: HypersonicConfig;
23
+ }>): Promise<LoadedConfig>;
24
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,EAAe,KAAK,GAAG,EAAE,MAAM,UAAU,CAAA;AAEhD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,gBAAgB,CAAA;IACxB,GAAG,EAAE,GAAG,CAAA;CACT;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,OAAO,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAEzC;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,GAAG,GAAE,MAAsB,EAC3B,MAAM,GAAE,MAAM,CAAC,UAAwB,EACvC,QAAQ,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,OAAO,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAoB,GACpF,OAAO,CAAC,YAAY,CAAC,CA0BvB"}
@@ -0,0 +1,37 @@
1
+ import { resolve } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { validateEnv } from './env.js';
4
+ /**
5
+ * Isolated so tests can inject a mock importer without touching the filesystem.
6
+ */
7
+ export async function importConfigFile(configUrl) {
8
+ return import(configUrl);
9
+ }
10
+ /**
11
+ * Loads hypersonic.config.ts from the project root, validates all required
12
+ * environment variables, and returns the resolved config + env.
13
+ *
14
+ * @param cwd - project root (defaults to process.cwd())
15
+ * @param rawEnv - environment variables (defaults to process.env)
16
+ * @param importer - override the dynamic import for testing
17
+ */
18
+ export async function loadConfig(cwd = process.cwd(), rawEnv = process.env, importer = importConfigFile) {
19
+ const configPath = resolve(cwd, 'hypersonic.config.ts');
20
+ const configUrl = pathToFileURL(configPath).href;
21
+ let mod;
22
+ try {
23
+ mod = await importer(configUrl);
24
+ }
25
+ catch (err) {
26
+ throw new Error(`Hypersonic: Failed to load hypersonic.config.ts at ${configPath}.\n` +
27
+ `Make sure the file exists and has a valid default export.\n` +
28
+ `Detail: ${err instanceof Error ? err.message : String(err)}`);
29
+ }
30
+ if (mod.default === undefined || mod.default === null) {
31
+ throw new Error('Hypersonic: hypersonic.config.ts must export a config via defineConfig() as the default export.');
32
+ }
33
+ const config = mod.default;
34
+ const env = validateEnv(config, rawEnv);
35
+ return { config, env };
36
+ }
37
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,WAAW,EAAY,MAAM,UAAU,CAAA;AAOhD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,SAAiB;IAEjB,OAAO,MAAM,CAAC,SAAS,CAA4C,CAAA;AACrE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAc,OAAO,CAAC,GAAG,EAAE,EAC3B,SAA4B,OAAO,CAAC,GAAG,EACvC,WAAqE,gBAAgB;IAErF,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAA;IACvD,MAAM,SAAS,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC,IAAI,CAAA;IAEhD,IAAI,GAAmC,CAAA;IAEvC,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAA;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,sDAAsD,UAAU,KAAK;YACnE,6DAA6D;YAC7D,WAAW,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAChE,CAAA;IACH,CAAC;IAED,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CACb,iGAAiG,CAClG,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAA;IAC1B,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEvC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAA;AACxB,CAAC"}
@@ -0,0 +1,22 @@
1
+ export interface ServerConfig {
2
+ port: number;
3
+ host: string;
4
+ }
5
+ export interface AuthProviders {
6
+ github?: boolean;
7
+ google?: boolean;
8
+ }
9
+ export interface AuthConfig {
10
+ trustedOrigins: string[];
11
+ providers?: AuthProviders;
12
+ }
13
+ export interface InertiaConfig {
14
+ ssr: boolean;
15
+ version?: string;
16
+ }
17
+ export interface HypersonicConfig {
18
+ server: ServerConfig;
19
+ auth: AuthConfig;
20
+ inertia: InertiaConfig;
21
+ }
22
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,SAAS,CAAC,EAAE,aAAa,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,OAAO,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,EAAE,aAAa,CAAA;CACvB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ export interface PrismaClientLike {
2
+ $disconnect(): Promise<void>;
3
+ }
4
+ /**
5
+ * Registers the application's PrismaClient instance.
6
+ * Called automatically by createApp() — pass your PrismaClient there.
7
+ */
8
+ export declare function setPrismaClient(client: PrismaClientLike): void;
9
+ /**
10
+ * Returns the registered PrismaClient instance.
11
+ * Throws if createApp() has not been called yet.
12
+ */
13
+ export declare function getPrismaClient(): PrismaClientLike;
14
+ /**
15
+ * Disconnects the PrismaClient and clears the singleton.
16
+ * Called automatically by app.stop().
17
+ */
18
+ export declare function disconnectPrismaClient(): Promise<void>;
19
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/database/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAID;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAE9D;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,gBAAgB,CAQlD;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAK5D"}
@@ -0,0 +1,30 @@
1
+ let instance = null;
2
+ /**
3
+ * Registers the application's PrismaClient instance.
4
+ * Called automatically by createApp() — pass your PrismaClient there.
5
+ */
6
+ export function setPrismaClient(client) {
7
+ instance = client;
8
+ }
9
+ /**
10
+ * Returns the registered PrismaClient instance.
11
+ * Throws if createApp() has not been called yet.
12
+ */
13
+ export function getPrismaClient() {
14
+ if (instance === null) {
15
+ throw new Error('Hypersonic: PrismaClient has not been initialised. ' +
16
+ 'Pass your PrismaClient instance to createApp({ prisma }).');
17
+ }
18
+ return instance;
19
+ }
20
+ /**
21
+ * Disconnects the PrismaClient and clears the singleton.
22
+ * Called automatically by app.stop().
23
+ */
24
+ export async function disconnectPrismaClient() {
25
+ if (instance !== null) {
26
+ await instance.$disconnect();
27
+ instance = null;
28
+ }
29
+ }
30
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/database/client.ts"],"names":[],"mappings":"AAIA,IAAI,QAAQ,GAA4B,IAAI,CAAA;AAE5C;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAwB;IACtD,QAAQ,GAAG,MAAM,CAAA;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,qDAAqD;YACnD,2DAA2D,CAC9D,CAAA;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAA;QAC5B,QAAQ,GAAG,IAAI,CAAA;IACjB,CAAC;AACH,CAAC"}
@@ -0,0 +1,20 @@
1
+ export { defineConfig } from './config/define-config.js';
2
+ export { loadConfig, importConfigFile } from './config/loader.js';
3
+ export { validateEnv, buildEnvSchema } from './config/env.js';
4
+ export type { HypersonicConfig, ServerConfig, AuthConfig, InertiaConfig, AuthProviders } from './config/types.js';
5
+ export type { Env } from './config/env.js';
6
+ export type { LoadedConfig } from './config/loader.js';
7
+ export { createApp } from './server/app.js';
8
+ export type { CreateAppOptions, HypersonicApp } from './server/types.js';
9
+ export { getPrismaClient, setPrismaClient, disconnectPrismaClient } from './database/client.js';
10
+ export type { PrismaClientLike } from './database/client.js';
11
+ export { createAuth } from './auth/setup.js';
12
+ export { mountAuth } from './auth/middleware.js';
13
+ export type { AuthSetupOptions, SocialProviderCredentials } from './auth/types.js';
14
+ export { createInertiaMiddleware, createInertiaErrorHandler } from './inertia/middleware.js';
15
+ export { createViteSetup } from './inertia/vite.js';
16
+ export type { InertiaPage, InertiaOptions, ViteSetup } from './inertia/types.js';
17
+ export { HttpError, NotFoundError, UnauthorizedError, ForbiddenError, ValidationError } from './utils/errors.js';
18
+ export { detectProvider } from './utils/detect-provider.js';
19
+ export type { DatabaseProvider } from './utils/detect-provider.js';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAC7D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjH,YAAY,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AAC1C,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGxE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAG5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,YAAY,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAA;AAGlF,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AAC5F,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGhF,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAChH,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3D,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ // Config
2
+ export { defineConfig } from './config/define-config.js';
3
+ export { loadConfig, importConfigFile } from './config/loader.js';
4
+ export { validateEnv, buildEnvSchema } from './config/env.js';
5
+ // Server
6
+ export { createApp } from './server/app.js';
7
+ // Database
8
+ export { getPrismaClient, setPrismaClient, disconnectPrismaClient } from './database/client.js';
9
+ // Auth
10
+ export { createAuth } from './auth/setup.js';
11
+ export { mountAuth } from './auth/middleware.js';
12
+ // Inertia
13
+ export { createInertiaMiddleware, createInertiaErrorHandler } from './inertia/middleware.js';
14
+ export { createViteSetup } from './inertia/vite.js';
15
+ // Utils
16
+ export { HttpError, NotFoundError, UnauthorizedError, ForbiddenError, ValidationError } from './utils/errors.js';
17
+ export { detectProvider } from './utils/detect-provider.js';
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAK7D,SAAS;AACT,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAG3C,WAAW;AACX,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAG/F,OAAO;AACP,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAGhD,UAAU;AACV,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AAC5F,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAGnD,QAAQ;AACR,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAChH,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA"}
@@ -0,0 +1,24 @@
1
+ import type { Application, Request, Response, NextFunction } from 'express';
2
+ import type { InertiaOptions } from './types.js';
3
+ /**
4
+ * Returns an error-handling middleware that intercepts HttpErrors on Inertia
5
+ * requests and redirects back to the referring page (or '/' as a fallback)
6
+ * instead of returning a plain JSON response that Inertia cannot render.
7
+ *
8
+ * Register this AFTER your routes and BEFORE your plain-JSON error handler so
9
+ * that the Inertia client always receives a redirect it can follow.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * registerRoutes(app, prisma, auth) // your routes
14
+ * app.use(createInertiaErrorHandler()) // Inertia-aware errors
15
+ * app.use(plainJsonErrorHandler) // fallback for non-Inertia
16
+ * ```
17
+ */
18
+ export declare function createInertiaErrorHandler(): (err: unknown, req: Request, res: Response, next: NextFunction) => void;
19
+ /**
20
+ * Mounts the Inertia middleware + Vite integration onto the Express app.
21
+ * This must be called before routes are registered.
22
+ */
23
+ export declare function createInertiaMiddleware(app: Application, options: InertiaOptions): Promise<void>;
24
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/inertia/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAkB,MAAM,SAAS,CAAA;AAC3F,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,YAAY,CAAA;AA+B7D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,IAAI,CAC3C,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,KACf,IAAI,CAWR;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAkDf"}
@@ -0,0 +1,94 @@
1
+ import { createViteSetup } from './vite.js';
2
+ import { HttpError } from '../utils/errors.js';
3
+ const INERTIA_HEADER = 'x-inertia';
4
+ const INERTIA_VERSION_HEADER = 'x-inertia-version';
5
+ function escapeJson(str) {
6
+ return str
7
+ .replace(/&/g, '\\u0026')
8
+ .replace(/</g, '\\u003c')
9
+ .replace(/>/g, '\\u003e')
10
+ .replace(/'/g, '\\u0027');
11
+ }
12
+ function buildHtml(page, assetTags) {
13
+ const pageJson = escapeJson(JSON.stringify(page));
14
+ return `<!DOCTYPE html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="UTF-8" />
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
19
+ ${assetTags}
20
+ </head>
21
+ <body>
22
+ <script type="application/json" data-page="app">${pageJson}</script>
23
+ <div id="app"></div>
24
+ </body>
25
+ </html>`;
26
+ }
27
+ /**
28
+ * Returns an error-handling middleware that intercepts HttpErrors on Inertia
29
+ * requests and redirects back to the referring page (or '/' as a fallback)
30
+ * instead of returning a plain JSON response that Inertia cannot render.
31
+ *
32
+ * Register this AFTER your routes and BEFORE your plain-JSON error handler so
33
+ * that the Inertia client always receives a redirect it can follow.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * registerRoutes(app, prisma, auth) // your routes
38
+ * app.use(createInertiaErrorHandler()) // Inertia-aware errors
39
+ * app.use(plainJsonErrorHandler) // fallback for non-Inertia
40
+ * ```
41
+ */
42
+ export function createInertiaErrorHandler() {
43
+ return (err, req, res, next) => {
44
+ if (!(err instanceof HttpError) || !req.headers[INERTIA_HEADER]) {
45
+ next(err);
46
+ return;
47
+ }
48
+ const referer = req.headers['referer'];
49
+ const redirectUrl = typeof referer === 'string' && referer.length > 0 ? referer : '/';
50
+ res.redirect(303, redirectUrl);
51
+ };
52
+ }
53
+ /**
54
+ * Mounts the Inertia middleware + Vite integration onto the Express app.
55
+ * This must be called before routes are registered.
56
+ */
57
+ export async function createInertiaMiddleware(app, options) {
58
+ const version = options.version ?? '1';
59
+ const vite = await createViteSetup(options.ssr);
60
+ // Mount Vite dev server or static file serving
61
+ app.use(vite.middleware);
62
+ // Inertia protocol middleware
63
+ const inertiaMiddleware = (req, res, next) => {
64
+ const isInertiaRequest = Boolean(req.headers[INERTIA_HEADER]);
65
+ // Asset version mismatch — force a full page reload
66
+ if (isInertiaRequest &&
67
+ req.method === 'GET' &&
68
+ req.headers[INERTIA_VERSION_HEADER] !== version) {
69
+ res.setHeader('X-Inertia-Location', req.url);
70
+ res.status(409).end();
71
+ return;
72
+ }
73
+ res.inertia = (component, props = {}) => {
74
+ const page = {
75
+ component,
76
+ props,
77
+ url: req.originalUrl,
78
+ version,
79
+ };
80
+ if (isInertiaRequest) {
81
+ res.setHeader('X-Inertia', 'true');
82
+ res.setHeader('Vary', 'X-Inertia');
83
+ res.status(200).json(page);
84
+ return;
85
+ }
86
+ const html = buildHtml(page, vite.assetTags());
87
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
88
+ res.status(200).send(html);
89
+ };
90
+ next();
91
+ };
92
+ app.use(inertiaMiddleware);
93
+ }
94
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/inertia/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C,MAAM,cAAc,GAAG,WAAW,CAAA;AAClC,MAAM,sBAAsB,GAAG,mBAAmB,CAAA;AAElD,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;AAC7B,CAAC;AAED,SAAS,SAAS,CAAC,IAAiB,EAAE,SAAiB;IACrD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;IACjD,OAAO;;;;;MAKH,SAAS;;;sDAGuC,QAAQ;;;QAGtD,CAAA;AACR,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,yBAAyB;IAMvC,OAAO,CAAC,GAAY,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC7E,IAAI,CAAC,CAAC,GAAG,YAAY,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YAChE,IAAI,CAAC,GAAG,CAAC,CAAA;YACT,OAAM;QACR,CAAC;QAED,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;QACtC,MAAM,WAAW,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAA;QACrF,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;IAChC,CAAC,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,GAAgB,EAChB,OAAuB;IAEvB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,GAAG,CAAA;IACtC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAE/C,+CAA+C;IAC/C,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAA4B,CAAC,CAAA;IAE1C,8BAA8B;IAC9B,MAAM,iBAAiB,GAAmB,CACxC,GAAY,EACZ,GAAa,EACb,IAAkB,EACZ,EAAE;QACR,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAA;QAE7D,oDAAoD;QACpD,IACE,gBAAgB;YAChB,GAAG,CAAC,MAAM,KAAK,KAAK;YACpB,GAAG,CAAC,OAAO,CAAC,sBAAsB,CAAC,KAAK,OAAO,EAC/C,CAAC;YACD,GAAG,CAAC,SAAS,CAAC,oBAAoB,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;YAC5C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAA;YACrB,OAAM;QACR,CAAC;QAED,GAAG,CAAC,OAAO,GAAG,CAAC,SAAiB,EAAE,QAAiC,EAAE,EAAQ,EAAE;YAC7E,MAAM,IAAI,GAAgB;gBACxB,SAAS;gBACT,KAAK;gBACL,GAAG,EAAE,GAAG,CAAC,WAAW;gBACpB,OAAO;aACR,CAAA;YAED,IAAI,gBAAgB,EAAE,CAAC;gBACrB,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;gBAClC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;gBAClC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAC1B,OAAM;YACR,CAAC;YAED,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;YAC9C,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAA;YACzD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC5B,CAAC,CAAA;QAED,IAAI,EAAE,CAAA;IACR,CAAC,CAAA;IAED,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;AAC5B,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { RequestHandler } from 'express';
2
+ export interface InertiaPage {
3
+ component: string;
4
+ props: Record<string, unknown>;
5
+ url: string;
6
+ version: string;
7
+ }
8
+ export interface InertiaOptions {
9
+ ssr: boolean;
10
+ version?: string;
11
+ }
12
+ export interface ViteSetup {
13
+ middleware: RequestHandler;
14
+ assetTags: () => string;
15
+ }
16
+ /**
17
+ * Augment Express Response with an optional res.inertia() helper.
18
+ * It is optional in the type because it is added at runtime by the middleware.
19
+ * After createInertiaMiddleware() mounts, it is always present on res.
20
+ */
21
+ declare module 'express' {
22
+ interface Response {
23
+ inertia?: (component: string, props?: Record<string, unknown>) => void;
24
+ }
25
+ }
26
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/inertia/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,cAAc,CAAA;IAC1B,SAAS,EAAE,MAAM,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,QAAQ;QAChB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;KACvE;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/inertia/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import type { ViteSetup } from './types.js';
2
+ /**
3
+ * Returns the correct Vite setup (dev server or static file serving)
4
+ * based on NODE_ENV. Isolated as a named export so tests can mock it.
5
+ */
6
+ export declare function createViteSetup(_ssr: boolean): Promise<ViteSetup>;
7
+ //# sourceMappingURL=vite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../src/inertia/vite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAoD3C;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAKvE"}
@@ -0,0 +1,50 @@
1
+ const DEV_ASSET_TAGS = [
2
+ '<script type="module" src="/@vite/client"></script>',
3
+ '<script type="module" src="/resources/js/app.tsx"></script>',
4
+ ].join('\n ');
5
+ async function createDevSetup() {
6
+ const { createServer } = await import('vite');
7
+ const viteServer = await createServer({
8
+ server: { middlewareMode: true },
9
+ appType: 'custom',
10
+ });
11
+ return {
12
+ middleware: viteServer.middlewares,
13
+ assetTags: () => DEV_ASSET_TAGS,
14
+ };
15
+ }
16
+ async function createProdSetup() {
17
+ const { readFileSync } = await import('node:fs');
18
+ const { static: expressStatic } = await import('express');
19
+ let tags = '';
20
+ try {
21
+ const raw = readFileSync('public/.vite/manifest.json', 'utf-8');
22
+ const manifest = JSON.parse(raw);
23
+ const entry = manifest['resources/js/app.tsx'];
24
+ if (entry !== undefined) {
25
+ const cssLinks = entry.css
26
+ ?.map((c) => `<link rel="stylesheet" href="/${c}" />`)
27
+ .join('\n ') ?? '';
28
+ tags = `${cssLinks}\n <script type="module" src="/${entry.file}"></script>`;
29
+ }
30
+ }
31
+ catch {
32
+ // No manifest found — assets will be missing but the server still boots
33
+ }
34
+ const captured = tags;
35
+ return {
36
+ middleware: expressStatic('public'),
37
+ assetTags: () => captured,
38
+ };
39
+ }
40
+ /**
41
+ * Returns the correct Vite setup (dev server or static file serving)
42
+ * based on NODE_ENV. Isolated as a named export so tests can mock it.
43
+ */
44
+ export async function createViteSetup(_ssr) {
45
+ if (process.env['NODE_ENV'] === 'production') {
46
+ return createProdSetup();
47
+ }
48
+ return createDevSetup();
49
+ }
50
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../src/inertia/vite.ts"],"names":[],"mappings":"AAGA,MAAM,cAAc,GAAG;IACrB,qDAAqD;IACrD,6DAA6D;CAC9D,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAEhB,KAAK,UAAU,cAAc;IAC3B,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAA;IAE7C,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC;QACpC,MAAM,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE;QAChC,OAAO,EAAE,QAAQ;KAClB,CAAC,CAAA;IAEF,OAAO;QACL,UAAU,EAAE,UAAU,CAAC,WAAwC;QAC/D,SAAS,EAAE,GAAG,EAAE,CAAC,cAAc;KAChC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAA;IAChD,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAA;IAEzD,IAAI,IAAI,GAAG,EAAE,CAAA;IAEb,IAAI,CAAC;QAEH,MAAM,GAAG,GAAG,YAAY,CAAC,4BAA4B,EAAE,OAAO,CAAC,CAAA;QAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkC,CAAA;QACjE,MAAM,KAAK,GAAG,QAAQ,CAAC,sBAAsB,CAAC,CAAA;QAE9C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,QAAQ,GACZ,KAAK,CAAC,GAAG;gBACP,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iCAAiC,CAAC,MAAM,CAAC;iBACrD,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;YACzB,IAAI,GAAG,GAAG,QAAQ,qCAAqC,KAAK,CAAC,IAAI,aAAa,CAAA;QAChF,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;IAC1E,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAA;IAErB,OAAO;QACL,UAAU,EAAE,aAAa,CAAC,QAAQ,CAAmB;QACrD,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ;KAC1B,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAa;IACjD,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,YAAY,EAAE,CAAC;QAC7C,OAAO,eAAe,EAAE,CAAA;IAC1B,CAAC;IACD,OAAO,cAAc,EAAE,CAAA;AACzB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { CreateAppOptions, HypersonicApp } from './types.js';
2
+ /**
3
+ * Creates and returns a fully wired Hypersonic application.
4
+ * The auth instance created internally is returned on `app.auth` so
5
+ * callers can pass it to route registration without creating a second instance.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { PrismaClient } from '@prisma/client'
10
+ * import { createApp, loadConfig } from '@hypersonic/core'
11
+ *
12
+ * const { config, env } = await loadConfig()
13
+ * const app = await createApp({ config, env, prisma: new PrismaClient() })
14
+ * registerRoutes(app.express, prisma, app.auth)
15
+ * await app.start()
16
+ * ```
17
+ */
18
+ export declare function createApp(options: CreateAppOptions): Promise<HypersonicApp>;
19
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AA4BjE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CA2BjF"}
@@ -0,0 +1,61 @@
1
+ import express from 'express';
2
+ import { setPrismaClient } from '../database/client.js';
3
+ import { createAuth } from '../auth/setup.js';
4
+ import { mountAuth } from '../auth/middleware.js';
5
+ import { createInertiaMiddleware } from '../inertia/middleware.js';
6
+ import { createLifecycle } from './lifecycle.js';
7
+ function resolveProviders(config, env) {
8
+ const providers = {};
9
+ const e = env;
10
+ if (config.auth.providers?.github === true) {
11
+ providers['github'] = {
12
+ clientId: e['GITHUB_CLIENT_ID'],
13
+ clientSecret: e['GITHUB_CLIENT_SECRET'],
14
+ };
15
+ }
16
+ if (config.auth.providers?.google === true) {
17
+ providers['google'] = {
18
+ clientId: e['GOOGLE_CLIENT_ID'],
19
+ clientSecret: e['GOOGLE_CLIENT_SECRET'],
20
+ };
21
+ }
22
+ return Object.keys(providers).length > 0 ? providers : undefined;
23
+ }
24
+ /**
25
+ * Creates and returns a fully wired Hypersonic application.
26
+ * The auth instance created internally is returned on `app.auth` so
27
+ * callers can pass it to route registration without creating a second instance.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { PrismaClient } from '@prisma/client'
32
+ * import { createApp, loadConfig } from '@hypersonic/core'
33
+ *
34
+ * const { config, env } = await loadConfig()
35
+ * const app = await createApp({ config, env, prisma: new PrismaClient() })
36
+ * registerRoutes(app.express, prisma, app.auth)
37
+ * await app.start()
38
+ * ```
39
+ */
40
+ export async function createApp(options) {
41
+ const { config, env, prisma } = options;
42
+ const app = express();
43
+ app.use(express.json());
44
+ app.use(express.urlencoded({ extended: true }));
45
+ setPrismaClient(prisma);
46
+ const auth = createAuth({
47
+ secret: env.BETTER_AUTH_SECRET,
48
+ trustedOrigins: config.auth.trustedOrigins,
49
+ databaseUrl: env.DATABASE_URL,
50
+ prisma,
51
+ providers: resolveProviders(config, env),
52
+ });
53
+ mountAuth(app, auth);
54
+ await createInertiaMiddleware(app, {
55
+ ssr: config.inertia.ssr,
56
+ version: config.inertia.version,
57
+ });
58
+ const { start, stop } = createLifecycle(app, config);
59
+ return { express: app, auth, start, stop };
60
+ }
61
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAKhD,SAAS,gBAAgB,CACvB,MAAwB,EACxB,GAAQ;IAER,MAAM,SAAS,GAA+D,EAAE,CAAA;IAChF,MAAM,CAAC,GAAG,GAAyC,CAAA;IAEnD,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3C,SAAS,CAAC,QAAQ,CAAC,GAAG;YACpB,QAAQ,EAAE,CAAC,CAAC,kBAAkB,CAAW;YACzC,YAAY,EAAE,CAAC,CAAC,sBAAsB,CAAW;SAClD,CAAA;IACH,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3C,SAAS,CAAC,QAAQ,CAAC,GAAG;YACpB,QAAQ,EAAE,CAAC,CAAC,kBAAkB,CAAW;YACzC,YAAY,EAAE,CAAC,CAAC,sBAAsB,CAAW;SAClD,CAAA;IACH,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;AAClE,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAyB;IACvD,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IAEvC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAA;IAErB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IACvB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAE/C,eAAe,CAAC,MAAM,CAAC,CAAA;IAEvB,MAAM,IAAI,GAAG,UAAU,CAAC;QACtB,MAAM,EAAE,GAAG,CAAC,kBAAkB;QAC9B,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,cAAc;QAC1C,WAAW,EAAE,GAAG,CAAC,YAAY;QAC7B,MAAM;QACN,SAAS,EAAE,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC;KACzC,CAAC,CAAA;IACF,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAEpB,MAAM,uBAAuB,CAAC,GAAG,EAAE;QACnC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG;QACvB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO;KAC9B,CAAC,CAAA;IAEF,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAEpD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AAC5C,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Application } from 'express';
2
+ import type { HypersonicConfig } from '../config/types.js';
3
+ export interface Lifecycle {
4
+ start: () => Promise<void>;
5
+ stop: () => Promise<void>;
6
+ }
7
+ export declare function createLifecycle(app: Application, config: HypersonicConfig): Lifecycle;
8
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/server/lifecycle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAG1D,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAmCrF"}
@@ -0,0 +1,33 @@
1
+ import { disconnectPrismaClient } from '../database/client.js';
2
+ export function createLifecycle(app, config) {
3
+ let server = null;
4
+ async function start() {
5
+ return new Promise((resolve, reject) => {
6
+ server = app.listen(config.server.port, config.server.host, () => {
7
+ resolve();
8
+ });
9
+ server.on('error', reject);
10
+ });
11
+ }
12
+ async function stop() {
13
+ await disconnectPrismaClient();
14
+ return new Promise((resolve, reject) => {
15
+ if (server === null) {
16
+ resolve();
17
+ return;
18
+ }
19
+ const closing = server;
20
+ server = null;
21
+ closing.close((err) => {
22
+ if (err !== undefined) {
23
+ reject(err);
24
+ }
25
+ else {
26
+ resolve();
27
+ }
28
+ });
29
+ });
30
+ }
31
+ return { start, stop };
32
+ }
33
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/server/lifecycle.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAO9D,MAAM,UAAU,eAAe,CAAC,GAAgB,EAAE,MAAwB;IACxE,IAAI,MAAM,GAAkB,IAAI,CAAA;IAEhC,KAAK,UAAU,KAAK;QAClB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;gBAC/D,OAAO,EAAE,CAAA;YACX,CAAC,CAAC,CAAA;YACF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC5B,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,UAAU,IAAI;QACjB,MAAM,sBAAsB,EAAE,CAAA;QAE9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,OAAO,EAAE,CAAA;gBACT,OAAM;YACR,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,CAAA;YACtB,MAAM,GAAG,IAAI,CAAA;YAEb,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACpB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACtB,MAAM,CAAC,GAAG,CAAC,CAAA;gBACb,CAAC;qBAAM,CAAC;oBACN,OAAO,EAAE,CAAA;gBACX,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACxB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { Application } from 'express';
2
+ import type { HypersonicConfig } from '../config/types.js';
3
+ import type { Env } from '../config/env.js';
4
+ import type { PrismaClientLike } from '../database/client.js';
5
+ import type { AuthInstance } from '../auth/setup.js';
6
+ export interface CreateAppOptions {
7
+ config: HypersonicConfig;
8
+ env: Env;
9
+ prisma: PrismaClientLike;
10
+ }
11
+ export interface HypersonicApp {
12
+ express: Application;
13
+ auth: AuthInstance;
14
+ start: () => Promise<void>;
15
+ stop: () => Promise<void>;
16
+ }
17
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,gBAAgB,CAAA;IACxB,GAAG,EAAE,GAAG,CAAA;IACR,MAAM,EAAE,gBAAgB,CAAA;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,WAAW,CAAA;IACpB,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ export type DatabaseProvider = 'postgresql' | 'mysql' | 'sqlite' | 'mongodb';
2
+ /**
3
+ * Infers the database provider from the DATABASE_URL scheme.
4
+ * Used internally to configure Better Auth's Prisma adapter
5
+ * without requiring the developer to repeat the provider in hypersonic.config.ts.
6
+ *
7
+ * Accepted forms:
8
+ * postgresql:// | postgres:// → postgresql
9
+ * mysql:// | mysql2:// → mysql
10
+ * mongodb:// | mongodb+srv:// → mongodb
11
+ * file: → sqlite
12
+ */
13
+ export declare function detectProvider(databaseUrl: string): DatabaseProvider;
14
+ //# sourceMappingURL=detect-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-provider.d.ts","sourceRoot":"","sources":["../../src/utils/detect-provider.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAA;AAE5E;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CA8BpE"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Infers the database provider from the DATABASE_URL scheme.
3
+ * Used internally to configure Better Auth's Prisma adapter
4
+ * without requiring the developer to repeat the provider in hypersonic.config.ts.
5
+ *
6
+ * Accepted forms:
7
+ * postgresql:// | postgres:// → postgresql
8
+ * mysql:// | mysql2:// → mysql
9
+ * mongodb:// | mongodb+srv:// → mongodb
10
+ * file: → sqlite
11
+ */
12
+ export function detectProvider(databaseUrl) {
13
+ if (databaseUrl.startsWith('postgresql://') ||
14
+ databaseUrl.startsWith('postgres://')) {
15
+ return 'postgresql';
16
+ }
17
+ if (databaseUrl.startsWith('mysql://') ||
18
+ databaseUrl.startsWith('mysql2://')) {
19
+ return 'mysql';
20
+ }
21
+ if (databaseUrl.startsWith('mongodb://') ||
22
+ databaseUrl.startsWith('mongodb+srv://')) {
23
+ return 'mongodb';
24
+ }
25
+ if (databaseUrl.startsWith('file:')) {
26
+ return 'sqlite';
27
+ }
28
+ throw new Error(`Hypersonic: unrecognised DATABASE_URL scheme — could not detect a database provider.\n` +
29
+ ` Expected one of: postgresql://, postgres://, mysql://, mysql2://, mongodb://, mongodb+srv://, file:`);
30
+ }
31
+ //# sourceMappingURL=detect-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-provider.js","sourceRoot":"","sources":["../../src/utils/detect-provider.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB;IAChD,IACE,WAAW,CAAC,UAAU,CAAC,eAAe,CAAC;QACvC,WAAW,CAAC,UAAU,CAAC,aAAa,CAAC,EACrC,CAAC;QACD,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,IACE,WAAW,CAAC,UAAU,CAAC,UAAU,CAAC;QAClC,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,EACnC,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,IACE,WAAW,CAAC,UAAU,CAAC,YAAY,CAAC;QACpC,WAAW,CAAC,UAAU,CAAC,gBAAgB,CAAC,EACxC,CAAC;QACD,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,IAAI,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACpC,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,MAAM,IAAI,KAAK,CACb,wFAAwF;QACxF,uGAAuG,CACxG,CAAA;AACH,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Base class for all HTTP errors thrown within a Hypersonic application.
3
+ */
4
+ export declare class HttpError extends Error {
5
+ readonly statusCode: number;
6
+ constructor(statusCode: number, message: string);
7
+ }
8
+ export declare class NotFoundError extends HttpError {
9
+ constructor(message?: string);
10
+ }
11
+ export declare class UnauthorizedError extends HttpError {
12
+ constructor(message?: string);
13
+ }
14
+ export declare class ForbiddenError extends HttpError {
15
+ constructor(message?: string);
16
+ }
17
+ export declare class ValidationError extends HttpError {
18
+ constructor(message?: string);
19
+ }
20
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAgB,UAAU,EAAE,MAAM,CAAA;gBAEtB,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAKhD;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,OAAO,SAAc;CAIlC;AAED,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,OAAO,SAAiB;CAIrC;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,OAAO,SAAc;CAIlC;AAED,qBAAa,eAAgB,SAAQ,SAAS;gBAChC,OAAO,SAAyB;CAI7C"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Base class for all HTTP errors thrown within a Hypersonic application.
3
+ */
4
+ export class HttpError extends Error {
5
+ statusCode;
6
+ constructor(statusCode, message) {
7
+ super(message);
8
+ this.name = 'HttpError';
9
+ this.statusCode = statusCode;
10
+ }
11
+ }
12
+ export class NotFoundError extends HttpError {
13
+ constructor(message = 'Not Found') {
14
+ super(404, message);
15
+ this.name = 'NotFoundError';
16
+ }
17
+ }
18
+ export class UnauthorizedError extends HttpError {
19
+ constructor(message = 'Unauthorized') {
20
+ super(401, message);
21
+ this.name = 'UnauthorizedError';
22
+ }
23
+ }
24
+ export class ForbiddenError extends HttpError {
25
+ constructor(message = 'Forbidden') {
26
+ super(403, message);
27
+ this.name = 'ForbiddenError';
28
+ }
29
+ }
30
+ export class ValidationError extends HttpError {
31
+ constructor(message = 'Unprocessable Entity') {
32
+ super(422, message);
33
+ this.name = 'ValidationError';
34
+ }
35
+ }
36
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClB,UAAU,CAAQ;IAElC,YAAY,UAAkB,EAAE,OAAe;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,WAAW,CAAA;QACvB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,aAAc,SAAQ,SAAS;IAC1C,YAAY,OAAO,GAAG,WAAW;QAC/B,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACnB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,SAAS;IAC9C,YAAY,OAAO,GAAG,cAAc;QAClC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACnB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;IACjC,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,SAAS;IAC3C,YAAY,OAAO,GAAG,WAAW;QAC/B,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACnB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,SAAS;IAC5C,YAAY,OAAO,GAAG,sBAAsB;QAC1C,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACnB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@hypersonic-js/core",
3
+ "version": "0.1.0",
4
+ "description": "The core of Hypersonic.js — a modern Django-inspired full-stack TypeScript framework",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/Zesuperaker/hypersonic.git"
20
+ },
21
+ "author": "Zesuperaker",
22
+ "license": "MIT",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "engines": {
27
+ "node": "^24.0.0"
28
+ },
29
+ "dependencies": {
30
+ "@inertiajs/core": "3.3.0",
31
+ "@inertiajs/react": "3.3.0",
32
+ "@tailwindcss/vite": "4.3.0",
33
+ "@vitejs/plugin-react": "6.0.2",
34
+ "better-auth": "1.6.11",
35
+ "express": "5.2.1",
36
+ "react": "19.2.6",
37
+ "react-dom": "19.2.6",
38
+ "tailwindcss": "4.3.0",
39
+ "vite": "8.0.14",
40
+ "zod": "4.4.3"
41
+ },
42
+ "peerDependencies": {
43
+ "@prisma/client": "7.8.0",
44
+ "prisma": "7.8.0"
45
+ },
46
+ "devDependencies": {
47
+ "@prisma/client": "7.8.0",
48
+ "@types/express": "5.0.6",
49
+ "@types/node": "25.9.1",
50
+ "@types/react": "19.2.15",
51
+ "@types/react-dom": "19.2.3",
52
+ "@types/supertest": "7.2.0",
53
+ "@vitest/coverage-v8": "4.1.7",
54
+ "prisma": "7.8.0",
55
+ "supertest": "7.2.2",
56
+ "typescript": "6.0.3",
57
+ "vitest": "4.1.7"
58
+ },
59
+ "scripts": {
60
+ "build": "tsc",
61
+ "test": "vitest run",
62
+ "test:coverage": "vitest run --coverage",
63
+ "type-check": "tsc --noEmit",
64
+ "clean": "rm -rf dist coverage"
65
+ }
66
+ }