@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.cjs
CHANGED
|
@@ -4,11 +4,11 @@ const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
|
4
4
|
const require_discover = require("./discover-BTh87DOO.cjs");
|
|
5
5
|
const require_config = require("./config-BTFLHfRb.cjs");
|
|
6
6
|
const require_workspace = require("./workspace-Bav3TVrP.cjs");
|
|
7
|
-
const require_storage = require("./storage-
|
|
8
|
-
const require_workspace$1 = require("./workspace-
|
|
7
|
+
const require_storage = require("./storage-C2JC31V9.cjs");
|
|
8
|
+
const require_workspace$1 = require("./workspace-B3ApLW6p.cjs");
|
|
9
9
|
const require_openapi = require("./openapi-K4H5tHzA.cjs");
|
|
10
10
|
const require_emit = require("./emit-BdoPFjZf.cjs");
|
|
11
|
-
const require_reconcile = require("./reconcile-
|
|
11
|
+
const require_reconcile = require("./reconcile-BxZTV2vl.cjs");
|
|
12
12
|
let commander = require("commander");
|
|
13
13
|
let node_process = require("node:process");
|
|
14
14
|
let node_readline_promises = require("node:readline/promises");
|
|
@@ -42,7 +42,7 @@ let node_dns_promises = require("node:dns/promises");
|
|
|
42
42
|
let _geekmidas_envkit = require("@geekmidas/envkit");
|
|
43
43
|
let _geekmidas_services = require("@geekmidas/services");
|
|
44
44
|
//#region package.json
|
|
45
|
-
var version = "10.0.0-alpha.
|
|
45
|
+
var version = "10.0.0-alpha.29";
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region src/auth/credentials.ts
|
|
48
48
|
/**
|
|
@@ -2906,7 +2906,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2906
2906
|
let masterKey;
|
|
2907
2907
|
if (context.production?.bundle && !skipBundle) {
|
|
2908
2908
|
logger$8.log(`\n📦 Bundling production server...`);
|
|
2909
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
2909
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-CYF5mWi9.cjs"));
|
|
2910
2910
|
const allConstructs = [
|
|
2911
2911
|
...endpoints.map((e) => e.construct),
|
|
2912
2912
|
...functions.map((f) => f.construct),
|
|
@@ -8132,15 +8132,20 @@ function databaseFiles() {
|
|
|
8132
8132
|
return [{
|
|
8133
8133
|
path: "src/constructs/database.ts",
|
|
8134
8134
|
content: `import { KyselyDatabase } from '@geekmidas/constructs/database/kysely';
|
|
8135
|
-
import type
|
|
8135
|
+
import { CamelCasePlugin, type Generated } from 'kysely';
|
|
8136
8136
|
|
|
8137
|
-
/**
|
|
8137
|
+
/**
|
|
8138
|
+
* Your database schema. Add tables here.
|
|
8139
|
+
*
|
|
8140
|
+
* camelCase, because of the plugin below: the columns are snake_case in
|
|
8141
|
+
* Postgres (\`created_at\`), and Kysely maps between the two.
|
|
8142
|
+
*/
|
|
8138
8143
|
export interface Database {
|
|
8139
8144
|
users: {
|
|
8140
8145
|
id: Generated<string>;
|
|
8141
8146
|
name: string;
|
|
8142
8147
|
email: string;
|
|
8143
|
-
|
|
8148
|
+
createdAt: Generated<Date>;
|
|
8144
8149
|
};
|
|
8145
8150
|
}
|
|
8146
8151
|
|
|
@@ -8155,7 +8160,11 @@ export interface Database {
|
|
|
8155
8160
|
* inference, so passing only \`Database\` would leave the name at \`string\`
|
|
8156
8161
|
* and widen the service key away from \`${db.service}\`.
|
|
8157
8162
|
*/
|
|
8158
|
-
export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}'
|
|
8163
|
+
export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
|
|
8164
|
+
// Yours to keep, change or remove. Every connection this construct opens —
|
|
8165
|
+
// every endpoint, the tests — uses it.
|
|
8166
|
+
plugins: [new CamelCasePlugin()],
|
|
8167
|
+
});
|
|
8159
8168
|
`
|
|
8160
8169
|
}, usersMigration()];
|
|
8161
8170
|
}
|
|
@@ -8330,7 +8339,7 @@ environment and fail in a way that looks like a config bug. Go through
|
|
|
8330
8339
|
\`gkm exec --\`, which is what injects them.`);
|
|
8331
8340
|
if (!isWorker) sections.push(endpointSection(options));
|
|
8332
8341
|
if (isWorker || isServerless) sections.push(backgroundSection(template));
|
|
8333
|
-
if (database) sections.push(databaseSection());
|
|
8342
|
+
if (database) sections.push(databaseSection(), queriesSection());
|
|
8334
8343
|
sections.push(`## Environment
|
|
8335
8344
|
|
|
8336
8345
|
Environment is parsed once, through \`envkit\`, and never read from
|
|
@@ -8517,6 +8526,88 @@ export const monthly = router
|
|
|
8517
8526
|
Queries go through Kysely. Never open your own connection pool — the one the
|
|
8518
8527
|
construct provides is the one that gets credentials, pooling and shutdown.`;
|
|
8519
8528
|
}
|
|
8529
|
+
function queriesSection() {
|
|
8530
|
+
return `## Writing queries and relations
|
|
8531
|
+
|
|
8532
|
+
**Names: snake_case in Postgres, camelCase in TypeScript.** Kysely's
|
|
8533
|
+
\`CamelCasePlugin\` does the mapping, and it is set where you can see it — on
|
|
8534
|
+
the database construct in \`constructs/database.ts\`:
|
|
8535
|
+
|
|
8536
|
+
\`\`\`typescript
|
|
8537
|
+
export const database = new KyselyDatabase<Database, 'Database'>('Database', {
|
|
8538
|
+
plugins: [new CamelCasePlugin()],
|
|
8539
|
+
});
|
|
8540
|
+
\`\`\`
|
|
8541
|
+
|
|
8542
|
+
It is the project's plugin, not the toolbox's: keep it, change it, or remove it,
|
|
8543
|
+
but do it there. It applies to every connection the construct opens: every
|
|
8544
|
+
endpoint, and the tests, which connect through the construct.
|
|
8545
|
+
|
|
8546
|
+
- The \`Database\` interface is camelCase: \`userId\`, \`createdAt\`.
|
|
8547
|
+
- Migrations are the SQL, so they spell columns in snake_case.
|
|
8548
|
+
- A raw \`sql\` fragment is not rewritten. Refer to columns with
|
|
8549
|
+
\`sql.ref('users.createdAt')\` or \`eb.ref(…)\`, never inside the string.
|
|
8550
|
+
- A schema tenant takes its own options. Better Auth's tenant gets no plugin —
|
|
8551
|
+
its tables are its own.
|
|
8552
|
+
|
|
8553
|
+
**Types.** \`Generated<T>\` for every column the database fills in (ids,
|
|
8554
|
+
defaults, timestamps). Derive row types with \`Selectable<Database['users']>\`,
|
|
8555
|
+
\`Insertable<…>\` and \`Updateable<…>\` rather than writing them again.
|
|
8556
|
+
|
|
8557
|
+
**Relations are foreign keys.** Every \`*_id\` column references its parent,
|
|
8558
|
+
with its delete rule written out:
|
|
8559
|
+
|
|
8560
|
+
\`\`\`typescript
|
|
8561
|
+
.addColumn('user_id', 'uuid', (col) =>
|
|
8562
|
+
col.notNull().references('users.id').onDelete('cascade'),
|
|
8563
|
+
)
|
|
8564
|
+
\`\`\`
|
|
8565
|
+
|
|
8566
|
+
- \`restrict\` by default.
|
|
8567
|
+
- \`cascade\` only for rows that belong to the parent — a user's notifications.
|
|
8568
|
+
- \`set null\` for an optional link, on a nullable column.
|
|
8569
|
+
- Index every foreign key column. Postgres does not do it for you.
|
|
8570
|
+
|
|
8571
|
+
**Reading related rows: one query, nested.** \`jsonArrayFrom\` and
|
|
8572
|
+
\`jsonObjectFrom\` from \`kysely/helpers/postgres\` return children already in
|
|
8573
|
+
the shape of the response:
|
|
8574
|
+
|
|
8575
|
+
\`\`\`typescript
|
|
8576
|
+
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
|
8577
|
+
|
|
8578
|
+
const user = await db
|
|
8579
|
+
.selectFrom('users')
|
|
8580
|
+
.select((eb) => [
|
|
8581
|
+
'users.id',
|
|
8582
|
+
'users.name',
|
|
8583
|
+
jsonArrayFrom(
|
|
8584
|
+
eb
|
|
8585
|
+
.selectFrom('notifications')
|
|
8586
|
+
.select(['notifications.id', 'notifications.body', 'notifications.createdAt'])
|
|
8587
|
+
.whereRef('notifications.userId', '=', 'users.id')
|
|
8588
|
+
.orderBy('notifications.createdAt', 'desc'),
|
|
8589
|
+
).as('notifications'),
|
|
8590
|
+
])
|
|
8591
|
+
.where('users.id', '=', id)
|
|
8592
|
+
.executeTakeFirstOrThrow();
|
|
8593
|
+
\`\`\`
|
|
8594
|
+
|
|
8595
|
+
- Never query once per row in a loop.
|
|
8596
|
+
- Use joins to filter, or for a flat result — not to build nested ones by hand.
|
|
8597
|
+
- Qualify columns (\`users.id\`) once a query touches more than one table.
|
|
8598
|
+
- Select the columns the response needs. \`selectAll()\` only returns a whole
|
|
8599
|
+
row.
|
|
8600
|
+
- Nested rows come back as JSON: a timestamp inside \`jsonArrayFrom\` is a
|
|
8601
|
+
string, not a \`Date\`. Say so in the output schema (\`z.coerce.date()\`).
|
|
8602
|
+
|
|
8603
|
+
**Writes that touch several tables run in one transaction.** Use
|
|
8604
|
+
\`withTransaction(db, async (trx) => …)\` from \`@geekmidas/db/kysely\`: it joins
|
|
8605
|
+
the transaction \`db\` already is — an endpoint with an auditor, a test —
|
|
8606
|
+
instead of opening a second one.
|
|
8607
|
+
|
|
8608
|
+
**Lists paginate by cursor**, with \`paginatedSearch\` from
|
|
8609
|
+
\`@geekmidas/db/kysely/pagination\`, not by offset.`;
|
|
8610
|
+
}
|
|
8520
8611
|
function conventionsSection() {
|
|
8521
8612
|
return `## Conventions
|
|
8522
8613
|
|
|
@@ -8691,26 +8782,26 @@ function loadPackageJson() {
|
|
|
8691
8782
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8692
8783
|
*/
|
|
8693
8784
|
const GEEKMIDAS_VERSIONS = {
|
|
8694
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8695
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8696
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8697
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8698
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8699
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8700
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8701
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8702
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8703
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8704
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8705
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8706
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8707
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8708
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8709
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8710
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8711
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8712
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8713
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8785
|
+
"@geekmidas/audit": "~10.0.0-alpha.29",
|
|
8786
|
+
"@geekmidas/auth": "~10.0.0-alpha.29",
|
|
8787
|
+
"@geekmidas/cache": "~10.0.0-alpha.29",
|
|
8788
|
+
"@geekmidas/client": "~10.0.0-alpha.29",
|
|
8789
|
+
"@geekmidas/cloud": "~10.0.0-alpha.29",
|
|
8790
|
+
"@geekmidas/constructs": "~10.0.0-alpha.29",
|
|
8791
|
+
"@geekmidas/db": "~10.0.0-alpha.29",
|
|
8792
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.29",
|
|
8793
|
+
"@geekmidas/envkit": "~10.0.0-alpha.29",
|
|
8794
|
+
"@geekmidas/errors": "~10.0.0-alpha.29",
|
|
8795
|
+
"@geekmidas/events": "~10.0.0-alpha.29",
|
|
8796
|
+
"@geekmidas/logger": "~10.0.0-alpha.29",
|
|
8797
|
+
"@geekmidas/manifest": "~10.0.0-alpha.29",
|
|
8798
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.29",
|
|
8799
|
+
"@geekmidas/schema": "~10.0.0-alpha.29",
|
|
8800
|
+
"@geekmidas/services": "~10.0.0-alpha.29",
|
|
8801
|
+
"@geekmidas/storage": "~10.0.0-alpha.29",
|
|
8802
|
+
"@geekmidas/studio": "~10.0.0-alpha.29",
|
|
8803
|
+
"@geekmidas/telescope": "~10.0.0-alpha.29",
|
|
8804
|
+
"@geekmidas/testkit": "~10.0.0-alpha.29",
|
|
8714
8805
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8715
8806
|
};
|
|
8716
8807
|
//#endregion
|
|
@@ -10648,15 +10739,20 @@ export const logger = createLogger();
|
|
|
10648
10739
|
files.push({
|
|
10649
10740
|
path: "constructs/database.ts",
|
|
10650
10741
|
content: `import { KyselyDatabase } from '@geekmidas/constructs/database/kysely';
|
|
10651
|
-
import type
|
|
10742
|
+
import { CamelCasePlugin, type Generated } from 'kysely';
|
|
10652
10743
|
|
|
10653
|
-
/**
|
|
10744
|
+
/**
|
|
10745
|
+
* Your database schema. Add tables here.
|
|
10746
|
+
*
|
|
10747
|
+
* camelCase, because of the plugin below: the columns are snake_case in
|
|
10748
|
+
* Postgres (\`created_at\`), and Kysely maps between the two.
|
|
10749
|
+
*/
|
|
10654
10750
|
export interface Database {
|
|
10655
10751
|
users: {
|
|
10656
10752
|
id: Generated<string>;
|
|
10657
10753
|
name: string;
|
|
10658
10754
|
email: string;
|
|
10659
|
-
|
|
10755
|
+
createdAt: Generated<Date>;
|
|
10660
10756
|
};
|
|
10661
10757
|
}
|
|
10662
10758
|
|
|
@@ -10667,14 +10763,19 @@ export interface Database {
|
|
|
10667
10763
|
* \`${db.urlKey}\` all derive from this line — which is why nothing lists
|
|
10668
10764
|
* \`postgres\` anywhere.
|
|
10669
10765
|
*/
|
|
10670
|
-
export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}'
|
|
10766
|
+
export const database = new KyselyDatabase<Database, '${db.id}'>('${db.id}', {
|
|
10767
|
+
// Yours to keep, change or remove. Every connection this construct opens —
|
|
10768
|
+
// every endpoint, the tests — uses it.
|
|
10769
|
+
plugins: [new CamelCasePlugin()],
|
|
10770
|
+
});
|
|
10671
10771
|
|
|
10672
10772
|
/**
|
|
10673
10773
|
* The auth server's own schema in that same Postgres, with its own role.
|
|
10674
10774
|
*
|
|
10675
10775
|
* A schema tenant rather than a second database: one container, and a role
|
|
10676
10776
|
* whose \`search_path\` is pinned, so Better Auth's tables cannot collide with
|
|
10677
|
-
* the application's.
|
|
10777
|
+
* the application's. It takes its own options: no plugin, because Better
|
|
10778
|
+
* Auth names its own columns.
|
|
10678
10779
|
*/
|
|
10679
10780
|
export const authDb = database.schema<Record<string, never>, 'AuthDb'>('AuthDb');
|
|
10680
10781
|
`
|
|
@@ -11002,54 +11103,11 @@ export const getUserEndpoint = router
|
|
|
11002
11103
|
}
|
|
11003
11104
|
];
|
|
11004
11105
|
if (options.monorepo) {
|
|
11005
|
-
files.push({
|
|
11006
|
-
path: "src/services/auth.ts",
|
|
11007
|
-
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
11008
|
-
|
|
11009
|
-
export interface Session {
|
|
11010
|
-
user: {
|
|
11011
|
-
id: string;
|
|
11012
|
-
email: string;
|
|
11013
|
-
name: string;
|
|
11014
|
-
};
|
|
11015
|
-
}
|
|
11016
|
-
|
|
11017
|
-
export interface AuthClient {
|
|
11018
|
-
getSession: (cookie: string) => Promise<Session | null>;
|
|
11019
|
-
}
|
|
11020
|
-
|
|
11021
|
-
export const authService = {
|
|
11022
|
-
serviceName: 'auth' as const,
|
|
11023
|
-
async register({ envParser, context }: ServiceRegisterOptions) {
|
|
11024
|
-
const logger = context.getLogger();
|
|
11025
|
-
|
|
11026
|
-
const config = envParser
|
|
11027
|
-
.create((get) => ({
|
|
11028
|
-
url: get('AUTH_URL').string(),
|
|
11029
|
-
}))
|
|
11030
|
-
.parse();
|
|
11031
|
-
|
|
11032
|
-
logger.info({ authUrl: config.url }, 'Auth service configured');
|
|
11033
|
-
|
|
11034
|
-
return {
|
|
11035
|
-
getSession: async (cookie: string): Promise<Session | null> => {
|
|
11036
|
-
const res = await fetch(\`\${config.url}/api/auth/get-session\`, {
|
|
11037
|
-
headers: { cookie },
|
|
11038
|
-
});
|
|
11039
|
-
if (!res.ok) return null;
|
|
11040
|
-
return (await res.json()) as Session | null;
|
|
11041
|
-
},
|
|
11042
|
-
};
|
|
11043
|
-
},
|
|
11044
|
-
} satisfies Service<'auth', AuthClient>;
|
|
11045
|
-
`
|
|
11046
|
-
});
|
|
11047
11106
|
files.push({
|
|
11048
11107
|
path: "src/router.ts",
|
|
11049
11108
|
content: `import { UnauthorizedError } from '@geekmidas/errors';
|
|
11050
11109
|
import { api } from '${constructsImport("api")}';${options.constructs.database ? `
|
|
11051
11110
|
import { database } from '${constructsImport("database")}';` : ""}
|
|
11052
|
-
import { authService, type Session } from './services/auth.ts';
|
|
11053
11111
|
|
|
11054
11112
|
/**
|
|
11055
11113
|
* The shared endpoint factory — no session required.
|
|
@@ -11060,15 +11118,17 @@ import { authService, type Session } from './services/auth.ts';
|
|
|
11060
11118
|
*/
|
|
11061
11119
|
export const router = api${options.constructs.database ? ".database(database)" : ""};
|
|
11062
11120
|
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11121
|
+
/**
|
|
11122
|
+
* Requires an active session — throws when there is none.
|
|
11123
|
+
*
|
|
11124
|
+
* \`auth\` is the construct named in \`.auth()\` on the API, bound to this
|
|
11125
|
+
* request: it asks the auth server whose cookie this is. Handlers built from
|
|
11126
|
+
* this router get whatever this returns as \`session\`.
|
|
11127
|
+
*/
|
|
11128
|
+
export const sessionRouter = router.session(async ({ auth }) => {
|
|
11129
|
+
const session = await auth.getSession();
|
|
11070
11130
|
|
|
11071
|
-
if (!session
|
|
11131
|
+
if (!session) {
|
|
11072
11132
|
throw new UnauthorizedError('No active session');
|
|
11073
11133
|
}
|
|
11074
11134
|
|
|
@@ -11155,46 +11215,6 @@ export const email = new Email('${mail.id}', { templates: {} });
|
|
|
11155
11215
|
* schema and reached by its role, whatever the target.
|
|
11156
11216
|
*/
|
|
11157
11217
|
export const cache = new Cache('${kv.id}');
|
|
11158
|
-
`
|
|
11159
|
-
});
|
|
11160
|
-
if (options.constructs.database && !declares) files.push({
|
|
11161
|
-
path: "src/services/database.ts",
|
|
11162
|
-
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
11163
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
11164
|
-
import pg from 'pg';
|
|
11165
|
-
|
|
11166
|
-
// Define your database schema
|
|
11167
|
-
export interface Database {
|
|
11168
|
-
users: {
|
|
11169
|
-
id: string;
|
|
11170
|
-
name: string;
|
|
11171
|
-
email: string;
|
|
11172
|
-
created_at: Date;
|
|
11173
|
-
};
|
|
11174
|
-
}
|
|
11175
|
-
|
|
11176
|
-
export const databaseService = {
|
|
11177
|
-
serviceName: 'database' as const,
|
|
11178
|
-
async register({ envParser, context }: ServiceRegisterOptions) {
|
|
11179
|
-
const logger = context.getLogger();
|
|
11180
|
-
logger.info('Connecting to database');
|
|
11181
|
-
|
|
11182
|
-
const config = envParser
|
|
11183
|
-
.create((get) => ({
|
|
11184
|
-
url: get('DATABASE_URL').string(),
|
|
11185
|
-
}))
|
|
11186
|
-
.parse();
|
|
11187
|
-
|
|
11188
|
-
const db = new Kysely<Database>({
|
|
11189
|
-
dialect: new PostgresDialect({
|
|
11190
|
-
pool: new pg.Pool({ connectionString: config.url }),
|
|
11191
|
-
}),
|
|
11192
|
-
});
|
|
11193
|
-
|
|
11194
|
-
logger.info('Database connection established');
|
|
11195
|
-
return db;
|
|
11196
|
-
},
|
|
11197
|
-
} satisfies Service<'database', Kysely<Database>>;
|
|
11198
11218
|
`
|
|
11199
11219
|
});
|
|
11200
11220
|
if (options.telescope) files.push({
|
|
@@ -11213,17 +11233,19 @@ export const telescope = new Telescope({
|
|
|
11213
11233
|
content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
|
|
11214
11234
|
import { Kysely, PostgresDialect } from 'kysely';
|
|
11215
11235
|
import pg from 'pg';
|
|
11216
|
-
import type
|
|
11236
|
+
import { type Database, database } from '${declares ? src("constructs/database.ts") : constructsImport("database")}';
|
|
11217
11237
|
import { envParser } from '${src("config/env.ts")}';
|
|
11218
11238
|
|
|
11219
11239
|
const studioConfig = envParser
|
|
11220
11240
|
.create((get) => ({
|
|
11221
|
-
databaseUrl: get('${
|
|
11241
|
+
databaseUrl: get('${db.urlKey}').string(),
|
|
11222
11242
|
}))
|
|
11223
11243
|
.parse();
|
|
11224
11244
|
|
|
11225
|
-
//
|
|
11245
|
+
// Studio's own client, built the way the construct builds its own — the same
|
|
11246
|
+
// plugins — so the browser shows what the app's queries see.
|
|
11226
11247
|
const db = new Kysely<Database>({
|
|
11248
|
+
...database.clientConfig,
|
|
11227
11249
|
dialect: new PostgresDialect({
|
|
11228
11250
|
pool: new pg.Pool({ connectionString: studioConfig.databaseUrl }),
|
|
11229
11251
|
}),
|
|
@@ -11578,7 +11600,8 @@ const workerTemplate = {
|
|
|
11578
11600
|
"@geekmidas/envkit": GEEKMIDAS_VERSIONS["@geekmidas/envkit"],
|
|
11579
11601
|
"@geekmidas/events": GEEKMIDAS_VERSIONS["@geekmidas/events"],
|
|
11580
11602
|
"@geekmidas/logger": GEEKMIDAS_VERSIONS["@geekmidas/logger"],
|
|
11581
|
-
"@geekmidas/schema": GEEKMIDAS_VERSIONS["@geekmidas/schema"]
|
|
11603
|
+
"@geekmidas/schema": GEEKMIDAS_VERSIONS["@geekmidas/schema"],
|
|
11604
|
+
zod: DEPENDENCY_VERSIONS.zod
|
|
11582
11605
|
},
|
|
11583
11606
|
devDependencies: {
|
|
11584
11607
|
"@biomejs/biome": DEPENDENCY_VERSIONS["@biomejs/biome"],
|
|
@@ -11637,51 +11660,34 @@ export const config = envParser
|
|
|
11637
11660
|
content: loggerContent
|
|
11638
11661
|
},
|
|
11639
11662
|
{
|
|
11640
|
-
path: "src/
|
|
11641
|
-
content: `import
|
|
11642
|
-
|
|
11643
|
-
// Define your event types here
|
|
11644
|
-
export type AppEvents =
|
|
11645
|
-
| PublishableMessage<'user.created', { userId: string; email: string }>
|
|
11646
|
-
| PublishableMessage<'user.updated', { userId: string; changes: Record<string, unknown> }>
|
|
11647
|
-
| PublishableMessage<'order.placed', { orderId: string; userId: string; total: number }>;
|
|
11648
|
-
`
|
|
11649
|
-
},
|
|
11650
|
-
{
|
|
11651
|
-
path: "src/events/publisher.ts",
|
|
11652
|
-
content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
|
|
11653
|
-
import { Publisher, type EventPublisher } from '@geekmidas/events';
|
|
11654
|
-
import type { AppEvents } from './types.ts';
|
|
11655
|
-
|
|
11656
|
-
export const eventsPublisherService = {
|
|
11657
|
-
serviceName: 'events' as const,
|
|
11658
|
-
async register({ envParser, context }: ServiceRegisterOptions) {
|
|
11659
|
-
const logger = context.getLogger();
|
|
11660
|
-
logger.info('Connecting to message broker');
|
|
11661
|
-
|
|
11662
|
-
const config = envParser
|
|
11663
|
-
.create((get) => ({
|
|
11664
|
-
url: get('RABBITMQ_URL').string().default('amqp://localhost:5672'),
|
|
11665
|
-
}))
|
|
11666
|
-
.parse();
|
|
11667
|
-
|
|
11668
|
-
const publisher = await Publisher.fromConnectionString<AppEvents>(
|
|
11669
|
-
\`rabbitmq://\${config.url.replace('amqp://', '')}?exchange=events\`
|
|
11670
|
-
);
|
|
11663
|
+
path: "src/constructs/topics.ts",
|
|
11664
|
+
content: `import { t } from '@geekmidas/constructs/topic';
|
|
11665
|
+
import { z } from 'zod';
|
|
11671
11666
|
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
|
|
11667
|
+
/**
|
|
11668
|
+
* The \`users\` topic: pub/sub fan-out, any number of subscribers.
|
|
11669
|
+
*
|
|
11670
|
+
* The event map is the contract. It types \`users.publisher\` and every
|
|
11671
|
+
* subscriber that binds with \`.topic(users)\`, so an event cannot be published
|
|
11672
|
+
* in one shape and read in another.
|
|
11673
|
+
*/
|
|
11674
|
+
export const users = t.topic('users').events({
|
|
11675
|
+
'user.created': z.object({ userId: z.string(), email: z.email() }),
|
|
11676
|
+
'user.updated': z.object({
|
|
11677
|
+
userId: z.string(),
|
|
11678
|
+
changes: z.record(z.string(), z.unknown()),
|
|
11679
|
+
}),
|
|
11680
|
+
});
|
|
11676
11681
|
`
|
|
11677
11682
|
},
|
|
11678
11683
|
{
|
|
11679
11684
|
path: "src/subscribers/user-events.ts",
|
|
11680
|
-
content: `import {
|
|
11681
|
-
import {
|
|
11685
|
+
content: `import { users } from '~/constructs/topics.ts';
|
|
11686
|
+
import { worker } from '~/constructs/worker.ts';
|
|
11682
11687
|
|
|
11688
|
+
// Bound to the topic, not handed its publisher: a subscriber consumes.
|
|
11683
11689
|
export const userEventsSubscriber = worker
|
|
11684
|
-
.
|
|
11690
|
+
.topic(users)
|
|
11685
11691
|
.subscribe(['user.created', 'user.updated'])
|
|
11686
11692
|
.handle(async ({ events, logger }) => {
|
|
11687
11693
|
// A batch, not a single event. Both transports deliver in batches, so
|
|
@@ -11987,7 +11993,7 @@ function generateSourceFiles(options, template) {
|
|
|
11987
11993
|
function generateTestFiles(options, _template) {
|
|
11988
11994
|
if (!options.constructs.database) return [];
|
|
11989
11995
|
const db = databaseFor();
|
|
11990
|
-
const schema = !options.monorepo ? "../src/constructs/database.ts" :
|
|
11996
|
+
const schema = !options.monorepo ? "../src/constructs/database.ts" : `@${options.name}/constructs/database.ts`;
|
|
11991
11997
|
const runtimeUrl = db.urlKey;
|
|
11992
11998
|
const ownerUrl = db.ownerUrlKey;
|
|
11993
11999
|
return [
|
|
@@ -12016,21 +12022,13 @@ export default defineConfig({
|
|
|
12016
12022
|
{
|
|
12017
12023
|
path: "test/config.ts",
|
|
12018
12024
|
content: `import { it as itVitest } from 'vitest';
|
|
12019
|
-
import { Kysely, PostgresDialect } from 'kysely';
|
|
12020
|
-
import pg from 'pg';
|
|
12021
12025
|
import { wrapVitestKyselyTransaction } from '@geekmidas/testkit/kysely';
|
|
12022
|
-
import type
|
|
12023
|
-
|
|
12024
|
-
const db = new Kysely<Database>({
|
|
12025
|
-
dialect: new PostgresDialect({
|
|
12026
|
-
pool: new pg.Pool({ connectionString: process.env.${runtimeUrl} }),
|
|
12027
|
-
}),
|
|
12028
|
-
});
|
|
12026
|
+
import { type Database, database } from '${schema}';
|
|
12029
12027
|
|
|
12030
12028
|
export const it = wrapVitestKyselyTransaction<Database>(itVitest, {
|
|
12031
|
-
//
|
|
12032
|
-
//
|
|
12033
|
-
connection:
|
|
12029
|
+
// The construct itself, so the tests connect the way the app does — with
|
|
12030
|
+
// the plugins it was given, \`CamelCasePlugin\` included.
|
|
12031
|
+
connection: database,
|
|
12034
12032
|
});
|
|
12035
12033
|
`
|
|
12036
12034
|
},
|
|
@@ -12104,7 +12102,7 @@ export const usersBuilder = KyselyFactory.createBuilder<Database, 'users'>(
|
|
|
12104
12102
|
id: faker.string.uuid(),
|
|
12105
12103
|
name: faker.person.fullName(),
|
|
12106
12104
|
email: faker.internet.email(),
|
|
12107
|
-
|
|
12105
|
+
createdAt: new Date(),
|
|
12108
12106
|
}),
|
|
12109
12107
|
);
|
|
12110
12108
|
`
|
|
@@ -14817,7 +14815,6 @@ async function secretsInitCommand(options) {
|
|
|
14817
14815
|
logger$2.log(` Location: .gkm/secrets/${stage}.json`);
|
|
14818
14816
|
if (secrets.urls.DATABASE_URL) logger$2.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14819
14817
|
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14820
|
-
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14821
14818
|
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14822
14819
|
if (Object.keys(secrets.custom).length > 0) logger$2.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
|
|
14823
14820
|
logger$2.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
|
|
@@ -14879,13 +14876,11 @@ async function secretsShowCommand(options) {
|
|
|
14879
14876
|
logger$2.log(` username: ${creds.username}`);
|
|
14880
14877
|
logger$2.log(` password: ${reveal ? creds.password : require_storage.maskPassword(creds.password)}`);
|
|
14881
14878
|
if (creds.database) logger$2.log(` database: ${creds.database}`);
|
|
14882
|
-
if (creds.vhost) logger$2.log(` vhost: ${creds.vhost}`);
|
|
14883
14879
|
if (creds.bucket) logger$2.log(` bucket: ${creds.bucket}`);
|
|
14884
14880
|
}
|
|
14885
14881
|
logger$2.log("\nConnection URLs:");
|
|
14886
14882
|
if (secrets.urls.DATABASE_URL) logger$2.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14887
14883
|
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14888
|
-
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14889
14884
|
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14890
14885
|
if (Object.keys(secrets.custom).length > 0) {
|
|
14891
14886
|
logger$2.log("\nCustom Secrets:");
|
|
@@ -15055,7 +15050,7 @@ var NoStoredSecrets = class extends Error {
|
|
|
15055
15050
|
}
|
|
15056
15051
|
};
|
|
15057
15052
|
async function reconcile(secrets, workspace, stage) {
|
|
15058
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-
|
|
15053
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-B3ApLW6p.cjs")).then((n) => n.workspace_exports);
|
|
15059
15054
|
const result = require_reconcile.reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15060
15055
|
return result ? {
|
|
15061
15056
|
secrets: result.secrets,
|
|
@@ -15215,8 +15210,7 @@ function credentialedServices(containers) {
|
|
|
15215
15210
|
redis: "redis",
|
|
15216
15211
|
minio: "minio",
|
|
15217
15212
|
mailpit: "mailpit",
|
|
15218
|
-
localstack: "localstack"
|
|
15219
|
-
rabbitmq: "rabbitmq"
|
|
15213
|
+
localstack: "localstack"
|
|
15220
15214
|
};
|
|
15221
15215
|
return [...new Set(containers.map((container) => byContainer[container]).filter((name) => Boolean(name)))];
|
|
15222
15216
|
}
|
|
@@ -15236,7 +15230,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15236
15230
|
[name]: creds
|
|
15237
15231
|
}
|
|
15238
15232
|
};
|
|
15239
|
-
result.urls = require_reconcile.generateConnectionUrls(result.services
|
|
15233
|
+
result.urls = require_reconcile.generateConnectionUrls(result.services);
|
|
15240
15234
|
logger$1.log(` 🔄 Adding missing service credentials: ${name}`);
|
|
15241
15235
|
changed = true;
|
|
15242
15236
|
}
|
|
@@ -15254,7 +15248,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15254
15248
|
}
|
|
15255
15249
|
}
|
|
15256
15250
|
};
|
|
15257
|
-
result.urls = require_reconcile.generateConnectionUrls(result.services
|
|
15251
|
+
result.urls = require_reconcile.generateConnectionUrls(result.services);
|
|
15258
15252
|
logger$1.log(" 🔄 Adding missing service credentials: pgboss");
|
|
15259
15253
|
changed = true;
|
|
15260
15254
|
}
|
|
@@ -16178,7 +16172,7 @@ program.command("secrets:show").description("Show secrets for a stage").required
|
|
|
16178
16172
|
process.exit(1);
|
|
16179
16173
|
}
|
|
16180
16174
|
});
|
|
16181
|
-
program.command("secrets:rotate").description("Rotate service passwords").requiredOption("--stage <stage>", "Stage name").option("--service <service>", "Specific service to rotate (postgres, redis,
|
|
16175
|
+
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) => {
|
|
16182
16176
|
try {
|
|
16183
16177
|
const globalOptions = program.opts();
|
|
16184
16178
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -16233,8 +16227,8 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16233
16227
|
const globalOptions = program.opts();
|
|
16234
16228
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16235
16229
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
16236
|
-
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-
|
|
16237
|
-
const { readStageSecrets, writeStageSecrets } = await Promise.resolve().then(() => require("./storage-
|
|
16230
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-BxZTV2vl.cjs")).then((n) => n.reconcile_exports);
|
|
16231
|
+
const { readStageSecrets, writeStageSecrets } = await Promise.resolve().then(() => require("./storage-C2JC31V9.cjs")).then((n) => n.storage_exports);
|
|
16238
16232
|
const { workspace } = await loadWorkspaceConfig();
|
|
16239
16233
|
const stage = options.stage ?? workspace.stages.local;
|
|
16240
16234
|
const secrets = await readStageSecrets(stage, workspace.root);
|
|
@@ -16242,7 +16236,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16242
16236
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16243
16237
|
process.exit(1);
|
|
16244
16238
|
}
|
|
16245
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-
|
|
16239
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-B3ApLW6p.cjs")).then((n) => n.workspace_exports);
|
|
16246
16240
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16247
16241
|
if (!result) {
|
|
16248
16242
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16301,7 +16295,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16301
16295
|
try {
|
|
16302
16296
|
const globalOptions = program.opts();
|
|
16303
16297
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16304
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
16298
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-ZL3RcUVD.cjs"));
|
|
16305
16299
|
await deployGithubCommand(options);
|
|
16306
16300
|
} catch (error) {
|
|
16307
16301
|
console.error(formatError(error));
|