@geekmidas/cli 10.0.0-alpha.28 → 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/{bundler-D5RcGmVM.mjs → bundler-7iaO93G8.mjs} +2 -2
- package/dist/{bundler-D5RcGmVM.mjs.map → bundler-7iaO93G8.mjs.map} +1 -1
- package/dist/{bundler-mjqGAvu1.cjs → bundler-CYF5mWi9.cjs} +2 -2
- package/dist/{bundler-mjqGAvu1.cjs.map → bundler-CYF5mWi9.cjs.map} +1 -1
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/{github-zkzu-ka2.mjs → github-DPpNfzu4.mjs} +2 -2
- package/dist/{github-zkzu-ka2.mjs.map → github-DPpNfzu4.mjs.map} +1 -1
- package/dist/{github-BUCXEl2v.cjs → github-ZL3RcUVD.cjs} +2 -2
- package/dist/{github-BUCXEl2v.cjs.map → github-ZL3RcUVD.cjs.map} +1 -1
- package/dist/{index-BPN7kghQ.d.cts → index-p89EzILa.d.mts} +2 -2
- package/dist/{index-BPN7kghQ.d.cts.map → index-p89EzILa.d.mts.map} +1 -1
- package/dist/{index-BqWKWRm0.d.mts → index-rU9u761a.d.cts} +2 -2
- package/dist/{index-BqWKWRm0.d.mts.map → index-rU9u761a.d.cts.map} +1 -1
- package/dist/index.cjs +189 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +189 -195
- package/dist/index.mjs.map +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{reconcile-szmJw_Sb.cjs → reconcile-BxZTV2vl.cjs} +5 -82
- package/dist/reconcile-BxZTV2vl.cjs.map +1 -0
- package/dist/{reconcile-DhbCvh4K.mjs → reconcile-yNq6Shau.mjs} +5 -82
- package/dist/reconcile-yNq6Shau.mjs.map +1 -0
- package/dist/{storage-TP0x118M.mjs → storage-BjTLjDDp.mjs} +1 -8
- package/dist/{storage-TP0x118M.mjs.map → storage-BjTLjDDp.mjs.map} +1 -1
- package/dist/{storage-DgypfSD6.cjs → storage-C2JC31V9.cjs} +1 -8
- package/dist/{storage-DgypfSD6.cjs.map → storage-C2JC31V9.cjs.map} +1 -1
- package/dist/{types-7R81hu0i.d.mts → types-CzK6hsXP.d.mts} +1 -17
- package/dist/types-CzK6hsXP.d.mts.map +1 -0
- package/dist/{types-N7c2O2m7.d.cts → types-DOOAevjG.d.cts} +1 -17
- package/dist/types-DOOAevjG.d.cts.map +1 -0
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/{workspace-Cv48QwPK.cjs → workspace-B3ApLW6p.cjs} +2 -2
- package/dist/{workspace-Cv48QwPK.cjs.map → workspace-B3ApLW6p.cjs.map} +1 -1
- package/dist/{workspace-cBobQVoA.mjs → workspace-DnHW8ooI.mjs} +2 -2
- package/dist/{workspace-cBobQVoA.mjs.map → workspace-DnHW8ooI.mjs.map} +1 -1
- package/package.json +14 -14
- package/dist/reconcile-DhbCvh4K.mjs.map +0 -1
- package/dist/reconcile-szmJw_Sb.cjs.map +0 -1
- package/dist/types-7R81hu0i.d.mts.map +0 -1
- package/dist/types-N7c2O2m7.d.cts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -3,11 +3,11 @@ import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mj
|
|
|
3
3
|
import { i as DEFAULT_CACHE, r as ConstructGenerator, s as isPartitionedRoutes, t as discover } from "./discover-CXGlQHm1.mjs";
|
|
4
4
|
import { a as loadConfig, c as parseModuleConfig, i as loadAppConfig, l as appKey, r as getAppNameFromCwd, s as loadWorkspaceConfig, u as derivedApps } from "./config-Dqxa-_Ge.mjs";
|
|
5
5
|
import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-T4IVGSoB.mjs";
|
|
6
|
-
import { c as getKeyPath, i as setCustomSecret, n as readStageSecrets, o as toEmbeddableSecrets, r as secretsExist, s as writeStageSecrets, t as maskPassword } from "./storage-
|
|
7
|
-
import { _ as findAvailablePort, b as loadSecretsForApp, c as applyPostgres, d as cacheBackendFor, f as eventsBackendFor, g as createEntryWrapper, h as createCredentialsPreload, i as reconcileWorkspace, l as resourceName, m as storageBackendFor, n as constructGlobs, o as COMPOSE_PATH, p as providerOf, r as derivedContainers, s as LOCAL_CA_PATH, t as backendsOf, u as rootSite, v as isPortAvailable, x as prepareEntryCredentials, y as loadEnvFiles } from "./workspace-
|
|
6
|
+
import { c as getKeyPath, i as setCustomSecret, n as readStageSecrets, o as toEmbeddableSecrets, r as secretsExist, s as writeStageSecrets, t as maskPassword } from "./storage-BjTLjDDp.mjs";
|
|
7
|
+
import { _ as findAvailablePort, b as loadSecretsForApp, c as applyPostgres, d as cacheBackendFor, f as eventsBackendFor, g as createEntryWrapper, h as createCredentialsPreload, i as reconcileWorkspace, l as resourceName, m as storageBackendFor, n as constructGlobs, o as COMPOSE_PATH, p as providerOf, r as derivedContainers, s as LOCAL_CA_PATH, t as backendsOf, u as rootSite, v as isPortAvailable, x as prepareEntryCredentials, y as loadEnvFiles } from "./workspace-DnHW8ooI.mjs";
|
|
8
8
|
import { a as openapiCommand, c as EndpointGenerator, l as runtimeFor, n as generateOpenApi, o as resolveOpenApiConfig, s as OpenApiTsGenerator } from "./openapi-D-Lk5Hr5.mjs";
|
|
9
9
|
import { i as withRoutes, r as withCompute } from "./emit-Bzq7JsMP.mjs";
|
|
10
|
-
import { a as createStageSecrets, c as generateServiceCredentials, i as writeDockerEnvFromSecrets, l as rotateServicePassword, o as generateConnectionUrls, r as generateFullstackCustomSecrets, s as generateSecurePassword, t as reconcileMissingSecrets } from "./reconcile-
|
|
10
|
+
import { a as createStageSecrets, c as generateServiceCredentials, i as writeDockerEnvFromSecrets, l as rotateServicePassword, o as generateConnectionUrls, r as generateFullstackCustomSecrets, s as generateSecurePassword, t as reconcileMissingSecrets } from "./reconcile-yNq6Shau.mjs";
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import { Command } from "commander";
|
|
13
13
|
import { stdin, stdout } from "node:process";
|
|
@@ -37,7 +37,7 @@ import { lookup } from "node:dns/promises";
|
|
|
37
37
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
38
38
|
import { serviceContext } from "@geekmidas/services";
|
|
39
39
|
//#region package.json
|
|
40
|
-
var version = "10.0.0-alpha.
|
|
40
|
+
var version = "10.0.0-alpha.29";
|
|
41
41
|
//#endregion
|
|
42
42
|
//#region src/auth/credentials.ts
|
|
43
43
|
/**
|
|
@@ -2901,7 +2901,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2901
2901
|
let masterKey;
|
|
2902
2902
|
if (context.production?.bundle && !skipBundle) {
|
|
2903
2903
|
logger$8.log(`\n📦 Bundling production server...`);
|
|
2904
|
-
const { bundleServer } = await import("./bundler-
|
|
2904
|
+
const { bundleServer } = await import("./bundler-7iaO93G8.mjs");
|
|
2905
2905
|
const allConstructs = [
|
|
2906
2906
|
...endpoints.map((e) => e.construct),
|
|
2907
2907
|
...functions.map((f) => f.construct),
|
|
@@ -8121,15 +8121,20 @@ function databaseFiles() {
|
|
|
8121
8121
|
return [{
|
|
8122
8122
|
path: "src/constructs/database.ts",
|
|
8123
8123
|
content: `import { KyselyDatabase } from '@geekmidas/constructs/database/kysely';
|
|
8124
|
-
import type
|
|
8124
|
+
import { CamelCasePlugin, type Generated } from 'kysely';
|
|
8125
8125
|
|
|
8126
|
-
/**
|
|
8126
|
+
/**
|
|
8127
|
+
* Your database schema. Add tables here.
|
|
8128
|
+
*
|
|
8129
|
+
* camelCase, because of the plugin below: the columns are snake_case in
|
|
8130
|
+
* Postgres (\`created_at\`), and Kysely maps between the two.
|
|
8131
|
+
*/
|
|
8127
8132
|
export interface Database {
|
|
8128
8133
|
users: {
|
|
8129
8134
|
id: Generated<string>;
|
|
8130
8135
|
name: string;
|
|
8131
8136
|
email: string;
|
|
8132
|
-
|
|
8137
|
+
createdAt: Generated<Date>;
|
|
8133
8138
|
};
|
|
8134
8139
|
}
|
|
8135
8140
|
|
|
@@ -8144,7 +8149,11 @@ export interface Database {
|
|
|
8144
8149
|
* inference, so passing only \`Database\` would leave the name at \`string\`
|
|
8145
8150
|
* and widen the service key away from \`${db.service}\`.
|
|
8146
8151
|
*/
|
|
8147
|
-
export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}'
|
|
8152
|
+
export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
|
|
8153
|
+
// Yours to keep, change or remove. Every connection this construct opens —
|
|
8154
|
+
// every endpoint, the tests — uses it.
|
|
8155
|
+
plugins: [new CamelCasePlugin()],
|
|
8156
|
+
});
|
|
8148
8157
|
`
|
|
8149
8158
|
}, usersMigration()];
|
|
8150
8159
|
}
|
|
@@ -8319,7 +8328,7 @@ environment and fail in a way that looks like a config bug. Go through
|
|
|
8319
8328
|
\`gkm exec --\`, which is what injects them.`);
|
|
8320
8329
|
if (!isWorker) sections.push(endpointSection(options));
|
|
8321
8330
|
if (isWorker || isServerless) sections.push(backgroundSection(template));
|
|
8322
|
-
if (database) sections.push(databaseSection());
|
|
8331
|
+
if (database) sections.push(databaseSection(), queriesSection());
|
|
8323
8332
|
sections.push(`## Environment
|
|
8324
8333
|
|
|
8325
8334
|
Environment is parsed once, through \`envkit\`, and never read from
|
|
@@ -8506,6 +8515,88 @@ export const monthly = router
|
|
|
8506
8515
|
Queries go through Kysely. Never open your own connection pool — the one the
|
|
8507
8516
|
construct provides is the one that gets credentials, pooling and shutdown.`;
|
|
8508
8517
|
}
|
|
8518
|
+
function queriesSection() {
|
|
8519
|
+
return `## Writing queries and relations
|
|
8520
|
+
|
|
8521
|
+
**Names: snake_case in Postgres, camelCase in TypeScript.** Kysely's
|
|
8522
|
+
\`CamelCasePlugin\` does the mapping, and it is set where you can see it — on
|
|
8523
|
+
the database construct in \`constructs/database.ts\`:
|
|
8524
|
+
|
|
8525
|
+
\`\`\`typescript
|
|
8526
|
+
export const database = new KyselyDatabase<Database, 'Database'>('Database', {
|
|
8527
|
+
plugins: [new CamelCasePlugin()],
|
|
8528
|
+
});
|
|
8529
|
+
\`\`\`
|
|
8530
|
+
|
|
8531
|
+
It is the project's plugin, not the toolbox's: keep it, change it, or remove it,
|
|
8532
|
+
but do it there. It applies to every connection the construct opens: every
|
|
8533
|
+
endpoint, and the tests, which connect through the construct.
|
|
8534
|
+
|
|
8535
|
+
- The \`Database\` interface is camelCase: \`userId\`, \`createdAt\`.
|
|
8536
|
+
- Migrations are the SQL, so they spell columns in snake_case.
|
|
8537
|
+
- A raw \`sql\` fragment is not rewritten. Refer to columns with
|
|
8538
|
+
\`sql.ref('users.createdAt')\` or \`eb.ref(…)\`, never inside the string.
|
|
8539
|
+
- A schema tenant takes its own options. Better Auth's tenant gets no plugin —
|
|
8540
|
+
its tables are its own.
|
|
8541
|
+
|
|
8542
|
+
**Types.** \`Generated<T>\` for every column the database fills in (ids,
|
|
8543
|
+
defaults, timestamps). Derive row types with \`Selectable<Database['users']>\`,
|
|
8544
|
+
\`Insertable<…>\` and \`Updateable<…>\` rather than writing them again.
|
|
8545
|
+
|
|
8546
|
+
**Relations are foreign keys.** Every \`*_id\` column references its parent,
|
|
8547
|
+
with its delete rule written out:
|
|
8548
|
+
|
|
8549
|
+
\`\`\`typescript
|
|
8550
|
+
.addColumn('user_id', 'uuid', (col) =>
|
|
8551
|
+
col.notNull().references('users.id').onDelete('cascade'),
|
|
8552
|
+
)
|
|
8553
|
+
\`\`\`
|
|
8554
|
+
|
|
8555
|
+
- \`restrict\` by default.
|
|
8556
|
+
- \`cascade\` only for rows that belong to the parent — a user's notifications.
|
|
8557
|
+
- \`set null\` for an optional link, on a nullable column.
|
|
8558
|
+
- Index every foreign key column. Postgres does not do it for you.
|
|
8559
|
+
|
|
8560
|
+
**Reading related rows: one query, nested.** \`jsonArrayFrom\` and
|
|
8561
|
+
\`jsonObjectFrom\` from \`kysely/helpers/postgres\` return children already in
|
|
8562
|
+
the shape of the response:
|
|
8563
|
+
|
|
8564
|
+
\`\`\`typescript
|
|
8565
|
+
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
|
8566
|
+
|
|
8567
|
+
const user = await db
|
|
8568
|
+
.selectFrom('users')
|
|
8569
|
+
.select((eb) => [
|
|
8570
|
+
'users.id',
|
|
8571
|
+
'users.name',
|
|
8572
|
+
jsonArrayFrom(
|
|
8573
|
+
eb
|
|
8574
|
+
.selectFrom('notifications')
|
|
8575
|
+
.select(['notifications.id', 'notifications.body', 'notifications.createdAt'])
|
|
8576
|
+
.whereRef('notifications.userId', '=', 'users.id')
|
|
8577
|
+
.orderBy('notifications.createdAt', 'desc'),
|
|
8578
|
+
).as('notifications'),
|
|
8579
|
+
])
|
|
8580
|
+
.where('users.id', '=', id)
|
|
8581
|
+
.executeTakeFirstOrThrow();
|
|
8582
|
+
\`\`\`
|
|
8583
|
+
|
|
8584
|
+
- Never query once per row in a loop.
|
|
8585
|
+
- Use joins to filter, or for a flat result — not to build nested ones by hand.
|
|
8586
|
+
- Qualify columns (\`users.id\`) once a query touches more than one table.
|
|
8587
|
+
- Select the columns the response needs. \`selectAll()\` only returns a whole
|
|
8588
|
+
row.
|
|
8589
|
+
- Nested rows come back as JSON: a timestamp inside \`jsonArrayFrom\` is a
|
|
8590
|
+
string, not a \`Date\`. Say so in the output schema (\`z.coerce.date()\`).
|
|
8591
|
+
|
|
8592
|
+
**Writes that touch several tables run in one transaction.** Use
|
|
8593
|
+
\`withTransaction(db, async (trx) => …)\` from \`@geekmidas/db/kysely\`: it joins
|
|
8594
|
+
the transaction \`db\` already is — an endpoint with an auditor, a test —
|
|
8595
|
+
instead of opening a second one.
|
|
8596
|
+
|
|
8597
|
+
**Lists paginate by cursor**, with \`paginatedSearch\` from
|
|
8598
|
+
\`@geekmidas/db/kysely/pagination\`, not by offset.`;
|
|
8599
|
+
}
|
|
8509
8600
|
function conventionsSection() {
|
|
8510
8601
|
return `## Conventions
|
|
8511
8602
|
|
|
@@ -8680,26 +8771,26 @@ function loadPackageJson() {
|
|
|
8680
8771
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8681
8772
|
*/
|
|
8682
8773
|
const GEEKMIDAS_VERSIONS = {
|
|
8683
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8684
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8685
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8686
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8687
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8688
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8689
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8690
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8691
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8692
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8693
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8694
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8695
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8696
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8697
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8698
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8699
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8700
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8701
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8702
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8774
|
+
"@geekmidas/audit": "~10.0.0-alpha.29",
|
|
8775
|
+
"@geekmidas/auth": "~10.0.0-alpha.29",
|
|
8776
|
+
"@geekmidas/cache": "~10.0.0-alpha.29",
|
|
8777
|
+
"@geekmidas/client": "~10.0.0-alpha.29",
|
|
8778
|
+
"@geekmidas/cloud": "~10.0.0-alpha.29",
|
|
8779
|
+
"@geekmidas/constructs": "~10.0.0-alpha.29",
|
|
8780
|
+
"@geekmidas/db": "~10.0.0-alpha.29",
|
|
8781
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.29",
|
|
8782
|
+
"@geekmidas/envkit": "~10.0.0-alpha.29",
|
|
8783
|
+
"@geekmidas/errors": "~10.0.0-alpha.29",
|
|
8784
|
+
"@geekmidas/events": "~10.0.0-alpha.29",
|
|
8785
|
+
"@geekmidas/logger": "~10.0.0-alpha.29",
|
|
8786
|
+
"@geekmidas/manifest": "~10.0.0-alpha.29",
|
|
8787
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.29",
|
|
8788
|
+
"@geekmidas/schema": "~10.0.0-alpha.29",
|
|
8789
|
+
"@geekmidas/services": "~10.0.0-alpha.29",
|
|
8790
|
+
"@geekmidas/storage": "~10.0.0-alpha.29",
|
|
8791
|
+
"@geekmidas/studio": "~10.0.0-alpha.29",
|
|
8792
|
+
"@geekmidas/telescope": "~10.0.0-alpha.29",
|
|
8793
|
+
"@geekmidas/testkit": "~10.0.0-alpha.29",
|
|
8703
8794
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8704
8795
|
};
|
|
8705
8796
|
//#endregion
|
|
@@ -10637,15 +10728,20 @@ export const logger = createLogger();
|
|
|
10637
10728
|
files.push({
|
|
10638
10729
|
path: "constructs/database.ts",
|
|
10639
10730
|
content: `import { KyselyDatabase } from '@geekmidas/constructs/database/kysely';
|
|
10640
|
-
import type
|
|
10731
|
+
import { CamelCasePlugin, type Generated } from 'kysely';
|
|
10641
10732
|
|
|
10642
|
-
/**
|
|
10733
|
+
/**
|
|
10734
|
+
* Your database schema. Add tables here.
|
|
10735
|
+
*
|
|
10736
|
+
* camelCase, because of the plugin below: the columns are snake_case in
|
|
10737
|
+
* Postgres (\`created_at\`), and Kysely maps between the two.
|
|
10738
|
+
*/
|
|
10643
10739
|
export interface Database {
|
|
10644
10740
|
users: {
|
|
10645
10741
|
id: Generated<string>;
|
|
10646
10742
|
name: string;
|
|
10647
10743
|
email: string;
|
|
10648
|
-
|
|
10744
|
+
createdAt: Generated<Date>;
|
|
10649
10745
|
};
|
|
10650
10746
|
}
|
|
10651
10747
|
|
|
@@ -10656,14 +10752,19 @@ export interface Database {
|
|
|
10656
10752
|
* \`${db.urlKey}\` all derive from this line — which is why nothing lists
|
|
10657
10753
|
* \`postgres\` anywhere.
|
|
10658
10754
|
*/
|
|
10659
|
-
export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}'
|
|
10755
|
+
export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
|
|
10756
|
+
// Yours to keep, change or remove. Every connection this construct opens —
|
|
10757
|
+
// every endpoint, the tests — uses it.
|
|
10758
|
+
plugins: [new CamelCasePlugin()],
|
|
10759
|
+
});
|
|
10660
10760
|
|
|
10661
10761
|
/**
|
|
10662
10762
|
* The auth server's own schema in that same Postgres, with its own role.
|
|
10663
10763
|
*
|
|
10664
10764
|
* A schema tenant rather than a second database: one container, and a role
|
|
10665
10765
|
* whose \`search_path\` is pinned, so Better Auth's tables cannot collide with
|
|
10666
|
-
* the application's.
|
|
10766
|
+
* the application's. It takes its own options: no plugin, because Better
|
|
10767
|
+
* Auth names its own columns.
|
|
10667
10768
|
*/
|
|
10668
10769
|
export const authDb = database.schema<Record<string, never>, 'AuthDb'>('AuthDb');
|
|
10669
10770
|
`
|
|
@@ -10991,54 +11092,11 @@ export const getUserEndpoint = router
|
|
|
10991
11092
|
}
|
|
10992
11093
|
];
|
|
10993
11094
|
if (options.monorepo) {
|
|
10994
|
-
files.push({
|
|
10995
|
-
path: "src/services/auth.ts",
|
|
10996
|
-
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
10997
|
-
|
|
10998
|
-
export interface Session {
|
|
10999
|
-
user: {
|
|
11000
|
-
id: string;
|
|
11001
|
-
email: string;
|
|
11002
|
-
name: string;
|
|
11003
|
-
};
|
|
11004
|
-
}
|
|
11005
|
-
|
|
11006
|
-
export interface AuthClient {
|
|
11007
|
-
getSession: (cookie: string) => Promise<Session | null>;
|
|
11008
|
-
}
|
|
11009
|
-
|
|
11010
|
-
export const authService = {
|
|
11011
|
-
serviceName: 'auth' as const,
|
|
11012
|
-
async register({ envParser, context }: ServiceRegisterOptions) {
|
|
11013
|
-
const logger = context.getLogger();
|
|
11014
|
-
|
|
11015
|
-
const config = envParser
|
|
11016
|
-
.create((get) => ({
|
|
11017
|
-
url: get('AUTH_URL').string(),
|
|
11018
|
-
}))
|
|
11019
|
-
.parse();
|
|
11020
|
-
|
|
11021
|
-
logger.info({ authUrl: config.url }, 'Auth service configured');
|
|
11022
|
-
|
|
11023
|
-
return {
|
|
11024
|
-
getSession: async (cookie: string): Promise<Session | null> => {
|
|
11025
|
-
const res = await fetch(\`\${config.url}/api/auth/get-session\`, {
|
|
11026
|
-
headers: { cookie },
|
|
11027
|
-
});
|
|
11028
|
-
if (!res.ok) return null;
|
|
11029
|
-
return (await res.json()) as Session | null;
|
|
11030
|
-
},
|
|
11031
|
-
};
|
|
11032
|
-
},
|
|
11033
|
-
} satisfies Service<'auth', AuthClient>;
|
|
11034
|
-
`
|
|
11035
|
-
});
|
|
11036
11095
|
files.push({
|
|
11037
11096
|
path: "src/router.ts",
|
|
11038
11097
|
content: `import { UnauthorizedError } from '@geekmidas/errors';
|
|
11039
11098
|
import { api } from '${constructsImport("api")}';${options.constructs.database ? `
|
|
11040
11099
|
import { database } from '${constructsImport("database")}';` : ""}
|
|
11041
|
-
import { authService, type Session } from './services/auth.ts';
|
|
11042
11100
|
|
|
11043
11101
|
/**
|
|
11044
11102
|
* The shared endpoint factory — no session required.
|
|
@@ -11049,15 +11107,17 @@ import { authService, type Session } from './services/auth.ts';
|
|
|
11049
11107
|
*/
|
|
11050
11108
|
export const router = api${options.constructs.database ? ".database(database)" : ""};
|
|
11051
11109
|
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11110
|
+
/**
|
|
11111
|
+
* Requires an active session — throws when there is none.
|
|
11112
|
+
*
|
|
11113
|
+
* \`auth\` is the construct named in \`.auth()\` on the API, bound to this
|
|
11114
|
+
* request: it asks the auth server whose cookie this is. Handlers built from
|
|
11115
|
+
* this router get whatever this returns as \`session\`.
|
|
11116
|
+
*/
|
|
11117
|
+
export const sessionRouter = router.session(async ({ auth }) => {
|
|
11118
|
+
const session = await auth.getSession();
|
|
11059
11119
|
|
|
11060
|
-
if (!session
|
|
11120
|
+
if (!session) {
|
|
11061
11121
|
throw new UnauthorizedError('No active session');
|
|
11062
11122
|
}
|
|
11063
11123
|
|
|
@@ -11144,46 +11204,6 @@ export const email = new Email('${mail.id}', { templates: {} });
|
|
|
11144
11204
|
* schema and reached by its role, whatever the target.
|
|
11145
11205
|
*/
|
|
11146
11206
|
export const cache = new Cache('${kv.id}');
|
|
11147
|
-
`
|
|
11148
|
-
});
|
|
11149
|
-
if (options.constructs.database && !declares) files.push({
|
|
11150
|
-
path: "src/services/database.ts",
|
|
11151
|
-
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
11152
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
11153
|
-
import pg from 'pg';
|
|
11154
|
-
|
|
11155
|
-
// Define your database schema
|
|
11156
|
-
export interface Database {
|
|
11157
|
-
users: {
|
|
11158
|
-
id: string;
|
|
11159
|
-
name: string;
|
|
11160
|
-
email: string;
|
|
11161
|
-
created_at: Date;
|
|
11162
|
-
};
|
|
11163
|
-
}
|
|
11164
|
-
|
|
11165
|
-
export const databaseService = {
|
|
11166
|
-
serviceName: 'database' as const,
|
|
11167
|
-
async register({ envParser, context }: ServiceRegisterOptions) {
|
|
11168
|
-
const logger = context.getLogger();
|
|
11169
|
-
logger.info('Connecting to database');
|
|
11170
|
-
|
|
11171
|
-
const config = envParser
|
|
11172
|
-
.create((get) => ({
|
|
11173
|
-
url: get('DATABASE_URL').string(),
|
|
11174
|
-
}))
|
|
11175
|
-
.parse();
|
|
11176
|
-
|
|
11177
|
-
const db = new Kysely<Database>({
|
|
11178
|
-
dialect: new PostgresDialect({
|
|
11179
|
-
pool: new pg.Pool({ connectionString: config.url }),
|
|
11180
|
-
}),
|
|
11181
|
-
});
|
|
11182
|
-
|
|
11183
|
-
logger.info('Database connection established');
|
|
11184
|
-
return db;
|
|
11185
|
-
},
|
|
11186
|
-
} satisfies Service<'database', Kysely<Database>>;
|
|
11187
11207
|
`
|
|
11188
11208
|
});
|
|
11189
11209
|
if (options.telescope) files.push({
|
|
@@ -11202,17 +11222,19 @@ export const telescope = new Telescope({
|
|
|
11202
11222
|
content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
|
|
11203
11223
|
import { Kysely, PostgresDialect } from 'kysely';
|
|
11204
11224
|
import pg from 'pg';
|
|
11205
|
-
import type
|
|
11225
|
+
import { type Database, database } from '${declares ? src("constructs/database.ts") : constructsImport("database")}';
|
|
11206
11226
|
import { envParser } from '${src("config/env.ts")}';
|
|
11207
11227
|
|
|
11208
11228
|
const studioConfig = envParser
|
|
11209
11229
|
.create((get) => ({
|
|
11210
|
-
databaseUrl: get('${
|
|
11230
|
+
databaseUrl: get('${db.urlKey}').string(),
|
|
11211
11231
|
}))
|
|
11212
11232
|
.parse();
|
|
11213
11233
|
|
|
11214
|
-
//
|
|
11234
|
+
// Studio's own client, built the way the construct builds its own — the same
|
|
11235
|
+
// plugins — so the browser shows what the app's queries see.
|
|
11215
11236
|
const db = new Kysely<Database>({
|
|
11237
|
+
...database.clientConfig,
|
|
11216
11238
|
dialect: new PostgresDialect({
|
|
11217
11239
|
pool: new pg.Pool({ connectionString: studioConfig.databaseUrl }),
|
|
11218
11240
|
}),
|
|
@@ -11567,7 +11589,8 @@ const workerTemplate = {
|
|
|
11567
11589
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
11568
11590
|
"@geekmidas/events": GEEKMIDAS_VERSIONS["@geekmidas/events"],
|
|
11569
11591
|
"@geekmidas/logger": GEEKMIDAS_VERSIONS["@geekmidas/logger"],
|
|
11570
|
-
"@geekmidas/schema": GEEKMIDAS_VERSIONS["@geekmidas/schema"]
|
|
11592
|
+
"@geekmidas/schema": GEEKMIDAS_VERSIONS["@geekmidas/schema"],
|
|
11593
|
+
zod: DEPENDENCY_VERSIONS.zod
|
|
11571
11594
|
},
|
|
11572
11595
|
devDependencies: {
|
|
11573
11596
|
"@biomejs/biome": DEPENDENCY_VERSIONS["@biomejs/biome"],
|
|
@@ -11626,51 +11649,34 @@ export const config = envParser
|
|
|
11626
11649
|
content: loggerContent
|
|
11627
11650
|
},
|
|
11628
11651
|
{
|
|
11629
|
-
path: "src/
|
|
11630
|
-
content: `import
|
|
11631
|
-
|
|
11632
|
-
// Define your event types here
|
|
11633
|
-
export type AppEvents =
|
|
11634
|
-
| PublishableMessage<'user.created', { userId: string; email: string }>
|
|
11635
|
-
| PublishableMessage<'user.updated', { userId: string; changes: Record<string, unknown> }>
|
|
11636
|
-
| PublishableMessage<'order.placed', { orderId: string; userId: string; total: number }>;
|
|
11637
|
-
`
|
|
11638
|
-
},
|
|
11639
|
-
{
|
|
11640
|
-
path: "src/events/publisher.ts",
|
|
11641
|
-
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
11642
|
-
import { Publisher, type EventPublisher } from '@geekmidas/events';
|
|
11643
|
-
import type { AppEvents } from './types.ts';
|
|
11644
|
-
|
|
11645
|
-
export const eventsPublisherService = {
|
|
11646
|
-
serviceName: 'events' as const,
|
|
11647
|
-
async register({ envParser, context }: ServiceRegisterOptions) {
|
|
11648
|
-
const logger = context.getLogger();
|
|
11649
|
-
logger.info('Connecting to message broker');
|
|
11650
|
-
|
|
11651
|
-
const config = envParser
|
|
11652
|
-
.create((get) => ({
|
|
11653
|
-
url: get('RABBITMQ_URL').string().default('amqp://localhost:5672'),
|
|
11654
|
-
}))
|
|
11655
|
-
.parse();
|
|
11656
|
-
|
|
11657
|
-
const publisher = await Publisher.fromConnectionString<AppEvents>(
|
|
11658
|
-
\`rabbitmq://\${config.url.replace('amqp://', '')}?exchange=events\`
|
|
11659
|
-
);
|
|
11652
|
+
path: "src/constructs/topics.ts",
|
|
11653
|
+
content: `import { t } from '@geekmidas/constructs/topic';
|
|
11654
|
+
import { z } from 'zod';
|
|
11660
11655
|
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
11656
|
+
/**
|
|
11657
|
+
* The \`users\` topic: pub/sub fan-out, any number of subscribers.
|
|
11658
|
+
*
|
|
11659
|
+
* The event map is the contract. It types \`users.publisher\` and every
|
|
11660
|
+
* subscriber that binds with \`.topic(users)\`, so an event cannot be published
|
|
11661
|
+
* in one shape and read in another.
|
|
11662
|
+
*/
|
|
11663
|
+
export const users = t.topic('users').events({
|
|
11664
|
+
'user.created': z.object({ userId: z.string(), email: z.email() }),
|
|
11665
|
+
'user.updated': z.object({
|
|
11666
|
+
userId: z.string(),
|
|
11667
|
+
changes: z.record(z.string(), z.unknown()),
|
|
11668
|
+
}),
|
|
11669
|
+
});
|
|
11665
11670
|
`
|
|
11666
11671
|
},
|
|
11667
11672
|
{
|
|
11668
11673
|
path: "src/subscribers/user-events.ts",
|
|
11669
|
-
content: `import {
|
|
11670
|
-
import {
|
|
11674
|
+
content: `import { users } from '~/constructs/topics.ts';
|
|
11675
|
+
import { worker } from '~/constructs/worker.ts';
|
|
11671
11676
|
|
|
11677
|
+
// Bound to the topic, not handed its publisher: a subscriber consumes.
|
|
11672
11678
|
export const userEventsSubscriber = worker
|
|
11673
|
-
.
|
|
11679
|
+
.topic(users)
|
|
11674
11680
|
.subscribe(['user.created', 'user.updated'])
|
|
11675
11681
|
.handle(async ({ events, logger }) => {
|
|
11676
11682
|
// A batch, not a single event. Both transports deliver in batches, so
|
|
@@ -11976,7 +11982,7 @@ function generateSourceFiles(options, template) {
|
|
|
11976
11982
|
function generateTestFiles(options, _template) {
|
|
11977
11983
|
if (!options.constructs.database) return [];
|
|
11978
11984
|
const db = databaseFor();
|
|
11979
|
-
const schema = !options.monorepo ? "../src/constructs/database.ts" :
|
|
11985
|
+
const schema = !options.monorepo ? "../src/constructs/database.ts" : `@${options.name}/constructs/database.ts`;
|
|
11980
11986
|
const runtimeUrl = db.urlKey;
|
|
11981
11987
|
const ownerUrl = db.ownerUrlKey;
|
|
11982
11988
|
return [
|
|
@@ -12005,21 +12011,13 @@ export default defineConfig({
|
|
|
12005
12011
|
{
|
|
12006
12012
|
path: "test/config.ts",
|
|
12007
12013
|
content: `import { it as itVitest } from 'vitest';
|
|
12008
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
12009
|
-
import pg from 'pg';
|
|
12010
12014
|
import { wrapVitestKyselyTransaction } from '@geekmidas/testkit/kysely';
|
|
12011
|
-
import type
|
|
12012
|
-
|
|
12013
|
-
const db = new Kysely<Database>({
|
|
12014
|
-
dialect: new PostgresDialect({
|
|
12015
|
-
pool: new pg.Pool({ connectionString: process.env.${runtimeUrl} }),
|
|
12016
|
-
}),
|
|
12017
|
-
});
|
|
12015
|
+
import { type Database, database } from '${schema}';
|
|
12018
12016
|
|
|
12019
12017
|
export const it = wrapVitestKyselyTransaction<Database>(itVitest, {
|
|
12020
|
-
//
|
|
12021
|
-
//
|
|
12022
|
-
connection:
|
|
12018
|
+
// The construct itself, so the tests connect the way the app does — with
|
|
12019
|
+
// the plugins it was given, \`CamelCasePlugin\` included.
|
|
12020
|
+
connection: database,
|
|
12023
12021
|
});
|
|
12024
12022
|
`
|
|
12025
12023
|
},
|
|
@@ -12093,7 +12091,7 @@ export const usersBuilder = KyselyFactory.createBuilder<Database, 'users'>(
|
|
|
12093
12091
|
id: faker.string.uuid(),
|
|
12094
12092
|
name: faker.person.fullName(),
|
|
12095
12093
|
email: faker.internet.email(),
|
|
12096
|
-
|
|
12094
|
+
createdAt: new Date(),
|
|
12097
12095
|
}),
|
|
12098
12096
|
);
|
|
12099
12097
|
`
|
|
@@ -14806,7 +14804,6 @@ async function secretsInitCommand(options) {
|
|
|
14806
14804
|
logger$2.log(` Location: .gkm/secrets/${stage}.json`);
|
|
14807
14805
|
if (secrets.urls.DATABASE_URL) logger$2.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14808
14806
|
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14809
|
-
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14810
14807
|
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14811
14808
|
if (Object.keys(secrets.custom).length > 0) logger$2.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
|
|
14812
14809
|
logger$2.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
|
|
@@ -14868,13 +14865,11 @@ async function secretsShowCommand(options) {
|
|
|
14868
14865
|
logger$2.log(` username: ${creds.username}`);
|
|
14869
14866
|
logger$2.log(` password: ${reveal ? creds.password : maskPassword(creds.password)}`);
|
|
14870
14867
|
if (creds.database) logger$2.log(` database: ${creds.database}`);
|
|
14871
|
-
if (creds.vhost) logger$2.log(` vhost: ${creds.vhost}`);
|
|
14872
14868
|
if (creds.bucket) logger$2.log(` bucket: ${creds.bucket}`);
|
|
14873
14869
|
}
|
|
14874
14870
|
logger$2.log("\nConnection URLs:");
|
|
14875
14871
|
if (secrets.urls.DATABASE_URL) logger$2.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14876
14872
|
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14877
|
-
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14878
14873
|
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14879
14874
|
if (Object.keys(secrets.custom).length > 0) {
|
|
14880
14875
|
logger$2.log("\nCustom Secrets:");
|
|
@@ -15044,7 +15039,7 @@ var NoStoredSecrets = class extends Error {
|
|
|
15044
15039
|
}
|
|
15045
15040
|
};
|
|
15046
15041
|
async function reconcile(secrets, workspace, stage) {
|
|
15047
|
-
const { derivedContainers } = await import("./workspace-
|
|
15042
|
+
const { derivedContainers } = await import("./workspace-DnHW8ooI.mjs").then((n) => n.a);
|
|
15048
15043
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15049
15044
|
return result ? {
|
|
15050
15045
|
secrets: result.secrets,
|
|
@@ -15204,8 +15199,7 @@ function credentialedServices(containers) {
|
|
|
15204
15199
|
redis: "redis",
|
|
15205
15200
|
minio: "minio",
|
|
15206
15201
|
mailpit: "mailpit",
|
|
15207
|
-
localstack: "localstack"
|
|
15208
|
-
rabbitmq: "rabbitmq"
|
|
15202
|
+
localstack: "localstack"
|
|
15209
15203
|
};
|
|
15210
15204
|
return [...new Set(containers.map((container) => byContainer[container]).filter((name) => Boolean(name)))];
|
|
15211
15205
|
}
|
|
@@ -15225,7 +15219,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15225
15219
|
[name]: creds
|
|
15226
15220
|
}
|
|
15227
15221
|
};
|
|
15228
|
-
result.urls = generateConnectionUrls(result.services
|
|
15222
|
+
result.urls = generateConnectionUrls(result.services);
|
|
15229
15223
|
logger$1.log(` 🔄 Adding missing service credentials: ${name}`);
|
|
15230
15224
|
changed = true;
|
|
15231
15225
|
}
|
|
@@ -15243,7 +15237,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15243
15237
|
}
|
|
15244
15238
|
}
|
|
15245
15239
|
};
|
|
15246
|
-
result.urls = generateConnectionUrls(result.services
|
|
15240
|
+
result.urls = generateConnectionUrls(result.services);
|
|
15247
15241
|
logger$1.log(" 🔄 Adding missing service credentials: pgboss");
|
|
15248
15242
|
changed = true;
|
|
15249
15243
|
}
|
|
@@ -16167,7 +16161,7 @@ program.command("secrets:show").description("Show secrets for a stage").required
|
|
|
16167
16161
|
process.exit(1);
|
|
16168
16162
|
}
|
|
16169
16163
|
});
|
|
16170
|
-
program.command("secrets:rotate").description("Rotate service passwords").requiredOption("--stage <stage>", "Stage name").option("--service <service>", "Specific service to rotate (postgres, redis,
|
|
16164
|
+
program.command("secrets:rotate").description("Rotate service passwords").requiredOption("--stage <stage>", "Stage name").option("--service <service>", "Specific service to rotate (postgres, redis, minio, …)").action(async (options) => {
|
|
16171
16165
|
try {
|
|
16172
16166
|
const globalOptions = program.opts();
|
|
16173
16167
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -16222,8 +16216,8 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16222
16216
|
const globalOptions = program.opts();
|
|
16223
16217
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16224
16218
|
const { loadWorkspaceConfig } = await import("./config.mjs");
|
|
16225
|
-
const { reconcileMissingSecrets } = await import("./reconcile-
|
|
16226
|
-
const { readStageSecrets, writeStageSecrets } = await import("./storage-
|
|
16219
|
+
const { reconcileMissingSecrets } = await import("./reconcile-yNq6Shau.mjs").then((n) => n.n);
|
|
16220
|
+
const { readStageSecrets, writeStageSecrets } = await import("./storage-BjTLjDDp.mjs").then((n) => n.a);
|
|
16227
16221
|
const { workspace } = await loadWorkspaceConfig();
|
|
16228
16222
|
const stage = options.stage ?? workspace.stages.local;
|
|
16229
16223
|
const secrets = await readStageSecrets(stage, workspace.root);
|
|
@@ -16231,7 +16225,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16231
16225
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16232
16226
|
process.exit(1);
|
|
16233
16227
|
}
|
|
16234
|
-
const { derivedContainers } = await import("./workspace-
|
|
16228
|
+
const { derivedContainers } = await import("./workspace-DnHW8ooI.mjs").then((n) => n.a);
|
|
16235
16229
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16236
16230
|
if (!result) {
|
|
16237
16231
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16290,7 +16284,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16290
16284
|
try {
|
|
16291
16285
|
const globalOptions = program.opts();
|
|
16292
16286
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16293
|
-
const { deployGithubCommand } = await import("./github-
|
|
16287
|
+
const { deployGithubCommand } = await import("./github-DPpNfzu4.mjs");
|
|
16294
16288
|
await deployGithubCommand(options);
|
|
16295
16289
|
} catch (error) {
|
|
16296
16290
|
console.error(formatError(error));
|