@idevconn/create-icore 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +140 -9
- package/dist/index.cjs +140 -9
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +140 -9
- package/package.json +1 -1
- package/templates/apps/templates/client-antd/src/components/layout/LayoutHeader.tsx +2 -2
- package/templates/apps/templates/client-mui/src/components/layout/LayoutHeader.tsx +1 -1
- package/templates/apps/templates/client-shadcn/src/components/layout/LayoutHeader.tsx +2 -2
- package/templates/apps/templates/client-shadcn/src/components/layout/LayoutSider.tsx +7 -3
- package/templates/apps/templates/client-shadcn/src/routes/index.tsx +2 -2
- package/templates/apps/templates/client-shadcn/vite.config.mts +18 -1
- package/templates/docker-compose.yml +14 -0
- package/templates/libs/db-strategies/postgres/eslint.config.mjs +30 -0
- package/templates/libs/db-strategies/postgres/package.json +19 -0
- package/templates/libs/db-strategies/postgres/project.json +19 -0
- package/templates/libs/db-strategies/postgres/src/index.ts +3 -0
- package/templates/libs/db-strategies/postgres/src/lib/__tests__/postgres-db.contract.unit.test.ts +4 -0
- package/templates/libs/db-strategies/postgres/src/lib/__tests__/postgres-db.module.unit.test.ts +37 -0
- package/templates/libs/db-strategies/postgres/src/lib/postgres-db.module.ts +33 -0
- package/templates/libs/db-strategies/postgres/src/lib/postgres-db.strategy.ts +139 -0
- package/templates/libs/db-strategies/postgres/src/lib/testing/mock-postgres.ts +94 -0
- package/templates/libs/db-strategies/postgres/tsconfig.json +19 -0
- package/templates/libs/db-strategies/postgres/tsconfig.lib.json +24 -0
- package/templates/libs/db-strategies/postgres/tsconfig.spec.json +22 -0
- package/templates/libs/db-strategies/postgres/vitest.config.mts +22 -0
- package/templates/tsconfig.base.json +1 -0
package/dist/index.js
CHANGED
|
@@ -78,6 +78,7 @@ async function rewriteRootPackageJson(targetDir, opts) {
|
|
|
78
78
|
const pkg = JSON.parse(raw);
|
|
79
79
|
pkg["name"] = opts.projectName;
|
|
80
80
|
pkg["version"] = "0.0.1";
|
|
81
|
+
pkg["icoreVersion"] = typeof ICORE_OWN_VERSION !== "undefined" ? ICORE_OWN_VERSION : "unknown";
|
|
81
82
|
pkg["private"] = true;
|
|
82
83
|
delete pkg.description;
|
|
83
84
|
const transportDeps = TRANSPORT_DEPS[opts.transport];
|
|
@@ -345,6 +346,7 @@ var AUTH_ONLY_PATHS = [
|
|
|
345
346
|
"apps/api/src/app/abilities",
|
|
346
347
|
"libs/shared/src/abilities",
|
|
347
348
|
"apps/client/src/components/auth",
|
|
349
|
+
"apps/client/src/components/AccessDeniedPage.tsx",
|
|
348
350
|
"apps/client/src/routes/login.tsx",
|
|
349
351
|
"apps/client/src/routes/auth.callback.tsx",
|
|
350
352
|
"apps/client/src/routes/auth.oauth.callback.tsx",
|
|
@@ -481,6 +483,121 @@ export * from './lib/draft/index.js';
|
|
|
481
483
|
export * from './lib/landing/LandingPage.js';
|
|
482
484
|
export * from './lib/stores/theme.store.js';
|
|
483
485
|
`;
|
|
486
|
+
var SHADCN_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
|
|
487
|
+
import { useTranslation } from 'react-i18next';
|
|
488
|
+
import { useDraft, useLoading } from '@icore/template-shared';
|
|
489
|
+
|
|
490
|
+
interface PageLayoutProps {
|
|
491
|
+
title: string;
|
|
492
|
+
description?: string;
|
|
493
|
+
actions?: ReactNode;
|
|
494
|
+
children: ReactNode;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function PageLayout({ title, description, actions, children }: PageLayoutProps) {
|
|
498
|
+
const { t } = useTranslation();
|
|
499
|
+
const isLoading = useLoading();
|
|
500
|
+
|
|
501
|
+
useDraft(false);
|
|
502
|
+
|
|
503
|
+
return (
|
|
504
|
+
<div className="p-4 md:p-6 space-y-4">
|
|
505
|
+
<div className="flex items-start justify-between gap-3">
|
|
506
|
+
<div>
|
|
507
|
+
<h1 className="text-xl font-semibold text-foreground">{title}</h1>
|
|
508
|
+
{description && <p className="text-sm text-muted-foreground mt-1">{description}</p>}
|
|
509
|
+
</div>
|
|
510
|
+
{actions && <div>{actions}</div>}
|
|
511
|
+
</div>
|
|
512
|
+
|
|
513
|
+
{isLoading && (
|
|
514
|
+
<div
|
|
515
|
+
role="status"
|
|
516
|
+
aria-label={t('common.loading')}
|
|
517
|
+
className="fixed inset-0 z-50 flex items-center justify-center bg-background/60 backdrop-blur-sm"
|
|
518
|
+
>
|
|
519
|
+
<div className="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
|
520
|
+
</div>
|
|
521
|
+
)}
|
|
522
|
+
|
|
523
|
+
{children}
|
|
524
|
+
</div>
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
`;
|
|
528
|
+
var ANTD_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
|
|
529
|
+
import { Descriptions, Spin } from 'antd';
|
|
530
|
+
import { useDraft, useLoading } from '@icore/template-shared';
|
|
531
|
+
|
|
532
|
+
export interface PageLayoutProps {
|
|
533
|
+
title: ReactNode;
|
|
534
|
+
description?: ReactNode;
|
|
535
|
+
extra?: ReactNode;
|
|
536
|
+
children?: ReactNode;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export function PageLayout({ title, description, extra, children }: PageLayoutProps) {
|
|
540
|
+
useDraft(false);
|
|
541
|
+
const loading = useLoading();
|
|
542
|
+
|
|
543
|
+
return (
|
|
544
|
+
<div style={{ padding: 24 }}>
|
|
545
|
+
<Descriptions title={title} extra={extra} style={{ marginBottom: 16 }}>
|
|
546
|
+
{description ? <Descriptions.Item>{description}</Descriptions.Item> : null}
|
|
547
|
+
</Descriptions>
|
|
548
|
+
<Spin spinning={loading}>
|
|
549
|
+
<div>{children}</div>
|
|
550
|
+
</Spin>
|
|
551
|
+
</div>
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
`;
|
|
555
|
+
var MUI_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
|
|
556
|
+
import { Box, LinearProgress, Stack, Typography } from '@mui/material';
|
|
557
|
+
import { useDraft, useLoading } from '@icore/template-shared';
|
|
558
|
+
|
|
559
|
+
export interface PageLayoutProps {
|
|
560
|
+
title: ReactNode;
|
|
561
|
+
description?: ReactNode;
|
|
562
|
+
extra?: ReactNode;
|
|
563
|
+
children?: ReactNode;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function PageLayout({ title, description, extra, children }: PageLayoutProps) {
|
|
567
|
+
useDraft(false);
|
|
568
|
+
const loading = useLoading();
|
|
569
|
+
|
|
570
|
+
return (
|
|
571
|
+
<Box sx={{ p: 3 }}>
|
|
572
|
+
<Stack
|
|
573
|
+
direction="row"
|
|
574
|
+
justifyContent="space-between"
|
|
575
|
+
alignItems="flex-start"
|
|
576
|
+
spacing={2}
|
|
577
|
+
mb={3}
|
|
578
|
+
>
|
|
579
|
+
<Box>
|
|
580
|
+
<Typography variant="h4" component="h1">
|
|
581
|
+
{title}
|
|
582
|
+
</Typography>
|
|
583
|
+
{description ? (
|
|
584
|
+
<Typography variant="body2" color="text.secondary" mt={0.5}>
|
|
585
|
+
{description}
|
|
586
|
+
</Typography>
|
|
587
|
+
) : null}
|
|
588
|
+
</Box>
|
|
589
|
+
{extra ? (
|
|
590
|
+
<Stack direction="row" spacing={1}>
|
|
591
|
+
{extra}
|
|
592
|
+
</Stack>
|
|
593
|
+
) : null}
|
|
594
|
+
</Stack>
|
|
595
|
+
{loading ? <LinearProgress sx={{ mb: 2 }} /> : null}
|
|
596
|
+
<Box>{children}</Box>
|
|
597
|
+
</Box>
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
`;
|
|
484
601
|
var SHADCN_MAIN_TSX = `import './globals.css';
|
|
485
602
|
import { StrictMode } from 'react';
|
|
486
603
|
import { createRoot } from 'react-dom/client';
|
|
@@ -581,7 +698,7 @@ export const Route = createFileRoute('/')({
|
|
|
581
698
|
),
|
|
582
699
|
});
|
|
583
700
|
`;
|
|
584
|
-
var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
|
|
701
|
+
var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale, i18next } from '@icore/template-shared';
|
|
585
702
|
import { ThemeToggle } from '../ThemeToggle';
|
|
586
703
|
|
|
587
704
|
const LOCALES: { code: IcoreLocale; label: string }[] = [
|
|
@@ -593,7 +710,7 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
|
|
|
593
710
|
export function LayoutHeader() {
|
|
594
711
|
function handleLocale(code: IcoreLocale) {
|
|
595
712
|
setStoredLocale(code);
|
|
596
|
-
|
|
713
|
+
void i18next.changeLanguage(code);
|
|
597
714
|
}
|
|
598
715
|
|
|
599
716
|
return (
|
|
@@ -727,7 +844,7 @@ export const Route = createFileRoute('/')({
|
|
|
727
844
|
});
|
|
728
845
|
`;
|
|
729
846
|
var ANTD_LAYOUT_HEADER_TSX = `import { Button, Layout, Space } from 'antd';
|
|
730
|
-
import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
|
|
847
|
+
import { setStoredLocale, type IcoreLocale, i18next } from '@icore/template-shared';
|
|
731
848
|
import { ThemeToggle } from '../ThemeToggle';
|
|
732
849
|
|
|
733
850
|
const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
|
|
@@ -741,7 +858,7 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
|
|
|
741
858
|
export function LayoutHeader() {
|
|
742
859
|
function handleLocale(code: IcoreLocale) {
|
|
743
860
|
setStoredLocale(code);
|
|
744
|
-
|
|
861
|
+
void i18next.changeLanguage(code);
|
|
745
862
|
}
|
|
746
863
|
|
|
747
864
|
return (
|
|
@@ -913,7 +1030,7 @@ export function LayoutHeader() {
|
|
|
913
1030
|
|
|
914
1031
|
function handleLocale(code: IcoreLocale) {
|
|
915
1032
|
setStoredLocale(code);
|
|
916
|
-
|
|
1033
|
+
void i18n.changeLanguage(code);
|
|
917
1034
|
}
|
|
918
1035
|
|
|
919
1036
|
return (
|
|
@@ -957,19 +1074,22 @@ var UI_VARIANTS = {
|
|
|
957
1074
|
"apps/client/src/main.tsx": SHADCN_MAIN_TSX,
|
|
958
1075
|
"apps/client/src/routes/_dashboard.tsx": SHADCN_DASHBOARD_TSX,
|
|
959
1076
|
"apps/client/src/routes/index.tsx": SHADCN_INDEX_TSX,
|
|
960
|
-
"apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX
|
|
1077
|
+
"apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX,
|
|
1078
|
+
"apps/client/src/components/PageLayout.tsx": SHADCN_PAGE_LAYOUT_TSX
|
|
961
1079
|
},
|
|
962
1080
|
antd: {
|
|
963
1081
|
"apps/client/src/main.tsx": ANTD_MAIN_TSX,
|
|
964
1082
|
"apps/client/src/routes/_dashboard.tsx": ANTD_DASHBOARD_TSX,
|
|
965
1083
|
"apps/client/src/routes/index.tsx": ANTD_INDEX_TSX,
|
|
966
|
-
"apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX
|
|
1084
|
+
"apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX,
|
|
1085
|
+
"apps/client/src/components/PageLayout.tsx": ANTD_PAGE_LAYOUT_TSX
|
|
967
1086
|
},
|
|
968
1087
|
mui: {
|
|
969
1088
|
"apps/client/src/main.tsx": MUI_MAIN_TSX,
|
|
970
1089
|
"apps/client/src/routes/_dashboard.tsx": MUI_DASHBOARD_TSX,
|
|
971
1090
|
"apps/client/src/routes/index.tsx": MUI_INDEX_TSX,
|
|
972
|
-
"apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX
|
|
1091
|
+
"apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX,
|
|
1092
|
+
"apps/client/src/components/PageLayout.tsx": MUI_PAGE_LAYOUT_TSX
|
|
973
1093
|
}
|
|
974
1094
|
};
|
|
975
1095
|
async function applyAuthNoneVariants(targetDir, ui) {
|
|
@@ -1086,6 +1206,16 @@ var MANIFEST = {
|
|
|
1086
1206
|
deps: { mongoose: "^9.6.3" },
|
|
1087
1207
|
tsPaths: { "@icore/db-mongodb": ["libs/db-strategies/mongodb/src/index.ts"] },
|
|
1088
1208
|
nestModule: { importFrom: "@icore/db-mongodb", symbol: "MongoDbDbModule", into: "notes" }
|
|
1209
|
+
},
|
|
1210
|
+
postgres: {
|
|
1211
|
+
libDirs: ["libs/db-strategies/postgres"],
|
|
1212
|
+
deps: { postgres: "^3" },
|
|
1213
|
+
tsPaths: { "@icore/db-postgres": ["libs/db-strategies/postgres/src/index.ts"] },
|
|
1214
|
+
nestModule: {
|
|
1215
|
+
importFrom: "@icore/db-postgres",
|
|
1216
|
+
symbol: "PostgresDbModule",
|
|
1217
|
+
into: "notes"
|
|
1218
|
+
}
|
|
1089
1219
|
}
|
|
1090
1220
|
},
|
|
1091
1221
|
feature: {
|
|
@@ -2084,7 +2214,8 @@ Re-run with @latest to refresh:
|
|
|
2084
2214
|
options: [
|
|
2085
2215
|
{ value: "supabase", label: "Supabase Postgres" },
|
|
2086
2216
|
{ value: "firebase", label: "Firestore" },
|
|
2087
|
-
{ value: "mongodb", label: "MongoDB" }
|
|
2217
|
+
{ value: "mongodb", label: "MongoDB" },
|
|
2218
|
+
{ value: "postgres", label: "PostgreSQL (direct, postgres.js)" }
|
|
2088
2219
|
],
|
|
2089
2220
|
initialValue: authProvider
|
|
2090
2221
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idevconn/create-icore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Bootstrap a new project from the iCore scaffold (Nx + NestJS + React + Vite + shadcn/Tailwind, swappable auth + storage providers).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "iDEVconn",
|
|
@@ -13,13 +13,13 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
|
|
|
13
13
|
];
|
|
14
14
|
|
|
15
15
|
export function LayoutHeader() {
|
|
16
|
-
const { t } = useTranslation();
|
|
16
|
+
const { t, i18n } = useTranslation();
|
|
17
17
|
const navigate = useNavigate();
|
|
18
18
|
const user = useAuthStore((s) => s.user);
|
|
19
19
|
|
|
20
20
|
function handleLocale(code: IcoreLocale) {
|
|
21
21
|
setStoredLocale(code);
|
|
22
|
-
|
|
22
|
+
void i18n.changeLanguage(code);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function handleLogout() {
|
|
@@ -12,14 +12,14 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
|
|
|
12
12
|
];
|
|
13
13
|
|
|
14
14
|
export function LayoutHeader() {
|
|
15
|
-
const { t } = useTranslation();
|
|
15
|
+
const { t, i18n } = useTranslation();
|
|
16
16
|
const navigate = useNavigate();
|
|
17
17
|
const user = useAuthStore((s) => s.user);
|
|
18
18
|
const logout = useAuthStore((s) => s.logout);
|
|
19
19
|
|
|
20
20
|
function handleLocale(code: IcoreLocale) {
|
|
21
21
|
setStoredLocale(code);
|
|
22
|
-
|
|
22
|
+
void i18n.changeLanguage(code);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function handleLogout() {
|
|
@@ -16,7 +16,7 @@ export function LayoutSider() {
|
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<aside
|
|
19
|
-
className={`relative flex flex-col border-
|
|
19
|
+
className={`relative flex flex-col border-e border-[--color-border] bg-[--color-card] transition-all duration-200 ${
|
|
20
20
|
collapsed ? 'w-14' : 'w-52'
|
|
21
21
|
}`}
|
|
22
22
|
>
|
|
@@ -41,9 +41,13 @@ export function LayoutSider() {
|
|
|
41
41
|
type="button"
|
|
42
42
|
onClick={() => setCollapsed((c) => !c)}
|
|
43
43
|
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
|
44
|
-
className="absolute -
|
|
44
|
+
className="absolute -end-3 top-5 z-10 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full border border-[--color-border] bg-[--color-card] text-[--color-muted-foreground] shadow-sm hover:text-[--color-foreground] transition-colors"
|
|
45
45
|
>
|
|
46
|
-
{collapsed ?
|
|
46
|
+
{collapsed ? (
|
|
47
|
+
<ChevronRight size={12} className="rtl:scale-x-[-1]" />
|
|
48
|
+
) : (
|
|
49
|
+
<ChevronLeft size={12} className="rtl:scale-x-[-1]" />
|
|
50
|
+
)}
|
|
47
51
|
</button>
|
|
48
52
|
</aside>
|
|
49
53
|
);
|
|
@@ -4,12 +4,12 @@ import { LandingPage } from '@icore/template-shared';
|
|
|
4
4
|
// All version strings are injected at build time by vite.config.mts
|
|
5
5
|
// (reads root package.json via fs.readFileSync so they stay accurate
|
|
6
6
|
// even when workspace packages are bumped independently).
|
|
7
|
-
const
|
|
7
|
+
const ICORE_VERSION = (import.meta.env.VITE_ICORE_VERSION as string | undefined) ?? '0.0.0-dev';
|
|
8
8
|
|
|
9
9
|
export const Route = createFileRoute('/')({
|
|
10
10
|
component: () => (
|
|
11
11
|
<LandingPage
|
|
12
|
-
coreVersion={
|
|
12
|
+
coreVersion={ICORE_VERSION}
|
|
13
13
|
uiLibrary="shadcn"
|
|
14
14
|
deps={[
|
|
15
15
|
{ name: 'react', version: (import.meta.env.VITE_DEP_REACT as string) ?? '?' },
|
|
@@ -19,12 +19,26 @@ import {
|
|
|
19
19
|
const rootPackageJsonPath = new URL('../../../package.json', import.meta.url);
|
|
20
20
|
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')) as {
|
|
21
21
|
version: string;
|
|
22
|
+
icoreVersion?: string;
|
|
23
|
+
dependencies?: Record<string, string>;
|
|
24
|
+
devDependencies?: Record<string, string>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const selfPackageJson = JSON.parse(
|
|
28
|
+
fs.readFileSync(new URL('./package.json', import.meta.url), 'utf-8'),
|
|
29
|
+
) as {
|
|
22
30
|
dependencies?: Record<string, string>;
|
|
23
31
|
devDependencies?: Record<string, string>;
|
|
24
32
|
};
|
|
25
33
|
|
|
26
34
|
function depVersion(name: string): string {
|
|
27
|
-
return
|
|
35
|
+
return (
|
|
36
|
+
rootPackageJson.dependencies?.[name] ??
|
|
37
|
+
rootPackageJson.devDependencies?.[name] ??
|
|
38
|
+
selfPackageJson.dependencies?.[name] ??
|
|
39
|
+
selfPackageJson.devDependencies?.[name] ??
|
|
40
|
+
'?'
|
|
41
|
+
);
|
|
28
42
|
}
|
|
29
43
|
|
|
30
44
|
export default defineConfig(() => ({
|
|
@@ -37,6 +51,9 @@ export default defineConfig(() => ({
|
|
|
37
51
|
},
|
|
38
52
|
define: {
|
|
39
53
|
...commonDefines(rootPackageJson),
|
|
54
|
+
'import.meta.env.VITE_ICORE_VERSION': JSON.stringify(
|
|
55
|
+
rootPackageJson.icoreVersion ?? rootPackageJson.version,
|
|
56
|
+
),
|
|
40
57
|
'import.meta.env.VITE_DEP_TAILWINDCSS': JSON.stringify(depVersion('tailwindcss')),
|
|
41
58
|
},
|
|
42
59
|
plugins: [
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16-alpine
|
|
4
|
+
environment:
|
|
5
|
+
POSTGRES_USER: ${POSTGRES_USER:-icore}
|
|
6
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-icore}
|
|
7
|
+
POSTGRES_DB: ${POSTGRES_DB:-icore}
|
|
8
|
+
ports:
|
|
9
|
+
- '5432:5432'
|
|
10
|
+
healthcheck:
|
|
11
|
+
test: ['CMD-SHELL', 'pg_isready -U icore']
|
|
12
|
+
interval: 5s
|
|
13
|
+
retries: 10
|
|
14
|
+
networks: [icore]
|
|
15
|
+
|
|
2
16
|
redis:
|
|
3
17
|
image: redis:7-alpine
|
|
4
18
|
healthcheck:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import baseConfig from '../../../eslint.config.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.json'],
|
|
7
|
+
rules: {
|
|
8
|
+
'@nx/dependency-checks': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
ignoredFiles: [
|
|
12
|
+
'{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}',
|
|
13
|
+
'{projectRoot}/vitest.config.{js,ts,mjs,mts}',
|
|
14
|
+
],
|
|
15
|
+
ignoredDependencies: [
|
|
16
|
+
'@icore/shared',
|
|
17
|
+
'postgres',
|
|
18
|
+
'@nestjs/common',
|
|
19
|
+
'@nestjs/config',
|
|
20
|
+
'@nestjs/testing',
|
|
21
|
+
'vitest',
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
languageOptions: {
|
|
27
|
+
parser: await import('jsonc-eslint-parser'),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@icore/db-postgres",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@icore/shared": "*",
|
|
10
|
+
"@nestjs/common": "^11.1.27",
|
|
11
|
+
"@nestjs/config": "^4.0.4",
|
|
12
|
+
"postgres": "^3.4.5",
|
|
13
|
+
"tslib": "^2.8.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@nestjs/testing": "^11.1.27",
|
|
17
|
+
"vitest": "^4.1.9"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "db-postgres",
|
|
3
|
+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "libs/db-strategies/postgres/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"targets": {
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "@nx/js:tsc",
|
|
10
|
+
"outputs": ["{options.outputPath}"],
|
|
11
|
+
"options": {
|
|
12
|
+
"outputPath": "dist/libs/db-strategies/postgres",
|
|
13
|
+
"main": "libs/db-strategies/postgres/src/index.ts",
|
|
14
|
+
"tsConfig": "libs/db-strategies/postgres/tsconfig.lib.json",
|
|
15
|
+
"assets": ["libs/db-strategies/postgres/*.md"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/templates/libs/db-strategies/postgres/src/lib/__tests__/postgres-db.module.unit.test.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Global, Module } from '@nestjs/common';
|
|
3
|
+
import { Test } from '@nestjs/testing';
|
|
4
|
+
import { ConfigService } from '@nestjs/config';
|
|
5
|
+
import { PostgresDbModule, POSTGRES_DB_REQUIRED_ENV } from '../postgres-db.module.js';
|
|
6
|
+
import { PostgresDBStrategy } from '../postgres-db.strategy.js';
|
|
7
|
+
|
|
8
|
+
let ENV: Record<string, string | undefined> = {};
|
|
9
|
+
|
|
10
|
+
@Global()
|
|
11
|
+
@Module({
|
|
12
|
+
providers: [
|
|
13
|
+
{
|
|
14
|
+
provide: ConfigService,
|
|
15
|
+
useValue: {
|
|
16
|
+
get: (k: string) => ENV[k],
|
|
17
|
+
getOrThrow: (k: string) => ENV[k],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
exports: [ConfigService],
|
|
22
|
+
})
|
|
23
|
+
class StubConfigModule {}
|
|
24
|
+
|
|
25
|
+
describe('PostgresDbModule', () => {
|
|
26
|
+
it('declares its required env', () => {
|
|
27
|
+
expect(POSTGRES_DB_REQUIRED_ENV).toEqual(['POSTGRES_URL']);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('provides a real PostgresDBStrategy under DBStrategy when env present', async () => {
|
|
31
|
+
ENV = { POSTGRES_URL: 'postgresql://user:pass@localhost:5432/test' };
|
|
32
|
+
const ref = await Test.createTestingModule({
|
|
33
|
+
imports: [StubConfigModule, PostgresDbModule.forRoot('.env')],
|
|
34
|
+
}).compile();
|
|
35
|
+
expect(ref.get('DBStrategy')).toBeInstanceOf(PostgresDBStrategy);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Module, DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { ConfigService } from '@nestjs/config';
|
|
3
|
+
import { buildStrategyWithFallback, FakeDBStrategy } from '@icore/shared';
|
|
4
|
+
import type { DBStrategy } from '@icore/shared';
|
|
5
|
+
import { PostgresDBStrategy } from './postgres-db.strategy';
|
|
6
|
+
|
|
7
|
+
export const POSTGRES_DB_REQUIRED_ENV = ['POSTGRES_URL'];
|
|
8
|
+
|
|
9
|
+
@Module({})
|
|
10
|
+
export class PostgresDbModule {
|
|
11
|
+
static forRoot(envPath: string): DynamicModule {
|
|
12
|
+
return {
|
|
13
|
+
module: PostgresDbModule,
|
|
14
|
+
providers: [
|
|
15
|
+
{
|
|
16
|
+
provide: 'DBStrategy',
|
|
17
|
+
useFactory: (cfg: ConfigService): DBStrategy =>
|
|
18
|
+
buildStrategyWithFallback<DBStrategy>({
|
|
19
|
+
service: 'notes MS',
|
|
20
|
+
provider: 'postgres',
|
|
21
|
+
requiredEnv: POSTGRES_DB_REQUIRED_ENV,
|
|
22
|
+
cfg,
|
|
23
|
+
envPath,
|
|
24
|
+
build: () => new PostgresDBStrategy(cfg.getOrThrow<string>('POSTGRES_URL')),
|
|
25
|
+
fake: () => new FakeDBStrategy(),
|
|
26
|
+
}),
|
|
27
|
+
inject: [ConfigService],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
exports: ['DBStrategy'],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|