@geekmidas/constructs 10.0.0-alpha.27 → 10.0.0-alpha.29
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/dist/{Cron-BrW7ibXz.d.mts → Cron-B_iuyNxr.d.mts} +2 -2
- package/dist/{Cron-BKMCPeaa.d.cts.map → Cron-B_iuyNxr.d.mts.map} +1 -1
- package/dist/{Cron-BKMCPeaa.d.cts → Cron-CA2oa2nC.d.cts} +2 -2
- package/dist/{Cron-BrW7ibXz.d.mts.map → Cron-CA2oa2nC.d.cts.map} +1 -1
- package/dist/{CronBuilder-BGUGy4W1.d.mts → CronBuilder-BIKCLWu7.d.mts} +4 -4
- package/dist/{CronBuilder-BGUGy4W1.d.mts.map → CronBuilder-BIKCLWu7.d.mts.map} +1 -1
- package/dist/{CronBuilder-CaFDcewY.mjs → CronBuilder-BRkzUOAs.mjs} +11 -5
- package/dist/CronBuilder-BRkzUOAs.mjs.map +1 -0
- package/dist/{CronBuilder-DjaWVJVQ.d.cts → CronBuilder-B_KqaodP.d.cts} +4 -4
- package/dist/{CronBuilder-DjaWVJVQ.d.cts.map → CronBuilder-B_KqaodP.d.cts.map} +1 -1
- package/dist/{CronBuilder-uDIUw8yp.cjs → CronBuilder-BbnT7tqo.cjs} +10 -4
- package/dist/CronBuilder-BbnT7tqo.cjs.map +1 -0
- package/dist/{Endpoint-pTZrMs_h.d.mts → Endpoint-C-elwBdf.d.mts} +4 -4
- package/dist/{Endpoint-CrhoafsT.d.cts.map → Endpoint-C-elwBdf.d.mts.map} +1 -1
- package/dist/{Endpoint-CrhoafsT.d.cts → Endpoint-C7LDoVTL.d.cts} +4 -4
- package/dist/{Endpoint-pTZrMs_h.d.mts.map → Endpoint-C7LDoVTL.d.cts.map} +1 -1
- package/dist/{Endpoint-DCr5tlbJ.mjs → Endpoint-DO332Dnm.mjs} +2 -2
- package/dist/{Endpoint-DCr5tlbJ.mjs.map → Endpoint-DO332Dnm.mjs.map} +1 -1
- package/dist/{Endpoint-CtiPUpRX.cjs → Endpoint-QKuFg5gs.cjs} +2 -2
- package/dist/{Endpoint-CtiPUpRX.cjs.map → Endpoint-QKuFg5gs.cjs.map} +1 -1
- package/dist/{EndpointFactory-Bs8pu-yY.mjs → EndpointFactory-B9HAjDAP.mjs} +10 -7
- package/dist/EndpointFactory-B9HAjDAP.mjs.map +1 -0
- package/dist/{EndpointFactory-vE2C1NG1.cjs → EndpointFactory-BmO0WnXE.cjs} +9 -6
- package/dist/EndpointFactory-BmO0WnXE.cjs.map +1 -0
- package/dist/{EndpointFactory-BSLH4hSU.d.mts → EndpointFactory-CBKpedSq.d.mts} +4 -4
- package/dist/{EndpointFactory-BSLH4hSU.d.mts.map → EndpointFactory-CBKpedSq.d.mts.map} +1 -1
- package/dist/{EndpointFactory-CiCzFdMx.d.cts → EndpointFactory-Dw69QqzK.d.cts} +4 -4
- package/dist/{EndpointFactory-CiCzFdMx.d.cts.map → EndpointFactory-Dw69QqzK.d.cts.map} +1 -1
- package/dist/{HonoEndpointAdaptor-Be6UdK0r.mjs → HonoEndpointAdaptor-CiqAZ5Mt.mjs} +2 -2
- package/dist/{HonoEndpointAdaptor-Be6UdK0r.mjs.map → HonoEndpointAdaptor-CiqAZ5Mt.mjs.map} +1 -1
- package/dist/{HonoEndpointAdaptor-C9aivwKl.cjs → HonoEndpointAdaptor-DoEQdC3r.cjs} +2 -2
- package/dist/{HonoEndpointAdaptor-C9aivwKl.cjs.map → HonoEndpointAdaptor-DoEQdC3r.cjs.map} +1 -1
- package/dist/{Queue-BB2-DgqJ.cjs → Queue-B7yRXQmL.cjs} +4 -3
- package/dist/Queue-B7yRXQmL.cjs.map +1 -0
- package/dist/Queue-Bhzi2WQC.d.cts.map +1 -1
- package/dist/Queue-BoUA-e83.d.mts.map +1 -1
- package/dist/{Queue-Dfumm1WR.mjs → Queue-DpKTLl6E.mjs} +4 -3
- package/dist/Queue-DpKTLl6E.mjs.map +1 -0
- package/dist/{SubscriberBuilder-Dbiaskfi.d.mts → SubscriberBuilder-C2FRfd26.d.mts} +2 -2
- package/dist/{SubscriberBuilder-ChX5v948.d.cts.map → SubscriberBuilder-C2FRfd26.d.mts.map} +1 -1
- package/dist/{SubscriberBuilder-ChX5v948.d.cts → SubscriberBuilder-CjDGSTZc.d.cts} +2 -2
- package/dist/{SubscriberBuilder-Dbiaskfi.d.mts.map → SubscriberBuilder-CjDGSTZc.d.cts.map} +1 -1
- package/dist/{Topic-QqnO_6Dl.mjs → Topic-Ct3Le8Wt.mjs} +4 -3
- package/dist/Topic-Ct3Le8Wt.mjs.map +1 -0
- package/dist/{Topic-BaNCgP61.cjs → Topic-D5rlM9wS.cjs} +4 -3
- package/dist/Topic-D5rlM9wS.cjs.map +1 -0
- package/dist/Topic-D6blllEn.d.cts.map +1 -1
- package/dist/Topic-DVvheJ_-.d.mts.map +1 -1
- package/dist/adaptors/aws.cjs +2 -2
- package/dist/adaptors/aws.d.cts +3 -3
- package/dist/adaptors/aws.d.mts +3 -3
- package/dist/adaptors/aws.mjs +2 -2
- package/dist/adaptors/hono.cjs +1 -1
- package/dist/adaptors/hono.d.cts +1 -1
- package/dist/adaptors/hono.d.mts +1 -1
- package/dist/adaptors/hono.mjs +1 -1
- package/dist/adaptors/testing.cjs +7 -7
- package/dist/adaptors/testing.cjs.map +1 -1
- package/dist/adaptors/testing.d.cts +3 -3
- package/dist/adaptors/testing.d.cts.map +1 -1
- package/dist/adaptors/testing.d.mts +3 -3
- package/dist/adaptors/testing.d.mts.map +1 -1
- package/dist/adaptors/testing.mjs +7 -7
- package/dist/adaptors/testing.mjs.map +1 -1
- package/dist/{auth-BR2eYxIW.d.cts → auth-DkN7_Dnk.d.mts} +31 -13
- package/dist/auth-DkN7_Dnk.d.mts.map +1 -0
- package/dist/{auth-DtprqoJP.d.mts → auth-t77pKG-i.d.cts} +31 -13
- package/dist/auth-t77pKG-i.d.cts.map +1 -0
- package/dist/auth.cjs +8 -2
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +2 -2
- package/dist/auth.d.mts +2 -2
- package/dist/auth.mjs +8 -2
- package/dist/auth.mjs.map +1 -1
- package/dist/{cache-AgQUMma6.d.mts → cache-B2s0b-ss.d.mts} +2 -2
- package/dist/{cache-AgQUMma6.d.mts.map → cache-B2s0b-ss.d.mts.map} +1 -1
- package/dist/{cache-obq0fOIM.d.cts → cache-CYe2Kzjc.d.cts} +2 -2
- package/dist/{cache-obq0fOIM.d.cts.map → cache-CYe2Kzjc.d.cts.map} +1 -1
- package/dist/cache.d.cts +1 -1
- package/dist/cache.d.mts +1 -1
- package/dist/{construct-interface-Clz7_BwB.d.mts → construct-interface-D2gesxos.d.mts} +15 -6
- package/dist/{construct-interface-Clz7_BwB.d.mts.map → construct-interface-D2gesxos.d.mts.map} +1 -1
- package/dist/{construct-interface-DIaTNKax.d.cts → construct-interface-DPusCUSQ.d.cts} +15 -6
- package/dist/{construct-interface-DIaTNKax.d.cts.map → construct-interface-DPusCUSQ.d.cts.map} +1 -1
- package/dist/construct-interface.cjs +29 -0
- package/dist/construct-interface.cjs.map +1 -1
- package/dist/construct-interface.d.cts +2 -2
- package/dist/construct-interface.d.mts +2 -2
- package/dist/construct-interface.mjs +28 -1
- package/dist/construct-interface.mjs.map +1 -1
- package/dist/credential.d.cts +1 -1
- package/dist/credential.d.mts +1 -1
- package/dist/crons/index.cjs +1 -1
- package/dist/crons/index.d.cts +2 -2
- package/dist/crons/index.d.mts +2 -2
- package/dist/crons/index.mjs +1 -1
- package/dist/database/kysely.cjs +13 -2
- package/dist/database/kysely.cjs.map +1 -1
- package/dist/database/kysely.d.cts +1 -1
- package/dist/database/kysely.d.mts +1 -1
- package/dist/database/kysely.mjs +13 -2
- package/dist/database/kysely.mjs.map +1 -1
- package/dist/email.d.cts +1 -1
- package/dist/email.d.mts +1 -1
- package/dist/endpoints/index.cjs +2 -2
- package/dist/endpoints/index.d.cts +2 -2
- package/dist/endpoints/index.d.mts +2 -2
- package/dist/endpoints/index.mjs +2 -2
- package/dist/file-server.d.cts +1 -1
- package/dist/file-server.d.mts +1 -1
- package/dist/functions/index.cjs +1 -1
- package/dist/functions/index.d.cts +1 -1
- package/dist/functions/index.d.mts +1 -1
- package/dist/functions/index.mjs +1 -1
- package/dist/{functions-VnOTEp74.cjs → functions-DKN6aQWJ.cjs} +17 -5
- package/dist/functions-DKN6aQWJ.cjs.map +1 -0
- package/dist/{functions-faoJOUmr.mjs → functions-DlFm8HmH.mjs} +18 -6
- package/dist/{functions-faoJOUmr.mjs.map → functions-DlFm8HmH.mjs.map} +1 -1
- package/dist/{index-nBQT1tAh.d.cts → index-BI23K3wy.d.cts} +2 -2
- package/dist/{index-DU3Twmwh.d.mts.map → index-BI23K3wy.d.cts.map} +1 -1
- package/dist/{index-DU3Twmwh.d.mts → index-yb3fXNyH.d.mts} +2 -2
- package/dist/{index-nBQT1tAh.d.cts.map → index-yb3fXNyH.d.mts.map} +1 -1
- package/dist/{kysely-CXi49zc2.d.cts → kysely-CEVXacPN.d.cts} +12 -3
- package/dist/{kysely-CXi49zc2.d.cts.map → kysely-CEVXacPN.d.cts.map} +1 -1
- package/dist/{kysely-D3-G5b0j.d.mts → kysely-mcTdPNhl.d.mts} +12 -3
- package/dist/{kysely-D3-G5b0j.d.mts.map → kysely-mcTdPNhl.d.mts.map} +1 -1
- package/dist/object-storage.d.cts +1 -1
- package/dist/object-storage.d.mts +1 -1
- package/dist/queue/index.cjs +1 -1
- package/dist/queue/index.d.cts +1 -1
- package/dist/queue/index.d.mts +1 -1
- package/dist/queue/index.mjs +1 -1
- package/dist/rest-api.cjs +1 -1
- package/dist/rest-api.d.cts +3 -3
- package/dist/rest-api.d.mts +3 -3
- package/dist/rest-api.mjs +1 -1
- package/dist/site.d.cts +1 -1
- package/dist/site.d.mts +1 -1
- package/dist/subscribers/index.d.cts +1 -1
- package/dist/subscribers/index.d.mts +1 -1
- package/dist/topic/index.cjs +1 -1
- package/dist/topic/index.mjs +1 -1
- package/dist/worker.cjs +2 -2
- package/dist/worker.d.cts +5 -5
- package/dist/worker.d.mts +5 -5
- package/dist/worker.mjs +2 -2
- package/package.json +31 -31
- package/dist/CronBuilder-CaFDcewY.mjs.map +0 -1
- package/dist/CronBuilder-uDIUw8yp.cjs.map +0 -1
- package/dist/EndpointFactory-Bs8pu-yY.mjs.map +0 -1
- package/dist/EndpointFactory-vE2C1NG1.cjs.map +0 -1
- package/dist/Queue-BB2-DgqJ.cjs.map +0 -1
- package/dist/Queue-Dfumm1WR.mjs.map +0 -1
- package/dist/Topic-BaNCgP61.cjs.map +0 -1
- package/dist/Topic-QqnO_6Dl.mjs.map +0 -1
- package/dist/auth-BR2eYxIW.d.cts.map +0 -1
- package/dist/auth-DtprqoJP.d.mts.map +0 -1
- package/dist/functions-VnOTEp74.cjs.map +0 -1
package/dist/auth.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.cjs","names":["canonicalId","environmentCase","provideKey","serviceKey","edgeTo","betterAuth"],"sources":["../src/auth.ts"],"sourcesContent":["/**\n * `BetterAuth` — a declared auth server.\n *\n * It is three things at once, which is what makes it the construct that\n * exercises most of the model: a consumer of a database, a producer of an\n * authorizer, and a set of endpoints. This lands the first two.\n *\n * Its tables live in a schema tenant it is *given* — `orders.schema('AuthDb')`\n * — rather than one it invents. The tenant declares the schema and the role;\n * this declares the one thing left, the signing secret, and reads back the keys\n * it needs.\n *\n * :::caution\n * The isolation that arrangement is *for* — the application's own role holding\n * no grant on these tables, so a compromised handler cannot read sessions — does\n * **not hold yet**. No target creates the per-schema role: both connect as the\n * database owner, so today the tenant is a `search_path`, which is a namespace\n * and not a privilege boundary. See \"Roles are declared and never created\" in\n * `docs/design/constructs-outstanding.md`.\n * :::\n *\n * It declares its own surface, so where it answers, who may call it, and the\n * domain its cookies are scoped to all arrive as environment resolved by the\n * target — none of them guessed from a port, and none of them listed by hand in\n * application config.\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tenvironmentCase,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport { betterAuth } from 'better-auth';\nimport type { Hono } from 'hono';\nimport type { Kysely } from 'kysely';\nimport {\n\ttype Authenticator,\n\ttype Construct,\n\ttype Consumable,\n\tedgeTo,\n} from './construct-interface';\n\n/** The server better-auth hands back. */\nexport type AuthServer = ReturnType<typeof betterAuth>;\n\n/** A signed-in session as better-auth hands it back: `{ user, session }`. */\nexport type AuthSession = AuthServer['$Infer']['Session'];\n\n/** Everything better-auth takes, minus what the construct owns. */\nexport type BetterAuthOptions = Omit<\n\tParameters<typeof betterAuth>[0],\n\t'database' | 'secret' | 'basePath' | 'baseURL'\n>;\n\nexport interface BetterAuthConfig<TDatabase extends Consumable> {\n\t/**\n\t * The schema tenant its tables live in.\n\t *\n\t * A construct rather than a URL: the tenant already declares its schema and\n\t * its role, and taking the declaration is what puts the edge in the graph\n\t * instead of a connection string in two places.\n\t */\n\tdatabase: TDatabase;\n\t/**\n\t * The app that serves this auth server, relative to the workspace root —\n\t * `'apps/auth'`.\n\t *\n\t * Required, for the reason `RestApi.path` is. Not to be confused with\n\t * `basePath`, which is where its routes are mounted in a URL.\n\t */\n\tpath: string;\n\t/**\n\t * Where the auth routes are mounted, e.g. `/api/auth`.\n\t *\n\t * Structural — it is part of the URL every client calls, so it cannot differ\n\t * between stages the way the host can.\n\t */\n\tbasePath?: string;\n\t/**\n\t * The rest of better-auth's options: providers, plugins, email settings.\n\t *\n\t * A function when they need another construct — a magic-link plugin has to\n\t * *send* the link, and the thing that sends mail is a construct whose client\n\t * has to be registered. It is handed the same `ServiceRegisterOptions` this\n\t * construct was, so the env parser flows through the graph rather than being\n\t * imported out of application config.\n\t */\n\toptions?:\n\t\t| BetterAuthOptions\n\t\t| ((\n\t\t\t\toptions: ServiceRegisterOptions,\n\t\t ) => BetterAuthOptions | Promise<BetterAuthOptions>);\n}\n\nconst DEFAULT_BASE_PATH = '/api/auth';\n\nexport class BetterAuth<\n\tTName extends string = string,\n\tTDatabase extends Consumable = Consumable,\n> implements Construct<TName, AuthServer>, Authenticator<AuthSession>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, AuthServer>;\n\treadonly basePath: string;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the keys the\n\t * target publishes and the keys the server reads cannot drift.\n\t */\n\tprivate readonly keys: {\n\t\tsecretId: string;\n\t\tsecret: string;\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: BetterAuthConfig<TDatabase>,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.basePath = config.basePath ?? DEFAULT_BASE_PATH;\n\n\t\t// A secret's name is its key: `Auth` signs with `AUTH_SECRET`, which is\n\t\t// also what better-auth's own tooling looks for.\n\t\tconst secretId = `${canonical}Secret`;\n\t\tthis.keys = {\n\t\t\tsecretId,\n\t\t\tsecret: environmentCase(secretId),\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (options) => this.connect(options),\n\t\t};\n\t}\n\n\t/**\n\t * A signing secret and a surface.\n\t *\n\t * The database is *not* declared here — the tenant that was passed in\n\t * declares it, and declaring it twice is the duplication the whole model\n\t * removes. What is declared is what this construct owns: the key it signs\n\t * with, and the routes it answers on.\n\t *\n\t * One endpoint, wildcarded. Better Auth routes internally and the set of\n\t * paths depends on which capabilities are enabled, so enumerating them here\n\t * would be a copy of its router that goes stale on its next release. The\n\t * surface's job is to send everything under `basePath` to one handler, which\n\t * is exactly what the declaration says.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'secret',\n\t\t\t\tid: this.keys.secretId,\n\t\t\t\tprovides: [this.keys.secret],\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t\tendpoints: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: `${this.id}Handler`,\n\t\t\t\t\t\thandler: `${this.id}.handler`,\n\t\t\t\t\t\tmethod: 'ANY',\n\t\t\t\t\t\tpath: `${this.basePath}/*`,\n\t\t\t\t\t\t// Read off the tenant rather than written down: this\n\t\t\t\t\t\t// construct takes whatever database it is given, and a\n\t\t\t\t\t\t// hardcoded kind is a second statement of a fact the\n\t\t\t\t\t\t// tenant already makes.\n\t\t\t\t\t\tdependencies: [edgeTo(this.config.database)],\n\t\t\t\t\t\trequires: [this.keys.secret],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * Whose request this is: the session for these headers, or `null`.\n\t *\n\t * Asked of the auth server over HTTP, at the URL the `.auth()` edge injects —\n\t * so it is the same call whether the server runs in this process, beside\n\t * it, or behind MSW in a test. Only the session headers are forwarded; the\n\t * surface decides which.\n\t */\n\tasync verify(\n\t\theaders: Headers,\n\t\tenvParser: EnvironmentParser<{}>,\n\t): Promise<AuthSession | null> {\n\t\tconst { url } = envParser\n\t\t\t.create((get) => ({ url: get(this.keys.url).string() }))\n\t\t\t.parse();\n\n\t\tconst response = await fetch(`${url}${this.basePath}/get-session`, {\n\t\t\theaders,\n\t\t});\n\n\t\t// Better Auth answers `null` for a request with no session. Anything\n\t\t// other than a 200 is the server failing, which is not the same thing as\n\t\t// \"signed out\" and must not be read as it.\n\t\tif (!response.ok) {\n\t\t\tthrow new SessionCheckFailed(this.id, response.status);\n\t\t}\n\n\t\treturn (await response.json()) as AuthSession | null;\n\t}\n\n\t/**\n\t * Better Auth's own migrations, for the app's migrate step.\n\t *\n\t * It brings its own schema — users, sessions, accounts, verifications — so\n\t * the app never writes those tables and never gets to drift from them.\n\t */\n\tasync migrations(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<() => Promise<void>> {\n\t\t// As the owner, not the runtime role. Creating tables is DDL, and the\n\t\t// whole point of the split is that a handler's role cannot do it — so a\n\t\t// migrator that connected the way a handler does would fail on the first\n\t\t// `CREATE TABLE` rather than working by accident.\n\t\t//\n\t\t// The options, not a server built from them: better-auth checks its\n\t\t// schema when a server starts, and before the migration that creates the\n\t\t// tables it can only report them missing — an error in every migrate\n\t\t// log, about the very tables the next line creates.\n\t\tconst resolved = await this.optionsFor(options, { owner: true });\n\t\t// better-auth 1.7 split the migration builder out of `better-auth/db`\n\t\t// into its own entry, so importing it no longer drags the whole db layer\n\t\t// in behind it.\n\t\tconst { getMigrations } = await import('better-auth/db/migration');\n\t\tconst { runMigrations } = await getMigrations(resolved);\n\n\t\treturn runMigrations;\n\t}\n\n\t/**\n\t * The server this surface runs as, built from its own declaration.\n\t *\n\t * A surface is a deploy unit, so something has to serve it, and the thing\n\t * that knows how is the construct — not a generator writing a mount into\n\t * somebody else's entry point. That is how the auth server came to be\n\t * mounted by a hook in the application it was supposed to be separate from,\n\t * and then to be served by nothing at all when the hook was deleted.\n\t *\n\t * The routes come from {@link declare}: one wildcard under `basePath`, read\n\t * off the same declaration the manifest carries, so the surface the graph\n\t * describes and the surface that answers requests cannot disagree.\n\t *\n\t * ```ts\n\t * // .gkm/server/app.ts — the whole generated entry\n\t * import { auth } from '@acme/constructs/auth.js';\n\t * export const { app } = await auth.server({ envParser });\n\t * ```\n\t */\n\tasync server(options: ServiceRegisterOptions): Promise<{ app: Hono }> {\n\t\tconst { Hono } = await import('hono');\n\t\tconst { cors } = await import('hono/cors');\n\t\tconst app = new Hono();\n\t\tconst server = await this.connect(options);\n\n\t\t// Who may call this surface, read off the graph. The same list Better\n\t\t// Auth checks for CSRF — it arrives as `<ID>_TRUSTED_ORIGINS`, composed\n\t\t// by the target from whatever declared an edge to this construct, so a\n\t\t// site that depends on the auth server is allowed to reach it and\n\t\t// nothing else is. Nobody writes an origin down.\n\t\tconst { origins } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\torigins: get(this.keys.trustedOrigins)\n\t\t\t\t\t.string()\n\t\t\t\t\t.default('')\n\t\t\t\t\t.transform((value: string) =>\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t.split(',')\n\t\t\t\t\t\t\t.map((origin) => origin.trim())\n\t\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t\t),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\tapp.use('*', cors({ origin: origins, credentials: true, maxAge: 3600 }));\n\n\t\t// Read off the declaration rather than restated here. `ANY` is every\n\t\t// method Better Auth routes internally, which is the reason the\n\t\t// declaration wildcards rather than enumerating.\n\t\tconst [surface] = this.declare().filter(\n\t\t\t(d): d is Extract<Declaration, { kind: 'rest-api' }> =>\n\t\t\t\td.kind === 'rest-api',\n\t\t);\n\n\t\tfor (const endpoint of surface?.endpoints ?? []) {\n\t\t\tconst methods =\n\t\t\t\tendpoint.method === 'ANY'\n\t\t\t\t\t? (['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const)\n\t\t\t\t\t: ([endpoint.method] as const);\n\n\t\t\tapp.on([...methods], endpoint.path, (c) => server.handler(c.req.raw));\n\t\t}\n\n\t\treturn { app };\n\t}\n\n\t/**\n\t * The ids of the better-auth plugins this server runs — what a client has\n\t * to pair its own plugins with (`magic-link` with `magicLinkClient`).\n\t * Resolved the way the server resolves them, options callback included.\n\t */\n\tasync pluginIds(options: ServiceRegisterOptions): Promise<string[]> {\n\t\tconst { plugins = [] } = await this.configured(options);\n\t\treturn plugins.map(({ id }) => id);\n\t}\n\n\t/** Better-auth's options as given — a value, or a callback of the environment. */\n\tprivate async configured(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<BetterAuthOptions> {\n\t\t// Hoisted: narrowing a property of `this` is not preserved across the\n\t\t// await, and the false branch is the plain-object form.\n\t\tconst configure = this.config.options;\n\t\treturn typeof configure === 'function'\n\t\t\t? await configure(options)\n\t\t\t: (configure ?? {});\n\t}\n\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<AuthServer> {\n\t\t// `Auth<O>` is invariant in its options in better-auth 1.7, so the value\n\t\t// built from a concrete literal is not assignable to the `AuthServer`\n\t\t// alias, which names the constraint. The runtime object is the same one\n\t\t// either way; the assertion keeps the variance where it happens instead\n\t\t// of spreading a generic through the construct's public type.\n\t\treturn betterAuth(await this.optionsFor(options, as)) as AuthServer;\n\t}\n\n\t/**\n\t * Everything better-auth is given: the app's options, and what the construct\n\t * owns — its secret, its URL, its tenant's connection, the origins and cookie\n\t * domain its graph derives. One place, so the server and its migrations are\n\t * built from the same options.\n\t */\n\tprivate async optionsFor(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<Parameters<typeof betterAuth>[0]> {\n\t\tconst { secret, baseUrl, trustedOrigins, cookieDomain } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\tsecret: get(this.keys.secret).string(),\n\t\t\t\t// The surface's own URL, resolved by the target — not guessed from\n\t\t\t\t// a port, which was wrong the moment the server moved to 3001.\n\t\t\t\tbaseUrl: get(this.keys.url).string(),\n\t\t\t\t// Better Auth's CSRF check applies to every caller, not just\n\t\t\t\t// browsers, so an API calling the auth server is rejected unless\n\t\t\t\t// its origin is trusted. The list is derived from the graph and\n\t\t\t\t// arrives as one comma-separated value, for the same reason every\n\t\t\t\t// other derived value arrives as a string: it crosses a process\n\t\t\t\t// boundary as env.\n\t\t\t\ttrustedOrigins: get(this.keys.trustedOrigins)\n\t\t\t\t\t.string()\n\t\t\t\t\t.default('')\n\t\t\t\t\t.transform((value) =>\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t.split(',')\n\t\t\t\t\t\t\t.map((origin) => origin.trim())\n\t\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t\t),\n\t\t\t\t// The domain a session cookie has to carry to be readable by a\n\t\t\t\t// frontend on a sibling host. Optional because there is often\n\t\t\t\t// nothing to widen to: locally everything shares `localhost`,\n\t\t\t\t// where cookies ignore the port and a `Domain` would only be a\n\t\t\t\t// value the browser refuses.\n\t\t\t\tcookieDomain: get(this.keys.cookieDomain).string().optional(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\t// The tenant's own client: one construct, one connection, so what auth\n\t\t// writes and what the browser inspects cannot be two different databases.\n\t\t// A migration asks the tenant for its owner connection, which the tenant\n\t\t// exposes as a `Service` and not as anything `.dependsOn()` would take.\n\t\tconst tenant = this.config.database as Consumable & {\n\t\t\towner?: Service<string, unknown>;\n\t\t};\n\t\tconst source = as.owner && tenant.owner ? tenant.owner : tenant.service;\n\n\t\tconst db = (await source.register(options)) as Kysely<\n\t\t\tRecord<string, never>\n\t\t>;\n\n\t\tconst configured = await this.configured(options);\n\n\t\treturn {\n\t\t\t...configured,\n\t\t\tsecret,\n\t\t\tbaseURL: baseUrl,\n\t\t\tbasePath: this.basePath,\n\t\t\tdatabase: { db, type: 'postgres' },\n\t\t\t// Whatever the app added wins over the derived list rather than\n\t\t\t// replacing it: an origin nobody declared is still sometimes real.\n\t\t\ttrustedOrigins: [\n\t\t\t\t...trustedOrigins,\n\t\t\t\t...(configured.trustedOrigins &&\n\t\t\t\tArray.isArray(configured.trustedOrigins)\n\t\t\t\t\t? configured.trustedOrigins\n\t\t\t\t\t: []),\n\t\t\t],\n\t\t\tadvanced: {\n\t\t\t\t...configured.advanced,\n\t\t\t\t// Only when a domain was derived. Better Auth reads the presence\n\t\t\t\t// of this block as intent, so enabling it with no domain would\n\t\t\t\t// widen the cookie to whatever host happened to set it — and the\n\t\t\t\t// case with nothing to widen to is the common one, not an error.\n\t\t\t\t...(cookieDomain\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tcrossSubDomainCookies: {\n\t\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\t\tdomain: cookieDomain,\n\t\t\t\t\t\t\t\t...configured.advanced?.crossSubDomainCookies,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t}\n}\n\n/** The auth server answered a session check with something other than 200. */\nexport class SessionCheckFailed extends Error {\n\tconstructor(\n\t\treadonly authenticator: string,\n\t\treadonly status: number,\n\t) {\n\t\tsuper(\n\t\t\t`'${authenticator}' answered a session check with ${status}. ` +\n\t\t\t\t`A request with no session gets 200 and null; this is the server ` +\n\t\t\t\t`failing — check that it is running and reachable at its URL.`,\n\t\t);\n\t\tthis.name = 'SessionCheckFailed';\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.cjs","names":["canonicalId","environmentCase","provideKey","serviceKey","edgeTo","betterAuth"],"sources":["../src/auth.ts"],"sourcesContent":["/**\n * `BetterAuth` — a declared auth server.\n *\n * It is three things at once, which is what makes it the construct that\n * exercises most of the model: a consumer of a database, a producer of an\n * authorizer, and a set of endpoints. This lands the first two.\n *\n * Its tables live in a schema tenant it is *given* — `orders.schema('AuthDb')`\n * — rather than one it invents. The tenant declares the schema and the role;\n * this declares the one thing left, the signing secret, and reads back the keys\n * it needs.\n *\n * :::caution\n * The isolation that arrangement is *for* — the application's own role holding\n * no grant on these tables, so a compromised handler cannot read sessions — does\n * **not hold yet**. No target creates the per-schema role: both connect as the\n * database owner, so today the tenant is a `search_path`, which is a namespace\n * and not a privilege boundary. See \"Roles are declared and never created\" in\n * `docs/design/constructs-outstanding.md`.\n * :::\n *\n * It declares its own surface, so where it answers, who may call it, and the\n * domain its cookies are scoped to all arrive as environment resolved by the\n * target — none of them guessed from a port, and none of them listed by hand in\n * application config.\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tenvironmentCase,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type {\n\tService,\n\tServiceRecord,\n\tServiceRegisterOptions,\n} from '@geekmidas/services';\nimport { betterAuth } from 'better-auth';\nimport type { Hono } from 'hono';\nimport type { Kysely } from 'kysely';\nimport {\n\ttype Authenticator,\n\ttype Construct,\n\ttype Consumable,\n\tedgeTo,\n\ttype ServicesOf,\n} from './construct-interface';\n\n/** The server better-auth hands back. */\nexport type AuthServer = ReturnType<typeof betterAuth>;\n\n/** A signed-in session as better-auth hands it back: `{ user, session }`. */\nexport type AuthSession = AuthServer['$Infer']['Session'];\n\n/** Everything better-auth takes, minus what the construct owns. */\nexport type BetterAuthOptions = Omit<\n\tParameters<typeof betterAuth>[0],\n\t'database' | 'secret' | 'basePath' | 'baseURL'\n>;\n\n/**\n * What an `options` callback is handed: the options this construct was\n * registered with, and a client for each construct its `dependsOn` names —\n * keyed by service name, exactly as an endpoint's handler receives them.\n */\nexport type BetterAuthOptionsContext<TUses extends readonly Consumable[]> =\n\tServiceRegisterOptions & {\n\t\tservices: ServiceRecord<[...ServicesOf<TUses>]>;\n\t};\n\nexport interface BetterAuthConfig<\n\tTDatabase extends Consumable,\n\tTUses extends readonly Consumable[] = readonly [],\n> {\n\t/**\n\t * The schema tenant its tables live in.\n\t *\n\t * A construct rather than a URL: the tenant already declares its schema and\n\t * its role, and taking the declaration is what puts the edge in the graph\n\t * instead of a connection string in two places.\n\t */\n\tdatabase: TDatabase;\n\t/**\n\t * The app that serves this auth server, relative to the workspace root —\n\t * `'apps/auth'`.\n\t *\n\t * Required, for the reason `RestApi.path` is. Not to be confused with\n\t * `basePath`, which is where its routes are mounted in a URL.\n\t */\n\tpath: string;\n\t/**\n\t * Where the auth routes are mounted, e.g. `/api/auth`.\n\t *\n\t * Structural — it is part of the URL every client calls, so it cannot differ\n\t * between stages the way the host can.\n\t */\n\tbasePath?: string;\n\t/**\n\t * The constructs `options` uses — the mailer a magic link goes through,\n\t * most often.\n\t *\n\t * One list, two jobs, the way an endpoint's `.dependsOn()` has: each is an\n\t * edge on the server's handler, so whatever is composed from the edges (a\n\t * container's environment, a deploy's grants) includes it; and each is\n\t * handed to `options` as a client in `services`, so there is no other way\n\t * for `options` to reach it — nothing to import and register by hand, and\n\t * so nothing that can be used without being declared.\n\t */\n\tdependsOn?: TUses;\n\t/**\n\t * The rest of better-auth's options: providers, plugins, email settings.\n\t *\n\t * A function when they need another construct — a magic-link plugin has to\n\t * *send* the link. It is handed the options this construct was registered\n\t * with, and `services`: a client for each construct in `dependsOn`.\n\t */\n\toptions?:\n\t\t| BetterAuthOptions\n\t\t| ((\n\t\t\t\tcontext: BetterAuthOptionsContext<TUses>,\n\t\t ) => BetterAuthOptions | Promise<BetterAuthOptions>);\n}\n\nconst DEFAULT_BASE_PATH = '/api/auth';\n\nexport class BetterAuth<\n\tTName extends string = string,\n\tTDatabase extends Consumable = Consumable,\n\tconst TUses extends readonly Consumable[] = readonly [],\n> implements Construct<TName, AuthServer>, Authenticator<AuthSession>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, AuthServer>;\n\treadonly basePath: string;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the keys the\n\t * target publishes and the keys the server reads cannot drift.\n\t */\n\tprivate readonly keys: {\n\t\tsecretId: string;\n\t\tsecret: string;\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: BetterAuthConfig<TDatabase, TUses>,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.basePath = config.basePath ?? DEFAULT_BASE_PATH;\n\n\t\t// A secret's name is its key: `Auth` signs with `AUTH_SECRET`, which is\n\t\t// also what better-auth's own tooling looks for.\n\t\tconst secretId = `${canonical}Secret`;\n\t\tthis.keys = {\n\t\t\tsecretId,\n\t\t\tsecret: environmentCase(secretId),\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (options) => this.connect(options),\n\t\t};\n\t}\n\n\t/**\n\t * A signing secret and a surface.\n\t *\n\t * The database is *not* declared here — the tenant that was passed in\n\t * declares it, and declaring it twice is the duplication the whole model\n\t * removes. What is declared is what this construct owns: the key it signs\n\t * with, and the routes it answers on.\n\t *\n\t * One endpoint, wildcarded. Better Auth routes internally and the set of\n\t * paths depends on which capabilities are enabled, so enumerating them here\n\t * would be a copy of its router that goes stale on its next release. The\n\t * surface's job is to send everything under `basePath` to one handler, which\n\t * is exactly what the declaration says.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'secret',\n\t\t\t\tid: this.keys.secretId,\n\t\t\t\tprovides: [this.keys.secret],\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t\tendpoints: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: `${this.id}Handler`,\n\t\t\t\t\t\thandler: `${this.id}.handler`,\n\t\t\t\t\t\tmethod: 'ANY',\n\t\t\t\t\t\tpath: `${this.basePath}/*`,\n\t\t\t\t\t\t// Read off the tenant rather than written down: this\n\t\t\t\t\t\t// construct takes whatever database it is given, and a\n\t\t\t\t\t\t// hardcoded kind is a second statement of a fact the\n\t\t\t\t\t\t// tenant already makes.\n\t\t\t\t\t\tdependencies: [\n\t\t\t\t\t\t\tedgeTo(this.config.database),\n\t\t\t\t\t\t\t...(this.config.dependsOn ?? []).map(edgeTo),\n\t\t\t\t\t\t],\n\t\t\t\t\t\trequires: [this.keys.secret],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * Whose request this is: the session for these headers, or `null`.\n\t *\n\t * Asked of the auth server over HTTP, at the URL the `.auth()` edge injects —\n\t * so it is the same call whether the server runs in this process, beside\n\t * it, or behind MSW in a test. Only the session headers are forwarded; the\n\t * surface decides which.\n\t */\n\tasync verify(\n\t\theaders: Headers,\n\t\tenvParser: EnvironmentParser<{}>,\n\t): Promise<AuthSession | null> {\n\t\tconst { url } = envParser\n\t\t\t.create((get) => ({ url: get(this.keys.url).string() }))\n\t\t\t.parse();\n\n\t\tconst response = await fetch(`${url}${this.basePath}/get-session`, {\n\t\t\theaders,\n\t\t});\n\n\t\t// Better Auth answers `null` for a request with no session. Anything\n\t\t// other than a 200 is the server failing, which is not the same thing as\n\t\t// \"signed out\" and must not be read as it.\n\t\tif (!response.ok) {\n\t\t\tthrow new SessionCheckFailed(this.id, response.status);\n\t\t}\n\n\t\treturn (await response.json()) as AuthSession | null;\n\t}\n\n\t/**\n\t * Better Auth's own migrations, for the app's migrate step.\n\t *\n\t * It brings its own schema — users, sessions, accounts, verifications — so\n\t * the app never writes those tables and never gets to drift from them.\n\t */\n\tasync migrations(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<() => Promise<void>> {\n\t\t// As the owner, not the runtime role. Creating tables is DDL, and the\n\t\t// whole point of the split is that a handler's role cannot do it — so a\n\t\t// migrator that connected the way a handler does would fail on the first\n\t\t// `CREATE TABLE` rather than working by accident.\n\t\t//\n\t\t// The options, not a server built from them: better-auth checks its\n\t\t// schema when a server starts, and before the migration that creates the\n\t\t// tables it can only report them missing — an error in every migrate\n\t\t// log, about the very tables the next line creates.\n\t\tconst resolved = await this.optionsFor(options, { owner: true });\n\t\t// better-auth 1.7 split the migration builder out of `better-auth/db`\n\t\t// into its own entry, so importing it no longer drags the whole db layer\n\t\t// in behind it.\n\t\tconst { getMigrations } = await import('better-auth/db/migration');\n\t\tconst { runMigrations } = await getMigrations(resolved);\n\n\t\treturn runMigrations;\n\t}\n\n\t/**\n\t * The server this surface runs as, built from its own declaration.\n\t *\n\t * A surface is a deploy unit, so something has to serve it, and the thing\n\t * that knows how is the construct — not a generator writing a mount into\n\t * somebody else's entry point. That is how the auth server came to be\n\t * mounted by a hook in the application it was supposed to be separate from,\n\t * and then to be served by nothing at all when the hook was deleted.\n\t *\n\t * The routes come from {@link declare}: one wildcard under `basePath`, read\n\t * off the same declaration the manifest carries, so the surface the graph\n\t * describes and the surface that answers requests cannot disagree.\n\t *\n\t * ```ts\n\t * // .gkm/server/app.ts — the whole generated entry\n\t * import { auth } from '@acme/constructs/auth.js';\n\t * export const { app } = await auth.server({ envParser });\n\t * ```\n\t */\n\tasync server(options: ServiceRegisterOptions): Promise<{ app: Hono }> {\n\t\tconst { Hono } = await import('hono');\n\t\tconst { cors } = await import('hono/cors');\n\t\tconst app = new Hono();\n\t\tconst server = await this.connect(options);\n\n\t\t// Who may call this surface, read off the graph. The same list Better\n\t\t// Auth checks for CSRF — it arrives as `<ID>_TRUSTED_ORIGINS`, composed\n\t\t// by the target from whatever declared an edge to this construct, so a\n\t\t// site that depends on the auth server is allowed to reach it and\n\t\t// nothing else is. Nobody writes an origin down.\n\t\tconst { origins } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\torigins: get(this.keys.trustedOrigins)\n\t\t\t\t\t.string()\n\t\t\t\t\t.default('')\n\t\t\t\t\t.transform((value: string) =>\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t.split(',')\n\t\t\t\t\t\t\t.map((origin) => origin.trim())\n\t\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t\t),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\tapp.use('*', cors({ origin: origins, credentials: true, maxAge: 3600 }));\n\n\t\t// Read off the declaration rather than restated here. `ANY` is every\n\t\t// method Better Auth routes internally, which is the reason the\n\t\t// declaration wildcards rather than enumerating.\n\t\tconst [surface] = this.declare().filter(\n\t\t\t(d): d is Extract<Declaration, { kind: 'rest-api' }> =>\n\t\t\t\td.kind === 'rest-api',\n\t\t);\n\n\t\tfor (const endpoint of surface?.endpoints ?? []) {\n\t\t\tconst methods =\n\t\t\t\tendpoint.method === 'ANY'\n\t\t\t\t\t? (['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const)\n\t\t\t\t\t: ([endpoint.method] as const);\n\n\t\t\tapp.on([...methods], endpoint.path, (c) => server.handler(c.req.raw));\n\t\t}\n\n\t\treturn { app };\n\t}\n\n\t/**\n\t * The ids of the better-auth plugins this server runs — what a client has\n\t * to pair its own plugins with (`magic-link` with `magicLinkClient`).\n\t * Resolved the way the server resolves them, options callback included.\n\t */\n\tasync pluginIds(options: ServiceRegisterOptions): Promise<string[]> {\n\t\tconst { plugins = [] } = await this.configured(options);\n\t\treturn plugins.map(({ id }) => id);\n\t}\n\n\t/** Better-auth's options as given — a value, or a callback of the environment. */\n\tprivate async configured(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<BetterAuthOptions> {\n\t\t// Hoisted: narrowing a property of `this` is not preserved across the\n\t\t// await, and the false branch is the plain-object form.\n\t\tconst configure = this.config.options;\n\t\tif (typeof configure !== 'function') return configure ?? {};\n\n\t\t// A client for each declared construct, from the options this call was\n\t\t// given — not the process-wide discovery, which keeps whichever env\n\t\t// parser reached it first.\n\t\tconst services: Record<string, unknown> = {};\n\t\tfor (const construct of this.config.dependsOn ?? []) {\n\t\t\tservices[construct.service.serviceName] =\n\t\t\t\tawait construct.service.register(options);\n\t\t}\n\n\t\treturn configure({\n\t\t\t...options,\n\t\t\tservices,\n\t\t} as BetterAuthOptionsContext<TUses>);\n\t}\n\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<AuthServer> {\n\t\t// `Auth<O>` is invariant in its options in better-auth 1.7, so the value\n\t\t// built from a concrete literal is not assignable to the `AuthServer`\n\t\t// alias, which names the constraint. The runtime object is the same one\n\t\t// either way; the assertion keeps the variance where it happens instead\n\t\t// of spreading a generic through the construct's public type.\n\t\treturn betterAuth(await this.optionsFor(options, as)) as AuthServer;\n\t}\n\n\t/**\n\t * Everything better-auth is given: the app's options, and what the construct\n\t * owns — its secret, its URL, its tenant's connection, the origins and cookie\n\t * domain its graph derives. One place, so the server and its migrations are\n\t * built from the same options.\n\t */\n\tprivate async optionsFor(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<Parameters<typeof betterAuth>[0]> {\n\t\tconst { secret, baseUrl, trustedOrigins, cookieDomain } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\tsecret: get(this.keys.secret).string(),\n\t\t\t\t// The surface's own URL, resolved by the target — not guessed from\n\t\t\t\t// a port, which was wrong the moment the server moved to 3001.\n\t\t\t\tbaseUrl: get(this.keys.url).string(),\n\t\t\t\t// Better Auth's CSRF check applies to every caller, not just\n\t\t\t\t// browsers, so an API calling the auth server is rejected unless\n\t\t\t\t// its origin is trusted. The list is derived from the graph and\n\t\t\t\t// arrives as one comma-separated value, for the same reason every\n\t\t\t\t// other derived value arrives as a string: it crosses a process\n\t\t\t\t// boundary as env.\n\t\t\t\ttrustedOrigins: get(this.keys.trustedOrigins)\n\t\t\t\t\t.string()\n\t\t\t\t\t.default('')\n\t\t\t\t\t.transform((value) =>\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t.split(',')\n\t\t\t\t\t\t\t.map((origin) => origin.trim())\n\t\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t\t),\n\t\t\t\t// The domain a session cookie has to carry to be readable by a\n\t\t\t\t// frontend on a sibling host. Optional because there is often\n\t\t\t\t// nothing to widen to: locally everything shares `localhost`,\n\t\t\t\t// where cookies ignore the port and a `Domain` would only be a\n\t\t\t\t// value the browser refuses.\n\t\t\t\tcookieDomain: get(this.keys.cookieDomain).string().optional(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\t// The tenant's own client: one construct, one connection, so what auth\n\t\t// writes and what the browser inspects cannot be two different databases.\n\t\t// A migration asks the tenant for its owner connection, which the tenant\n\t\t// exposes as a `Service` and not as anything `.dependsOn()` would take.\n\t\tconst tenant = this.config.database as Consumable & {\n\t\t\towner?: Service<string, unknown>;\n\t\t};\n\t\tconst source = as.owner && tenant.owner ? tenant.owner : tenant.service;\n\n\t\tconst db = (await source.register(options)) as Kysely<\n\t\t\tRecord<string, never>\n\t\t>;\n\n\t\tconst configured = await this.configured(options);\n\n\t\treturn {\n\t\t\t...configured,\n\t\t\tsecret,\n\t\t\tbaseURL: baseUrl,\n\t\t\tbasePath: this.basePath,\n\t\t\tdatabase: { db, type: 'postgres' },\n\t\t\t// Whatever the app added wins over the derived list rather than\n\t\t\t// replacing it: an origin nobody declared is still sometimes real.\n\t\t\ttrustedOrigins: [\n\t\t\t\t...trustedOrigins,\n\t\t\t\t...(configured.trustedOrigins &&\n\t\t\t\tArray.isArray(configured.trustedOrigins)\n\t\t\t\t\t? configured.trustedOrigins\n\t\t\t\t\t: []),\n\t\t\t],\n\t\t\tadvanced: {\n\t\t\t\t...configured.advanced,\n\t\t\t\t// Only when a domain was derived. Better Auth reads the presence\n\t\t\t\t// of this block as intent, so enabling it with no domain would\n\t\t\t\t// widen the cookie to whatever host happened to set it — and the\n\t\t\t\t// case with nothing to widen to is the common one, not an error.\n\t\t\t\t...(cookieDomain\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tcrossSubDomainCookies: {\n\t\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\t\tdomain: cookieDomain,\n\t\t\t\t\t\t\t\t...configured.advanced?.crossSubDomainCookies,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t}\n}\n\n/** The auth server answered a session check with something other than 200. */\nexport class SessionCheckFailed extends Error {\n\tconstructor(\n\t\treadonly authenticator: string,\n\t\treadonly status: number,\n\t) {\n\t\tsuper(\n\t\t\t`'${authenticator}' answered a session check with ${status}. ` +\n\t\t\t\t`A request with no session gets 200 and null; this is the server ` +\n\t\t\t\t`failing — check that it is running and reachable at its URL.`,\n\t\t);\n\t\tthis.name = 'SessionCheckFailed';\n\t}\n}\n"],"mappings":"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"}
|
package/dist/auth.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as BetterAuthOptions, i as BetterAuthConfig, n as AuthSession, o as
|
|
2
|
-
export { AuthServer, AuthSession, BetterAuth, BetterAuthConfig, BetterAuthOptions, SessionCheckFailed };
|
|
1
|
+
import { a as BetterAuthOptions, i as BetterAuthConfig, n as AuthSession, o as BetterAuthOptionsContext, r as BetterAuth, s as SessionCheckFailed, t as AuthServer } from "./auth-t77pKG-i.cjs";
|
|
2
|
+
export { AuthServer, AuthSession, BetterAuth, BetterAuthConfig, BetterAuthOptions, BetterAuthOptionsContext, SessionCheckFailed };
|
package/dist/auth.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as BetterAuthOptions, i as BetterAuthConfig, n as AuthSession, o as
|
|
2
|
-
export { AuthServer, AuthSession, BetterAuth, BetterAuthConfig, BetterAuthOptions, SessionCheckFailed };
|
|
1
|
+
import { a as BetterAuthOptions, i as BetterAuthConfig, n as AuthSession, o as BetterAuthOptionsContext, r as BetterAuth, s as SessionCheckFailed, t as AuthServer } from "./auth-DkN7_Dnk.mjs";
|
|
2
|
+
export { AuthServer, AuthSession, BetterAuth, BetterAuthConfig, BetterAuthOptions, BetterAuthOptionsContext, SessionCheckFailed };
|
package/dist/auth.mjs
CHANGED
|
@@ -64,7 +64,7 @@ var BetterAuth = class {
|
|
|
64
64
|
handler: `${this.id}.handler`,
|
|
65
65
|
method: "ANY",
|
|
66
66
|
path: `${this.basePath}/*`,
|
|
67
|
-
dependencies: [edgeTo(this.config.database)],
|
|
67
|
+
dependencies: [edgeTo(this.config.database), ...(this.config.dependsOn ?? []).map(edgeTo)],
|
|
68
68
|
requires: [this.keys.secret]
|
|
69
69
|
}]
|
|
70
70
|
}];
|
|
@@ -151,7 +151,13 @@ var BetterAuth = class {
|
|
|
151
151
|
/** Better-auth's options as given — a value, or a callback of the environment. */
|
|
152
152
|
async configured(options) {
|
|
153
153
|
const configure = this.config.options;
|
|
154
|
-
|
|
154
|
+
if (typeof configure !== "function") return configure ?? {};
|
|
155
|
+
const services = {};
|
|
156
|
+
for (const construct of this.config.dependsOn ?? []) services[construct.service.serviceName] = await construct.service.register(options);
|
|
157
|
+
return configure({
|
|
158
|
+
...options,
|
|
159
|
+
services
|
|
160
|
+
});
|
|
155
161
|
}
|
|
156
162
|
async connect(options, as = {}) {
|
|
157
163
|
return betterAuth(await this.optionsFor(options, as));
|
package/dist/auth.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["/**\n * `BetterAuth` — a declared auth server.\n *\n * It is three things at once, which is what makes it the construct that\n * exercises most of the model: a consumer of a database, a producer of an\n * authorizer, and a set of endpoints. This lands the first two.\n *\n * Its tables live in a schema tenant it is *given* — `orders.schema('AuthDb')`\n * — rather than one it invents. The tenant declares the schema and the role;\n * this declares the one thing left, the signing secret, and reads back the keys\n * it needs.\n *\n * :::caution\n * The isolation that arrangement is *for* — the application's own role holding\n * no grant on these tables, so a compromised handler cannot read sessions — does\n * **not hold yet**. No target creates the per-schema role: both connect as the\n * database owner, so today the tenant is a `search_path`, which is a namespace\n * and not a privilege boundary. See \"Roles are declared and never created\" in\n * `docs/design/constructs-outstanding.md`.\n * :::\n *\n * It declares its own surface, so where it answers, who may call it, and the\n * domain its cookies are scoped to all arrive as environment resolved by the\n * target — none of them guessed from a port, and none of them listed by hand in\n * application config.\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tenvironmentCase,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport { betterAuth } from 'better-auth';\nimport type { Hono } from 'hono';\nimport type { Kysely } from 'kysely';\nimport {\n\ttype Authenticator,\n\ttype Construct,\n\ttype Consumable,\n\tedgeTo,\n} from './construct-interface';\n\n/** The server better-auth hands back. */\nexport type AuthServer = ReturnType<typeof betterAuth>;\n\n/** A signed-in session as better-auth hands it back: `{ user, session }`. */\nexport type AuthSession = AuthServer['$Infer']['Session'];\n\n/** Everything better-auth takes, minus what the construct owns. */\nexport type BetterAuthOptions = Omit<\n\tParameters<typeof betterAuth>[0],\n\t'database' | 'secret' | 'basePath' | 'baseURL'\n>;\n\nexport interface BetterAuthConfig<TDatabase extends Consumable> {\n\t/**\n\t * The schema tenant its tables live in.\n\t *\n\t * A construct rather than a URL: the tenant already declares its schema and\n\t * its role, and taking the declaration is what puts the edge in the graph\n\t * instead of a connection string in two places.\n\t */\n\tdatabase: TDatabase;\n\t/**\n\t * The app that serves this auth server, relative to the workspace root —\n\t * `'apps/auth'`.\n\t *\n\t * Required, for the reason `RestApi.path` is. Not to be confused with\n\t * `basePath`, which is where its routes are mounted in a URL.\n\t */\n\tpath: string;\n\t/**\n\t * Where the auth routes are mounted, e.g. `/api/auth`.\n\t *\n\t * Structural — it is part of the URL every client calls, so it cannot differ\n\t * between stages the way the host can.\n\t */\n\tbasePath?: string;\n\t/**\n\t * The rest of better-auth's options: providers, plugins, email settings.\n\t *\n\t * A function when they need another construct — a magic-link plugin has to\n\t * *send* the link, and the thing that sends mail is a construct whose client\n\t * has to be registered. It is handed the same `ServiceRegisterOptions` this\n\t * construct was, so the env parser flows through the graph rather than being\n\t * imported out of application config.\n\t */\n\toptions?:\n\t\t| BetterAuthOptions\n\t\t| ((\n\t\t\t\toptions: ServiceRegisterOptions,\n\t\t ) => BetterAuthOptions | Promise<BetterAuthOptions>);\n}\n\nconst DEFAULT_BASE_PATH = '/api/auth';\n\nexport class BetterAuth<\n\tTName extends string = string,\n\tTDatabase extends Consumable = Consumable,\n> implements Construct<TName, AuthServer>, Authenticator<AuthSession>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, AuthServer>;\n\treadonly basePath: string;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the keys the\n\t * target publishes and the keys the server reads cannot drift.\n\t */\n\tprivate readonly keys: {\n\t\tsecretId: string;\n\t\tsecret: string;\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: BetterAuthConfig<TDatabase>,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.basePath = config.basePath ?? DEFAULT_BASE_PATH;\n\n\t\t// A secret's name is its key: `Auth` signs with `AUTH_SECRET`, which is\n\t\t// also what better-auth's own tooling looks for.\n\t\tconst secretId = `${canonical}Secret`;\n\t\tthis.keys = {\n\t\t\tsecretId,\n\t\t\tsecret: environmentCase(secretId),\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (options) => this.connect(options),\n\t\t};\n\t}\n\n\t/**\n\t * A signing secret and a surface.\n\t *\n\t * The database is *not* declared here — the tenant that was passed in\n\t * declares it, and declaring it twice is the duplication the whole model\n\t * removes. What is declared is what this construct owns: the key it signs\n\t * with, and the routes it answers on.\n\t *\n\t * One endpoint, wildcarded. Better Auth routes internally and the set of\n\t * paths depends on which capabilities are enabled, so enumerating them here\n\t * would be a copy of its router that goes stale on its next release. The\n\t * surface's job is to send everything under `basePath` to one handler, which\n\t * is exactly what the declaration says.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'secret',\n\t\t\t\tid: this.keys.secretId,\n\t\t\t\tprovides: [this.keys.secret],\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t\tendpoints: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: `${this.id}Handler`,\n\t\t\t\t\t\thandler: `${this.id}.handler`,\n\t\t\t\t\t\tmethod: 'ANY',\n\t\t\t\t\t\tpath: `${this.basePath}/*`,\n\t\t\t\t\t\t// Read off the tenant rather than written down: this\n\t\t\t\t\t\t// construct takes whatever database it is given, and a\n\t\t\t\t\t\t// hardcoded kind is a second statement of a fact the\n\t\t\t\t\t\t// tenant already makes.\n\t\t\t\t\t\tdependencies: [edgeTo(this.config.database)],\n\t\t\t\t\t\trequires: [this.keys.secret],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * Whose request this is: the session for these headers, or `null`.\n\t *\n\t * Asked of the auth server over HTTP, at the URL the `.auth()` edge injects —\n\t * so it is the same call whether the server runs in this process, beside\n\t * it, or behind MSW in a test. Only the session headers are forwarded; the\n\t * surface decides which.\n\t */\n\tasync verify(\n\t\theaders: Headers,\n\t\tenvParser: EnvironmentParser<{}>,\n\t): Promise<AuthSession | null> {\n\t\tconst { url } = envParser\n\t\t\t.create((get) => ({ url: get(this.keys.url).string() }))\n\t\t\t.parse();\n\n\t\tconst response = await fetch(`${url}${this.basePath}/get-session`, {\n\t\t\theaders,\n\t\t});\n\n\t\t// Better Auth answers `null` for a request with no session. Anything\n\t\t// other than a 200 is the server failing, which is not the same thing as\n\t\t// \"signed out\" and must not be read as it.\n\t\tif (!response.ok) {\n\t\t\tthrow new SessionCheckFailed(this.id, response.status);\n\t\t}\n\n\t\treturn (await response.json()) as AuthSession | null;\n\t}\n\n\t/**\n\t * Better Auth's own migrations, for the app's migrate step.\n\t *\n\t * It brings its own schema — users, sessions, accounts, verifications — so\n\t * the app never writes those tables and never gets to drift from them.\n\t */\n\tasync migrations(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<() => Promise<void>> {\n\t\t// As the owner, not the runtime role. Creating tables is DDL, and the\n\t\t// whole point of the split is that a handler's role cannot do it — so a\n\t\t// migrator that connected the way a handler does would fail on the first\n\t\t// `CREATE TABLE` rather than working by accident.\n\t\t//\n\t\t// The options, not a server built from them: better-auth checks its\n\t\t// schema when a server starts, and before the migration that creates the\n\t\t// tables it can only report them missing — an error in every migrate\n\t\t// log, about the very tables the next line creates.\n\t\tconst resolved = await this.optionsFor(options, { owner: true });\n\t\t// better-auth 1.7 split the migration builder out of `better-auth/db`\n\t\t// into its own entry, so importing it no longer drags the whole db layer\n\t\t// in behind it.\n\t\tconst { getMigrations } = await import('better-auth/db/migration');\n\t\tconst { runMigrations } = await getMigrations(resolved);\n\n\t\treturn runMigrations;\n\t}\n\n\t/**\n\t * The server this surface runs as, built from its own declaration.\n\t *\n\t * A surface is a deploy unit, so something has to serve it, and the thing\n\t * that knows how is the construct — not a generator writing a mount into\n\t * somebody else's entry point. That is how the auth server came to be\n\t * mounted by a hook in the application it was supposed to be separate from,\n\t * and then to be served by nothing at all when the hook was deleted.\n\t *\n\t * The routes come from {@link declare}: one wildcard under `basePath`, read\n\t * off the same declaration the manifest carries, so the surface the graph\n\t * describes and the surface that answers requests cannot disagree.\n\t *\n\t * ```ts\n\t * // .gkm/server/app.ts — the whole generated entry\n\t * import { auth } from '@acme/constructs/auth.js';\n\t * export const { app } = await auth.server({ envParser });\n\t * ```\n\t */\n\tasync server(options: ServiceRegisterOptions): Promise<{ app: Hono }> {\n\t\tconst { Hono } = await import('hono');\n\t\tconst { cors } = await import('hono/cors');\n\t\tconst app = new Hono();\n\t\tconst server = await this.connect(options);\n\n\t\t// Who may call this surface, read off the graph. The same list Better\n\t\t// Auth checks for CSRF — it arrives as `<ID>_TRUSTED_ORIGINS`, composed\n\t\t// by the target from whatever declared an edge to this construct, so a\n\t\t// site that depends on the auth server is allowed to reach it and\n\t\t// nothing else is. Nobody writes an origin down.\n\t\tconst { origins } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\torigins: get(this.keys.trustedOrigins)\n\t\t\t\t\t.string()\n\t\t\t\t\t.default('')\n\t\t\t\t\t.transform((value: string) =>\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t.split(',')\n\t\t\t\t\t\t\t.map((origin) => origin.trim())\n\t\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t\t),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\tapp.use('*', cors({ origin: origins, credentials: true, maxAge: 3600 }));\n\n\t\t// Read off the declaration rather than restated here. `ANY` is every\n\t\t// method Better Auth routes internally, which is the reason the\n\t\t// declaration wildcards rather than enumerating.\n\t\tconst [surface] = this.declare().filter(\n\t\t\t(d): d is Extract<Declaration, { kind: 'rest-api' }> =>\n\t\t\t\td.kind === 'rest-api',\n\t\t);\n\n\t\tfor (const endpoint of surface?.endpoints ?? []) {\n\t\t\tconst methods =\n\t\t\t\tendpoint.method === 'ANY'\n\t\t\t\t\t? (['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const)\n\t\t\t\t\t: ([endpoint.method] as const);\n\n\t\t\tapp.on([...methods], endpoint.path, (c) => server.handler(c.req.raw));\n\t\t}\n\n\t\treturn { app };\n\t}\n\n\t/**\n\t * The ids of the better-auth plugins this server runs — what a client has\n\t * to pair its own plugins with (`magic-link` with `magicLinkClient`).\n\t * Resolved the way the server resolves them, options callback included.\n\t */\n\tasync pluginIds(options: ServiceRegisterOptions): Promise<string[]> {\n\t\tconst { plugins = [] } = await this.configured(options);\n\t\treturn plugins.map(({ id }) => id);\n\t}\n\n\t/** Better-auth's options as given — a value, or a callback of the environment. */\n\tprivate async configured(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<BetterAuthOptions> {\n\t\t// Hoisted: narrowing a property of `this` is not preserved across the\n\t\t// await, and the false branch is the plain-object form.\n\t\tconst configure = this.config.options;\n\t\treturn typeof configure === 'function'\n\t\t\t? await configure(options)\n\t\t\t: (configure ?? {});\n\t}\n\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<AuthServer> {\n\t\t// `Auth<O>` is invariant in its options in better-auth 1.7, so the value\n\t\t// built from a concrete literal is not assignable to the `AuthServer`\n\t\t// alias, which names the constraint. The runtime object is the same one\n\t\t// either way; the assertion keeps the variance where it happens instead\n\t\t// of spreading a generic through the construct's public type.\n\t\treturn betterAuth(await this.optionsFor(options, as)) as AuthServer;\n\t}\n\n\t/**\n\t * Everything better-auth is given: the app's options, and what the construct\n\t * owns — its secret, its URL, its tenant's connection, the origins and cookie\n\t * domain its graph derives. One place, so the server and its migrations are\n\t * built from the same options.\n\t */\n\tprivate async optionsFor(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<Parameters<typeof betterAuth>[0]> {\n\t\tconst { secret, baseUrl, trustedOrigins, cookieDomain } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\tsecret: get(this.keys.secret).string(),\n\t\t\t\t// The surface's own URL, resolved by the target — not guessed from\n\t\t\t\t// a port, which was wrong the moment the server moved to 3001.\n\t\t\t\tbaseUrl: get(this.keys.url).string(),\n\t\t\t\t// Better Auth's CSRF check applies to every caller, not just\n\t\t\t\t// browsers, so an API calling the auth server is rejected unless\n\t\t\t\t// its origin is trusted. The list is derived from the graph and\n\t\t\t\t// arrives as one comma-separated value, for the same reason every\n\t\t\t\t// other derived value arrives as a string: it crosses a process\n\t\t\t\t// boundary as env.\n\t\t\t\ttrustedOrigins: get(this.keys.trustedOrigins)\n\t\t\t\t\t.string()\n\t\t\t\t\t.default('')\n\t\t\t\t\t.transform((value) =>\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t.split(',')\n\t\t\t\t\t\t\t.map((origin) => origin.trim())\n\t\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t\t),\n\t\t\t\t// The domain a session cookie has to carry to be readable by a\n\t\t\t\t// frontend on a sibling host. Optional because there is often\n\t\t\t\t// nothing to widen to: locally everything shares `localhost`,\n\t\t\t\t// where cookies ignore the port and a `Domain` would only be a\n\t\t\t\t// value the browser refuses.\n\t\t\t\tcookieDomain: get(this.keys.cookieDomain).string().optional(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\t// The tenant's own client: one construct, one connection, so what auth\n\t\t// writes and what the browser inspects cannot be two different databases.\n\t\t// A migration asks the tenant for its owner connection, which the tenant\n\t\t// exposes as a `Service` and not as anything `.dependsOn()` would take.\n\t\tconst tenant = this.config.database as Consumable & {\n\t\t\towner?: Service<string, unknown>;\n\t\t};\n\t\tconst source = as.owner && tenant.owner ? tenant.owner : tenant.service;\n\n\t\tconst db = (await source.register(options)) as Kysely<\n\t\t\tRecord<string, never>\n\t\t>;\n\n\t\tconst configured = await this.configured(options);\n\n\t\treturn {\n\t\t\t...configured,\n\t\t\tsecret,\n\t\t\tbaseURL: baseUrl,\n\t\t\tbasePath: this.basePath,\n\t\t\tdatabase: { db, type: 'postgres' },\n\t\t\t// Whatever the app added wins over the derived list rather than\n\t\t\t// replacing it: an origin nobody declared is still sometimes real.\n\t\t\ttrustedOrigins: [\n\t\t\t\t...trustedOrigins,\n\t\t\t\t...(configured.trustedOrigins &&\n\t\t\t\tArray.isArray(configured.trustedOrigins)\n\t\t\t\t\t? configured.trustedOrigins\n\t\t\t\t\t: []),\n\t\t\t],\n\t\t\tadvanced: {\n\t\t\t\t...configured.advanced,\n\t\t\t\t// Only when a domain was derived. Better Auth reads the presence\n\t\t\t\t// of this block as intent, so enabling it with no domain would\n\t\t\t\t// widen the cookie to whatever host happened to set it — and the\n\t\t\t\t// case with nothing to widen to is the common one, not an error.\n\t\t\t\t...(cookieDomain\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tcrossSubDomainCookies: {\n\t\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\t\tdomain: cookieDomain,\n\t\t\t\t\t\t\t\t...configured.advanced?.crossSubDomainCookies,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t}\n}\n\n/** The auth server answered a session check with something other than 200. */\nexport class SessionCheckFailed extends Error {\n\tconstructor(\n\t\treadonly authenticator: string,\n\t\treadonly status: number,\n\t) {\n\t\tsuper(\n\t\t\t`'${authenticator}' answered a session check with ${status}. ` +\n\t\t\t\t`A request with no session gets 200 and null; this is the server ` +\n\t\t\t\t`failing — check that it is running and reachable at its URL.`,\n\t\t);\n\t\tthis.name = 'SessionCheckFailed';\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["/**\n * `BetterAuth` — a declared auth server.\n *\n * It is three things at once, which is what makes it the construct that\n * exercises most of the model: a consumer of a database, a producer of an\n * authorizer, and a set of endpoints. This lands the first two.\n *\n * Its tables live in a schema tenant it is *given* — `orders.schema('AuthDb')`\n * — rather than one it invents. The tenant declares the schema and the role;\n * this declares the one thing left, the signing secret, and reads back the keys\n * it needs.\n *\n * :::caution\n * The isolation that arrangement is *for* — the application's own role holding\n * no grant on these tables, so a compromised handler cannot read sessions — does\n * **not hold yet**. No target creates the per-schema role: both connect as the\n * database owner, so today the tenant is a `search_path`, which is a namespace\n * and not a privilege boundary. See \"Roles are declared and never created\" in\n * `docs/design/constructs-outstanding.md`.\n * :::\n *\n * It declares its own surface, so where it answers, who may call it, and the\n * domain its cookies are scoped to all arrive as environment resolved by the\n * target — none of them guessed from a port, and none of them listed by hand in\n * application config.\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tenvironmentCase,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type {\n\tService,\n\tServiceRecord,\n\tServiceRegisterOptions,\n} from '@geekmidas/services';\nimport { betterAuth } from 'better-auth';\nimport type { Hono } from 'hono';\nimport type { Kysely } from 'kysely';\nimport {\n\ttype Authenticator,\n\ttype Construct,\n\ttype Consumable,\n\tedgeTo,\n\ttype ServicesOf,\n} from './construct-interface';\n\n/** The server better-auth hands back. */\nexport type AuthServer = ReturnType<typeof betterAuth>;\n\n/** A signed-in session as better-auth hands it back: `{ user, session }`. */\nexport type AuthSession = AuthServer['$Infer']['Session'];\n\n/** Everything better-auth takes, minus what the construct owns. */\nexport type BetterAuthOptions = Omit<\n\tParameters<typeof betterAuth>[0],\n\t'database' | 'secret' | 'basePath' | 'baseURL'\n>;\n\n/**\n * What an `options` callback is handed: the options this construct was\n * registered with, and a client for each construct its `dependsOn` names —\n * keyed by service name, exactly as an endpoint's handler receives them.\n */\nexport type BetterAuthOptionsContext<TUses extends readonly Consumable[]> =\n\tServiceRegisterOptions & {\n\t\tservices: ServiceRecord<[...ServicesOf<TUses>]>;\n\t};\n\nexport interface BetterAuthConfig<\n\tTDatabase extends Consumable,\n\tTUses extends readonly Consumable[] = readonly [],\n> {\n\t/**\n\t * The schema tenant its tables live in.\n\t *\n\t * A construct rather than a URL: the tenant already declares its schema and\n\t * its role, and taking the declaration is what puts the edge in the graph\n\t * instead of a connection string in two places.\n\t */\n\tdatabase: TDatabase;\n\t/**\n\t * The app that serves this auth server, relative to the workspace root —\n\t * `'apps/auth'`.\n\t *\n\t * Required, for the reason `RestApi.path` is. Not to be confused with\n\t * `basePath`, which is where its routes are mounted in a URL.\n\t */\n\tpath: string;\n\t/**\n\t * Where the auth routes are mounted, e.g. `/api/auth`.\n\t *\n\t * Structural — it is part of the URL every client calls, so it cannot differ\n\t * between stages the way the host can.\n\t */\n\tbasePath?: string;\n\t/**\n\t * The constructs `options` uses — the mailer a magic link goes through,\n\t * most often.\n\t *\n\t * One list, two jobs, the way an endpoint's `.dependsOn()` has: each is an\n\t * edge on the server's handler, so whatever is composed from the edges (a\n\t * container's environment, a deploy's grants) includes it; and each is\n\t * handed to `options` as a client in `services`, so there is no other way\n\t * for `options` to reach it — nothing to import and register by hand, and\n\t * so nothing that can be used without being declared.\n\t */\n\tdependsOn?: TUses;\n\t/**\n\t * The rest of better-auth's options: providers, plugins, email settings.\n\t *\n\t * A function when they need another construct — a magic-link plugin has to\n\t * *send* the link. It is handed the options this construct was registered\n\t * with, and `services`: a client for each construct in `dependsOn`.\n\t */\n\toptions?:\n\t\t| BetterAuthOptions\n\t\t| ((\n\t\t\t\tcontext: BetterAuthOptionsContext<TUses>,\n\t\t ) => BetterAuthOptions | Promise<BetterAuthOptions>);\n}\n\nconst DEFAULT_BASE_PATH = '/api/auth';\n\nexport class BetterAuth<\n\tTName extends string = string,\n\tTDatabase extends Consumable = Consumable,\n\tconst TUses extends readonly Consumable[] = readonly [],\n> implements Construct<TName, AuthServer>, Authenticator<AuthSession>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, AuthServer>;\n\treadonly basePath: string;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the keys the\n\t * target publishes and the keys the server reads cannot drift.\n\t */\n\tprivate readonly keys: {\n\t\tsecretId: string;\n\t\tsecret: string;\n\t\turl: string;\n\t\ttrustedOrigins: string;\n\t\tcookieDomain: string;\n\t};\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly config: BetterAuthConfig<TDatabase, TUses>,\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.basePath = config.basePath ?? DEFAULT_BASE_PATH;\n\n\t\t// A secret's name is its key: `Auth` signs with `AUTH_SECRET`, which is\n\t\t// also what better-auth's own tooling looks for.\n\t\tconst secretId = `${canonical}Secret`;\n\t\tthis.keys = {\n\t\t\tsecretId,\n\t\t\tsecret: environmentCase(secretId),\n\t\t\turl: provideKey(canonical, 'url'),\n\t\t\ttrustedOrigins: provideKey(canonical, 'trustedOrigins'),\n\t\t\tcookieDomain: provideKey(canonical, 'cookieDomain'),\n\t\t};\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (options) => this.connect(options),\n\t\t};\n\t}\n\n\t/**\n\t * A signing secret and a surface.\n\t *\n\t * The database is *not* declared here — the tenant that was passed in\n\t * declares it, and declaring it twice is the duplication the whole model\n\t * removes. What is declared is what this construct owns: the key it signs\n\t * with, and the routes it answers on.\n\t *\n\t * One endpoint, wildcarded. Better Auth routes internally and the set of\n\t * paths depends on which capabilities are enabled, so enumerating them here\n\t * would be a copy of its router that goes stale on its next release. The\n\t * surface's job is to send everything under `basePath` to one handler, which\n\t * is exactly what the declaration says.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'secret',\n\t\t\t\tid: this.keys.secretId,\n\t\t\t\tprovides: [this.keys.secret],\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'rest-api',\n\t\t\t\tid: this.id,\n\t\t\t\tpath: this.config.path,\n\t\t\t\tprovides: [\n\t\t\t\t\tthis.keys.url,\n\t\t\t\t\tthis.keys.trustedOrigins,\n\t\t\t\t\tthis.keys.cookieDomain,\n\t\t\t\t],\n\t\t\t\tendpoints: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: `${this.id}Handler`,\n\t\t\t\t\t\thandler: `${this.id}.handler`,\n\t\t\t\t\t\tmethod: 'ANY',\n\t\t\t\t\t\tpath: `${this.basePath}/*`,\n\t\t\t\t\t\t// Read off the tenant rather than written down: this\n\t\t\t\t\t\t// construct takes whatever database it is given, and a\n\t\t\t\t\t\t// hardcoded kind is a second statement of a fact the\n\t\t\t\t\t\t// tenant already makes.\n\t\t\t\t\t\tdependencies: [\n\t\t\t\t\t\t\tedgeTo(this.config.database),\n\t\t\t\t\t\t\t...(this.config.dependsOn ?? []).map(edgeTo),\n\t\t\t\t\t\t],\n\t\t\t\t\t\trequires: [this.keys.secret],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * Whose request this is: the session for these headers, or `null`.\n\t *\n\t * Asked of the auth server over HTTP, at the URL the `.auth()` edge injects —\n\t * so it is the same call whether the server runs in this process, beside\n\t * it, or behind MSW in a test. Only the session headers are forwarded; the\n\t * surface decides which.\n\t */\n\tasync verify(\n\t\theaders: Headers,\n\t\tenvParser: EnvironmentParser<{}>,\n\t): Promise<AuthSession | null> {\n\t\tconst { url } = envParser\n\t\t\t.create((get) => ({ url: get(this.keys.url).string() }))\n\t\t\t.parse();\n\n\t\tconst response = await fetch(`${url}${this.basePath}/get-session`, {\n\t\t\theaders,\n\t\t});\n\n\t\t// Better Auth answers `null` for a request with no session. Anything\n\t\t// other than a 200 is the server failing, which is not the same thing as\n\t\t// \"signed out\" and must not be read as it.\n\t\tif (!response.ok) {\n\t\t\tthrow new SessionCheckFailed(this.id, response.status);\n\t\t}\n\n\t\treturn (await response.json()) as AuthSession | null;\n\t}\n\n\t/**\n\t * Better Auth's own migrations, for the app's migrate step.\n\t *\n\t * It brings its own schema — users, sessions, accounts, verifications — so\n\t * the app never writes those tables and never gets to drift from them.\n\t */\n\tasync migrations(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<() => Promise<void>> {\n\t\t// As the owner, not the runtime role. Creating tables is DDL, and the\n\t\t// whole point of the split is that a handler's role cannot do it — so a\n\t\t// migrator that connected the way a handler does would fail on the first\n\t\t// `CREATE TABLE` rather than working by accident.\n\t\t//\n\t\t// The options, not a server built from them: better-auth checks its\n\t\t// schema when a server starts, and before the migration that creates the\n\t\t// tables it can only report them missing — an error in every migrate\n\t\t// log, about the very tables the next line creates.\n\t\tconst resolved = await this.optionsFor(options, { owner: true });\n\t\t// better-auth 1.7 split the migration builder out of `better-auth/db`\n\t\t// into its own entry, so importing it no longer drags the whole db layer\n\t\t// in behind it.\n\t\tconst { getMigrations } = await import('better-auth/db/migration');\n\t\tconst { runMigrations } = await getMigrations(resolved);\n\n\t\treturn runMigrations;\n\t}\n\n\t/**\n\t * The server this surface runs as, built from its own declaration.\n\t *\n\t * A surface is a deploy unit, so something has to serve it, and the thing\n\t * that knows how is the construct — not a generator writing a mount into\n\t * somebody else's entry point. That is how the auth server came to be\n\t * mounted by a hook in the application it was supposed to be separate from,\n\t * and then to be served by nothing at all when the hook was deleted.\n\t *\n\t * The routes come from {@link declare}: one wildcard under `basePath`, read\n\t * off the same declaration the manifest carries, so the surface the graph\n\t * describes and the surface that answers requests cannot disagree.\n\t *\n\t * ```ts\n\t * // .gkm/server/app.ts — the whole generated entry\n\t * import { auth } from '@acme/constructs/auth.js';\n\t * export const { app } = await auth.server({ envParser });\n\t * ```\n\t */\n\tasync server(options: ServiceRegisterOptions): Promise<{ app: Hono }> {\n\t\tconst { Hono } = await import('hono');\n\t\tconst { cors } = await import('hono/cors');\n\t\tconst app = new Hono();\n\t\tconst server = await this.connect(options);\n\n\t\t// Who may call this surface, read off the graph. The same list Better\n\t\t// Auth checks for CSRF — it arrives as `<ID>_TRUSTED_ORIGINS`, composed\n\t\t// by the target from whatever declared an edge to this construct, so a\n\t\t// site that depends on the auth server is allowed to reach it and\n\t\t// nothing else is. Nobody writes an origin down.\n\t\tconst { origins } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\torigins: get(this.keys.trustedOrigins)\n\t\t\t\t\t.string()\n\t\t\t\t\t.default('')\n\t\t\t\t\t.transform((value: string) =>\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t.split(',')\n\t\t\t\t\t\t\t.map((origin) => origin.trim())\n\t\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t\t),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\tapp.use('*', cors({ origin: origins, credentials: true, maxAge: 3600 }));\n\n\t\t// Read off the declaration rather than restated here. `ANY` is every\n\t\t// method Better Auth routes internally, which is the reason the\n\t\t// declaration wildcards rather than enumerating.\n\t\tconst [surface] = this.declare().filter(\n\t\t\t(d): d is Extract<Declaration, { kind: 'rest-api' }> =>\n\t\t\t\td.kind === 'rest-api',\n\t\t);\n\n\t\tfor (const endpoint of surface?.endpoints ?? []) {\n\t\t\tconst methods =\n\t\t\t\tendpoint.method === 'ANY'\n\t\t\t\t\t? (['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const)\n\t\t\t\t\t: ([endpoint.method] as const);\n\n\t\t\tapp.on([...methods], endpoint.path, (c) => server.handler(c.req.raw));\n\t\t}\n\n\t\treturn { app };\n\t}\n\n\t/**\n\t * The ids of the better-auth plugins this server runs — what a client has\n\t * to pair its own plugins with (`magic-link` with `magicLinkClient`).\n\t * Resolved the way the server resolves them, options callback included.\n\t */\n\tasync pluginIds(options: ServiceRegisterOptions): Promise<string[]> {\n\t\tconst { plugins = [] } = await this.configured(options);\n\t\treturn plugins.map(({ id }) => id);\n\t}\n\n\t/** Better-auth's options as given — a value, or a callback of the environment. */\n\tprivate async configured(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<BetterAuthOptions> {\n\t\t// Hoisted: narrowing a property of `this` is not preserved across the\n\t\t// await, and the false branch is the plain-object form.\n\t\tconst configure = this.config.options;\n\t\tif (typeof configure !== 'function') return configure ?? {};\n\n\t\t// A client for each declared construct, from the options this call was\n\t\t// given — not the process-wide discovery, which keeps whichever env\n\t\t// parser reached it first.\n\t\tconst services: Record<string, unknown> = {};\n\t\tfor (const construct of this.config.dependsOn ?? []) {\n\t\t\tservices[construct.service.serviceName] =\n\t\t\t\tawait construct.service.register(options);\n\t\t}\n\n\t\treturn configure({\n\t\t\t...options,\n\t\t\tservices,\n\t\t} as BetterAuthOptionsContext<TUses>);\n\t}\n\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<AuthServer> {\n\t\t// `Auth<O>` is invariant in its options in better-auth 1.7, so the value\n\t\t// built from a concrete literal is not assignable to the `AuthServer`\n\t\t// alias, which names the constraint. The runtime object is the same one\n\t\t// either way; the assertion keeps the variance where it happens instead\n\t\t// of spreading a generic through the construct's public type.\n\t\treturn betterAuth(await this.optionsFor(options, as)) as AuthServer;\n\t}\n\n\t/**\n\t * Everything better-auth is given: the app's options, and what the construct\n\t * owns — its secret, its URL, its tenant's connection, the origins and cookie\n\t * domain its graph derives. One place, so the server and its migrations are\n\t * built from the same options.\n\t */\n\tprivate async optionsFor(\n\t\toptions: ServiceRegisterOptions,\n\t\tas: { owner?: boolean } = {},\n\t): Promise<Parameters<typeof betterAuth>[0]> {\n\t\tconst { secret, baseUrl, trustedOrigins, cookieDomain } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\tsecret: get(this.keys.secret).string(),\n\t\t\t\t// The surface's own URL, resolved by the target — not guessed from\n\t\t\t\t// a port, which was wrong the moment the server moved to 3001.\n\t\t\t\tbaseUrl: get(this.keys.url).string(),\n\t\t\t\t// Better Auth's CSRF check applies to every caller, not just\n\t\t\t\t// browsers, so an API calling the auth server is rejected unless\n\t\t\t\t// its origin is trusted. The list is derived from the graph and\n\t\t\t\t// arrives as one comma-separated value, for the same reason every\n\t\t\t\t// other derived value arrives as a string: it crosses a process\n\t\t\t\t// boundary as env.\n\t\t\t\ttrustedOrigins: get(this.keys.trustedOrigins)\n\t\t\t\t\t.string()\n\t\t\t\t\t.default('')\n\t\t\t\t\t.transform((value) =>\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t.split(',')\n\t\t\t\t\t\t\t.map((origin) => origin.trim())\n\t\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t\t),\n\t\t\t\t// The domain a session cookie has to carry to be readable by a\n\t\t\t\t// frontend on a sibling host. Optional because there is often\n\t\t\t\t// nothing to widen to: locally everything shares `localhost`,\n\t\t\t\t// where cookies ignore the port and a `Domain` would only be a\n\t\t\t\t// value the browser refuses.\n\t\t\t\tcookieDomain: get(this.keys.cookieDomain).string().optional(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\t// The tenant's own client: one construct, one connection, so what auth\n\t\t// writes and what the browser inspects cannot be two different databases.\n\t\t// A migration asks the tenant for its owner connection, which the tenant\n\t\t// exposes as a `Service` and not as anything `.dependsOn()` would take.\n\t\tconst tenant = this.config.database as Consumable & {\n\t\t\towner?: Service<string, unknown>;\n\t\t};\n\t\tconst source = as.owner && tenant.owner ? tenant.owner : tenant.service;\n\n\t\tconst db = (await source.register(options)) as Kysely<\n\t\t\tRecord<string, never>\n\t\t>;\n\n\t\tconst configured = await this.configured(options);\n\n\t\treturn {\n\t\t\t...configured,\n\t\t\tsecret,\n\t\t\tbaseURL: baseUrl,\n\t\t\tbasePath: this.basePath,\n\t\t\tdatabase: { db, type: 'postgres' },\n\t\t\t// Whatever the app added wins over the derived list rather than\n\t\t\t// replacing it: an origin nobody declared is still sometimes real.\n\t\t\ttrustedOrigins: [\n\t\t\t\t...trustedOrigins,\n\t\t\t\t...(configured.trustedOrigins &&\n\t\t\t\tArray.isArray(configured.trustedOrigins)\n\t\t\t\t\t? configured.trustedOrigins\n\t\t\t\t\t: []),\n\t\t\t],\n\t\t\tadvanced: {\n\t\t\t\t...configured.advanced,\n\t\t\t\t// Only when a domain was derived. Better Auth reads the presence\n\t\t\t\t// of this block as intent, so enabling it with no domain would\n\t\t\t\t// widen the cookie to whatever host happened to set it — and the\n\t\t\t\t// case with nothing to widen to is the common one, not an error.\n\t\t\t\t...(cookieDomain\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tcrossSubDomainCookies: {\n\t\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\t\tdomain: cookieDomain,\n\t\t\t\t\t\t\t\t...configured.advanced?.crossSubDomainCookies,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t}\n}\n\n/** The auth server answered a session check with something other than 200. */\nexport class SessionCheckFailed extends Error {\n\tconstructor(\n\t\treadonly authenticator: string,\n\t\treadonly status: number,\n\t) {\n\t\tsuper(\n\t\t\t`'${authenticator}' answered a session check with ${status}. ` +\n\t\t\t\t`A request with no session gets 200 and null; this is the server ` +\n\t\t\t\t`failing — check that it is running and reachable at its URL.`,\n\t\t);\n\t\tthis.name = 'SessionCheckFailed';\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as Construct } from "./construct-interface-
|
|
1
|
+
import { n as Construct } from "./construct-interface-D2gesxos.mjs";
|
|
2
2
|
import { ConstructName, Declaration } from "@geekmidas/manifest";
|
|
3
3
|
import { Cache } from "@geekmidas/cache";
|
|
4
4
|
import { Service } from "@geekmidas/services";
|
|
@@ -41,4 +41,4 @@ declare class Cache$1<TName extends string = string> implements Construct<TName,
|
|
|
41
41
|
}
|
|
42
42
|
//#endregion
|
|
43
43
|
export { CacheOptions as n, Cache$1 as t };
|
|
44
|
-
//# sourceMappingURL=cache-
|
|
44
|
+
//# sourceMappingURL=cache-B2s0b-ss.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-
|
|
1
|
+
{"version":3,"file":"cache-B2s0b-ss.d.mts","names":["Cache","CacheClient"],"sources":["../src/cache.ts"],"mappings":";;;;;UA6BiB;;;;;;;;EAQhB;;EAEA;;cAGYA,QAAM,0CACP,UAAU,OAAOC;mBAaV;WAXT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQA;;;;;mBAM9B;EAEjB,YACC,IAAI,cAAc,QACD,UAAS;EAc3B,WAAW;;;;;;;;;;;;UAuBG"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as Construct } from "./construct-interface-
|
|
1
|
+
import { n as Construct } from "./construct-interface-DPusCUSQ.cjs";
|
|
2
2
|
import { Service } from "@geekmidas/services";
|
|
3
3
|
import { ConstructName, Declaration } from "@geekmidas/manifest";
|
|
4
4
|
import { Cache } from "@geekmidas/cache";
|
|
@@ -41,4 +41,4 @@ declare class Cache$1<TName extends string = string> implements Construct<TName,
|
|
|
41
41
|
}
|
|
42
42
|
//#endregion
|
|
43
43
|
export { CacheOptions as n, Cache$1 as t };
|
|
44
|
-
//# sourceMappingURL=cache-
|
|
44
|
+
//# sourceMappingURL=cache-CYe2Kzjc.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-
|
|
1
|
+
{"version":3,"file":"cache-CYe2Kzjc.d.cts","names":["Cache","CacheClient"],"sources":["../src/cache.ts"],"mappings":";;;;;UA6BiB;;;;;;;;EAQhB;;EAEA;;cAGYA,QAAM,0CACP,UAAU,OAAOC;mBAaV;WAXT,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQA;;;;;mBAM9B;EAEjB,YACC,IAAI,cAAc,QACD,UAAS;EAc3B,WAAW;;;;;;;;;;;;UAuBG"}
|
package/dist/cache.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as CacheOptions, t as Cache } from "./cache-
|
|
1
|
+
import { n as CacheOptions, t as Cache } from "./cache-CYe2Kzjc.cjs";
|
|
2
2
|
export { Cache, CacheOptions };
|
package/dist/cache.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as CacheOptions, t as Cache } from "./cache-
|
|
1
|
+
import { n as CacheOptions, t as Cache } from "./cache-B2s0b-ss.mjs";
|
|
2
2
|
export { Cache, CacheOptions };
|
|
@@ -120,12 +120,21 @@ declare class NotAConstruct extends Error {
|
|
|
120
120
|
constructor(value: unknown);
|
|
121
121
|
}
|
|
122
122
|
/**
|
|
123
|
-
*
|
|
123
|
+
* Mark a service as standing for the construct it was derived from.
|
|
124
124
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
125
|
+
* \`.publisher(users.publisher)\` takes a service, not the topic, and an id
|
|
126
|
+
* cannot be recovered from a service name. Without the mark the edge was lost:
|
|
127
|
+
* the manifest never heard that the API publishes to \`users\`, so the app's
|
|
128
|
+
* container was composed without \`USERS_PUBLISHER_CONNECTION_STRING\`.
|
|
128
129
|
*/
|
|
130
|
+
declare function derivedFrom<S extends Service>(service: S, construct: {
|
|
131
|
+
id: string;
|
|
132
|
+
}): S;
|
|
133
|
+
/**
|
|
134
|
+
* \`existing\`, with the edge a service stands for — a construct's own id, or
|
|
135
|
+
* the id a derived service was marked with. Unchanged for a plain service.
|
|
136
|
+
*/
|
|
137
|
+
declare function edgesWith(service: unknown, existing?: readonly string[]): string[];
|
|
129
138
|
declare function isConsumable(value: unknown): value is Consumable;
|
|
130
139
|
/**
|
|
131
140
|
* The service behind either face.
|
|
@@ -136,5 +145,5 @@ declare function isConsumable(value: unknown): value is Consumable;
|
|
|
136
145
|
*/
|
|
137
146
|
declare function serviceOf<TName extends string, TClient>(source: Consumable<TName, TClient> | Service<TName, TClient>): Service<TName, TClient>;
|
|
138
147
|
//#endregion
|
|
139
|
-
export { Infer as a, ServicesOf as c,
|
|
140
|
-
//# sourceMappingURL=construct-interface-
|
|
148
|
+
export { Infer as a, ServicesOf as c, edgesWith as d, idsOf as f, servicesOf as h, Declarable as i, derivedFrom as l, serviceOf as m, Construct as n, NameOf as o, isConsumable as p, Consumable as r, NotAConstruct as s, Authenticator as t, edgeTo as u };
|
|
149
|
+
//# sourceMappingURL=construct-interface-D2gesxos.d.mts.map
|
package/dist/{construct-interface-Clz7_BwB.d.mts.map → construct-interface-D2gesxos.d.mts.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"construct-interface-
|
|
1
|
+
{"version":3,"file":"construct-interface-D2gesxos.d.mts","names":[],"sources":["../src/construct-interface.ts"],"mappings":";;;;;;;;;;;;;UA8BiB,UAAU,+BAA+B;;WAEhD,IAAI;;;;;;;;EASb,WAAW;;;;;;;;WASF,UAAU,mCAEhB,QAAQ,aAAa,QAAQ;;;;;;;;;;;UAYhB,WAAW;WAClB,IAAI;EACb,WAAW;;;;;;;;;;UAWK,cAAc,4BAA4B;EAC1D,OACC,SAAS,SACT,WAAW,wBACT,QAAQ;;;;;;;;;;;;;;iBAeI,OAAO,WAAW,aAAa;;KAWnC,MAAM,KACjB,UAAU,wBAAwB,WAAW;;KAGlC,OAAO,KAClB,UAAU,gBAAgB,kBAAkB;;;;;;;;;;;UAY5B,WAAW,+BAA+B;WACjD,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;EAC/C,WAAW;;;;;;;;;;KAWA,WAAW,mBAAmB,mBACxC,WAAW,IAAI,EAAE;;;;;;;;iBAUH,iBAAiB,mBAAmB,cACnD,YAAY,IACV,WAAW;;;;;;;;;;iBAiBE,MACf,qBAAqB,cACrB;;cAMY,sBAAsB;WACb;EAArB,YAAqB;;;;;;;;;;iBAqCN,YAAY,UAAU,SACrC,SAAS,GACT;EAAa;IACX;;;;;iBASa,UACf,kBACA;iBAce,aAAa,iBAAiB,SAAS;;;;;;;;iBAmBvC,UAAU,sBAAsB,SAC/C,QAAQ,WAAW,OAAO,WAAW,QAAQ,OAAO,WAClD,QAAQ,OAAO"}
|
|
@@ -120,12 +120,21 @@ declare class NotAConstruct extends Error {
|
|
|
120
120
|
constructor(value: unknown);
|
|
121
121
|
}
|
|
122
122
|
/**
|
|
123
|
-
*
|
|
123
|
+
* Mark a service as standing for the construct it was derived from.
|
|
124
124
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
125
|
+
* \`.publisher(users.publisher)\` takes a service, not the topic, and an id
|
|
126
|
+
* cannot be recovered from a service name. Without the mark the edge was lost:
|
|
127
|
+
* the manifest never heard that the API publishes to \`users\`, so the app's
|
|
128
|
+
* container was composed without \`USERS_PUBLISHER_CONNECTION_STRING\`.
|
|
128
129
|
*/
|
|
130
|
+
declare function derivedFrom<S extends Service>(service: S, construct: {
|
|
131
|
+
id: string;
|
|
132
|
+
}): S;
|
|
133
|
+
/**
|
|
134
|
+
* \`existing\`, with the edge a service stands for — a construct's own id, or
|
|
135
|
+
* the id a derived service was marked with. Unchanged for a plain service.
|
|
136
|
+
*/
|
|
137
|
+
declare function edgesWith(service: unknown, existing?: readonly string[]): string[];
|
|
129
138
|
declare function isConsumable(value: unknown): value is Consumable;
|
|
130
139
|
/**
|
|
131
140
|
* The service behind either face.
|
|
@@ -136,5 +145,5 @@ declare function isConsumable(value: unknown): value is Consumable;
|
|
|
136
145
|
*/
|
|
137
146
|
declare function serviceOf<TName extends string, TClient>(source: Consumable<TName, TClient> | Service<TName, TClient>): Service<TName, TClient>;
|
|
138
147
|
//#endregion
|
|
139
|
-
export { Infer as a, ServicesOf as c,
|
|
140
|
-
//# sourceMappingURL=construct-interface-
|
|
148
|
+
export { Infer as a, ServicesOf as c, edgesWith as d, idsOf as f, servicesOf as h, Declarable as i, derivedFrom as l, serviceOf as m, Construct as n, NameOf as o, isConsumable as p, Consumable as r, NotAConstruct as s, Authenticator as t, edgeTo as u };
|
|
149
|
+
//# sourceMappingURL=construct-interface-DPusCUSQ.d.cts.map
|
package/dist/{construct-interface-DIaTNKax.d.cts.map → construct-interface-DPusCUSQ.d.cts.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"construct-interface-
|
|
1
|
+
{"version":3,"file":"construct-interface-DPusCUSQ.d.cts","names":[],"sources":["../src/construct-interface.ts"],"mappings":";;;;;;;;;;;;;UA8BiB,UAAU,+BAA+B;;WAEhD,IAAI;;;;;;;;EASb,WAAW;;;;;;;;WASF,UAAU,mCAEhB,QAAQ,aAAa,QAAQ;;;;;;;;;;;UAYhB,WAAW;WAClB,IAAI;EACb,WAAW;;;;;;;;;;UAWK,cAAc,4BAA4B;EAC1D,OACC,SAAS,SACT,WAAW,wBACT,QAAQ;;;;;;;;;;;;;;iBAeI,OAAO,WAAW,aAAa;;KAWnC,MAAM,KACjB,UAAU,wBAAwB,WAAW;;KAGlC,OAAO,KAClB,UAAU,gBAAgB,kBAAkB;;;;;;;;;;;UAY5B,WAAW,+BAA+B;WACjD,IAAI;WACJ,SAAS,QAAQ,aAAa,QAAQ;EAC/C,WAAW;;;;;;;;;;KAWA,WAAW,mBAAmB,mBACxC,WAAW,IAAI,EAAE;;;;;;;;iBAUH,iBAAiB,mBAAmB,cACnD,YAAY,IACV,WAAW;;;;;;;;;;iBAiBE,MACf,qBAAqB,cACrB;;cAMY,sBAAsB;WACb;EAArB,YAAqB;;;;;;;;;;iBAqCN,YAAY,UAAU,SACrC,SAAS,GACT;EAAa;IACX;;;;;iBASa,UACf,kBACA;iBAce,aAAa,iBAAiB,SAAS;;;;;;;;iBAmBvC,UAAU,sBAAsB,SAC/C,QAAQ,WAAW,OAAO,WAAW,QAAQ,OAAO,WAClD,QAAQ,OAAO"}
|
|
@@ -62,6 +62,33 @@ var NotAConstruct = class extends Error {
|
|
|
62
62
|
* `@geekmidas/constructs` — a linked workspace, two versions in a lockfile — is
|
|
63
63
|
* still a construct, and `instanceof` is exactly the check that says otherwise.
|
|
64
64
|
*/
|
|
65
|
+
/**
|
|
66
|
+
* The construct a service was derived from — the \`users\` topic, for
|
|
67
|
+
* \`users.publisher\`. A symbol, so it rides on the service object without
|
|
68
|
+
* becoming part of its shape.
|
|
69
|
+
*/
|
|
70
|
+
const DERIVED_FROM = Symbol.for("@geekmidas/constructs/derivedFrom");
|
|
71
|
+
/**
|
|
72
|
+
* Mark a service as standing for the construct it was derived from.
|
|
73
|
+
*
|
|
74
|
+
* \`.publisher(users.publisher)\` takes a service, not the topic, and an id
|
|
75
|
+
* cannot be recovered from a service name. Without the mark the edge was lost:
|
|
76
|
+
* the manifest never heard that the API publishes to \`users\`, so the app's
|
|
77
|
+
* container was composed without \`USERS_PUBLISHER_CONNECTION_STRING\`.
|
|
78
|
+
*/
|
|
79
|
+
function derivedFrom(service, construct) {
|
|
80
|
+
Object.defineProperty(service, DERIVED_FROM, { value: construct.id });
|
|
81
|
+
return service;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* \`existing\`, with the edge a service stands for — a construct's own id, or
|
|
85
|
+
* the id a derived service was marked with. Unchanged for a plain service.
|
|
86
|
+
*/
|
|
87
|
+
function edgesWith(service, existing = []) {
|
|
88
|
+
if (isConsumable(service)) return idsOf([service], existing);
|
|
89
|
+
const id = typeof service === "object" && service !== null ? service[DERIVED_FROM] : void 0;
|
|
90
|
+
return typeof id === "string" ? [.../* @__PURE__ */ new Set([...existing, id])] : [...existing];
|
|
91
|
+
}
|
|
65
92
|
function isConsumable(value) {
|
|
66
93
|
if (typeof value !== "object" || value === null) return false;
|
|
67
94
|
const candidate = value;
|
|
@@ -79,7 +106,9 @@ function serviceOf(source) {
|
|
|
79
106
|
}
|
|
80
107
|
//#endregion
|
|
81
108
|
exports.NotAConstruct = NotAConstruct;
|
|
109
|
+
exports.derivedFrom = derivedFrom;
|
|
82
110
|
exports.edgeTo = edgeTo;
|
|
111
|
+
exports.edgesWith = edgesWith;
|
|
83
112
|
exports.idsOf = idsOf;
|
|
84
113
|
exports.isConsumable = isConsumable;
|
|
85
114
|
exports.serviceOf = serviceOf;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"construct-interface.cjs","names":[],"sources":["../src/construct-interface.ts"],"sourcesContent":["/**\n * The construct contract.\n *\n * A construct is three things at once — an infrastructure requirement at build\n * time, a runtime capability, and something other code can consume — which is\n * what forces exactly these three members.\n *\n * An **interface**, not a base class: `Endpoint`, `Cron`, `Queue`, and `Topic`\n * can satisfy it without inheritance surgery, a lifted `Service` can satisfy it\n * as a plain object, and `.dependsOn()` gets its enforcement structurally — a\n * `Service` (`{ serviceName, register }`) simply does not match\n * `{ id, declare, service }`, with no `instanceof` anywhere.\n *\n * Lives in its own module while the existing abstract `Construct` class is\n * still in use; it takes that name once the class is gone.\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Declaration, Dependency } from '@geekmidas/manifest';\nimport type { Service } from '@geekmidas/services';\n\n/**\n * @typeParam TName - the construct's canonical id, PascalCase. Keeping it a\n * literal is what lets the service key be `Uncapitalize<TName>` — a TypeScript\n * intrinsic, so no custom transform needs a runtime twin.\n * @typeParam TClient - what consuming this construct hands you. `never` means it\n * owns no address and cannot be consumed: a `Cron` is fired by a schedule and a\n * `Subscriber` is reached through its topic, so `.dependsOn()` on either is a\n * compile error rather than a stub that throws.\n */\nexport interface Construct<TName extends string = string, TClient = never> {\n\t/** Canonical, PascalCase, unique within the manifest. */\n\treadonly id: TName;\n\n\t/**\n\t * The build-time face: what this construct contributes to the manifest.\n\t *\n\t * Returns an array because some constructs contribute more than one node — a\n\t * queue declares the queue and its worker, a database declares its migrator\n\t * and seeder. Most return one.\n\t */\n\tdeclare(): Declaration[];\n\n\t/**\n\t * The consumable face — the address this construct owns, as a client.\n\t *\n\t * A field assigned once rather than a getter. `Construct.getEnvironment`\n\t * caches by service *object*, and `Topic.publisher` returns a fresh literal\n\t * on every access, so that cache never hits for topics or queues today.\n\t */\n\treadonly service: [TClient] extends [never]\n\t\t? never\n\t\t: Service<Uncapitalize<TName>, TClient>;\n}\n\n/**\n * The build-time face alone — an id and what it contributes to the manifest.\n *\n * Weaker than {@link Consumable} on purpose. An edge is not always an\n * injection: a site depending on an API needs that API's *address at build\n * time* and has no service record to put a client into, so requiring one would\n * make the weakest kind of edge impossible to express with the strongest\n * interface. Discovery asks this same question and nothing more.\n */\nexport interface Declarable<TName extends string = string> {\n\treadonly id: TName;\n\tdeclare(): Declaration[];\n}\n\n/**\n * A construct that authenticates requests — what `RestApi.auth()` takes.\n *\n * `verify` is the one thing every provider shares: given a request's headers,\n * whose request is it — the session, or `null` when there is none. Whether the\n * provider is a server mounted beside the API, deployed elsewhere, or an issuer\n * somebody else runs is its own business; a surface only ever asks this.\n */\nexport interface Authenticator<TSession = unknown> extends Declarable {\n\tverify(\n\t\theaders: Headers,\n\t\tenvParser: EnvironmentParser<{}>,\n\t): Promise<TSession | null>;\n}\n\n/**\n * The edge that points at a construct.\n *\n * A construct's *canonical* node is the one carrying its own id — the others are\n * things it owns rather than things it is, like the signing secret beside an\n * auth server — so the kind an edge records is read off that node instead of\n * being written down beside it. Hand-written kinds are the same fact stated\n * twice, and the copy is what goes stale when a construct changes what it\n * declares.\n *\n * @throws {NotAConstruct} when the value declares nothing under its own id.\n */\nexport function edgeTo(construct: Declarable): Dependency {\n\tconst canonical = construct\n\t\t.declare()\n\t\t.find((declaration) => declaration.id === construct.id);\n\n\tif (!canonical) throw new NotAConstruct(construct);\n\n\treturn { target: construct.id, kind: canonical.kind };\n}\n\n/** What consuming a construct hands you. */\nexport type Infer<C> =\n\tC extends Construct<string, infer TClient> ? TClient : never;\n\n/** A construct's canonical id. */\nexport type NameOf<C> =\n\tC extends Construct<infer TName, unknown> ? TName : never;\n\n/**\n * What `.dependsOn([…])` accepts: a construct that owns a client.\n *\n * Structural, and deliberately narrower than {@link Construct} — it is the\n * face a *consumer* needs, so the builders can accept a database, a bucket, a\n * mail sender, a queue, or a topic without importing any of them. A plain\n * `Service` is not one of these, which is the point: constructs-only confines\n * env sniffing to `.services()` and the explicit lift, so retiring that method\n * retires implicit inference with it.\n */\nexport interface Consumable<TName extends string = string, TClient = unknown> {\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, TClient>;\n\tdeclare(): Declaration[];\n}\n\n/**\n * The services a list of constructs dissolves into.\n *\n * A mapped tuple, so `[db, uploads]` types as `[Service<'orders', Kysely<DB>>,\n * Service<'uploads', StorageClient>]` and the handler's service record gains\n * both keys — the same machinery `.services()` already uses, which is why\n * `dependsOn` needs no type plumbing of its own.\n */\nexport type ServicesOf<T extends readonly Consumable[]> = {\n\t[K in keyof T]: T[K]['service'];\n};\n\n/**\n * The services of a list of constructs, at runtime.\n *\n * @throws {NotAConstruct} when handed something else. The type already refuses\n * it, but a JavaScript caller gets no such warning, and a `Service` slipping\n * through would fail later as an undefined key in the handler.\n */\nexport function servicesOf<const T extends readonly Consumable[]>(\n\tconstructs: T,\n): ServicesOf<T> {\n\treturn constructs.map((construct) => {\n\t\tif (!isConsumable(construct)) throw new NotAConstruct(construct);\n\n\t\treturn construct.service;\n\t}) as ServicesOf<T>;\n}\n\n/**\n * The other half of `servicesOf`: the same edge, as the manifest records it.\n *\n * `.dependsOn()` used to keep only the services, which is what a handler runs\n * with — and an id cannot be recovered from a service name reliably, so the\n * edges were gone by the time the build could read them. Both halves are taken\n * from one call now, and `existing` is folded in so repeated `.dependsOn()`\n * calls accumulate the way `.services()` already does.\n */\nexport function idsOf(\n\tconstructs: readonly Consumable[],\n\texisting: readonly string[] = [],\n): string[] {\n\treturn [...new Set([...existing, ...constructs.map(({ id }) => id)])];\n}\n\n/** Something that is not a construct was passed to `.dependsOn()`. */\nexport class NotAConstruct extends Error {\n\tconstructor(readonly value: unknown) {\n\t\tconst name =\n\t\t\ttypeof value === 'object' && value !== null && 'serviceName' in value\n\t\t\t\t? `the service '${String((value as { serviceName: unknown }).serviceName)}'`\n\t\t\t\t: `a ${typeof value}`;\n\n\t\tsuper(\n\t\t\t`.dependsOn() takes constructs, and was given ${name}. ` +\n\t\t\t\t`Pass it to .services([…]) instead, or lift it with ` +\n\t\t\t\t`Construct.fromService().`,\n\t\t);\n\t\tthis.name = 'NotAConstruct';\n\t}\n}\n\n/**\n * Whether a value is a construct rather than a bare `Service`.\n *\n * Structural rather than `instanceof`: a construct from a second copy of\n * `@geekmidas/constructs` — a linked workspace, two versions in a lockfile — is\n * still a construct, and `instanceof` is exactly the check that says otherwise.\n */\nexport function isConsumable(value: unknown): value is Consumable {\n\tif (typeof value !== 'object' || value === null) return false;\n\n\tconst candidate = value as Partial<Consumable>;\n\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\ttypeof candidate.declare === 'function' &&\n\t\ttypeof (candidate.service as Service | undefined)?.serviceName === 'string'\n\t);\n}\n\n/**\n * The service behind either face.\n *\n * `.database()` and the publisher slots take a construct now; they took a\n * `Service` before and still do, because a hand-written one is exactly what\n * `Construct.fromService` exists to lift and not everything has been lifted.\n */\nexport function serviceOf<TName extends string, TClient>(\n\tsource: Consumable<TName, TClient> | Service<TName, TClient>,\n): Service<TName, TClient> {\n\t// A construct's service key is `Uncapitalize<TName>` and the slot is typed\n\t// by `TName`; the two describe the same key, and only the cast says so.\n\treturn isConsumable(source)\n\t\t? (source.service as unknown as Service<TName, TClient>)\n\t\t: (source as Service<TName, TClient>);\n}\n"],"mappings":";;;;;;;;;;;;;;AAgGA,SAAgB,OAAO,WAAmC;CACzD,MAAM,YAAY,UAChB,QAAQ,CAAC,CACT,MAAM,gBAAgB,YAAY,OAAO,UAAU,EAAE;CAEvD,IAAI,CAAC,WAAW,MAAM,IAAI,cAAc,SAAS;CAEjD,OAAO;EAAE,QAAQ,UAAU;EAAI,MAAM,UAAU;CAAK;AACrD;;;;;;;;AA6CA,SAAgB,WACf,YACgB;CAChB,OAAO,WAAW,KAAK,cAAc;EACpC,IAAI,CAAC,aAAa,SAAS,GAAG,MAAM,IAAI,cAAc,SAAS;EAE/D,OAAO,UAAU;CAClB,CAAC;AACF;;;;;;;;;;AAWA,SAAgB,MACf,YACA,WAA8B,CAAC,GACpB;CACX,OAAO,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,UAAU,GAAG,WAAW,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;AACrE;;AAGA,IAAa,gBAAb,cAAmC,MAAM;CACnB;CAArB,YAAY,OAAyB;EACpC,MAAM,OACL,OAAO,UAAU,YAAY,UAAU,QAAQ,iBAAiB,QAC7D,gBAAgB,OAAQ,MAAmC,WAAW,EAAE,KACxE,KAAK,OAAO;EAEhB,MACC,gDAAgD,KAAK,8EAGtD;EAVoB,KAAA,QAAA;EAWpB,KAAK,OAAO;CACb;AACD;;;;;;;;AASA,SAAgB,aAAa,OAAqC;CACjE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CAExD,MAAM,YAAY;CAElB,OACC,OAAO,UAAU,OAAO,YACxB,OAAO,UAAU,YAAY,cAC7B,OAAQ,UAAU,SAAiC,gBAAgB;AAErE;;;;;;;;AASA,SAAgB,UACf,QAC0B;CAG1B,OAAO,aAAa,MAAM,IACtB,OAAO,UACP;AACL"}
|
|
1
|
+
{"version":3,"file":"construct-interface.cjs","names":[],"sources":["../src/construct-interface.ts"],"sourcesContent":["/**\n * The construct contract.\n *\n * A construct is three things at once — an infrastructure requirement at build\n * time, a runtime capability, and something other code can consume — which is\n * what forces exactly these three members.\n *\n * An **interface**, not a base class: `Endpoint`, `Cron`, `Queue`, and `Topic`\n * can satisfy it without inheritance surgery, a lifted `Service` can satisfy it\n * as a plain object, and `.dependsOn()` gets its enforcement structurally — a\n * `Service` (`{ serviceName, register }`) simply does not match\n * `{ id, declare, service }`, with no `instanceof` anywhere.\n *\n * Lives in its own module while the existing abstract `Construct` class is\n * still in use; it takes that name once the class is gone.\n */\n\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Declaration, Dependency } from '@geekmidas/manifest';\nimport type { Service } from '@geekmidas/services';\n\n/**\n * @typeParam TName - the construct's canonical id, PascalCase. Keeping it a\n * literal is what lets the service key be `Uncapitalize<TName>` — a TypeScript\n * intrinsic, so no custom transform needs a runtime twin.\n * @typeParam TClient - what consuming this construct hands you. `never` means it\n * owns no address and cannot be consumed: a `Cron` is fired by a schedule and a\n * `Subscriber` is reached through its topic, so `.dependsOn()` on either is a\n * compile error rather than a stub that throws.\n */\nexport interface Construct<TName extends string = string, TClient = never> {\n\t/** Canonical, PascalCase, unique within the manifest. */\n\treadonly id: TName;\n\n\t/**\n\t * The build-time face: what this construct contributes to the manifest.\n\t *\n\t * Returns an array because some constructs contribute more than one node — a\n\t * queue declares the queue and its worker, a database declares its migrator\n\t * and seeder. Most return one.\n\t */\n\tdeclare(): Declaration[];\n\n\t/**\n\t * The consumable face — the address this construct owns, as a client.\n\t *\n\t * A field assigned once rather than a getter. `Construct.getEnvironment`\n\t * caches by service *object*, and `Topic.publisher` returns a fresh literal\n\t * on every access, so that cache never hits for topics or queues today.\n\t */\n\treadonly service: [TClient] extends [never]\n\t\t? never\n\t\t: Service<Uncapitalize<TName>, TClient>;\n}\n\n/**\n * The build-time face alone — an id and what it contributes to the manifest.\n *\n * Weaker than {@link Consumable} on purpose. An edge is not always an\n * injection: a site depending on an API needs that API's *address at build\n * time* and has no service record to put a client into, so requiring one would\n * make the weakest kind of edge impossible to express with the strongest\n * interface. Discovery asks this same question and nothing more.\n */\nexport interface Declarable<TName extends string = string> {\n\treadonly id: TName;\n\tdeclare(): Declaration[];\n}\n\n/**\n * A construct that authenticates requests — what `RestApi.auth()` takes.\n *\n * `verify` is the one thing every provider shares: given a request's headers,\n * whose request is it — the session, or `null` when there is none. Whether the\n * provider is a server mounted beside the API, deployed elsewhere, or an issuer\n * somebody else runs is its own business; a surface only ever asks this.\n */\nexport interface Authenticator<TSession = unknown> extends Declarable {\n\tverify(\n\t\theaders: Headers,\n\t\tenvParser: EnvironmentParser<{}>,\n\t): Promise<TSession | null>;\n}\n\n/**\n * The edge that points at a construct.\n *\n * A construct's *canonical* node is the one carrying its own id — the others are\n * things it owns rather than things it is, like the signing secret beside an\n * auth server — so the kind an edge records is read off that node instead of\n * being written down beside it. Hand-written kinds are the same fact stated\n * twice, and the copy is what goes stale when a construct changes what it\n * declares.\n *\n * @throws {NotAConstruct} when the value declares nothing under its own id.\n */\nexport function edgeTo(construct: Declarable): Dependency {\n\tconst canonical = construct\n\t\t.declare()\n\t\t.find((declaration) => declaration.id === construct.id);\n\n\tif (!canonical) throw new NotAConstruct(construct);\n\n\treturn { target: construct.id, kind: canonical.kind };\n}\n\n/** What consuming a construct hands you. */\nexport type Infer<C> =\n\tC extends Construct<string, infer TClient> ? TClient : never;\n\n/** A construct's canonical id. */\nexport type NameOf<C> =\n\tC extends Construct<infer TName, unknown> ? TName : never;\n\n/**\n * What `.dependsOn([…])` accepts: a construct that owns a client.\n *\n * Structural, and deliberately narrower than {@link Construct} — it is the\n * face a *consumer* needs, so the builders can accept a database, a bucket, a\n * mail sender, a queue, or a topic without importing any of them. A plain\n * `Service` is not one of these, which is the point: constructs-only confines\n * env sniffing to `.services()` and the explicit lift, so retiring that method\n * retires implicit inference with it.\n */\nexport interface Consumable<TName extends string = string, TClient = unknown> {\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, TClient>;\n\tdeclare(): Declaration[];\n}\n\n/**\n * The services a list of constructs dissolves into.\n *\n * A mapped tuple, so `[db, uploads]` types as `[Service<'orders', Kysely<DB>>,\n * Service<'uploads', StorageClient>]` and the handler's service record gains\n * both keys — the same machinery `.services()` already uses, which is why\n * `dependsOn` needs no type plumbing of its own.\n */\nexport type ServicesOf<T extends readonly Consumable[]> = {\n\t[K in keyof T]: T[K]['service'];\n};\n\n/**\n * The services of a list of constructs, at runtime.\n *\n * @throws {NotAConstruct} when handed something else. The type already refuses\n * it, but a JavaScript caller gets no such warning, and a `Service` slipping\n * through would fail later as an undefined key in the handler.\n */\nexport function servicesOf<const T extends readonly Consumable[]>(\n\tconstructs: T,\n): ServicesOf<T> {\n\treturn constructs.map((construct) => {\n\t\tif (!isConsumable(construct)) throw new NotAConstruct(construct);\n\n\t\treturn construct.service;\n\t}) as ServicesOf<T>;\n}\n\n/**\n * The other half of `servicesOf`: the same edge, as the manifest records it.\n *\n * `.dependsOn()` used to keep only the services, which is what a handler runs\n * with — and an id cannot be recovered from a service name reliably, so the\n * edges were gone by the time the build could read them. Both halves are taken\n * from one call now, and `existing` is folded in so repeated `.dependsOn()`\n * calls accumulate the way `.services()` already does.\n */\nexport function idsOf(\n\tconstructs: readonly Consumable[],\n\texisting: readonly string[] = [],\n): string[] {\n\treturn [...new Set([...existing, ...constructs.map(({ id }) => id)])];\n}\n\n/** Something that is not a construct was passed to `.dependsOn()`. */\nexport class NotAConstruct extends Error {\n\tconstructor(readonly value: unknown) {\n\t\tconst name =\n\t\t\ttypeof value === 'object' && value !== null && 'serviceName' in value\n\t\t\t\t? `the service '${String((value as { serviceName: unknown }).serviceName)}'`\n\t\t\t\t: `a ${typeof value}`;\n\n\t\tsuper(\n\t\t\t`.dependsOn() takes constructs, and was given ${name}. ` +\n\t\t\t\t`Pass it to .services([…]) instead, or lift it with ` +\n\t\t\t\t`Construct.fromService().`,\n\t\t);\n\t\tthis.name = 'NotAConstruct';\n\t}\n}\n\n/**\n * Whether a value is a construct rather than a bare `Service`.\n *\n * Structural rather than `instanceof`: a construct from a second copy of\n * `@geekmidas/constructs` — a linked workspace, two versions in a lockfile — is\n * still a construct, and `instanceof` is exactly the check that says otherwise.\n */\n/**\n * The construct a service was derived from — the \\`users\\` topic, for\n * \\`users.publisher\\`. A symbol, so it rides on the service object without\n * becoming part of its shape.\n */\nconst DERIVED_FROM = Symbol.for('@geekmidas/constructs/derivedFrom');\n\n/**\n * Mark a service as standing for the construct it was derived from.\n *\n * \\`.publisher(users.publisher)\\` takes a service, not the topic, and an id\n * cannot be recovered from a service name. Without the mark the edge was lost:\n * the manifest never heard that the API publishes to \\`users\\`, so the app's\n * container was composed without \\`USERS_PUBLISHER_CONNECTION_STRING\\`.\n */\nexport function derivedFrom<S extends Service>(\n\tservice: S,\n\tconstruct: { id: string },\n): S {\n\tObject.defineProperty(service, DERIVED_FROM, { value: construct.id });\n\treturn service;\n}\n\n/**\n * \\`existing\\`, with the edge a service stands for — a construct's own id, or\n * the id a derived service was marked with. Unchanged for a plain service.\n */\nexport function edgesWith(\n\tservice: unknown,\n\texisting: readonly string[] = [],\n): string[] {\n\tif (isConsumable(service)) return idsOf([service], existing);\n\n\tconst id =\n\t\ttypeof service === 'object' && service !== null\n\t\t\t? (service as { [DERIVED_FROM]?: unknown })[DERIVED_FROM]\n\t\t\t: undefined;\n\n\treturn typeof id === 'string'\n\t\t? [...new Set([...existing, id])]\n\t\t: [...existing];\n}\n\nexport function isConsumable(value: unknown): value is Consumable {\n\tif (typeof value !== 'object' || value === null) return false;\n\n\tconst candidate = value as Partial<Consumable>;\n\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\ttypeof candidate.declare === 'function' &&\n\t\ttypeof (candidate.service as Service | undefined)?.serviceName === 'string'\n\t);\n}\n\n/**\n * The service behind either face.\n *\n * `.database()` and the publisher slots take a construct now; they took a\n * `Service` before and still do, because a hand-written one is exactly what\n * `Construct.fromService` exists to lift and not everything has been lifted.\n */\nexport function serviceOf<TName extends string, TClient>(\n\tsource: Consumable<TName, TClient> | Service<TName, TClient>,\n): Service<TName, TClient> {\n\t// A construct's service key is `Uncapitalize<TName>` and the slot is typed\n\t// by `TName`; the two describe the same key, and only the cast says so.\n\treturn isConsumable(source)\n\t\t? (source.service as unknown as Service<TName, TClient>)\n\t\t: (source as Service<TName, TClient>);\n}\n"],"mappings":";;;;;;;;;;;;;;AAgGA,SAAgB,OAAO,WAAmC;CACzD,MAAM,YAAY,UAChB,QAAQ,CAAC,CACT,MAAM,gBAAgB,YAAY,OAAO,UAAU,EAAE;CAEvD,IAAI,CAAC,WAAW,MAAM,IAAI,cAAc,SAAS;CAEjD,OAAO;EAAE,QAAQ,UAAU;EAAI,MAAM,UAAU;CAAK;AACrD;;;;;;;;AA6CA,SAAgB,WACf,YACgB;CAChB,OAAO,WAAW,KAAK,cAAc;EACpC,IAAI,CAAC,aAAa,SAAS,GAAG,MAAM,IAAI,cAAc,SAAS;EAE/D,OAAO,UAAU;CAClB,CAAC;AACF;;;;;;;;;;AAWA,SAAgB,MACf,YACA,WAA8B,CAAC,GACpB;CACX,OAAO,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,UAAU,GAAG,WAAW,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;AACrE;;AAGA,IAAa,gBAAb,cAAmC,MAAM;CACnB;CAArB,YAAY,OAAyB;EACpC,MAAM,OACL,OAAO,UAAU,YAAY,UAAU,QAAQ,iBAAiB,QAC7D,gBAAgB,OAAQ,MAAmC,WAAW,EAAE,KACxE,KAAK,OAAO;EAEhB,MACC,gDAAgD,KAAK,8EAGtD;EAVoB,KAAA,QAAA;EAWpB,KAAK,OAAO;CACb;AACD;;;;;;;;;;;;;AAcA,MAAM,eAAe,OAAO,IAAI,mCAAmC;;;;;;;;;AAUnE,SAAgB,YACf,SACA,WACI;CACJ,OAAO,eAAe,SAAS,cAAc,EAAE,OAAO,UAAU,GAAG,CAAC;CACpE,OAAO;AACR;;;;;AAMA,SAAgB,UACf,SACA,WAA8B,CAAC,GACpB;CACX,IAAI,aAAa,OAAO,GAAG,OAAO,MAAM,CAAC,OAAO,GAAG,QAAQ;CAE3D,MAAM,KACL,OAAO,YAAY,YAAY,YAAY,OACvC,QAAyC,gBAC1C,KAAA;CAEJ,OAAO,OAAO,OAAO,WAClB,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC,CAAC,IAC9B,CAAC,GAAG,QAAQ;AAChB;AAEA,SAAgB,aAAa,OAAqC;CACjE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CAExD,MAAM,YAAY;CAElB,OACC,OAAO,UAAU,OAAO,YACxB,OAAO,UAAU,YAAY,cAC7B,OAAQ,UAAU,SAAiC,gBAAgB;AAErE;;;;;;;;AASA,SAAgB,UACf,QAC0B;CAG1B,OAAO,aAAa,MAAM,IACtB,OAAO,UACP;AACL"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as Infer, c as ServicesOf, d as
|
|
2
|
-
export { Authenticator, Construct, Consumable, Declarable, Infer, NameOf, NotAConstruct, ServicesOf, edgeTo, idsOf, isConsumable, serviceOf, servicesOf };
|
|
1
|
+
import { a as Infer, c as ServicesOf, d as edgesWith, f as idsOf, h as servicesOf, i as Declarable, l as derivedFrom, m as serviceOf, n as Construct, o as NameOf, p as isConsumable, r as Consumable, s as NotAConstruct, t as Authenticator, u as edgeTo } from "./construct-interface-DPusCUSQ.cjs";
|
|
2
|
+
export { Authenticator, Construct, Consumable, Declarable, Infer, NameOf, NotAConstruct, ServicesOf, derivedFrom, edgeTo, edgesWith, idsOf, isConsumable, serviceOf, servicesOf };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as Infer, c as ServicesOf, d as
|
|
2
|
-
export { Authenticator, Construct, Consumable, Declarable, Infer, NameOf, NotAConstruct, ServicesOf, edgeTo, idsOf, isConsumable, serviceOf, servicesOf };
|
|
1
|
+
import { a as Infer, c as ServicesOf, d as edgesWith, f as idsOf, h as servicesOf, i as Declarable, l as derivedFrom, m as serviceOf, n as Construct, o as NameOf, p as isConsumable, r as Consumable, s as NotAConstruct, t as Authenticator, u as edgeTo } from "./construct-interface-D2gesxos.mjs";
|
|
2
|
+
export { Authenticator, Construct, Consumable, Declarable, Infer, NameOf, NotAConstruct, ServicesOf, derivedFrom, edgeTo, edgesWith, idsOf, isConsumable, serviceOf, servicesOf };
|
|
@@ -61,6 +61,33 @@ var NotAConstruct = class extends Error {
|
|
|
61
61
|
* `@geekmidas/constructs` — a linked workspace, two versions in a lockfile — is
|
|
62
62
|
* still a construct, and `instanceof` is exactly the check that says otherwise.
|
|
63
63
|
*/
|
|
64
|
+
/**
|
|
65
|
+
* The construct a service was derived from — the \`users\` topic, for
|
|
66
|
+
* \`users.publisher\`. A symbol, so it rides on the service object without
|
|
67
|
+
* becoming part of its shape.
|
|
68
|
+
*/
|
|
69
|
+
const DERIVED_FROM = Symbol.for("@geekmidas/constructs/derivedFrom");
|
|
70
|
+
/**
|
|
71
|
+
* Mark a service as standing for the construct it was derived from.
|
|
72
|
+
*
|
|
73
|
+
* \`.publisher(users.publisher)\` takes a service, not the topic, and an id
|
|
74
|
+
* cannot be recovered from a service name. Without the mark the edge was lost:
|
|
75
|
+
* the manifest never heard that the API publishes to \`users\`, so the app's
|
|
76
|
+
* container was composed without \`USERS_PUBLISHER_CONNECTION_STRING\`.
|
|
77
|
+
*/
|
|
78
|
+
function derivedFrom(service, construct) {
|
|
79
|
+
Object.defineProperty(service, DERIVED_FROM, { value: construct.id });
|
|
80
|
+
return service;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* \`existing\`, with the edge a service stands for — a construct's own id, or
|
|
84
|
+
* the id a derived service was marked with. Unchanged for a plain service.
|
|
85
|
+
*/
|
|
86
|
+
function edgesWith(service, existing = []) {
|
|
87
|
+
if (isConsumable(service)) return idsOf([service], existing);
|
|
88
|
+
const id = typeof service === "object" && service !== null ? service[DERIVED_FROM] : void 0;
|
|
89
|
+
return typeof id === "string" ? [.../* @__PURE__ */ new Set([...existing, id])] : [...existing];
|
|
90
|
+
}
|
|
64
91
|
function isConsumable(value) {
|
|
65
92
|
if (typeof value !== "object" || value === null) return false;
|
|
66
93
|
const candidate = value;
|
|
@@ -77,6 +104,6 @@ function serviceOf(source) {
|
|
|
77
104
|
return isConsumable(source) ? source.service : source;
|
|
78
105
|
}
|
|
79
106
|
//#endregion
|
|
80
|
-
export { NotAConstruct, edgeTo, idsOf, isConsumable, serviceOf, servicesOf };
|
|
107
|
+
export { NotAConstruct, derivedFrom, edgeTo, edgesWith, idsOf, isConsumable, serviceOf, servicesOf };
|
|
81
108
|
|
|
82
109
|
//# sourceMappingURL=construct-interface.mjs.map
|