@highstate/library 0.9.18 → 0.9.19
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/highstate.library.msgpack +0 -0
- package/dist/index.js +3467 -3083
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/common/access-point.ts +105 -0
- package/src/{files.ts → common/files.ts} +10 -8
- package/src/common/index.ts +3 -0
- package/src/{common.ts → common/server.ts} +69 -38
- package/src/databases/index.ts +4 -0
- package/src/databases/mariadb.ts +37 -0
- package/src/databases/mongodb.ts +37 -0
- package/src/databases/postgresql.ts +37 -0
- package/src/databases/shared.ts +61 -0
- package/src/distributions/ubuntu.ts +6 -4
- package/src/dns.ts +110 -12
- package/src/git.ts +7 -3
- package/src/impl-ref.ts +26 -0
- package/src/index.ts +14 -15
- package/src/k3s.ts +7 -5
- package/src/k8s/apps/code-server.ts +48 -0
- package/src/k8s/apps/gitea.ts +25 -0
- package/src/k8s/apps/grocy.ts +39 -0
- package/src/k8s/apps/hubble.ts +30 -0
- package/src/{apps → k8s/apps}/index.ts +16 -13
- package/src/k8s/apps/kubernetes-dashboard.ts +28 -0
- package/src/k8s/apps/mariadb.ts +83 -0
- package/src/k8s/apps/maybe.ts +39 -0
- package/src/k8s/apps/mongodb.ts +84 -0
- package/src/k8s/apps/postgresql.ts +86 -0
- package/src/k8s/apps/shared.ts +149 -0
- package/src/{apps → k8s/apps}/syncthing.ts +27 -9
- package/src/k8s/apps/traefik.ts +40 -0
- package/src/k8s/apps/vaultwarden.ts +31 -0
- package/src/k8s/apps/workload.ts +214 -0
- package/src/k8s/apps/zitadel.ts +26 -0
- package/src/k8s/cert-manager.ts +80 -0
- package/src/k8s/cilium.ts +64 -0
- package/src/k8s/gateway.ts +70 -0
- package/src/k8s/index.ts +9 -0
- package/src/{obfuscators → k8s/obfuscators}/phantun.ts +10 -6
- package/src/{obfuscators → k8s/obfuscators}/shared.ts +11 -5
- package/src/k8s/resources.ts +111 -0
- package/src/k8s/service.ts +65 -0
- package/src/{k8s.ts → k8s/shared.ts} +35 -329
- package/src/k8s/workload.ts +77 -0
- package/src/network.ts +208 -22
- package/src/nixos.ts +23 -8
- package/src/proxmox.ts +62 -75
- package/src/restic.ts +15 -6
- package/src/sops.ts +16 -5
- package/src/ssh.ts +107 -9
- package/src/talos.ts +6 -4
- package/src/third-party/cloudflare.ts +59 -0
- package/src/third-party/index.ts +3 -0
- package/src/{mullvad.ts → third-party/mullvad.ts} +6 -4
- package/src/third-party/timeweb.ts +99 -0
- package/src/utils.ts +24 -3
- package/src/wireguard.ts +171 -48
- package/src/apps/code-server.ts +0 -34
- package/src/apps/deployment.ts +0 -60
- package/src/apps/dns.ts +0 -107
- package/src/apps/gitea.ts +0 -18
- package/src/apps/grocy.ts +0 -20
- package/src/apps/hubble.ts +0 -20
- package/src/apps/kubernetes-dashboard.ts +0 -19
- package/src/apps/mariadb.ts +0 -81
- package/src/apps/maybe.ts +0 -25
- package/src/apps/mongodb.ts +0 -81
- package/src/apps/network.ts +0 -55
- package/src/apps/postgresql.ts +0 -81
- package/src/apps/shared.ts +0 -289
- package/src/apps/test.ts +0 -19
- package/src/apps/traefik.ts +0 -36
- package/src/apps/vaultwarden.ts +0 -23
- package/src/apps/zitadel.ts +0 -21
- package/src/cloudflare.ts +0 -26
- package/src/timeweb.ts +0 -75
- package/src/{obfuscators → k8s/obfuscators}/index.ts +1 -1
@@ -0,0 +1,80 @@
|
|
1
|
+
import { defineUnit, z } from "@highstate/contract"
|
2
|
+
import { tlsIssuerEntity } from "../common"
|
3
|
+
import * as dns from "../dns"
|
4
|
+
import { clusterEntity } from "./shared"
|
5
|
+
|
6
|
+
export const tlsIssuerDataSchema = z.object({
|
7
|
+
/**
|
8
|
+
* The Kubernetes cluster to use for creating gateway routes.
|
9
|
+
*/
|
10
|
+
cluster: clusterEntity.schema,
|
11
|
+
|
12
|
+
/**
|
13
|
+
* The name of the cluster issuer which should be used to issue TLS certificates.
|
14
|
+
*/
|
15
|
+
clusterIssuerName: z.string(),
|
16
|
+
})
|
17
|
+
|
18
|
+
/**
|
19
|
+
* The cert-manager installed on the Kubernetes cluster.
|
20
|
+
*/
|
21
|
+
export const certManager = defineUnit({
|
22
|
+
type: "k8s.cert-manager.v1",
|
23
|
+
|
24
|
+
args: {
|
25
|
+
/**
|
26
|
+
* Whether to enable the native support for Gateway API in cert-manager.
|
27
|
+
*
|
28
|
+
* Note that this can conflict with "Gateway API" unit since it is bringing its own CRDs.
|
29
|
+
*/
|
30
|
+
enableGatewayApi: z.boolean().default(false),
|
31
|
+
},
|
32
|
+
|
33
|
+
inputs: {
|
34
|
+
k8sCluster: clusterEntity,
|
35
|
+
},
|
36
|
+
|
37
|
+
outputs: {
|
38
|
+
k8sCluster: clusterEntity,
|
39
|
+
},
|
40
|
+
|
41
|
+
meta: {
|
42
|
+
title: "Cert Manager",
|
43
|
+
icon: "simple-icons:letsencrypt",
|
44
|
+
category: "Kubernetes",
|
45
|
+
},
|
46
|
+
|
47
|
+
source: {
|
48
|
+
package: "@highstate/k8s",
|
49
|
+
path: "units/cert-manager",
|
50
|
+
},
|
51
|
+
})
|
52
|
+
|
53
|
+
/**
|
54
|
+
* The DNS01 TLS issuer for issuing certificates using DNS01 challenge.
|
55
|
+
*/
|
56
|
+
export const dns01TlsIssuer = defineUnit({
|
57
|
+
type: "k8s.dns01-issuer.v1",
|
58
|
+
|
59
|
+
inputs: {
|
60
|
+
k8sCluster: clusterEntity,
|
61
|
+
dnsProvider: dns.providerEntity,
|
62
|
+
},
|
63
|
+
|
64
|
+
outputs: {
|
65
|
+
tlsIssuer: tlsIssuerEntity,
|
66
|
+
},
|
67
|
+
|
68
|
+
meta: {
|
69
|
+
title: "DNS01 Issuer",
|
70
|
+
icon: "mdi:certificate",
|
71
|
+
category: "Kubernetes",
|
72
|
+
},
|
73
|
+
|
74
|
+
source: {
|
75
|
+
package: "@highstate/k8s",
|
76
|
+
path: "units/dns01-issuer",
|
77
|
+
},
|
78
|
+
})
|
79
|
+
|
80
|
+
export type TlsIssuerData = z.infer<typeof tlsIssuerDataSchema>
|
@@ -0,0 +1,64 @@
|
|
1
|
+
import { defineUnit, z } from "@highstate/contract"
|
2
|
+
import { clusterEntity } from "./shared"
|
3
|
+
|
4
|
+
/**
|
5
|
+
* The Cilium CNI deployed on Kubernetes.
|
6
|
+
*/
|
7
|
+
export const cilium = defineUnit({
|
8
|
+
type: "k8s.cilium.v1",
|
9
|
+
|
10
|
+
args: {
|
11
|
+
/**
|
12
|
+
* If set to `true`, the generated network policy will allow
|
13
|
+
* all DNS queries to be resolved, even if they are
|
14
|
+
* for forbidden (non-allowed) FQDNs.
|
15
|
+
*
|
16
|
+
* By default, is `false`.
|
17
|
+
*/
|
18
|
+
allowForbiddenFqdnResolution: z.boolean().default(false),
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Whether to enable Hubble Relay and UI for observability.
|
22
|
+
*
|
23
|
+
* By default, this is `true`.
|
24
|
+
*
|
25
|
+
* To expose the Hubble UI, you can use `k8s.apps.hubble-ui` unit.
|
26
|
+
*/
|
27
|
+
enableHubble: z.boolean().default(true),
|
28
|
+
},
|
29
|
+
|
30
|
+
inputs: {
|
31
|
+
k8sCluster: clusterEntity,
|
32
|
+
},
|
33
|
+
|
34
|
+
outputs: {
|
35
|
+
k8sCluster: clusterEntity,
|
36
|
+
},
|
37
|
+
|
38
|
+
meta: {
|
39
|
+
title: "Cilium",
|
40
|
+
icon: "simple-icons:cilium",
|
41
|
+
secondaryIcon: "devicon:kubernetes",
|
42
|
+
category: "Kubernetes",
|
43
|
+
},
|
44
|
+
|
45
|
+
source: {
|
46
|
+
package: "@highstate/cilium",
|
47
|
+
path: "unit",
|
48
|
+
},
|
49
|
+
})
|
50
|
+
|
51
|
+
export const ciliumClusterMetadata = z.object({
|
52
|
+
cilium: z.object({
|
53
|
+
/**
|
54
|
+
* If set to `true`, the generated network policy will allow
|
55
|
+
* all DNS queries to be resolved, even if they are
|
56
|
+
* for forbidden (non-allowed) FQDNs.
|
57
|
+
*
|
58
|
+
* By default, is `false`.
|
59
|
+
*/
|
60
|
+
allowForbiddenFqdnResolution: z.boolean().default(false),
|
61
|
+
}),
|
62
|
+
})
|
63
|
+
|
64
|
+
export type CiliumClusterMetadata = z.infer<typeof ciliumClusterMetadata>
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import { defineUnit, z } from "@highstate/contract"
|
2
|
+
import { namespaceEntity } from "./resources"
|
3
|
+
import { clusterEntity } from "./shared"
|
4
|
+
|
5
|
+
export const gatewayDataSchema = z.object({
|
6
|
+
/**
|
7
|
+
* The Kubernetes cluster to use for creating gateway routes.
|
8
|
+
*/
|
9
|
+
cluster: clusterEntity.schema,
|
10
|
+
|
11
|
+
/**
|
12
|
+
* The namespace where the gateway controller of the class is running.
|
13
|
+
*/
|
14
|
+
namespace: namespaceEntity.schema,
|
15
|
+
|
16
|
+
/**
|
17
|
+
* The name of the gateway class to use.
|
18
|
+
*/
|
19
|
+
className: z.string(),
|
20
|
+
|
21
|
+
/**
|
22
|
+
* The port to use for HTTP in listener.
|
23
|
+
*
|
24
|
+
* If not provided, defaults to 80.
|
25
|
+
*/
|
26
|
+
httpPort: z.number().default(80),
|
27
|
+
|
28
|
+
/**
|
29
|
+
* The port to use for HTTPS in listener.
|
30
|
+
*
|
31
|
+
* If not provided, defaults to 443.
|
32
|
+
*/
|
33
|
+
httpsPort: z.number().default(443),
|
34
|
+
})
|
35
|
+
|
36
|
+
export const gatewayImplRefSchema = z.object({
|
37
|
+
package: z.literal("@highstate/k8s"),
|
38
|
+
data: gatewayDataSchema,
|
39
|
+
})
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Installs the Gateway API CRDs to the cluster.
|
43
|
+
*/
|
44
|
+
export const gatewayApi = defineUnit({
|
45
|
+
type: "k8s.gateway-api.v1",
|
46
|
+
|
47
|
+
inputs: {
|
48
|
+
k8sCluster: clusterEntity,
|
49
|
+
},
|
50
|
+
|
51
|
+
outputs: {
|
52
|
+
k8sCluster: clusterEntity,
|
53
|
+
},
|
54
|
+
|
55
|
+
meta: {
|
56
|
+
title: "Gateway API",
|
57
|
+
icon: "devicon:kubernetes",
|
58
|
+
secondaryIcon: "mdi:api",
|
59
|
+
secondaryIconColor: "#4CAF50",
|
60
|
+
category: "Kubernetes",
|
61
|
+
},
|
62
|
+
|
63
|
+
source: {
|
64
|
+
package: "@highstate/k8s",
|
65
|
+
path: "units/gateway-api",
|
66
|
+
},
|
67
|
+
})
|
68
|
+
|
69
|
+
export type GatewayData = z.infer<typeof gatewayDataSchema>
|
70
|
+
export type GatewayImplRef = z.infer<typeof gatewayImplRefSchema>
|
package/src/k8s/index.ts
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
export * as apps from "./apps"
|
2
|
+
export * from "./cert-manager"
|
3
|
+
export * from "./cilium"
|
4
|
+
export * from "./gateway"
|
5
|
+
export * as obfuscators from "./obfuscators"
|
6
|
+
export * from "./resources"
|
7
|
+
export * from "./service"
|
8
|
+
export * from "./shared"
|
9
|
+
export * from "./workload"
|
@@ -1,38 +1,42 @@
|
|
1
1
|
import { defineUnit } from "@highstate/contract"
|
2
2
|
import { deobfuscatorSpec, obfuscatorSpec } from "./shared"
|
3
3
|
|
4
|
+
/**
|
5
|
+
* The Phantun Deobfuscator deployed on Kubernetes.
|
6
|
+
*/
|
4
7
|
export const deobfuscator = defineUnit({
|
5
|
-
type: "obfuscators.phantun.deobfuscator",
|
8
|
+
type: "k8s.obfuscators.phantun.deobfuscator.v1",
|
6
9
|
...deobfuscatorSpec,
|
7
10
|
|
8
11
|
meta: {
|
9
12
|
title: "Phantun Deobfuscator",
|
10
|
-
description: "The Phantun Deobfuscator deployed on Kubernetes.",
|
11
13
|
icon: "mdi:network-outline",
|
12
14
|
secondaryIcon: "mdi:hide",
|
13
15
|
category: "Obfuscators",
|
14
16
|
},
|
15
17
|
|
16
18
|
source: {
|
17
|
-
package: "@highstate/obfuscators",
|
19
|
+
package: "@highstate/k8s.obfuscators",
|
18
20
|
path: "phantun/deobfuscator",
|
19
21
|
},
|
20
22
|
})
|
21
23
|
|
24
|
+
/**
|
25
|
+
* The Phantun Obfuscator deployed on Kubernetes.
|
26
|
+
*/
|
22
27
|
export const obfuscator = defineUnit({
|
23
|
-
type: "obfuscators.phantun.obfuscator",
|
28
|
+
type: "k8s.obfuscators.phantun.obfuscator.v1",
|
24
29
|
...obfuscatorSpec,
|
25
30
|
|
26
31
|
meta: {
|
27
32
|
title: "Phantun Obfuscator",
|
28
|
-
description: "The Phantun Obfuscator deployed on Kubernetes.",
|
29
33
|
icon: "mdi:network-outline",
|
30
34
|
secondaryIcon: "mdi:hide",
|
31
35
|
category: "Obfuscators",
|
32
36
|
},
|
33
37
|
|
34
38
|
source: {
|
35
|
-
package: "@highstate/obfuscators",
|
39
|
+
package: "@highstate/k8s.obfuscators",
|
36
40
|
path: "phantun/obfuscator",
|
37
41
|
},
|
38
42
|
})
|
@@ -1,6 +1,6 @@
|
|
1
|
-
import { $args, $inputs, $outputs, z } from "@highstate/contract"
|
2
|
-
import {
|
3
|
-
import {
|
1
|
+
import { $args, $inputs, $outputs, type FullComponentArgumentOptions, z } from "@highstate/contract"
|
2
|
+
import { l4EndpointEntity } from "../../network"
|
3
|
+
import { clusterEntity } from "../shared"
|
4
4
|
|
5
5
|
export const deobfuscatorSpec = {
|
6
6
|
args: $args({
|
@@ -109,5 +109,11 @@ export const obfuscatorSpec = {
|
|
109
109
|
}),
|
110
110
|
}
|
111
111
|
|
112
|
-
|
113
|
-
|
112
|
+
type ArgsFromSpec<T extends Record<string, FullComponentArgumentOptions>> = z.infer<
|
113
|
+
z.ZodObject<{
|
114
|
+
[K in keyof T]: T[K]["schema"]
|
115
|
+
}>
|
116
|
+
>
|
117
|
+
|
118
|
+
export type DeobfuscatorArgs = ArgsFromSpec<typeof deobfuscatorSpec.args>
|
119
|
+
export type ObfuscatorArgs = ArgsFromSpec<typeof obfuscatorSpec.args>
|
@@ -0,0 +1,111 @@
|
|
1
|
+
import { defineEntity, z } from "@highstate/contract"
|
2
|
+
|
3
|
+
/**
|
4
|
+
* The generic metadata schema for Kubernetes resources.
|
5
|
+
*/
|
6
|
+
export const metadataSchema = z.object({
|
7
|
+
name: z.string(),
|
8
|
+
labels: z.record(z.string(), z.string()).optional(),
|
9
|
+
annotations: z.record(z.string(), z.string()).optional(),
|
10
|
+
uid: z.string(),
|
11
|
+
})
|
12
|
+
|
13
|
+
/**
|
14
|
+
* The metadata schema for Kubernetes resources that are scoped to a namespace.
|
15
|
+
*
|
16
|
+
* It includes the namespace field.
|
17
|
+
*/
|
18
|
+
export const scopedMetadataSchema = z.object({
|
19
|
+
...metadataSchema.shape,
|
20
|
+
namespace: z.string(),
|
21
|
+
})
|
22
|
+
|
23
|
+
/**
|
24
|
+
* The base schema for Kubernetes resources.
|
25
|
+
*
|
26
|
+
* It includes the cluster ID and name, which are required for all Kubernetes resources.
|
27
|
+
*/
|
28
|
+
export const resourceSchema = z.object({
|
29
|
+
clusterId: z.string(),
|
30
|
+
clusterName: z.string(),
|
31
|
+
type: z.string(),
|
32
|
+
metadata: metadataSchema,
|
33
|
+
})
|
34
|
+
|
35
|
+
/**
|
36
|
+
* The schema for Kubernetes resources that are scoped to a namespace.
|
37
|
+
*
|
38
|
+
* Extends the base resource schema with the scoped metadata.
|
39
|
+
*/
|
40
|
+
export const scopedResourceSchema = z.object({
|
41
|
+
...resourceSchema.shape,
|
42
|
+
metadata: scopedMetadataSchema,
|
43
|
+
})
|
44
|
+
|
45
|
+
/**
|
46
|
+
* The entity which represents a Kubernetes namespace managed by Highstate.
|
47
|
+
*/
|
48
|
+
export const namespaceEntity = defineEntity({
|
49
|
+
type: "k8s.namespace.v1",
|
50
|
+
|
51
|
+
schema: z.object({
|
52
|
+
...resourceSchema.shape,
|
53
|
+
type: z.literal("namespace"),
|
54
|
+
}),
|
55
|
+
|
56
|
+
meta: {
|
57
|
+
color: "#9E9E9E",
|
58
|
+
},
|
59
|
+
})
|
60
|
+
|
61
|
+
/**
|
62
|
+
* The entity which represents a Kubernetes persistent volume claim managed by Highstate.
|
63
|
+
*/
|
64
|
+
export const persistentVolumeClaimEntity = defineEntity({
|
65
|
+
type: "k8s.persistent-volume-claim.v1",
|
66
|
+
|
67
|
+
schema: z.object({
|
68
|
+
...scopedResourceSchema.shape,
|
69
|
+
type: z.literal("persistent-volume-claim"),
|
70
|
+
}),
|
71
|
+
|
72
|
+
meta: {
|
73
|
+
color: "#FFC107",
|
74
|
+
},
|
75
|
+
})
|
76
|
+
|
77
|
+
/**
|
78
|
+
* The entity which represents a Gateway resource from the Gateway API.
|
79
|
+
*/
|
80
|
+
export const gatewayEntity = defineEntity({
|
81
|
+
type: "k8s.gateway.v1",
|
82
|
+
|
83
|
+
schema: z.object({
|
84
|
+
...scopedResourceSchema.shape,
|
85
|
+
type: z.literal("gateway"),
|
86
|
+
}),
|
87
|
+
|
88
|
+
meta: {
|
89
|
+
color: "#4CAF50",
|
90
|
+
},
|
91
|
+
})
|
92
|
+
|
93
|
+
export const certificateEntity = defineEntity({
|
94
|
+
type: "k8s.certificate.v1",
|
95
|
+
|
96
|
+
schema: z.object({
|
97
|
+
...scopedResourceSchema.shape,
|
98
|
+
type: z.literal("certificate"),
|
99
|
+
}),
|
100
|
+
})
|
101
|
+
|
102
|
+
export type Metadata = z.infer<typeof metadataSchema>
|
103
|
+
export type Resource = z.infer<typeof resourceSchema>
|
104
|
+
|
105
|
+
export type ScopedMetadata = z.infer<typeof scopedMetadataSchema>
|
106
|
+
export type ScopedResource = z.infer<typeof scopedResourceSchema>
|
107
|
+
|
108
|
+
export type Namespace = z.infer<typeof namespaceEntity.schema>
|
109
|
+
export type PersistentVolumeClaim = z.infer<typeof persistentVolumeClaimEntity.schema>
|
110
|
+
export type Gateway = z.infer<typeof gatewayEntity.schema>
|
111
|
+
export type Certificate = z.infer<typeof certificateEntity.schema>
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import { defineEntity, z } from "@highstate/contract"
|
2
|
+
import { l4EndpointEntity } from "../network"
|
3
|
+
import { scopedResourceSchema } from "./resources"
|
4
|
+
|
5
|
+
export const endpointServiceMetadataSchema = z.object({
|
6
|
+
"k8s.service": z.object({
|
7
|
+
/**
|
8
|
+
* The ID of the cluster where the service is located.
|
9
|
+
*/
|
10
|
+
clusterId: z.string(),
|
11
|
+
|
12
|
+
/**
|
13
|
+
* The name of the cluster where the service is located.
|
14
|
+
*/
|
15
|
+
clusterName: z.string(),
|
16
|
+
|
17
|
+
/**
|
18
|
+
* The name of the service.
|
19
|
+
*/
|
20
|
+
name: z.string(),
|
21
|
+
|
22
|
+
/**
|
23
|
+
* The namespace of the service.
|
24
|
+
*/
|
25
|
+
namespace: z.string(),
|
26
|
+
|
27
|
+
/**
|
28
|
+
* The selector of the service.
|
29
|
+
*/
|
30
|
+
selector: z.record(z.string(), z.string()),
|
31
|
+
|
32
|
+
/**
|
33
|
+
* The target port of the service.
|
34
|
+
*/
|
35
|
+
targetPort: z.union([z.string(), z.number()]),
|
36
|
+
}),
|
37
|
+
})
|
38
|
+
|
39
|
+
export const serviceEndpointSchema = z.intersection(
|
40
|
+
l4EndpointEntity.schema,
|
41
|
+
z.object({
|
42
|
+
metadata: endpointServiceMetadataSchema,
|
43
|
+
}),
|
44
|
+
)
|
45
|
+
|
46
|
+
export const serviceEntity = defineEntity({
|
47
|
+
type: "k8s.service.v1",
|
48
|
+
|
49
|
+
schema: z.object({
|
50
|
+
...scopedResourceSchema.shape,
|
51
|
+
type: z.literal("service"),
|
52
|
+
endpoints: serviceEndpointSchema.array(),
|
53
|
+
}),
|
54
|
+
|
55
|
+
meta: {
|
56
|
+
color: "#2196F3",
|
57
|
+
},
|
58
|
+
})
|
59
|
+
|
60
|
+
export const serviceTypeSchema = z.enum(["NodePort", "LoadBalancer", "ClusterIP"])
|
61
|
+
|
62
|
+
export type EndpointServiceMetadata = z.infer<typeof endpointServiceMetadataSchema>
|
63
|
+
export type ServiceEndpoint = z.infer<typeof serviceEndpointSchema>
|
64
|
+
export type ServiceType = z.infer<typeof serviceTypeSchema>
|
65
|
+
export type Service = z.infer<typeof serviceEntity.schema>
|